Browse Source

Factor repeated headless-test setup into shared helpers

Nearly every headless UI test opened with the same four-line prologue
(resolve the shared MainWindow, show it, cast its DataContext, wait for the
assembly list), then repeated the corelib lookup, the EnsureLazyChildren +
Children.OfType<T>().Single() drill, the registry single-by-header lookups,
and the open-an-assembly-and-wait dance. The duplication made the intent of
each test hard to see and every signature tweak a suite-wide edit.

Collapse those into TestHarness (BootAsync, OpenAssemblyAsync, GetCommand,
GetEntry) and TreeNavigation extensions (FindCoreLib, GetChild<T>, Expand<T>),
then apply them across the suite. Net ~865 lines of boilerplate removed with
no change in behaviour; the full headless suite stays green.
pull/3755/head
Siegfried Pammer 1 month ago
parent
commit
56afcf8abd
  1. 46
      ILSpy.Tests/Analyzers/AnalyzeContextMenuTests.cs
  2. 10
      ILSpy.Tests/Analyzers/AnalyzedMethodTreeNodeTests.cs
  3. 16
      ILSpy.Tests/Analyzers/AnalyzedTypeTreeNodeTests.cs
  4. 15
      ILSpy.Tests/Analyzers/AnalyzerConstructorUsesTests.cs
  5. 14
      ILSpy.Tests/Analyzers/AnalyzerErrorDisplayTests.cs
  6. 10
      ILSpy.Tests/Analyzers/AnalyzerNavigationTests.cs
  7. 24
      ILSpy.Tests/Analyzers/AnalyzerPaneCopyResultsTests.cs
  8. 10
      ILSpy.Tests/Analyzers/AnalyzerPaneRemoveTests.cs
  9. 10
      ILSpy.Tests/Analyzers/AnalyzerResultHighlightTests.cs
  10. 13
      ILSpy.Tests/Analyzers/AnalyzerSearchTreeNodeTests.cs
  11. 13
      ILSpy.Tests/Analyzers/AnalyzerTreeNodeTests.cs
  12. 13
      ILSpy.Tests/AssemblyList/AssemblyTreeContextMenuTests.cs
  13. 33
      ILSpy.Tests/AssemblyList/AssemblyTreeDragReorderTests.cs
  14. 11
      ILSpy.Tests/AssemblyList/AssemblyTreeExpanderHitboxTests.cs
  15. 33
      ILSpy.Tests/AssemblyList/AssemblyTreeFileDropTests.cs
  16. 423
      ILSpy.Tests/AssemblyList/AssemblyTreeTests.cs
  17. 15
      ILSpy.Tests/AssemblyList/ReloadAssemblyContextMenuTests.cs
  18. 20
      ILSpy.Tests/AssemblyList/RemoveAssemblyContextMenuTests.cs
  19. 7
      ILSpy.Tests/AssemblyList/ShowMetadataTokensTests.cs
  20. 7
      ILSpy.Tests/AssemblyList/TreeViewSettingsLiveReactivityTests.cs
  21. 7
      ILSpy.Tests/AssemblyList/UseNestedNamespaceNodesGridVerification.cs
  22. 7
      ILSpy.Tests/AssemblyList/UseNestedNamespaceNodesLiveTests.cs
  23. 7
      ILSpy.Tests/AssemblyList/UseNestedNamespaceNodesScreenshot.cs
  24. 12
      ILSpy.Tests/AssemblyList/UseNestedNamespaceNodesTests.cs
  25. 16
      ILSpy.Tests/Docking/LayoutPersistenceTests.cs
  26. 60
      ILSpy.Tests/Docking/PreviewTabPromotionTests.cs
  27. 3
      ILSpy.Tests/Docking/SingletonDocumentTabTests.cs
  28. 11
      ILSpy.Tests/MainWindow/MainToolBarLayoutTests.cs
  29. 5
      ILSpy.Tests/MainWindow/MainWindowTests.cs
  30. 7
      ILSpy.Tests/MainWindow/WindowMenuOpenDocumentsTests.cs
  31. 35
      ILSpy.Tests/Metadata/DebugDirectoryChildNodesTests.cs
  32. 50
      ILSpy.Tests/Metadata/EmbeddedPdbTreeTests.cs
  33. 60
      ILSpy.Tests/Metadata/HeapTreeTests.cs
  34. 18
      ILSpy.Tests/Metadata/HideEmptyMetadataTablesTests.cs
  35. 13
      ILSpy.Tests/Metadata/HideEmptyMetadataTablesUiBindingTests.cs
  36. 15
      ILSpy.Tests/Metadata/MetadataDisablesLanguageSwitchingTests.cs
  37. 21
      ILSpy.Tests/Metadata/MetadataFilterTests.cs
  38. 16
      ILSpy.Tests/Metadata/MetadataNodeIconParityTests.cs
  39. 19
      ILSpy.Tests/Metadata/MetadataProtocolHandlerDrillDownTests.cs
  40. 38
      ILSpy.Tests/Metadata/MetadataRowActivationTests.cs
  41. 55
      ILSpy.Tests/Metadata/MetadataTabSessionRestoreTests.cs
  42. 46
      ILSpy.Tests/Metadata/MetadataTablesTreeTests.cs
  43. 38
      ILSpy.Tests/Metadata/MetadataTokenNavigationTests.cs
  44. 24
      ILSpy.Tests/Metadata/MetadataTreeShapeTests.cs
  45. 105
      ILSpy.Tests/Metadata/PEHeaderTreeTests.cs
  46. 13
      ILSpy.Tests/Metadata/ShowInMetadataContextMenuTests.cs
  47. 73
      ILSpy.Tests/Metadata/TypedMetadataTableTreeTests.cs
  48. 5
      ILSpy.Tests/Navigation/BrowseBackForwardCommandTests.cs
  49. 18
      ILSpy.Tests/Navigation/NavigationTests.cs
  50. 18
      ILSpy.Tests/Navigation/ViewStateRoundTripTests.cs
  51. 20
      ILSpy.Tests/Options/ApiVisibilityFilterTests.cs
  52. 6
      ILSpy.Tests/Options/OptionsPageScrollReachTests.cs
  53. 43
      ILSpy.Tests/Options/OptionsTabTests.cs
  54. 24
      ILSpy.Tests/Search/ScopeSearchToTests.cs
  55. 10
      ILSpy.Tests/Search/SearchInputFocusTests.cs
  56. 10
      ILSpy.Tests/Search/SearchPaneAssemblyListChangedTests.cs
  57. 28
      ILSpy.Tests/Search/SearchPaneStreamingTests.cs
  58. 10
      ILSpy.Tests/Search/SearchProgressTests.cs
  59. 5
      ILSpy.Tests/Search/SearchResultNavigationTests.cs
  60. 5
      ILSpy.Tests/Search/SearchResultSortOrderTests.cs
  61. 5
      ILSpy.Tests/Search/ShowSearchCommandTests.cs
  62. 112
      ILSpy.Tests/TestHarness.cs
  63. 51
      ILSpy.Tests/TreeNavigation.cs

46
ILSpy.Tests/Analyzers/AnalyzeContextMenuTests.cs

@ -43,10 +43,7 @@ public class AnalyzeContextMenuTests @@ -43,10 +43,7 @@ public class AnalyzeContextMenuTests
[AvaloniaTest]
public async Task Analyze_Entry_Is_Registered_With_The_Localised_Header()
{
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
await TestHarness.BootAsync();
var registry = AppComposition.Current.GetExport<ContextMenuEntryRegistry>();
registry.Entries.Should().Contain(e => e.Metadata.Header == nameof(Resources.Analyze),
@ -59,15 +56,10 @@ public class AnalyzeContextMenuTests @@ -59,15 +56,10 @@ public class AnalyzeContextMenuTests
// AnalyzeContextMenuEntry visibility contract: visible when every selected node is
// an IMemberTreeNode (types, methods, fields, properties, events), hidden otherwise.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var registry = AppComposition.Current.GetExport<ContextMenuEntryRegistry>();
var entry = registry.Entries
.Single(e => e.Metadata.Header == nameof(Resources.Analyze))
.Value;
var entry = AppComposition.Current.GetExport<ContextMenuEntryRegistry>()
.GetEntry(nameof(Resources.Analyze));
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
"System.Linq", "System.Linq", "System.Linq.Enumerable");
@ -91,15 +83,10 @@ public class AnalyzeContextMenuTests @@ -91,15 +83,10 @@ public class AnalyzeContextMenuTests
// selection. The pane's Root.Children must hold one AnalyzedTypeTreeNode wrapping
// the same type definition after the call.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var registry = AppComposition.Current.GetExport<ContextMenuEntryRegistry>();
var entry = registry.Entries
.Single(e => e.Metadata.Header == nameof(Resources.Analyze))
.Value;
var entry = AppComposition.Current.GetExport<ContextMenuEntryRegistry>()
.GetEntry(nameof(Resources.Analyze));
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
"System.Linq", "System.Linq", "System.Linq.Enumerable");
@ -121,10 +108,7 @@ public class AnalyzeContextMenuTests @@ -121,10 +108,7 @@ public class AnalyzeContextMenuTests
// Ctrl+R while a member is selected on the assembly tree pane must surface the
// member in the analyzer pane — the same end-state as right-click + Analyze.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (window, vm) = await TestHarness.BootAsync();
var pane = await window.WaitForComponent<global::ILSpy.AssemblyTree.AssemblyListPane>();
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
@ -153,15 +137,12 @@ public class AnalyzeContextMenuTests @@ -153,15 +137,12 @@ public class AnalyzeContextMenuTests
// pane's Root.Children, but if the pane is hidden the user doesn't see anything
// happen. Execute should call DockWorkspace.ShowToolPane so the pane surfaces.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var dockWorkspace = AppComposition.Current.GetExport<global::ILSpy.Docking.DockWorkspace>();
var analyzerVm = AppComposition.Current.GetExport<AnalyzerTreeViewModel>();
var entry = AppComposition.Current.GetExport<ContextMenuEntryRegistry>()
.Entries.Single(e => e.Metadata.Header == nameof(Resources.Analyze)).Value;
.GetEntry(nameof(Resources.Analyze));
// Pick a method that won't already be in the analyzer pane.
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
@ -208,14 +189,11 @@ public class AnalyzeContextMenuTests @@ -208,14 +189,11 @@ public class AnalyzeContextMenuTests
// the leaf result rows below had perfectly good icons. Every materialised node in
// the analyzer tree must report a non-null Icon for the cell template to render.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var analyzerVm = AppComposition.Current.GetExport<AnalyzerTreeViewModel>();
var entry = AppComposition.Current.GetExport<ContextMenuEntryRegistry>()
.Entries.Single(e => e.Metadata.Header == nameof(Resources.Analyze)).Value;
.GetEntry(nameof(Resources.Analyze));
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
"System.Linq", "System.Linq", "System.Linq.Enumerable");

10
ILSpy.Tests/Analyzers/AnalyzedMethodTreeNodeTests.cs

@ -47,10 +47,7 @@ public class AnalyzedMethodTreeNodeTests @@ -47,10 +47,7 @@ public class AnalyzedMethodTreeNodeTests
// in the pane rather than the bare method name. An optional `prefix` argument lets
// accessor / event-helper nodes prepend a tag without touching the entity string.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
"System.Linq", "System.Linq", "System.Linq.Enumerable");
@ -74,10 +71,7 @@ public class AnalyzedMethodTreeNodeTests @@ -74,10 +71,7 @@ public class AnalyzedMethodTreeNodeTests
// are "Uses" (MethodUsesAnalyzer, body scanner) and "Used By" (MethodUsedByAnalyzer,
// call-site scanner).
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
"System.Linq", "System.Linq", "System.Linq.Enumerable");

16
ILSpy.Tests/Analyzers/AnalyzedTypeTreeNodeTests.cs

@ -47,13 +47,9 @@ public class AnalyzedTypeTreeNodeTests @@ -47,13 +47,9 @@ public class AnalyzedTypeTreeNodeTests
// the rest of the assembly tree uses — so users see "System.Object" exactly the way
// it appears under the assembly node, not a raw ReflectionName.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
var assemblyNode = vm.AssemblyTreeModel.FindCoreLib();
var module = assemblyNode.LoadedAssembly.GetMetadataFileOrNull();
module.Should().NotBeNull();
var typeSystem = module!.GetTypeSystemOrNull();
@ -76,13 +72,9 @@ public class AnalyzedTypeTreeNodeTests @@ -76,13 +72,9 @@ public class AnalyzedTypeTreeNodeTests
// non-static reference type like System.Object, "Used By", "Instantiated By", "Exposed
// By", and "Extension Methods" all match.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
var assemblyNode = vm.AssemblyTreeModel.FindCoreLib();
var module = assemblyNode.LoadedAssembly.GetMetadataFileOrNull();
module.Should().NotBeNull();
var typeSystem = module!.GetTypeSystemOrNull();

15
ILSpy.Tests/Analyzers/AnalyzerConstructorUsesTests.cs

@ -49,10 +49,7 @@ public class AnalyzerConstructorUsesTests @@ -49,10 +49,7 @@ public class AnalyzerConstructorUsesTests
[AvaloniaTest]
public async Task Used_By_Analyzer_Runs_Over_A_Constructor_Without_Throwing()
{
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
// Pick any concrete type with at least one public instance ctor; the type itself
// doesn't matter — what matters is that the symbol fed to the analyzer is a method
@ -93,10 +90,7 @@ public class AnalyzerConstructorUsesTests @@ -93,10 +90,7 @@ public class AnalyzerConstructorUsesTests
// MethodUsesAnalyzer can return type/method/field/property/event ISymbols — the
// path most likely to surface a switch-arm hole for some result shape we missed.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
"System.Linq", "System.Linq", "System.Linq.Lookup`2");
@ -122,10 +116,7 @@ public class AnalyzerConstructorUsesTests @@ -122,10 +116,7 @@ public class AnalyzerConstructorUsesTests
[AvaloniaTest]
public async Task Analyze_Pushes_A_Constructor_Onto_The_Pane_Without_Throwing()
{
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
"System.Linq", "System.Linq", "System.Linq.Lookup`2");

14
ILSpy.Tests/Analyzers/AnalyzerErrorDisplayTests.cs

@ -75,22 +75,16 @@ public class AnalyzerErrorDisplayTests @@ -75,22 +75,16 @@ public class AnalyzerErrorDisplayTests
[AvaloniaTest]
public async Task CopyErrorMessage_Entry_Is_Visible_Only_For_Error_Nodes()
{
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var registry = AppComposition.Current.GetExport<ContextMenuEntryRegistry>();
var entry = registry.Entries
.Single(e => e.Metadata.Header == nameof(Resources.CopyErrorMessage))
.Value;
var entry = AppComposition.Current.GetExport<ContextMenuEntryRegistry>()
.GetEntry(nameof(Resources.CopyErrorMessage));
var errorNode = new AnalyzerErrorNode(new InvalidOperationException("boom"));
entry.IsVisible(new TextViewContext { SelectedTreeNodes = new SharpTreeNode[] { errorNode } })
.Should().BeTrue("Copy Error Message must surface for AnalyzerErrorNode rows");
var unrelatedNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(
typeof(object).Assembly.GetName().Name!);
var unrelatedNode = vm.AssemblyTreeModel.FindCoreLib();
entry.IsVisible(new TextViewContext { SelectedTreeNodes = new SharpTreeNode[] { unrelatedNode } })
.Should().BeFalse("non-error rows must not see the Copy Error Message entry");
}

10
ILSpy.Tests/Analyzers/AnalyzerNavigationTests.cs

@ -50,10 +50,7 @@ public class AnalyzerNavigationTests @@ -50,10 +50,7 @@ public class AnalyzerNavigationTests
// Double-click on an analyser result must navigate back to the entity's home in
// the assembly tree — same UX as clicking a hyperlink in the decompiler view.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
"System.Linq", "System.Linq", "System.Linq.Enumerable");
@ -69,10 +66,7 @@ public class AnalyzerNavigationTests @@ -69,10 +66,7 @@ public class AnalyzerNavigationTests
[AvaloniaTest]
public async Task ActivateItem_On_An_AnalyzedMethodTreeNode_Selects_The_MethodTreeNode_Sibling()
{
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
"System.Linq", "System.Linq", "System.Linq.Enumerable");

24
ILSpy.Tests/Analyzers/AnalyzerPaneCopyResultsTests.cs

