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 @@ |
|||||||
|
// 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 @@ |
|||||||
|
// 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 @@ |
|||||||
|
// 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 @@ |
|||||||
|
// 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 @@ |
|||||||
|
// 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 @@ |
|||||||
|
// 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 @@ |
|||||||
|
// 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