Bläddra i källkod

Fixes #4456 - Clear `MouseGrabView` in `App.End` (#4460)

* Fixed MouseGrabView bug.

Added extensive test coverage for `Keyboard`, `Mouse`, `Timeout`, and `Popover` functionalities, including edge cases and concurrent access. Introduced parameterized and data-driven tests to reduce redundancy and improve clarity.

Refactored codebase for modularity and maintainability,
introducing new namespaces and reorganizing classes. Enhanced `MouseImpl`, `KeyboardImpl`, and `Runnable` implementations with improved event handling, thread safety, and support for the Terminal.Gui Cancellable Work Pattern (CWP).

Removed deprecated code and legacy tests, such as `LogarithmicTimeout` and `SmoothAcceleratingTimeout`. Fixed bugs related to mouse grabbing during drag operations and unbalanced `ApplicationImpl.Begin/End` calls. Improved documentation and code readability with modern C# features.

* Code cleanup.

* Update Tests/UnitTestsParallelizable/Application/Runnable/RunnableIntegrationTests.cs

Co-authored-by: Copilot <[email protected]>

* Improve null handling and simplify test setup

In `MouseImpl.cs`, added an early `return` after the `UngrabMouse()`
call within the `if (view is null)` block to prevent further execution
when `view` is `null`, improving null reference handling.

In `RunnableIntegrationTests.cs`, removed the initialization of the
`IApplication` object (`app`) from the `MultipleRunnables_IndependentResults`
test method, simplifying the test setup and focusing on runnable behavior.

* Code cleanup

* API doc link cleanup

---------

Co-authored-by: Copilot <[email protected]>
Tig 6 dagar sedan
förälder
incheckning
5da7e59aa2
32 ändrade filer med 791 tillägg och 768 borttagningar
  1. 2 0
      Terminal.Gui/App/ApplicationImpl.Run.cs
  2. 7 1
      Terminal.Gui/App/Mouse/MouseImpl.cs
  3. 11 12
      Terminal.Gui/ViewBase/Adornment/Border.Arrangment.cs
  4. 0 1
      Terminal.Gui/ViewBase/Adornment/Border.cs
  5. 1 1
      Terminal.Gui/Views/Runnable/Runnable.cs
  6. 1 1
      Terminal.Gui/Views/Runnable/RunnableTResult.cs
  7. 1 1
      Terminal.Gui/Views/Runnable/RunnableWrapper.cs
  8. 1 1
      Terminal.Gui/Views/Runnable/ViewRunnableExtensions.cs
  9. 3 1
      Tests/UnitTests/View/Mouse/MouseTests.cs
  10. 3 0
      Tests/UnitTests/View/ViewCommandTests.cs
  11. 1 1
      Tests/UnitTestsParallelizable/Application/Application.NavigationTests.cs
  12. 60 92
      Tests/UnitTestsParallelizable/Application/ApplicationImplTests.cs
  13. 0 510
      Tests/UnitTestsParallelizable/Application/ApplicationTests.cs
  14. 69 1
      Tests/UnitTestsParallelizable/Application/BeginEndTests.cs
  15. 56 49
      Tests/UnitTestsParallelizable/Application/CWP/ResultEventArgsTests.cs
  16. 170 0
      Tests/UnitTestsParallelizable/Application/InitTests.cs
  17. 1 1
      Tests/UnitTestsParallelizable/Application/Keyboard/KeyboardImplThreadSafetyTests.cs
  18. 20 3
      Tests/UnitTestsParallelizable/Application/Keyboard/KeyboardTests.cs
  19. 1 1
      Tests/UnitTestsParallelizable/Application/Mouse/ApplicationMouseEnterLeaveTests.cs
  20. 24 26
      Tests/UnitTestsParallelizable/Application/Mouse/MouseInterfaceTests.cs
  21. 1 3
      Tests/UnitTestsParallelizable/Application/Mouse/MouseTests.cs
  22. 1 1
      Tests/UnitTestsParallelizable/Application/Popover/Application.PopoverTests.cs
  23. 4 8
      Tests/UnitTestsParallelizable/Application/Popover/PopoverBaseImplTests.cs
  24. 252 0
      Tests/UnitTestsParallelizable/Application/RunTests.cs
  25. 2 3
      Tests/UnitTestsParallelizable/Application/Runnable/RunnableEdgeCasesTests.cs
  26. 44 40
      Tests/UnitTestsParallelizable/Application/Runnable/RunnableIntegrationTests.cs
  27. 29 2
      Tests/UnitTestsParallelizable/Application/ScreeenTests.cs
  28. 1 1
      Tests/UnitTestsParallelizable/Application/Timeouts/LogarithmicTimeoutTests.cs
  29. 0 0
      Tests/UnitTestsParallelizable/Application/Timeouts/NestedRunTimeoutTests.cs
  30. 1 2
      Tests/UnitTestsParallelizable/Application/Timeouts/SmoothAcceleratingTimeoutTests.cs
  31. 19 0
      Tests/UnitTestsParallelizable/Application/Timeouts/TimeoutTests.cs
  32. 5 5
      Tests/UnitTestsParallelizable/ViewBase/Mouse/MouseEventRoutingTests.cs

