#nullable enable using Xunit.Abstractions; namespace ApplicationTests; /// /// Parallelizable tests for IApplication that don't require the main event loop. /// Tests using the modern non-static IApplication API. /// public class ApplicationTests (ITestOutputHelper output) { private readonly ITestOutputHelper _output = output; [Fact] public void Begin_Null_Runnable_Throws () { IApplication app = Application.Create (); app.Init ("fake"); // Test null Runnable Assert.Throws (() => app.Begin (null!)); app.Dispose (); } [Fact] public void Begin_Sets_Application_Top_To_Console_Size () { IApplication app = Application.Create (); app.Init ("fake"); Assert.Null (app.TopRunnableView); app.Driver!.SetScreenSize (80, 25); Runnable top = new (); SessionToken? token = app.Begin (top); Assert.Equal (new (0, 0, 80, 25), app.TopRunnableView!.Frame); app.Driver!.SetScreenSize (5, 5); app.LayoutAndDraw (); Assert.Equal (new (0, 0, 5, 5), app.TopRunnableView!.Frame); if (token is { }) { app.End (token); } top.Dispose (); app.Dispose (); } [Fact] public void Init_Null_Driver_Should_Pick_A_Driver () { IApplication app = Application.Create (); app.Init (); Assert.NotNull (app.Driver); app.Dispose (); } [Fact] public void Init_Dispose_Cleans_Up () { IApplication app = Application.Create (); app.Init ("fake"); app.Dispose (); #if DEBUG_IDISPOSABLE // Validate there are no outstanding Responder-based instances // after cleanup // Note: We can't check View.Instances in parallel tests as it's a static field // that would be shared across parallel test runs #endif } [Fact] public void Init_Dispose_Fire_InitializedChanged () { var initialized = false; var Dispose = false; IApplication app = Application.Create (); app.InitializedChanged += OnApplicationOnInitializedChanged; app.Init (driverName: "fake"); Assert.True (initialized); Assert.False (Dispose); app.Dispose (); Assert.True (initialized); Assert.True (Dispose); app.InitializedChanged -= OnApplicationOnInitializedChanged; return; void OnApplicationOnInitializedChanged (object? s, EventArgs a) { if (a.Value) { initialized = true; } else { Dispose = true; } } } [Fact] public void Init_KeyBindings_Are_Not_Reset () { IApplication app = Application.Create (); // Set via Keyboard property (modern API) app.Keyboard.QuitKey = Key.Q; Assert.Equal (Key.Q, app.Keyboard.QuitKey); app.Init ("fake"); Assert.Equal (Key.Q, app.Keyboard.QuitKey); app.Dispose (); } [Fact] public void Init_NoParam_ForceDriver_Works () { using IApplication app = Application.Create (); app.ForceDriver = "fake"; // Note: Init() without params picks up driver configuration app.Init (); Assert.Equal ("fake", app.Driver!.GetName ()); } [Fact] public void Init_Dispose_Resets_Instance_Properties () { IApplication app = Application.Create (); // Init the app app.Init (driverName: "fake"); // Verify initialized Assert.True (app.Initialized); Assert.NotNull (app.Driver); // Dispose cleans up app.Dispose (); // Check reset state on the instance CheckReset (app); // Create a new instance and set values app = Application.Create (); app.Init ("fake"); app.StopAfterFirstIteration = true; app.Keyboard.PrevTabGroupKey = Key.A; app.Keyboard.NextTabGroupKey = Key.B; app.Keyboard.QuitKey = Key.C; app.Keyboard.KeyBindings.Add (Key.D, Command.Cancel); app.Mouse.CachedViewsUnderMouse.Clear (); app.Mouse.LastMousePosition = new Point (1, 1); // Dispose and check reset app.Dispose (); CheckReset (app); return; void CheckReset (IApplication application) { // Check that all fields and properties are reset on the instance // Public Properties Assert.Null (application.TopRunnableView); Assert.Null (application.Mouse.MouseGrabView); Assert.Null (application.Driver); Assert.False (application.StopAfterFirstIteration); // Internal properties Assert.False (application.Initialized); Assert.Null (application.MainThreadId); Assert.Empty (application.Mouse.CachedViewsUnderMouse); } } [Fact] public void Internal_Properties_Correct () { IApplication app = Application.Create (); app.Init ("fake"); Assert.True (app.Initialized); Assert.Null (app.TopRunnableView); SessionToken? rs = app.Begin (new Runnable ()); Assert.Equal (app.TopRunnable, rs!.Runnable); Assert.Null (app.Mouse.MouseGrabView); // public app.Dispose (); } [Fact] public void Invoke_Adds_Idle () { IApplication app = Application.Create (); app.Init ("fake"); Runnable top = new (); SessionToken? rs = app.Begin (top); var actionCalled = 0; app.Invoke ((_) => { actionCalled++; }); app.TimedEvents!.RunTimers (); Assert.Equal (1, actionCalled); top.Dispose (); app.Dispose (); } [Fact] public void Run_Iteration_Fires () { var iteration = 0; IApplication app = Application.Create (); app.Init ("fake"); app.Iteration += Application_Iteration; app.Run (); app.Iteration -= Application_Iteration; Assert.Equal (1, iteration); app.Dispose (); return; void Application_Iteration (object? sender, EventArgs e) { iteration++; app.RequestStop (); } } [Fact] public void Screen_Size_Changes () { IApplication app = Application.Create (); app.Init ("fake"); IDriver? driver = app.Driver; app.Driver!.SetScreenSize (80, 25); Assert.Equal (new (0, 0, 80, 25), driver!.Screen); Assert.Equal (new (0, 0, 80, 25), app.Screen); // TODO: Should not be possible to manually change these at whim! driver.Cols = 100; driver.Rows = 30; app.Driver!.SetScreenSize (100, 30); Assert.Equal (new (0, 0, 100, 30), driver.Screen); app.Screen = new (0, 0, driver.Cols, driver.Rows); Assert.Equal (new (0, 0, 100, 30), driver.Screen); app.Dispose (); } [Fact] public void Dispose_Alone_Does_Nothing () { IApplication app = Application.Create (); app.Dispose (); } #region RunTests [Fact] public void Run_T_After_InitWithDriver_with_Runnable_and_Driver_Does_Not_Throw () { IApplication app = Application.Create (); app.StopAfterFirstIteration = true; // Run> when already initialized or not with a Driver will not throw (because Window is derived from Runnable) // Using another type not derived from Runnable will throws at compile time app.Run (null, "fake"); // Run> when already initialized or not with a Driver will not throw (because Dialog is derived from Runnable) app.Run (null, "fake"); app.Dispose (); } [Fact] public void Run_T_After_Init_Does_Not_Disposes_Application_Top () { IApplication app = Application.Create (); app.Init ("fake"); // Init doesn't create a Runnable and assigned it to app.TopRunnable // but Begin does var initTop = new Runnable (); app.Iteration += OnApplicationOnIteration; app.Run (); app.Iteration -= OnApplicationOnIteration; #if DEBUG_IDISPOSABLE Assert.False (initTop.WasDisposed); initTop.Dispose (); Assert.True (initTop.WasDisposed); #endif initTop.Dispose (); app.Dispose (); return; void OnApplicationOnIteration (object? s, EventArgs a) { Assert.NotEqual (initTop, app.TopRunnableView); #if DEBUG_IDISPOSABLE Assert.False (initTop.WasDisposed); #endif app.RequestStop (); } } [Fact] public void Run_T_After_InitWithDriver_with_TestRunnable_DoesNotThrow () { IApplication app = Application.Create (); app.Init ("fake"); app.StopAfterFirstIteration = true; // Init has been called and we're passing no driver to Run. This is ok. app.Run (); app.Dispose (); } [Fact] public void Run_T_After_InitNullDriver_with_TestRunnable_DoesNotThrow () { IApplication app = Application.Create (); app.Init ("fake"); app.StopAfterFirstIteration = true; // Init has been called, selecting FakeDriver; we're passing no driver to Run. Should be fine. app.Run (); app.Dispose (); } [Fact] public void Run_T_NoInit_DoesNotThrow () { IApplication app = Application.Create (); app.StopAfterFirstIteration = true; app.Run (); app.Dispose (); } [Fact] public void Run_T_NoInit_WithDriver_DoesNotThrow () { IApplication app = Application.Create (); app.StopAfterFirstIteration = true; // Init has NOT been called and we're passing a valid driver to Run. This is ok. app.Run (null, "fake"); app.Dispose (); } [Fact] public void Run_Sets_Running_True () { IApplication app = Application.Create (); app.Init ("fake"); var top = new Runnable (); SessionToken? rs = app.Begin (top); Assert.NotNull (rs); app.Iteration += OnApplicationOnIteration; app.Run (top); app.Iteration -= OnApplicationOnIteration; top.Dispose (); app.Dispose (); return; void OnApplicationOnIteration (object? s, EventArgs a) { Assert.True (top.IsRunning); top.RequestStop (); } } [Fact] public void Run_A_Modal_Runnable_Refresh_Background_On_Moving () { IApplication app = Application.Create (); app.Init ("fake"); // 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 }; app.Driver!.SetScreenSize (10, 10); SessionToken? rs = app.Begin (w); // Don't use visuals to test as style of border can change over time. Assert.Equal (new (0, 0), w.Frame.Location); app.Mouse.RaiseMouseEvent (new () { Flags = MouseFlags.Button1Pressed }); Assert.Equal (w.Border, app.Mouse.MouseGrabView); Assert.Equal (new (0, 0), w.Frame.Location); // Move down and to the right. app.Mouse.RaiseMouseEvent (new () { ScreenPosition = new (1, 1), Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition }); Assert.Equal (new (1, 1), w.Frame.Location); app.End (rs!); w.Dispose (); app.Dispose (); } [Fact] public void Run_T_Creates_Top_Without_Init () { IApplication app = Application.Create (); app.StopAfterFirstIteration = true; app.SessionEnded += OnApplicationOnSessionEnded; app.Run (null, "fake"); Assert.Null (app.TopRunnableView); app.Dispose (); Assert.Null (app.TopRunnableView); return; void OnApplicationOnSessionEnded (object? sender, SessionTokenEventArgs e) { app.SessionEnded -= OnApplicationOnSessionEnded; e.State.Result = (e.State.Runnable as IRunnable)?.Result; } } #endregion #region DisposeTests [Fact] public async Task Dispose_Allows_Async () { var isCompletedSuccessfully = false; async Task TaskWithAsyncContinuation () { await Task.Yield (); await Task.Yield (); isCompletedSuccessfully = true; } IApplication app = Application.Create (); app.Dispose (); Assert.False (isCompletedSuccessfully); await TaskWithAsyncContinuation (); Thread.Sleep (100); Assert.True (isCompletedSuccessfully); } [Fact] public void Dispose_Resets_SyncContext () { IApplication app = Application.Create (); app.Dispose (); Assert.Null (SynchronizationContext.Current); } #endregion }