using System; using System.Collections.Generic; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Platform; using Avalonia.Threading; using Avalonia.UnitTests; using Moq; using Xunit; namespace Avalonia.Controls.UnitTests { public class DesktopStyleApplicationLifetimeTests : ScopedTestBase { [Fact] public void Should_Set_ExitCode_After_Shutdown() { using (UnitTestApplication.Start(new TestServices())) using(var lifetime = new ClassicDesktopStyleApplicationLifetime()) { Setup(lifetime); Dispatcher.UIThread.Post(() => lifetime.Shutdown(1337)); var exitCode = lifetime.Start(Array.Empty()); Assert.Equal(1337, exitCode); } } [Fact] public void Should_Close_All_Remaining_Open_Windows_After_Explicit_Exit_Call() { using (UnitTestApplication.Start(TestServices.StyledWindow)) using(var lifetime = new ClassicDesktopStyleApplicationLifetime()) { Setup(lifetime); var windows = new List { new Window(), new Window(), new Window(), new Window() }; foreach (var window in windows) { window.Show(); } Assert.Equal(4, lifetime.Windows.Count); lifetime.Shutdown(); Assert.Empty(lifetime.Windows); } } [Fact] public void Should_Only_Exit_On_Explicit_Exit() { using (UnitTestApplication.Start(TestServices.StyledWindow)) using(var lifetime = new ClassicDesktopStyleApplicationLifetime()) { lifetime.ShutdownMode = ShutdownMode.OnExplicitShutdown; Setup(lifetime); var hasExit = false; lifetime.Exit += (_, _) => hasExit = true; var windowA = new Window(); windowA.Show(); var windowB = new Window(); windowB.Show(); windowA.Close(); Assert.False(hasExit); windowB.Close(); Assert.False(hasExit); lifetime.Shutdown(); Assert.True(hasExit); } } [Fact] public void Should_Exit_After_MainWindow_Closed() { using (UnitTestApplication.Start(TestServices.StyledWindow)) using(var lifetime = new ClassicDesktopStyleApplicationLifetime()) { lifetime.ShutdownMode = ShutdownMode.OnMainWindowClose; Setup(lifetime); var hasExit = false; lifetime.Exit += (_, _) => hasExit = true; var mainWindow = new Window(); mainWindow.Show(); lifetime.MainWindow = mainWindow; var window = new Window(); window.Show(); mainWindow.Close(); Assert.True(hasExit); } } [Fact] public void OnMainWindowClose_Overrides_Secondary_Window_Cancellation() { using (UnitTestApplication.Start(TestServices.StyledWindow)) using(var lifetime = new ClassicDesktopStyleApplicationLifetime()) { lifetime.ShutdownMode = ShutdownMode.OnMainWindowClose; Setup(lifetime); var hasExit = false; var secondaryWindowClosingExecuted = false; var secondaryWindowClosedExecuted = false; lifetime.Exit += (_, _) => hasExit = true; var mainWindow = new Window(); mainWindow.Show(); lifetime.MainWindow = mainWindow; var window = new Window(); window.Closing += (_, args) => { secondaryWindowClosingExecuted = true; args.Cancel = true; }; window.Closed += (_, _) => { secondaryWindowClosedExecuted = true; }; window.Show(); mainWindow.Close(); Assert.True(secondaryWindowClosingExecuted); Assert.True(secondaryWindowClosedExecuted); Assert.True(hasExit); } } [Fact] public void OnMainWindowClose_Overrides_Secondary_Window_Cancellation_From_TryShutdown() { using (UnitTestApplication.Start(TestServices.StyledWindow)) using(var lifetime = new ClassicDesktopStyleApplicationLifetime()) { lifetime.ShutdownMode = ShutdownMode.OnMainWindowClose; Setup(lifetime); var hasExit = false; var secondaryWindowClosingExecuted = false; var secondaryWindowClosedExecuted = false; lifetime.Exit += (_, _) => hasExit = true; var mainWindow = new Window(); mainWindow.Show(); lifetime.MainWindow = mainWindow; var window = new Window(); window.Closing += (_, args) => { secondaryWindowClosingExecuted = true; args.Cancel = true; }; window.Closed += (_, _) => { secondaryWindowClosedExecuted = true; }; window.Show(); lifetime.TryShutdown(); Assert.True(secondaryWindowClosingExecuted); Assert.True(secondaryWindowClosedExecuted); Assert.True(hasExit); } } [Fact] public void Should_Exit_After_Last_Window_Closed() { using (UnitTestApplication.Start(TestServices.StyledWindow)) using(var lifetime = new ClassicDesktopStyleApplicationLifetime()) { lifetime.ShutdownMode = ShutdownMode.OnLastWindowClose; Setup(lifetime); var hasExit = false; lifetime.Exit += (_, _) => hasExit = true; var windowA = new Window(); windowA.Show(); var windowB = new Window(); windowB.Show(); windowA.Close(); Assert.False(hasExit); windowB.Close(); Assert.True(hasExit); } } [Fact] public void Show_Should_Add_Window_To_OpenWindows() { using (UnitTestApplication.Start(TestServices.StyledWindow)) using(var lifetime = new ClassicDesktopStyleApplicationLifetime()) { Setup(lifetime); var window = new Window(); window.Show(); Assert.Equal(new[] { window }, lifetime.Windows); } } [Fact] public void Window_Should_Be_Added_To_OpenWindows_Only_Once() { using (UnitTestApplication.Start(TestServices.StyledWindow)) using(var lifetime = new ClassicDesktopStyleApplicationLifetime()) { Setup(lifetime); var window = new Window(); window.Show(); window.Show(); window.IsVisible = true; Assert.Equal(new[] { window }, lifetime.Windows); window.Close(); } } [Fact] public void Close_Should_Remove_Window_From_OpenWindows() { using (UnitTestApplication.Start(TestServices.StyledWindow)) using(var lifetime = new ClassicDesktopStyleApplicationLifetime()) { Setup(lifetime); var window = new Window(); window.Show(); Assert.Equal(1, lifetime.Windows.Count); window.Close(); Assert.Empty(lifetime.Windows); } } [Fact] public void Impl_Closing_Should_Remove_Window_From_OpenWindows() { var windowImpl = new Mock(); windowImpl.Setup(x => x.Compositor).Returns(RendererMocks.CreateDummyCompositor()); windowImpl.SetupProperty(x => x.Closed); windowImpl.Setup(x => x.DesktopScaling).Returns(1); windowImpl.Setup(x => x.RenderScaling).Returns(1); var screen1 = new MockScreen(1.75, new PixelRect(new PixelSize(1920, 1080)), new PixelRect(new PixelSize(1920, 966)), true); var screens = new Mock(); screens.Setup(x => x.ScreenFromWindow(It.IsAny())).Returns(screen1); windowImpl.Setup(x => x.TryGetFeature(It.Is(t => t == typeof(IScreenImpl)))).Returns(screens.Object); var services = TestServices.StyledWindow.With( windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object)); using (UnitTestApplication.Start(services)) using(var lifetime = new ClassicDesktopStyleApplicationLifetime()) { Setup(lifetime); var window = new Window(); window.Show(); Assert.Equal(1, lifetime.Windows.Count); windowImpl.Object.Closed!(); Assert.Empty(lifetime.Windows); } } [Fact] public void Should_Allow_Canceling_Shutdown_Via_ShutdownRequested_Event() { using (UnitTestApplication.Start(TestServices.StyledWindow.With())) using (var lifetime = new ClassicDesktopStyleApplicationLifetime()) { var lifetimeEvents = new Mock(); AvaloniaLocator.CurrentMutable.Bind().ToConstant(lifetimeEvents.Object); // Force exit immediately Dispatcher.UIThread.Post(Dispatcher.UIThread.ExitAllFrames); lifetime.Start(Array.Empty()); var window = new Window(); var raised = 0; window.Show(); lifetime.ShutdownRequested += (_, e) => { e.Cancel = true; ++raised; }; lifetimeEvents.Raise(x => x.ShutdownRequested += null, new ShutdownRequestedEventArgs()); Assert.Equal(1, raised); Assert.Equal(new[] { window }, lifetime.Windows); } } [Fact] public void MainWindow_Closed_Shutdown_Should_Be_Cancellable() { using (UnitTestApplication.Start(TestServices.StyledWindow)) using(var lifetime = new ClassicDesktopStyleApplicationLifetime()) { lifetime.ShutdownMode = ShutdownMode.OnMainWindowClose; Setup(lifetime); var hasExit = false; lifetime.Exit += (_, _) => hasExit = true; var mainWindow = new Window(); mainWindow.Show(); lifetime.MainWindow = mainWindow; var window = new Window(); window.Show(); var raised = 0; lifetime.ShutdownRequested += (_, e) => { e.Cancel = true; ++raised; }; mainWindow.Close(); Assert.Equal(1, raised); Assert.False(hasExit); } } [Fact] public void LastWindow_Closed_Shutdown_Should_Be_Cancellable() { using (UnitTestApplication.Start(TestServices.StyledWindow)) using(var lifetime = new ClassicDesktopStyleApplicationLifetime()) { lifetime.ShutdownMode = ShutdownMode.OnLastWindowClose; Setup(lifetime); var hasExit = false; lifetime.Exit += (_, _) => hasExit = true; var windowA = new Window(); windowA.Show(); var windowB = new Window(); windowB.Show(); var raised = 0; lifetime.ShutdownRequested += (_, e) => { e.Cancel = true; ++raised; }; windowA.Close(); Assert.False(hasExit); windowB.Close(); Assert.Equal(1, raised); Assert.False(hasExit); } } [Fact] public void TryShutdown_Cancellable_By_Preventing_Window_Close() { using (UnitTestApplication.Start(TestServices.StyledWindow)) using(var lifetime = new ClassicDesktopStyleApplicationLifetime()) { Setup(lifetime); lifetime.Exit += (_, _) => Assert.Fail("lifetime.Exit was called."); Dispatcher.UIThread.ShutdownStarted += UiThreadOnShutdownStarted; static void UiThreadOnShutdownStarted(object? sender, EventArgs e) { Assert.Fail("Dispatcher.UIThread.ShutdownStarted was called."); } var windowA = new Window(); windowA.Show(); var windowB = new Window(); windowB.Show(); var raised = 0; windowA.Closing += (_, e) => { e.Cancel = true; ++raised; }; lifetime.TryShutdown(); Assert.Equal(1, raised); Dispatcher.UIThread.ShutdownStarted -= UiThreadOnShutdownStarted; } } [Fact] public void Shutdown_NotCancellable_By_Preventing_Window_Close() { using (UnitTestApplication.Start(TestServices.StyledWindow.With())) using(var lifetime = new ClassicDesktopStyleApplicationLifetime()) { Setup(lifetime); var hasExit = false; var closingRaised = 0; var closedRaised = 0; lifetime.Exit += (_, _) => hasExit = true; var windowA = new Window(); windowA.Show(); var windowB = new Window(); windowB.Show(); windowA.Closing += (_, e) => { e.Cancel = true; ++closingRaised; }; windowA.Closed += (_, e) => { ++closedRaised; }; lifetime.Shutdown(); Assert.Equal(1, closingRaised); Assert.Equal(1, closedRaised); Assert.True(hasExit); } } [Fact] public void Shutdown_Doesnt_Raise_Shutdown_Requested() { using (UnitTestApplication.Start(TestServices.StyledWindow)) using(var lifetime = new ClassicDesktopStyleApplicationLifetime()) { Setup(lifetime); var hasExit = false; lifetime.Exit += (_, _) => hasExit = true; var raised = 0; lifetime.ShutdownRequested += (_, _) => { ++raised; }; lifetime.Shutdown(); Assert.Equal(0, raised); Assert.True(hasExit); } } [Fact] public void SetupWithClassicDesktopLifetime_Should_Subscribe_To_Platform_ShutdownRequested() { var lifetimeEvents = new Mock(); ClassicDesktopStyleApplicationLifetime? lifetime = null; CreateAppBuilder(lifetimeEvents.Object).SetupWithClassicDesktopLifetime( [], l => lifetime = (ClassicDesktopStyleApplicationLifetime)l); Assert.NotNull(lifetime); using (lifetime) { var window = new Window(); window.Show(); var raised = 0; lifetime.ShutdownRequested += (_, e) => { e.Cancel = true; ++raised; }; lifetimeEvents.Raise(x => x.ShutdownRequested += null, new ShutdownRequestedEventArgs()); Assert.Equal(1, raised); Assert.Equal([window], lifetime.Windows); } } [Fact] public void SetupWithClassicDesktopLifetime_Should_Not_Raise_Startup() { var frameworkInitCalled = false; var lifetimeBuilderCalled = false; var startupRaised = false; CreateAppBuilder(onFrameworkInitializationCompleted: () => frameworkInitCalled = true) .SetupWithClassicDesktopLifetime( ["foo", "bar"], l => { lifetimeBuilderCalled = true; l.Startup += (_, _) => startupRaised = true; }); Assert.True(frameworkInitCalled); Assert.True(lifetimeBuilderCalled); Assert.False(startupRaised); } [Fact] public void Start_After_SetupWithClassicDesktopLifetime_Should_Not_Raise_Startup_Twice() { ClassicDesktopStyleApplicationLifetime? lifetime = null; var raised = 0; CreateAppBuilder().SetupWithClassicDesktopLifetime( [], l => { lifetime = (ClassicDesktopStyleApplicationLifetime)l; l.Startup += (_, _) => ++raised; }); Assert.NotNull(lifetime); using (lifetime) { Assert.Equal(0, raised); Dispatcher.UIThread.Post(Dispatcher.UIThread.ExitAllFrames); lifetime.Start([]); Assert.Equal(1, raised); } } private static void Setup(ClassicDesktopStyleApplicationLifetime lifetime) { ISetupApplicationLifetime setupLifetime = lifetime; setupLifetime.BeforeAppInit(); setupLifetime.AfterAppInit(); } private static AppBuilder CreateAppBuilder( IPlatformLifetimeEventsImpl? platformLifetimeEvents = null, Action? onFrameworkInitializationCompleted = null) { AppBuilder.ResetSetupForUnitTests(); return AppBuilder.Configure(() => new SetupTestApplication(onFrameworkInitializationCompleted)) .UseRuntimePlatformSubsystem(() => { }) .UseRenderingSubsystem(() => { }) .UseTextShapingSubsystem(() => { }) .UseWindowingSubsystem(() => { if (platformLifetimeEvents is not null) AvaloniaLocator.CurrentMutable.Bind().ToConstant(platformLifetimeEvents); }); } private sealed class SetupTestApplication(Action? onFrameworkInitializationCompleted) : UnitTestApplication(TestServices.StyledWindow) { private bool _servicesRegistered; public override void RegisterServices() { if (_servicesRegistered) return; _servicesRegistered = true; base.RegisterServices(); } public override void OnFrameworkInitializationCompleted() { base.OnFrameworkInitializationCompleted(); onFrameworkInitializationCompleted?.Invoke(); } } } }