@ -42,15 +42,10 @@ public class AnalyzerPaneCopyResultsTests @@ -42,15 +42,10 @@ public class AnalyzerPaneCopyResultsTests
[AvaloniaTest]
public async Task CopyResults_Entry_Is_Visible_For_AnalyzerSearchTreeNode_And_Hidden_Otherwise()
{
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var registry = AppComposition.Current.GetExport<ContextMenuEntryRegistry>();
var entry = registry.Entries
.Single(e => e.Metadata.Header == "Copy results")
.Value;
var entry = AppComposition.Current.GetExport<ContextMenuEntryRegistry>()
.GetEntry("Copy results");
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
"System.Linq", "System.Linq", "System.Linq.Enumerable");
@ -71,15 +66,10 @@ public class AnalyzerPaneCopyResultsTests @@ -71,15 +66,10 @@ public class AnalyzerPaneCopyResultsTests
[AvaloniaTest]
public async Task CopyResults_Execute_Writes_Each_Child_On_Its_Own_Line()
{
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var registry = AppComposition.Current.GetExport<ContextMenuEntryRegistry>();
var entry = registry.Entries
.Single(e => e.Metadata.Header == "Copy results")
.Value;
var (_, vm) = await TestHarness.BootAsync();
var entry = AppComposition.Current.GetExport<ContextMenuEntryRegistry>()
.GetEntry("Copy results");
// Build a search node and stuff some deterministic children in directly, bypassing
// the analyzer altogether — the copy entry should only care about Text values.

10
ILSpy.Tests/Analyzers/AnalyzerPaneRemoveTests.cs

@ -42,10 +42,7 @@ public class AnalyzerPaneRemoveTests @@ -42,10 +42,7 @@ public class AnalyzerPaneRemoveTests
[AvaloniaTest]
public async Task Remove_Entry_Is_Visible_Only_For_Top_Level_Analysed_Entities()
{
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var registry = AppComposition.Current.GetExport<ContextMenuEntryRegistry>();
var entry = registry.Entries
@ -68,10 +65,7 @@ public class AnalyzerPaneRemoveTests @@ -68,10 +65,7 @@ public class AnalyzerPaneRemoveTests
[AvaloniaTest]
public async Task Remove_Execute_Drops_The_Selected_Root_Children()
{
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var registry = AppComposition.Current.GetExport<ContextMenuEntryRegistry>();
var entry = registry.Entries

10
ILSpy.Tests/Analyzers/AnalyzerResultHighlightTests.cs

@ -57,10 +57,7 @@ public class AnalyzerResultHighlightTests @@ -57,10 +57,7 @@ public class AnalyzerResultHighlightTests
// MessageBus → AssemblyTreeModel's subscription selects the Reference's tree node
// AND pushes Source onto the active decompiler tab's HighlightedReference.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var coreLibName = typeof(object).Assembly.GetName().Name!;
var stringNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
@ -94,10 +91,7 @@ public class AnalyzerResultHighlightTests @@ -94,10 +91,7 @@ public class AnalyzerResultHighlightTests
// so the subscriber must skip the HighlightedReference assignment rather than blow
// away an active highlight.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var coreLibName = typeof(object).Assembly.GetName().Name!;
var stringNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(

13
ILSpy.Tests/Analyzers/AnalyzerSearchTreeNodeTests.cs

@ -50,10 +50,7 @@ public class AnalyzerSearchTreeNodeTests @@ -50,10 +50,7 @@ public class AnalyzerSearchTreeNodeTests
// the elapsed-time stamp ("Used By (N in M ms)"). This single test exercises all
// three contracts on a small fixture so it stays fast.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
// Pick a small target so the analyzer completes quickly in headless. The "Used By"
// analyzer applies to every concrete type; we use a relatively unused type from
@ -96,14 +93,10 @@ public class AnalyzerSearchTreeNodeTests @@ -96,14 +93,10 @@ public class AnalyzerSearchTreeNodeTests
// a clean fetch. The "Used By" analyzer over a deeply-referenced type takes long
// enough that we can race a collapse against it.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
// Object is referenced by almost every type — the analyzer streams for a while.
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
var assemblyNode = vm.AssemblyTreeModel.FindCoreLib();
var module = assemblyNode.LoadedAssembly.GetMetadataFileOrNull();
module.Should().NotBeNull();
var typeSystem = module!.GetTypeSystemOrNull();

13
ILSpy.Tests/Analyzers/AnalyzerTreeNodeTests.cs

@ -49,10 +49,7 @@ public class AnalyzerTreeNodeTests @@ -49,10 +49,7 @@ public class AnalyzerTreeNodeTests
// reaches the list, (b) the order is monotonically non-decreasing by Metadata.Order
// so users see headers in the same sequence the WPF host produces.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
await TestHarness.BootAsync();
var analyzers = AnalyzerTreeNode.Analyzers.ToArray();
analyzers.Should().HaveCountGreaterThanOrEqualTo(15,
@ -76,14 +73,10 @@ public class AnalyzerTreeNodeTests @@ -76,14 +73,10 @@ public class AnalyzerTreeNodeTests
// every Analyzed*TreeNode's symbol references a module the new list may not
// contain. The root must wipe its children rather than risk a stale navigation.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var root = new AnalyzerRootNode();
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
var assemblyNode = vm.AssemblyTreeModel.FindCoreLib();
var module = assemblyNode.LoadedAssembly.GetMetadataFileOrNull();
module.Should().NotBeNull("the test needs a loaded CoreLib module to seed a sample analyzed entry");

13
ILSpy.Tests/AssemblyList/AssemblyTreeContextMenuTests.cs

@ -28,11 +28,8 @@ using AwesomeAssertions; @@ -28,11 +28,8 @@ using AwesomeAssertions;
using ICSharpCode.ILSpyX.TreeView;
using ILSpy;
using ILSpy.AppEnv;
using ILSpy.AssemblyTree;
using ILSpy.TreeNodes;
using ILSpy.ViewModels;
using ILSpy.Views;
using NUnit.Framework;
@ -49,10 +46,7 @@ public class AssemblyTreeContextMenuTests @@ -49,10 +46,7 @@ public class AssemblyTreeContextMenuTests
// without further per-entry plumbing.
// Arrange + Act — boot the window so the pane materialises.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (window, vm) = await TestHarness.BootAsync();
// Assert — TreeGrid carries a ContextMenu. (The menu may be empty if no entries are
// registered yet; verifies only that the host is in place.)
@ -71,10 +65,7 @@ public class AssemblyTreeContextMenuTests @@ -71,10 +65,7 @@ public class AssemblyTreeContextMenuTests
// Arrange — boot, select an assembly node, install a stub entry that records the
// context it sees on Execute.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (window, vm) = await TestHarness.BootAsync();
var pane = await window.WaitForComponent<AssemblyListPane>();
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>("System.Linq");
vm.AssemblyTreeModel.SelectNode(assemblyNode);

33
ILSpy.Tests/AssemblyList/AssemblyTreeDragReorderTests.cs

@ -29,11 +29,8 @@ using AwesomeAssertions; @@ -29,11 +29,8 @@ using AwesomeAssertions;
using ICSharpCode.ILSpyX;
using ILSpy.AppEnv;
using ILSpy.AssemblyTree;
using ILSpy.TreeNodes;
using ILSpy.ViewModels;
using ILSpy.Views;
using NUnit.Framework;
@ -48,10 +45,7 @@ public class AssemblyTreeDragReorderTests @@ -48,10 +45,7 @@ public class AssemblyTreeDragReorderTests
// Mirrors WPF's SharpTreeView AllowDropOrder=True — the assembly tree must opt in to
// ProDataGrid's row-drag-drop machinery (otherwise the handler we wire below is never
// asked to validate anything).
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (window, vm) = await TestHarness.BootAsync();
var pane = await window.WaitForComponent<AssemblyListPane>();
var grid = await pane.WaitForComponent<DataGrid>();
@ -71,10 +65,7 @@ public class AssemblyTreeDragReorderTests @@ -71,10 +65,7 @@ public class AssemblyTreeDragReorderTests
{
// The pane owns the handler instance — it builds one from the model's AssemblyList so
// dropping into the grid mutates the same list that file-open and Unload mutate.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (window, vm) = await TestHarness.BootAsync();
var pane = await window.WaitForComponent<AssemblyListPane>();
var grid = await pane.WaitForComponent<DataGrid>();
@ -88,10 +79,7 @@ public class AssemblyTreeDragReorderTests @@ -88,10 +79,7 @@ public class AssemblyTreeDragReorderTests
// the underlying AssemblyList reordered. The handler is responsible for turning
// HierarchicalNode wrappers (or bare AssemblyTreeNodes) into LoadedAssembly refs and
// calling AssemblyList.Move with the correct insert index.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 2);
var (window, vm) = await TestHarness.BootAsync(2);
var pane = await window.WaitForComponent<AssemblyListPane>();
var grid = await pane.WaitForComponent<DataGrid>();
var list = vm.AssemblyTreeModel.AssemblyList!;
@ -123,10 +111,7 @@ public class AssemblyTreeDragReorderTests @@ -123,10 +111,7 @@ public class AssemblyTreeDragReorderTests
// "Inside" would mean dropping one assembly as a child of another — there's no such
// relationship in the model, so the handler must refuse it (the grid then renders the
// "not allowed" cursor).
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 2);
var (window, vm) = await TestHarness.BootAsync(2);
var pane = await window.WaitForComponent<AssemblyListPane>();
var grid = await pane.WaitForComponent<DataGrid>();
@ -143,10 +128,7 @@ public class AssemblyTreeDragReorderTests @@ -143,10 +128,7 @@ public class AssemblyTreeDragReorderTests
{
// Dropping onto a child of an assembly (a namespace or type) must not reorder anything
// — that target doesn't live in AssemblyList at all.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (window, vm) = await TestHarness.BootAsync();
var pane = await window.WaitForComponent<AssemblyListPane>();
var grid = await pane.WaitForComponent<DataGrid>();
@ -165,10 +147,7 @@ public class AssemblyTreeDragReorderTests @@ -165,10 +147,7 @@ public class AssemblyTreeDragReorderTests
{
// Sub-nodes (namespaces, types, etc.) must not be picked up by the reorder gesture —
// only top-level AssemblyTreeNodes are eligible source items.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 2);
var (window, vm) = await TestHarness.BootAsync(2);
var pane = await window.WaitForComponent<AssemblyListPane>();
var grid = await pane.WaitForComponent<DataGrid>();

11
ILSpy.Tests/AssemblyList/AssemblyTreeExpanderHitboxTests.cs

@ -33,11 +33,8 @@ using AwesomeAssertions; @@ -33,11 +33,8 @@ using AwesomeAssertions;
using ICSharpCode.ILSpyX.TreeView;
using ILSpy.AppEnv;
using ILSpy.AssemblyTree;
using ILSpy.TreeNodes;
using ILSpy.ViewModels;
using ILSpy.Views;
using NUnit.Framework;
@ -56,14 +53,10 @@ public class AssemblyTreeExpanderHitboxTests @@ -56,14 +53,10 @@ public class AssemblyTreeExpanderHitboxTests
// occupy 16x16 of layout while only the 9x9 glyph receives input.
// Arrange — boot, wait for assemblies, expand a node so an expandable row is realised.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (window, vm) = await TestHarness.BootAsync(3);
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>("System.Linq");
assemblyNode.EnsureLazyChildren();
assemblyNode.IsExpanded = true;
assemblyNode.Expand();
vm.AssemblyTreeModel.SelectNode(assemblyNode);
var pane = await window.WaitForComponent<AssemblyListPane>();

33
ILSpy.Tests/AssemblyList/AssemblyTreeFileDropTests.cs

@ -28,11 +28,8 @@ using Avalonia.Input; @@ -28,11 +28,8 @@ using Avalonia.Input;
using AwesomeAssertions;
using ILSpy.AppEnv;
using ILSpy.AssemblyTree;
using ILSpy.TreeNodes;
using ILSpy.ViewModels;
using ILSpy.Views;
using NUnit.Framework;
@ -47,10 +44,7 @@ public class AssemblyTreeFileDropTests @@ -47,10 +44,7 @@ public class AssemblyTreeFileDropTests
// Avalonia's standard drag-drop pipeline routes Explorer file drops through
// DragDrop.AllowDrop / DragDrop.DropEvent. The grid must opt in or external drops
// produce a "no" cursor and never reach our handler.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (window, vm) = await TestHarness.BootAsync();
var pane = await window.WaitForComponent<AssemblyListPane>();
var grid = await pane.WaitForComponent<DataGrid>();
@ -63,10 +57,7 @@ public class AssemblyTreeFileDropTests @@ -63,10 +57,7 @@ public class AssemblyTreeFileDropTests
// Simulating Windows Explorer dropping a .dll on empty space inside the tree.
// No target row → the new entry is appended (WPF's AssemblyListTreeNode.Drop with
// no target index does the same).
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (window, vm) = await TestHarness.BootAsync(3);
var pane = await window.WaitForComponent<AssemblyListPane>();
var list = vm.AssemblyTreeModel.AssemblyList!;
@ -92,10 +83,7 @@ public class AssemblyTreeFileDropTests @@ -92,10 +83,7 @@ public class AssemblyTreeFileDropTests
{
// Drop position is honoured: dragging onto the first row with Position=Before lands
// the opened assembly at index 0.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (window, vm) = await TestHarness.BootAsync(3);
var pane = await window.WaitForComponent<AssemblyListPane>();
var list = vm.AssemblyTreeModel.AssemblyList!;
@ -125,10 +113,7 @@ public class AssemblyTreeFileDropTests @@ -125,10 +113,7 @@ public class AssemblyTreeFileDropTests
public async Task File_Drop_After_A_Target_Row_Inserts_Past_That_Index()
{
// Position=After: insertion lands at targetIndex + 1.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (window, vm) = await TestHarness.BootAsync(3);
var pane = await window.WaitForComponent<AssemblyListPane>();
var list = vm.AssemblyTreeModel.AssemblyList!;
@ -158,10 +143,7 @@ public class AssemblyTreeFileDropTests @@ -158,10 +143,7 @@ public class AssemblyTreeFileDropTests
// AssemblyListTreeNode.Drop (which ends in AssemblyTreeModel.SelectNodes).
// Without this, the user gets no visual confirmation that the drop took, and
// the decompiler view stays parked on whatever was selected before.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (window, vm) = await TestHarness.BootAsync(3);
var pane = await window.WaitForComponent<AssemblyListPane>();
var list = vm.AssemblyTreeModel.AssemblyList!;
@ -192,10 +174,7 @@ public class AssemblyTreeFileDropTests @@ -192,10 +174,7 @@ public class AssemblyTreeFileDropTests
{
// AssemblyList.OpenAssembly returns null for non-existent / non-PE paths; the
// handler must filter those out and never raise on the caller's behalf.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (window, vm) = await TestHarness.BootAsync(3);
var pane = await window.WaitForComponent<AssemblyListPane>();
var list = vm.AssemblyTreeModel.AssemblyList!;

423
ILSpy.Tests/AssemblyList/AssemblyTreeTests.cs

@ -48,7 +48,6 @@ using ILSpy.Metadata.CorTables; @@ -48,7 +48,6 @@ using ILSpy.Metadata.CorTables;
using ILSpy.Search;
using ILSpy.TreeNodes;
using ILSpy.ViewModels;
using ILSpy.Views;
using NUnit.Framework;
@ -66,22 +65,16 @@ public class AssemblyTreeTests @@ -66,22 +65,16 @@ public class AssemblyTreeTests
// ResourceTreeNode — both are ILSpyTreeNodes).
// Arrange — boot, wait for assemblies.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
// Act — pick CoreLib (always ships localised error-message tables), expand it.
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
assemblyNode.EnsureLazyChildren();
assemblyNode.IsExpanded = true;
var assemblyNode = vm.AssemblyTreeModel.FindCoreLib();
assemblyNode.Expand();
// Assert — exactly one Resources folder, named, non-empty, every child an ILSpyTreeNode.
var resources = assemblyNode.Children.OfType<ResourceListTreeNode>().Single();
resources.Text.Should().Be(Resources._Resources);
resources.EnsureLazyChildren();
resources.IsExpanded = true;
resources.Expand();
resources.Children.Should().NotBeEmpty();
resources.Children.Should().AllBeAssignableTo<ILSpyTreeNode>();
}
@ -93,10 +86,7 @@ public class AssemblyTreeTests @@ -93,10 +86,7 @@ public class AssemblyTreeTests
// Ctrl-click to multi-select rows.
// Arrange — boot, wait for assemblies + AssemblyListPane to materialise.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (window, vm) = await TestHarness.BootAsync(3);
// Act — locate the realised DataGrid inside the AssemblyListPane.
var pane = await window.WaitForComponent<AssemblyListPane>();
@ -115,15 +105,11 @@ public class AssemblyTreeTests @@ -115,15 +105,11 @@ public class AssemblyTreeTests
// flip its IsSelected back without affecting siblings.
// Arrange — boot, wait for assemblies, expand Enumerable, grab two methods.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
"System.Linq", "System.Linq", "System.Linq.Enumerable");
typeNode.EnsureLazyChildren();
typeNode.IsExpanded = true;
typeNode.Expand();
var first = typeNode.Children.OfType<MethodTreeNode>()
.First(m => m.MethodDefinition.Name == "ElementAt");
var second = typeNode.Children.OfType<MethodTreeNode>()
@ -155,10 +141,7 @@ public class AssemblyTreeTests @@ -155,10 +141,7 @@ public class AssemblyTreeTests
// Arrange — boot, wait for assemblies, build a fixture zip with a flat file + two
// folders containing one file each.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var tempDir = Path.Combine(Path.GetTempPath(), "ILSpy.Tests", System.Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(tempDir);
@ -174,22 +157,10 @@ public class AssemblyTreeTests @@ -174,22 +157,10 @@ public class AssemblyTreeTests
}
// Act — open the zip via the OpenCommand and wait for it to load.
var registry = AppComposition.Current.GetExport<MainMenuCommandRegistry>();
var openCommand = registry.Commands
.Single(c => c.Metadata.Header == nameof(Resources._Open))
.CreateExport().Value;
openCommand.Execute(zipPath);
await Waiters.WaitForAsync(() =>
vm.AssemblyTreeModel.AssemblyList!.GetAssemblies().Any(a =>
string.Equals(a.FileName, zipPath, System.StringComparison.OrdinalIgnoreCase)));
var loaded = vm.AssemblyTreeModel.AssemblyList!.GetAssemblies()
.First(a => string.Equals(a.FileName, zipPath, System.StringComparison.OrdinalIgnoreCase));
await loaded.GetLoadResultAsync();
var loaded = await vm.OpenAssemblyAsync(zipPath);
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(loaded.ShortName);
assemblyNode.EnsureLazyChildren();
assemblyNode.IsExpanded = true;
assemblyNode.Expand();
// Assert — both nested folders show as PackageFolderTreeNodes; the flat file shows as
// a ResourceTreeNode with its original entry name.
@ -212,18 +183,12 @@ public class AssemblyTreeTests @@ -212,18 +183,12 @@ public class AssemblyTreeTests
// string-table or inner ResourceEntryNode children (depending on payload type).
// Arrange — boot, wait for assemblies, expand CoreLib's Resources folder.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
assemblyNode.EnsureLazyChildren();
assemblyNode.IsExpanded = true;
var assemblyNode = vm.AssemblyTreeModel.FindCoreLib();
assemblyNode.Expand();
var resources = assemblyNode.Children.OfType<ResourceListTreeNode>().Single();
resources.EnsureLazyChildren();
resources.IsExpanded = true;
resources.Expand();
// Act — look up the first .resources file and select it.
var resourceFileNode = resources.Children.OfType<ResourcesFileTreeNode>().FirstOrDefault();
@ -246,14 +211,10 @@ public class AssemblyTreeTests @@ -246,14 +211,10 @@ public class AssemblyTreeTests
// AssemblyReferenceTreeNodes — one per AssemblyRef row.
// Arrange — boot, wait for assemblies, expand System.Linq.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>("System.Linq");
assemblyNode.EnsureLazyChildren();
assemblyNode.IsExpanded = true;
assemblyNode.Expand();
// Act — find the References folder.
var refFolder = assemblyNode.Children.OfType<ReferenceFolderTreeNode>().Single();
@ -261,8 +222,7 @@ public class AssemblyTreeTests @@ -261,8 +222,7 @@ public class AssemblyTreeTests
// Assert — folder is named, expands to non-empty AssemblyReferenceTreeNode children.
refFolder.Text.Should().Be(Resources.References);
refFolder.EnsureLazyChildren();
refFolder.IsExpanded = true;
refFolder.Expand();
refFolder.Children.Should().NotBeEmpty();
refFolder.Children.Should().AllBeAssignableTo<AssemblyReferenceTreeNode>();
}
@ -277,14 +237,10 @@ public class AssemblyTreeTests @@ -277,14 +237,10 @@ public class AssemblyTreeTests
// Arrange — boot, wait for assemblies, force lazy children on assembly + References.
// Stay model-only (no IsExpanded = true) — visually expanding rows would render the
// icons, triggering the resolver and losing the lazy "loading" state we're asserting.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>("System.Linq");
assemblyNode.EnsureLazyChildren();
var refFolder = assemblyNode.Children.OfType<ReferenceFolderTreeNode>().Single();
var refFolder = assemblyNode.GetChild<ReferenceFolderTreeNode>();
refFolder.EnsureLazyChildren();
var refNode = refFolder.Children.OfType<AssemblyReferenceTreeNode>()
@ -309,27 +265,20 @@ public class AssemblyTreeTests @@ -309,27 +265,20 @@ public class AssemblyTreeTests
// TypeRef rows the host assembly imports from that reference.
// Arrange — boot, wait for assemblies, expand System.Linq → References → System.Runtime.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>("System.Linq");
assemblyNode.EnsureLazyChildren();
assemblyNode.IsExpanded = true;
assemblyNode.Expand();
var refFolder = assemblyNode.Children.OfType<ReferenceFolderTreeNode>().Single();
refFolder.EnsureLazyChildren();
refFolder.IsExpanded = true;
refFolder.Expand();
var refNode = refFolder.Children.OfType<AssemblyReferenceTreeNode>()
.First(n => n.AssemblyReference.Name == "System.Runtime");
refNode.EnsureLazyChildren();
refNode.IsExpanded = true;
refNode.Expand();
// Act — find the ReferencedTypes subnode and expand it.
var typesNode = refNode.Children.OfType<AssemblyReferenceReferencedTypesTreeNode>().Single();
typesNode.EnsureLazyChildren();
typesNode.IsExpanded = true;
typesNode.Expand();
// Assert — header text contains the expected label, and the children include at least
// one TypeReferenceTreeNode.
@ -345,14 +294,10 @@ public class AssemblyTreeTests @@ -345,14 +294,10 @@ public class AssemblyTreeTests
// search to land on a specific namespace.
// Arrange — boot, wait for assemblies, expand System.Linq.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>("System.Linq");
assemblyNode.EnsureLazyChildren();
assemblyNode.IsExpanded = true;
assemblyNode.Expand();
// Act — look up the System.Linq namespace.
var ns = assemblyNode.FindNamespaceNode("System.Linq");
@ -370,14 +315,10 @@ public class AssemblyTreeTests @@ -370,14 +315,10 @@ public class AssemblyTreeTests
// to land on a specific type by ITypeDefinition.
// Arrange — boot, wait for assemblies, expand System.Linq.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>("System.Linq");
assemblyNode.EnsureLazyChildren();
assemblyNode.IsExpanded = true;
assemblyNode.Expand();
// Act — resolve System.Linq.Enumerable's ITypeDefinition, then look up its tree node.
var module = assemblyNode.LoadedAssembly.GetMetadataFileOrNull()!;
@ -399,17 +340,12 @@ public class AssemblyTreeTests @@ -399,17 +340,12 @@ public class AssemblyTreeTests
// top of the underlying Method/Field glyph — verifies that overlay routing works.
// Arrange — boot, wait for assemblies, expand System.Linq → References.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>("System.Linq");
assemblyNode.EnsureLazyChildren();
assemblyNode.IsExpanded = true;
assemblyNode.Expand();
var refFolder = assemblyNode.Children.OfType<ReferenceFolderTreeNode>().Single();
refFolder.EnsureLazyChildren();
refFolder.IsExpanded = true;
refFolder.Expand();
// Act — walk every reference's ReferencedTypes subtree until we find a TypeRef with
// at least one MemberReference. System.Linq imports plenty (delegates, Func ctors)
@ -417,17 +353,14 @@ public class AssemblyTreeTests @@ -417,17 +353,14 @@ public class AssemblyTreeTests
MemberReferenceTreeNode? memberRefNode = null;
foreach (var refNode in refFolder.Children.OfType<AssemblyReferenceTreeNode>())
{
refNode.EnsureLazyChildren();
refNode.IsExpanded = true;
refNode.Expand();
var typesNode = refNode.Children.OfType<AssemblyReferenceReferencedTypesTreeNode>().FirstOrDefault();
if (typesNode == null)
continue;
typesNode.EnsureLazyChildren();
typesNode.IsExpanded = true;
typesNode.Expand();
foreach (var typeRef in typesNode.Children.OfType<TypeReferenceTreeNode>())
{
typeRef.EnsureLazyChildren();
typeRef.IsExpanded = true;
typeRef.Expand();
memberRefNode = typeRef.Children.OfType<MemberReferenceTreeNode>().FirstOrDefault();
if (memberRefNode != null)
break;
@ -452,10 +385,7 @@ public class AssemblyTreeTests @@ -452,10 +385,7 @@ public class AssemblyTreeTests
// Arrange — boot, wait for assemblies. Open mscorlib explicitly: it's the canonical
// type-forwarder facade and we need a known forwarder regardless of default startup.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var coreLibDir = System.IO.Path.GetDirectoryName(typeof(object).Assembly.Location)!;
var mscorlibPath = System.IO.Path.Combine(coreLibDir, "mscorlib.dll");
@ -464,37 +394,22 @@ public class AssemblyTreeTests @@ -464,37 +394,22 @@ public class AssemblyTreeTests
Assert.Inconclusive($"mscorlib.dll not present next to System.Private.CoreLib at {coreLibDir} — runtime layout differs.");
return;
}
var registry = AppComposition.Current.GetExport<MainMenuCommandRegistry>();
var openCommand = registry.Commands
.Single(c => c.Metadata.Header == nameof(Resources._Open))
.CreateExport().Value;
openCommand.Execute(mscorlibPath);
await Waiters.WaitForAsync(() =>
vm.AssemblyTreeModel.AssemblyList!.GetAssemblies().Any(a =>
string.Equals(a.FileName, mscorlibPath, System.StringComparison.OrdinalIgnoreCase)));
var loaded = vm.AssemblyTreeModel.AssemblyList!.GetAssemblies()
.First(a => string.Equals(a.FileName, mscorlibPath, System.StringComparison.OrdinalIgnoreCase));
await loaded.GetLoadResultAsync();
var loaded = await vm.OpenAssemblyAsync(mscorlibPath);
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(loaded.ShortName);
assemblyNode.EnsureLazyChildren();
assemblyNode.IsExpanded = true;
assemblyNode.Expand();
var refFolder = assemblyNode.Children.OfType<ReferenceFolderTreeNode>().Single();
refFolder.EnsureLazyChildren();
refFolder.IsExpanded = true;
refFolder.Expand();
// Act — walk every reference's ReferencedTypes for an ExportedTypeTreeNode.
ExportedTypeTreeNode? exportedNode = null;
foreach (var refNode in refFolder.Children.OfType<AssemblyReferenceTreeNode>())
{
refNode.EnsureLazyChildren();
refNode.IsExpanded = true;
refNode.Expand();
var typesNode = refNode.Children.OfType<AssemblyReferenceReferencedTypesTreeNode>().FirstOrDefault();
if (typesNode == null)
continue;
typesNode.EnsureLazyChildren();
typesNode.IsExpanded = true;
typesNode.Expand();
exportedNode = typesNode.Children.OfType<ExportedTypeTreeNode>().FirstOrDefault();
if (exportedNode != null)
break;
@ -514,25 +429,19 @@ public class AssemblyTreeTests @@ -514,25 +429,19 @@ public class AssemblyTreeTests
// — building a transitive reference tree the user can drill into.
// Arrange — boot, wait for assemblies, expand System.Linq → References.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>("System.Linq");
assemblyNode.EnsureLazyChildren();
assemblyNode.IsExpanded = true;
assemblyNode.Expand();
var refFolder = assemblyNode.Children.OfType<ReferenceFolderTreeNode>().Single();
refFolder.EnsureLazyChildren();
refFolder.IsExpanded = true;
refFolder.Expand();
// Act — many BCL refs resolve to facade assemblies with zero transitive references;
// iterate until we find one that has its own AssemblyRefs.
bool foundTransitive = false;
foreach (var refNode in refFolder.Children.OfType<AssemblyReferenceTreeNode>())
{
refNode.EnsureLazyChildren();
refNode.IsExpanded = true;
refNode.Expand();
if (refNode.Children.OfType<AssemblyReferenceTreeNode>().Any())
{
foundTransitive = true;
@ -551,17 +460,12 @@ public class AssemblyTreeTests @@ -551,17 +460,12 @@ public class AssemblyTreeTests
// assembly's AssemblyTreeNode in the tree — auto-loading it if necessary.
// Arrange — boot, wait for assemblies, expand System.Linq → References.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>("System.Linq");
assemblyNode.EnsureLazyChildren();
assemblyNode.IsExpanded = true;
assemblyNode.Expand();
var refFolder = assemblyNode.Children.OfType<ReferenceFolderTreeNode>().Single();
refFolder.EnsureLazyChildren();
refFolder.IsExpanded = true;
refFolder.Expand();
var refNode = refFolder.Children.OfType<AssemblyReferenceTreeNode>()
.Single(n => n.AssemblyReference.Name == "System.Runtime");
@ -591,25 +495,10 @@ public class AssemblyTreeTests @@ -591,25 +495,10 @@ public class AssemblyTreeTests
// Arrange — boot, wait for assemblies, open System.Net.Http (its CancelPendingRequests
// pulls in plenty of currently-unreferenced types).
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var newAsmPath = typeof(System.Net.Http.HttpClient).Assembly.Location;
var registry = AppComposition.Current.GetExport<MainMenuCommandRegistry>();
var openCommand = registry.Commands
.Single(c => c.Metadata.Header == nameof(Resources._Open))
.CreateExport().Value;
openCommand.Execute(newAsmPath);
await Waiters.WaitForAsync(() =>
vm.AssemblyTreeModel.AssemblyList!.GetAssemblies().Any(a =>
string.Equals(a.FileName, newAsmPath, System.StringComparison.OrdinalIgnoreCase)));
var loaded = vm.AssemblyTreeModel.AssemblyList!.GetAssemblies()
.First(a => string.Equals(a.FileName, newAsmPath, System.StringComparison.OrdinalIgnoreCase));
await loaded.GetLoadResultAsync();
var loaded = await vm.OpenAssemblyAsync(newAsmPath);
var initialFiles = vm.AssemblyTreeModel.AssemblyList!.GetAssemblies()
.Select(a => a.FileName)
@ -619,8 +508,7 @@ public class AssemblyTreeTests @@ -619,8 +508,7 @@ public class AssemblyTreeTests
// assemblies to appear.
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
loaded.ShortName, "System.Net.Http", "System.Net.Http.HttpClient");
typeNode.EnsureLazyChildren();
typeNode.IsExpanded = true;
typeNode.Expand();
var methodNode = typeNode.Children.OfType<MethodTreeNode>()
.First(m => m.MethodDefinition.Name == "CancelPendingRequests");
@ -653,30 +541,14 @@ public class AssemblyTreeTests @@ -653,30 +541,14 @@ public class AssemblyTreeTests
// user actually opened.
// Arrange — boot, wait for assemblies, open + select a method that triggers auto-load.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var newAsmPath = typeof(System.Net.Http.HttpClient).Assembly.Location;
var registry = AppComposition.Current.GetExport<MainMenuCommandRegistry>();
var openCommand = registry.Commands
.Single(c => c.Metadata.Header == nameof(Resources._Open))
.CreateExport().Value;
openCommand.Execute(newAsmPath);
await Waiters.WaitForAsync(() =>
vm.AssemblyTreeModel.AssemblyList!.GetAssemblies().Any(a =>
string.Equals(a.FileName, newAsmPath, System.StringComparison.OrdinalIgnoreCase)));
var loaded = vm.AssemblyTreeModel.AssemblyList!.GetAssemblies()
.First(a => string.Equals(a.FileName, newAsmPath, System.StringComparison.OrdinalIgnoreCase));
await loaded.GetLoadResultAsync();
var loaded = await vm.OpenAssemblyAsync(newAsmPath);
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
loaded.ShortName, "System.Net.Http", "System.Net.Http.HttpClient");
typeNode.EnsureLazyChildren();
typeNode.IsExpanded = true;
typeNode.Expand();
var methodNode = typeNode.Children.OfType<MethodTreeNode>()
.First(m => m.MethodDefinition.Name == "CancelPendingRequests");
@ -717,28 +589,13 @@ public class AssemblyTreeTests @@ -717,28 +589,13 @@ public class AssemblyTreeTests
// matching tree node, and select that node so the user sees their newly-opened file.
// Arrange — boot, wait for assemblies, snapshot the initial count.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var newAsmPath = typeof(System.Net.Http.HttpClient).Assembly.Location;
var initialCount = vm.AssemblyTreeModel.AssemblyList!.Count;
// Act — fire OpenCommand on a new path and wait for the load to complete.
var registry = AppComposition.Current.GetExport<MainMenuCommandRegistry>();
var openCommand = registry.Commands
.Single(c => c.Metadata.Header == nameof(Resources._Open))
.CreateExport().Value;
openCommand.Execute(newAsmPath);
await Waiters.WaitForAsync(() =>
vm.AssemblyTreeModel.AssemblyList!.GetAssemblies().Any(a =>
string.Equals(a.FileName, newAsmPath, System.StringComparison.OrdinalIgnoreCase)));
var loaded = vm.AssemblyTreeModel.AssemblyList!.GetAssemblies()
.First(a => string.Equals(a.FileName, newAsmPath, System.StringComparison.OrdinalIgnoreCase));
await loaded.GetLoadResultAsync();
var loaded = await vm.OpenAssemblyAsync(newAsmPath);
// Assert — count incremented by one; the corresponding tree node points at the
// freshly-loaded assembly and is selected.
@ -758,24 +615,18 @@ public class AssemblyTreeTests @@ -758,24 +615,18 @@ public class AssemblyTreeTests
// Arrange — boot, wait for assemblies, expand a sub-tree large enough to scroll. Park
// the viewport mid-list so clicks are at non-zero offset.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (window, vm) = await TestHarness.BootAsync(3);
var enumerable = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
"System.Linq", "System.Linq", "System.Linq.Enumerable");
enumerable.EnsureLazyChildren();
enumerable.IsExpanded = true;
enumerable.Expand();
var ns = (NamespaceTreeNode)enumerable.Parent!;
ns.EnsureLazyChildren();
ns.IsExpanded = true;
ns.Expand();
var asm = (AssemblyTreeNode)ns.Parent!;
asm.IsExpanded = true;
foreach (var child in ns.Children.OfType<TypeTreeNode>())
{
child.EnsureLazyChildren();
child.IsExpanded = true;
child.Expand();
}
var pane = await window.WaitForComponent<AssemblyListPane>();
@ -838,19 +689,13 @@ public class AssemblyTreeTests @@ -838,19 +689,13 @@ public class AssemblyTreeTests
// method — that's how nodes (e.g. resources) opt into a custom save dialog/format.
// Arrange — boot, wait for assemblies, plant a probe node that records when Save runs.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var probe = new SaveProbeNode();
vm.AssemblyTreeModel.SelectNode(probe);
// Act — fire the Save Code main-menu command.
var registry = AppComposition.Current.GetExport<MainMenuCommandRegistry>();
var saveCmd = registry.Commands
.Single(c => c.Metadata.Header == nameof(Resources._SaveCode))
.CreateExport().Value;
var saveCmd = AppComposition.Current.GetExport<MainMenuCommandRegistry>().GetCommand(nameof(Resources._SaveCode));
saveCmd.Execute(null);
// Assert — probe's Save() ran.
@ -877,15 +722,11 @@ public class AssemblyTreeTests @@ -877,15 +722,11 @@ public class AssemblyTreeTests
// write the resulting C# to the user-picked path.
// Arrange — boot, wait for assemblies, select a method, wait for the in-tab decompile.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
"System.Linq", "System.Linq", "System.Linq.Enumerable");
typeNode.EnsureLazyChildren();
typeNode.IsExpanded = true;
typeNode.Expand();
var method = typeNode.Children.OfType<MethodTreeNode>()
.First(m => m.MethodDefinition.Name == "AsEnumerable");
vm.AssemblyTreeModel.SelectNode(method);
@ -929,10 +770,7 @@ public class AssemblyTreeTests @@ -929,10 +770,7 @@ public class AssemblyTreeTests
// Arrange — boot, wait for assemblies, pick the assembly to evict + a sibling we
// expect to survive, select the sacrificial one, focus the grid.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (window, vm) = await TestHarness.BootAsync(3);
var sacrificialName = typeof(System.Linq.Enumerable).Assembly.GetName().Name!;
var survivorName = typeof(object).Assembly.GetName().Name!;
@ -980,17 +818,11 @@ public class AssemblyTreeTests @@ -980,17 +818,11 @@ public class AssemblyTreeTests
// The Clear Assembly List menu command must drop every entry from the active list.
// Arrange — boot, wait for assemblies, baseline the non-empty count.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
vm.AssemblyTreeModel.AssemblyList!.Count.Should().BeGreaterThan(0);
// Act — fire the Clear command.
var registry = AppComposition.Current.GetExport<MainMenuCommandRegistry>();
var clearCmd = registry.Commands
.Single(c => c.Metadata.Header == nameof(Resources.ClearAssemblyList))
.CreateExport().Value;
var clearCmd = AppComposition.Current.GetExport<MainMenuCommandRegistry>().GetCommand(nameof(Resources.ClearAssemblyList));
clearCmd.CanExecute(null).Should().BeTrue("non-empty list must enable Clear");
clearCmd.Execute(null);
@ -1008,10 +840,7 @@ public class AssemblyTreeTests @@ -1008,10 +840,7 @@ public class AssemblyTreeTests
// Arrange — boot, wait for assemblies, plant a "broken" entry: a real file that isn't
// a PE binary. AssemblyList records it but the load fails — that's the state the
// command targets.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var brokenPath = System.IO.Path.Combine(
System.IO.Path.GetTempPath(),
@ -1035,10 +864,7 @@ public class AssemblyTreeTests @@ -1035,10 +864,7 @@ public class AssemblyTreeTests
validBefore.Should().NotBeEmpty("at least one real assembly must remain to verify it's spared");
// Act — fire the command.
var registry = AppComposition.Current.GetExport<MainMenuCommandRegistry>();
var cmd = registry.Commands
.Single(c => c.Metadata.Header == nameof(Resources._RemoveAssembliesWithLoadErrors))
.CreateExport().Value;
var cmd = AppComposition.Current.GetExport<MainMenuCommandRegistry>().GetCommand(nameof(Resources._RemoveAssembliesWithLoadErrors));
cmd.CanExecute(null).Should().BeTrue("a broken entry must enable the command");
cmd.Execute(null);
@ -1069,10 +895,7 @@ public class AssemblyTreeTests @@ -1069,10 +895,7 @@ public class AssemblyTreeTests
// Arrange — boot, wait for assemblies, locate the realised DataGrid and the
// HierarchicalModel wrapper for the assembly node.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (window, vm) = await TestHarness.BootAsync(3);
var pane = await window.WaitForComponent<AssemblyListPane>();
var grid = await pane.WaitForComponent<DataGrid>();
@ -1084,8 +907,7 @@ public class AssemblyTreeTests @@ -1084,8 +907,7 @@ public class AssemblyTreeTests
hNode!.IsExpanded.Should().BeFalse("baseline: top-level assembly rows start collapsed");
// Act — production-shaped action: just toggle the model property.
assemblyNode.EnsureLazyChildren();
assemblyNode.IsExpanded = true;
assemblyNode.Expand();
// Drain the dispatcher so the wiring (PropertyChanged → hm.Expand) settles.
for (int i = 0; i < 5; i++)
@ -1106,10 +928,7 @@ public class AssemblyTreeTests @@ -1106,10 +928,7 @@ public class AssemblyTreeTests
// tab opens with the supplied node decompiled, the existing tab keeps its content,
// and the assembly-tree selection is pulled across to the new tab's source node
// (the active tab and the tree are kept in lockstep).
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (window, vm) = await TestHarness.BootAsync(3);
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
"System.Linq", "System.Linq", "System.Linq.Enumerable");
@ -1147,10 +966,7 @@ public class AssemblyTreeTests @@ -1147,10 +966,7 @@ public class AssemblyTreeTests
{
// Activate two tabs (each carrying a different SourceNode), then flip the active
// dockable back to the first one and confirm the assembly-tree selection follows.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
"System.Linq", "System.Linq", "System.Linq.Enumerable");
@ -1188,19 +1004,12 @@ public class AssemblyTreeTests @@ -1188,19 +1004,12 @@ public class AssemblyTreeTests
// DecompilerTabPageModel. Exercises the regression behind "there can't be multiple
// metadata views": before the OpenNodeInNewTab consolidation, the new-tab path
// always built a decompiler tab and the metadata page-type couldn't be carved out.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
assemblyNode.EnsureLazyChildren();
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single();
metadataNode.EnsureLazyChildren();
var tablesNode = metadataNode.Children.OfType<MetadataTablesTreeNode>().Single();
tablesNode.EnsureLazyChildren();
var typeDefNode = tablesNode.Children.OfType<TypeDefTableTreeNode>().Single();
var tablesNode = vm.AssemblyTreeModel.FindCoreLib()
.GetChild<MetadataTreeNode>()
.GetChild<MetadataTablesTreeNode>();
var typeDefNode = tablesNode.GetChild<TypeDefTableTreeNode>();
// First metadata view via tree-selection (active tab gets MetadataTablePageModel content).
vm.AssemblyTreeModel.SelectNode(typeDefNode);
@ -1229,10 +1038,7 @@ public class AssemblyTreeTests @@ -1229,10 +1038,7 @@ public class AssemblyTreeTests
// ILSpy. Without this, tree-based navigation can't reach get_X / set_X — and
// MMB on a property-accessor row in the metadata grid silently no-ops because
// FindTreeNode can't resolve the IMethod to a tree node.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var coreLibName = typeof(object).Assembly.GetName().Name!;
var stringTypeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
@ -1257,10 +1063,7 @@ public class AssemblyTreeTests @@ -1257,10 +1063,7 @@ public class AssemblyTreeTests
// Events surface add_X / remove_X (and invoke_X if present) as MethodTreeNode
// children, mirroring WPF ILSpy. Same regression-protection as the property
// case above.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var coreLibName = typeof(object).Assembly.GetName().Name!;
// AppDomain.AssemblyLoad is a public, well-known event in CoreLib.
@ -1285,10 +1088,7 @@ public class AssemblyTreeTests @@ -1285,10 +1088,7 @@ public class AssemblyTreeTests
// the per-node Filter must hide them under the default ShowApiLevel — they're
// rendered as part of the parent property in the C# surface, so the tree should
// not duplicate them as standalone rows. ShowAll is the explicit opt-in.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var coreLibName = typeof(object).Assembly.GetName().Name!;
var stringTypeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
@ -1317,10 +1117,7 @@ public class AssemblyTreeTests @@ -1317,10 +1117,7 @@ public class AssemblyTreeTests
// an IMethod whose AccessorOwner is the IProperty. Without the AccessorOwner-aware
// branch in FindMemberNode, the lookup falls through to "MethodTreeNode child of the
// type" and returns null because accessors live under the property, not the type.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var coreLibName = typeof(object).Assembly.GetName().Name!;
var stringTypeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
@ -1346,10 +1143,7 @@ public class AssemblyTreeTests @@ -1346,10 +1143,7 @@ public class AssemblyTreeTests
// expect a single BaseTypesEntryNode whose label ends in "Object".
// Arrange — boot, wait for assemblies, drill into System.Exception.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var coreLibName = typeof(object).Assembly.GetName().Name!;
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
@ -1371,10 +1165,7 @@ public class AssemblyTreeTests @@ -1371,10 +1165,7 @@ public class AssemblyTreeTests
// Activating a BaseTypesEntryNode jumps the assembly-tree selection to the underlying
// type node — same gesture the WPF tree uses to walk an inheritance chain.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var coreLibName = typeof(object).Assembly.GetName().Name!;
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
@ -1404,10 +1195,7 @@ public class AssemblyTreeTests @@ -1404,10 +1195,7 @@ public class AssemblyTreeTests
// scan the assembly list synchronously — for the small fixture loaded in tests this
// yields a few hits per common base (e.g. SystemException is derived from Exception).
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var coreLibName = typeof(object).Assembly.GetName().Name!;
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
@ -1425,10 +1213,7 @@ public class AssemblyTreeTests @@ -1425,10 +1213,7 @@ public class AssemblyTreeTests
{
// Sealed types can't be derived from, so the DerivedTypes sub-tree must not appear.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var coreLibName = typeof(object).Assembly.GetName().Name!;
var stringNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
@ -1444,10 +1229,7 @@ public class AssemblyTreeTests @@ -1444,10 +1229,7 @@ public class AssemblyTreeTests
{
// System.Object has no base types, so the BaseTypes sub-tree must not appear.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var coreLibName = typeof(object).Assembly.GetName().Name!;
var objectNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
@ -1498,10 +1280,7 @@ public class AssemblyTreeTests @@ -1498,10 +1280,7 @@ public class AssemblyTreeTests
// must NOT return Hidden purely because the field's name doesn't contain the active
// SearchTerm — only ShowApiLevel + ShowMember remain valid hiding criteria.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var settings = AppComposition.Current.GetExport<SettingsService>()
.SessionSettings.LanguageSettings;
@ -1533,13 +1312,9 @@ public class AssemblyTreeTests @@ -1533,13 +1312,9 @@ public class AssemblyTreeTests
// NOT as bare TypeTreeNodes hanging directly off the assembly node.
// Arrange — boot, wait for assemblies, locate CoreLib.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
var assemblyNode = vm.AssemblyTreeModel.FindCoreLib();
assemblyNode.EnsureLazyChildren();
// Assert — exactly one global-namespace node, labelled "-", non-empty contents.
@ -1570,10 +1345,7 @@ public class AssemblyTreeTests @@ -1570,10 +1345,7 @@ public class AssemblyTreeTests
// the current cell to the last row (the user reported having to press it twice).
// Arrange — boot, wait for assemblies, realise the grid.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (window, vm) = await TestHarness.BootAsync(3);
var pane = await window.WaitForComponent<AssemblyListPane>();
var grid = await pane.WaitForComponent<DataGrid>();
@ -1619,10 +1391,7 @@ public class AssemblyTreeTests @@ -1619,10 +1391,7 @@ public class AssemblyTreeTests
// one row must collapse the selection down to just that row.
// Arrange — boot, wait for assemblies, realise the grid, select everything.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (window, vm) = await TestHarness.BootAsync(3);
var pane = await window.WaitForComponent<AssemblyListPane>();
var grid = await pane.WaitForComponent<DataGrid>();
@ -1670,16 +1439,10 @@ public class AssemblyTreeTests @@ -1670,16 +1439,10 @@ public class AssemblyTreeTests
// to do nothing.
// Arrange -- boot, open System.Net.Http (it references many assemblies not in the list).
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var newAsmPath = typeof(System.Net.Http.HttpClient).Assembly.Location;
var registry = AppComposition.Current.GetExport<MainMenuCommandRegistry>();
var openCommand = registry.Commands
.Single(c => c.Metadata.Header == nameof(Resources._Open))
.CreateExport().Value;
var openCommand = AppComposition.Current.GetExport<MainMenuCommandRegistry>().GetCommand(nameof(Resources._Open));
openCommand.Execute(newAsmPath);
await Waiters.WaitForAsync(() =>
vm.AssemblyTreeModel.AssemblyList!.GetAssemblies().Any(a =>

15
ILSpy.Tests/AssemblyList/ReloadAssemblyContextMenuTests.cs

@ -32,7 +32,6 @@ using ILSpy; @@ -32,7 +32,6 @@ using ILSpy;
using ILSpy.AppEnv;
using ILSpy.AssemblyTree;
using ILSpy.TreeNodes;
using ILSpy.ViewModels;
using ILSpy.Views;
using NUnit.Framework;
@ -68,14 +67,9 @@ public class ReloadAssemblyContextMenuTests @@ -68,14 +67,9 @@ public class ReloadAssemblyContextMenuTests
// pollute their right-click menus.
// Arrange — boot, locate the registered entry.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var registry = AppComposition.Current.GetExport<ContextMenuEntryRegistry>();
var reloadEntry = registry.Entries
.Single(e => e.Metadata.Header == nameof(Resources._Reload))
.Value;
var reloadEntry = registry.GetEntry(nameof(Resources._Reload));
var asm = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>("System.Linq");
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
@ -106,10 +100,7 @@ public class ReloadAssemblyContextMenuTests @@ -106,10 +100,7 @@ public class ReloadAssemblyContextMenuTests
// the same file name.
// Arrange — boot, snapshot the LoadedAssembly we'll reload.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (window, vm) = await TestHarness.BootAsync(3);
var pane = await window.WaitForComponent<AssemblyListPane>();
var registry = AppComposition.Current.GetExport<ContextMenuEntryRegistry>();

20
ILSpy.Tests/AssemblyList/RemoveAssemblyContextMenuTests.cs

@ -33,7 +33,6 @@ using ILSpy; @@ -33,7 +33,6 @@ using ILSpy;
using ILSpy.AppEnv;
using ILSpy.AssemblyTree;
using ILSpy.TreeNodes;
using ILSpy.ViewModels;
using ILSpy.Views;
using NUnit.Framework;
@ -69,14 +68,9 @@ public class RemoveAssemblyContextMenuTests @@ -69,14 +68,9 @@ public class RemoveAssemblyContextMenuTests
// e.g. method/type tree contexts.
// Arrange — boot, locate the registered entry.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var registry = AppComposition.Current.GetExport<ContextMenuEntryRegistry>();
var removeEntry = registry.Entries
.Single(e => e.Metadata.Header == nameof(Resources._Remove))
.Value;
var removeEntry = registry.GetEntry(nameof(Resources._Remove));
var asm = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>("System.Linq");
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
@ -110,10 +104,7 @@ public class RemoveAssemblyContextMenuTests @@ -110,10 +104,7 @@ public class RemoveAssemblyContextMenuTests
public async Task Right_Click_Then_Remove_On_An_Assembly_Unloads_It_From_The_List()
{
// Arrange — boot, snapshot a sacrificial assembly + a survivor.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (window, vm) = await TestHarness.BootAsync(3);
var pane = await window.WaitForComponent<AssemblyListPane>();
var registry = AppComposition.Current.GetExport<ContextMenuEntryRegistry>();
@ -152,10 +143,7 @@ public class RemoveAssemblyContextMenuTests @@ -152,10 +143,7 @@ public class RemoveAssemblyContextMenuTests
// when right-clicking on a selected assembly node.
// Arrange — boot, select an assembly node.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (window, vm) = await TestHarness.BootAsync();
await Waiters.WaitForAsync(
() => window.GetVisualDescendants().OfType<AssemblyListPane>().Any());
var pane = await window.WaitForComponent<AssemblyListPane>();

7
ILSpy.Tests/AssemblyList/ShowMetadataTokensTests.cs

@ -27,8 +27,6 @@ using AwesomeAssertions; @@ -27,8 +27,6 @@ using AwesomeAssertions;
using ILSpy;
using ILSpy.AppEnv;
using ILSpy.TreeNodes;
using ILSpy.ViewModels;
using ILSpy.Views;
using NUnit.Framework;
@ -46,10 +44,7 @@ public class ShowMetadataTokensTests @@ -46,10 +44,7 @@ public class ShowMetadataTokensTests
// empty otherwise.
var settings = AppComposition.Current.GetExport<SettingsService>().DisplaySettings;
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
// Drill into a known type and grab a method node.
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(

7
ILSpy.Tests/AssemblyList/TreeViewSettingsLiveReactivityTests.cs

@ -29,8 +29,6 @@ using ICSharpCode.ILSpyX.TreeView; @@ -29,8 +29,6 @@ using ICSharpCode.ILSpyX.TreeView;
using ILSpy;
using ILSpy.AppEnv;
using ILSpy.TreeNodes;
using ILSpy.ViewModels;
using ILSpy.Views;
using NUnit.Framework;
@ -48,10 +46,7 @@ public class TreeViewSettingsLiveReactivityTests @@ -48,10 +46,7 @@ public class TreeViewSettingsLiveReactivityTests
// 'Text' fires when the setting flips.
var settings = AppComposition.Current.GetExport<SettingsService>().DisplaySettings;
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
"System.Linq", "System.Linq", "System.Linq.Enumerable");

7
ILSpy.Tests/AssemblyList/UseNestedNamespaceNodesGridVerification.cs

@ -27,8 +27,6 @@ using ILSpy; @@ -27,8 +27,6 @@ using ILSpy;
using ILSpy.AppEnv;
using ILSpy.AssemblyTree;
using ILSpy.TreeNodes;
using ILSpy.ViewModels;
using ILSpy.Views;
using NUnit.Framework;
@ -49,10 +47,7 @@ public class UseNestedNamespaceNodesGridVerification @@ -49,10 +47,7 @@ public class UseNestedNamespaceNodesGridVerification
var settings = AppComposition.Current.GetExport<SettingsService>().DisplaySettings;
settings.UseNestedNamespaceNodes = false;
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (window, vm) = await TestHarness.BootAsync(3);
var pane = await window.WaitForComponent<AssemblyListPane>();
var grid = pane.FindControl<DataGrid>("TreeGrid")!;

7
ILSpy.Tests/AssemblyList/UseNestedNamespaceNodesLiveTests.cs

@ -26,8 +26,6 @@ using AwesomeAssertions; @@ -26,8 +26,6 @@ using AwesomeAssertions;
using ILSpy;
using ILSpy.AppEnv;
using ILSpy.TreeNodes;
using ILSpy.ViewModels;
using ILSpy.Views;
using NUnit.Framework;
@ -45,10 +43,7 @@ public class UseNestedNamespaceNodesLiveTests @@ -45,10 +43,7 @@ public class UseNestedNamespaceNodesLiveTests
var settings = AppComposition.Current.GetExport<SettingsService>().DisplaySettings;
settings.UseNestedNamespaceNodes = false;
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
try
{

7
ILSpy.Tests/AssemblyList/UseNestedNamespaceNodesScreenshot.cs

@ -26,8 +26,6 @@ using Avalonia.Threading; @@ -26,8 +26,6 @@ using Avalonia.Threading;
using ILSpy;
using ILSpy.AppEnv;
using ILSpy.TreeNodes;
using ILSpy.ViewModels;
using ILSpy.Views;
using NUnit.Framework;
@ -43,10 +41,7 @@ public class UseNestedNamespaceNodesScreenshot @@ -43,10 +41,7 @@ public class UseNestedNamespaceNodesScreenshot
var settings = AppComposition.Current.GetExport<SettingsService>().DisplaySettings;
settings.UseNestedNamespaceNodes = false;
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (window, vm) = await TestHarness.BootAsync(3);
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>("System.Linq");
assemblyNode.IsExpanded = true;

12
ILSpy.Tests/AssemblyList/UseNestedNamespaceNodesTests.cs

@ -26,8 +26,6 @@ using AwesomeAssertions; @@ -26,8 +26,6 @@ using AwesomeAssertions;
using ILSpy;
using ILSpy.AppEnv;
using ILSpy.TreeNodes;
using ILSpy.ViewModels;
using ILSpy.Views;
using NUnit.Framework;
@ -44,10 +42,7 @@ public class UseNestedNamespaceNodesTests @@ -44,10 +42,7 @@ public class UseNestedNamespaceNodesTests
// "System.IO" siblings the default flat mode produces.
var settings = AppComposition.Current.GetExport<SettingsService>().DisplaySettings;
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
try
{
@ -88,10 +83,7 @@ public class UseNestedNamespaceNodesTests @@ -88,10 +83,7 @@ public class UseNestedNamespaceNodesTests
var settings = AppComposition.Current.GetExport<SettingsService>().DisplaySettings;
settings.UseNestedNamespaceNodes = false;
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>("System.Linq");
assemblyNode.Children.Clear();

16
ILSpy.Tests/Docking/LayoutPersistenceTests.cs

@ -30,7 +30,6 @@ using Dock.Model.Core; @@ -30,7 +30,6 @@ using Dock.Model.Core;
using ILSpy.AppEnv;
using ILSpy.Docking;
using ILSpy.ViewModels;
using ILSpy.Views;
using NUnit.Framework;
@ -59,10 +58,7 @@ public class LayoutPersistenceTests @@ -59,10 +58,7 @@ public class LayoutPersistenceTests
[AvaloniaTest]
public async Task SaveLayout_Followed_By_LoadLayout_Round_Trips_The_Live_Layout()
{
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
_ = await TestHarness.BootAsync();
var dockWorkspace = AppComposition.Current.GetExport<DockWorkspace>();
var path = Path.Combine(Path.GetTempPath(), $"ILSpy.Layout.test.{System.Guid.NewGuid():N}.json");
@ -127,10 +123,7 @@ public class LayoutPersistenceTests @@ -127,10 +123,7 @@ public class LayoutPersistenceTests
// `if (factory.MainTab is not { } main) return;` and the user sees an empty
// editor when they click a tree node.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
_ = await TestHarness.BootAsync();
var dockWorkspace = AppComposition.Current.GetExport<DockWorkspace>();
var path = Path.Combine(Path.GetTempPath(), $"ILSpy.Layout.test.{System.Guid.NewGuid():N}.json");
@ -184,10 +177,7 @@ public class LayoutPersistenceTests @@ -184,10 +177,7 @@ public class LayoutPersistenceTests
// DocumentDock comes back with a single fresh MainTab; user's tree selection
// re-projects through ShowSelectedNode as on first launch.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
_ = await TestHarness.BootAsync();
var dockWorkspace = AppComposition.Current.GetExport<DockWorkspace>();

60
ILSpy.Tests/Docking/PreviewTabPromotionTests.cs

@ -58,10 +58,7 @@ public class PreviewTabPromotionTests @@ -58,10 +58,7 @@ public class PreviewTabPromotionTests
{
// Open-in-new-tab tabs are explicit user intent — they should never be replaced
// by subsequent tree-node selections, so they're born pinned (IsPreview=false).
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
"System.Linq", "System.Linq", "System.Linq.Enumerable");
@ -81,10 +78,7 @@ public class PreviewTabPromotionTests @@ -81,10 +78,7 @@ public class PreviewTabPromotionTests
// preview tab spawns at pin time. A fresh preview tab opens lazily later, when a
// tree-selection change finds the active tab frozen — see
// Selecting_A_Different_Node_After_Pin_Opens_A_New_Preview_Tab below.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var factory = (ILSpyDockFactory)vm.DockWorkspace.Factory;
var previousMainTab = factory.MainTab!;
@ -124,10 +118,7 @@ public class PreviewTabPromotionTests @@ -124,10 +118,7 @@ public class PreviewTabPromotionTests
// Pin holds the current tab's content. Selecting a different tree node while
// the (now-pinned) tab is active must spawn a fresh preview tab beside it and
// route the new content there — never overwrite the pinned tab.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var factory = (ILSpyDockFactory)vm.DockWorkspace.Factory;
@ -175,10 +166,7 @@ public class PreviewTabPromotionTests @@ -175,10 +166,7 @@ public class PreviewTabPromotionTests
// Visual contract: the DocumentTabStripItem for the preview MainTab binds its
// FontStyle to ContentTabPage.IsPreview via BoolToFontStyleConverter.Italic.
// Pinned tabs and tool-pane tabs fall through to FontStyle.Normal.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (window, vm) = await TestHarness.BootAsync();
// Wait for the tab strip to realise its items.
await Waiters.WaitForAsync(() => window.GetVisualDescendants().OfType<DocumentTabStripItem>().Any(),
@ -223,10 +211,7 @@ public class PreviewTabPromotionTests @@ -223,10 +211,7 @@ public class PreviewTabPromotionTests
// tab so both MainTab and carve-out exist; close button becomes visible; verify
// the pin's right edge stays inside the tab's right edge.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (window, vm) = await TestHarness.BootAsync(3);
// Open a carve-out so close buttons become visible on both tabs.
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
@ -297,10 +282,7 @@ public class PreviewTabPromotionTests @@ -297,10 +282,7 @@ public class PreviewTabPromotionTests
// width if the title isn't capped. The pin button must still fit inside the tab's
// right edge regardless of title length.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (window, vm) = await TestHarness.BootAsync(3);
// Open carve-out so close button is visible.
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
@ -365,10 +347,7 @@ public class PreviewTabPromotionTests @@ -365,10 +347,7 @@ public class PreviewTabPromotionTests
[AvaloniaTest]
public async Task Inline_Pin_Button_Appears_On_Preview_Tab_And_Pins_When_Clicked()
{
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (window, vm) = await TestHarness.BootAsync();
await Waiters.WaitForAsync(() => window.GetVisualDescendants().OfType<DocumentTabStripItem>().Any(),
System.TimeSpan.FromSeconds(10));
@ -409,10 +388,7 @@ public class PreviewTabPromotionTests @@ -409,10 +388,7 @@ public class PreviewTabPromotionTests
{
// The right-click context-menu Pin entry must be visible on the preview MainTab
// and hidden (its IsVisible flips) on pinned carve-out tabs.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (window, vm) = await TestHarness.BootAsync(3);
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
"System.Linq", "System.Linq", "System.Linq.Enumerable");
vm.DockWorkspace.OpenNodeInNewTab(typeNode);
@ -442,10 +418,7 @@ public class PreviewTabPromotionTests @@ -442,10 +418,7 @@ public class PreviewTabPromotionTests
// because the App.axaml setter used a static `FontStyle="Italic"` value instead
// of the binding to IsPreview. A carve-out tab (IsPreview=false) must render
// Normal, even when sitting next to a preview MainTab.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (window, vm) = await TestHarness.BootAsync(3);
// Open a carve-out tab via the production code path.
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
@ -497,10 +470,7 @@ public class PreviewTabPromotionTests @@ -497,10 +470,7 @@ public class PreviewTabPromotionTests
// (Open / Save / Remove / Exit groups in the File menu, ...). With the menu now living
// as a NativeMenu, separator rendering is the platform's job; this test pins only
// the structural claim that separators are inserted between groups.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (window, _) = await TestHarness.BootAsync();
var nativeMenu = global::Avalonia.Controls.NativeMenu.GetMenu(window)
?? throw new System.InvalidOperationException("MainMenu.Attach should have set NativeMenu on the window");
@ -519,10 +489,7 @@ public class PreviewTabPromotionTests @@ -519,10 +489,7 @@ public class PreviewTabPromotionTests
// hover background. Both are plain Avalonia.Controls.Button instances; the close
// button gets its visual identity from a ControlTheme applied by Dock's tab
// template. Pin should copy that Theme rather than carry its own custom style.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (window, vm) = await TestHarness.BootAsync(3);
// Open a carve-out so the close button is visible alongside the pin (single-tab
// scenario hides the close button).
@ -561,10 +528,7 @@ public class PreviewTabPromotionTests @@ -561,10 +528,7 @@ public class PreviewTabPromotionTests
// selections while any of those are active must spawn a fresh preview tab and
// route the new content there. This test uses a carve-out as a stand-in for the
// frozen-tab family — the IsWritablePreview check returns null for all three.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var factory = (ILSpyDockFactory)vm.DockWorkspace.Factory;

3
ILSpy.Tests/Docking/SingletonDocumentTabTests.cs

@ -45,8 +45,7 @@ public class SingletonDocumentTabTests @@ -45,8 +45,7 @@ public class SingletonDocumentTabTests
{
static void Invoke(MainWindow window, string header)
{
var registry = AppComposition.Current.GetExport<MainMenuCommandRegistry>();
registry.Commands.Single(c => c.Metadata.Header == header).CreateExport().Value.Execute(null);
AppComposition.Current.GetExport<MainMenuCommandRegistry>().GetCommand(header).Execute(null);
Avalonia.Threading.Dispatcher.UIThread.RunJobs();
}

11
ILSpy.Tests/MainWindow/MainToolBarLayoutTests.cs

@ -32,7 +32,6 @@ using ILSpy; @@ -32,7 +32,6 @@ using ILSpy;
using ILSpy.AppEnv;
using ILSpy.Commands;
using ILSpy.Search;
using ILSpy.ViewModels;
using ILSpy.Views;
using NUnit.Framework;
@ -92,10 +91,7 @@ public class MainToolBarLayoutTests @@ -92,10 +91,7 @@ public class MainToolBarLayoutTests
// Avalonia port only had the key-binding wiring — this test pins the toolbar surface area
// and verifies executing the button activates the search pane.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (window, _) = await TestHarness.BootAsync();
var toolbar = await window.WaitForComponent<MainToolBar>();
var rootPanel = toolbar.GetVisualDescendants().OfType<StackPanel>()
@ -160,10 +156,7 @@ public class MainToolBarLayoutTests @@ -160,10 +156,7 @@ public class MainToolBarLayoutTests
// the assembly-list combo each live inside a Grid wrapper (axaml lines 223 / 232 / 245),
// so we tag those positions by descending into the Grid for the ComboBox name.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (window, _) = await TestHarness.BootAsync();
var toolbar = await window.WaitForComponent<MainToolBar>();
var rootPanel = toolbar.GetVisualDescendants().OfType<StackPanel>()

5
ILSpy.Tests/MainWindow/MainWindowTests.cs

@ -137,10 +137,7 @@ public class MainWindowTests @@ -137,10 +137,7 @@ public class MainWindowTests
// transition to Indeterminate, and once done it must drop back to None.
// Arrange — boot window, wait for assemblies, subscribe to TaskbarProgressService events.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var service = AppComposition.Current.GetExport<TaskbarProgressService>();
var states = new List<TaskbarProgressState>();

7
ILSpy.Tests/MainWindow/WindowMenuOpenDocumentsTests.cs

@ -27,11 +27,9 @@ using AwesomeAssertions; @@ -27,11 +27,9 @@ using AwesomeAssertions;
using ICSharpCode.ILSpy.Properties;
using ILSpy.AppEnv;
using ILSpy.Docking;
using ILSpy.TreeNodes;
using ILSpy.ViewModels;
using ILSpy.Views;
using NUnit.Framework;
@ -49,10 +47,7 @@ public class WindowMenuOpenDocumentsTests @@ -49,10 +47,7 @@ public class WindowMenuOpenDocumentsTests
// (NativeMenuBar's visual presenter renders these on Windows / Linux, and the
// system menu bar projects them on macOS).
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (window, vm) = await TestHarness.BootAsync(3);
var firstAsm = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>("System.Linq");
vm.AssemblyTreeModel.SelectNode(firstAsm);

35
ILSpy.Tests/Metadata/DebugDirectoryChildNodesTests.cs

@ -23,11 +23,7 @@ using Avalonia.Headless.NUnit; @@ -23,11 +23,7 @@ using Avalonia.Headless.NUnit;
using AwesomeAssertions;
using ILSpy.AppEnv;
using ILSpy.Metadata;
using ILSpy.TreeNodes;
using ILSpy.ViewModels;
using ILSpy.Views;
using NUnit.Framework;
@ -96,37 +92,26 @@ public class DebugDirectoryChildNodesTests @@ -96,37 +92,26 @@ public class DebugDirectoryChildNodesTests
static async Task<DebugDirectoryTreeNode> GetDebugDirectoryForCoreLib()
{
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
assemblyNode.EnsureLazyChildren();
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single();
metadataNode.EnsureLazyChildren();
var debugDir = metadataNode.Children.OfType<DebugDirectoryTreeNode>().Single();
var (_, vm) = await TestHarness.BootAsync();
var debugDir = vm.AssemblyTreeModel.FindCoreLib()
.GetChild<MetadataTreeNode>()
.GetChild<DebugDirectoryTreeNode>();
debugDir.EnsureLazyChildren();
return debugDir;
}
static async Task<(DebugDirectoryTreeNode node, int rawCount)> GetDebugDirectoryAndRawCountForCoreLib()
{
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
var assemblyNode = vm.AssemblyTreeModel.FindCoreLib();
var module = (ICSharpCode.Decompiler.Metadata.PEFile)assemblyNode.LoadedAssembly.GetMetadataFileOrNull()!;
int rawCount = module.Reader.ReadDebugDirectory().Length;
assemblyNode.EnsureLazyChildren();
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single();
metadataNode.EnsureLazyChildren();
var debugDir = metadataNode.Children.OfType<DebugDirectoryTreeNode>().Single();
var debugDir = assemblyNode
.GetChild<MetadataTreeNode>()
.GetChild<DebugDirectoryTreeNode>();
debugDir.EnsureLazyChildren();
return (debugDir, rawCount);
}

50
ILSpy.Tests/Metadata/EmbeddedPdbTreeTests.cs

@ -25,13 +25,9 @@ using AwesomeAssertions; @@ -25,13 +25,9 @@ using AwesomeAssertions;
using ICSharpCode.ILSpy.Properties;
using ILSpy.AppEnv;
using ILSpy.Commands;
using ILSpy.Metadata;
using ILSpy.Metadata.DebugTables;
using ILSpy.TreeNodes;
using ILSpy.ViewModels;
using ILSpy.Views;
using NUnit.Framework;
@ -51,38 +47,21 @@ public class EmbeddedPdbTreeTests @@ -51,38 +47,21 @@ public class EmbeddedPdbTreeTests
// own Tables subtree contains the PDB-only tables (Document and friends) — the
// canonical entry point for browsing debug metadata in the live app.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
// ICSharpCode.Decompiler ships with an embedded portable PDB (no separate .pdb in
// the build output) — use it as the fixture rather than the test DLL itself, which
// the test SDK builds with portable + sidecar PDB.
var testDllPath = typeof(ICSharpCode.Decompiler.Metadata.MetadataFile).Assembly.Location;
var registry = AppComposition.Current.GetExport<MainMenuCommandRegistry>();
var openCommand = registry.Commands
.Single(c => c.Metadata.Header == nameof(Resources._Open))
.CreateExport().Value;
openCommand.Execute(testDllPath);
await Waiters.WaitForAsync(() =>
vm.AssemblyTreeModel.AssemblyList!.GetAssemblies().Any(a =>
string.Equals(a.FileName, testDllPath, System.StringComparison.OrdinalIgnoreCase)));
var loaded = vm.AssemblyTreeModel.AssemblyList!.GetAssemblies()
.First(a => string.Equals(a.FileName, testDllPath, System.StringComparison.OrdinalIgnoreCase));
await loaded.GetLoadResultAsync();
var loaded = await vm.OpenAssemblyAsync(testDllPath);
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(loaded.ShortName);
assemblyNode.EnsureLazyChildren();
// AssemblyTreeNode now surfaces two MetadataTreeNode children for assemblies with an
// embedded PDB (host metadata + a top-level PDB sibling). This test cares about the
// nested-under-DebugDirectory path, so pick the host metadata explicitly by label.
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>()
.Single(n => (string?)n.Text == Resources.Metadata);
metadataNode.EnsureLazyChildren();
var metadataNode = assemblyNode.GetChild<MetadataTreeNode>(n => (string?)n.Text == Resources.Metadata);
var debugDirectoryNode = metadataNode.Children.OfType<DebugDirectoryTreeNode>().Single();
var debugDirectoryNode = metadataNode.GetChild<DebugDirectoryTreeNode>();
debugDirectoryNode.EnsureLazyChildren();
// The embedded-PDB sub-tree is a MetadataTreeNode whose own children include a
@ -92,8 +71,7 @@ public class EmbeddedPdbTreeTests @@ -92,8 +71,7 @@ public class EmbeddedPdbTreeTests
"the test DLL ships with an embedded portable PDB, so the Debug Directory must expose its metadata as a nested MetadataTreeNode");
embeddedPdbNode!.Text.Should().Be("Debug Metadata (Embedded)");
embeddedPdbNode.EnsureLazyChildren();
var pdbTables = embeddedPdbNode.Children.OfType<MetadataTablesTreeNode>().Single();
var pdbTables = embeddedPdbNode.GetChild<MetadataTablesTreeNode>();
pdbTables.EnsureLazyChildren();
pdbTables.Children.OfType<DocumentTableTreeNode>().Should().NotBeEmpty(
"the embedded PDB's Tables subtree must expose the Document table — the lead debug-only table that always has rows in a populated PDB");
@ -107,24 +85,10 @@ public class EmbeddedPdbTreeTests @@ -107,24 +85,10 @@ public class EmbeddedPdbTreeTests
// folder), not only nested under DebugDirectory. Without this, users have to drill
// three levels deep to reach PDB metadata — same content, harder to find.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var testDllPath = typeof(ICSharpCode.Decompiler.Metadata.MetadataFile).Assembly.Location;
var registry = AppComposition.Current.GetExport<MainMenuCommandRegistry>();
var openCommand = registry.Commands
.Single(c => c.Metadata.Header == nameof(Resources._Open))
.CreateExport().Value;
openCommand.Execute(testDllPath);
await Waiters.WaitForAsync(() =>
vm.AssemblyTreeModel.AssemblyList!.GetAssemblies().Any(a =>
string.Equals(a.FileName, testDllPath, System.StringComparison.OrdinalIgnoreCase)));
var loaded = vm.AssemblyTreeModel.AssemblyList!.GetAssemblies()
.First(a => string.Equals(a.FileName, testDllPath, System.StringComparison.OrdinalIgnoreCase));
await loaded.GetLoadResultAsync();
var loaded = await vm.OpenAssemblyAsync(testDllPath);
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(loaded.ShortName);
assemblyNode.EnsureLazyChildren();

60
ILSpy.Tests/Metadata/HeapTreeTests.cs

@ -23,11 +23,7 @@ using Avalonia.Headless.NUnit; @@ -23,11 +23,7 @@ using Avalonia.Headless.NUnit;
using AwesomeAssertions;
using ILSpy.AppEnv;
using ILSpy.Metadata;
using ILSpy.TreeNodes;
using ILSpy.ViewModels;
using ILSpy.Views;
using NUnit.Framework;
@ -45,15 +41,9 @@ public class HeapTreeTests @@ -45,15 +41,9 @@ public class HeapTreeTests
// reachable from the Metadata folder regardless of whether the file's debug
// metadata is present.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
assemblyNode.EnsureLazyChildren();
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single();
var metadataNode = vm.AssemblyTreeModel.FindCoreLib().GetChild<MetadataTreeNode>();
metadataNode.EnsureLazyChildren();
metadataNode.Children.OfType<StringHeapTreeNode>().Should().ContainSingle();
@ -69,17 +59,11 @@ public class HeapTreeTests @@ -69,17 +59,11 @@ public class HeapTreeTests
// row through Avalonia's built-in row virtualisation, so unlike the Phase 1 text
// dump there's no preview cap and no truncation footer.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
assemblyNode.EnsureLazyChildren();
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single();
metadataNode.EnsureLazyChildren();
var heapNode = metadataNode.Children.OfType<StringHeapTreeNode>().Single();
var heapNode = vm.AssemblyTreeModel.FindCoreLib()
.GetChild<MetadataTreeNode>()
.GetChild<StringHeapTreeNode>();
vm.AssemblyTreeModel.SelectNode(heapNode);
var tab = await vm.DockWorkspace.WaitForMetadataTabAsync();
@ -93,17 +77,11 @@ public class HeapTreeTests @@ -93,17 +77,11 @@ public class HeapTreeTests
[AvaloniaTest]
public async Task GuidHeapTreeNode_Opens_Grid_Tab_With_Index_Length_Value_Columns()
{
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
assemblyNode.EnsureLazyChildren();
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single();
metadataNode.EnsureLazyChildren();
var heapNode = metadataNode.Children.OfType<GuidHeapTreeNode>().Single();
var (_, vm) = await TestHarness.BootAsync();
var heapNode = vm.AssemblyTreeModel.FindCoreLib()
.GetChild<MetadataTreeNode>()
.GetChild<GuidHeapTreeNode>();
vm.AssemblyTreeModel.SelectNode(heapNode);
var tab = await vm.DockWorkspace.WaitForMetadataTabAsync();
@ -118,17 +96,11 @@ public class HeapTreeTests @@ -118,17 +96,11 @@ public class HeapTreeTests
[AvaloniaTest]
public async Task BlobHeapTreeNode_Opens_Grid_Tab_With_Offset_Length_Value_Columns()
{
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
assemblyNode.EnsureLazyChildren();
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single();
metadataNode.EnsureLazyChildren();
var heapNode = metadataNode.Children.OfType<BlobHeapTreeNode>().Single();
var (_, vm) = await TestHarness.BootAsync();
var heapNode = vm.AssemblyTreeModel.FindCoreLib()
.GetChild<MetadataTreeNode>()
.GetChild<BlobHeapTreeNode>();
vm.AssemblyTreeModel.SelectNode(heapNode);
var tab = await vm.DockWorkspace.WaitForMetadataTabAsync();

18
ILSpy.Tests/Metadata/HideEmptyMetadataTablesTests.cs

@ -29,7 +29,6 @@ using ILSpy.AppEnv; @@ -29,7 +29,6 @@ using ILSpy.AppEnv;
using ILSpy.Metadata;
using ILSpy.TreeNodes;
using ILSpy.ViewModels;
using ILSpy.Views;
using NUnit.Framework;
@ -47,10 +46,7 @@ public class HideEmptyMetadataTablesTests @@ -47,10 +46,7 @@ public class HideEmptyMetadataTablesTests
var settings = AppComposition.Current.GetExport<SettingsService>().DisplaySettings;
settings.HideEmptyMetadataTables = true;
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var coreLibName = typeof(object).Assembly.GetName().Name!;
var tables = LoadTablesNode(vm, coreLibName, out var metadata);
@ -71,10 +67,7 @@ public class HideEmptyMetadataTablesTests @@ -71,10 +67,7 @@ public class HideEmptyMetadataTablesTests
settings.HideEmptyMetadataTables = false;
try
{
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var coreLibName = typeof(object).Assembly.GetName().Name!;
var tables = LoadTablesNode(vm, coreLibName, out var _);
@ -94,10 +87,9 @@ public class HideEmptyMetadataTablesTests @@ -94,10 +87,9 @@ public class HideEmptyMetadataTablesTests
out System.Reflection.Metadata.MetadataReader metadata)
{
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(assemblyName);
assemblyNode.EnsureLazyChildren();
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single();
metadataNode.EnsureLazyChildren();
var tables = metadataNode.Children.OfType<MetadataTablesTreeNode>().Single();
var tables = assemblyNode
.GetChild<MetadataTreeNode>()
.GetChild<MetadataTablesTreeNode>();
tables.EnsureLazyChildren();
metadata = assemblyNode.LoadedAssembly.GetMetadataFileOrNull()!.Metadata;
return tables;

13
ILSpy.Tests/Metadata/HideEmptyMetadataTablesUiBindingTests.cs

@ -28,7 +28,6 @@ using ILSpy.AppEnv; @@ -28,7 +28,6 @@ using ILSpy.AppEnv;
using ILSpy.Metadata;
using ILSpy.TreeNodes;
using ILSpy.ViewModels;
using ILSpy.Views;
using NUnit.Framework;
@ -46,10 +45,7 @@ public class HideEmptyMetadataTablesUiBindingTests @@ -46,10 +45,7 @@ public class HideEmptyMetadataTablesUiBindingTests
// SessionSettings.HideEmptyMetadataTables (an orphaned parallel setting).
var settings = AppComposition.Current.GetExport<SettingsService>();
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var coreLibName = typeof(object).Assembly.GetName().Name!;
@ -80,10 +76,9 @@ public class HideEmptyMetadataTablesUiBindingTests @@ -80,10 +76,9 @@ public class HideEmptyMetadataTablesUiBindingTests
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(assemblyName);
assemblyNode.Children.Clear();
assemblyNode.LazyLoading = true;
assemblyNode.EnsureLazyChildren();
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single();
metadataNode.EnsureLazyChildren();
var tables = metadataNode.Children.OfType<MetadataTablesTreeNode>().Single();
var tables = assemblyNode
.GetChild<MetadataTreeNode>()
.GetChild<MetadataTablesTreeNode>();
tables.EnsureLazyChildren();
return tables.Children.OfType<MetadataTableTreeNode>().Count();
}

15
ILSpy.Tests/Metadata/MetadataDisablesLanguageSwitchingTests.cs

@ -26,11 +26,9 @@ using Avalonia.VisualTree; @@ -26,11 +26,9 @@ using Avalonia.VisualTree;
using AwesomeAssertions;
using ILSpy;
using ILSpy.AppEnv;
using ILSpy.Metadata;
using ILSpy.TreeNodes;
using ILSpy.ViewModels;
using ILSpy.Views;
using NUnit.Framework;
@ -88,10 +86,7 @@ public class MetadataDisablesLanguageSwitchingTests @@ -88,10 +86,7 @@ public class MetadataDisablesLanguageSwitchingTests
// DockWorkspace.ActiveContentTabPage.SupportsLanguageSwitching must flip to false.
// Selecting a decompilable type after that must flip it back to true.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (window, vm) = await TestHarness.BootAsync();
var toolbar = await window.WaitForComponent<MainToolBar>();
var languageCombo = toolbar.GetVisualDescendants().OfType<ComboBox>()
@ -103,11 +98,9 @@ public class MetadataDisablesLanguageSwitchingTests @@ -103,11 +98,9 @@ public class MetadataDisablesLanguageSwitchingTests
// Navigate to a metadata node (DOS Header is a stable choice — every PE has one).
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
assemblyNode.EnsureLazyChildren();
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single();
metadataNode.EnsureLazyChildren();
var dosHeader = metadataNode.Children.OfType<DosHeaderTreeNode>().Single();
var dosHeader = vm.AssemblyTreeModel.FindCoreLib()
.GetChild<MetadataTreeNode>()
.GetChild<DosHeaderTreeNode>();
vm.AssemblyTreeModel.SelectNode(dosHeader);
await Waiters.WaitForAsync(

21
ILSpy.Tests/Metadata/MetadataFilterTests.cs

@ -28,10 +28,8 @@ using Avalonia.VisualTree; @@ -28,10 +28,8 @@ using Avalonia.VisualTree;
using AwesomeAssertions;
using ILSpy.AppEnv;
using ILSpy.Metadata;
using ILSpy.Metadata.CorTables;
using ILSpy.TreeNodes;
using ILSpy.ViewModels;
using ILSpy.Views;
@ -295,19 +293,12 @@ public class MetadataFilterTests @@ -295,19 +293,12 @@ public class MetadataFilterTests
// assert on the live DataGridCollectionView's Count (what the user actually sees on
// screen) — not on the predicate evaluated over Items, which can pass even when the
// CollectionView's Filter is silently null.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
assemblyNode.EnsureLazyChildren();
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single();
metadataNode.EnsureLazyChildren();
var tablesNode = metadataNode.Children.OfType<MetadataTablesTreeNode>().Single();
tablesNode.EnsureLazyChildren();
var typeDefNode = tablesNode.Children.OfType<TypeDefTableTreeNode>().Single();
var (window, vm) = await TestHarness.BootAsync();
var typeDefNode = vm.AssemblyTreeModel.FindCoreLib()
.GetChild<MetadataTreeNode>()
.GetChild<MetadataTablesTreeNode>()
.GetChild<TypeDefTableTreeNode>();
vm.AssemblyTreeModel.SelectNode(typeDefNode);
var tab = await vm.DockWorkspace.WaitForMetadataTabAsync();

16
ILSpy.Tests/Metadata/MetadataNodeIconParityTests.cs

@ -24,11 +24,7 @@ using Avalonia.Media; @@ -24,11 +24,7 @@ using Avalonia.Media;
using AwesomeAssertions;
using ILSpy.AppEnv;
using ILSpy.Metadata;
using ILSpy.TreeNodes;
using ILSpy.ViewModels;
using ILSpy.Views;
using NUnit.Framework;
@ -47,15 +43,9 @@ public class MetadataNodeIconParityTests @@ -47,15 +43,9 @@ public class MetadataNodeIconParityTests
// restored per-node parity.
// Arrange — boot, expand CoreLib's Metadata folder.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
assemblyNode.EnsureLazyChildren();
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single();
var (_, vm) = await TestHarness.BootAsync();
var metadataNode = vm.AssemblyTreeModel.FindCoreLib().GetChild<MetadataTreeNode>();
metadataNode.EnsureLazyChildren();
var children = metadataNode.Children;

19
ILSpy.Tests/Metadata/MetadataProtocolHandlerDrillDownTests.cs

@ -28,7 +28,6 @@ using AwesomeAssertions; @@ -28,7 +28,6 @@ using AwesomeAssertions;
using ILSpy.AppEnv;
using ILSpy.Metadata;
using ILSpy.Metadata.CorTables;
using ILSpy.TreeNodes;
using ILSpy.ViewModels;
using ILSpy.Views;
@ -117,18 +116,11 @@ public class MetadataProtocolHandlerDrillDownTests @@ -117,18 +116,11 @@ public class MetadataProtocolHandlerDrillDownTests
static async Task<MetadataTreeNode> GetMetadataTreeNodeForCoreLib()
{
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
assemblyNode.EnsureLazyChildren();
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single();
var (_, vm) = await TestHarness.BootAsync();
var metadataNode = vm.AssemblyTreeModel.FindCoreLib().GetChild<MetadataTreeNode>();
// Drill-down needs the children realised; EnsureLazyChildren cascades only one level.
metadataNode.EnsureLazyChildren();
var tablesNode = metadataNode.Children.OfType<MetadataTablesTreeNode>().Single();
var tablesNode = metadataNode.GetChild<MetadataTablesTreeNode>();
tablesNode.EnsureLazyChildren();
return metadataNode;
}
@ -137,8 +129,7 @@ public class MetadataProtocolHandlerDrillDownTests @@ -137,8 +129,7 @@ public class MetadataProtocolHandlerDrillDownTests
{
var metadataNode = await GetMetadataTreeNodeForCoreLib();
var vm = (MainWindowViewModel)AppComposition.Current.GetExport<MainWindow>().DataContext!;
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
var assemblyNode = vm.AssemblyTreeModel.FindCoreLib();
var module = assemblyNode.LoadedAssembly.GetMetadataFileOrNull()!;
// The handler is exported as IProtocolHandler, not as itself — fish out the
// MetadataProtocolHandler concrete impl from the contract list.

38
ILSpy.Tests/Metadata/MetadataRowActivationTests.cs

@ -47,19 +47,12 @@ public class MetadataRowActivationTests @@ -47,19 +47,12 @@ public class MetadataRowActivationTests
// RowActivated, and confirm the assembly tree's TypeTreeNode for System.Object
// is now selected — that's the gateway to the decompiler view.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
assemblyNode.EnsureLazyChildren();
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single();
metadataNode.EnsureLazyChildren();
var tablesNode = metadataNode.Children.OfType<MetadataTablesTreeNode>().Single();
tablesNode.EnsureLazyChildren();
var typeDefNode = tablesNode.Children.OfType<TypeDefTableTreeNode>().Single();
var (_, vm) = await TestHarness.BootAsync();
var typeDefNode = vm.AssemblyTreeModel.FindCoreLib()
.GetChild<MetadataTreeNode>()
.GetChild<MetadataTablesTreeNode>()
.GetChild<TypeDefTableTreeNode>();
vm.AssemblyTreeModel.SelectNode(typeDefNode);
var tab = await vm.DockWorkspace.WaitForMetadataTabAsync();
@ -84,19 +77,12 @@ public class MetadataRowActivationTests @@ -84,19 +77,12 @@ public class MetadataRowActivationTests
// Shift+double-click on a metadata row should NOT replace the active tab — it should
// open a brand-new decompiler tab containing the row's entity. The single-tab-reuse
// path stays put.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
assemblyNode.EnsureLazyChildren();
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single();
metadataNode.EnsureLazyChildren();
var tablesNode = metadataNode.Children.OfType<MetadataTablesTreeNode>().Single();
tablesNode.EnsureLazyChildren();
var typeDefNode = tablesNode.Children.OfType<TypeDefTableTreeNode>().Single();
var (_, vm) = await TestHarness.BootAsync();
var typeDefNode = vm.AssemblyTreeModel.FindCoreLib()
.GetChild<MetadataTreeNode>()
.GetChild<MetadataTablesTreeNode>()
.GetChild<TypeDefTableTreeNode>();
vm.AssemblyTreeModel.SelectNode(typeDefNode);
var metadataTab = await vm.DockWorkspace.WaitForMetadataTabAsync();

55
ILSpy.Tests/Metadata/MetadataTabSessionRestoreTests.cs

@ -25,12 +25,9 @@ using AwesomeAssertions; @@ -25,12 +25,9 @@ using AwesomeAssertions;
using ILSpy;
using ILSpy.AppEnv;
using ILSpy.Commands;
using ILSpy.Metadata;
using ILSpy.Metadata.CorTables;
using ILSpy.TreeNodes;
using ILSpy.ViewModels;
using ILSpy.Views;
using NUnit.Framework;
@ -46,19 +43,12 @@ public class MetadataTabSessionRestoreTests @@ -46,19 +43,12 @@ public class MetadataTabSessionRestoreTests
// restart (or a tree-path lookup) can land back on the same node. The metadata
// sub-tree's ToString() values must be stable for that round-trip to work.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
assemblyNode.EnsureLazyChildren();
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single();
metadataNode.EnsureLazyChildren();
var tablesNode = metadataNode.Children.OfType<MetadataTablesTreeNode>().Single();
tablesNode.EnsureLazyChildren();
var typeDefNode = tablesNode.Children.OfType<TypeDefTableTreeNode>().Single();
var typeDefNode = vm.AssemblyTreeModel.FindCoreLib()
.GetChild<MetadataTreeNode>()
.GetChild<MetadataTablesTreeNode>()
.GetChild<TypeDefTableTreeNode>();
vm.AssemblyTreeModel.SelectNode(typeDefNode);
@ -82,40 +72,21 @@ public class MetadataTabSessionRestoreTests @@ -82,40 +72,21 @@ public class MetadataTabSessionRestoreTests
// The path-restore must walk through both Metadata folders and land on a debug-only
// table.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var testDllPath = typeof(ICSharpCode.Decompiler.Metadata.MetadataFile).Assembly.Location;
var registry = AppComposition.Current.GetExport<MainMenuCommandRegistry>();
var openCommand = registry.Commands
.Single(c => c.Metadata.Header == nameof(ICSharpCode.ILSpy.Properties.Resources._Open))
.CreateExport().Value;
openCommand.Execute(testDllPath);
await Waiters.WaitForAsync(() =>
vm.AssemblyTreeModel.AssemblyList!.GetAssemblies().Any(a =>
string.Equals(a.FileName, testDllPath, System.StringComparison.OrdinalIgnoreCase)));
var loaded = vm.AssemblyTreeModel.AssemblyList!.GetAssemblies()
.First(a => string.Equals(a.FileName, testDllPath, System.StringComparison.OrdinalIgnoreCase));
await loaded.GetLoadResultAsync();
var loaded = await vm.OpenAssemblyAsync(testDllPath);
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(loaded.ShortName);
assemblyNode.EnsureLazyChildren();
// AssemblyTreeNode surfaces the embedded PDB's metadata as a second top-level
// MetadataTreeNode sibling, so disambiguate by label — this test exercises the
// nested-under-DebugDirectory path.
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>()
.Single(n => (string?)n.Text == ICSharpCode.ILSpy.Properties.Resources.Metadata);
metadataNode.EnsureLazyChildren();
var debugDirectoryNode = metadataNode.Children.OfType<DebugDirectoryTreeNode>().Single();
debugDirectoryNode.EnsureLazyChildren();
var embeddedPdbNode = debugDirectoryNode.Children.OfType<MetadataTreeNode>().Single();
embeddedPdbNode.EnsureLazyChildren();
var pdbTables = embeddedPdbNode.Children.OfType<MetadataTablesTreeNode>().Single();
pdbTables.EnsureLazyChildren();
var documentTable = pdbTables.Children.OfType<global::ILSpy.Metadata.DebugTables.DocumentTableTreeNode>().Single();
var documentTable = assemblyNode
.GetChild<MetadataTreeNode>(n => (string?)n.Text == ICSharpCode.ILSpy.Properties.Resources.Metadata)
.GetChild<DebugDirectoryTreeNode>()
.GetChild<MetadataTreeNode>()
.GetChild<MetadataTablesTreeNode>()
.GetChild<global::ILSpy.Metadata.DebugTables.DocumentTableTreeNode>();
vm.AssemblyTreeModel.SelectNode(documentTable);

46
ILSpy.Tests/Metadata/MetadataTablesTreeTests.cs

@ -24,11 +24,7 @@ using Avalonia.Headless.NUnit; @@ -24,11 +24,7 @@ using Avalonia.Headless.NUnit;
using AwesomeAssertions;
using ILSpy.AppEnv;
using ILSpy.Metadata;
using ILSpy.TreeNodes;
using ILSpy.ViewModels;
using ILSpy.Views;
using NUnit.Framework;
@ -45,15 +41,9 @@ public class MetadataTablesTreeTests @@ -45,15 +41,9 @@ public class MetadataTablesTreeTests
// Phase 1 lazy-loads its children with a generic placeholder per table; Phase 1e
// replaces them with per-table typed leaves carrying the actual row data.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
assemblyNode.EnsureLazyChildren();
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single();
var metadataNode = vm.AssemblyTreeModel.FindCoreLib().GetChild<MetadataTreeNode>();
metadataNode.EnsureLazyChildren();
var tablesNode = metadataNode.Children.OfType<MetadataTablesTreeNode>().Single();
@ -68,17 +58,11 @@ public class MetadataTablesTreeTests @@ -68,17 +58,11 @@ public class MetadataTablesTreeTests
[AvaloniaTest]
public async Task MetadataTablesTreeNode_LazyLoads_One_Child_Per_NonEmpty_Table()
{
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
assemblyNode.EnsureLazyChildren();
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single();
metadataNode.EnsureLazyChildren();
var tablesNode = metadataNode.Children.OfType<MetadataTablesTreeNode>().Single();
var (_, vm) = await TestHarness.BootAsync();
var tablesNode = vm.AssemblyTreeModel.FindCoreLib()
.GetChild<MetadataTreeNode>()
.GetChild<MetadataTablesTreeNode>();
tablesNode.EnsureLazyChildren();
var tableChildren = tablesNode.Children.OfType<MetadataTableTreeNode>().ToList();
@ -93,17 +77,11 @@ public class MetadataTablesTreeTests @@ -93,17 +77,11 @@ public class MetadataTablesTreeTests
[AvaloniaTest]
public async Task MetadataTablesTreeNode_Decompiles_To_A_Per_Table_Row_Count_Summary()
{
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
assemblyNode.EnsureLazyChildren();
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single();
metadataNode.EnsureLazyChildren();
var tablesNode = metadataNode.Children.OfType<MetadataTablesTreeNode>().Single();
var (_, vm) = await TestHarness.BootAsync();
var tablesNode = vm.AssemblyTreeModel.FindCoreLib()
.GetChild<MetadataTreeNode>()
.GetChild<MetadataTablesTreeNode>();
vm.AssemblyTreeModel.SelectNode(tablesNode);
var tab = await vm.DockWorkspace.WaitForDecompiledTextAsync();

38
ILSpy.Tests/Metadata/MetadataTokenNavigationTests.cs

@ -24,10 +24,8 @@ using Avalonia.Headless.NUnit; @@ -24,10 +24,8 @@ using Avalonia.Headless.NUnit;
using AwesomeAssertions;
using ILSpy.AppEnv;
using ILSpy.Metadata;
using ILSpy.Metadata.CorTables;
using ILSpy.TreeNodes;
using ILSpy.ViewModels;
using ILSpy.Views;
@ -45,19 +43,12 @@ public class MetadataTokenNavigationTests @@ -45,19 +43,12 @@ public class MetadataTokenNavigationTests
// must dispatch through the docking host to the TypeRef / TypeDef / TypeSpec table
// the token belongs to and scroll the receiving grid to the referenced row.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
assemblyNode.EnsureLazyChildren();
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single();
metadataNode.EnsureLazyChildren();
var tablesNode = metadataNode.Children.OfType<MetadataTablesTreeNode>().Single();
tablesNode.EnsureLazyChildren();
var typeDefNode = tablesNode.Children.OfType<TypeDefTableTreeNode>().Single();
var typeDefNode = vm.AssemblyTreeModel.FindCoreLib()
.GetChild<MetadataTreeNode>()
.GetChild<MetadataTablesTreeNode>()
.GetChild<TypeDefTableTreeNode>();
vm.AssemblyTreeModel.SelectNode(typeDefNode);
var tab = await vm.DockWorkspace.WaitForMetadataTabAsync();
@ -94,19 +85,12 @@ public class MetadataTokenNavigationTests @@ -94,19 +85,12 @@ public class MetadataTokenNavigationTests
// entry. It should fire the same NavigateToCellRequested path as the hyperlink-style
// click, with the focused (or last-clicked) token cell as the target.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
assemblyNode.EnsureLazyChildren();
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single();
metadataNode.EnsureLazyChildren();
var tablesNode = metadataNode.Children.OfType<MetadataTablesTreeNode>().Single();
tablesNode.EnsureLazyChildren();
var typeDefNode = tablesNode.Children.OfType<TypeDefTableTreeNode>().Single();
var (window, vm) = await TestHarness.BootAsync();
var typeDefNode = vm.AssemblyTreeModel.FindCoreLib()
.GetChild<MetadataTreeNode>()
.GetChild<MetadataTablesTreeNode>()
.GetChild<TypeDefTableTreeNode>();
vm.AssemblyTreeModel.SelectNode(typeDefNode);
var tab = await vm.DockWorkspace.WaitForMetadataTabAsync();

24
ILSpy.Tests/Metadata/MetadataTreeShapeTests.cs

@ -23,11 +23,8 @@ using Avalonia.Headless.NUnit; @@ -23,11 +23,8 @@ using Avalonia.Headless.NUnit;
using AwesomeAssertions;
using ILSpy.AppEnv;
using ILSpy.Metadata;
using ILSpy.TreeNodes;
using ILSpy.ViewModels;
using ILSpy.Views;
using NUnit.Framework;
@ -45,17 +42,12 @@ public class MetadataTreeShapeTests @@ -45,17 +42,12 @@ public class MetadataTreeShapeTests
// MetadataTreeNode instance ready to lazy-load its children.
// Arrange — boot, wait for assemblies, expand CoreLib (always a PE).
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
assemblyNode.EnsureLazyChildren();
var assemblyNode = vm.AssemblyTreeModel.FindCoreLib();
// Assert — exactly one MetadataTreeNode child and it precedes the References folder.
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single();
var metadataNode = assemblyNode.GetChild<MetadataTreeNode>();
var children = assemblyNode.Children.ToList();
children.IndexOf(metadataNode).Should().BeLessThan(
children.IndexOf(assemblyNode.Children.OfType<ReferenceFolderTreeNode>().Single()),
@ -71,15 +63,9 @@ public class MetadataTreeShapeTests @@ -71,15 +63,9 @@ public class MetadataTreeShapeTests
// pipeline rather than introducing a new tab type for v1.
// Arrange — boot, find the Metadata folder under CoreLib.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
assemblyNode.EnsureLazyChildren();
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single();
var metadataNode = vm.AssemblyTreeModel.FindCoreLib().GetChild<MetadataTreeNode>();
// Act — select it through the helper that mirrors a click (loads lazy children + assigns
// SelectedItem), wait for the decompile to finish.

105
ILSpy.Tests/Metadata/PEHeaderTreeTests.cs

@ -23,11 +23,7 @@ using Avalonia.Headless.NUnit; @@ -23,11 +23,7 @@ using Avalonia.Headless.NUnit;
using AwesomeAssertions;
using ILSpy.AppEnv;
using ILSpy.Metadata;
using ILSpy.TreeNodes;
using ILSpy.ViewModels;
using ILSpy.Views;
using NUnit.Framework;
@ -44,15 +40,9 @@ public class PEHeaderTreeTests @@ -44,15 +40,9 @@ public class PEHeaderTreeTests
// user shifting between hosts sees the same tree shape. Each node is the entry point
// to a header / table view; Phase 1 renders text, Phase 2 swaps to a DataGrid.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
assemblyNode.EnsureLazyChildren();
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single();
var metadataNode = vm.AssemblyTreeModel.FindCoreLib().GetChild<MetadataTreeNode>();
metadataNode.EnsureLazyChildren();
metadataNode.Children.OfType<DosHeaderTreeNode>().Should().ContainSingle();
@ -78,17 +68,11 @@ public class PEHeaderTreeTests @@ -78,17 +68,11 @@ public class PEHeaderTreeTests
// row per field, columns reflected from the Entry shape (Member / Offset / Size /
// Value / Meaning).
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
assemblyNode.EnsureLazyChildren();
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single();
metadataNode.EnsureLazyChildren();
var dosNode = metadataNode.Children.OfType<DosHeaderTreeNode>().Single();
var dosNode = vm.AssemblyTreeModel.FindCoreLib()
.GetChild<MetadataTreeNode>()
.GetChild<DosHeaderTreeNode>();
vm.AssemblyTreeModel.SelectNode(dosNode);
var tab = await vm.DockWorkspace.WaitForMetadataTabAsync();
@ -113,17 +97,12 @@ public class PEHeaderTreeTests @@ -113,17 +97,12 @@ public class PEHeaderTreeTests
// decompiler tab but never restored it as the user-visible page. Both round-trip
// directions should swap the active dockable to the right tab type.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
assemblyNode.EnsureLazyChildren();
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single();
metadataNode.EnsureLazyChildren();
var dosNode = metadataNode.Children.OfType<DosHeaderTreeNode>().Single();
var assemblyNode = vm.AssemblyTreeModel.FindCoreLib();
var dosNode = assemblyNode
.GetChild<MetadataTreeNode>()
.GetChild<DosHeaderTreeNode>();
// Step 1 — pick a regular entity node (the assembly itself decompiles to its
// AssemblyDef metadata via the decompiler text path). Decompiler tab should be active.
@ -151,17 +130,12 @@ public class PEHeaderTreeTests @@ -151,17 +130,12 @@ public class PEHeaderTreeTests
// the docking host can lazily create and activate the decompiler tab even when the
// document area starts out holding only the metadata grid.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
assemblyNode.EnsureLazyChildren();
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single();
metadataNode.EnsureLazyChildren();
var dosNode = metadataNode.Children.OfType<DosHeaderTreeNode>().Single();
var assemblyNode = vm.AssemblyTreeModel.FindCoreLib();
var dosNode = assemblyNode
.GetChild<MetadataTreeNode>()
.GetChild<DosHeaderTreeNode>();
vm.AssemblyTreeModel.SelectNode(dosNode);
await vm.DockWorkspace.WaitForMetadataTabAsync();
@ -183,18 +157,12 @@ public class PEHeaderTreeTests @@ -183,18 +157,12 @@ public class PEHeaderTreeTests
// previous inner view rendered (the user would see a "Decompiling" spinner that
// never goes away because the prior decompiler tab is still on screen).
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
assemblyNode.EnsureLazyChildren();
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single();
metadataNode.EnsureLazyChildren();
var dosNode = metadataNode.Children.OfType<DosHeaderTreeNode>().Single();
var coffNode = metadataNode.Children.OfType<CoffHeaderTreeNode>().Single();
var assemblyNode = vm.AssemblyTreeModel.FindCoreLib();
var metadataNode = assemblyNode.GetChild<MetadataTreeNode>();
var dosNode = metadataNode.GetChild<DosHeaderTreeNode>();
var coffNode = metadataNode.GetChild<CoffHeaderTreeNode>();
var factoryFactory = (global::ILSpy.Docking.ILSpyDockFactory)vm.DockWorkspace.Factory;
var documents = factoryFactory.Documents!;
var mainTab = factoryFactory.MainTab!;
@ -228,19 +196,12 @@ public class PEHeaderTreeTests @@ -228,19 +196,12 @@ public class PEHeaderTreeTests
// decompiler tab reuses itself across tree-node selections. Otherwise every click
// piles up a new dockable and the tab strip grows without bound.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
assemblyNode.EnsureLazyChildren();
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single();
metadataNode.EnsureLazyChildren();
var metadataNode = vm.AssemblyTreeModel.FindCoreLib().GetChild<MetadataTreeNode>();
var dosNode = metadataNode.Children.OfType<DosHeaderTreeNode>().Single();
var coffNode = metadataNode.Children.OfType<CoffHeaderTreeNode>().Single();
var dosNode = metadataNode.GetChild<DosHeaderTreeNode>();
var coffNode = metadataNode.GetChild<CoffHeaderTreeNode>();
vm.AssemblyTreeModel.SelectNode(dosNode);
var firstTab = await vm.DockWorkspace.WaitForMetadataTabAsync();
@ -263,17 +224,11 @@ public class PEHeaderTreeTests @@ -263,17 +224,11 @@ public class PEHeaderTreeTests
[AvaloniaTest]
public async Task CoffHeaderTreeNode_Opens_A_DataGrid_Tab_With_Machine_And_Characteristics_Rows()
{
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
assemblyNode.EnsureLazyChildren();
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single();
metadataNode.EnsureLazyChildren();
var coffNode = metadataNode.Children.OfType<CoffHeaderTreeNode>().Single();
var (_, vm) = await TestHarness.BootAsync();
var coffNode = vm.AssemblyTreeModel.FindCoreLib()
.GetChild<MetadataTreeNode>()
.GetChild<CoffHeaderTreeNode>();
vm.AssemblyTreeModel.SelectNode(coffNode);
var tab = await vm.DockWorkspace.WaitForMetadataTabAsync();

13
ILSpy.Tests/Metadata/ShowInMetadataContextMenuTests.cs

@ -27,14 +27,9 @@ using AwesomeAssertions; @@ -27,14 +27,9 @@ using AwesomeAssertions;
using ICSharpCode.Decompiler.TypeSystem;
using ILSpy;
using ILSpy.AppEnv;
using ILSpy.Commands;
using ILSpy.Metadata;
using ILSpy.Metadata.CorTables;
using ILSpy.TextView;
using ILSpy.TreeNodes;
using ILSpy.ViewModels;
using ILSpy.Views;
using NUnit.Framework;
@ -51,13 +46,9 @@ public class ShowInMetadataContextMenuTests @@ -51,13 +46,9 @@ public class ShowInMetadataContextMenuTests
// verify the entry's IsVisible/Execute drives the dock workspace into the right
// metadata table for the entity's MetadataToken.kind.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
var assemblyNode = vm.AssemblyTreeModel.FindCoreLib();
// Resolve System.Object's IEntity via the type system, since the entry only acts
// on IEntity-bearing references.

73
ILSpy.Tests/Metadata/TypedMetadataTableTreeTests.cs

@ -24,12 +24,9 @@ using Avalonia.Headless.NUnit; @@ -24,12 +24,9 @@ using Avalonia.Headless.NUnit;
using AwesomeAssertions;
using ILSpy.AppEnv;
using ILSpy.Metadata;
using ILSpy.Metadata.CorTables;
using ILSpy.TreeNodes;
using ILSpy.ViewModels;
using ILSpy.Views;
using NUnit.Framework;
@ -46,18 +43,12 @@ public class TypedMetadataTableTreeTests @@ -46,18 +43,12 @@ public class TypedMetadataTableTreeTests
// tab whose Items list mirrors the table's rows; a known reference's display name
// must show up on at least one row.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>("System.Linq");
assemblyNode.EnsureLazyChildren();
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single();
metadataNode.EnsureLazyChildren();
var tablesNode = metadataNode.Children.OfType<MetadataTablesTreeNode>().Single();
tablesNode.EnsureLazyChildren();
var assemblyRefNode = tablesNode.Children.OfType<AssemblyRefTableTreeNode>().Single();
var assemblyRefNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>("System.Linq")
.GetChild<MetadataTreeNode>()
.GetChild<MetadataTablesTreeNode>()
.GetChild<AssemblyRefTableTreeNode>();
assemblyRefNode.Kind.Should().Be(TableIndex.AssemblyRef);
assemblyRefNode.RowCount.Should().BeGreaterThan(0);
@ -76,19 +67,12 @@ public class TypedMetadataTableTreeTests @@ -76,19 +67,12 @@ public class TypedMetadataTableTreeTests
[AvaloniaTest]
public async Task TypeDefTableTreeNode_Opens_A_Grid_Including_System_Object()
{
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
assemblyNode.EnsureLazyChildren();
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single();
metadataNode.EnsureLazyChildren();
var tablesNode = metadataNode.Children.OfType<MetadataTablesTreeNode>().Single();
tablesNode.EnsureLazyChildren();
var typeDefNode = tablesNode.Children.OfType<TypeDefTableTreeNode>().Single();
var (_, vm) = await TestHarness.BootAsync();
var typeDefNode = vm.AssemblyTreeModel.FindCoreLib()
.GetChild<MetadataTreeNode>()
.GetChild<MetadataTablesTreeNode>()
.GetChild<TypeDefTableTreeNode>();
typeDefNode.Kind.Should().Be(TableIndex.TypeDef);
typeDefNode.RowCount.Should().BeGreaterThan(1000);
@ -107,17 +91,11 @@ public class TypedMetadataTableTreeTests @@ -107,17 +91,11 @@ public class TypedMetadataTableTreeTests
// Field / MethodDef are by far the largest tables in any real assembly. The typed
// leaves should report the right kind and expose row data through the grid view.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
assemblyNode.EnsureLazyChildren();
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single();
metadataNode.EnsureLazyChildren();
var tablesNode = metadataNode.Children.OfType<MetadataTablesTreeNode>().Single();
var (_, vm) = await TestHarness.BootAsync();
var tablesNode = vm.AssemblyTreeModel.FindCoreLib()
.GetChild<MetadataTreeNode>()
.GetChild<MetadataTablesTreeNode>();
tablesNode.EnsureLazyChildren();
tablesNode.Children.OfType<FieldTableTreeNode>().Should().ContainSingle();
@ -137,19 +115,12 @@ public class TypedMetadataTableTreeTests @@ -137,19 +115,12 @@ public class TypedMetadataTableTreeTests
[AvaloniaTest]
public async Task ModuleTableTreeNode_Opens_A_Grid_With_The_Single_Module_Row()
{
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
assemblyNode.EnsureLazyChildren();
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single();
metadataNode.EnsureLazyChildren();
var tablesNode = metadataNode.Children.OfType<MetadataTablesTreeNode>().Single();
tablesNode.EnsureLazyChildren();
var moduleNode = tablesNode.Children.OfType<ModuleTableTreeNode>().Single();
var (_, vm) = await TestHarness.BootAsync();
var moduleNode = vm.AssemblyTreeModel.FindCoreLib()
.GetChild<MetadataTreeNode>()
.GetChild<MetadataTablesTreeNode>()
.GetChild<ModuleTableTreeNode>();
moduleNode.RowCount.Should().Be(1, "Module is a one-row table by spec");

5
ILSpy.Tests/Navigation/BrowseBackForwardCommandTests.cs

@ -79,10 +79,7 @@ public class BrowseBackForwardCommandTests @@ -79,10 +79,7 @@ public class BrowseBackForwardCommandTests
// Execute pops one entry. Verifies via the live MenuItem that ends up in the View menu.
// Arrange — boot the window, load assemblies, expand Enumerable, capture two methods.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (window, vm) = await TestHarness.BootAsync(3);
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
"System.Linq", "System.Linq", "System.Linq.Enumerable");

18
ILSpy.Tests/Navigation/NavigationTests.cs

@ -25,10 +25,7 @@ using Avalonia.VisualTree; @@ -25,10 +25,7 @@ using Avalonia.VisualTree;
using AwesomeAssertions;
using ILSpy.AppEnv;
using ILSpy.TreeNodes;
using ILSpy.ViewModels;
using ILSpy.Views;
using NUnit.Framework;
@ -46,10 +43,7 @@ public class NavigationTests @@ -46,10 +43,7 @@ public class NavigationTests
// Arrange — boot the window, wait for assemblies, expand Enumerable, capture two methods
// that we'll bounce between.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
"System.Linq", "System.Linq", "System.Linq.Enumerable");
@ -98,10 +92,7 @@ public class NavigationTests @@ -98,10 +92,7 @@ public class NavigationTests
// displaced entries pushed onto the forward stack).
// Arrange — boot, wait for assemblies, expand Enumerable, capture three methods.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (window, vm) = await TestHarness.BootAsync(3);
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
"System.Linq", "System.Linq", "System.Linq.Enumerable");
@ -167,10 +158,7 @@ public class NavigationTests @@ -167,10 +158,7 @@ public class NavigationTests
// Arrange — boot, wait for assemblies, expand System.Exception so we can select its
// BaseTypesTreeNode child.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (window, vm) = await TestHarness.BootAsync(3);
var coreLibName = typeof(object).Assembly.GetName().Name!;
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(

18
ILSpy.Tests/Navigation/ViewStateRoundTripTests.cs

@ -23,12 +23,9 @@ using Avalonia.Headless.NUnit; @@ -23,12 +23,9 @@ using Avalonia.Headless.NUnit;
using AwesomeAssertions;
using ILSpy.AppEnv;
using ILSpy.Navigation;
using ILSpy.TextView;
using ILSpy.TreeNodes;
using ILSpy.ViewModels;
using ILSpy.Views;
using NUnit.Framework;
@ -47,10 +44,7 @@ public class ViewStateRoundTripTests @@ -47,10 +44,7 @@ public class ViewStateRoundTripTests
[AvaloniaTest]
public async Task Back_Restores_Caret_Position_The_User_Left_On_The_Previous_Node()
{
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var dockWorkspace = vm.DockWorkspace;
@ -111,10 +105,7 @@ public class ViewStateRoundTripTests @@ -111,10 +105,7 @@ public class ViewStateRoundTripTests
// a deterministic snapshot, exercise the navigation pipeline, and assert the snapshot
// survives the capture -> entry -> pending hops.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var dockWorkspace = vm.DockWorkspace;
var coreLibName = typeof(object).Assembly.GetName().Name!;
@ -165,10 +156,7 @@ public class ViewStateRoundTripTests @@ -165,10 +156,7 @@ public class ViewStateRoundTripTests
// as missing, but it was already implemented. This test makes a regression in any
// of the three wirings (command, toolbar button, Alt+Right) detectable.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
((object?)vm.DockWorkspace.NavigateForwardCommand).Should().NotBeNull();
((object?)vm.DockWorkspace.NavigateBackCommand).Should().NotBeNull();

20
ILSpy.Tests/Options/ApiVisibilityFilterTests.cs

@ -153,10 +153,7 @@ public class ApiVisibilityFilterTests @@ -153,10 +153,7 @@ public class ApiVisibilityFilterTests
// re-evaluates visibility when ShowApiLevel changes.
// Arrange — boot, find a CoreLib type with mixed accessibility (String has many).
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var coreLibName = typeof(object).Assembly.GetName().Name!;
var stringType = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(coreLibName, "System", "System.String");
@ -187,10 +184,7 @@ public class ApiVisibilityFilterTests @@ -187,10 +184,7 @@ public class ApiVisibilityFilterTests
// new HierarchicalModel reference whenever the setting changes.
// Arrange — boot, locate the pane.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (window, _) = await TestHarness.BootAsync();
await Waiters.WaitForAsync(
() => window.GetVisualDescendants().OfType<AssemblyListPane>().Any());
var pane = await window.WaitForComponent<AssemblyListPane>();
@ -219,10 +213,7 @@ public class ApiVisibilityFilterTests @@ -219,10 +213,7 @@ public class ApiVisibilityFilterTests
// than Foreground brush) avoids depending on theme brush resolution.
// Arrange — boot, ensure ShowApiLevel=All so non-public methods are visible at all.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (window, vm) = await TestHarness.BootAsync();
var settings = AppComposition.Current.GetExport<SettingsService>().SessionSettings.LanguageSettings;
settings.ShowApiLevel = ApiVisibility.All;
@ -308,10 +299,7 @@ public class ApiVisibilityFilterTests @@ -308,10 +299,7 @@ public class ApiVisibilityFilterTests
static async Task<(MainWindowViewModel vm, TypeTreeNode enumerableNode)> BootAndExpandEnumerableAsync()
{
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
var node = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
"System.Linq", "System.Linq", "System.Linq.Enumerable");
node.IsExpanded = true;

6
ILSpy.Tests/Options/OptionsPageScrollReachTests.cs

@ -58,10 +58,8 @@ public class OptionsPageScrollReachTests @@ -58,10 +58,8 @@ public class OptionsPageScrollReachTests
window.Show();
Dispatcher.UIThread.RunJobs();
var registry = AppComposition.Current.GetExport<MainMenuCommandRegistry>();
var command = registry.Commands
.Single(c => c.Metadata.Header == nameof(Resources._Options))
.CreateExport().Value;
var command = AppComposition.Current.GetExport<MainMenuCommandRegistry>()
.GetCommand(nameof(Resources._Options));
command.Execute(null);
Dispatcher.UIThread.RunJobs();

43
ILSpy.Tests/Options/OptionsTabTests.cs

@ -66,10 +66,8 @@ public class OptionsTabTests @@ -66,10 +66,8 @@ public class OptionsTabTests
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
var registry = AppComposition.Current.GetExport<MainMenuCommandRegistry>();
var command = registry.Commands
.Single(c => c.Metadata.Header == nameof(Resources._Options))
.CreateExport().Value;
var command = AppComposition.Current.GetExport<MainMenuCommandRegistry>()
.GetCommand(nameof(Resources._Options));
command.Execute(null);
@ -92,15 +90,11 @@ public class OptionsTabTests @@ -92,15 +90,11 @@ public class OptionsTabTests
// document is still Options — so the user sees no visible change. The fix:
// ShowSelectedNode pulls focus back to MainTab whenever a tree-selection writes
// new content there.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var (_, vm) = await TestHarness.BootAsync(3);
// Open Options and confirm it's the active document.
var registry = AppComposition.Current.GetExport<MainMenuCommandRegistry>();
registry.Commands.Single(c => c.Metadata.Header == nameof(Resources._Options))
.CreateExport().Value.Execute(null);
AppComposition.Current.GetExport<MainMenuCommandRegistry>()
.GetCommand(nameof(Resources._Options)).Execute(null);
var documents = vm.DockWorkspace.Documents!;
var optionsTab = documents.VisibleDockables!.OfType<ContentTabPage>()
@ -133,9 +127,8 @@ public class OptionsTabTests @@ -133,9 +127,8 @@ public class OptionsTabTests
// tab should use the bare Resources.Options ("Options") instead.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var registry = AppComposition.Current.GetExport<MainMenuCommandRegistry>();
registry.Commands.Single(c => c.Metadata.Header == nameof(Resources._Options))
.CreateExport().Value.Execute(null);
AppComposition.Current.GetExport<MainMenuCommandRegistry>()
.GetCommand(nameof(Resources._Options)).Execute(null);
var vm = (MainWindowViewModel)window.DataContext!;
var model = (OptionsPageModel)vm.DockWorkspace.Documents!.VisibleDockables!
@ -157,10 +150,8 @@ public class OptionsTabTests @@ -157,10 +150,8 @@ public class OptionsTabTests
// byte-for-byte.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var registry = AppComposition.Current.GetExport<MainMenuCommandRegistry>();
var command = registry.Commands
.Single(c => c.Metadata.Header == nameof(Resources._Options))
.CreateExport().Value;
var command = AppComposition.Current.GetExport<MainMenuCommandRegistry>()
.GetCommand(nameof(Resources._Options));
command.Execute(null);
var vm = (MainWindowViewModel)window.DataContext!;
@ -181,10 +172,8 @@ public class OptionsTabTests @@ -181,10 +172,8 @@ public class OptionsTabTests
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
var registry = AppComposition.Current.GetExport<MainMenuCommandRegistry>();
var command = registry.Commands
.Single(c => c.Metadata.Header == nameof(Resources._Options))
.CreateExport().Value;
var command = AppComposition.Current.GetExport<MainMenuCommandRegistry>()
.GetCommand(nameof(Resources._Options));
command.Execute(null);
command.Execute(null);
@ -205,12 +194,11 @@ public class OptionsTabTests @@ -205,12 +194,11 @@ public class OptionsTabTests
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var settings = AppComposition.Current.GetExport<SettingsService>();
var registry = AppComposition.Current.GetExport<MainMenuCommandRegistry>();
var liveBefore = settings.DecompilerSettings.UsingDeclarations;
registry.Commands.Single(c => c.Metadata.Header == nameof(Resources._Options))
.CreateExport().Value.Execute(null);
AppComposition.Current.GetExport<MainMenuCommandRegistry>()
.GetCommand(nameof(Resources._Options)).Execute(null);
var vm = (MainWindowViewModel)window.DataContext!;
var model = (OptionsPageModel)vm.DockWorkspace.Documents!.VisibleDockables!
@ -238,9 +226,8 @@ public class OptionsTabTests @@ -238,9 +226,8 @@ public class OptionsTabTests
// default; Display and Misc panels are untouched (verified by leaving them alone).
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var registry = AppComposition.Current.GetExport<MainMenuCommandRegistry>();
registry.Commands.Single(c => c.Metadata.Header == nameof(Resources._Options))
.CreateExport().Value.Execute(null);
AppComposition.Current.GetExport<MainMenuCommandRegistry>()
.GetCommand(nameof(Resources._Options)).Execute(null);
var vm = (MainWindowViewModel)window.DataContext!;
var model = (OptionsPageModel)vm.DockWorkspace.Documents!.VisibleDockables!

24
ILSpy.Tests/Search/ScopeSearchToTests.cs

@ -43,15 +43,10 @@ public class ScopeSearchToTests @@ -43,15 +43,10 @@ public class ScopeSearchToTests
[AvaloniaTest]
public async Task Scope_Search_To_Assembly_Prepends_InAssembly_To_The_Search_Term()
{
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var registry = AppComposition.Current.GetExport<ContextMenuEntryRegistry>();
var entry = registry.Entries
.Single(e => e.Metadata.Header == nameof(Resources.ScopeSearchToThisAssembly))
.Value;
var entry = AppComposition.Current.GetExport<ContextMenuEntryRegistry>()
.GetEntry(nameof(Resources.ScopeSearchToThisAssembly));
var asm = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>("System.Linq");
var search = AppComposition.Current.GetExport<SearchPaneModel>();
@ -71,15 +66,10 @@ public class ScopeSearchToTests @@ -71,15 +66,10 @@ public class ScopeSearchToTests
[AvaloniaTest]
public async Task Scope_Search_To_Namespace_Prepends_InNamespace_To_The_Search_Term()
{
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var registry = AppComposition.Current.GetExport<ContextMenuEntryRegistry>();
var entry = registry.Entries
.Single(e => e.Metadata.Header == nameof(Resources.ScopeSearchToThisNamespace))
.Value;
var (_, vm) = await TestHarness.BootAsync();
var entry = AppComposition.Current.GetExport<ContextMenuEntryRegistry>()
.GetEntry(nameof(Resources.ScopeSearchToThisNamespace));
var ns = vm.AssemblyTreeModel.FindNode<NamespaceTreeNode>("System.Linq", "System.Linq");
var search = AppComposition.Current.GetExport<SearchPaneModel>();

10
ILSpy.Tests/Search/SearchInputFocusTests.cs

@ -44,10 +44,7 @@ public class SearchInputFocusTests @@ -44,10 +44,7 @@ public class SearchInputFocusTests
// can start typing immediately. The VM raises FocusRequested; the SearchPane
// code-behind subscribes and calls Focus() on the TextBox.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
await TestHarness.BootAsync();
var search = AppComposition.Current.GetExport<SearchPaneModel>();
var dockWorkspace = AppComposition.Current.GetExport<DockWorkspace>();
@ -64,10 +61,7 @@ public class SearchInputFocusTests @@ -64,10 +61,7 @@ public class SearchInputFocusTests
[AvaloniaTest]
public async Task SearchInput_Is_Focused_After_ShowSearchCommand_Pumps_Through_The_Dispatcher()
{
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (window, _) = await TestHarness.BootAsync();
var pane = await window.WaitForComponent<SearchPane>();
var dockWorkspace = AppComposition.Current.GetExport<DockWorkspace>();

10
ILSpy.Tests/Search/SearchPaneAssemblyListChangedTests.cs

@ -48,10 +48,7 @@ public class SearchPaneAssemblyListChangedTests @@ -48,10 +48,7 @@ public class SearchPaneAssemblyListChangedTests
[AvaloniaTest]
public async Task Search_Restarts_When_A_User_Loaded_Assembly_Is_Added()
{
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var search = AppComposition.Current.GetExport<SearchPaneModel>();
search.SearchTerm = "Object";
@ -82,10 +79,7 @@ public class SearchPaneAssemblyListChangedTests @@ -82,10 +79,7 @@ public class SearchPaneAssemblyListChangedTests
// Auto-loaded dependencies fire from result navigation in a large assembly; restarting
// the search there would feed back into more loads → more events → flicker. The
// handler MUST take the early-out path.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var search = AppComposition.Current.GetExport<SearchPaneModel>();
search.SearchTerm = "Object";

28
ILSpy.Tests/Search/SearchPaneStreamingTests.cs

@ -45,13 +45,9 @@ public class SearchPaneStreamingTests @@ -45,13 +45,9 @@ public class SearchPaneStreamingTests
// logic works for the fixture. If this fails, the bug is in the strategy / fixture;
// if it passes but the orchestrator test below fails, the bug is in our wiring.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<global::ILSpy.TreeNodes.AssemblyTreeNode>(coreLibName);
var assemblyNode = vm.AssemblyTreeModel.FindCoreLib();
var module = await assemblyNode.LoadedAssembly.GetMetadataFileAsync();
var queue = new System.Collections.Concurrent.ConcurrentQueue<ICSharpCode.ILSpyX.Search.SearchResult>();
@ -82,10 +78,7 @@ public class SearchPaneStreamingTests @@ -82,10 +78,7 @@ public class SearchPaneStreamingTests
// search across the loaded fixture assemblies, and wait for at least one matching
// result to land in the Results collection.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
await TestHarness.BootAsync();
var search = AppComposition.Current.GetExport<SearchPaneModel>();
search.Results.Clear();
@ -107,10 +100,7 @@ public class SearchPaneStreamingTests @@ -107,10 +100,7 @@ public class SearchPaneStreamingTests
// Switching to an empty term tears down the running search and clears the visible
// results. The user shouldn't see stale rows after they've explicitly cleared the box.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
await TestHarness.BootAsync();
var search = AppComposition.Current.GetExport<SearchPaneModel>();
search.Results.Clear();
@ -131,10 +121,7 @@ public class SearchPaneStreamingTests @@ -131,10 +121,7 @@ public class SearchPaneStreamingTests
// contain". Confirm end-to-end: a Type search for "String -Builder" returns names
// containing "String" (System.String, StringComparer, …) but never StringBuilder.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
await TestHarness.BootAsync();
var search = AppComposition.Current.GetExport<SearchPaneModel>();
search.Results.Clear();
@ -159,10 +146,7 @@ public class SearchPaneStreamingTests @@ -159,10 +146,7 @@ public class SearchPaneStreamingTests
// (length-aware; ignores IL backtick generic arity). Confirm: =String must match
// System.String but NOT StringBuilder or StringComparer.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
await TestHarness.BootAsync();
var search = AppComposition.Current.GetExport<SearchPaneModel>();
search.Results.Clear();

10
ILSpy.Tests/Search/SearchProgressTests.cs

@ -44,10 +44,7 @@ public class SearchProgressTests @@ -44,10 +44,7 @@ public class SearchProgressTests
[AvaloniaTest]
public async Task IsSearching_Flips_True_When_A_Search_Starts_And_False_When_It_Finishes()
{
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
await TestHarness.BootAsync();
var search = AppComposition.Current.GetExport<SearchPaneModel>();
search.SearchTerm = string.Empty;
@ -84,10 +81,7 @@ public class SearchProgressTests @@ -84,10 +81,7 @@ public class SearchProgressTests
[AvaloniaTest]
public async Task SearchResult_Image_Properties_Are_Non_Null_So_Row_Icons_Render()
{
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
await TestHarness.BootAsync();
var search = AppComposition.Current.GetExport<SearchPaneModel>();
search.SearchTerm = string.Empty;

5
ILSpy.Tests/Search/SearchResultNavigationTests.cs

@ -46,10 +46,7 @@ public class SearchResultNavigationTests @@ -46,10 +46,7 @@ public class SearchResultNavigationTests
// to activate it, and confirm the assembly tree's SelectedItem becomes the matching
// tree node.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var (_, vm) = await TestHarness.BootAsync();
var search = AppComposition.Current.GetExport<SearchPaneModel>();
search.Results.Clear();

5
ILSpy.Tests/Search/SearchResultSortOrderTests.cs

@ -47,10 +47,7 @@ public class SearchResultSortOrderTests @@ -47,10 +47,7 @@ public class SearchResultSortOrderTests
// Name asc (StringComparer.Ordinal). Mirrors WPF's SearchPane.xaml.cs:288-290
// which captures the comparer at start-of-search based on DisplaySettings.SortResults.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
await TestHarness.BootAsync();
var settings = AppComposition.Current.GetExport<SettingsService>();
var originalSortResults = settings.DisplaySettings.SortResults;

5
ILSpy.Tests/Search/ShowSearchCommandTests.cs

@ -42,10 +42,7 @@ public class ShowSearchCommandTests @@ -42,10 +42,7 @@ public class ShowSearchCommandTests
// MainWindow.KeyBindings. Here we drive the command directly and verify the
// activation reaches the SearchPaneModel.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
await TestHarness.BootAsync();
var dockWorkspace = AppComposition.Current.GetExport<DockWorkspace>();
var search = AppComposition.Current.GetExport<SearchPaneModel>();

112
ILSpy.Tests/TestHarness.cs

@ -0,0 +1,112 @@ @@ -0,0 +1,112 @@
// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using ICSharpCode.ILSpy.Properties;
using ICSharpCode.ILSpyX;
using global::ILSpy;
using global::ILSpy.AppEnv;
using global::ILSpy.Commands;
using global::ILSpy.ViewModels;
using global::ILSpy.Views;
namespace ICSharpCode.ILSpy.Tests;
/// <summary>
/// Boots and drives the shared MEF-composed application for headless UI tests, collapsing the
/// four-line prologue (resolve <see cref="MainWindow"/>, show it, cast its DataContext, wait for
/// the assembly list) and the "open an assembly via the production command and wait for it to
/// load" dance that nearly every fixture repeats. The container is rebuilt per test by
/// <see cref="ResetAppStateAttribute"/>, so each call hands back that test's own singletons.
/// </summary>
public static class TestHarness
{
/// <summary>
/// Resolves the shared <see cref="MainWindow"/>, shows it, and waits until the default
/// assembly list has loaded at least <paramref name="minimumAssemblies"/> assemblies.
/// Returns the window and its <see cref="MainWindowViewModel"/> — the two handles the test
/// body almost always needs next.
/// </summary>
public static async Task<(MainWindow Window, MainWindowViewModel ViewModel)> BootAsync(
int minimumAssemblies = 1,
TimeSpan? timeout = null)
{
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumAssemblies, timeout);
return (window, vm);
}
/// <summary>
/// Looks up a [ExportMainMenuCommand]-tagged command by its <c>Header</c> metadata (pass the
/// resource key, e.g. <c>nameof(Resources._Open)</c>) and materialises a fresh instance.
/// </summary>
public static ICommand GetCommand(this MainMenuCommandRegistry registry, string header)
{
ArgumentNullException.ThrowIfNull(registry);
return registry.Commands
.Single(c => string.Equals(c.Metadata.Header, header, StringComparison.Ordinal))
.CreateExport().Value;
}
/// <summary>
/// Looks up a [ExportContextMenuEntry]-tagged entry by its <c>Header</c> metadata.
/// </summary>
public static IContextMenuEntry GetEntry(this ContextMenuEntryRegistry registry, string header)
{
ArgumentNullException.ThrowIfNull(registry);
return registry.Entries
.Single(e => string.Equals(e.Metadata.Header, header, StringComparison.Ordinal))
.Value;
}
/// <summary>
/// Opens <paramref name="path"/> through the production Open command, waits for it to appear
/// in the assembly list, awaits its load result, and returns the resulting
/// <see cref="LoadedAssembly"/>. This is the canonical "add a test assembly to the tree"
/// sequence — registry lookup, execute, poll-for-appearance, await-load — in one call.
/// </summary>
public static async Task<LoadedAssembly> OpenAssemblyAsync(
this MainWindowViewModel vm,
string path,
TimeSpan? timeout = null)
{
ArgumentNullException.ThrowIfNull(vm);
ArgumentNullException.ThrowIfNull(path);
var open = AppComposition.Current.GetExport<MainMenuCommandRegistry>().GetCommand(nameof(Resources._Open));
open.Execute(path);
await Waiters.WaitForAsync(
() => vm.AssemblyTreeModel.AssemblyList!.GetAssemblies()
.Any(a => string.Equals(a.FileName, path, StringComparison.OrdinalIgnoreCase)),
timeout,
$"assembly '{path}' to appear in the active list");
var loaded = vm.AssemblyTreeModel.AssemblyList!.GetAssemblies()
.First(a => string.Equals(a.FileName, path, StringComparison.OrdinalIgnoreCase));
await loaded.GetLoadResultAsync();
return loaded;
}
}

51
ILSpy.Tests/TreeNavigation.cs

@ -22,6 +22,7 @@ using System.Linq; @@ -22,6 +22,7 @@ using System.Linq;
using ICSharpCode.ILSpyX.TreeView;
using global::ILSpy.AssemblyTree;
using global::ILSpy.TreeNodes;
namespace ICSharpCode.ILSpy.Tests;
@ -36,6 +37,56 @@ namespace ICSharpCode.ILSpy.Tests; @@ -36,6 +37,56 @@ namespace ICSharpCode.ILSpy.Tests;
/// </summary>
public static class TreeNavigation
{
/// <summary>
/// Short name of the runtime core library (e.g. <c>System.Private.CoreLib</c>) — the one
/// assembly the headless suite is guaranteed to have loaded. Use as the first path segment.
/// </summary>
public static string CoreLibName { get; } = typeof(object).Assembly.GetName().Name!;
/// <summary>
/// Resolves the core-library <see cref="AssemblyTreeNode"/>, the fixture every test can rely
/// on. Replaces the repeated <c>FindNode&lt;AssemblyTreeNode&gt;(typeof(object).Assembly...)</c>.
/// </summary>
internal static AssemblyTreeNode FindCoreLib(this AssemblyTreeModel atm)
=> atm.FindNode<AssemblyTreeNode>(CoreLibName);
/// <summary>
/// Materialises <paramref name="parent"/>'s lazy children and returns the single child of type
/// <typeparamref name="T"/>. The workhorse for drilling metadata sub-trees, replacing the
/// <c>node.EnsureLazyChildren(); node.Children.OfType&lt;T&gt;().Single()</c> pair. Chains.
/// </summary>
public static T GetChild<T>(this SharpTreeNode parent) where T : SharpTreeNode
{
ArgumentNullException.ThrowIfNull(parent);
parent.EnsureLazyChildren();
return parent.Children.OfType<T>().Single();
}
/// <summary>
/// Materialises lazy children and returns the single child of type <typeparamref name="T"/>
/// matching <paramref name="predicate"/> (e.g. a method picked by name).
/// </summary>
public static T GetChild<T>(this SharpTreeNode parent, Func<T, bool> predicate) where T : SharpTreeNode
{
ArgumentNullException.ThrowIfNull(parent);
ArgumentNullException.ThrowIfNull(predicate);
parent.EnsureLazyChildren();
return parent.Children.OfType<T>().Single(predicate);
}
/// <summary>
/// Materialises lazy children, flips <see cref="SharpTreeNode.IsExpanded"/>, and returns the
/// node so callers can keep drilling. Replaces the
/// <c>node.EnsureLazyChildren(); node.IsExpanded = true;</c> pair.
/// </summary>
public static T Expand<T>(this T node) where T : SharpTreeNode
{
ArgumentNullException.ThrowIfNull(node);
node.EnsureLazyChildren();
node.IsExpanded = true;
return node;
}
public static SharpTreeNode FindNode(this AssemblyTreeModel atm, params string[] segments)
{
ArgumentNullException.ThrowIfNull(atm);

Loading…
Cancel
Save