| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510 |
- #nullable enable
- using Xunit.Abstractions;
- namespace ApplicationTests;
- /// <summary>
- /// Parallelizable tests for IApplication that don't require the main event loop.
- /// Tests using the modern non-static IApplication API.
- /// </summary>
- 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<ArgumentNullException> (() => 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<bool> 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 ();
- Application.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<bool> ());
- 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<Runnable> ();
- app.Iteration -= Application_Iteration;
- Assert.Equal (1, iteration);
- app.Dispose ();
- return;
- void Application_Iteration (object? sender, EventArgs<IApplication?> 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<Runnable<bool>> 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<Window> (null, "fake");
- // Run<Runnable<bool>> when already initialized or not with a Driver will not throw (because Dialog is derived from Runnable)
- app.Run<Dialog> (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<Runnable> ();
- 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<IApplication?> 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<TestRunnable>. This is ok.
- app.Run<Window> ();
- 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<TestRunnable>. Should be fine.
- app.Run<Window> ();
- app.Dispose ();
- }
- [Fact]
- public void Run_T_NoInit_DoesNotThrow ()
- {
- IApplication app = Application.Create ();
- app.StopAfterFirstIteration = true;
- app.Run<Window> ();
- 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<TestRunnable>. This is ok.
- app.Run<Runnable> (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<IApplication?> 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<Window> (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<object?>)?.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
- }
|