diff --git a/ILSpy.Tests/Analyzers/AnalyzeContextMenuTests.cs b/ILSpy.Tests/Analyzers/AnalyzeContextMenuTests.cs index 6644423f7..30cba6e64 100644 --- a/ILSpy.Tests/Analyzers/AnalyzeContextMenuTests.cs +++ b/ILSpy.Tests/Analyzers/AnalyzeContextMenuTests.cs @@ -154,32 +154,23 @@ public class AnalyzeContextMenuTests var methodNode = typeNode.Children.OfType() .First(m => m.MethodDefinition.Name == "Count"); - // Walk the dock layout to find the analyzer pane's current visibility state. - var analyzerPane = FindAnalyzerPane(dockWorkspace); - Assert.That(analyzerPane, Is.Not.Null, "the analyzer pane must be in the dock layout"); - var owningDock = analyzerPane!.Owner as global::Dock.Model.Core.IDock; - Assert.That(owningDock, Is.Not.Null, "the analyzer pane must have a dock parent"); - - // Pick any sibling dockable and force it to be active first, so the analyzer pane - // starts in the inactive state — that's the regression we're testing. - var sibling = owningDock!.VisibleDockables? - .FirstOrDefault(d => !ReferenceEquals(d, analyzerPane)); - if (sibling != null) - { - owningDock.ActiveDockable = sibling; - ((object?)owningDock.ActiveDockable).Should().NotBeSameAs(analyzerPane, - "baseline: sibling-active must really make the analyzer pane inactive"); - } + // The analyzer pane is hidden by default — not in the layout until something surfaces it. + FindAnalyzerPane(dockWorkspace).Should().BeNull( + "baseline: the analyzer pane is hidden until invoked"); entry.Execute(new TextViewContext { SelectedTreeNodes = new ICSharpCode.ILSpyX.TreeView.SharpTreeNode[] { methodNode } }); TestCapture.Step("analyzer-pane-surfaced"); - // After Execute, the active dockable in the analyzer pane's owning dock should be - // the analyzer pane itself — that's what ShowToolPane does. - ((object?)owningDock.ActiveDockable).Should().BeSameAs(analyzerPane, - "Execute must call ShowToolPane so the analyzer pane surfaces"); + // After Execute, the pane is materialised into the layout and active in its dock — + // that's what ShowToolPane does, so the user sees the entity they just analysed. + var analyzerPane = FindAnalyzerPane(dockWorkspace); + analyzerPane.Should().NotBeNull("Execute must surface (materialise) the analyzer pane"); + var owningDock = analyzerPane!.Owner as global::Dock.Model.Core.IDock; + owningDock.Should().NotBeNull("the surfaced analyzer pane must have a dock parent"); + ((object?)owningDock!.ActiveDockable).Should().BeSameAs(analyzerPane, + "Execute must call ShowToolPane so the analyzer pane surfaces and is active"); } [AvaloniaTest] diff --git a/ILSpy.Tests/Analyzers/AnalyzerPaneVisibilityTests.cs b/ILSpy.Tests/Analyzers/AnalyzerPaneVisibilityTests.cs index 8dd9d8cb8..ed2507afc 100644 --- a/ILSpy.Tests/Analyzers/AnalyzerPaneVisibilityTests.cs +++ b/ILSpy.Tests/Analyzers/AnalyzerPaneVisibilityTests.cs @@ -17,6 +17,7 @@ // DEALINGS IN THE SOFTWARE. using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Avalonia.Headless.NUnit; @@ -59,8 +60,10 @@ public class AnalyzerPaneVisibilityTests var analyzer = AppComposition.Current.GetExport(); var factory = (ILSpyDockFactory)dockWorkspace.Factory; + // The analyzer pane is hidden by default; show it so the user has it open. + dockWorkspace.ShowToolPane(AnalyzerTreeViewModel.PaneContentId); AllDockables(dockWorkspace.Layout).Should().Contain(analyzer, - "baseline: the analyzer pane is in the default layout"); + "showing the analyzer pane adds it to the layout"); // The user closes the bottom Analyzer panel (its close button calls CloseDockable). factory.CloseDockable(analyzer); @@ -75,4 +78,31 @@ public class AnalyzerPaneVisibilityTests (analyzer.Owner as IDock)?.ActiveDockable.Should().BeSameAs(analyzer, "the restored pane must be the active dockable in its dock"); } + + [AvaloniaTest] + public async Task Default_Layout_Shows_Only_The_Assembly_Tree_Pane() + { + // The default dock config shows just the assembly-tree pane: Search, Analyzer and Debug + // Steps are hidden until invoked (IsVisibleByDefault = false), while keeping their home + // locations for when they are first opened. + var (_, vm) = await TestHarness.BootAsync(3); + var dockWorkspace = AppComposition.Current.GetExport(); + var analyzer = AppComposition.Current.GetExport(); + var search = AppComposition.Current.GetExport(); + + var paneIds = AllDockables(dockWorkspace.Layout) + .OfType() + .Select(p => p.Id) + .ToList(); + paneIds.Should().Contain("AssemblyTree", "the assembly-tree pane is visible by default"); + paneIds.Should().NotContain("Search", "Search is hidden until invoked"); + paneIds.Should().NotContain("Analyzer", "Analyzer is hidden until invoked"); + + // ...but showing one materialises it into its home location. + dockWorkspace.ShowToolPane(global::ILSpy.Search.SearchPaneModel.PaneContentId); + AllDockables(dockWorkspace.Layout).Should().Contain(search, + "Show Search must materialise the hidden Search pane"); + (search.Owner as IDock)!.Id.Should().Be("TopTools", + "Search keeps its top-edge home dock"); + } } diff --git a/ILSpy.Tests/Docking/LayoutPersistenceTests.cs b/ILSpy.Tests/Docking/LayoutPersistenceTests.cs index 95832ff79..ebf0951b8 100644 --- a/ILSpy.Tests/Docking/LayoutPersistenceTests.cs +++ b/ILSpy.Tests/Docking/LayoutPersistenceTests.cs @@ -61,6 +61,9 @@ public class LayoutPersistenceTests _ = await TestHarness.BootAsync(); var dockWorkspace = AppComposition.Current.GetExport(); + // Search is hidden by default; surface it so the round-trip covers a tool pane that + // lives in a materialised-on-demand dock, not just the assembly tree. + dockWorkspace.ShowToolPane(global::ILSpy.Search.SearchPaneModel.PaneContentId); var path = Path.Combine(Path.GetTempPath(), $"ILSpy.Layout.test.{System.Guid.NewGuid():N}.json"); try diff --git a/ILSpy.Tests/MainWindow/ToolPaneRegistryTests.cs b/ILSpy.Tests/MainWindow/ToolPaneRegistryTests.cs index 93592eddc..d5a7bb85d 100644 --- a/ILSpy.Tests/MainWindow/ToolPaneRegistryTests.cs +++ b/ILSpy.Tests/MainWindow/ToolPaneRegistryTests.cs @@ -74,19 +74,19 @@ public class ToolPaneRegistryTests [AvaloniaTest] public void DockFactory_Builds_Layout_Zones_Driven_By_The_Registry() { - // ILSpyDockFactory iterates the registry instead of taking each pane in its ctor — - // verifies via the live DockWorkspace that the same three docks (Left/Top/Bottom) - // still get created and contain the right panes. + // ILSpyDockFactory iterates the registry instead of taking each pane in its ctor. + // The default config places only the visible-by-default assembly-tree pane; Search and + // Analyzer are hidden (IsVisibleByDefault = false) and materialised on demand. // Arrange + Act — DockWorkspace is constructed by the composition host; its layout is // built eagerly in the constructor. var workspace = AppComposition.Current.GetExport(); var allDockables = FlattenDockables(workspace.Layout).ToArray(); - // Assert — each pane is registered as a dockable inside the layout. + // Assert — only the assembly-tree pane is placed up front. allDockables.OfType().Should().ContainSingle(); - allDockables.OfType().Should().ContainSingle(); - allDockables.OfType().Should().ContainSingle(); + allDockables.OfType().Should().BeEmpty("Search is hidden until invoked"); + allDockables.OfType().Should().BeEmpty("Analyzer is hidden until invoked"); } static System.Collections.Generic.IEnumerable FlattenDockables(Dock.Model.Core.IDockable root) diff --git a/ILSpy.Tests/Search/SearchInputFocusTests.cs b/ILSpy.Tests/Search/SearchInputFocusTests.cs index 35ac99a4b..4bd86a72a 100644 --- a/ILSpy.Tests/Search/SearchInputFocusTests.cs +++ b/ILSpy.Tests/Search/SearchInputFocusTests.cs @@ -64,8 +64,10 @@ public class SearchInputFocusTests { var (window, _) = await TestHarness.BootAsync(); - var pane = await window.WaitForComponent(); var dockWorkspace = AppComposition.Current.GetExport(); + // Search is hidden by default; surface it so its view realises. + dockWorkspace.ShowToolPane(global::ILSpy.Search.SearchPaneModel.PaneContentId); + var pane = await window.WaitForComponent(); dockWorkspace.ShowSearchCommand.Execute(null); diff --git a/ILSpy.Tests/Search/SearchPaneViewTests.cs b/ILSpy.Tests/Search/SearchPaneViewTests.cs index d8a0067a8..69cd8aaf0 100644 --- a/ILSpy.Tests/Search/SearchPaneViewTests.cs +++ b/ILSpy.Tests/Search/SearchPaneViewTests.cs @@ -45,6 +45,9 @@ public class SearchPaneViewTests var window = AppComposition.Current.GetExport(); window.Show(); + // Search is hidden by default; surface it so its view realises. + AppComposition.Current.GetExport() + .ShowToolPane(SearchPaneModel.PaneContentId); var pane = await window.WaitForComponent(); TestCapture.Step("booted"); @@ -65,6 +68,9 @@ public class SearchPaneViewTests { var window = AppComposition.Current.GetExport(); window.Show(); + // Search is hidden by default; surface it so its view realises. + AppComposition.Current.GetExport() + .ShowToolPane(SearchPaneModel.PaneContentId); var pane = await window.WaitForComponent(); TestCapture.Step("booted"); var vm = (SearchPaneModel)pane.DataContext!; @@ -87,6 +93,9 @@ public class SearchPaneViewTests var window = AppComposition.Current.GetExport(); window.Show(); + // Search is hidden by default; surface it so its view realises. + AppComposition.Current.GetExport() + .ShowToolPane(SearchPaneModel.PaneContentId); var pane = await window.WaitForComponent(); TestCapture.Step("booted"); var vm = (SearchPaneModel)pane.DataContext!; diff --git a/ILSpy.Tests/Search/SearchProgressTests.cs b/ILSpy.Tests/Search/SearchProgressTests.cs index 0b43e3885..137763d40 100644 --- a/ILSpy.Tests/Search/SearchProgressTests.cs +++ b/ILSpy.Tests/Search/SearchProgressTests.cs @@ -106,6 +106,9 @@ public class SearchProgressTests { var window = AppComposition.Current.GetExport(); window.Show(); + // Search is hidden by default; surface it so its view realises. + AppComposition.Current.GetExport() + .ShowToolPane(global::ILSpy.Search.SearchPaneModel.PaneContentId); var pane = await window.WaitForComponent(); TestCapture.Step("booted"); diff --git a/ILSpy/Analyzers/AnalyzerTreeViewModel.cs b/ILSpy/Analyzers/AnalyzerTreeViewModel.cs index 77dc130b2..8a0b58276 100644 --- a/ILSpy/Analyzers/AnalyzerTreeViewModel.cs +++ b/ILSpy/Analyzers/AnalyzerTreeViewModel.cs @@ -31,7 +31,7 @@ using ILSpy.ViewModels; namespace ILSpy.Analyzers { [Export] - [ExportToolPane(ContentId = PaneContentId, Alignment = ToolPaneAlignment.Bottom, Order = 0)] + [ExportToolPane(ContentId = PaneContentId, Alignment = ToolPaneAlignment.Bottom, Order = 0, IsVisibleByDefault = false)] [Shared] public class AnalyzerTreeViewModel : ToolPaneModel { diff --git a/ILSpy/Docking/ILSpyDockFactory.cs b/ILSpy/Docking/ILSpyDockFactory.cs index 1483fc79e..379eaf0ec 100644 --- a/ILSpy/Docking/ILSpyDockFactory.cs +++ b/ILSpy/Docking/ILSpyDockFactory.cs @@ -416,8 +416,12 @@ namespace ILSpy.Docking ToolDock? BuildToolDock(string id, ToolPaneAlignment alignment, double proportion) { + // Only panes that opt into the default layout are placed up front; the rest + // (Search, Analyzer, Debug Steps) are materialised on demand by ShowToolPane, so the + // default config shows just the assembly-tree pane while keeping every pane's home + // location intact for when it is first opened. var dockables = panes - .Where(p => p.Metadata.Alignment == alignment) + .Where(p => p.Metadata.Alignment == alignment && p.Metadata.IsVisibleByDefault) .OrderBy(p => p.Metadata.Order) .Select(p => (IDockable)p.Pane) .ToArray(); diff --git a/ILSpy/Search/SearchPaneModel.cs b/ILSpy/Search/SearchPaneModel.cs index 038b6eb18..b01ff4ad8 100644 --- a/ILSpy/Search/SearchPaneModel.cs +++ b/ILSpy/Search/SearchPaneModel.cs @@ -51,7 +51,7 @@ namespace ILSpy.Search } [Export] - [ExportToolPane(ContentId = PaneContentId, Alignment = ToolPaneAlignment.Top, Order = 0)] + [ExportToolPane(ContentId = PaneContentId, Alignment = ToolPaneAlignment.Top, Order = 0, IsVisibleByDefault = false)] [Shared] public partial class SearchPaneModel : ToolPaneModel {