Files
Terminal.Gui/Tests/UnitTestsParallelizable/Application/IApplicationScreenChangedTests.cs
Tig b061aacf18 Simplify the Screen property in ApplicationImpl by removing the _screen field and its locking mechanism. The getter now directly retrieves the screen size from the Driver or defaults to 2048x2048. The setter now calls Driver?.SetScreenSize to update the screen size, eliminating the need for the ResetScreen method.
Update `RaiseScreenChangedEvent` to no longer explicitly set the `Screen` property. Remove unnecessary `.ToArray()` conversion in `View.Draw`.

Clarify `Screen` property documentation in `IApplication` to specify constraints on location and size. Update tests to reflect the new behavior where setting the `Screen` property raises the `ScreenChanged` event. Rename and adjust test cases accordingly.
2025-12-04 16:42:51 -07:00

453 lines
12 KiB
C#

using Xunit.Abstractions;
namespace ApplicationTests;
/// <summary>
/// Parallelizable tests for IApplication.ScreenChanged event and Screen property.
/// Tests using the modern instance-based IApplication API.
/// </summary>
public class IApplicationScreenChangedTests (ITestOutputHelper output)
{
private readonly ITestOutputHelper _output = output;
#region ScreenChanged Event Tests
[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
}