// 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 ICSharpCode.ILSpy.Properties;
using ICSharpCode.ILSpyX;
using global::ILSpy;
using global::ILSpy.AppEnv;
using global::ILSpy.Commands;
using global::ILSpy.ViewModels;
using global::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);
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;
}
///
/// Opens through the production Open command, waits for it to appear
/// in the assembly list, awaits its load result, and returns the resulting
/// . This is the canonical "add a test assembly to the tree"
/// sequence — registry lookup, execute, poll-for-appearance, await-load — in one call.
///
public static async Task OpenAssemblyAsync(
this MainWindowViewModel vm,
string path,
TimeSpan? timeout = null)
{
ArgumentNullException.ThrowIfNull(vm);
ArgumentNullException.ThrowIfNull(path);
var open = AppComposition.Current.GetExport().GetCommand(nameof(Resources._Open));
open.Execute(path);
await Waiters.WaitForAsync(
() => vm.AssemblyTreeModel.AssemblyList!.GetAssemblies()
.Any(a => string.Equals(a.FileName, path, StringComparison.OrdinalIgnoreCase)),
timeout,
$"assembly '{path}' to appear in the active list");
var loaded = vm.AssemblyTreeModel.AssemblyList!.GetAssemblies()
.First(a => string.Equals(a.FileName, path, StringComparison.OrdinalIgnoreCase));
await loaded.GetLoadResultAsync();
return loaded;
}
}