using System; using System.Threading; using Avalonia.Controls; using Avalonia.Headless; using Avalonia.Input; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Themes.Simple; using Avalonia.Threading; using Xunit; namespace Avalonia.UnitTests; /// /// Test application running on the real headless platform, set up through /// so that the platform owns its own slice of the locator. /// /// /// This is deliberately not a flavour: the headless platform registers /// services of its own (keyboard device, clipboard, render loop, platform settings, hotkey config), /// and binds the same keys from its service bag, so the two /// overwrite each other depending on when the platform happens to initialize. /// public class HeadlessUnitTestApplication : Application { public HeadlessUnitTestApplication() { Styles.Add(new SimpleTheme()); } public static Scope Start(AvaloniaHeadlessPlatformOptions? options = null) { var scope = AvaloniaLocator.EnterScope(); var oldContext = SynchronizationContext.Current; try { Dispatcher.ResetBeforeUnitTests(); AppBuilder.Configure() // Popups default to dedicated top-levels here, matching the desktop platforms // and the app used by Avalonia.Headless.UnitTests. .UseHeadless(options ?? new AvaloniaHeadlessPlatformOptions { OverlayPopups = false }) .AfterPlatformServicesSetup(_ => AvaloniaLocator.CurrentMutable .Bind().ToConstant(new TestFontManager())) .SetupUnsafe(); } catch { scope.Dispose(); throw; } return new Scope(scope, oldContext); } public sealed class Scope(IDisposable locatorScope, SynchronizationContext? oldContext) : IDisposable { /// /// Runs pending dispatcher jobs, tied to the test's cancellation token. Headless top-levels /// post activation, resizes and rendering, so tests have to let those settle between acts. /// public void RunJobs(DispatcherPriority? priority = null) => Dispatcher.UIThread.RunJobs(priority, TestContext.Current.CancellationToken); public void Dispose() { if (Dispatcher.UIThread.CheckAccess()) { Dispatcher.UIThread.RunJobs(); } (AvaloniaLocator.Current.GetService() as ToolTipService)?.Dispose(); (AvaloniaLocator.Current.GetService() as IDisposable)?.Dispose(); (AvaloniaLocator.Current.GetService() as IDisposable)?.Dispose(); Dispatcher.ResetForUnitTests(); locatorScope.Dispose(); Dispatcher.ResetBeforeUnitTests(); SynchronizationContext.SetSynchronizationContext(oldContext); } } }