+ 2 - 0
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;

+ 7 - 1
Terminal.Gui/App/Mouse/MouseImpl.cs

@@ -266,11 +266,17 @@ internal class MouseImpl : IMouse, IDisposable
     /// <inheritdoc/>
     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.

+ 11 - 12
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
         }
     }
 
+    /// <summary>
+    ///     Cancels <see cref="IMouseGrabHandler.GrabbingMouse"/> events during an active drag to prevent other views from
+    ///     stealing the mouse grab mid-operation.
+    /// </summary>
+    /// <remarks>
+    ///     During an Arrange Mode drag (<see cref="_dragPosition"/> 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 <see cref="IMouseGrabHandler.UngrabMouse"/> in <see cref="OnMouseEvent"/>.
+    /// </remarks>
     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
 
-
-
     /// <inheritdoc/>
     protected override void Dispose (bool disposing)
     {
         if (App is { })
         {
             App.Mouse.GrabbingMouse -= Application_GrabbingMouse;
-            App.Mouse.UnGrabbingMouse -= Application_UnGrabbingMouse;
         }
 
         _dragPosition = null;

+ 0 - 1
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)

+ 1 - 1
Terminal.Gui/ViewBase/Runnable/Runnable.cs → Terminal.Gui/Views/Runnable/Runnable.cs

@@ -1,4 +1,4 @@
-namespace Terminal.Gui.ViewBase;
+namespace Terminal.Gui.Views;
 
 /// <summary>
 ///     Base implementation of <see cref="IRunnable"/> for views that can be run as blocking sessions without returning a result.

+ 1 - 1
Terminal.Gui/ViewBase/Runnable/RunnableTResult.cs → Terminal.Gui/Views/Runnable/RunnableTResult.cs

@@ -1,4 +1,4 @@
-namespace Terminal.Gui.ViewBase;
+namespace Terminal.Gui.Views;
 
 /// <summary>
 ///     Base implementation of <see cref="IRunnable{TResult}"/> for views that can be run as blocking sessions.

+ 1 - 1
Terminal.Gui/ViewBase/Runnable/RunnableWrapper.cs → Terminal.Gui/Views/Runnable/RunnableWrapper.cs

@@ -1,4 +1,4 @@
-namespace Terminal.Gui.ViewBase;
+namespace Terminal.Gui.Views;
 
 /// <summary>
 ///     Wraps any <see cref="View"/> to make it runnable with a typed result, similar to how

+ 1 - 1
Terminal.Gui/ViewBase/Runnable/ViewRunnableExtensions.cs → Terminal.Gui/Views/Runnable/ViewRunnableExtensions.cs

