Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 17 additions & 36 deletions ILSpy.Tests/AssemblyList/AssemblyTreeExpanderHitboxTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,10 @@ public class AssemblyTreeExpanderHitboxTests
[AvaloniaTest]
public async Task Expander_Toggle_Offers_At_Least_16x16_Clickable_Target()
{
// The +/- expander in the assembly tree must give a click target that fills the full 13px
// expander column and the 16px row-tall toggle, while its visible glyph stays the classic
// 9x9 box. The column is kept at 13px so the +/- box centres on the tree connector lines;
// the target must be genuinely hittable across that whole area — not merely occupy it in
// layout while only the 9x9 glyph receives input.
// The +/- expander's click target fills the 13px expander column and the 16px row height,
// while the drawn glyph stays the classic 9x9 box centred on the tree connector lines. The
// grown area has to be genuinely hittable rather than merely occupied in layout, so the
// test clicks below the glyph instead of measuring the boxes.

// Arrange — boot, wait for assemblies, expand a node so an expandable row is realised.
var (window, vm) = await TestHarness.BootAsync(3);
Expand All @@ -63,39 +62,21 @@ public async Task Expander_Toggle_Offers_At_Least_16x16_Clickable_Target()
var pane = await window.WaitForComponent<AssemblyListPane>();
var grid = await pane.WaitForComponent<ICSharpCode.ILSpy.Controls.TreeView.SharpTreeView>();

// Let rows realise and layout settle.
for (int i = 0; i < 8; i++)
{
Dispatcher.UIThread.RunJobs();
grid.UpdateLayout();
await Task.Delay(25);
}

// Act — locate the expander toggle of the (expandable) assembly row.
var row = grid.GetVisualDescendants().OfType<ICSharpCode.ILSpy.Controls.TreeView.SharpTreeViewItem>()
.FirstOrDefault(r => RowMatches(r, assemblyNode));
row.Should().NotBeNull("the expanded assembly row must be realised");
var expander = row!.GetVisualDescendants().OfType<ToggleButton>()
.FirstOrDefault(b => b.Name == "PART_Expander");
expander.Should().NotBeNull("an expandable row must realise a PART_Expander toggle");
// Act — locate the expander toggle of the (expandable) assembly row, once the row and its
// template have been realised.
ToggleButton? expander = null;
await Waiters.WaitForAsync(
() => {
grid.UpdateLayout();
expander = grid.GetVisualDescendants().OfType<ICSharpCode.ILSpy.Controls.TreeView.SharpTreeViewItem>()
.FirstOrDefault(r => RowMatches(r, assemblyNode))
?.GetVisualDescendants().OfType<ToggleButton>()
.FirstOrDefault(b => b.Name == "PART_Expander");
return expander is { Bounds.Height: >= 16 };
},
description: "the expanded assembly row must realise a PART_Expander toggle filling the row height");
expander!.IsEnabled.Should().BeTrue("the assembly row is expandable");

// Assert — the click target fills the 13px expander column and is 16px tall.
expander.Bounds.Width.Should().BeGreaterThanOrEqualTo(13,
"the expander click target must fill the 13px expander column for reliable tapping");
expander.Bounds.Height.Should().BeGreaterThanOrEqualTo(16,
"the expander click target must be at least 16px tall for reliable tapping");

// Assert — the visible glyph box is unchanged at 9x9 (the nearest Border around ExpandPath,
// i.e. the drawn box, not the transparent hit-target wrapper).
var glyphPath = expander.GetVisualDescendants().OfType<Path>()
.FirstOrDefault(p => p.Name == "ExpandPath");
glyphPath.Should().NotBeNull("the expander must render its ExpandPath glyph");
var glyph = glyphPath!.GetVisualAncestors().OfType<Border>().FirstOrDefault();
glyph.Should().NotBeNull("the expander must still render its glyph box");
glyph!.Bounds.Width.Should().BeApproximately(9, 0.5, "the visible glyph box must stay 9px wide");
glyph.Bounds.Height.Should().BeApproximately(9, 0.5, "the visible glyph box must stay 9px tall");

// Assert — a real click well below the 9x9 glyph (y=14, inside the 16-tall target but
// outside the centred glyph at ~y=3.5..12.5) collapses the node. This proves the grown
// area is genuinely hittable, not just larger in layout.
Expand Down

This file was deleted.

78 changes: 0 additions & 78 deletions ILSpy.Tests/AssemblyList/UseNestedNamespaceNodesLiveTests.cs

This file was deleted.

108 changes: 58 additions & 50 deletions ILSpy.Tests/AssemblyList/UseNestedNamespaceNodesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,84 +16,92 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

using System;
using System.Collections;
using System.Linq;
using System.Threading.Tasks;

using Avalonia.Headless.NUnit;

using AwesomeAssertions;

using ICSharpCode.ILSpy;
using ICSharpCode.ILSpy.AppEnv;
using ICSharpCode.ILSpy.AssemblyTree;
using ICSharpCode.ILSpy.TreeNodes;

using ICSharpCode.ILSpyX.TreeView;

using NUnit.Framework;

using SharpTreeView = ICSharpCode.ILSpy.Controls.TreeView.SharpTreeView;

namespace ICSharpCode.ILSpy.Tests;

/// <summary>
/// The "Use nested namespace structure" display setting, from the setting through to the
/// rows on screen. Flat mode keeps every dotted namespace as its own sibling of the assembly
/// node; nested mode splits them, so "System.Linq" becomes "Linq" under "System". The switch
/// happens live: toggling rebuilds each loaded assembly's namespace subtree, and because
/// SharpTreeView's flattener observes node.Children directly, the rebuilt shape reaches the
/// visible rows with no model re-bind.
/// </summary>
[TestFixture]
public class UseNestedNamespaceNodesTests
{
[AvaloniaTest]
public async Task When_UseNestedNamespaceNodes_True_Namespaces_Are_Hierarchical()
public async Task Toggling_The_Setting_Reshapes_The_Tree_Live_Down_To_The_Visible_Rows()
{
// 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 (_, vm) = await TestHarness.BootAsync(3);
settings.UseNestedNamespaceNodes = false;

var (window, vm) = await TestHarness.BootAsync(3);
var pane = await window.WaitForComponent<AssemblyListPane>();
var grid = await pane.WaitForComponent<SharpTreeView>();

try
{
settings.UseNestedNamespaceNodes = true;

// Use System.Linq's assembly — it has System and System.Linq as namespaces.
// System.Linq's assembly carries both "System" and "System.Linq", so one assembly
// shows the difference between the two layouts.
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");
assemblyNode.IsExpanded = true;
await Waiters.WaitForAsync(() => assemblyNode.Children.OfType<NamespaceTreeNode>().Any());

var visibleRows = (IList)grid.ItemsSource!;
bool VisibleNamespace(string name) => visibleRows.Cast<SharpTreeNode>()
.OfType<NamespaceTreeNode>()
.Any(n => string.Equals(n.Text?.ToString(), name, StringComparison.Ordinal));

NamespaceNames().Should().Contain("System.Linq",
"flat mode lists the whole dotted namespace as one sibling of the assembly node");
await Waiters.WaitForAsync(() => VisibleNamespace("System.Collections.Generic"),
description: "flat mode shows the dotted namespace as a single visible row");
TestCapture.Step("flat-mode");

// The setting fans out through MessageBus<SettingsChangedEventArgs> to
// AssemblyTreeModel.OnSettingsChanged, which rebuilds the namespace subtrees.
settings.UseNestedNamespaceNodes = true;
assemblyNode.IsExpanded = true;

await Waiters.WaitForAsync(() => NamespaceNames().Contains("System"),
TimeSpan.FromSeconds(5),
"toggling the setting must rebuild the namespace subtree into nested nodes");
TestCapture.Step("nested-mode");

NamespaceNames().Should().NotContain("System.Linq",
"the flat dotted sibling must be gone once its segments are nested");
assemblyNode.Children.OfType<NamespaceTreeNode>().Single(n => n.Name == "System")
.Children.OfType<NamespaceTreeNode>().Select(n => n.Name).Should().Contain("Linq",
"the trailing segment must hang under the node for the leading one");
VisibleNamespace("System").Should().BeTrue(
"the rebuilt node must reach the visible rows without a model re-bind - the live "
+ "flattener observes the child mutations directly");

string[] NamespaceNames() => assemblyNode.Children.OfType<NamespaceTreeNode>()
.Select(n => n.Name).ToArray();
}
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 (_, vm) = await TestHarness.BootAsync(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");
}
}
Loading
Loading