diff --git a/Terminal.Gui/App/ApplicationImpl.Run.cs b/Terminal.Gui/App/ApplicationImpl.Run.cs index 1e037fee2..9f8e0a9b8 100644 --- a/Terminal.Gui/App/ApplicationImpl.Run.cs +++ b/Terminal.Gui/App/ApplicationImpl.Run.cs @@ -368,6 +368,8 @@ internal partial class ApplicationImpl previousRunnable.RaiseIsModalChangedEvent (true); } + Mouse?.UngrabMouse (); + runnable.RaiseIsRunningChangedEvent (false); token.Result = runnable.Result; diff --git a/Terminal.Gui/App/Mouse/MouseImpl.cs b/Terminal.Gui/App/Mouse/MouseImpl.cs index fe8c26d37..ce9e0a9b6 100644 --- a/Terminal.Gui/App/Mouse/MouseImpl.cs +++ b/Terminal.Gui/App/Mouse/MouseImpl.cs @@ -266,11 +266,17 @@ internal class MouseImpl : IMouse, IDisposable /// public void GrabMouse (View? view) { - if (view is null || RaiseGrabbingMouseEvent (view)) + if (RaiseGrabbingMouseEvent (view)) { return; } + if (view is null) + { + UngrabMouse(); + return; + } + RaiseGrabbedMouseEvent (view); // MouseGrabView is only set if the application is initialized. diff --git a/Terminal.Gui/ViewBase/Adornment/Border.Arrangment.cs b/Terminal.Gui/ViewBase/Adornment/Border.Arrangment.cs index ac0a593a1..c170ff949 100644 --- a/Terminal.Gui/ViewBase/Adornment/Border.Arrangment.cs +++ b/Terminal.Gui/ViewBase/Adornment/Border.Arrangment.cs @@ -1,4 +1,3 @@ -using System.ComponentModel; using System.Diagnostics; namespace Terminal.Gui.ViewBase; @@ -766,6 +765,17 @@ public partial class Border } } + /// + /// Cancels events during an active drag to prevent other views from + /// stealing the mouse grab mid-operation. + /// + /// + /// During an Arrange Mode drag ( has a value), Border owns the mouse grab and + /// must receive all mouse events until Button1Released. If another view (e.g., scrollbar, slider) were allowed + /// to grab the mouse, the drag would freeze, leaving Border in an inconsistent state with no cleanup. + /// Canceling follows the CWP pattern, ensuring Border maintains exclusive mouse control until it explicitly + /// releases via in . + /// private void Application_GrabbingMouse (object? sender, GrabMouseEventArgs e) { if (App?.Mouse.MouseGrabView == this && _dragPosition.HasValue) @@ -774,25 +784,14 @@ public partial class Border } } - private void Application_UnGrabbingMouse (object? sender, GrabMouseEventArgs e) - { - if (App?.Mouse.MouseGrabView == this && _dragPosition.HasValue) - { - e.Cancel = true; - } - } - #endregion Mouse Support - - /// protected override void Dispose (bool disposing) { if (App is { }) { App.Mouse.GrabbingMouse -= Application_GrabbingMouse; - App.Mouse.UnGrabbingMouse -= Application_UnGrabbingMouse; } _dragPosition = null; diff --git a/Terminal.Gui/ViewBase/Adornment/Border.cs b/Terminal.Gui/ViewBase/Adornment/Border.cs index 65a38f811..3e996c270 100644 --- a/Terminal.Gui/ViewBase/Adornment/Border.cs +++ b/Terminal.Gui/ViewBase/Adornment/Border.cs @@ -111,7 +111,6 @@ public partial class Border : Adornment if (App is { }) { App.Mouse.GrabbingMouse += Application_GrabbingMouse; - App.Mouse.UnGrabbingMouse += Application_UnGrabbingMouse; } if (Parent is null) diff --git a/Terminal.Gui/ViewBase/Runnable/Runnable.cs b/Terminal.Gui/Views/Runnable/Runnable.cs similarity index 99% rename from Terminal.Gui/ViewBase/Runnable/Runnable.cs rename to Terminal.Gui/Views/Runnable/Runnable.cs index d51363354..8d0332732 100644 --- a/Terminal.Gui/ViewBase/Runnable/Runnable.cs +++ b/Terminal.Gui/Views/Runnable/Runnable.cs @@ -1,4 +1,4 @@ -namespace Terminal.Gui.ViewBase; +namespace Terminal.Gui.Views; /// /// Base implementation of for views that can be run as blocking sessions without returning a result. diff --git a/Terminal.Gui/ViewBase/Runnable/RunnableTResult.cs b/Terminal.Gui/Views/Runnable/RunnableTResult.cs similarity index 98% rename from Terminal.Gui/ViewBase/Runnable/RunnableTResult.cs rename to Terminal.Gui/Views/Runnable/RunnableTResult.cs index 44245b235..872f1ec3a 100644 --- a/Terminal.Gui/ViewBase/Runnable/RunnableTResult.cs +++ b/Terminal.Gui/Views/Runnable/RunnableTResult.cs @@ -1,4 +1,4 @@ -namespace Terminal.Gui.ViewBase; +namespace Terminal.Gui.Views; /// /// Base implementation of for views that can be run as blocking sessions. diff --git a/Terminal.Gui/ViewBase/Runnable/RunnableWrapper.cs b/Terminal.Gui/Views/Runnable/RunnableWrapper.cs similarity index 98% rename from Terminal.Gui/ViewBase/Runnable/RunnableWrapper.cs rename to Terminal.Gui/Views/Runnable/RunnableWrapper.cs index bf10b4c0f..fde3c48c1 100644 --- a/Terminal.Gui/ViewBase/Runnable/RunnableWrapper.cs +++ b/Terminal.Gui/Views/Runnable/RunnableWrapper.cs @@ -1,4 +1,4 @@ -namespace Terminal.Gui.ViewBase; +namespace Terminal.Gui.Views; /// /// Wraps any to make it runnable with a typed result, similar to how diff --git a/Terminal.Gui/ViewBase/Runnable/ViewRunnableExtensions.cs b/Terminal.Gui/Views/Runnable/ViewRunnableExtensions.cs similarity index 99% rename from Terminal.Gui/ViewBase/Runnable/ViewRunnableExtensions.cs rename to Terminal.Gui/Views/Runnable/ViewRunnableExtensions.cs index 7b12bb055..ea9eca269 100644 --- a/Terminal.Gui/ViewBase/Runnable/ViewRunnableExtensions.cs +++ b/Terminal.Gui/Views/Runnable/ViewRunnableExtensions.cs @@ -1,4 +1,4 @@ -namespace Terminal.Gui.ViewBase; +namespace Terminal.Gui.Views; /// /// Extension methods for making any runnable with typed results. diff --git a/Tests/UnitTests/View/Mouse/MouseTests.cs b/Tests/UnitTests/View/Mouse/MouseTests.cs index c832af152..f2c17e186 100644 --- a/Tests/UnitTests/View/Mouse/MouseTests.cs +++ b/Tests/UnitTests/View/Mouse/MouseTests.cs @@ -49,8 +49,10 @@ public class MouseTests : TestsAllViews Application.RaiseMouseEvent (new () { ScreenPosition = new (xy + 1, xy + 1), Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition }); AutoInitShutdownAttribute.RunIteration (); - Assert.Equal (expectedMoved, new Point (5, 5) == testView.Frame.Location); + // The above grabbed the mouse. Need to ungrab. + Application.Mouse.UngrabMouse (); + top.Dispose (); } diff --git a/Tests/UnitTests/View/ViewCommandTests.cs b/Tests/UnitTests/View/ViewCommandTests.cs index f77f36af1..ae4fe2e12 100644 --- a/Tests/UnitTests/View/ViewCommandTests.cs +++ b/Tests/UnitTests/View/ViewCommandTests.cs @@ -152,6 +152,9 @@ public class ViewCommandTests Assert.Equal (1, btnAcceptedCount); Assert.Equal (0, wAcceptedCount); + // The above grabbed the mouse. Need to ungrab. + Application.Mouse.UngrabMouse (); + w.Dispose (); Application.ResetState (true); } diff --git a/Tests/UnitTestsParallelizable/Application/Application.NavigationTests.cs b/Tests/UnitTestsParallelizable/Application/Application.NavigationTests.cs index 6d25610bd..2085e70a8 100644 --- a/Tests/UnitTestsParallelizable/Application/Application.NavigationTests.cs +++ b/Tests/UnitTestsParallelizable/Application/Application.NavigationTests.cs @@ -1,6 +1,6 @@ using Xunit.Abstractions; -namespace ApplicationTests; +namespace ApplicationTests.Navigation; public class ApplicationNavigationTests (ITestOutputHelper output) { diff --git a/Tests/UnitTestsParallelizable/Application/ApplicationImplTests.cs b/Tests/UnitTestsParallelizable/Application/ApplicationImplTests.cs index 048c683d5..1e542dc46 100644 --- a/Tests/UnitTestsParallelizable/Application/ApplicationImplTests.cs +++ b/Tests/UnitTestsParallelizable/Application/ApplicationImplTests.cs @@ -5,6 +5,66 @@ namespace ApplicationTests; public class ApplicationImplTests { + + [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 (); + } + + + #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); + } + + [Fact] + public void Dispose_Alone_Does_Nothing () + { + IApplication app = Application.Create (); + app.Dispose (); + } + + + #endregion + + /// /// Crates a new ApplicationImpl instance for testing. The input, output, and size monitor components are mocked. /// @@ -44,21 +104,6 @@ public class ApplicationImplTests .Verifiable (Times.Once); } - [Fact] - public void Init_CreatesKeybindings () - { - IApplication app = NewMockedApplicationImpl (); - - app.Keyboard.KeyBindings.Clear (); - - Assert.Empty (app.Keyboard.KeyBindings.GetBindings ()); - - app.Init ("fake"); - - Assert.NotEmpty (app.Keyboard.KeyBindings.GetBindings ()); - - app.Dispose (); - } [Fact] public void NoInitThrowOnRun () @@ -480,81 +525,4 @@ public class ApplicationImplTests Assert.Null (v2.TopRunnableView); Assert.Empty (v2.SessionStack!); } - - [Fact] - public void Init_Begin_End_Cleans_Up () - { - IApplication? app = Application.Create (); - - SessionToken? newSessionToken = null; - - EventHandler newSessionTokenFn = (s, e) => - { - Assert.NotNull (e.State); - newSessionToken = e.State; - }; - app.SessionBegun += newSessionTokenFn; - - Runnable runnable = new (); - SessionToken sessionToken = app.Begin (runnable)!; - Assert.NotNull (sessionToken); - Assert.NotNull (newSessionToken); - Assert.Equal (sessionToken, newSessionToken); - - // Assert.Equal (runnable, Application.TopRunnable); - - app.SessionBegun -= newSessionTokenFn; - app.End (newSessionToken); - - Assert.Null (app.TopRunnable); - Assert.Null (app.Driver); - - runnable.Dispose (); - } - - [Fact] - public void Run_RequestStop_Stops () - { - IApplication? app = Application.Create (); - app.Init ("fake"); - - var top = new Runnable (); - SessionToken? sessionToken = app.Begin (top); - Assert.NotNull (sessionToken); - - app.Iteration += OnApplicationOnIteration; - app.Run (top); - app.Iteration -= OnApplicationOnIteration; - - top.Dispose (); - - return; - - void OnApplicationOnIteration (object? s, EventArgs a) { app.RequestStop (); } - } - - [Fact] - public void Run_T_Init_Driver_Cleared_with_Runnable_Throws () - { - IApplication? app = Application.Create (); - - app.Init ("fake"); - app.Driver = null; - - app.StopAfterFirstIteration = true; - - // Init has been called, but Driver has been set to null. Bad. - Assert.Throws (() => app.Run ()); - } - - [Fact] - public void Init_Unbalanced_Throws () - { - IApplication? app = Application.Create (); - app.Init ("fake"); - - Assert.Throws (() => - app.Init ("fake") - ); - } } diff --git a/Tests/UnitTestsParallelizable/Application/ApplicationTests.cs b/Tests/UnitTestsParallelizable/Application/ApplicationTests.cs deleted file mode 100644 index e28381940..000000000 --- a/Tests/UnitTestsParallelizable/Application/ApplicationTests.cs +++ /dev/null @@ -1,510 +0,0 @@ -#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 - -} diff --git a/Tests/UnitTestsParallelizable/Application/ApplicationImplBeginEndTests.cs b/Tests/UnitTestsParallelizable/Application/BeginEndTests.cs similarity index 84% rename from Tests/UnitTestsParallelizable/Application/ApplicationImplBeginEndTests.cs rename to Tests/UnitTestsParallelizable/Application/BeginEndTests.cs index c331e7a15..af5c55dc8 100644 --- a/Tests/UnitTestsParallelizable/Application/ApplicationImplBeginEndTests.cs +++ b/Tests/UnitTestsParallelizable/Application/BeginEndTests.cs @@ -1,6 +1,6 @@ using Xunit.Abstractions; -namespace ApplicationTests; +namespace ApplicationTests.BeginEnd; /// /// Comprehensive tests for ApplicationImpl.Begin/End logic that manages Current and SessionStack. @@ -11,6 +11,74 @@ public class ApplicationImplBeginEndTests (ITestOutputHelper output) { private readonly ITestOutputHelper _output = output; + + [Fact] + public void Init_Begin_End_Cleans_Up () + { + IApplication? app = Application.Create (); + + SessionToken? newSessionToken = null; + + EventHandler newSessionTokenFn = (s, e) => + { + Assert.NotNull (e.State); + newSessionToken = e.State; + }; + app.SessionBegun += newSessionTokenFn; + + Runnable runnable = new (); + SessionToken sessionToken = app.Begin (runnable)!; + Assert.NotNull (sessionToken); + Assert.NotNull (newSessionToken); + Assert.Equal (sessionToken, newSessionToken); + + // Assert.Equal (runnable, Application.TopRunnable); + + app.SessionBegun -= newSessionTokenFn; + app.End (newSessionToken); + + Assert.Null (app.TopRunnable); + Assert.Null (app.Driver); + + runnable.Dispose (); + } + + [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 Begin_WithNullRunnable_ThrowsArgumentNullException () { diff --git a/Tests/UnitTestsParallelizable/Application/CWP/ResultEventArgsTests.cs b/Tests/UnitTestsParallelizable/Application/CWP/ResultEventArgsTests.cs index 2a6d038d1..efbf868ef 100644 --- a/Tests/UnitTestsParallelizable/Application/CWP/ResultEventArgsTests.cs +++ b/Tests/UnitTestsParallelizable/Application/CWP/ResultEventArgsTests.cs @@ -1,7 +1,3 @@ -#nullable enable -using System; -using Terminal.Gui.App; -using Xunit; namespace ApplicationTests; public class ResultEventArgsTests @@ -9,7 +5,7 @@ public class ResultEventArgsTests [Fact] public void DefaultConstructor_InitializesProperties () { - var args = new ResultEventArgs (); + ResultEventArgs args = new (); Assert.Null (args.Result); Assert.False (args.Handled); @@ -18,7 +14,7 @@ public class ResultEventArgsTests [Fact] public void Constructor_WithResult_SetsResult () { - var args = new ResultEventArgs (42); + ResultEventArgs args = new (42); Assert.Equal (42, args.Result); Assert.False (args.Handled); @@ -27,7 +23,7 @@ public class ResultEventArgsTests [Fact] public void Constructor_WithNullResult_AllowsNull () { - var args = new ResultEventArgs (null); + ResultEventArgs args = new (null); Assert.Null (args.Result); Assert.False (args.Handled); @@ -36,7 +32,7 @@ public class ResultEventArgsTests [Fact] public void Result_CanBeSetAndRetrieved () { - var args = new ResultEventArgs (); + ResultEventArgs args = new (); args.Result = "foo"; Assert.Equal ("foo", args.Result); @@ -48,7 +44,7 @@ public class ResultEventArgsTests [Fact] public void Handled_CanBeSetAndRetrieved () { - var args = new ResultEventArgs (); + ResultEventArgs args = new (); Assert.False (args.Handled); args.Handled = true; @@ -61,7 +57,7 @@ public class ResultEventArgsTests [Fact] public void WorksWithValueTypes () { - var args = new ResultEventArgs (); + ResultEventArgs args = new (); Assert.Equal (0, args.Result); // default(int) is 0 args.Result = 123; @@ -72,7 +68,7 @@ public class ResultEventArgsTests public void WorksWithReferenceTypes () { var obj = new object (); - var args = new ResultEventArgs (obj); + ResultEventArgs args = new (obj); Assert.Same (obj, args.Result); @@ -87,7 +83,8 @@ public class ResultEventArgsTests public void EventHandler_CanChangeResult_AndCallerSeesChange () { // Arrange - var args = new ResultEventArgs ("initial"); + ResultEventArgs args = new ("initial"); + StringResultEvent += (sender, e) => { // Handler changes the result @@ -101,17 +98,12 @@ public class ResultEventArgsTests Assert.Equal ("changed by handler", args.Result); } - - [Fact] public void EventHandler_CanSetResultToNull () { // Arrange - var args = new ResultEventArgs ("not null"); - StringResultEvent += (sender, e) => - { - e.Result = null; - }; + ResultEventArgs args = new ("not null"); + StringResultEvent += (sender, e) => { e.Result = null; }; // Act StringResultEvent?.Invoke (this, args); @@ -124,7 +116,7 @@ public class ResultEventArgsTests public void MultipleHandlers_LastHandlerWins () { // Arrange - var args = new ResultEventArgs (1); + ResultEventArgs args = new (1); EventHandler>? intEvent = null; intEvent += (s, e) => e.Result = 2; intEvent += (s, e) => e.Result = 3; @@ -141,7 +133,7 @@ public class ResultEventArgsTests public void EventHandler_CanChangeResult_Int () { EventHandler> handler = (s, e) => e.Result = 99; - var args = new ResultEventArgs (1); + ResultEventArgs args = new (1); handler.Invoke (this, args); Assert.Equal (99, args.Result); } @@ -151,7 +143,7 @@ public class ResultEventArgsTests public void EventHandler_CanChangeResult_Double () { EventHandler> handler = (s, e) => e.Result = 2.718; - var args = new ResultEventArgs (3.14); + ResultEventArgs args = new (3.14); handler.Invoke (this, args); Assert.Equal (2.718, args.Result); } @@ -161,29 +153,39 @@ public class ResultEventArgsTests public void EventHandler_CanChangeResult_Bool () { EventHandler> handler = (s, e) => e.Result = false; - var args = new ResultEventArgs (true); + ResultEventArgs args = new (true); handler.Invoke (this, args); Assert.False (args.Result); } // Enum - enum MyEnum { A, B, C } + private enum MyEnum + { + A, + B, + C + } + [Fact] public void EventHandler_CanChangeResult_Enum () { EventHandler> handler = (s, e) => e.Result = MyEnum.C; - var args = new ResultEventArgs (MyEnum.A); + ResultEventArgs args = new (MyEnum.A); handler.Invoke (this, args); Assert.Equal (MyEnum.C, args.Result); } // Struct - struct MyStruct { public int X; } + private struct MyStruct + { + public int X; + } + [Fact] public void EventHandler_CanChangeResult_Struct () { - EventHandler> handler = (s, e) => e.Result = new MyStruct { X = 42 }; - var args = new ResultEventArgs (new MyStruct { X = 1 }); + EventHandler> handler = (s, e) => e.Result = new() { X = 42 }; + ResultEventArgs args = new (new() { X = 1 }); handler.Invoke (this, args); Assert.Equal (42, args.Result.X); } @@ -193,7 +195,7 @@ public class ResultEventArgsTests public void EventHandler_CanChangeResult_String () { EventHandler> handler = (s, e) => e.Result = "changed"; - var args = new ResultEventArgs ("original"); + ResultEventArgs args = new ("original"); handler.Invoke (this, args); Assert.Equal ("changed", args.Result); } @@ -204,7 +206,7 @@ public class ResultEventArgsTests { var newObj = new object (); EventHandler> handler = (s, e) => e.Result = newObj; - var args = new ResultEventArgs (new object ()); + ResultEventArgs args = new (new ()); handler.Invoke (this, args); Assert.Same (newObj, args.Result); } @@ -214,7 +216,7 @@ public class ResultEventArgsTests public void EventHandler_CanChangeResult_NullableInt () { EventHandler> handler = (s, e) => e.Result = null; - var args = new ResultEventArgs (42); + ResultEventArgs args = new (42); handler.Invoke (this, args); Assert.Null (args.Result); } @@ -225,7 +227,7 @@ public class ResultEventArgsTests { var newArr = new [] { "x", "y" }; EventHandler> handler = (s, e) => e.Result = newArr; - var args = new ResultEventArgs (new [] { "a", "b" }); + ResultEventArgs args = new (new [] { "a", "b" }); handler.Invoke (this, args); Assert.Equal (newArr, args.Result); } @@ -234,9 +236,9 @@ public class ResultEventArgsTests [Fact] public void EventHandler_CanChangeResult_List () { - var newList = new List { 1, 2, 3 }; + List newList = new() { 1, 2, 3 }; EventHandler>> handler = (s, e) => e.Result = newList; - var args = new ResultEventArgs> (new List { 9 }); + ResultEventArgs> args = new (new() { 9 }); handler.Invoke (this, args); Assert.Equal (newList, args.Result); } @@ -245,21 +247,22 @@ public class ResultEventArgsTests [Fact] public void EventHandler_CanChangeResult_Dictionary () { - var newDict = new Dictionary { ["a"] = 1 }; + Dictionary newDict = new() { ["a"] = 1 }; EventHandler>> handler = (s, e) => e.Result = newDict; - var args = new ResultEventArgs> (new Dictionary ()); + ResultEventArgs> args = new (new ()); handler.Invoke (this, args); Assert.Equal (newDict, args.Result); } // Record public record MyRecord (int Id, string Name); + [Fact] public void EventHandler_CanChangeResult_Record () { var rec = new MyRecord (1, "foo"); EventHandler> handler = (s, e) => e.Result = rec; - var args = new ResultEventArgs (null); + ResultEventArgs args = new (null); handler.Invoke (this, args); Assert.Equal (rec, args.Result); } @@ -269,12 +272,12 @@ public class ResultEventArgsTests public void EventHandler_CanChangeResult_NullableInt_ToValue_AndNull () { EventHandler> handler = (s, e) => e.Result = 123; - var args = new ResultEventArgs (null); + ResultEventArgs args = new (null); handler.Invoke (this, args); Assert.Equal (123, args.Result); handler = (s, e) => e.Result = null; - args = new ResultEventArgs (456); + args = new (456); handler.Invoke (this, args); Assert.Null (args.Result); } @@ -284,12 +287,12 @@ public class ResultEventArgsTests public void EventHandler_CanChangeResult_NullableDouble_ToValue_AndNull () { EventHandler> handler = (s, e) => e.Result = 3.14; - var args = new ResultEventArgs (null); + ResultEventArgs args = new (null); handler.Invoke (this, args); Assert.Equal (3.14, args.Result); handler = (s, e) => e.Result = null; - args = new ResultEventArgs (2.71); + args = new (2.71); handler.Invoke (this, args); Assert.Null (args.Result); } @@ -299,12 +302,12 @@ public class ResultEventArgsTests public void EventHandler_CanChangeResult_NullableStruct_ToValue_AndNull () { EventHandler> handler = (s, e) => e.Result = new MyStruct { X = 7 }; - var args = new ResultEventArgs (null); + ResultEventArgs args = new (null); handler.Invoke (this, args); Assert.Equal (7, args.Result?.X); handler = (s, e) => e.Result = null; - args = new ResultEventArgs (new MyStruct { X = 8 }); + args = new (new MyStruct { X = 8 }); handler.Invoke (this, args); Assert.Null (args.Result); } @@ -314,29 +317,33 @@ public class ResultEventArgsTests public void EventHandler_CanChangeResult_NullableString_ToValue_AndNull () { EventHandler> handler = (s, e) => e.Result = "hello"; - var args = new ResultEventArgs (null); + ResultEventArgs args = new (null); handler.Invoke (this, args); Assert.Equal ("hello", args.Result); handler = (s, e) => e.Result = null; - args = new ResultEventArgs ("world"); + args = new ("world"); handler.Invoke (this, args); Assert.Null (args.Result); } // Nullable custom class - class MyClass { public int Y { get; set; } } + private class MyClass + { + public int Y { get; set; } + } + [Fact] public void EventHandler_CanChangeResult_NullableClass_ToValue_AndNull () { - EventHandler> handler = (s, e) => e.Result = new MyClass { Y = 42 }; - var args = new ResultEventArgs (null); + EventHandler> handler = (s, e) => e.Result = new() { Y = 42 }; + ResultEventArgs args = new (null); handler.Invoke (this, args); Assert.NotNull (args.Result); Assert.Equal (42, args.Result?.Y); handler = (s, e) => e.Result = null; - args = new ResultEventArgs (new MyClass { Y = 99 }); + args = new (new() { Y = 99 }); handler.Invoke (this, args); Assert.Null (args.Result); } diff --git a/Tests/UnitTestsParallelizable/Application/InitTests.cs b/Tests/UnitTestsParallelizable/Application/InitTests.cs new file mode 100644 index 000000000..28f27c20e --- /dev/null +++ b/Tests/UnitTestsParallelizable/Application/InitTests.cs @@ -0,0 +1,170 @@ +using Xunit.Abstractions; + +namespace ApplicationTests.Init; + +/// +/// Comprehensive tests for ApplicationImpl.Begin/End logic that manages Current and SessionStack. +/// These tests ensure the fragile state management logic is robust and catches regressions. +/// Tests work directly with ApplicationImpl instances to avoid global Application state issues. +/// +public class InitTests (ITestOutputHelper output) +{ + private readonly ITestOutputHelper _output = output; + + [Fact] + public void Init_Unbalanced_Throws () + { + IApplication? app = Application.Create (); + app.Init ("fake"); + + Assert.Throws (() => + app.Init ("fake") + ); + } + + [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); + } + } + +} diff --git a/Tests/UnitTestsParallelizable/Application/KeyboardImplThreadSafetyTests.cs b/Tests/UnitTestsParallelizable/Application/Keyboard/KeyboardImplThreadSafetyTests.cs similarity index 99% rename from Tests/UnitTestsParallelizable/Application/KeyboardImplThreadSafetyTests.cs rename to Tests/UnitTestsParallelizable/Application/Keyboard/KeyboardImplThreadSafetyTests.cs index 4b74a2424..bd55516e3 100644 --- a/Tests/UnitTestsParallelizable/Application/KeyboardImplThreadSafetyTests.cs +++ b/Tests/UnitTestsParallelizable/Application/Keyboard/KeyboardImplThreadSafetyTests.cs @@ -1,7 +1,7 @@ // ReSharper disable AccessToDisposedClosure #nullable enable -namespace ApplicationTests; +namespace ApplicationTests.Keyboard; /// /// Tests to verify that KeyboardImpl is thread-safe for concurrent access scenarios. diff --git a/Tests/UnitTestsParallelizable/Application/KeyboardTests.cs b/Tests/UnitTestsParallelizable/Application/Keyboard/KeyboardTests.cs similarity index 96% rename from Tests/UnitTestsParallelizable/Application/KeyboardTests.cs rename to Tests/UnitTestsParallelizable/Application/Keyboard/KeyboardTests.cs index f275e96da..480c56204 100644 --- a/Tests/UnitTestsParallelizable/Application/KeyboardTests.cs +++ b/Tests/UnitTestsParallelizable/Application/Keyboard/KeyboardTests.cs @@ -1,7 +1,7 @@ #nullable enable using Terminal.Gui.App; -namespace ApplicationTests; +namespace ApplicationTests.Keyboard; /// /// Parallelizable tests for keyboard handling. @@ -9,6 +9,23 @@ namespace ApplicationTests; /// public class KeyboardTests { + + [Fact] + public void Init_CreatesKeybindings () + { + IApplication app = Application.Create (); + + app.Keyboard.KeyBindings.Clear (); + + Assert.Empty (app.Keyboard.KeyBindings.GetBindings ()); + + app.Init ("fake"); + + Assert.NotEmpty (app.Keyboard.KeyBindings.GetBindings ()); + + app.Dispose (); + } + [Fact] public void Constructor_InitializesKeyBindings () { @@ -245,7 +262,7 @@ public class KeyboardTests } // Migrated from UnitTests/Application/KeyboardTests.cs - + [Fact] public void KeyBindings_Add_Adds () { @@ -465,7 +482,7 @@ public class KeyboardTests // Get the commands from the old binding Assert.True (keyboard.KeyBindings.TryGet (oldKey, out KeyBinding oldBinding)); - Command[] oldCommands = oldBinding.Commands.ToArray (); + Command [] oldCommands = oldBinding.Commands.ToArray (); // Act keyboard.KeyBindings.Replace (oldKey, newKey); diff --git a/Tests/UnitTestsParallelizable/Application/ApplicationMouseEnterLeaveTests.cs b/Tests/UnitTestsParallelizable/Application/Mouse/ApplicationMouseEnterLeaveTests.cs similarity index 99% rename from Tests/UnitTestsParallelizable/Application/ApplicationMouseEnterLeaveTests.cs rename to Tests/UnitTestsParallelizable/Application/Mouse/ApplicationMouseEnterLeaveTests.cs index c03a1d477..db48f8dba 100644 --- a/Tests/UnitTestsParallelizable/Application/ApplicationMouseEnterLeaveTests.cs +++ b/Tests/UnitTestsParallelizable/Application/Mouse/ApplicationMouseEnterLeaveTests.cs @@ -1,7 +1,7 @@ #nullable enable using System.ComponentModel; -namespace ApplicationTests; +namespace ApplicationTests.Mouse; [Trait ("Category", "Input")] public class ApplicationMouseEnterLeaveTests diff --git a/Tests/UnitTestsParallelizable/Application/MouseInterfaceTests.cs b/Tests/UnitTestsParallelizable/Application/Mouse/MouseInterfaceTests.cs similarity index 90% rename from Tests/UnitTestsParallelizable/Application/MouseInterfaceTests.cs rename to Tests/UnitTestsParallelizable/Application/Mouse/MouseInterfaceTests.cs index 1ebda8b62..106135651 100644 --- a/Tests/UnitTestsParallelizable/Application/MouseInterfaceTests.cs +++ b/Tests/UnitTestsParallelizable/Application/Mouse/MouseInterfaceTests.cs @@ -1,8 +1,6 @@ -#nullable enable -using Terminal.Gui.App; using Xunit.Abstractions; -namespace ApplicationTests; +namespace ApplicationTests.Mouse; /// /// Parallelizable tests for IMouse interface. @@ -93,14 +91,14 @@ public class MouseInterfaceTests (ITestOutputHelper output) MouseEventArgs? capturedArgs = null; mouse.MouseEvent += (sender, args) => - { - eventFired = true; - capturedArgs = args; - }; + { + eventFired = true; + capturedArgs = args; + }; MouseEventArgs testEvent = new () { - ScreenPosition = new Point (5, 10), + ScreenPosition = new (5, 10), Flags = MouseFlags.Button1Pressed }; @@ -121,13 +119,13 @@ public class MouseInterfaceTests (ITestOutputHelper output) MouseImpl mouse = new (); var eventCount = 0; - void Handler (object? sender, MouseEventArgs args) => eventCount++; + void Handler (object? sender, MouseEventArgs args) { eventCount++; } mouse.MouseEvent += Handler; MouseEventArgs testEvent = new () { - ScreenPosition = new Point (0, 0), + ScreenPosition = new (0, 0), Flags = MouseFlags.Button1Pressed }; @@ -157,7 +155,7 @@ public class MouseInterfaceTests (ITestOutputHelper output) MouseEventArgs testEvent = new () { - ScreenPosition = new Point (0, 0), + ScreenPosition = new (0, 0), Flags = MouseFlags.Button1Pressed }; @@ -185,7 +183,7 @@ public class MouseInterfaceTests (ITestOutputHelper output) MouseEventArgs testEvent = new () { - ScreenPosition = new Point (5, 5), + ScreenPosition = new (5, 5), Flags = flags }; @@ -231,7 +229,7 @@ public class MouseInterfaceTests (ITestOutputHelper output) MouseEventArgs testEvent = new () { - ScreenPosition = new Point (0, 0), + ScreenPosition = new (0, 0), Flags = MouseFlags.Button1Pressed }; @@ -300,7 +298,7 @@ public class MouseInterfaceTests (ITestOutputHelper output) MouseEventArgs testEvent = new () { - ScreenPosition = new Point (0, 0), + ScreenPosition = new (0, 0), Flags = MouseFlags.Button1Pressed }; @@ -380,10 +378,10 @@ public class MouseInterfaceTests (ITestOutputHelper output) var eventFired = false; mouse.GrabbingMouse += (sender, args) => - { - eventFired = true; - args.Cancel = true; - }; + { + eventFired = true; + args.Cancel = true; + }; // Act mouse.GrabMouse (testView); @@ -403,10 +401,10 @@ public class MouseInterfaceTests (ITestOutputHelper output) View? eventView = null; mouse.GrabbedMouse += (sender, args) => - { - eventFired = true; - eventView = args.View; - }; + { + eventFired = true; + eventView = args.View; + }; // Act mouse.GrabMouse (testView); @@ -428,10 +426,10 @@ public class MouseInterfaceTests (ITestOutputHelper output) View? eventView = null; mouse.UnGrabbedMouse += (sender, args) => - { - eventFired = true; - eventView = args.View; - }; + { + eventFired = true; + eventView = args.View; + }; // Act mouse.UngrabMouse (); diff --git a/Tests/UnitTestsParallelizable/Application/MouseTests.cs b/Tests/UnitTestsParallelizable/Application/Mouse/MouseTests.cs similarity index 99% rename from Tests/UnitTestsParallelizable/Application/MouseTests.cs rename to Tests/UnitTestsParallelizable/Application/Mouse/MouseTests.cs index df000e8fe..b29b67b90 100644 --- a/Tests/UnitTestsParallelizable/Application/MouseTests.cs +++ b/Tests/UnitTestsParallelizable/Application/Mouse/MouseTests.cs @@ -1,6 +1,4 @@ -using Xunit.Abstractions; - -namespace ApplicationTests; +namespace ApplicationTests.Mouse; /// /// Tests for the interface and implementation. diff --git a/Tests/UnitTestsParallelizable/Application/ApplicationPopoverTests.cs b/Tests/UnitTestsParallelizable/Application/Popover/Application.PopoverTests.cs similarity index 99% rename from Tests/UnitTestsParallelizable/Application/ApplicationPopoverTests.cs rename to Tests/UnitTestsParallelizable/Application/Popover/Application.PopoverTests.cs index 67cda869d..6cb23d865 100644 --- a/Tests/UnitTestsParallelizable/Application/ApplicationPopoverTests.cs +++ b/Tests/UnitTestsParallelizable/Application/Popover/Application.PopoverTests.cs @@ -2,7 +2,7 @@ using Moq; using Terminal.Gui.App; -namespace ApplicationTests; +namespace ApplicationTests.Popover; public class ApplicationPopoverTests { diff --git a/Tests/UnitTestsParallelizable/Application/PopoverBaseImplTests.cs b/Tests/UnitTestsParallelizable/Application/Popover/PopoverBaseImplTests.cs similarity index 92% rename from Tests/UnitTestsParallelizable/Application/PopoverBaseImplTests.cs rename to Tests/UnitTestsParallelizable/Application/Popover/PopoverBaseImplTests.cs index 0e1917303..9642b266d 100644 --- a/Tests/UnitTestsParallelizable/Application/PopoverBaseImplTests.cs +++ b/Tests/UnitTestsParallelizable/Application/Popover/PopoverBaseImplTests.cs @@ -1,13 +1,10 @@ -using System; -using Terminal.Gui; -using Terminal.Gui.App; -using Xunit; -namespace ApplicationTests; +namespace ApplicationTests.Popover; public class PopoverBaseImplTests { // Minimal concrete implementation for testing - private class TestPopover : PopoverBaseImpl { } + private class TestPopover : PopoverBaseImpl + { } [Fact] public void Constructor_SetsDefaults () @@ -40,12 +37,11 @@ public class PopoverBaseImplTests popover.ViewportSettings = ViewportSettingsFlags.None; // Remove required flags var popoverManager = new ApplicationPopover (); + // Test missing Transparent flags Assert.ThrowsAny (() => popoverManager.Show (popover)); - } - [Fact] public void Show_ThrowsIfPopoverMissingQuitCommand () { diff --git a/Tests/UnitTestsParallelizable/Application/RunTests.cs b/Tests/UnitTestsParallelizable/Application/RunTests.cs new file mode 100644 index 000000000..1b9c53ffb --- /dev/null +++ b/Tests/UnitTestsParallelizable/Application/RunTests.cs @@ -0,0 +1,252 @@ +#nullable enable +using Xunit.Abstractions; + +namespace ApplicationTests; + +public class RunTests +{ + [Fact] + public void Run_RequestStop_Stops () + { + IApplication? app = Application.Create (); + app.Init ("fake"); + + var top = new Runnable (); + SessionToken? sessionToken = app.Begin (top); + Assert.NotNull (sessionToken); + + app.Iteration += OnApplicationOnIteration; + app.Run (top); + app.Iteration -= OnApplicationOnIteration; + + top.Dispose (); + + return; + + void OnApplicationOnIteration (object? s, EventArgs a) { app.RequestStop (); } + } + + [Fact] + public void Run_T_Init_Driver_Cleared_with_Runnable_Throws () + { + IApplication? app = Application.Create (); + + app.Init ("fake"); + app.Driver = null; + + app.StopAfterFirstIteration = true; + + // Init has been called, but Driver has been set to null. Bad. + Assert.Throws (() => app.Run ()); + } + + [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 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; + } + } +} diff --git a/Tests/UnitTestsParallelizable/Application/Runnable/RunnableEdgeCasesTests.cs b/Tests/UnitTestsParallelizable/Application/Runnable/RunnableEdgeCasesTests.cs index 6fcbd47c5..c513aa4c8 100644 --- a/Tests/UnitTestsParallelizable/Application/Runnable/RunnableEdgeCasesTests.cs +++ b/Tests/UnitTestsParallelizable/Application/Runnable/RunnableEdgeCasesTests.cs @@ -1,7 +1,6 @@ -#nullable enable using Xunit.Abstractions; -namespace ApplicationTests; +namespace ApplicationTests.RunnableTests; /// /// Tests for edge cases and error conditions in IRunnable implementation. @@ -9,7 +8,7 @@ namespace ApplicationTests; public class RunnableEdgeCasesTests (ITestOutputHelper output) { private readonly ITestOutputHelper _output = output; - + [Fact] public void Runnable_MultipleEventSubscribers_AllInvoked () { diff --git a/Tests/UnitTestsParallelizable/Application/Runnable/RunnableIntegrationTests.cs b/Tests/UnitTestsParallelizable/Application/Runnable/RunnableIntegrationTests.cs index e38a77cb1..6fbdbec82 100644 --- a/Tests/UnitTestsParallelizable/Application/Runnable/RunnableIntegrationTests.cs +++ b/Tests/UnitTestsParallelizable/Application/Runnable/RunnableIntegrationTests.cs @@ -1,28 +1,19 @@ #nullable enable using Xunit.Abstractions; -namespace ApplicationTests; +namespace ApplicationTests.RunnableTests; /// /// Integration tests for IApplication's IRunnable support. /// Tests the full lifecycle of IRunnable instances through Application methods. /// -public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : IDisposable +public class ApplicationRunnableIntegrationTests { - private readonly ITestOutputHelper _output = output; - private IApplication? _app; - - public void Dispose () - { - _app?.Dispose (); - _app = null; - } - [Fact] public void Begin_AddsRunnableToStack () { // Arrange - IApplication app = GetApp (); + IApplication app = CreateAndInitApp (); Runnable runnable = new (); int stackCountBefore = app.SessionStack?.Count ?? 0; @@ -43,7 +34,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID public void Begin_CanBeCanceled_ByIsRunningChanging () { // Arrange - IApplication app = GetApp (); + IApplication app = CreateAndInitApp (); CancelableRunnable runnable = new () { CancelStart = true }; // Act @@ -60,7 +51,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID public void Begin_RaisesIsModalChangedEvent () { // Arrange - IApplication app = GetApp (); + IApplication app = CreateAndInitApp (); Runnable runnable = new (); var isModalChangedRaised = false; bool? receivedValue = null; @@ -86,7 +77,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID public void Begin_RaisesIsRunningChangedEvent () { // Arrange - IApplication app = GetApp (); + IApplication app = CreateAndInitApp (); Runnable runnable = new (); var isRunningChangedRaised = false; bool? receivedValue = null; @@ -112,7 +103,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID public void Begin_RaisesIsRunningChangingEvent () { // Arrange - IApplication app = GetApp (); + IApplication app = CreateAndInitApp (); Runnable runnable = new (); var isRunningChangingRaised = false; bool? oldValue = null; @@ -141,7 +132,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID public void Begin_SetsIsModalToTrue () { // Arrange - IApplication app = GetApp (); + IApplication app = CreateAndInitApp (); Runnable runnable = new (); // Act @@ -158,7 +149,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID public void Begin_SetsIsRunningToTrue () { // Arrange - IApplication app = GetApp (); + IApplication app = CreateAndInitApp (); Runnable runnable = new (); // Act @@ -175,7 +166,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID public void Begin_ThrowsOnNullRunnable () { // Arrange - IApplication app = GetApp (); + IApplication app = CreateAndInitApp (); // Act & Assert Assert.Throws (() => app.Begin ((IRunnable)null!)); @@ -185,7 +176,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID public void End_CanBeCanceled_ByIsRunningChanging () { // Arrange - IApplication app = GetApp (); + IApplication app = CreateAndInitApp (); CancelableRunnable runnable = new () { CancelStop = true }; SessionToken? token = app.Begin (runnable); runnable.CancelStop = true; // Enable cancellation @@ -205,7 +196,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID public void End_ClearsTokenRunnable () { // Arrange - IApplication app = GetApp (); + IApplication app = CreateAndInitApp (); Runnable runnable = new (); SessionToken? token = app.Begin (runnable); @@ -220,7 +211,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID public void End_RaisesIsRunningChangedEvent () { // Arrange - IApplication app = GetApp (); + IApplication app = CreateAndInitApp (); Runnable runnable = new (); SessionToken? token = app.Begin (runnable); var isRunningChangedRaised = false; @@ -244,7 +235,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID public void End_RaisesIsRunningChangingEvent () { // Arrange - IApplication app = GetApp (); + IApplication app = CreateAndInitApp (); Runnable runnable = new (); SessionToken? token = app.Begin (runnable); var isRunningChangingRaised = false; @@ -271,7 +262,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID public void End_RemovesRunnableFromStack () { // Arrange - IApplication app = GetApp (); + IApplication app = CreateAndInitApp (); Runnable runnable = new (); SessionToken? token = app.Begin (runnable); int stackCountBefore = app.SessionStack?.Count ?? 0; @@ -287,7 +278,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID public void End_SetsIsModalToFalse () { // Arrange - IApplication app = GetApp (); + IApplication app = CreateAndInitApp (); Runnable runnable = new (); SessionToken? token = app.Begin (runnable); @@ -302,7 +293,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID public void End_SetsIsRunningToFalse () { // Arrange - IApplication app = GetApp (); + IApplication app = CreateAndInitApp (); Runnable runnable = new (); SessionToken? token = app.Begin (runnable); @@ -317,17 +308,33 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID public void End_ThrowsOnNullToken () { // Arrange - IApplication app = GetApp (); + IApplication app = CreateAndInitApp (); // Act & Assert Assert.Throws (() => app.End ((SessionToken)null!)); } + [Fact] + public void End_ClearsMouseGrabView () + { + // Arrange + IApplication app = CreateAndInitApp (); + + Runnable runnable = new (); + SessionToken? token = app.Begin (runnable); + app.Mouse.GrabMouse (runnable); + app.End (token!); + + Assert.Null (app.Mouse.MouseGrabView); + + runnable.Dispose (); + app.Dispose (); + } + [Fact] public void MultipleRunnables_IndependentResults () { // Arrange - IApplication app = GetApp (); Runnable runnable1 = new (); Runnable runnable2 = new (); @@ -344,7 +351,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID public void NestedBegin_MaintainsStackOrder () { // Arrange - IApplication app = GetApp (); + IApplication app = CreateAndInitApp (); Runnable runnable1 = new () { Id = "1" }; Runnable runnable2 = new () { Id = "2" }; @@ -367,7 +374,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID public void NestedEnd_RestoresPreviousModal () { // Arrange - IApplication app = GetApp (); + IApplication app = CreateAndInitApp (); Runnable runnable1 = new () { Id = "1" }; Runnable runnable2 = new () { Id = "2" }; SessionToken token1 = app.Begin (runnable1)!; @@ -390,7 +397,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID public void RequestStop_WithIRunnable_WorksCorrectly () { // Arrange - IApplication app = GetApp (); + IApplication app = CreateAndInitApp (); StoppableRunnable runnable = new (); SessionToken? token = app.Begin (runnable); @@ -409,7 +416,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID public void RequestStop_WithNull_UsesTopRunnable () { // Arrange - IApplication app = GetApp (); + IApplication app = CreateAndInitApp (); StoppableRunnable runnable = new (); SessionToken? token = app.Begin (runnable); @@ -427,7 +434,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID public void RunGeneric_CreatesAndReturnsRunnable () { // Arrange - IApplication app = GetApp (); + IApplication app = CreateAndInitApp (); app.StopAfterFirstIteration = true; // Act - With fluent API, Run() returns IApplication for chaining @@ -456,15 +463,12 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID app.Dispose (); } - private IApplication GetApp () + private IApplication CreateAndInitApp () { - if (_app is null) - { - _app = Application.Create (); - _app.Init ("fake"); - } + IApplication app = Application.Create (); + app.Init ("fake"); - return _app; + return app; } /// diff --git a/Tests/UnitTestsParallelizable/Application/IApplicationScreenChangedTests.cs b/Tests/UnitTestsParallelizable/Application/ScreeenTests.cs similarity index 93% rename from Tests/UnitTestsParallelizable/Application/IApplicationScreenChangedTests.cs rename to Tests/UnitTestsParallelizable/Application/ScreeenTests.cs index 55557266b..6e86eb153 100644 --- a/Tests/UnitTestsParallelizable/Application/IApplicationScreenChangedTests.cs +++ b/Tests/UnitTestsParallelizable/Application/ScreeenTests.cs @@ -1,17 +1,44 @@ using Xunit.Abstractions; -namespace ApplicationTests; +namespace ApplicationTests.Screen; /// /// Parallelizable tests for IApplication.ScreenChanged event and Screen property. /// Tests using the modern instance-based IApplication API. /// -public class IApplicationScreenChangedTests (ITestOutputHelper output) +public class ScreenTests (ITestOutputHelper output) { private readonly ITestOutputHelper _output = output; #region ScreenChanged Event Tests + [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 ScreenChanged_Event_Fires_When_Driver_Size_Changes () { diff --git a/Tests/UnitTestsParallelizable/Application/LogarithmicTimeoutTests.cs b/Tests/UnitTestsParallelizable/Application/Timeouts/LogarithmicTimeoutTests.cs similarity index 98% rename from Tests/UnitTestsParallelizable/Application/LogarithmicTimeoutTests.cs rename to Tests/UnitTestsParallelizable/Application/Timeouts/LogarithmicTimeoutTests.cs index fba071860..d5fc30851 100644 --- a/Tests/UnitTestsParallelizable/Application/LogarithmicTimeoutTests.cs +++ b/Tests/UnitTestsParallelizable/Application/Timeouts/LogarithmicTimeoutTests.cs @@ -1,4 +1,4 @@ -namespace ApplicationTests; +namespace ApplicationTests.Timeout; public class LogarithmicTimeoutTests { diff --git a/Tests/UnitTestsParallelizable/Application/NestedRunTimeoutTests.cs b/Tests/UnitTestsParallelizable/Application/Timeouts/NestedRunTimeoutTests.cs similarity index 100% rename from Tests/UnitTestsParallelizable/Application/NestedRunTimeoutTests.cs rename to Tests/UnitTestsParallelizable/Application/Timeouts/NestedRunTimeoutTests.cs diff --git a/Tests/UnitTestsParallelizable/Application/SmoothAcceleratingTimeoutTests.cs b/Tests/UnitTestsParallelizable/Application/Timeouts/SmoothAcceleratingTimeoutTests.cs similarity index 98% rename from Tests/UnitTestsParallelizable/Application/SmoothAcceleratingTimeoutTests.cs rename to Tests/UnitTestsParallelizable/Application/Timeouts/SmoothAcceleratingTimeoutTests.cs index 9c484cd67..f9199a78c 100644 --- a/Tests/UnitTestsParallelizable/Application/SmoothAcceleratingTimeoutTests.cs +++ b/Tests/UnitTestsParallelizable/Application/Timeouts/SmoothAcceleratingTimeoutTests.cs @@ -1,5 +1,4 @@ -namespace ApplicationTests; - +namespace ApplicationTests.Timeout; public class SmoothAcceleratingTimeoutTests { diff --git a/Tests/UnitTestsParallelizable/Application/TimeoutTests.cs b/Tests/UnitTestsParallelizable/Application/Timeouts/TimeoutTests.cs similarity index 98% rename from Tests/UnitTestsParallelizable/Application/TimeoutTests.cs rename to Tests/UnitTestsParallelizable/Application/Timeouts/TimeoutTests.cs index 75e3de809..83fa353b8 100644 --- a/Tests/UnitTestsParallelizable/Application/TimeoutTests.cs +++ b/Tests/UnitTestsParallelizable/Application/Timeouts/TimeoutTests.cs @@ -853,4 +853,23 @@ public class TimeoutTests } } } + + + [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 (); + } } diff --git a/Tests/UnitTestsParallelizable/ViewBase/Mouse/MouseEventRoutingTests.cs b/Tests/UnitTestsParallelizable/ViewBase/Mouse/MouseEventRoutingTests.cs index d132c220d..b72cd4c84 100644 --- a/Tests/UnitTestsParallelizable/ViewBase/Mouse/MouseEventRoutingTests.cs +++ b/Tests/UnitTestsParallelizable/ViewBase/Mouse/MouseEventRoutingTests.cs @@ -1,7 +1,7 @@ using Terminal.Gui.App; using Xunit.Abstractions; -namespace ApplicationTests; +namespace ApplicationTests.Mouse; /// /// Parallelizable tests for mouse event routing and coordinate transformation. @@ -283,10 +283,10 @@ public class MouseEventRoutingTests (ITestOutputHelper output) #region Mouse Button Events [Theory] - [InlineData (MouseFlags.Button1Pressed, 1, 0, 0)] - [InlineData (MouseFlags.Button1Released, 0, 1, 0)] - [InlineData (MouseFlags.Button1Clicked, 0, 0, 1)] - public void View_MouseButtonEvents_RaiseCorrectHandlers (MouseFlags flags, int expectedPressed, int expectedReleased, int expectedClicked) + [InlineData (MouseFlags.Button1Pressed, 1, 0)] + [InlineData (MouseFlags.Button1Released, 0, 1)] + [InlineData (MouseFlags.Button1Clicked, 0, 0)] + public void View_MouseButtonEvents_RaiseCorrectHandlers (MouseFlags flags, int expectedPressed, int expectedReleased) { // Arrange View view = new () { Width = 10, Height = 10 };