@@ -1,4 +1,4 @@
-namespace Terminal.Gui.ViewBase;
+namespace Terminal.Gui.Views;
 
 /// <summary>
 ///     Extension methods for making any <see cref="View"/> runnable with typed results.

+ 3 - 1
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 ();
     }
 

+ 3 - 0
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);
     }

+ 1 - 1
Tests/UnitTestsParallelizable/Application/Application.NavigationTests.cs

@@ -1,6 +1,6 @@
 using Xunit.Abstractions;
 
-namespace ApplicationTests;
+namespace ApplicationTests.Navigation;
 
 public class ApplicationNavigationTests (ITestOutputHelper output)
 {

+ 60 - 92
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<bool> ());
+        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
+
+
     /// <summary>
     ///     Crates a new ApplicationImpl instance for testing. The input, output, and size monitor components are mocked.
     /// </summary>
@@ -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<SessionTokenEventArgs> newSessionTokenFn = (s, e) =>
-                                                                {
-                                                                    Assert.NotNull (e.State);
-                                                                    newSessionToken = e.State;
-                                                                };
-        app.SessionBegun += newSessionTokenFn;
-
-        Runnable<bool> 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<IApplication?> 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<InvalidOperationException> (() => app.Run<Runnable> ());
-    }
-
-    [Fact]
-    public void Init_Unbalanced_Throws ()
-    {
-        IApplication? app = Application.Create ();
-        app.Init ("fake");
-
-        Assert.Throws<InvalidOperationException> (() =>
-                                                      app.Init ("fake")
-                                                 );
-    }
 }

+ 0 - 510
Tests/UnitTestsParallelizable/Application/ApplicationTests.cs

@@ -1,510 +0,0 @@
-#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 ();
-
-        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<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
-
-}

+ 69 - 1
Tests/UnitTestsParallelizable/Application/ApplicationImplBeginEndTests.cs → Tests/UnitTestsParallelizable/Application/BeginEndTests.cs

@@ -1,6 +1,6 @@
 using Xunit.Abstractions;
 
-namespace ApplicationTests;
+namespace ApplicationTests.BeginEnd;
 
 /// <summary>
 ///     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<SessionTokenEventArgs> newSessionTokenFn = (s, e) =>
