using System; using System.Threading; using System.Windows.Threading; using Xunit; namespace ytLive.Tests; /// /// Tests that spin up the real WPF App need exactly one Application instance per /// AppDomain (WPF enforces it) — so they share this serial collection and a /// single App created on one dedicated STA thread. Marshal test bodies onto it /// via , never `new App()` per test. /// [CollectionDefinition("RealApp", DisableParallelization = true)] public sealed class RealAppCollection : ICollectionFixture { } /// Owns the one-and-only WPF App on a dedicated STA thread. public sealed class RealAppHost : IDisposable { private readonly Thread _thread; private readonly Dispatcher _dispatcher; public RealAppHost() { Exception? boot = null; Dispatcher? dispatcher = null; _thread = new Thread(() => { try { var app = new App(); app.InitializeComponent(); dispatcher = Dispatcher.CurrentDispatcher; Dispatcher.Run(); } catch (Exception ex) { boot = ex; } }); _thread.SetApartmentState(ApartmentState.STA); _thread.Start(); while (dispatcher == null && boot == null) Thread.Sleep(5); if (boot != null) throw new Xunit.Sdk.XunitException("Real WPF App failed to start: " + boot); _dispatcher = dispatcher!; } /// Runs on the App's STA thread and rethrows any failure. public void Run(Action action) { Exception? failure = null; _dispatcher.Invoke(() => { try { action(); } catch (Exception ex) { failure = ex; } }); if (failure != null) throw failure; } public void Dispose() { try { _dispatcher.InvokeShutdown(); } catch { // The App never ran its message loop — shutdown is best-effort. } _thread.Join(2000); } }