csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
83 lines
3.0 KiB
83 lines
3.0 KiB
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;
|
|
|
|
/// <summary>
|
|
/// Test application running on the real headless platform, set up through <see cref="AppBuilder"/>
|
|
/// so that the platform owns its own slice of the locator.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This is deliberately not a <see cref="TestServices"/> flavour: the headless platform registers
|
|
/// services of its own (keyboard device, clipboard, render loop, platform settings, hotkey config),
|
|
/// and <see cref="UnitTestApplication"/> binds the same keys from its service bag, so the two
|
|
/// overwrite each other depending on when the platform happens to initialize.
|
|
/// </remarks>
|
|
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<HeadlessUnitTestApplication>()
|
|
// 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<IFontManagerImpl>().ToConstant(new TestFontManager()))
|
|
.SetupUnsafe();
|
|
}
|
|
catch
|
|
{
|
|
scope.Dispose();
|
|
throw;
|
|
}
|
|
|
|
return new Scope(scope, oldContext);
|
|
}
|
|
|
|
public sealed class Scope(IDisposable locatorScope, SynchronizationContext? oldContext) : IDisposable
|
|
{
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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<IToolTipService>() as ToolTipService)?.Dispose();
|
|
(AvaloniaLocator.Current.GetService<FontManager>() as IDisposable)?.Dispose();
|
|
(AvaloniaLocator.Current.GetService<IInputManager>() as IDisposable)?.Dispose();
|
|
|
|
Dispatcher.ResetForUnitTests();
|
|
locatorScope.Dispose();
|
|
Dispatcher.ResetBeforeUnitTests();
|
|
SynchronizationContext.SetSynchronizationContext(oldContext);
|
|
}
|
|
}
|
|
}
|
|
|