+                                                                {
+                                                                    Assert.NotNull (e.State);
+                                                                    newSessionToken = e.State;
+                                                                };
+        app.SessionBegun += newSessionTokenFn;
+
+        Runnable<bool> 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<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 Begin_WithNullRunnable_ThrowsArgumentNullException ()
     {

+ 56 - 49
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<string> ();
+        ResultEventArgs<string> 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<int> (42);
+        ResultEventArgs<int> 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<string?> (null);
+        ResultEventArgs<string?> 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<string> ();
+        ResultEventArgs<string> 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<object> ();
+        ResultEventArgs<object> args = new ();
         Assert.False (args.Handled);
 
         args.Handled = true;
@@ -61,7 +57,7 @@ public class ResultEventArgsTests
     [Fact]
     public void WorksWithValueTypes ()
     {
-        var args = new ResultEventArgs<int> ();
+        ResultEventArgs<int> 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<object> (obj);
+        ResultEventArgs<object> 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<string> ("initial");
+        ResultEventArgs<string> 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<string> ("not null");
-        StringResultEvent += (sender, e) =>
-                             {
-                                 e.Result = null;
-                             };
+        ResultEventArgs<string> 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<int> (1);
+        ResultEventArgs<int> args = new (1);
         EventHandler<ResultEventArgs<int>>? 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<ResultEventArgs<int>> handler = (s, e) => e.Result = 99;
-        var args = new ResultEventArgs<int> (1);
+        ResultEventArgs<int> 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<ResultEventArgs<double>> handler = (s, e) => e.Result = 2.718;
-        var args = new ResultEventArgs<double> (3.14);
+        ResultEventArgs<double> 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<ResultEventArgs<bool>> handler = (s, e) => e.Result = false;
-        var args = new ResultEventArgs<bool> (true);
+        ResultEventArgs<bool> 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<ResultEventArgs<MyEnum>> handler = (s, e) => e.Result = MyEnum.C;
-        var args = new ResultEventArgs<MyEnum> (MyEnum.A);
+        ResultEventArgs<MyEnum> 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<ResultEventArgs<MyStruct>> handler = (s, e) => e.Result = new MyStruct { X = 42 };
-        var args = new ResultEventArgs<MyStruct> (new MyStruct { X = 1 });
+        EventHandler<ResultEventArgs<MyStruct>> handler = (s, e) => e.Result = new() { X = 42 };
+        ResultEventArgs<MyStruct> 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<ResultEventArgs<string>> handler = (s, e) => e.Result = "changed";
-        var args = new ResultEventArgs<string> ("original");
+        ResultEventArgs<string> args = new ("original");
         handler.Invoke (this, args);
         Assert.Equal ("changed", args.Result);
     }
@@ -204,7 +206,7 @@ public class ResultEventArgsTests
     {
         var newObj = new object ();
         EventHandler<ResultEventArgs<object>> handler = (s, e) => e.Result = newObj;
-        var args = new ResultEventArgs<object> (new object ());
+        ResultEventArgs<object> 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<ResultEventArgs<int?>> handler = (s, e) => e.Result = null;
-        var args = new ResultEventArgs<int?> (42);
+        ResultEventArgs<int?> args = new (42);
         handler.Invoke (this, args);
         Assert.Null (args.Result);
     }
@@ -225,7 +227,7 @@ public class ResultEventArgsTests
     {
         var newArr = new [] { "x", "y" };
         EventHandler<ResultEventArgs<string []>> handler = (s, e) => e.Result = newArr;
-        var args = new ResultEventArgs<string []> (new [] { "a", "b" });
+        ResultEventArgs<string []> 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<int> { 1, 2, 3 };
+        List<int> newList = new() { 1, 2, 3 };
         EventHandler<ResultEventArgs<List<int>>> handler = (s, e) => e.Result = newList;
-        var args = new ResultEventArgs<List<int>> (new List<int> { 9 });
+        ResultEventArgs<List<int>> 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<string, int> { ["a"] = 1 };
+        Dictionary<string, int> newDict = new() { ["a"] = 1 };
         EventHandler<ResultEventArgs<Dictionary<string, int>>> handler = (s, e) => e.Result = newDict;
-        var args = new ResultEventArgs<Dictionary<string, int>> (new Dictionary<string, int> ());
+        ResultEventArgs<Dictionary<string, int>> 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<ResultEventArgs<MyRecord>> handler = (s, e) => e.Result = rec;
-        var args = new ResultEventArgs<MyRecord> (null);
+        ResultEventArgs<MyRecord> 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<ResultEventArgs<int?>> handler = (s, e) => e.Result = 123;
-        var args = new ResultEventArgs<int?> (null);
+        ResultEventArgs<int?> args = new (null);
         handler.Invoke (this, args);
         Assert.Equal (123, args.Result);
 
         handler = (s, e) => e.Result = null;
-        args = new ResultEventArgs<int?> (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<ResultEventArgs<double?>> handler = (s, e) => e.Result = 3.14;
-        var args = new ResultEventArgs<double?> (null);
+        ResultEventArgs<double?> args = new (null);
         handler.Invoke (this, args);
         Assert.Equal (3.14, args.Result);
 
         handler = (s, e) => e.Result = null;
-        args = new ResultEventArgs<double?> (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<ResultEventArgs<MyStruct?>> handler = (s, e) => e.Result = new MyStruct { X = 7 };
-        var args = new ResultEventArgs<MyStruct?> (null);
+        ResultEventArgs<MyStruct?> args = new (null);
         handler.Invoke (this, args);
         Assert.Equal (7, args.Result?.X);
 
         handler = (s, e) => e.Result = null;
-        args = new ResultEventArgs<MyStruct?> (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<ResultEventArgs<string?>> handler = (s, e) => e.Result = "hello";
-        var args = new ResultEventArgs<string?> (null);
+        ResultEventArgs<string?> args = new (null);
         handler.Invoke (this, args);
         Assert.Equal ("hello", args.Result);
 
         handler = (s, e) => e.Result = null;
-        args = new ResultEventArgs<string?> ("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<ResultEventArgs<MyClass?>> handler = (s, e) => e.Result = new MyClass { Y = 42 };
-        var args = new ResultEventArgs<MyClass?> (null);
+        EventHandler<ResultEventArgs<MyClass?>> handler = (s, e) => e.Result = new() { Y = 42 };
+        ResultEventArgs<MyClass?> 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<MyClass?> (new MyClass { Y = 99 });
+        args = new (new() { Y = 99 });
         handler.Invoke (this, args);
         Assert.Null (args.Result);
     }

+ 170 - 0
Tests/UnitTestsParallelizable/Application/InitTests.cs

@@ -0,0 +1,170 @@
+using Xunit.Abstractions;
+
+namespace ApplicationTests.Init;
+
+/// <summary>
+///     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.
+/// </summary>
+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<InvalidOperationException> (() =>
+                                                      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<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 ();
+
+        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);
+        }
+    }
+
+}

+ 1 - 1
Tests/UnitTestsParallelizable/Application/KeyboardImplThreadSafetyTests.cs → Tests/UnitTestsParallelizable/Application/Keyboard/KeyboardImplThreadSafetyTests.cs

@@ -1,7 +1,7 @@
 // ReSharper disable AccessToDisposedClosure
 
 #nullable enable
-namespace ApplicationTests;
+namespace ApplicationTests.Keyboard;
 
 /// <summary>
 ///     Tests to verify that KeyboardImpl is thread-safe for concurrent access scenarios.

+ 20 - 3
Tests/UnitTestsParallelizable/Application/KeyboardTests.cs → Tests/UnitTestsParallelizable/Application/Keyboard/KeyboardTests.cs

@@ -1,7 +1,7 @@
 #nullable enable
 using Terminal.Gui.App;
 
-namespace ApplicationTests;
+namespace ApplicationTests.Keyboard;
 
 /// <summary>
 ///     Parallelizable tests for keyboard handling.
@@ -9,6 +9,23 @@ namespace ApplicationTests;
 /// </summary>
 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);

+ 1 - 1
Tests/UnitTestsParallelizable/Application/ApplicationMouseEnterLeaveTests.cs → 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

+ 24 - 26
Tests/UnitTestsParallelizable/Application/MouseInterfaceTests.cs → Tests/UnitTestsParallelizable/Application/Mouse/MouseInterfaceTests.cs

@@ -1,8 +1,6 @@
-#nullable enable
-using Terminal.Gui.App;
 using Xunit.Abstractions;
 
-namespace ApplicationTests;
+namespace ApplicationTests.Mouse;
 
 /// <summary>
 ///     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 ();

+ 1 - 3
Tests/UnitTestsParallelizable/Application/MouseTests.cs → Tests/UnitTestsParallelizable/Application/Mouse/MouseTests.cs

@@ -1,6 +1,4 @@
-using Xunit.Abstractions;
-
-namespace ApplicationTests;
+namespace ApplicationTests.Mouse;
 
 /// <summary>
 ///     Tests for the <see cref="IMouse"/> interface and <see cref="MouseImpl"/> implementation.

+ 1 - 1
Tests/UnitTestsParallelizable/Application/ApplicationPopoverTests.cs → 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
 {

+ 4 - 8
Tests/UnitTestsParallelizable/Application/PopoverBaseImplTests.cs → 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<Exception> (() => popoverManager.Show (popover));
-
     }
 
-
     [Fact]
     public void Show_ThrowsIfPopoverMissingQuitCommand ()
     {

+ 252 - 0
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<IApplication?> 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<InvalidOperationException> (() => app.Run<Runnable> ());
+    }
+
+    [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 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;
+        }
+    }
+}

