mirror of https://github.com/icsharpcode/ILSpy.git
Browse Source
Wires the four Display Settings "Tree view options" through to the rendered tree: Assisted-by: Claude:claude-opus-4-7:Claude Codepull/3755/head
19 changed files with 818 additions and 29 deletions
@ -0,0 +1,86 @@
@@ -0,0 +1,86 @@
|
||||
// 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.Linq; |
||||
using System.Text.RegularExpressions; |
||||
using System.Threading.Tasks; |
||||
|
||||
using Avalonia.Headless.NUnit; |
||||
|
||||
using AwesomeAssertions; |
||||
|
||||
using ILSpy; |
||||
using ILSpy.AppEnv; |
||||
using ILSpy.TreeNodes; |
||||
using ILSpy.ViewModels; |
||||
using ILSpy.Views; |
||||
|
||||
using NUnit.Framework; |
||||
|
||||
namespace ICSharpCode.ILSpy.Tests; |
||||
|
||||
[TestFixture] |
||||
public class ShowMetadataTokensTests |
||||
{ |
||||
[AvaloniaTest] |
||||
public async Task Member_Text_Carries_Hex_Or_Decimal_Token_Suffix_Per_DisplaySettings() |
||||
{ |
||||
// Display Settings "Show metadata tokens" + "Show metadata tokens in base 10" must
|
||||
// reach the tree-node Text values. Mirrors WPF ILSpyTreeNode.GetSuffixString —
|
||||
// suffix is " @xNNNNNNNN" (hex, 8 digits) or " @NNNNNNNNN" (decimal) when enabled,
|
||||
// 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); |
||||
|
||||
// Drill into a known type and grab a method node.
|
||||
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>( |
||||
"System.Linq", "System.Linq", "System.Linq.Enumerable"); |
||||
typeNode.IsExpanded = true; |
||||
var method = typeNode.Children.OfType<MethodTreeNode>() |
||||
.First(m => m.MethodDefinition.Name == "AsEnumerable"); |
||||
|
||||
try |
||||
{ |
||||
settings.ShowMetadataTokens = false; |
||||
((string)method.Text).Should().NotContain(" @", |
||||
"with ShowMetadataTokens=false the Text must have no token suffix"); |
||||
|
||||
settings.ShowMetadataTokens = true; |
||||
settings.ShowMetadataTokensInBase10 = false; |
||||
var hexText = (string)method.Text; |
||||
hexText.Should().MatchRegex(@" @[0-9a-fA-F]{8}$", |
||||
"with ShowMetadataTokens=true and Base10=false the suffix is ' @' + 8 hex digits"); |
||||
|
||||
settings.ShowMetadataTokensInBase10 = true; |
||||
var decText = (string)method.Text; |
||||
decText.Should().MatchRegex(@" @\d+$", |
||||
"with Base10=true the suffix switches to ' @' + decimal digits"); |
||||
decText.Should().NotMatchRegex(@" @[0-9a-fA-F]{8}$", |
||||
"decimal form must not coincidentally match the hex regex (token > 16777215 makes them distinguishable)"); |
||||
} |
||||
finally |
||||
{ |
||||
settings.ShowMetadataTokens = false; |
||||
settings.ShowMetadataTokensInBase10 = false; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,93 @@
@@ -0,0 +1,93 @@
|
||||
// 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.ComponentModel; |
||||
using System.Linq; |
||||
using System.Threading.Tasks; |
||||
|
||||
using Avalonia.Headless.NUnit; |
||||
|
||||
using AwesomeAssertions; |
||||
|
||||
using ICSharpCode.ILSpyX.TreeView; |
||||
|
||||
using ILSpy; |
||||
using ILSpy.AppEnv; |
||||
using ILSpy.TreeNodes; |
||||
using ILSpy.ViewModels; |
||||
using ILSpy.Views; |
||||
|
||||
using NUnit.Framework; |
||||
|
||||
namespace ICSharpCode.ILSpy.Tests; |
||||
|
||||
[TestFixture] |
||||
public class TreeViewSettingsLiveReactivityTests |
||||
{ |
||||
[AvaloniaTest] |
||||
public async Task Toggling_ShowMetadataTokens_Raises_PropertyChanged_On_Tree_Node_Text() |
||||
{ |
||||
// Live re-render contract: when the user flips Display Settings → "Show metadata
|
||||
// tokens" the visible tree nodes must re-fetch their Text (which now includes the
|
||||
// suffix). Verified by subscribing to a tree node's PropertyChanged and asserting
|
||||
// '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 typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>( |
||||
"System.Linq", "System.Linq", "System.Linq.Enumerable"); |
||||
|
||||
try |
||||
{ |
||||
settings.ShowMetadataTokens = false; |
||||
settings.ShowMetadataTokensInBase10 = false; |
||||
|
||||
int textFiredCount = 0; |
||||
PropertyChangedEventHandler handler = (_, e) => { |
||||
if (e.PropertyName == nameof(SharpTreeNode.Text)) |
||||
textFiredCount++; |
||||
}; |
||||
typeNode.PropertyChanged += handler; |
||||
|
||||
try |
||||
{ |
||||
settings.ShowMetadataTokens = true; |
||||
textFiredCount.Should().BeGreaterThan(0, |
||||
"flipping ShowMetadataTokens must raise PropertyChanged(Text) on visible tree nodes"); |
||||
|
||||
int baseline = textFiredCount; |
||||
settings.ShowMetadataTokensInBase10 = true; |
||||
textFiredCount.Should().BeGreaterThan(baseline, |
||||
"flipping Base10 must also raise PropertyChanged(Text)"); |
||||
} |
||||
finally |
||||
{ |
||||
typeNode.PropertyChanged -= handler; |
||||
} |
||||
} |
||||
finally |
||||
{ |
||||
settings.ShowMetadataTokens = false; |
||||
settings.ShowMetadataTokensInBase10 = false; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,84 @@
@@ -0,0 +1,84 @@
|
||||
// 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.Threading.Tasks; |
||||
|
||||
using Avalonia.Controls; |
||||
using Avalonia.Headless.NUnit; |
||||
|
||||
using AwesomeAssertions; |
||||
|
||||
using ILSpy; |
||||
using ILSpy.AppEnv; |
||||
using ILSpy.AssemblyTree; |
||||
using ILSpy.TreeNodes; |
||||
using ILSpy.ViewModels; |
||||
using ILSpy.Views; |
||||
|
||||
using NUnit.Framework; |
||||
|
||||
namespace ICSharpCode.ILSpy.Tests; |
||||
|
||||
[TestFixture] |
||||
public class UseNestedNamespaceNodesGridVerification |
||||
{ |
||||
[AvaloniaTest] |
||||
public async Task Toggling_UseNestedNamespaceNodes_Triggers_BindTree_So_The_Grid_Sees_The_New_Shape() |
||||
{ |
||||
// The pane caches a snapshot of each expanded node's children via the
|
||||
// HierarchicalOptions.ChildrenSelector — mid-expand mutations to AssemblyTreeNode.
|
||||
// Children aren't observed by the live grid. The fix raises AssemblyTreeModel.Root
|
||||
// PropertyChanged so AssemblyListPane.BindTree fires and replaces the
|
||||
// HierarchicalModel; this test verifies that re-bind happens.
|
||||
|
||||
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 pane = await window.WaitForComponent<AssemblyListPane>(); |
||||
var grid = pane.FindControl<DataGrid>("TreeGrid")!; |
||||
|
||||
// Expand an assembly to force the ChildrenSelector to fire and cache its snapshot.
|
||||
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>("System.Linq"); |
||||
assemblyNode.IsExpanded = true; |
||||
await Waiters.WaitForAsync(() => grid.HierarchicalModel != null); |
||||
|
||||
try |
||||
{ |
||||
var modelBefore = grid.HierarchicalModel; |
||||
((object?)modelBefore).Should().NotBeNull("baseline: the grid must have a HierarchicalModel before the toggle"); |
||||
|
||||
settings.UseNestedNamespaceNodes = true; |
||||
|
||||
await Waiters.WaitForAsync( |
||||
() => !ReferenceEquals(grid.HierarchicalModel, modelBefore), |
||||
System.TimeSpan.FromSeconds(5)); |
||||
|
||||
grid.HierarchicalModel.Should().NotBeSameAs(modelBefore, |
||||
"toggling UseNestedNamespaceNodes must force AssemblyListPane.BindTree, " |
||||
+ "replacing the HierarchicalModel with one that reads the rebuilt children"); |
||||
} |
||||
finally |
||||
{ |
||||
settings.UseNestedNamespaceNodes = false; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,83 @@
@@ -0,0 +1,83 @@
|
||||
// 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.Linq; |
||||
using System.Threading.Tasks; |
||||
|
||||
using Avalonia.Headless.NUnit; |
||||
|
||||
using AwesomeAssertions; |
||||
|
||||
using ILSpy; |
||||
using ILSpy.AppEnv; |
||||
using ILSpy.TreeNodes; |
||||
using ILSpy.ViewModels; |
||||
using ILSpy.Views; |
||||
|
||||
using NUnit.Framework; |
||||
|
||||
namespace ICSharpCode.ILSpy.Tests; |
||||
|
||||
[TestFixture] |
||||
public class UseNestedNamespaceNodesLiveTests |
||||
{ |
||||
[AvaloniaTest] |
||||
public async Task Toggling_UseNestedNamespaceNodes_Live_Refreshes_The_Tree_Shape() |
||||
{ |
||||
// Drives the live-reactivity path: toggle the Display-Settings flag and observe
|
||||
// that the AssemblyTreeNode's namespace children switch between flat and nested
|
||||
// layouts without a manual rebuild.
|
||||
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); |
||||
|
||||
try |
||||
{ |
||||
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>("System.Linq"); |
||||
assemblyNode.EnsureLazyChildren(); |
||||
|
||||
var flatNamespaces = assemblyNode.Children.OfType<NamespaceTreeNode>() |
||||
.Select(n => n.Name).ToList(); |
||||
flatNamespaces.Should().Contain("System.Linq", |
||||
"baseline: flat mode lists 'System.Linq' as a top-level sibling"); |
||||
|
||||
// Toggle the live setting — should fan out through MessageBus<SettingsChangedEventArgs>
|
||||
// to AssemblyTreeModel.OnSettingsChanged and rebuild the namespace subtrees.
|
||||
settings.UseNestedNamespaceNodes = true; |
||||
|
||||
await Waiters.WaitForAsync(() => |
||||
assemblyNode.Children.OfType<NamespaceTreeNode>().Any(n => n.Name == "System"), |
||||
System.TimeSpan.FromSeconds(5)); |
||||
|
||||
var nestedNames = assemblyNode.Children.OfType<NamespaceTreeNode>() |
||||
.Select(n => n.Name).ToList(); |
||||
nestedNames.Should().Contain("System", |
||||
"after live toggle: nested mode must surface 'System' as top-level"); |
||||
nestedNames.Should().NotContain("System.Linq", |
||||
"after live toggle: the flat 'System.Linq' sibling must disappear"); |
||||
} |
||||
finally |
||||
{ |
||||
settings.UseNestedNamespaceNodes = false; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,84 @@
@@ -0,0 +1,84 @@
|
||||
// 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.IO; |
||||
using System.Threading.Tasks; |
||||
|
||||
using Avalonia.Headless; |
||||
using Avalonia.Headless.NUnit; |
||||
using Avalonia.Threading; |
||||
|
||||
using ILSpy; |
||||
using ILSpy.AppEnv; |
||||
using ILSpy.TreeNodes; |
||||
using ILSpy.ViewModels; |
||||
using ILSpy.Views; |
||||
|
||||
using NUnit.Framework; |
||||
|
||||
namespace ICSharpCode.ILSpy.Tests; |
||||
|
||||
[TestFixture] |
||||
public class UseNestedNamespaceNodesScreenshot |
||||
{ |
||||
[AvaloniaTest] |
||||
[Explicit("Visual evidence — requires ILSPY_TESTS_VISIBLE=1 so the headless platform renders.")] |
||||
public async Task Capture_Before_And_After_Toggle() |
||||
{ |
||||
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 assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>("System.Linq"); |
||||
assemblyNode.IsExpanded = true; |
||||
await Waiters.WaitForAsync(() => assemblyNode.Children.Count > 0); |
||||
Dispatcher.UIThread.RunJobs(); |
||||
|
||||
SaveFrame(window, "before-flat"); |
||||
|
||||
settings.UseNestedNamespaceNodes = true; |
||||
await Waiters.WaitForAsync(() => |
||||
assemblyNode.Children.Count > 0 |
||||
&& assemblyNode.Children[assemblyNode.Children.Count - 1] is NamespaceTreeNode); |
||||
// Re-expand — BindTree replaces the HierarchicalModel and resets visible expansion.
|
||||
assemblyNode.IsExpanded = true; |
||||
Dispatcher.UIThread.RunJobs(); |
||||
|
||||
SaveFrame(window, "after-nested"); |
||||
|
||||
settings.UseNestedNamespaceNodes = false; |
||||
} |
||||
|
||||
static void SaveFrame(global::Avalonia.Controls.Window window, string label) |
||||
{ |
||||
var frame = window.CaptureRenderedFrame(); |
||||
if (frame is null) |
||||
{ |
||||
TestContext.WriteLine($"[{label}] no frame (headless drawing on)"); |
||||
return; |
||||
} |
||||
var path = Path.Combine(Path.GetTempPath(), |
||||
$"ilspy-tree-{label}-{System.DateTime.Now:yyyyMMdd-HHmmss}.png"); |
||||
frame.Save(path); |
||||
TestContext.WriteLine($"[{label}] saved {path}"); |
||||
} |
||||
} |
||||
@ -0,0 +1,107 @@
@@ -0,0 +1,107 @@
|
||||
// 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.Linq; |
||||
using System.Threading.Tasks; |
||||
|
||||
using Avalonia.Headless.NUnit; |
||||
|
||||
using AwesomeAssertions; |
||||
|
||||
using ILSpy; |
||||
using ILSpy.AppEnv; |
||||
using ILSpy.TreeNodes; |
||||
using ILSpy.ViewModels; |
||||
using ILSpy.Views; |
||||
|
||||
using NUnit.Framework; |
||||
|
||||
namespace ICSharpCode.ILSpy.Tests; |
||||
|
||||
[TestFixture] |
||||
public class UseNestedNamespaceNodesTests |
||||
{ |
||||
[AvaloniaTest] |
||||
public async Task When_UseNestedNamespaceNodes_True_Namespaces_Are_Hierarchical() |
||||
{ |
||||
// With the setting on, "System" becomes a single root node holding "Collections",
|
||||
// "IO", "Linq", … as descendants — not the flat "System", "System.Collections",
|
||||
// "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); |
||||
|
||||
try |
||||
{ |
||||
settings.UseNestedNamespaceNodes = true; |
||||
|
||||
// Use System.Linq's assembly — it has System and System.Linq as namespaces.
|
||||
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>("System.Linq"); |
||||
assemblyNode.Children.Clear(); |
||||
assemblyNode.LazyLoading = true; |
||||
assemblyNode.EnsureLazyChildren(); |
||||
|
||||
var systemNode = assemblyNode.Children.OfType<NamespaceTreeNode>() |
||||
.SingleOrDefault(ns => ns.Name == "System"); |
||||
((object?)systemNode).Should().NotBeNull( |
||||
"in nested mode the top-level node for the System namespace must exist as 'System' (last segment), not 'System.Linq'"); |
||||
|
||||
var nestedLinq = systemNode!.Children.OfType<NamespaceTreeNode>() |
||||
.SingleOrDefault(ns => ns.Name == "Linq"); |
||||
((object?)nestedLinq).Should().NotBeNull( |
||||
"the System.Linq namespace must nest under the System node in nested mode"); |
||||
|
||||
// Sanity: there is NO sibling "System.Linq" at the assembly-node level.
|
||||
assemblyNode.Children.OfType<NamespaceTreeNode>() |
||||
.Select(n => n.Name).Should().NotContain("System.Linq", |
||||
"flat-style 'System.Linq' sibling must not appear when nesting is on"); |
||||
} |
||||
finally |
||||
{ |
||||
settings.UseNestedNamespaceNodes = false; |
||||
} |
||||
} |
||||
|
||||
[AvaloniaTest] |
||||
public async Task When_UseNestedNamespaceNodes_False_Namespaces_Are_Flat() |
||||
{ |
||||
// Baseline: the default flat layout keeps every distinct namespace string as a
|
||||
// sibling under the AssemblyTreeNode.
|
||||
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 assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>("System.Linq"); |
||||
assemblyNode.Children.Clear(); |
||||
assemblyNode.LazyLoading = true; |
||||
assemblyNode.EnsureLazyChildren(); |
||||
|
||||
var namespaceNames = assemblyNode.Children.OfType<NamespaceTreeNode>() |
||||
.Select(n => n.Name).ToList(); |
||||
|
||||
namespaceNames.Should().Contain("System.Linq", |
||||
"in flat mode 'System.Linq' must appear as a top-level sibling"); |
||||
} |
||||
} |
||||
@ -0,0 +1,90 @@
@@ -0,0 +1,90 @@
|
||||
// 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.Linq; |
||||
using System.Threading.Tasks; |
||||
|
||||
using Avalonia.Headless.NUnit; |
||||
|
||||
using AwesomeAssertions; |
||||
|
||||
using ILSpy; |
||||
using ILSpy.AppEnv; |
||||
using ILSpy.Metadata; |
||||
using ILSpy.TreeNodes; |
||||
using ILSpy.ViewModels; |
||||
using ILSpy.Views; |
||||
|
||||
using NUnit.Framework; |
||||
|
||||
namespace ICSharpCode.ILSpy.Tests.Metadata; |
||||
|
||||
[TestFixture] |
||||
public class HideEmptyMetadataTablesUiBindingTests |
||||
{ |
||||
[AvaloniaTest] |
||||
public async Task Toggling_DisplaySettings_HideEmptyMetadataTables_Affects_The_Tree() |
||||
{ |
||||
// The Display Settings "Hide empty metadata tables" checkbox binds to
|
||||
// DisplaySettings.HideEmptyMetadataTables. The tree must read from the SAME source —
|
||||
// otherwise the checkbox is dead UI. Regression: prior to this fix the tree read
|
||||
// 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 coreLibName = typeof(object).Assembly.GetName().Name!; |
||||
|
||||
try |
||||
{ |
||||
// Disable via the same property the checkbox writes to.
|
||||
settings.DisplaySettings.HideEmptyMetadataTables = false; |
||||
var withAll = CountVisibleTables(vm, coreLibName); |
||||
|
||||
settings.DisplaySettings.HideEmptyMetadataTables = true; |
||||
var withoutEmpty = CountVisibleTables(vm, coreLibName); |
||||
|
||||
withAll.Should().BeGreaterThan(withoutEmpty, |
||||
"flipping the Display-Settings flag must cause additional empty tables to surface in the tree"); |
||||
} |
||||
finally |
||||
{ |
||||
settings.DisplaySettings.HideEmptyMetadataTables = true; |
||||
} |
||||
} |
||||
|
||||
static int CountVisibleTables(MainWindowViewModel vm, string assemblyName) |
||||
{ |
||||
// Each test phase needs a fresh tree fragment because LoadChildren is idempotent —
|
||||
// the children dictionary is captured the first time the node is expanded, not on
|
||||
// every read. Re-finding the assembly node + re-walking is the lightweight way to
|
||||
// re-trigger the metadata-table enumeration against the current setting.
|
||||
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(); |
||||
tables.EnsureLazyChildren(); |
||||
return tables.Children.OfType<MetadataTableTreeNode>().Count(); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue