mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2026-01-01 08:50:25 +01:00
* 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>
This commit is contained in:
479
Tests/UnitTestsParallelizable/Application/ScreeenTests.cs
Normal file
479
Tests/UnitTestsParallelizable/Application/ScreeenTests.cs
Normal file
@@ -0,0 +1,479 @@
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace ApplicationTests.Screen;
|
||||
|
||||
/// <summary>
|
||||
/// Parallelizable tests for IApplication.ScreenChanged event and Screen property.
|
||||
/// Tests using the modern instance-based IApplication API.
|
||||
/// </summary>
|
||||
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 ()
|
||||
{
|
||||
// Arrange
|
||||
using IApplication app = Application.Create ();
|
||||
app.Init ("fake");
|
||||
|
||||
var eventFired = false;
|
||||
Rectangle? newScreen = null;
|
||||
|
||||
EventHandler<EventArgs<Rectangle>> handler = (sender, args) =>
|
||||
{
|
||||
eventFired = true;
|
||||
newScreen = args.Value;
|
||||
};
|
||||
|
||||
app.ScreenChanged += handler;
|
||||
|
||||
try
|
||||
{
|
||||
// Act
|
||||
app.Driver!.SetScreenSize (100, 40);
|
||||
|
||||
// Assert
|
||||
Assert.True (eventFired);
|
||||
Assert.NotNull (newScreen);
|
||||
Assert.Equal (new (0, 0, 100, 40), newScreen.Value);
|
||||
}
|
||||
finally
|
||||
{
|
||||
app.ScreenChanged -= handler;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScreenChanged_Event_Updates_Application_Screen_Property ()
|
||||
{
|
||||
// Arrange
|
||||
using IApplication app = Application.Create ();
|
||||
app.Init ("fake");
|
||||
|
||||
Rectangle initialScreen = app.Screen;
|
||||
Assert.Equal (new (0, 0, 80, 25), initialScreen);
|
||||
|
||||
// Act
|
||||
app.Driver!.SetScreenSize (120, 50);
|
||||
|
||||
// Assert
|
||||
Assert.Equal (new (0, 0, 120, 50), app.Screen);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScreenChanged_Event_Sender_Is_IApplication ()
|
||||
{
|
||||
// Arrange
|
||||
using IApplication app = Application.Create ();
|
||||
app.Init ("fake");
|
||||
|
||||
object? eventSender = null;
|
||||
|
||||
EventHandler<EventArgs<Rectangle>> handler = (sender, args) => { eventSender = sender; };
|
||||
|
||||
app.ScreenChanged += handler;
|
||||
|
||||
try
|
||||
{
|
||||
// Act
|
||||
app.Driver!.SetScreenSize (100, 30);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull (eventSender);
|
||||
Assert.IsAssignableFrom<IApplication> (eventSender);
|
||||
}
|
||||
finally
|
||||
{
|
||||
app.ScreenChanged -= handler;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScreenChanged_Event_Provides_Correct_Rectangle_In_EventArgs ()
|
||||
{
|
||||
// Arrange
|
||||
using IApplication app = Application.Create ();
|
||||
app.Init ("fake");
|
||||
|
||||
Rectangle? capturedRectangle = null;
|
||||
|
||||
EventHandler<EventArgs<Rectangle>> handler = (sender, args) => { capturedRectangle = args.Value; };
|
||||
|
||||
app.ScreenChanged += handler;
|
||||
|
||||
try
|
||||
{
|
||||
// Act
|
||||
app.Driver!.SetScreenSize (200, 60);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull (capturedRectangle);
|
||||
Assert.Equal (0, capturedRectangle.Value.X);
|
||||
Assert.Equal (0, capturedRectangle.Value.Y);
|
||||
Assert.Equal (200, capturedRectangle.Value.Width);
|
||||
Assert.Equal (60, capturedRectangle.Value.Height);
|
||||
}
|
||||
finally
|
||||
{
|
||||
app.ScreenChanged -= handler;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScreenChanged_Event_Fires_Multiple_Times_For_Multiple_Resizes ()
|
||||
{
|
||||
// Arrange
|
||||
using IApplication app = Application.Create ();
|
||||
app.Init ("fake");
|
||||
|
||||
var eventCount = 0;
|
||||
List<Size> sizes = new ();
|
||||
|
||||
EventHandler<EventArgs<Rectangle>> handler = (sender, args) =>
|
||||
{
|
||||
eventCount++;
|
||||
sizes.Add (args.Value.Size);
|
||||
};
|
||||
|
||||
app.ScreenChanged += handler;
|
||||
|
||||
try
|
||||
{
|
||||
// Act
|
||||
app.Driver!.SetScreenSize (100, 30);
|
||||
app.Driver!.SetScreenSize (120, 40);
|
||||
app.Driver!.SetScreenSize (80, 25);
|
||||
|
||||
// Assert
|
||||
Assert.Equal (3, eventCount);
|
||||
Assert.Equal (3, sizes.Count);
|
||||
Assert.Equal (new (100, 30), sizes [0]);
|
||||
Assert.Equal (new (120, 40), sizes [1]);
|
||||
Assert.Equal (new (80, 25), sizes [2]);
|
||||
}
|
||||
finally
|
||||
{
|
||||
app.ScreenChanged -= handler;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScreenChanged_Event_Does_Not_Fire_When_No_Resize_Occurs ()
|
||||
{
|
||||
// Arrange
|
||||
using IApplication app = Application.Create ();
|
||||
app.Init ("fake");
|
||||
|
||||
var eventFired = false;
|
||||
|
||||
EventHandler<EventArgs<Rectangle>> handler = (sender, args) => { eventFired = true; };
|
||||
|
||||
app.ScreenChanged += handler;
|
||||
|
||||
try
|
||||
{
|
||||
// Act - Don't resize, just access Screen property
|
||||
Rectangle screen = app.Screen;
|
||||
|
||||
// Assert
|
||||
Assert.False (eventFired);
|
||||
Assert.Equal (new (0, 0, 80, 25), screen);
|
||||
}
|
||||
finally
|
||||
{
|
||||
app.ScreenChanged -= handler;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScreenChanged_Event_Can_Be_Unsubscribed ()
|
||||
{
|
||||
// Arrange
|
||||
using IApplication app = Application.Create ();
|
||||
app.Init ("fake");
|
||||
|
||||
var eventCount = 0;
|
||||
|
||||
EventHandler<EventArgs<Rectangle>> handler = (sender, args) => { eventCount++; };
|
||||
|
||||
app.ScreenChanged += handler;
|
||||
|
||||
// Act - First resize should fire
|
||||
app.Driver!.SetScreenSize (100, 30);
|
||||
Assert.Equal (1, eventCount);
|
||||
|
||||
// Unsubscribe
|
||||
app.ScreenChanged -= handler;
|
||||
|
||||
// Second resize should not fire
|
||||
app.Driver!.SetScreenSize (120, 40);
|
||||
|
||||
// Assert
|
||||
Assert.Equal (1, eventCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScreenChanged_Event_Sets_Runnables_To_NeedsLayout ()
|
||||
{
|
||||
// Arrange
|
||||
using IApplication app = Application.Create ();
|
||||
app.Init ("fake");
|
||||
|
||||
using var runnable = new Runnable ();
|
||||
SessionToken? token = app.Begin (runnable);
|
||||
|
||||
Assert.NotNull (app.TopRunnableView);
|
||||
app.LayoutAndDraw ();
|
||||
|
||||
// Clear the NeedsLayout flag
|
||||
Assert.False (app.TopRunnableView.NeedsLayout);
|
||||
|
||||
try
|
||||
{
|
||||
// Act
|
||||
app.Driver!.SetScreenSize (100, 30);
|
||||
|
||||
// Assert
|
||||
Assert.True (app.TopRunnableView.NeedsLayout);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Cleanup
|
||||
if (token is { })
|
||||
{
|
||||
app.End (token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScreenChanged_Event_Handles_Multiple_Runnables_In_Session_Stack ()
|
||||
{
|
||||
// Arrange
|
||||
using IApplication app = Application.Create ();
|
||||
app.Init ("fake");
|
||||
|
||||
using var runnable1 = new Runnable ();
|
||||
SessionToken? token1 = app.Begin (runnable1);
|
||||
app.LayoutAndDraw ();
|
||||
|
||||
using var runnable2 = new Runnable ();
|
||||
SessionToken? token2 = app.Begin (runnable2);
|
||||
app.LayoutAndDraw ();
|
||||
|
||||
// Both should not need layout after drawing
|
||||
Assert.False (runnable1.NeedsLayout);
|
||||
Assert.False (runnable2.NeedsLayout);
|
||||
|
||||
try
|
||||
{
|
||||
// Act - Resize should mark both as needing layout
|
||||
app.Driver!.SetScreenSize (100, 30);
|
||||
|
||||
// Assert
|
||||
Assert.True (runnable1.NeedsLayout);
|
||||
Assert.True (runnable2.NeedsLayout);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Cleanup
|
||||
if (token2 is { })
|
||||
{
|
||||
app.End (token2);
|
||||
}
|
||||
|
||||
if (token1 is { })
|
||||
{
|
||||
app.End (token1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScreenChanged_Event_With_No_Active_Runnables_Does_Not_Throw ()
|
||||
{
|
||||
// Arrange
|
||||
using IApplication app = Application.Create ();
|
||||
app.Init ("fake");
|
||||
|
||||
var eventFired = false;
|
||||
|
||||
EventHandler<EventArgs<Rectangle>> handler = (sender, args) => { eventFired = true; };
|
||||
|
||||
app.ScreenChanged += handler;
|
||||
|
||||
try
|
||||
{
|
||||
// Act - Resize with no runnables
|
||||
Exception? exception = Record.Exception (() => app.Driver!.SetScreenSize (100, 30));
|
||||
|
||||
// Assert
|
||||
Assert.Null (exception);
|
||||
Assert.True (eventFired);
|
||||
}
|
||||
finally
|
||||
{
|
||||
app.ScreenChanged -= handler;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion ScreenChanged Event Tests
|
||||
|
||||
#region Screen Property Tests
|
||||
|
||||
[Fact]
|
||||
public void Screen_Property_Returns_Driver_Screen_When_Not_Set ()
|
||||
{
|
||||
// Arrange
|
||||
using IApplication app = Application.Create ();
|
||||
app.Init ("fake");
|
||||
|
||||
// Act
|
||||
Rectangle screen = app.Screen;
|
||||
|
||||
// Assert
|
||||
Assert.Equal (app.Driver!.Screen, screen);
|
||||
Assert.Equal (new (0, 0, 80, 25), screen);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Screen_Property_Returns_Default_Size_When_Driver_Not_Initialized ()
|
||||
{
|
||||
// Arrange
|
||||
using IApplication app = Application.Create ();
|
||||
|
||||
// Act - Don't call Init
|
||||
Rectangle screen = app.Screen;
|
||||
|
||||
// Assert - Should return default size
|
||||
Assert.Equal (new (0, 0, 2048, 2048), screen);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Screen_Property_Throws_When_Setting_Non_Zero_Origin ()
|
||||
{
|
||||
// Arrange
|
||||
using IApplication app = Application.Create ();
|
||||
app.Init ("fake");
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<NotImplementedException> (() =>
|
||||
app.Screen = new (10, 10, 80, 25));
|
||||
|
||||
Assert.Contains ("Screen locations other than 0, 0", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Screen_Property_Allows_Setting_With_Zero_Origin ()
|
||||
{
|
||||
// Arrange
|
||||
using IApplication app = Application.Create ();
|
||||
app.Init ("fake");
|
||||
|
||||
// Act
|
||||
Exception? exception = Record.Exception (() =>
|
||||
app.Screen = new (0, 0, 100, 50));
|
||||
|
||||
// Assert
|
||||
Assert.Null (exception);
|
||||
Assert.Equal (new (0, 0, 100, 50), app.Screen);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Screen_Property_Setting_Raises_ScreenChanged_Event ()
|
||||
{
|
||||
// Arrange
|
||||
using IApplication app = Application.Create ();
|
||||
app.Init ("fake");
|
||||
|
||||
var eventFired = false;
|
||||
|
||||
EventHandler<EventArgs<Rectangle>> handler = (sender, args) => { eventFired = true; };
|
||||
|
||||
app.ScreenChanged += handler;
|
||||
|
||||
try
|
||||
{
|
||||
// Act - Manually set Screen property
|
||||
app.Screen = new (0, 0, 100, 50);
|
||||
|
||||
Assert.True (eventFired);
|
||||
Assert.Equal (new (0, 0, 100, 50), app.Screen);
|
||||
}
|
||||
finally
|
||||
{
|
||||
app.ScreenChanged -= handler;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Screen_Property_Thread_Safe_Access ()
|
||||
{
|
||||
// Arrange
|
||||
using IApplication app = Application.Create ();
|
||||
app.Init ("fake");
|
||||
|
||||
List<Exception> exceptions = new ();
|
||||
List<Task> tasks = new ();
|
||||
|
||||
// Act - Access Screen property from multiple threads
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
tasks.Add (
|
||||
Task.Run (() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Rectangle screen = app.Screen;
|
||||
Assert.NotEqual (Rectangle.Empty, screen);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
lock (exceptions)
|
||||
{
|
||||
exceptions.Add (ex);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
#pragma warning disable xUnit1031
|
||||
Task.WaitAll (tasks.ToArray ());
|
||||
#pragma warning restore xUnit1031
|
||||
|
||||
// Assert - No exceptions should occur
|
||||
Assert.Empty (exceptions);
|
||||
}
|
||||
|
||||
#endregion Screen Property Tests
|
||||
}
|
||||
Reference in New Issue
Block a user