diff --git a/ILSpy.Tests/Analyzers/AnalyzerTreeKeyboardTests.cs b/ILSpy.Tests/Analyzers/AnalyzerTreeKeyboardTests.cs index 517418ad3..ccba92f1f 100644 --- a/ILSpy.Tests/Analyzers/AnalyzerTreeKeyboardTests.cs +++ b/ILSpy.Tests/Analyzers/AnalyzerTreeKeyboardTests.cs @@ -16,6 +16,7 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using System.Linq; using System.Threading.Tasks; using Avalonia.Controls; @@ -68,4 +69,36 @@ public class AnalyzerTreeKeyboardTests await Waiters.WaitForAsync(() => analyzed.IsExpanded, description: "Right must expand the node via the shared TreeKeyboardController on the analyzer tree"); } + + [AvaloniaTest] + public async Task Ctrl_R_Analyzes_The_Selected_Member() + { + // Ctrl+R on the assembly tree analyzes the selected member(s) -- the keyboard equivalent of the + // Analyze context-menu entry. Here a selected type lands as a node in the analyzer pane. + var (window, vm) = await TestHarness.BootAsync(3); + var analyzerVm = AppComposition.Current.GetExport(); + var pane = await window.WaitForComponent(); + var tree = await pane.WaitForComponent(); + + var typeNode = vm.AssemblyTreeModel.FindNode( + "System.Linq", "System.Linq", "System.Linq.Enumerable"); + vm.AssemblyTreeModel.SelectNode(typeNode); + for (int i = 0; i < 6; i++) + { + Dispatcher.UIThread.RunJobs(); + tree.UpdateLayout(); + await Task.Delay(20); + } + tree.Focus(); + Dispatcher.UIThread.RunJobs(); + + int before = analyzerVm.Root.Children.Count; + window.KeyPress(Key.R, RawInputModifiers.Control, PhysicalKey.R, null); + await Waiters.WaitForAsync(() => analyzerVm.Root.Children.Count > before, + description: "Ctrl+R must add an analyzer node for the selected member"); + + analyzerVm.Root.Children.OfType() + .Any(n => n.Member is { } m && m.MetadataToken == ((ITypeDefinition)typeNode.Member!).MetadataToken) + .Should().BeTrue("the analyzer pane must hold a node for the type that Ctrl+R analyzed"); + } } diff --git a/ILSpy.Tests/ContextMenus/DecompileInNewViewTests.cs b/ILSpy.Tests/ContextMenus/DecompileInNewViewTests.cs index 7b164789b..2d3007682 100644 --- a/ILSpy.Tests/ContextMenus/DecompileInNewViewTests.cs +++ b/ILSpy.Tests/ContextMenus/DecompileInNewViewTests.cs @@ -286,6 +286,50 @@ public class DecompileInNewViewTests "B's highlight must be gone"); } + [AvaloniaTest] + public async Task Middle_Clicking_A_Row_Opens_It_In_A_New_Tab() + { + // Middle mouse button on a tree row opens that node in a fresh document tab (mirrors the + // "Decompile to new tab" gesture without going through the context menu). + var window = AppComposition.Current.GetExport(); + window.Show(); + var vm = (MainWindowViewModel)window.DataContext!; + await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3); + var pane = await window.WaitForComponent(); + var grid = await pane.WaitForComponent(); + + var nodeA = vm.AssemblyTreeModel.FindNode("System.Linq"); + var nodeB = vm.AssemblyTreeModel.FindNode(TreeNavigation.CoreLibName); + vm.AssemblyTreeModel.SelectNode(nodeA); + for (int i = 0; i < 8; i++) + { + Dispatcher.UIThread.RunJobs(); + grid.UpdateLayout(); + await Task.Delay(25); + } + + var rowB = grid.GetVisualDescendants().OfType() + .First(r => RowNodeEquals(r, nodeB)); + int tabsBefore = vm.DockWorkspace.Documents!.VisibleDockables!.OfType().Count(); + + var clickX = System.Math.Min(rowB.Bounds.Width, grid.Bounds.Width) / 2; + var point = rowB.TranslatePoint(new Point(clickX, rowB.Bounds.Height / 2), window)!.Value; + HeadlessWindowExtensions.MouseDown(window, point, MouseButton.Middle); + HeadlessWindowExtensions.MouseUp(window, point, MouseButton.Middle); + for (int i = 0; i < 8; i++) + { + Dispatcher.UIThread.RunJobs(); + grid.UpdateLayout(); + await Task.Delay(20); + } + + vm.DockWorkspace.Documents!.VisibleDockables!.OfType().Count() + .Should().BeGreaterThan(tabsBefore, "middle-clicking a row must open it in a new document tab"); + // Activating the new tab pulls the tree selection across to its node. + ReferenceEquals(vm.AssemblyTreeModel.SelectedItem, nodeB).Should().BeTrue( + "the new tab opened by middle-click must be sourced from the middle-clicked node"); + } + [AvaloniaTest] public async Task Opening_A_Node_In_A_New_Tab_Syncs_The_Tree_Selection_To_It() {