// 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;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using Avalonia.Controls;
using Avalonia.Interactivity;
using ICSharpCode.ILSpy.Properties;
using ICSharpCode.ILSpyX;
using ICSharpCode.ILSpy;
using ICSharpCode.ILSpy.AppEnv;
using ICSharpCode.ILSpy.Commands;
using ICSharpCode.ILSpy.ViewModels;
using ICSharpCode.ILSpy.Views;
namespace ICSharpCode.ILSpy.Tests;
///
/// Boots and drives the shared MEF-composed application for headless UI tests, collapsing the
/// four-line prologue (resolve , show it, cast its DataContext, wait for
/// the assembly list) and the "open an assembly via the production command and wait for it to
/// load" dance that nearly every fixture repeats. The container is rebuilt per test by
/// , so each call hands back that test's own singletons.
///
public static class TestHarness
{
///
/// Resolves the shared , shows it, and waits until the default
/// assembly list has loaded at least assemblies.
/// Returns the window and its — the two handles the test
/// body almost always needs next.
///
public static async Task<(MainWindow Window, MainWindowViewModel ViewModel)> BootAsync(
int minimumAssemblies = 1,
TimeSpan? timeout = null)
{
var window = AppComposition.Current.GetExport();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumAssemblies, timeout);
// First visual breakpoint of every UI test: the booted window with its assembly list loaded.
window.Capture("booted");
return (window, vm);
}
///
/// Looks up a [ExportMainMenuCommand]-tagged command by its Header metadata (pass the
/// resource key, e.g. nameof(Resources._Open)) and materialises a fresh instance.
///
public static ICommand GetCommand(this MainMenuCommandRegistry registry, string header)
{
ArgumentNullException.ThrowIfNull(registry);
return registry.Commands
.Single(c => string.Equals(c.Metadata.Header, header, StringComparison.Ordinal))
.CreateExport().Value;
}
///
/// Looks up a [ExportContextMenuEntry]-tagged entry by its Header metadata.
///
public static IContextMenuEntry GetEntry(this ContextMenuEntryRegistry registry, string header)
{
ArgumentNullException.ThrowIfNull(registry);
return registry.Entries
.Single(e => string.Equals(e.Metadata.Header, header, StringComparison.Ordinal))
.Value;
}
///
/// Finds the whose Header equals (pass
/// the resolved resource string, e.g. Resources._Reload) in a menu built by
/// AssemblyListPane.BuildContextMenuForCurrentState and raises its Click — the
/// same routed event the production menu fires when the user picks the item. Collapses the
/// Items.OfType<MenuItem>().Single(...).RaiseEvent(...) dance every context-menu
/// test repeats.
///
public static void ClickItem(this ContextMenu menu, string header)
{
ArgumentNullException.ThrowIfNull(menu);
ArgumentNullException.ThrowIfNull(header);
var item = menu.Items.OfType