// 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.Controls;
using Avalonia.Headless.NUnit;
using Avalonia.VisualTree;
using AwesomeAssertions;
using ICSharpCode.ILSpyX.TreeView;
using ICSharpCode.ILSpy;
using ICSharpCode.ILSpy.AppEnv;
using ICSharpCode.ILSpy.Commands;
using ICSharpCode.ILSpy.Compare;
using ICSharpCode.ILSpy.TreeNodes;
using ICSharpCode.ILSpy.ViewModels;
using ICSharpCode.ILSpy.Views;
using NUnit.Framework;
namespace ICSharpCode.ILSpy.Tests.Compare;
///
/// End-to-end render checks for the assembly-comparison view. Pure context-menu and engine
/// tests don't catch the case where the new tab opens but renders empty because the
/// hierarchical DataGrid was never wired with a model — these tests mount the view, walk
/// the visual tree, and assert rows materialise.
///
[TestFixture]
public class CompareViewRenderTests
{
[AvaloniaTest]
public async Task CompareView_Binds_A_HierarchicalModel_When_Opened()
{
var window = AppComposition.Current.GetExport();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 2);
var entry = AppComposition.Current.GetExport()
.Entries.Single(e => e.Metadata.Header == "Compare...").Value;
var assemblies = new[] {
await vm.OpenFixtureAsync("FixtureA"),
await vm.OpenFixtureAsync("FixtureB"),
};
var nodes = assemblies.Select(a =>
(SharpTreeNode)vm.AssemblyTreeModel.FindNode(a.ShortName)).ToArray();
entry.Execute(new TextViewContext { SelectedTreeNodes = nodes });
var view = await window.WaitForComponent();
var grid = await view.WaitForComponent();
await Waiters.WaitForAsync(() => grid.HierarchicalModel != null,
description: "CompareView must populate the DataGrid's HierarchicalModel — without it "
+ "the hierarchical column is rendered over an empty source and the tab shows nothing");
grid.HierarchicalModel.Should().NotBeNull();
}
[AvaloniaTest]
public async Task CompareView_Renders_Rows_For_The_Merged_Tree()
{
var window = AppComposition.Current.GetExport();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 2);
var entry = AppComposition.Current.GetExport()
.Entries.Single(e => e.Metadata.Header == "Compare...").Value;
var assemblies = new[] {
await vm.OpenFixtureAsync("FixtureA"),
await vm.OpenFixtureAsync("FixtureB"),
};
var nodes = assemblies.Select(a =>
(SharpTreeNode)vm.AssemblyTreeModel.FindNode(a.ShortName)).ToArray();
entry.Execute(new TextViewContext { SelectedTreeNodes = nodes });
var view = await window.WaitForComponent();
var grid = await view.WaitForComponent();
// Default state: ShowIdentical=false, so we expect at least one diff row to be present
// when comparing two non-empty assemblies against themselves *or* against a sibling.
// (Comparing an assembly with itself yields zero diffs; if the two assemblies are the
// same instance we toggle ShowIdentical to force the tree to surface rows.)
var page = (CompareTabPageModel)view.DataContext!;
if (ReferenceEquals(page.LeftAssembly, page.RightAssembly))
page.ShowIdentical = true;
await Waiters.WaitForAsync(
() => 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 = await vm.OpenFixtureAsync();
// 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");
}
}