Files
Tig 5da7e59aa2 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 <175728472+Copilot@users.noreply.github.com>

* 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 <175728472+Copilot@users.noreply.github.com>
2025-12-07 13:01:19 -07:00

253 lines
6.7 KiB
C#

#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;
}
}
}