diff --git a/ILSpy.Tests/Compare/CompareViewRenderTests.cs b/ILSpy.Tests/Compare/CompareViewRenderTests.cs index 0fd3e06ea..0d6db9c6c 100644 --- a/ILSpy.Tests/Compare/CompareViewRenderTests.cs +++ b/ILSpy.Tests/Compare/CompareViewRenderTests.cs @@ -106,4 +106,56 @@ public class CompareViewRenderTests () => grid.GetVisualDescendants().OfType().Any(), description: "the diff grid must render at least one row once the merged tree is bound"); } + + [AvaloniaTest] + public async Task CompareView_ShowIdentical_Toggle_Changes_Visible_Row_Set() + { + // Comparing an assembly with itself yields only identical rows. With ShowIdentical=false + // (default) every row should be hidden; flipping ShowIdentical=true must surface them. + // Regression: the bug was that the HierarchicalModel's ChildrenSelector returned + // node.Children unfiltered, so IsHidden flags set by ApplyFilterToChild had no effect + // on what the DataGrid actually rendered. + var window = AppComposition.Current.GetExport(); + window.Show(); + var vm = (MainWindowViewModel)window.DataContext!; + await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1); + + var assembly = vm.AssemblyTreeModel.AssemblyList!.GetAssemblies() + .First(a => a.IsLoadedAsValidAssembly); + + // Construct the compare model directly with the same assembly on both sides so every + // entry collapses to DiffKind.None. The View renders via the standard OpenNewTab path. + var page = new CompareTabPageModel(assembly, assembly); + vm.DockWorkspace.OpenNewTab(page); + + var view = await window.WaitForComponent(); + var grid = await view.WaitForComponent(); + await Waiters.WaitForAsync(() => grid.HierarchicalModel != null); + global::Avalonia.Threading.Dispatcher.UIThread.RunJobs(); + + // Baseline: ShowIdentical=false. All rows are identical-only, so the visible row count + // must be zero (every entry filters to Hidden). + page.ShowIdentical.Should().BeFalse("baseline state"); + var rowsBeforeToggle = grid.GetVisualDescendants().OfType().Count(); + rowsBeforeToggle.Should().Be(0, + "with ShowIdentical=false and only-identical content, no rows must be visible"); + + // Flip ShowIdentical=true and re-evaluate. + page.ShowIdentical = true; + global::Avalonia.Threading.Dispatcher.UIThread.RunJobs(); + await Waiters.WaitForAsync( + () => grid.GetVisualDescendants().OfType().Any(), + description: "flipping ShowIdentical=true must surface previously-hidden identical rows"); + + var rowsAfterToggle = grid.GetVisualDescendants().OfType().Count(); + rowsAfterToggle.Should().BeGreaterThan(0, + "with ShowIdentical=true, identical rows must surface in the grid"); + + // Flip back and verify it returns to the hidden state. + page.ShowIdentical = false; + global::Avalonia.Threading.Dispatcher.UIThread.RunJobs(); + await Waiters.WaitForAsync( + () => grid.GetVisualDescendants().OfType().Count() == 0, + description: "flipping ShowIdentical=false again must re-hide all identical rows"); + } } diff --git a/ILSpy/Views/CompareView.axaml.cs b/ILSpy/Views/CompareView.axaml.cs index e292c5a6f..0205aed18 100644 --- a/ILSpy/Views/CompareView.axaml.cs +++ b/ILSpy/Views/CompareView.axaml.cs @@ -18,6 +18,7 @@ using System; using System.ComponentModel; +using System.Linq; using Avalonia.Controls; using Avalonia.Controls.DataGridHierarchical; @@ -80,15 +81,24 @@ namespace ILSpy.Compare ilspy.EnsureChildrenFiltered(); else node.EnsureLazyChildren(); - return node.Children; + return FilterHidden(node.Children); }, IsLeafSelector = node => !node.ShowExpander, VirtualizeChildren = false, IsExpandedPropertyPath = nameof(SharpTreeNode.IsExpanded), }; var model = new HierarchicalModel(options); - model.SetRoots(root.Children); + model.SetRoots(FilterHidden(root.Children)); DiffGrid.HierarchicalModel = model; } + + // ShowIdentical=false hides identical rows by setting IsHidden=true via + // ComparisonEntryTreeNode.Filter, but the DataGrid's HierarchicalModel only sees + // what ChildrenSelector / SetRoots feed it — IsHidden alone has no effect on the + // rendered row set. Filter the children here so the cascade-set IsHidden flags + // actually reach the grid. Same pattern AssemblyListPane.FilterChildren uses for + // the assembly tree's ShowApiLevel cascade. + static System.Collections.Generic.IEnumerable FilterHidden(System.Collections.Generic.IEnumerable children) + => children.Where(c => c is not TreeNodes.ILSpyTreeNode it || !it.IsHidden).ToList(); } }