using Microsoft.VisualBasic; using Xunit.Abstractions; // Alias Console to MockConsole so we don't accidentally use Console namespace Terminal.Gui.ApplicationTests; public class ApplicationTests { private readonly ITestOutputHelper _output; public ApplicationTests (ITestOutputHelper output) { _output = output; ConsoleDriver.RunningUnitTests = true; #if DEBUG_IDISPOSABLE Responder.Instances.Clear (); RunState.Instances.Clear (); #endif } [Fact] public void Begin_Null_Toplevel_Throws () { // Setup Mock driver Init (); // Test null Toplevel Assert.Throws (() => Application.Begin (null)); Shutdown (); Assert.Null (Application.Top); Assert.Null (Application.MainLoop); Assert.Null (Application.Driver); } [Fact] [AutoInitShutdown] public void Begin_Sets_Application_Top_To_Console_Size () { Assert.Null (Application.Top); Toplevel top = new (); Application.Begin (top); Assert.Equal (new (0, 0, 80, 25), Application.Top.Frame); ((FakeDriver)Application.Driver!).SetBufferSize (5, 5); Assert.Equal (new (0, 0, 5, 5), Application.Top.Frame); top.Dispose (); } [Fact] public void End_And_Shutdown_Should_Not_Dispose_ApplicationTop () { Init (); RunState rs = Application.Begin (new ()); Assert.Equal (rs.Toplevel, Application.Top); Application.End (rs); #if DEBUG_IDISPOSABLE Assert.True (rs.WasDisposed); Assert.False (Application.Top.WasDisposed); // Is true because the rs.Toplevel is the same as Application.Top #endif Assert.Null (rs.Toplevel); Toplevel top = Application.Top; #if DEBUG_IDISPOSABLE Exception exception = Record.Exception (() => Shutdown ()); Assert.NotNull (exception); Assert.False (top.WasDisposed); top.Dispose (); Assert.True (top.WasDisposed); #endif Shutdown (); Assert.Null (Application.Top); } [Fact] public void Init_Begin_End_Cleans_Up () { Init (); // Begin will cause Run() to be called, which will call Begin(). Thus will block the tests // if we don't stop Application.Iteration += (s, a) => { Application.RequestStop (); }; RunState runstate = null; EventHandler newRunStateFn = (s, e) => { Assert.NotNull (e.State); runstate = e.State; }; Application.NotifyNewRunState += newRunStateFn; var topLevel = new Toplevel (); RunState rs = Application.Begin (topLevel); Assert.NotNull (rs); Assert.NotNull (runstate); Assert.Equal (rs, runstate); Assert.Equal (topLevel, Application.Top); Assert.Equal (topLevel, Application.Current); Application.NotifyNewRunState -= newRunStateFn; Application.End (runstate); Assert.Null (Application.Current); Assert.NotNull (Application.Top); Assert.NotNull (Application.MainLoop); Assert.NotNull (Application.Driver); topLevel.Dispose (); Shutdown (); Assert.Null (Application.Top); Assert.Null (Application.MainLoop); Assert.Null (Application.Driver); } [Theory] [InlineData (typeof (FakeDriver))] [InlineData (typeof (NetDriver))] //[InlineData (typeof (ANSIDriver))] [InlineData (typeof (WindowsDriver))] [InlineData (typeof (CursesDriver))] public void Init_DriverName_Should_Pick_Correct_Driver (Type driverType) { var driver = (ConsoleDriver)Activator.CreateInstance (driverType); Application.Init (driverName: driverType.Name); Assert.NotNull (Application.Driver); Assert.NotEqual (driver, Application.Driver); Assert.Equal (driverType, Application.Driver?.GetType ()); Shutdown (); } [Fact] public void Init_Null_Driver_Should_Pick_A_Driver () { Application.Init (); Assert.NotNull (Application.Driver); Shutdown (); } [Theory] [InlineData (typeof (FakeDriver))] [InlineData (typeof (NetDriver))] [InlineData (typeof (WindowsDriver))] [InlineData (typeof (CursesDriver))] public void Init_ResetState_Resets_Properties (Type driverType) { ConfigurationManager.ThrowOnJsonErrors = true; // For all the fields/properties of Application, check that they are reset to their default values // Set some values Application.Init (driverName: driverType.Name); Application.IsInitialized = true; // Reset Application.ResetState (); void CheckReset () { // Check that all fields and properties are set to their default values // Public Properties Assert.Null (Application.Top); Assert.Null (Application.Current); Assert.Null (Application.MouseGrabView); Assert.Null (Application.WantContinuousButtonPressedView); // Don't check Application.ForceDriver // Assert.Empty (Application.ForceDriver); // Don't check Application.Force16Colors //Assert.False (Application.Force16Colors); Assert.Null (Application.Driver); Assert.Null (Application.MainLoop); Assert.False (Application.EndAfterFirstIteration); Assert.Equal (Key.Empty, Application.AlternateBackwardKey); Assert.Equal (Key.Empty, Application.AlternateForwardKey); Assert.Equal (Key.Empty, Application.QuitKey); Assert.Null (ApplicationOverlapped.OverlappedChildren); Assert.Null (ApplicationOverlapped.OverlappedTop); // Internal properties Assert.False (Application.IsInitialized); Assert.Equal (Application.GetSupportedCultures (), Application.SupportedCultures); Assert.False (Application._forceFakeConsole); Assert.Equal (-1, Application.MainThreadId); Assert.Empty (Application.TopLevels); Assert.Null (Application.MouseEnteredView); // Keyboard Assert.Empty (Application.GetViewKeyBindings ()); // Events - Can't check //Assert.Null (Application.NotifyNewRunState); //Assert.Null (Application.NotifyNewRunState); //Assert.Null (Application.Iteration); //Assert.Null (Application.SizeChanging); //Assert.Null (Application.GrabbedMouse); //Assert.Null (Application.UnGrabbingMouse); //Assert.Null (Application.GrabbedMouse); //Assert.Null (Application.UnGrabbedMouse); //Assert.Null (Application.MouseEvent); //Assert.Null (Application.KeyDown); //Assert.Null (Application.KeyUp); } CheckReset (); // Set the values that can be set Application.IsInitialized = true; Application._forceFakeConsole = true; Application.MainThreadId = 1; //Application._topLevels = new List (); Application.MouseEnteredView = new (); //Application.SupportedCultures = new List (); Application.Force16Colors = true; //Application.ForceDriver = "driver"; Application.EndAfterFirstIteration = true; Application.AlternateBackwardKey = Key.A; Application.AlternateForwardKey = Key.B; Application.QuitKey = Key.C; Application.KeyBindings.Add (Key.A, KeyBindingScope.Application, Command.Cancel); //ApplicationOverlapped.OverlappedChildren = new List (); //ApplicationOverlapped.OverlappedTop = Application.MouseEnteredView = new (); //Application.WantContinuousButtonPressedView = new View (); Application.ResetState (); CheckReset (); ConfigurationManager.ThrowOnJsonErrors = false; } [Fact] public void Init_Shutdown_Cleans_Up () { // Verify initial state is per spec //Pre_Init_State (); Application.Init (new FakeDriver ()); // Verify post-Init state is correct //Post_Init_State (); Application.Shutdown (); // Verify state is back to initial //Pre_Init_State (); #if DEBUG_IDISPOSABLE // Validate there are no outstanding Responder-based instances // after a scenario was selected to run. This proves the main UI Catalog // 'app' closed cleanly. Assert.Empty (Responder.Instances); #endif } [Theory] [InlineData (typeof (FakeDriver))] [InlineData (typeof (NetDriver))] [InlineData (typeof (WindowsDriver))] [InlineData (typeof (CursesDriver))] public void Init_Shutdown_Fire_InitializedChanged (Type driverType) { bool initialized = false; bool shutdown = false; Application.InitializedChanged += OnApplicationOnInitializedChanged; Application.Init (driverName: driverType.Name); Assert.True (initialized); Assert.False (shutdown); Application.Shutdown (); Assert.True (initialized); Assert.True (shutdown); Application.InitializedChanged -= OnApplicationOnInitializedChanged; return; void OnApplicationOnInitializedChanged (object s, EventArgs a) { if (a.CurrentValue) { initialized = true; } else { shutdown = true; } } } [Fact] public void Run_Iteration_Fires () { int iteration = 0; Application.Init (new FakeDriver ()); Application.Iteration += Application_Iteration; Application.Run ().Dispose (); Assert.Equal (1, iteration); Application.Shutdown (); return; void Application_Iteration (object sender, IterationEventArgs e) { if (iteration > 0) { Assert.Fail (); } iteration++; Application.RequestStop (); } } [Fact] public void Init_Unbalanced_Throws () { Application.Init (new FakeDriver ()); Assert.Throws ( () => Application.InternalInit ( new FakeDriver () ) ); Shutdown (); Assert.Null (Application.Top); Assert.Null (Application.MainLoop); Assert.Null (Application.Driver); // Now try the other way Application.InternalInit (new FakeDriver ()); Assert.Throws (() => Application.Init (new FakeDriver ())); Shutdown (); Assert.Null (Application.Top); Assert.Null (Application.MainLoop); Assert.Null (Application.Driver); } [Fact] public void InitWithoutTopLevelFactory_Begin_End_Cleans_Up () { // Begin will cause Run() to be called, which will call Begin(). Thus will block the tests // if we don't stop Application.Iteration += (s, a) => { Application.RequestStop (); }; // NOTE: Run, when called after Init has been called behaves differently than // when called if Init has not been called. Toplevel topLevel = new (); Application.InternalInit (new FakeDriver ()); RunState runstate = null; EventHandler newRunStateFn = (s, e) => { Assert.NotNull (e.State); runstate = e.State; }; Application.NotifyNewRunState += newRunStateFn; RunState rs = Application.Begin (topLevel); Assert.NotNull (rs); Assert.NotNull (runstate); Assert.Equal (rs, runstate); Assert.Equal (topLevel, Application.Top); Assert.Equal (topLevel, Application.Current); Application.NotifyNewRunState -= newRunStateFn; Application.End (runstate); Assert.Null (Application.Current); Assert.NotNull (Application.Top); Assert.NotNull (Application.MainLoop); Assert.NotNull (Application.Driver); topLevel.Dispose (); Shutdown (); Assert.Null (Application.Top); Assert.Null (Application.MainLoop); Assert.Null (Application.Driver); } [Fact] [AutoInitShutdown] public void Internal_Properties_Correct () { Assert.True (Application.IsInitialized); Assert.Null (Application.Top); RunState rs = Application.Begin (new ()); Assert.Equal (Application.Top, rs.Toplevel); Assert.Null (Application.MouseGrabView); // public Assert.Null (Application.WantContinuousButtonPressedView); // public Assert.False (ApplicationOverlapped.MoveToOverlappedChild (Application.Top!)); Application.Top.Dispose (); } // Invoke Tests // TODO: Test with threading scenarios [Fact] public void Invoke_Adds_Idle () { Application.Init (new FakeDriver ()); var top = new Toplevel (); RunState rs = Application.Begin (top); var firstIteration = false; var actionCalled = 0; Application.Invoke (() => { actionCalled++; }); Application.MainLoop.Running = true; Application.RunIteration (ref rs, ref firstIteration); Assert.Equal (1, actionCalled); top.Dispose (); Application.Shutdown (); } [Fact] [AutoInitShutdown] public void SetCurrentAsTop_Run_A_Not_Modal_Toplevel_Make_It_The_Current_Application_Top () { var top = new Toplevel (); var t1 = new Toplevel (); var t2 = new Toplevel (); var t3 = new Toplevel (); // Don't use Dialog here as it has more layout logic. Use Window instead. var d = new Dialog (); var t4 = new Toplevel (); // t1, t2, t3, d, t4 var iterations = 5; t1.Ready += (s, e) => { Assert.Equal (t1, Application.Top); Application.Run (t2); }; t2.Ready += (s, e) => { Assert.Equal (t2, Application.Top); Application.Run (t3); }; t3.Ready += (s, e) => { Assert.Equal (t3, Application.Top); Application.Run (d); }; d.Ready += (s, e) => { Assert.Equal (t3, Application.Top); Application.Run (t4); }; t4.Ready += (s, e) => { Assert.Equal (t4, Application.Top); t4.RequestStop (); d.RequestStop (); t3.RequestStop (); t2.RequestStop (); }; // Now this will close the OverlappedContainer when all OverlappedChildren was closed t2.Closed += (s, _) => { t1.RequestStop (); }; Application.Iteration += (s, a) => { if (iterations == 5) { // The Current still is t4 because Current.Running is false. Assert.Equal (t4, Application.Current); Assert.False (Application.Current.Running); Assert.Equal (t4, Application.Top); } else if (iterations == 4) { // The Current is d and Current.Running is false. Assert.Equal (d, Application.Current); Assert.False (Application.Current.Running); Assert.Equal (t4, Application.Top); } else if (iterations == 3) { // The Current is t3 and Current.Running is false. Assert.Equal (t3, Application.Current); Assert.False (Application.Current.Running); Assert.Equal (t3, Application.Top); } else if (iterations == 2) { // The Current is t2 and Current.Running is false. Assert.Equal (t2, Application.Current); Assert.False (Application.Current.Running); Assert.Equal (t2, Application.Top); } else { // The Current is t1. Assert.Equal (t1, Application.Current); Assert.False (Application.Current.Running); Assert.Equal (t1, Application.Top); } iterations--; }; Application.Run (t1); Assert.Equal (t1, Application.Top); // top wasn't run and so never was added to toplevel's stack Assert.NotEqual (top, Application.Top); #if DEBUG_IDISPOSABLE Assert.False (Application.Top.WasDisposed); t1.Dispose (); Assert.True (Application.Top.WasDisposed); #endif } private void Init () { Application.Init (new FakeDriver ()); Assert.NotNull (Application.Driver); Assert.NotNull (Application.MainLoop); Assert.NotNull (SynchronizationContext.Current); } private void Post_Init_State () { Assert.NotNull (Application.Driver); Assert.NotNull (Application.Top); Assert.NotNull (Application.Current); Assert.NotNull (Application.MainLoop); // FakeDriver is always 80x25 Assert.Equal (80, Application.Driver!.Cols); Assert.Equal (25, Application.Driver!.Rows); } private void Pre_Init_State () { Assert.Null (Application.Driver); Assert.Null (Application.Top); Assert.Null (Application.Current); Assert.Null (Application.MainLoop); } private void Shutdown () { Application.Shutdown (); } private class TestToplevel : Toplevel { public TestToplevel () { IsOverlappedContainer = false; } } #region RunTests [Fact] public void Run_T_After_InitWithDriver_with_TopLevel_Does_Not_Throws () { // Setup Mock driver Init (); Application.Iteration += (s, e) => Application.RequestStop (); // Run when already initialized or not with a Driver will not throw (because Window is derived from Toplevel) // Using another type not derived from Toplevel will throws at compile time Application.Run (); Assert.True (Application.Top is Window); Application.Top.Dispose (); Shutdown (); Assert.Null (Application.Top); Assert.Null (Application.MainLoop); Assert.Null (Application.Driver); } [Fact] public void Run_T_After_InitWithDriver_with_TopLevel_and_Driver_Does_Not_Throws () { // Setup Mock driver Init (); Application.Iteration += (s, e) => Application.RequestStop (); // Run when already initialized or not with a Driver will not throw (because Window is derived from Toplevel) // Using another type not derived from Toplevel will throws at compile time Application.Run (null, new FakeDriver ()); Assert.True (Application.Top is Window); Application.Top.Dispose (); // Run when already initialized or not with a Driver will not throw (because Dialog is derived from Toplevel) Application.Run (null, new FakeDriver ()); Assert.True (Application.Top is Dialog); Application.Top.Dispose (); Shutdown (); Assert.Null (Application.Top); Assert.Null (Application.MainLoop); Assert.Null (Application.Driver); } [Fact] [TestRespondersDisposed] public void Run_T_After_Init_Does_Not_Disposes_Application_Top () { Init (); // Init doesn't create a Toplevel and assigned it to Application.Top // but Begin does var initTop = new Toplevel (); Application.Iteration += (s, a) => { Assert.NotEqual (initTop, Application.Top); #if DEBUG_IDISPOSABLE Assert.False (initTop.WasDisposed); #endif Application.RequestStop (); }; Application.Run (); #if DEBUG_IDISPOSABLE Assert.False (initTop.WasDisposed); initTop.Dispose (); Assert.True (initTop.WasDisposed); #endif Application.Top.Dispose (); Shutdown (); Assert.Null (Application.Top); Assert.Null (Application.MainLoop); Assert.Null (Application.Driver); } [Fact] [TestRespondersDisposed] public void Run_T_After_InitWithDriver_with_TestTopLevel_DoesNotThrow () { // Setup Mock driver Init (); Application.Iteration += (s, a) => { Application.RequestStop (); }; // Init has been called and we're passing no driver to Run. This is ok. Application.Run (); Application.Top.Dispose (); Shutdown (); Assert.Null (Application.Top); Assert.Null (Application.MainLoop); Assert.Null (Application.Driver); } [Fact] [TestRespondersDisposed] public void Run_T_After_InitNullDriver_with_TestTopLevel_DoesNotThrow () { Application.ForceDriver = "FakeDriver"; Application.Init (); Assert.Equal (typeof (FakeDriver), Application.Driver?.GetType ()); Application.Iteration += (s, a) => { Application.RequestStop (); }; // Init has been called, selecting FakeDriver; we're passing no driver to Run. Should be fine. Application.Run (); Application.Top.Dispose (); Shutdown (); Assert.Null (Application.Top); Assert.Null (Application.MainLoop); Assert.Null (Application.Driver); } [Fact] [TestRespondersDisposed] public void Run_T_Init_Driver_Cleared_with_TestTopLevel_Throws () { Init (); Application.Driver = null; // Init has been called, but Driver has been set to null. Bad. Assert.Throws (() => Application.Run ()); Shutdown (); Assert.Null (Application.Top); Assert.Null (Application.MainLoop); Assert.Null (Application.Driver); } [Fact] [TestRespondersDisposed] public void Run_T_NoInit_DoesNotThrow () { Application.ForceDriver = "FakeDriver"; Application.Iteration += (s, a) => { Application.RequestStop (); }; Application.Run (); Assert.Equal (typeof (FakeDriver), Application.Driver?.GetType ()); Application.Top.Dispose (); Shutdown (); Assert.Null (Application.Top); Assert.Null (Application.MainLoop); Assert.Null (Application.Driver); } [Fact] [TestRespondersDisposed] public void Run_T_NoInit_WithDriver_DoesNotThrow () { Application.Iteration += (s, a) => { Application.RequestStop (); }; // Init has NOT been called and we're passing a valid driver to Run. This is ok. Application.Run (null, new FakeDriver ()); Application.Top.Dispose (); Shutdown (); Assert.Null (Application.Top); Assert.Null (Application.MainLoop); Assert.Null (Application.Driver); } [Fact] [TestRespondersDisposed] public void Run_RequestStop_Stops () { // Setup Mock driver Init (); var top = new Toplevel (); RunState rs = Application.Begin (top); Assert.NotNull (rs); Assert.Equal (top, Application.Current); Application.Iteration += (s, a) => { Application.RequestStop (); }; Application.Run (top); top.Dispose (); Application.Shutdown (); Assert.Null (Application.Current); Assert.Null (Application.Top); Assert.Null (Application.MainLoop); Assert.Null (Application.Driver); } [Fact] [TestRespondersDisposed] public void Run_RunningFalse_Stops () { // Setup Mock driver Init (); var top = new Toplevel (); RunState rs = Application.Begin (top); Assert.NotNull (rs); Assert.Equal (top, Application.Current); Application.Iteration += (s, a) => { top.Running = false; }; Application.Run (top); top.Dispose (); Application.Shutdown (); Assert.Null (Application.Current); Assert.Null (Application.Top); Assert.Null (Application.MainLoop); Assert.Null (Application.Driver); } [Fact] [TestRespondersDisposed] public void Run_Loaded_Ready_Unlodaded_Events () { Init (); Toplevel top = new (); var count = 0; top.Loaded += (s, e) => count++; top.Ready += (s, e) => count++; top.Unloaded += (s, e) => count++; Application.Iteration += (s, a) => Application.RequestStop (); Application.Run (top); top.Dispose (); Application.Shutdown (); Assert.Equal (3, count); } // TODO: All Toplevel layout tests should be moved to ToplevelTests.cs [Fact] public void Run_Toplevel_With_Modal_View_Does_Not_Refresh_If_Not_Dirty () { Init (); var count = 0; // Don't use Dialog here as it has more layout logic. Use Window instead. Dialog d = null; Toplevel top = new (); top.DrawContent += (s, a) => count++; int iteration = -1; Application.Iteration += (s, a) => { iteration++; if (iteration == 0) { // TODO: Don't use Dialog here as it has more layout logic. Use Window instead. d = new (); d.DrawContent += (s, a) => count++; Application.Run (d); } else if (iteration < 3) { Application.OnMouseEvent (new () { Flags = MouseFlags.ReportMousePosition }); Assert.False (top.NeedsDisplay); Assert.False (top.SubViewNeedsDisplay); Assert.False (top.LayoutNeeded); Assert.False (d.NeedsDisplay); Assert.False (d.SubViewNeedsDisplay); Assert.False (d.LayoutNeeded); } else { Application.RequestStop (); } }; Application.Run (top); top.Dispose (); Application.Shutdown (); // 1 - First top load, 1 - Dialog load, 1 - Dialog unload, Total - 3. Assert.Equal (3, count); } // TODO: All Toplevel layout tests should be moved to ToplevelTests.cs [Fact] public void Run_A_Modal_Toplevel_Refresh_Background_On_Moving () { Init (); // Don't use Dialog here as it has more layout logic. Use Window instead. var w = new Window { Width = 5, Height = 5, Arrangement = ViewArrangement.Movable }; ((FakeDriver)Application.Driver!).SetBufferSize (10, 10); RunState rs = Application.Begin (w); // Don't use visuals to test as style of border can change over time. Assert.Equal (new Point (0, 0), w.Frame.Location); Application.OnMouseEvent (new () { Flags = MouseFlags.Button1Pressed }); Assert.Equal (w.Border, Application.MouseGrabView); Assert.Equal (new Point (0, 0), w.Frame.Location); // Move down and to the right. Application.OnMouseEvent (new () { Position = new (1, 1), Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition }); Assert.Equal (new Point (1, 1), w.Frame.Location); Application.End (rs); w.Dispose (); Application.Shutdown (); } [Fact] public void End_Does_Not_Dispose () { Init (); var top = new Toplevel (); Window w = new (); w.Ready += (s, e) => Application.RequestStop (); // Causes `End` to be called Application.Run (w); #if DEBUG_IDISPOSABLE Assert.False (w.WasDisposed); #endif Assert.NotNull (w); Assert.Equal (string.Empty, w.Title); // Valid - w has not been disposed. The user may want to run it again Assert.NotNull (Application.Top); Assert.Equal (w, Application.Top); Assert.NotEqual (top, Application.Top); Assert.Null (Application.Current); Application.Run (w); // Valid - w has not been disposed. #if DEBUG_IDISPOSABLE Assert.False (w.WasDisposed); Exception exception = Record.Exception (Application.Shutdown); // Invalid - w has not been disposed. Assert.NotNull (exception); w.Dispose (); Assert.True (w.WasDisposed); exception = Record.Exception ( () => Application.Run ( w)); // Invalid - w has been disposed. Run it in debug mode will throw, otherwise the user may want to run it again Assert.NotNull (exception); exception = Record.Exception (() => Assert.Equal (string.Empty, w.Title)); // Invalid - w has been disposed and cannot be accessed Assert.NotNull (exception); exception = Record.Exception (() => w.Title = "NewTitle"); // Invalid - w has been disposed and cannot be accessed Assert.NotNull (exception); #endif Application.Shutdown (); Assert.NotNull (w); Assert.Null (Application.Current); Assert.NotNull (top); Assert.Null (Application.Top); } [Fact] public void Run_Creates_Top_Without_Init () { var driver = new FakeDriver (); Assert.Null (Application.Top); Application.Iteration += (s, e) => { Assert.NotNull (Application.Top); Application.RequestStop (); }; Toplevel top = Application.Run (null, driver); #if DEBUG_IDISPOSABLE Assert.Equal (top, Application.Top); Assert.False (top.WasDisposed); Exception exception = Record.Exception (Application.Shutdown); Assert.NotNull (exception); Assert.False (top.WasDisposed); #endif // It's up to caller to dispose it top.Dispose (); #if DEBUG_IDISPOSABLE Assert.True (top.WasDisposed); #endif Assert.NotNull (Application.Top); Application.Shutdown (); Assert.Null (Application.Top); } [Fact] public void Run_T_Creates_Top_Without_Init () { var driver = new FakeDriver (); Assert.Null (Application.Top); Application.Iteration += (s, e) => { Assert.NotNull (Application.Top); Application.RequestStop (); }; Application.Run (null, driver); #if DEBUG_IDISPOSABLE Assert.False (Application.Top.WasDisposed); Exception exception = Record.Exception (Application.Shutdown); Assert.NotNull (exception); Assert.False (Application.Top.WasDisposed); // It's up to caller to dispose it Application.Top.Dispose (); Assert.True (Application.Top.WasDisposed); #endif Assert.NotNull (Application.Top); Application.Shutdown (); Assert.Null (Application.Top); } [Fact] public void Run_t_Does_Not_Creates_Top_Without_Init () { // When a Toplevel is created it must already have all the Application configuration loaded // This is only possible by two ways: // 1 - Using Application.Init first // 2 - Using Application.Run() or Application.Run() // The Application.Run(new(Toplevel)) must always call Application.Init() first because // the new(Toplevel) may be a derived class that is possible using Application static // properties that is only available after the Application.Init was called var driver = new FakeDriver (); Assert.Null (Application.Top); Assert.Throws (() => Application.Run (new Toplevel ())); Application.Init (driver); Application.Iteration += (s, e) => { Assert.NotNull (Application.Top); Application.RequestStop (); }; Application.Run (new Toplevel ()); #if DEBUG_IDISPOSABLE Assert.False (Application.Top.WasDisposed); Exception exception = Record.Exception (Application.Shutdown); Assert.NotNull (exception); Assert.False (Application.Top.WasDisposed); // It's up to caller to dispose it Application.Top.Dispose (); Assert.True (Application.Top.WasDisposed); #endif Assert.NotNull (Application.Top); Application.Shutdown (); Assert.Null (Application.Top); } // TODO: Add tests for Run that test errorHandler #endregion #region ShutdownTests [Fact] public async void Shutdown_Allows_Async () { var isCompletedSuccessfully = false; async Task TaskWithAsyncContinuation () { await Task.Yield (); await Task.Yield (); isCompletedSuccessfully = true; } Init (); Application.Shutdown (); Assert.False (isCompletedSuccessfully); await TaskWithAsyncContinuation (); Thread.Sleep (100); Assert.True (isCompletedSuccessfully); } [Fact] public void Shutdown_Resets_SyncContext () { Init (); Application.Shutdown (); Assert.Null (SynchronizationContext.Current); } #endregion private object _timeoutLock; [Fact] public void AddTimeout_Fires () { Assert.Null (_timeoutLock); _timeoutLock = new object (); uint timeoutTime = 250; bool initialized = false; int iteration = 0; bool shutdown = false; object timeout = null; int timeoutCount = 0; Application.InitializedChanged += OnApplicationOnInitializedChanged; Application.Init (new FakeDriver ()); Assert.True (initialized); Assert.False (shutdown); _output.WriteLine ("Application.Run ().Dispose ().."); Application.Run ().Dispose (); _output.WriteLine ("Back from Application.Run ().Dispose ()"); Assert.True (initialized); Assert.False (shutdown); Assert.Equal (1, timeoutCount); Application.Shutdown (); Application.InitializedChanged -= OnApplicationOnInitializedChanged; lock (_timeoutLock) { if (timeout is { }) { Application.RemoveTimeout (timeout); timeout = null; } } Assert.True (initialized); Assert.True (shutdown); #if DEBUG_IDISPOSABLE Assert.Empty (Responder.Instances); #endif lock (_timeoutLock) { _timeoutLock = null; } return; void OnApplicationOnInitializedChanged (object s, EventArgs a) { if (a.CurrentValue) { Application.Iteration += OnApplicationOnIteration; initialized = true; lock (_timeoutLock) { _output.WriteLine ($"Setting timeout for {timeoutTime}ms"); timeout = Application.AddTimeout (TimeSpan.FromMilliseconds (timeoutTime), TimeoutCallback); } } else { Application.Iteration -= OnApplicationOnIteration; shutdown = true; } } bool TimeoutCallback () { lock (_timeoutLock) { _output.WriteLine ($"TimeoutCallback. Count: {++timeoutCount}. Application Iteration: {iteration}"); if (timeout is { }) { _output.WriteLine ($" Nulling timeout."); timeout = null; } } // False means "don't re-do timer and remove it" return false; } void OnApplicationOnIteration (object s, IterationEventArgs a) { lock (_timeoutLock) { if (timeoutCount > 0) { _output.WriteLine ($"Iteration #{iteration} - Timeout fired. Calling Application.RequestStop."); Application.RequestStop (); return; } } iteration++; // Simulate a delay Thread.Sleep ((int)timeoutTime / 10); // Worst case scenario - something went wrong if (Application.IsInitialized && iteration > 25) { _output.WriteLine ($"Too many iterations ({iteration}): Calling Application.RequestStop."); Application.RequestStop (); } } } }