@ -181,12 +181,7 @@ public class DecompileInNewViewTests
vm . AssemblyTreeModel . SelectNode ( nodeA ) ;
vm . AssemblyTreeModel . SelectNode ( nodeA ) ;
// Let the top-level rows realise and layout settle.
// Let the top-level rows realise and layout settle.
for ( int i = 0 ; i < 8 ; i + + )
await Waiters . WaitForIdleAsync ( ) ;
{
Dispatcher . UIThread . RunJobs ( ) ;
grid . UpdateLayout ( ) ;
await Task . Delay ( 2 5 ) ;
}
var rowB = grid . GetVisualDescendants ( ) . OfType < ICSharpCode . ILSpy . Controls . TreeView . SharpTreeViewItem > ( )
var rowB = grid . GetVisualDescendants ( ) . OfType < ICSharpCode . ILSpy . Controls . TreeView . SharpTreeViewItem > ( )
. FirstOrDefault ( r = > RowNodeEquals ( r , nodeB ) ) ;
. FirstOrDefault ( r = > RowNodeEquals ( r , nodeB ) ) ;
@ -200,16 +195,9 @@ public class DecompileInNewViewTests
// Right-click the centre of B's row (clear of the far-left expander glyph). Tree rows
// Right-click the centre of B's row (clear of the far-left expander glyph). Tree rows
// stretch to content width, so clamp X to the visible grid viewport.
// stretch to content width, so clamp X to the visible grid viewport.
var clickX = System . Math . Min ( rowB ! . Bounds . Width , grid . Bounds . Width ) / 2 ;
await window . ClickAsync ( ( ) = > rowB , MouseButton . Right ,
var point = rowB . TranslatePoint ( new Point ( clickX , rowB . Bounds . Height / 2 ) , window ) ;
pointInTarget : r = > new Point ( System . Math . Min ( r . Bounds . Width , grid . Bounds . Width ) / 2 , r . Bounds . Height / 2 ) ) ;
point . Should ( ) . NotBeNull ( ) ;
await Waiters . WaitForIdleAsync ( ) ;
HeadlessWindowExtensions . MouseDown ( window , point ! . Value , MouseButton . Right ) ;
HeadlessWindowExtensions . MouseUp ( window , point . Value , MouseButton . Right ) ;
for ( int i = 0 ; i < 4 ; i + + )
{
Dispatcher . UIThread . RunJobs ( ) ;
await Task . Delay ( 2 0 ) ;
}
ReferenceEquals ( vm . AssemblyTreeModel . SelectedItem , nodeA ) . Should ( ) . BeTrue (
ReferenceEquals ( vm . AssemblyTreeModel . SelectedItem , nodeA ) . Should ( ) . BeTrue (
"right-clicking an unselected row must not change the selection (Thunderbird-style context target)" ) ;
"right-clicking an unselected row must not change the selection (Thunderbird-style context target)" ) ;
@ -239,12 +227,7 @@ public class DecompileInNewViewTests
var nodeC = assemblies [ 2 ] ;
var nodeC = assemblies [ 2 ] ;
vm . AssemblyTreeModel . SelectNode ( nodeA ) ;
vm . AssemblyTreeModel . SelectNode ( nodeA ) ;
for ( int i = 0 ; i < 8 ; i + + )
await Waiters . WaitForIdleAsync ( ) ;
{
Dispatcher . UIThread . RunJobs ( ) ;
grid . UpdateLayout ( ) ;
await Task . Delay ( 2 5 ) ;
}
ICSharpCode . ILSpy . Controls . TreeView . SharpTreeViewItem Row ( SharpTreeNode node ) = > grid . GetVisualDescendants ( )
ICSharpCode . ILSpy . Controls . TreeView . SharpTreeViewItem Row ( SharpTreeNode node ) = > grid . GetVisualDescendants ( )
. OfType < ICSharpCode . ILSpy . Controls . TreeView . SharpTreeViewItem > ( )
. OfType < ICSharpCode . ILSpy . Controls . TreeView . SharpTreeViewItem > ( )
@ -252,43 +235,16 @@ public class DecompileInNewViewTests
var menu = grid . ContextMenu ! ;
var menu = grid . ContextMenu ! ;
Point ? ClickPoint ( SharpTreeNode node )
{
var row = grid . GetVisualDescendants ( )
. OfType < ICSharpCode . ILSpy . Controls . TreeView . SharpTreeViewItem > ( )
. FirstOrDefault ( r = > RowNodeEquals ( r , node ) ) ;
if ( row = = null )
return null ;
var clickX = System . Math . Min ( row . Bounds . Width , grid . Bounds . Width ) / 2 ;
return row . TranslatePoint ( new Point ( clickX , row . Bounds . Height / 2 ) , window ) ;
}
async Task RightClick ( SharpTreeNode node )
async Task RightClick ( SharpTreeNode node )
{
{
// A press only becomes a context request for the row when the hit test at the press
// Tree rows stretch to content width, so clamp X to the visible grid viewport.
// point answers with that row - the same question OnTreeContextRequested asks - and it
await window . ClickAsync (
// takes an unknown number of frames for that to hold: a closed popup's light-dismiss
( ) = > grid . GetVisualDescendants ( )
// overlay keeps answering hit tests until the scene is rendered again, and assemblies
. OfType < ICSharpCode . ILSpy . Controls . TreeView . SharpTreeViewItem > ( )
// still loading in the background reshuffle the rows, which re-realises containers and
. FirstOrDefault ( r = > RowNodeEquals ( r , node ) ) ,
// shifts them. So the row container and the point are resolved afresh on every poll and
MouseButton . Right ,
// the hit is matched by node, not by container identity.
pointInTarget : row = > new Point ( System . Math . Min ( row . Bounds . Width , grid . Bounds . Width ) / 2 , row . Bounds . Height / 2 ) ,
Point ? pt = null ;
description : $"the row for {node}" ) ;
Visual ? lastHit = null ;
try
{
await Waiters . WaitForAsync (
( ) = > ( pt = ClickPoint ( node ) ) is { } p
& & ( lastHit = window . InputHitTest ( p ) as Visual ) is { } hit
& & hit . FindAncestorOfType < ICSharpCode . ILSpy . Controls . TreeView . SharpTreeViewItem > ( includeSelf : true ) is { } hitRow
& & RowNodeEquals ( hitRow , node ) ,
description : "the row to answer hit tests at the point about to be right-clicked" ) ;
}
catch ( System . TimeoutException ex )
{
throw new System . TimeoutException ( $"{ex.Message} (point: {pt?.ToString() ?? " row not realised "}, hit: {lastHit?.GetType().Name ?? " nothing "})" , ex ) ;
}
HeadlessWindowExtensions . MouseDown ( window , pt ! . Value , MouseButton . Right ) ;
HeadlessWindowExtensions . MouseUp ( window , pt . Value , MouseButton . Right ) ;
// The highlight is scoped to the popup - set while the menu is being requested, dropped
// The highlight is scoped to the popup - set while the menu is being requested, dropped
// again when it closes - so the popup is the point at which the gesture is finished and
// again when it closes - so the popup is the point at which the gesture is finished and
// the row's classes are worth reading.
// the row's classes are worth reading.
@ -328,27 +284,15 @@ public class DecompileInNewViewTests
var nodeA = vm . AssemblyTreeModel . FindNode < AssemblyTreeNode > ( "System.Linq" ) ;
var nodeA = vm . AssemblyTreeModel . FindNode < AssemblyTreeNode > ( "System.Linq" ) ;
var nodeB = vm . AssemblyTreeModel . FindNode < AssemblyTreeNode > ( TreeNavigation . CoreLibName ) ;
var nodeB = vm . AssemblyTreeModel . FindNode < AssemblyTreeNode > ( TreeNavigation . CoreLibName ) ;
vm . AssemblyTreeModel . SelectNode ( nodeA ) ;
vm . AssemblyTreeModel . SelectNode ( nodeA ) ;
for ( int i = 0 ; i < 8 ; i + + )
await Waiters . WaitForIdleAsync ( ) ;
{
Dispatcher . UIThread . RunJobs ( ) ;
grid . UpdateLayout ( ) ;
await Task . Delay ( 2 5 ) ;
}
var rowB = grid . GetVisualDescendants ( ) . OfType < ICSharpCode . ILSpy . Controls . TreeView . SharpTreeViewItem > ( )
var rowB = grid . GetVisualDescendants ( ) . OfType < ICSharpCode . ILSpy . Controls . TreeView . SharpTreeViewItem > ( )
. First ( r = > RowNodeEquals ( r , nodeB ) ) ;
. First ( r = > RowNodeEquals ( r , nodeB ) ) ;
int tabsBefore = vm . DockWorkspace . Documents ! . VisibleDockables ! . OfType < ContentTabPage > ( ) . Count ( ) ;
int tabsBefore = vm . DockWorkspace . Documents ! . VisibleDockables ! . OfType < ContentTabPage > ( ) . Count ( ) ;
var clickX = System . Math . Min ( rowB . Bounds . Width , grid . Bounds . Width ) / 2 ;
await window . ClickAsync ( ( ) = > rowB , MouseButton . Middle ,
var point = rowB . TranslatePoint ( new Point ( clickX , rowB . Bounds . Height / 2 ) , window ) ! . Value ;
pointInTarget : r = > new Point ( System . Math . Min ( r . Bounds . Width , grid . Bounds . Width ) / 2 , r . Bounds . Height / 2 ) ) ;
HeadlessWindowExtensions . MouseDown ( window , point , MouseButton . Middle ) ;
await Waiters . WaitForIdleAsync ( ) ;
HeadlessWindowExtensions . MouseUp ( window , point , MouseButton . Middle ) ;
for ( int i = 0 ; i < 8 ; i + + )
{
Dispatcher . UIThread . RunJobs ( ) ;
grid . UpdateLayout ( ) ;
await Task . Delay ( 2 0 ) ;
}
vm . DockWorkspace . Documents ! . VisibleDockables ! . OfType < ContentTabPage > ( ) . Count ( )
vm . DockWorkspace . Documents ! . VisibleDockables ! . OfType < ContentTabPage > ( ) . Count ( )
. Should ( ) . BeGreaterThan ( tabsBefore , "middle-clicking a row must open it in a new document tab" ) ;
. Should ( ) . BeGreaterThan ( tabsBefore , "middle-clicking a row must open it in a new document tab" ) ;
@ -376,23 +320,13 @@ public class DecompileInNewViewTests
vm . AssemblyTreeModel . SelectNode ( nodeA ) ;
vm . AssemblyTreeModel . SelectNode ( nodeA ) ;
await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
for ( int i = 0 ; i < 6 ; i + + )
await Waiters . WaitForIdleAsync ( ) ;
{
Dispatcher . UIThread . RunJobs ( ) ;
grid . UpdateLayout ( ) ;
await Task . Delay ( 2 0 ) ;
}
var registry = AppComposition . Current . GetExport < ContextMenuEntryRegistry > ( ) ;
var registry = AppComposition . Current . GetExport < ContextMenuEntryRegistry > ( ) ;
var menu = pane . BuildContextMenuForCurrentState ( registry . Entries , rightClickedNode : nodeB ) ;
var menu = pane . BuildContextMenuForCurrentState ( registry . Entries , rightClickedNode : nodeB ) ;
menu ! . ClickItem ( Resources . DecompileToNewPanel ) ;
menu ! . ClickItem ( Resources . DecompileToNewPanel ) ;
await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
for ( int i = 0 ; i < 6 ; i + + )
await Waiters . WaitForIdleAsync ( ) ;
{
Dispatcher . UIThread . RunJobs ( ) ;
grid . UpdateLayout ( ) ;
await Task . Delay ( 2 0 ) ;
}
// The model selection follows the new active tab...
// The model selection follows the new active tab...
ReferenceEquals ( vm . AssemblyTreeModel . SelectedItem , nodeB ) . Should ( ) . BeTrue (
ReferenceEquals ( vm . AssemblyTreeModel . SelectedItem , nodeB ) . Should ( ) . BeTrue (
@ -428,12 +362,7 @@ public class DecompileInNewViewTests
vm . AssemblyTreeModel . SelectedItems . Add ( nodeA ) ;
vm . AssemblyTreeModel . SelectedItems . Add ( nodeA ) ;
vm . AssemblyTreeModel . SelectedItems . Add ( nodeB ) ;
vm . AssemblyTreeModel . SelectedItems . Add ( nodeB ) ;
await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
for ( int i = 0 ; i < 6 ; i + + )
await Waiters . WaitForIdleAsync ( ) ;
{
Dispatcher . UIThread . RunJobs ( ) ;
grid . UpdateLayout ( ) ;
await Task . Delay ( 2 0 ) ;
}
vm . AssemblyTreeModel . SelectedItems . Count . Should ( ) . Be ( 2 , "precondition: a multi-selection is held" ) ;
vm . AssemblyTreeModel . SelectedItems . Count . Should ( ) . Be ( 2 , "precondition: a multi-selection is held" ) ;
// Open C in a new tab -> activates it -> the tree must follow to C.
// Open C in a new tab -> activates it -> the tree must follow to C.
@ -441,12 +370,7 @@ public class DecompileInNewViewTests
var menu = pane . BuildContextMenuForCurrentState ( registry . Entries , rightClickedNode : nodeC ) ;
var menu = pane . BuildContextMenuForCurrentState ( registry . Entries , rightClickedNode : nodeC ) ;
menu ! . ClickItem ( Resources . DecompileToNewPanel ) ;
menu ! . ClickItem ( Resources . DecompileToNewPanel ) ;
await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
await vm . DockWorkspace . WaitForDecompiledTextAsync ( ) ;
for ( int i = 0 ; i < 6 ; i + + )
await Waiters . WaitForIdleAsync ( ) ;
{
Dispatcher . UIThread . RunJobs ( ) ;
grid . UpdateLayout ( ) ;
await Task . Delay ( 2 0 ) ;
}
ReferenceEquals ( vm . AssemblyTreeModel . SelectedItem , nodeC ) . Should ( ) . BeTrue (
ReferenceEquals ( vm . AssemblyTreeModel . SelectedItem , nodeC ) . Should ( ) . BeTrue (
"the tree model selection must follow the newly-activated single-node tab" ) ;
"the tree model selection must follow the newly-activated single-node tab" ) ;
@ -494,12 +418,7 @@ public class DecompileInNewViewTests
// 3) Re-activate the multi-node tab.
// 3) Re-activate the multi-node tab.
vm . DockWorkspace . Factory . SetActiveDockable ( multiTab ) ;
vm . DockWorkspace . Factory . SetActiveDockable ( multiTab ) ;
for ( int i = 0 ; i < 8 ; i + + )
await Waiters . WaitForIdleAsync ( ) ;
{
Dispatcher . UIThread . RunJobs ( ) ;
grid . UpdateLayout ( ) ;
await Task . Delay ( 2 0 ) ;
}
// 4) The tree selection must contain BOTH original nodes again -- in the model...
// 4) The tree selection must contain BOTH original nodes again -- in the model...
vm . AssemblyTreeModel . SelectedItems . Should ( ) . Contain ( nodeA )
vm . AssemblyTreeModel . SelectedItems . Should ( ) . Contain ( nodeA )