+ 2 - 3
Tests/UnitTestsParallelizable/Application/Runnable/RunnableEdgeCasesTests.cs

@@ -1,7 +1,6 @@
-#nullable enable
 using Xunit.Abstractions;
 
-namespace ApplicationTests;
+namespace ApplicationTests.RunnableTests;
 
 /// <summary>
 ///     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 ()
     {

+ 44 - 40
Tests/UnitTestsParallelizable/Application/Runnable/RunnableIntegrationTests.cs

@@ -1,28 +1,19 @@
 #nullable enable
 using Xunit.Abstractions;
 
-namespace ApplicationTests;
+namespace ApplicationTests.RunnableTests;
 
 /// <summary>
 ///     Integration tests for IApplication's IRunnable support.
 ///     Tests the full lifecycle of IRunnable instances through Application methods.
 /// </summary>
-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<int> 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<int> 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<int> 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<int> 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<int> 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<int> 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<ArgumentNullException> (() => 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<int> 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<int> 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<int> 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<int> 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<int> 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<int> 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<ArgumentNullException> (() => app.End ((SessionToken)null!));
     }
 
+    [Fact]
+    public void End_ClearsMouseGrabView ()
+    {
+        // Arrange
+        IApplication app = CreateAndInitApp ();
+
+        Runnable<int> 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<int> runnable1 = new ();
         Runnable<string> runnable2 = new ();
 
@@ -344,7 +351,7 @@ public class ApplicationRunnableIntegrationTests (ITestOutputHelper output) : ID
     public void NestedBegin_MaintainsStackOrder ()
     {
         // Arrange
-        IApplication app = GetApp ();
+        IApplication app = CreateAndInitApp ();
         Runnable<int> runnable1 = new () { Id = "1" };
         Runnable<int> 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<int> runnable1 = new () { Id = "1" };
         Runnable<int> 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<T>() 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;
     }
 
     /// <summary>

+ 29 - 2
Tests/UnitTestsParallelizable/Application/IApplicationScreenChangedTests.cs → Tests/UnitTestsParallelizable/Application/ScreeenTests.cs

@@ -1,17 +1,44 @@
 using Xunit.Abstractions;
 
-namespace ApplicationTests;
+namespace ApplicationTests.Screen;
 
 /// <summary>
 ///     Parallelizable tests for IApplication.ScreenChanged event and Screen property.
 ///     Tests using the modern instance-based IApplication API.
 /// </summary>
-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 ()
     {

+ 1 - 1
Tests/UnitTestsParallelizable/Application/LogarithmicTimeoutTests.cs → Tests/UnitTestsParallelizable/Application/Timeouts/LogarithmicTimeoutTests.cs

@@ -1,4 +1,4 @@
-namespace ApplicationTests;
+namespace ApplicationTests.Timeout;
 
 public class LogarithmicTimeoutTests
 {

+ 0 - 0
Tests/UnitTestsParallelizable/Application/NestedRunTimeoutTests.cs → Tests/UnitTestsParallelizable/Application/Timeouts/NestedRunTimeoutTests.cs


+ 1 - 2
Tests/UnitTestsParallelizable/Application/SmoothAcceleratingTimeoutTests.cs → Tests/UnitTestsParallelizable/Application/Timeouts/SmoothAcceleratingTimeoutTests.cs

@@ -1,5 +1,4 @@
-namespace ApplicationTests;
-
+namespace ApplicationTests.Timeout;
 
 public class SmoothAcceleratingTimeoutTests
 {

+ 19 - 0
Tests/UnitTestsParallelizable/Application/TimeoutTests.cs → 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 ();
+    }
 }

+ 5 - 5
Tests/UnitTestsParallelizable/ViewBase/Mouse/MouseEventRoutingTests.cs

@@ -1,7 +1,7 @@
 using Terminal.Gui.App;
 using Xunit.Abstractions;
 
-namespace ApplicationTests;
+namespace ApplicationTests.Mouse;
 
 /// <summary>
 ///     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 };