mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2026-01-01 08:50:25 +01:00
Merge branch 'v2_develop' into copilot/enable-menubar-replacement
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace ApplicationTests;
|
||||
namespace ApplicationTests.Navigation;
|
||||
|
||||
public class ApplicationNavigationTests (ITestOutputHelper output)
|
||||
{
|
||||
|
||||
@@ -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")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -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 ()
|
||||
{
|
||||
@@ -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
Tests/UnitTestsParallelizable/Application/InitTests.cs
Normal file
170
Tests/UnitTestsParallelizable/Application/InitTests.cs
Normal file
@@ -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,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.
|
||||
@@ -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,7 +1,7 @@
|
||||
#nullable enable
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace ApplicationTests;
|
||||
namespace ApplicationTests.Mouse;
|
||||
|
||||
[Trait ("Category", "Input")]
|
||||
public class ApplicationMouseEnterLeaveTests
|
||||
@@ -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,6 +1,4 @@
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace ApplicationTests;
|
||||
namespace ApplicationTests.Mouse;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for the <see cref="IMouse"/> interface and <see cref="MouseImpl"/> implementation.
|
||||
@@ -128,46 +126,46 @@ public class MouseTests
|
||||
[Theory]
|
||||
|
||||
// click on border
|
||||
[InlineData (0, 0, 0, 0, 0, false)]
|
||||
[InlineData (0, 1, 0, 0, 0, false)]
|
||||
[InlineData (0, 0, 1, 0, 0, false)]
|
||||
[InlineData (0, 9, 0, 0, 0, false)]
|
||||
[InlineData (0, 0, 9, 0, 0, false)]
|
||||
[InlineData (0, 0, 0, 0, 0, 0)]
|
||||
[InlineData (0, 1, 0, 0, 0, 0)]
|
||||
[InlineData (0, 0, 1, 0, 0, 0)]
|
||||
[InlineData (0, 9, 0, 0, 0, 0)]
|
||||
[InlineData (0, 0, 9, 0, 0, 0)]
|
||||
|
||||
// outside border
|
||||
[InlineData (0, 10, 0, 0, 0, false)]
|
||||
[InlineData (0, 0, 10, 0, 0, false)]
|
||||
[InlineData (0, 10, 0, 0, 0, 0)]
|
||||
[InlineData (0, 0, 10, 0, 0, 0)]
|
||||
|
||||
// view is offset from origin ; click is on border
|
||||
[InlineData (1, 1, 1, 0, 0, false)]
|
||||
[InlineData (1, 2, 1, 0, 0, false)]
|
||||
[InlineData (1, 1, 2, 0, 0, false)]
|
||||
[InlineData (1, 10, 1, 0, 0, false)]
|
||||
[InlineData (1, 1, 10, 0, 0, false)]
|
||||
[InlineData (1, 1, 1, 0, 0, 0)]
|
||||
[InlineData (1, 2, 1, 0, 0, 0)]
|
||||
[InlineData (1, 1, 2, 0, 0, 0)]
|
||||
[InlineData (1, 10, 1, 0, 0, 0)]
|
||||
[InlineData (1, 1, 10, 0, 0, 0)]
|
||||
|
||||
// outside border
|
||||
[InlineData (1, -1, 0, 0, 0, false)]
|
||||
[InlineData (1, 0, -1, 0, 0, false)]
|
||||
[InlineData (1, 10, 10, 0, 0, false)]
|
||||
[InlineData (1, 11, 11, 0, 0, false)]
|
||||
[InlineData (1, -1, 0, 0, 0, 0)]
|
||||
[InlineData (1, 0, -1, 0, 0, 0)]
|
||||
[InlineData (1, 10, 10, 0, 0, 0)]
|
||||
[InlineData (1, 11, 11, 0, 0, 0)]
|
||||
|
||||
// view is at origin, click is inside border
|
||||
[InlineData (0, 1, 1, 0, 0, true)]
|
||||
[InlineData (0, 2, 1, 1, 0, true)]
|
||||
[InlineData (0, 1, 2, 0, 1, true)]
|
||||
[InlineData (0, 8, 1, 7, 0, true)]
|
||||
[InlineData (0, 1, 8, 0, 7, true)]
|
||||
[InlineData (0, 8, 8, 7, 7, true)]
|
||||
[InlineData (0, 1, 1, 0, 0, 1)]
|
||||
[InlineData (0, 2, 1, 1, 0, 1)]
|
||||
[InlineData (0, 1, 2, 0, 1, 1)]
|
||||
[InlineData (0, 8, 1, 7, 0, 1)]
|
||||
[InlineData (0, 1, 8, 0, 7, 1)]
|
||||
[InlineData (0, 8, 8, 7, 7, 1)]
|
||||
|
||||
// view is offset from origin ; click inside border
|
||||
// our view is 10x10, but has a border, so it's bounds is 8x8
|
||||
[InlineData (1, 2, 2, 0, 0, true)]
|
||||
[InlineData (1, 3, 2, 1, 0, true)]
|
||||
[InlineData (1, 2, 3, 0, 1, true)]
|
||||
[InlineData (1, 9, 2, 7, 0, true)]
|
||||
[InlineData (1, 2, 9, 0, 7, true)]
|
||||
[InlineData (1, 9, 9, 7, 7, true)]
|
||||
[InlineData (1, 10, 10, 7, 7, false)]
|
||||
[InlineData (1, 2, 2, 0, 0, 1)]
|
||||
[InlineData (1, 3, 2, 1, 0, 1)]
|
||||
[InlineData (1, 2, 3, 0, 1, 1)]
|
||||
[InlineData (1, 9, 2, 7, 0, 1)]
|
||||
[InlineData (1, 2, 9, 0, 7, 1)]
|
||||
[InlineData (1, 9, 9, 7, 7, 1)]
|
||||
[InlineData (1, 10, 10, 7, 7, 0)]
|
||||
|
||||
//01234567890123456789
|
||||
// |12345678|
|
||||
@@ -178,13 +176,13 @@ public class MouseTests
|
||||
int clickY,
|
||||
int expectedX,
|
||||
int expectedY,
|
||||
bool expectedClicked
|
||||
int expectedClickedCount
|
||||
)
|
||||
{
|
||||
Size size = new (10, 10);
|
||||
Point pos = new (offset, offset);
|
||||
|
||||
var clicked = false;
|
||||
int clickedCount = 0;
|
||||
|
||||
using IApplication? application = Application.Create ();
|
||||
|
||||
@@ -208,14 +206,14 @@ public class MouseTests
|
||||
|
||||
var mouseEvent = new MouseEventArgs { Position = new (clickX, clickY), ScreenPosition = new (clickX, clickY), Flags = MouseFlags.Button1Clicked };
|
||||
|
||||
view.MouseClick += (s, e) =>
|
||||
view.MouseEvent += (_s, e) =>
|
||||
{
|
||||
Assert.Equal (expectedX, e.Position.X);
|
||||
Assert.Equal (expectedY, e.Position.Y);
|
||||
clicked = true;
|
||||
clickedCount += e.IsSingleDoubleOrTripleClicked ? 1 : 0;
|
||||
};
|
||||
|
||||
application.Mouse.RaiseMouseEvent (mouseEvent);
|
||||
Assert.Equal (expectedClicked, clicked);
|
||||
Assert.Equal (expectedClickedCount, clickedCount);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
using Moq;
|
||||
using Terminal.Gui.App;
|
||||
|
||||
namespace ApplicationTests;
|
||||
namespace ApplicationTests.Popover;
|
||||
|
||||
public class ApplicationPopoverTests
|
||||
{
|
||||
@@ -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
Tests/UnitTestsParallelizable/Application/RunTests.cs
Normal file
252
Tests/UnitTestsParallelizable/Application/RunTests.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 ()
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,4 +1,4 @@
|
||||
namespace ApplicationTests;
|
||||
namespace ApplicationTests.Timeout;
|
||||
|
||||
public class LogarithmicTimeoutTests
|
||||
{
|
||||
@@ -19,7 +19,7 @@ public class NestedRunTimeoutTests (ITestOutputHelper output)
|
||||
List<string> executionOrder = new ();
|
||||
|
||||
var mainWindow = new Window { Title = "Main Window" };
|
||||
var dialog = new Dialog { Title = "Nested Dialog", Buttons = [new() { Text = "Ok" }] };
|
||||
var dialog = new Dialog { Title = "Nested Dialog", Buttons = [new () { Text = "Ok" }] };
|
||||
var nestedRunCompleted = false;
|
||||
|
||||
// Use iteration counter for safety instead of time-based timeout
|
||||
@@ -158,17 +158,17 @@ public class NestedRunTimeoutTests (ITestOutputHelper output)
|
||||
var mainWindow = new Window { Title = "Main Window" };
|
||||
|
||||
// Create a dialog for the nested run loop
|
||||
var dialog = new Dialog { Title = "Nested Dialog", Buttons = [new() { Text = "Ok" }] };
|
||||
var dialog = new Dialog { Title = "Nested Dialog", Buttons = [new () { Text = "Ok" }] };
|
||||
|
||||
// Schedule a safety timeout that will ensure the app quits if test hangs
|
||||
var requestStopTimeoutFired = false;
|
||||
var safetyRequestStopTimeoutFired = false;
|
||||
|
||||
app.AddTimeout (
|
||||
TimeSpan.FromMilliseconds (10000),
|
||||
() =>
|
||||
{
|
||||
output.WriteLine ("SAFETY: RequestStop Timeout fired - test took too long!");
|
||||
requestStopTimeoutFired = true;
|
||||
output.WriteLine ("SAFETY: RequestStop Timeout fired - test took too long - Assuming slow environment. Skipping assertions.");
|
||||
safetyRequestStopTimeoutFired = true;
|
||||
app.RequestStop ();
|
||||
|
||||
return false;
|
||||
@@ -217,12 +217,13 @@ public class NestedRunTimeoutTests (ITestOutputHelper output)
|
||||
// Act - Start the main run loop
|
||||
app.Run (mainWindow);
|
||||
|
||||
// Assert
|
||||
Assert.True (nestedRunStarted, "Nested run should have started");
|
||||
Assert.True (timeoutFired, "Timeout should have fired during nested run");
|
||||
Assert.True (nestedRunEnded, "Nested run should have ended");
|
||||
|
||||
Assert.False (requestStopTimeoutFired, "Safety timeout should NOT have fired");
|
||||
if (!safetyRequestStopTimeoutFired)
|
||||
{
|
||||
// Assert
|
||||
Assert.True (nestedRunStarted, "Nested run should have started");
|
||||
Assert.True (timeoutFired, "Timeout should have fired during nested run");
|
||||
Assert.True (nestedRunEnded, "Nested run should have ended");
|
||||
}
|
||||
|
||||
dialog.Dispose ();
|
||||
mainWindow.Dispose ();
|
||||
@@ -273,14 +274,14 @@ public class NestedRunTimeoutTests (ITestOutputHelper output)
|
||||
app.Init ("FakeDriver");
|
||||
|
||||
// Schedule a safety timeout that will ensure the app quits if test hangs
|
||||
var requestStopTimeoutFired = false;
|
||||
var safetyRequestStopTimeoutFired = false;
|
||||
|
||||
app.AddTimeout (
|
||||
TimeSpan.FromMilliseconds (10000),
|
||||
() =>
|
||||
{
|
||||
output.WriteLine ("SAFETY: RequestStop Timeout fired - test took too long!");
|
||||
requestStopTimeoutFired = true;
|
||||
output.WriteLine ("SAFETY: RequestStop Timeout fired - test took too long - Assuming slow environment. Skipping assertions.");
|
||||
safetyRequestStopTimeoutFired = true;
|
||||
app.RequestStop ();
|
||||
|
||||
return false;
|
||||
@@ -288,7 +289,7 @@ public class NestedRunTimeoutTests (ITestOutputHelper output)
|
||||
);
|
||||
|
||||
var mainWindow = new Window { Title = "Main Window" };
|
||||
var dialog = new Dialog { Title = "Dialog", Buttons = [new() { Text = "Ok" }] };
|
||||
var dialog = new Dialog { Title = "Dialog", Buttons = [new () { Text = "Ok" }] };
|
||||
|
||||
var initialTimeoutCount = 0;
|
||||
var timeoutCountDuringNestedRun = 0;
|
||||
@@ -349,12 +350,13 @@ public class NestedRunTimeoutTests (ITestOutputHelper output)
|
||||
// Assert
|
||||
output.WriteLine ($"Final counts - Initial: {initialTimeoutCount}, During: {timeoutCountDuringNestedRun}, After: {timeoutCountAfterNestedRun}");
|
||||
|
||||
// The timeout queue should have pending timeouts throughout
|
||||
Assert.True (initialTimeoutCount >= 0, "Should have timeouts in queue initially");
|
||||
Assert.True (timeoutCountDuringNestedRun >= 0, "Should have timeouts in queue during nested run");
|
||||
Assert.True (timeoutCountAfterNestedRun >= 0, "Should have timeouts in queue after nested run");
|
||||
|
||||
Assert.False (requestStopTimeoutFired, "Safety timeout should NOT have fired");
|
||||
if (!safetyRequestStopTimeoutFired)
|
||||
{
|
||||
// The timeout queue should have pending timeouts throughout
|
||||
Assert.True (initialTimeoutCount >= 0, "Should have timeouts in queue initially");
|
||||
Assert.True (timeoutCountDuringNestedRun >= 0, "Should have timeouts in queue during nested run");
|
||||
Assert.True (timeoutCountAfterNestedRun >= 0, "Should have timeouts in queue after nested run");
|
||||
}
|
||||
|
||||
dialog.Dispose ();
|
||||
mainWindow.Dispose ();
|
||||
@@ -378,17 +380,17 @@ public class NestedRunTimeoutTests (ITestOutputHelper output)
|
||||
var messageBoxClosed = false;
|
||||
|
||||
var mainWindow = new Window { Title = "Login Window" };
|
||||
var messageBox = new Dialog { Title = "Success", Buttons = [new() { Text = "Ok" }] };
|
||||
var messageBox = new Dialog { Title = "Success", Buttons = [new () { Text = "Ok" }] };
|
||||
|
||||
// Schedule a safety timeout that will ensure the app quits if test hangs
|
||||
var requestStopTimeoutFired = false;
|
||||
var safetyRequestStopTimeoutFired = false;
|
||||
|
||||
app.AddTimeout (
|
||||
TimeSpan.FromMilliseconds (10000),
|
||||
() =>
|
||||
{
|
||||
output.WriteLine ("SAFETY: RequestStop Timeout fired - test took too long!");
|
||||
requestStopTimeoutFired = true;
|
||||
output.WriteLine ("SAFETY: RequestStop Timeout fired - test took too long - Assuming slow environment. Skipping assertions.");
|
||||
safetyRequestStopTimeoutFired = true;
|
||||
app.RequestStop ();
|
||||
|
||||
return false;
|
||||
@@ -448,13 +450,14 @@ public class NestedRunTimeoutTests (ITestOutputHelper output)
|
||||
// Act
|
||||
app.Run (mainWindow);
|
||||
|
||||
// Assert
|
||||
Assert.True (enterFired, "Enter timeout should have fired");
|
||||
Assert.True (messageBoxShown, "MessageBox should have been shown");
|
||||
Assert.True (escFired, "ESC timeout should have fired during MessageBox"); // THIS WAS THE BUG - NOW FIXED!
|
||||
Assert.True (messageBoxClosed, "MessageBox should have been closed");
|
||||
|
||||
Assert.False (requestStopTimeoutFired, "Safety timeout should NOT have fired");
|
||||
if (!safetyRequestStopTimeoutFired)
|
||||
{
|
||||
// Assert
|
||||
Assert.True (enterFired, "Enter timeout should have fired");
|
||||
Assert.True (messageBoxShown, "MessageBox should have been shown");
|
||||
Assert.True (escFired, "ESC timeout should have fired during MessageBox"); // THIS WAS THE BUG - NOW FIXED!
|
||||
Assert.True (messageBoxClosed, "MessageBox should have been closed");
|
||||
}
|
||||
|
||||
messageBox.Dispose ();
|
||||
mainWindow.Dispose ();
|
||||
@@ -1,5 +1,4 @@
|
||||
namespace ApplicationTests;
|
||||
|
||||
namespace ApplicationTests.Timeout;
|
||||
|
||||
public class SmoothAcceleratingTimeoutTests
|
||||
{
|
||||
@@ -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 ();
|
||||
}
|
||||
}
|
||||
@@ -3,14 +3,10 @@ using System.Text;
|
||||
using UnitTests;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
// Alias Console to MockConsole so we don't accidentally use Console
|
||||
|
||||
namespace DriverTests;
|
||||
|
||||
public class AddRuneTests (ITestOutputHelper output) : FakeDriverBase
|
||||
{
|
||||
private readonly ITestOutputHelper _output = output;
|
||||
|
||||
[Fact]
|
||||
public void AddRune ()
|
||||
{
|
||||
@@ -179,4 +175,36 @@ public class AddRuneTests (ITestOutputHelper output) : FakeDriverBase
|
||||
|
||||
driver.Dispose ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddStr_Glyph_On_Second_Cell_Of_Wide_Glyph_Outputs_Correctly ()
|
||||
{
|
||||
IDriver? driver = CreateFakeDriver ();
|
||||
driver.SetScreenSize (6, 3);
|
||||
|
||||
driver!.Clip = new (driver.Screen);
|
||||
|
||||
driver.Move (1, 0);
|
||||
driver.AddStr ("┌");
|
||||
driver.Move (2, 0);
|
||||
driver.AddStr ("─");
|
||||
driver.Move (3, 0);
|
||||
driver.AddStr ("┐");
|
||||
driver.Clip.Exclude (new Region (new (1, 0, 3, 1)));
|
||||
|
||||
driver.Move (0, 0);
|
||||
driver.AddStr ("🍎🍎🍎🍎");
|
||||
|
||||
DriverAssert.AssertDriverContentsAre (
|
||||
"""
|
||||
<EFBFBD>┌─┐🍎
|
||||
""",
|
||||
output,
|
||||
driver);
|
||||
|
||||
driver.Refresh ();
|
||||
|
||||
DriverAssert.AssertDriverOutputIs (@"\x1b[38;2;0;0;0m\x1b[48;2;0;0;0m<30>┌─┐🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m",
|
||||
output, driver);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#nullable enable
|
||||
using System.Text;
|
||||
using UnitTests;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
|
||||
@@ -92,6 +92,51 @@ public class DriverTests (ITestOutputHelper output) : FakeDriverBase
|
||||
|
||||
app.Dispose ();
|
||||
}
|
||||
|
||||
// Tests fix for https://github.com/gui-cs/Terminal.Gui/issues/4258
|
||||
[Theory]
|
||||
[InlineData ("fake")]
|
||||
[InlineData ("windows")]
|
||||
[InlineData ("dotnet")]
|
||||
[InlineData ("unix")]
|
||||
public void All_Drivers_When_Clipped_AddStr_Glyph_On_Second_Cell_Of_Wide_Glyph_Outputs_Correctly (string driverName)
|
||||
{
|
||||
IApplication? app = Application.Create ();
|
||||
app.Init (driverName);
|
||||
IDriver driver = app.Driver!;
|
||||
|
||||
// Need to force "windows" driver to override legacy console mode for this test
|
||||
driver.IsLegacyConsole = false;
|
||||
driver.Force16Colors = false;
|
||||
|
||||
driver.SetScreenSize (6, 3);
|
||||
|
||||
driver!.Clip = new (driver.Screen);
|
||||
|
||||
driver.Move (1, 0);
|
||||
driver.AddStr ("┌");
|
||||
driver.Move (2, 0);
|
||||
driver.AddStr ("─");
|
||||
driver.Move (3, 0);
|
||||
driver.AddStr ("┐");
|
||||
driver.Clip.Exclude (new Region (new (1, 0, 3, 1)));
|
||||
|
||||
driver.Move (0, 0);
|
||||
driver.AddStr ("🍎🍎🍎🍎");
|
||||
|
||||
|
||||
DriverAssert.AssertDriverContentsAre (
|
||||
"""
|
||||
<EFBFBD>┌─┐🍎
|
||||
""",
|
||||
output,
|
||||
driver);
|
||||
|
||||
driver.Refresh ();
|
||||
|
||||
DriverAssert.AssertDriverOutputIs (@"\x1b[38;2;0;0;0m\x1b[48;2;0;0;0m<30>┌─┐🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m",
|
||||
output, driver);
|
||||
}
|
||||
}
|
||||
|
||||
public class TestTop : Runnable
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
#nullable enable
|
||||
|
||||
namespace DriverTests;
|
||||
namespace DriverTests;
|
||||
|
||||
public class OutputBaseTests
|
||||
{
|
||||
@@ -9,7 +7,7 @@ public class OutputBaseTests
|
||||
{
|
||||
// Arrange
|
||||
var output = new FakeOutput ();
|
||||
IOutputBuffer buffer = output.LastBuffer!;
|
||||
IOutputBuffer buffer = output.GetLastBuffer ()!;
|
||||
buffer.SetSize (1, 1);
|
||||
|
||||
// Act
|
||||
@@ -32,21 +30,21 @@ public class OutputBaseTests
|
||||
|
||||
// Create DriverImpl and associate it with the FakeOutput to test Sixel output
|
||||
IDriver driver = new DriverImpl (
|
||||
new FakeInputProcessor (null!),
|
||||
new OutputBufferImpl (),
|
||||
output,
|
||||
new (new AnsiResponseParser ()),
|
||||
new SizeMonitorImpl (output));
|
||||
new FakeInputProcessor (null!),
|
||||
new OutputBufferImpl (),
|
||||
output,
|
||||
new (new AnsiResponseParser ()),
|
||||
new SizeMonitorImpl (output));
|
||||
|
||||
driver.Force16Colors = force16Colors;
|
||||
|
||||
IOutputBuffer buffer = output.LastBuffer!;
|
||||
IOutputBuffer buffer = output.GetLastBuffer ()!;
|
||||
buffer.SetSize (1, 1);
|
||||
|
||||
// Use a known RGB color and attribute
|
||||
var fg = new Color (1, 2, 3);
|
||||
var bg = new Color (4, 5, 6);
|
||||
buffer.CurrentAttribute = new Attribute (fg, bg);
|
||||
buffer.CurrentAttribute = new (fg, bg);
|
||||
buffer.AddStr ("X");
|
||||
|
||||
// Act
|
||||
@@ -59,7 +57,7 @@ public class OutputBaseTests
|
||||
}
|
||||
else if (!isLegacyConsole && force16Colors)
|
||||
{
|
||||
var expected16 = EscSeqUtils.CSI_SetForegroundColor (fg.GetAnsiColorCode ());
|
||||
string expected16 = EscSeqUtils.CSI_SetForegroundColor (fg.GetAnsiColorCode ());
|
||||
Assert.Contains (expected16, ansi);
|
||||
}
|
||||
else
|
||||
@@ -78,7 +76,7 @@ public class OutputBaseTests
|
||||
{
|
||||
// Arrange
|
||||
var output = new FakeOutput ();
|
||||
IOutputBuffer buffer = output.LastBuffer!;
|
||||
IOutputBuffer buffer = output.GetLastBuffer ()!;
|
||||
buffer.SetSize (2, 1);
|
||||
|
||||
// Mark two characters as dirty by writing them into the buffer
|
||||
@@ -92,7 +90,7 @@ public class OutputBaseTests
|
||||
output.Write (buffer); // calls OutputBase.Write via FakeOutput
|
||||
|
||||
// Assert: content was written to the fake output and dirty flags cleared
|
||||
Assert.Contains ("AB", output.Output);
|
||||
Assert.Contains ("AB", output.GetLastOutput ());
|
||||
Assert.False (buffer.Contents! [0, 0].IsDirty);
|
||||
Assert.False (buffer.Contents! [0, 1].IsDirty);
|
||||
}
|
||||
@@ -105,7 +103,7 @@ public class OutputBaseTests
|
||||
// Arrange
|
||||
// FakeOutput exposes this because it's in test scope
|
||||
var output = new FakeOutput { IsLegacyConsole = isLegacyConsole };
|
||||
IOutputBuffer buffer = output.LastBuffer!;
|
||||
IOutputBuffer buffer = output.GetLastBuffer ()!;
|
||||
buffer.SetSize (3, 1);
|
||||
|
||||
// Write 'A' at col 0 and 'C' at col 2; leave col 1 untouched (not dirty)
|
||||
@@ -122,15 +120,15 @@ public class OutputBaseTests
|
||||
output.Write (buffer);
|
||||
|
||||
// Assert: both characters were written (use Contains to avoid CI side effects)
|
||||
Assert.Contains ("A", output.Output);
|
||||
Assert.Contains ("C", output.Output);
|
||||
Assert.Contains ("A", output.GetLastOutput ());
|
||||
Assert.Contains ("C", output.GetLastOutput ());
|
||||
|
||||
// Dirty flags cleared for the written cells
|
||||
Assert.False (buffer.Contents! [0, 0].IsDirty);
|
||||
Assert.False (buffer.Contents! [0, 2].IsDirty);
|
||||
|
||||
// Verify SetCursorPositionImpl was invoked by WriteToConsole (cursor set to a written column)
|
||||
Assert.Equal (new Point (0, 0), output.GetCursorPosition ());
|
||||
Assert.Equal (new (0, 0), output.GetCursorPosition ());
|
||||
|
||||
// Now write 'X' at col 0 to verify subsequent writes also work
|
||||
buffer.Move (0, 0);
|
||||
@@ -143,15 +141,84 @@ public class OutputBaseTests
|
||||
output.Write (buffer);
|
||||
|
||||
// Assert: both characters were written (use Contains to avoid CI side effects)
|
||||
Assert.Contains ("A", output.Output);
|
||||
Assert.Contains ("C", output.Output);
|
||||
Assert.Contains ("A", output.GetLastOutput ());
|
||||
Assert.Contains ("C", output.GetLastOutput ());
|
||||
|
||||
// Dirty flags cleared for the written cells
|
||||
Assert.False (buffer.Contents! [0, 0].IsDirty);
|
||||
Assert.False (buffer.Contents! [0, 2].IsDirty);
|
||||
|
||||
// Verify SetCursorPositionImpl was invoked by WriteToConsole (cursor set to a written column)
|
||||
Assert.Equal (new Point (2, 0), output.GetCursorPosition ());
|
||||
Assert.Equal (new (2, 0), output.GetCursorPosition ());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData (true)]
|
||||
[InlineData (false)]
|
||||
public void Write_Virtual_Or_NonVirtual_Uses_WriteToConsole_And_Clears_Dirty_Flags_Mixed_Graphemes (bool isLegacyConsole)
|
||||
{
|
||||
// Arrange
|
||||
// FakeOutput exposes this because it's in test scope
|
||||
var output = new FakeOutput { IsLegacyConsole = isLegacyConsole };
|
||||
IOutputBuffer buffer = output.GetLastBuffer ()!;
|
||||
buffer.SetSize (3, 1);
|
||||
|
||||
// Write '🦮' at col 0 and 'A' at col 2
|
||||
buffer.Move (0, 0);
|
||||
buffer.AddStr ("🦮A");
|
||||
|
||||
// After the fix for https://github.com/gui-cs/Terminal.Gui/issues/4258:
|
||||
// Writing a wide glyph at column 0 no longer sets column 1 to IsDirty = false.
|
||||
// Column 1 retains whatever state it had (in this case, it was initialized as dirty
|
||||
// by ClearContents, but may have been cleared by a previous Write call).
|
||||
//
|
||||
// What we care about is that wide glyphs work correctly and don't prevent
|
||||
// other content from being drawn at odd columns.
|
||||
Assert.True (buffer.Contents! [0, 0].IsDirty);
|
||||
|
||||
// Column 1 state depends on whether it was cleared by a previous Write - don't assert
|
||||
Assert.True (buffer.Contents! [0, 2].IsDirty);
|
||||
|
||||
// Act
|
||||
output.Write (buffer);
|
||||
|
||||
Assert.Contains ("🦮", output.GetLastOutput ());
|
||||
Assert.Contains ("A", output.GetLastOutput ());
|
||||
|
||||
// Dirty flags cleared for the written cells
|
||||
// Column 0 was written (wide glyph)
|
||||
Assert.False (buffer.Contents! [0, 0].IsDirty);
|
||||
|
||||
// Column 1 was skipped by OutputBase.Write because column 0 had a wide glyph
|
||||
// So its dirty flag remains true (it was initialized as dirty by ClearContents)
|
||||
Assert.True (buffer.Contents! [0, 1].IsDirty);
|
||||
|
||||
// Column 2 was written ('A')
|
||||
Assert.False (buffer.Contents! [0, 2].IsDirty);
|
||||
|
||||
Assert.Equal (new (0, 0), output.GetCursorPosition ());
|
||||
|
||||
// Now write 'X' at col 1 which invalidates the wide glyph at col 0
|
||||
buffer.Move (1, 0);
|
||||
buffer.AddStr ("X");
|
||||
|
||||
// Confirm dirtiness state before to write
|
||||
Assert.True (buffer.Contents! [0, 0].IsDirty); // Invalidated by writing at col 1
|
||||
Assert.True (buffer.Contents! [0, 1].IsDirty); // Just written
|
||||
Assert.True (buffer.Contents! [0, 2].IsDirty); // Marked dirty by writing at col 1
|
||||
|
||||
output.Write (buffer);
|
||||
|
||||
Assert.Contains ("<22>", output.GetLastOutput ());
|
||||
Assert.Contains ("X", output.GetLastOutput ());
|
||||
|
||||
// Dirty flags cleared for the written cells
|
||||
Assert.False (buffer.Contents! [0, 0].IsDirty);
|
||||
Assert.False (buffer.Contents! [0, 1].IsDirty);
|
||||
Assert.False (buffer.Contents! [0, 2].IsDirty);
|
||||
|
||||
// Verify SetCursorPositionImpl was invoked by WriteToConsole (cursor set to a written column)
|
||||
Assert.Equal (new (0, 0), output.GetCursorPosition ());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -161,7 +228,7 @@ public class OutputBaseTests
|
||||
{
|
||||
// Arrange
|
||||
var output = new FakeOutput ();
|
||||
IOutputBuffer buffer = output.LastBuffer!;
|
||||
IOutputBuffer buffer = output.GetLastBuffer ()!;
|
||||
buffer.SetSize (1, 1);
|
||||
|
||||
// Ensure the buffer has some content so Write traverses rows
|
||||
@@ -171,16 +238,16 @@ public class OutputBaseTests
|
||||
var s = new SixelToRender
|
||||
{
|
||||
SixelData = "SIXEL-DATA",
|
||||
ScreenPosition = new Point (4, 2)
|
||||
ScreenPosition = new (4, 2)
|
||||
};
|
||||
|
||||
// Create DriverImpl and associate it with the FakeOutput to test Sixel output
|
||||
IDriver driver = new DriverImpl (
|
||||
new FakeInputProcessor (null!),
|
||||
new OutputBufferImpl (),
|
||||
output,
|
||||
new (new AnsiResponseParser ()),
|
||||
new SizeMonitorImpl (output));
|
||||
new FakeInputProcessor (null!),
|
||||
new OutputBufferImpl (),
|
||||
output,
|
||||
new (new AnsiResponseParser ()),
|
||||
new SizeMonitorImpl (output));
|
||||
|
||||
// Add the Sixel to the driver
|
||||
driver.GetSixels ().Enqueue (s);
|
||||
@@ -194,7 +261,7 @@ public class OutputBaseTests
|
||||
if (!isLegacyConsole)
|
||||
{
|
||||
// Assert: Sixel data was emitted (use Contains to avoid equality/side-effects)
|
||||
Assert.Contains ("SIXEL-DATA", output.Output);
|
||||
Assert.Contains ("SIXEL-DATA", output.GetLastOutput ());
|
||||
|
||||
// Cursor was moved to Sixel position
|
||||
Assert.Equal (s.ScreenPosition, output.GetCursorPosition ());
|
||||
@@ -202,7 +269,7 @@ public class OutputBaseTests
|
||||
else
|
||||
{
|
||||
// Assert: Sixel data was NOT emitted
|
||||
Assert.DoesNotContain ("SIXEL-DATA", output.Output);
|
||||
Assert.DoesNotContain ("SIXEL-DATA", output.GetLastOutput ());
|
||||
|
||||
// Cursor was NOT moved to Sixel position
|
||||
Assert.NotEqual (s.ScreenPosition, output.GetCursorPosition ());
|
||||
@@ -215,4 +282,4 @@ public class OutputBaseTests
|
||||
|
||||
app.Dispose ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,7 +240,7 @@ public class ViewDrawTextAndLineCanvasTests () : FakeDriverBase
|
||||
Point screenPos = new Point (15, 15);
|
||||
view.LineCanvas.AddLine (screenPos, 5, Orientation.Horizontal, LineStyle.Single);
|
||||
|
||||
view.RenderLineCanvas ();
|
||||
view.RenderLineCanvas (null);
|
||||
|
||||
// Verify the line was drawn (check for horizontal line character)
|
||||
for (int i = 0; i < 5; i++)
|
||||
@@ -272,7 +272,7 @@ public class ViewDrawTextAndLineCanvasTests () : FakeDriverBase
|
||||
|
||||
Assert.NotEqual (Rectangle.Empty, view.LineCanvas.Bounds);
|
||||
|
||||
view.RenderLineCanvas ();
|
||||
view.RenderLineCanvas (null);
|
||||
|
||||
// LineCanvas should be cleared after rendering
|
||||
Assert.Equal (Rectangle.Empty, view.LineCanvas.Bounds);
|
||||
@@ -302,7 +302,7 @@ public class ViewDrawTextAndLineCanvasTests () : FakeDriverBase
|
||||
|
||||
Rectangle boundsBefore = view.LineCanvas.Bounds;
|
||||
|
||||
view.RenderLineCanvas ();
|
||||
view.RenderLineCanvas (null);
|
||||
|
||||
// LineCanvas should NOT be cleared when SuperViewRendersLineCanvas is true
|
||||
Assert.Equal (boundsBefore, view.LineCanvas.Bounds);
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
#nullable enable
|
||||
using System.Text;
|
||||
using UnitTests;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace ViewBaseTests.Drawing;
|
||||
|
||||
public class ViewDrawingClippingTests () : FakeDriverBase
|
||||
public class ViewDrawingClippingTests (ITestOutputHelper output) : FakeDriverBase
|
||||
{
|
||||
#region GetClip / SetClip Tests
|
||||
|
||||
|
||||
[Fact]
|
||||
public void GetClip_ReturnsDriverClip ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver (80, 25);
|
||||
var region = new Region (new Rectangle (10, 10, 20, 20));
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
var region = new Region (new (10, 10, 20, 20));
|
||||
driver.Clip = region;
|
||||
View view = new () { Driver = driver };
|
||||
|
||||
@@ -26,8 +25,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
|
||||
[Fact]
|
||||
public void SetClip_NullRegion_DoesNothing ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver (80, 25);
|
||||
var original = new Region (new Rectangle (5, 5, 10, 10));
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
var original = new Region (new (5, 5, 10, 10));
|
||||
driver.Clip = original;
|
||||
|
||||
View view = new () { Driver = driver };
|
||||
@@ -40,8 +39,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
|
||||
[Fact]
|
||||
public void SetClip_ValidRegion_SetsDriverClip ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver (80, 25);
|
||||
var region = new Region (new Rectangle (10, 10, 30, 30));
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
var region = new Region (new (10, 10, 30, 30));
|
||||
View view = new () { Driver = driver };
|
||||
|
||||
view.SetClip (region);
|
||||
@@ -56,8 +55,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
|
||||
[Fact]
|
||||
public void SetClipToScreen_ReturnsPreviousClip ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver (80, 25);
|
||||
var original = new Region (new Rectangle (5, 5, 10, 10));
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
var original = new Region (new (5, 5, 10, 10));
|
||||
driver.Clip = original;
|
||||
View view = new () { Driver = driver };
|
||||
|
||||
@@ -70,7 +69,7 @@ public class ViewDrawingClippingTests () : FakeDriverBase
|
||||
[Fact]
|
||||
public void SetClipToScreen_SetsClipToScreen ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver (80, 25);
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
View view = new () { Driver = driver };
|
||||
|
||||
view.SetClipToScreen ();
|
||||
@@ -87,15 +86,15 @@ public class ViewDrawingClippingTests () : FakeDriverBase
|
||||
public void ExcludeFromClip_Rectangle_NullDriver_DoesNotThrow ()
|
||||
{
|
||||
View view = new () { Driver = null };
|
||||
var exception = Record.Exception (() => view.ExcludeFromClip (new Rectangle (5, 5, 10, 10)));
|
||||
Exception? exception = Record.Exception (() => view.ExcludeFromClip (new Rectangle (5, 5, 10, 10)));
|
||||
Assert.Null (exception);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExcludeFromClip_Rectangle_ExcludesArea ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver (80, 25);
|
||||
driver.Clip = new Region (new Rectangle (0, 0, 80, 25));
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
driver.Clip = new (new (0, 0, 80, 25));
|
||||
View view = new () { Driver = driver };
|
||||
|
||||
var toExclude = new Rectangle (10, 10, 20, 20);
|
||||
@@ -111,19 +110,18 @@ public class ViewDrawingClippingTests () : FakeDriverBase
|
||||
{
|
||||
View view = new () { Driver = null };
|
||||
|
||||
var exception = Record.Exception (() => view.ExcludeFromClip (new Region (new Rectangle (5, 5, 10, 10))));
|
||||
Exception? exception = Record.Exception (() => view.ExcludeFromClip (new Region (new (5, 5, 10, 10))));
|
||||
Assert.Null (exception);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExcludeFromClip_Region_ExcludesArea ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver (80, 25);
|
||||
driver.Clip = new Region (new Rectangle (0, 0, 80, 25));
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
driver.Clip = new (new (0, 0, 80, 25));
|
||||
View view = new () { Driver = driver };
|
||||
|
||||
|
||||
var toExclude = new Region (new Rectangle (10, 10, 20, 20));
|
||||
var toExclude = new Region (new (10, 10, 20, 20));
|
||||
view.ExcludeFromClip (toExclude);
|
||||
|
||||
// Verify the region was excluded
|
||||
@@ -150,8 +148,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
|
||||
[Fact]
|
||||
public void AddFrameToClip_IntersectsWithFrame ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver (80, 25);
|
||||
driver.Clip = new Region (driver.Screen);
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
driver.Clip = new (driver.Screen);
|
||||
|
||||
var view = new View
|
||||
{
|
||||
@@ -171,7 +169,7 @@ public class ViewDrawingClippingTests () : FakeDriverBase
|
||||
Assert.NotNull (driver.Clip);
|
||||
|
||||
// The clip should now be the intersection of the screen and the view's frame
|
||||
Rectangle expectedBounds = new Rectangle (1, 1, 20, 20);
|
||||
var expectedBounds = new Rectangle (1, 1, 20, 20);
|
||||
Assert.Equal (expectedBounds, driver.Clip.GetBounds ());
|
||||
}
|
||||
|
||||
@@ -194,8 +192,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
|
||||
[Fact]
|
||||
public void AddViewportToClip_IntersectsWithViewport ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver (80, 25);
|
||||
driver.Clip = new Region (driver.Screen);
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
driver.Clip = new (driver.Screen);
|
||||
|
||||
var view = new View
|
||||
{
|
||||
@@ -222,8 +220,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
|
||||
[Fact]
|
||||
public void AddViewportToClip_WithClipContentOnly_LimitsToVisibleContent ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver (80, 25);
|
||||
driver.Clip = new Region (driver.Screen);
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
driver.Clip = new (driver.Screen);
|
||||
|
||||
var view = new View
|
||||
{
|
||||
@@ -260,7 +258,7 @@ public class ViewDrawingClippingTests () : FakeDriverBase
|
||||
public void ClipRegions_StackCorrectly_WithNestedViews ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver (100, 100);
|
||||
driver.Clip = new Region (driver.Screen);
|
||||
driver.Clip = new (driver.Screen);
|
||||
|
||||
var superView = new View
|
||||
{
|
||||
@@ -278,7 +276,7 @@ public class ViewDrawingClippingTests () : FakeDriverBase
|
||||
X = 5,
|
||||
Y = 5,
|
||||
Width = 30,
|
||||
Height = 30,
|
||||
Height = 30
|
||||
};
|
||||
superView.Add (view);
|
||||
superView.LayoutSubViews ();
|
||||
@@ -296,14 +294,15 @@ public class ViewDrawingClippingTests () : FakeDriverBase
|
||||
|
||||
// Restore superView clip
|
||||
view.SetClip (superViewClip);
|
||||
|
||||
// Assert.Equal (superViewBounds, driver.Clip.GetBounds ());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClipRegions_RespectPreviousClip ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver (80, 25);
|
||||
var initialClip = new Region (new Rectangle (20, 20, 40, 40));
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
var initialClip = new Region (new (20, 20, 40, 40));
|
||||
driver.Clip = initialClip;
|
||||
|
||||
var view = new View
|
||||
@@ -322,9 +321,9 @@ public class ViewDrawingClippingTests () : FakeDriverBase
|
||||
|
||||
// The new clip should be the intersection of the initial clip and the view's frame
|
||||
Rectangle expected = Rectangle.Intersect (
|
||||
initialClip.GetBounds (),
|
||||
view.FrameToScreen ()
|
||||
);
|
||||
initialClip.GetBounds (),
|
||||
view.FrameToScreen ()
|
||||
);
|
||||
|
||||
Assert.Equal (expected, driver.Clip.GetBounds ());
|
||||
|
||||
@@ -340,8 +339,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
|
||||
[Fact]
|
||||
public void AddFrameToClip_EmptyFrame_WorksCorrectly ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver (80, 25);
|
||||
driver.Clip = new Region (driver.Screen);
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
driver.Clip = new (driver.Screen);
|
||||
|
||||
var view = new View
|
||||
{
|
||||
@@ -364,18 +363,18 @@ public class ViewDrawingClippingTests () : FakeDriverBase
|
||||
[Fact]
|
||||
public void AddViewportToClip_EmptyViewport_WorksCorrectly ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver (80, 25);
|
||||
driver.Clip = new Region (driver.Screen);
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
driver.Clip = new (driver.Screen);
|
||||
|
||||
var view = new View
|
||||
{
|
||||
X = 1,
|
||||
Y = 1,
|
||||
Width = 1, // Minimal size to have adornments
|
||||
Width = 1, // Minimal size to have adornments
|
||||
Height = 1,
|
||||
Driver = driver
|
||||
};
|
||||
view.Border!.Thickness = new Thickness (1);
|
||||
view.Border!.Thickness = new (1);
|
||||
view.BeginInit ();
|
||||
view.EndInit ();
|
||||
view.LayoutSubViews ();
|
||||
@@ -391,12 +390,12 @@ public class ViewDrawingClippingTests () : FakeDriverBase
|
||||
[Fact]
|
||||
public void ClipRegions_OutOfBounds_HandledCorrectly ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver (80, 25);
|
||||
driver.Clip = new Region (driver.Screen);
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
driver.Clip = new (driver.Screen);
|
||||
|
||||
var view = new View
|
||||
{
|
||||
X = 100, // Outside screen bounds
|
||||
X = 100, // Outside screen bounds
|
||||
Y = 100,
|
||||
Width = 20,
|
||||
Height = 20,
|
||||
@@ -409,6 +408,7 @@ public class ViewDrawingClippingTests () : FakeDriverBase
|
||||
Region? previous = view.AddFrameToClip ();
|
||||
|
||||
Assert.NotNull (previous);
|
||||
|
||||
// The clip should be empty since the view is outside the screen
|
||||
Assert.True (driver.Clip.IsEmpty () || !driver.Clip.Contains (100, 100));
|
||||
}
|
||||
@@ -420,8 +420,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
|
||||
[Fact]
|
||||
public void Clip_Set_BeforeDraw_ClipsDrawing ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver (80, 25);
|
||||
var clip = new Region (new Rectangle (10, 10, 10, 10));
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
var clip = new Region (new (10, 10, 10, 10));
|
||||
driver.Clip = clip;
|
||||
|
||||
var view = new View
|
||||
@@ -445,8 +445,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
|
||||
[Fact]
|
||||
public void Draw_UpdatesDriverClip ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver (80, 25);
|
||||
driver.Clip = new Region (driver.Screen);
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
driver.Clip = new (driver.Screen);
|
||||
|
||||
var view = new View
|
||||
{
|
||||
@@ -464,14 +464,15 @@ public class ViewDrawingClippingTests () : FakeDriverBase
|
||||
|
||||
// Clip should be updated to exclude the drawn view
|
||||
Assert.NotNull (driver.Clip);
|
||||
|
||||
// Assert.False (driver.Clip.Contains (15, 15)); // Point inside the view should be excluded
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Draw_WithSubViews_ClipsCorrectly ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver (80, 25);
|
||||
driver.Clip = new Region (driver.Screen);
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
driver.Clip = new (driver.Screen);
|
||||
|
||||
var superView = new View
|
||||
{
|
||||
@@ -491,13 +492,277 @@ public class ViewDrawingClippingTests () : FakeDriverBase
|
||||
|
||||
// Both superView and view should be excluded from clip
|
||||
Assert.NotNull (driver.Clip);
|
||||
|
||||
// Assert.False (driver.Clip.Contains (15, 15)); // Point in superView should be excluded
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that wide glyphs (🍎) are correctly clipped when overlapped by bordered subviews
|
||||
/// at different column alignments (even vs odd). Demonstrates:
|
||||
/// 1. Full clipping at even columns (X=0, X=2)
|
||||
/// 2. Partial clipping at odd columns (X=1) resulting in half-glyphs (<28>)
|
||||
/// 3. The recursive draw flow and clip exclusion mechanism
|
||||
///
|
||||
/// For detailed draw flow documentation, see ViewDrawingClippingTests.DrawFlow.md
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Draw_WithBorderSubView_DrawsCorrectly ()
|
||||
{
|
||||
IApplication app = Application.Create ();
|
||||
app.Init ("fake");
|
||||
IDriver driver = app!.Driver!;
|
||||
driver.SetScreenSize (30, 20);
|
||||
|
||||
driver!.Clip = new (driver.Screen);
|
||||
|
||||
var superView = new Runnable ()
|
||||
{
|
||||
X = 0,
|
||||
Y = 0,
|
||||
Width = Dim.Auto () + 4,
|
||||
Height = Dim.Auto () + 1,
|
||||
Driver = driver
|
||||
};
|
||||
|
||||
Rune codepoint = Glyphs.Apple;
|
||||
|
||||
superView.DrawingContent += (s, e) =>
|
||||
{
|
||||
var view = s as View;
|
||||
for (var r = 0; r < view!.Viewport.Height; r++)
|
||||
{
|
||||
for (var c = 0; c < view.Viewport.Width; c += 2)
|
||||
{
|
||||
if (codepoint != default (Rune))
|
||||
{
|
||||
view.AddRune (c, r, codepoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
e.DrawContext?.AddDrawnRectangle (view.Viewport);
|
||||
e.Cancel = true;
|
||||
};
|
||||
|
||||
var viewWithBorderAtX0 = new View
|
||||
{
|
||||
Text = "viewWithBorderAtX0",
|
||||
BorderStyle = LineStyle.Dashed,
|
||||
X = 0,
|
||||
Y = 1,
|
||||
Width = Dim.Auto (),
|
||||
Height = 3
|
||||
};
|
||||
|
||||
var viewWithBorderAtX1 = new View
|
||||
{
|
||||
Text = "viewWithBorderAtX1",
|
||||
BorderStyle = LineStyle.Dashed,
|
||||
X = 1,
|
||||
Y = Pos.Bottom (viewWithBorderAtX0) + 1,
|
||||
Width = Dim.Auto (),
|
||||
Height = 3
|
||||
};
|
||||
|
||||
var viewWithBorderAtX2 = new View
|
||||
{
|
||||
Text = "viewWithBorderAtX2",
|
||||
BorderStyle = LineStyle.Dashed,
|
||||
X = 2,
|
||||
Y = Pos.Bottom (viewWithBorderAtX1) + 1,
|
||||
Width = Dim.Auto (),
|
||||
Height = 3
|
||||
};
|
||||
|
||||
superView.Add (viewWithBorderAtX0, viewWithBorderAtX1, viewWithBorderAtX2);
|
||||
app.Begin (superView);
|
||||
// Begin calls LayoutAndDraw, so no need to call it again here
|
||||
// app.LayoutAndDraw();
|
||||
|
||||
DriverAssert.AssertDriverContentsAre (
|
||||
"""
|
||||
🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎
|
||||
┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐🍎🍎🍎
|
||||
┆viewWithBorderAtX0┆🍎🍎🍎
|
||||
└╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘🍎🍎🍎
|
||||
🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎
|
||||
<EFBFBD>┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐ 🍎🍎
|
||||
<EFBFBD>┆viewWithBorderAtX1┆ 🍎🍎
|
||||
<EFBFBD>└╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘ 🍎🍎
|
||||
🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎
|
||||
🍎┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐🍎🍎
|
||||
🍎┆viewWithBorderAtX2┆🍎🍎
|
||||
🍎└╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘🍎🍎
|
||||
🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎
|
||||
""",
|
||||
output,
|
||||
driver);
|
||||
|
||||
DriverAssert.AssertDriverOutputIs (@"\x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m┆viewWithBorderAtX0┆🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m└╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m<39>┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐ 🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m<39>┆viewWithBorderAtX1┆ 🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m<39>└╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘ 🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎┆viewWithBorderAtX2┆🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎└╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m",
|
||||
output, driver);
|
||||
|
||||
DriverImpl? driverImpl = driver as DriverImpl;
|
||||
FakeOutput? fakeOutput = driverImpl!.GetOutput () as FakeOutput;
|
||||
|
||||
output.WriteLine ("Driver Output After Redraw:\n" + driver.GetOutput().GetLastOutput());
|
||||
|
||||
// BUGBUG: Border.set_LineStyle does not call SetNeedsDraw
|
||||
viewWithBorderAtX1!.Border!.LineStyle = LineStyle.Single;
|
||||
viewWithBorderAtX1.Border!.SetNeedsDraw ();
|
||||
app.LayoutAndDraw ();
|
||||
|
||||
DriverAssert.AssertDriverContentsAre (
|
||||
"""
|
||||
🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎
|
||||
┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐🍎🍎🍎
|
||||
┆viewWithBorderAtX0┆🍎🍎🍎
|
||||
└╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘🍎🍎🍎
|
||||
🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎
|
||||
<EFBFBD>┌──────────────────┐ 🍎🍎
|
||||
<EFBFBD>│viewWithBorderAtX1│ 🍎🍎
|
||||
<EFBFBD>└──────────────────┘ 🍎🍎
|
||||
🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎
|
||||
🍎┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐🍎🍎
|
||||
🍎┆viewWithBorderAtX2┆🍎🍎
|
||||
🍎└╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘🍎🍎
|
||||
🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎
|
||||
""",
|
||||
output,
|
||||
driver);
|
||||
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Draw_WithBorderSubView_At_Col1_In_WideGlyph_DrawsCorrectly ()
|
||||
{
|
||||
IApplication app = Application.Create ();
|
||||
app.Init ("fake");
|
||||
IDriver driver = app!.Driver!;
|
||||
driver.SetScreenSize (6, 3); // Minimal: 6 cols wide (3 for content + 2 for border + 1), 3 rows high (1 for content + 2 for border)
|
||||
|
||||
driver!.Clip = new (driver.Screen);
|
||||
|
||||
var superView = new Runnable ()
|
||||
{
|
||||
X = 0,
|
||||
Y = 0,
|
||||
Width = Dim.Fill (),
|
||||
Height = Dim.Fill (),
|
||||
Driver = driver
|
||||
};
|
||||
|
||||
Rune codepoint = Glyphs.Apple;
|
||||
|
||||
superView.DrawingContent += (s, e) =>
|
||||
{
|
||||
View? view = s as View;
|
||||
view?.AddStr (0, 0, "🍎🍎🍎🍎");
|
||||
view?.AddStr (0, 1, "🍎🍎🍎🍎");
|
||||
view?.AddStr (0, 2, "🍎🍎🍎🍎");
|
||||
e.DrawContext?.AddDrawnRectangle (view!.Viewport);
|
||||
e.Cancel = true;
|
||||
};
|
||||
|
||||
// Minimal border at X=1 (odd column), Width=3, Height=3 (includes border)
|
||||
var viewWithBorder = new View
|
||||
{
|
||||
Text = "X",
|
||||
BorderStyle = LineStyle.Single,
|
||||
X = 1,
|
||||
Y = 0,
|
||||
Width = 3,
|
||||
Height = 3
|
||||
};
|
||||
|
||||
superView.Add (viewWithBorder);
|
||||
app.Begin (superView);
|
||||
|
||||
DriverAssert.AssertDriverContentsAre (
|
||||
"""
|
||||
<EFBFBD>┌─┐🍎
|
||||
<EFBFBD>│X│🍎
|
||||
<EFBFBD>└─┘🍎
|
||||
""",
|
||||
output,
|
||||
driver);
|
||||
|
||||
DriverAssert.AssertDriverOutputIs (@"\x1b[38;2;95;158;160m\x1b[48;2;54;69;79m<39>┌─┐🍎<E29490>│X│🍎<E29482>└─┘🍎",
|
||||
output, driver);
|
||||
|
||||
DriverImpl? driverImpl = driver as DriverImpl;
|
||||
FakeOutput? fakeOutput = driverImpl!.GetOutput () as FakeOutput;
|
||||
|
||||
output.WriteLine ("Driver Output:\n" + fakeOutput!.GetLastOutput ());
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void Draw_WithBorderSubView_At_Col3_In_WideGlyph_DrawsCorrectly ()
|
||||
{
|
||||
IApplication app = Application.Create ();
|
||||
app.Init ("fake");
|
||||
IDriver driver = app!.Driver!;
|
||||
driver.SetScreenSize (6, 3); // Screen: 6 cols wide, 3 rows high; enough for 3x3 border subview at col 3 plus content on the left
|
||||
|
||||
driver!.Clip = new (driver.Screen);
|
||||
|
||||
var superView = new Runnable ()
|
||||
{
|
||||
X = 0,
|
||||
Y = 0,
|
||||
Width = Dim.Fill (),
|
||||
Height = Dim.Fill (),
|
||||
Driver = driver
|
||||
};
|
||||
|
||||
Rune codepoint = Glyphs.Apple;
|
||||
|
||||
superView.DrawingContent += (s, e) =>
|
||||
{
|
||||
View? view = s as View;
|
||||
view?.AddStr (0, 0, "🍎🍎🍎🍎");
|
||||
view?.AddStr (0, 1, "🍎🍎🍎🍎");
|
||||
view?.AddStr (0, 2, "🍎🍎🍎🍎");
|
||||
e.DrawContext?.AddDrawnRectangle (view!.Viewport);
|
||||
e.Cancel = true;
|
||||
};
|
||||
|
||||
// Minimal border at X=3 (odd column), Width=3, Height=3 (includes border)
|
||||
var viewWithBorder = new View
|
||||
{
|
||||
Text = "X",
|
||||
BorderStyle = LineStyle.Single,
|
||||
X = 3,
|
||||
Y = 0,
|
||||
Width = 3,
|
||||
Height = 3
|
||||
};
|
||||
|
||||
superView.Add (viewWithBorder);
|
||||
app.Begin (superView);
|
||||
|
||||
DriverAssert.AssertDriverContentsAre (
|
||||
"""
|
||||
🍎<EFBFBD>┌─┐
|
||||
🍎<EFBFBD>│X│
|
||||
🍎<EFBFBD>└─┘
|
||||
""",
|
||||
output,
|
||||
driver);
|
||||
|
||||
DriverAssert.AssertDriverOutputIs (@"\x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎<6D>┌─┐🍎<E29490>│X│🍎<E29482>└─┘",
|
||||
output, driver);
|
||||
|
||||
DriverImpl? driverImpl = driver as DriverImpl;
|
||||
FakeOutput? fakeOutput = driverImpl!.GetOutput () as FakeOutput;
|
||||
|
||||
output.WriteLine ("Driver Output:\n" + fakeOutput!.GetLastOutput ());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Draw_NonVisibleView_DoesNotUpdateClip ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver (80, 25);
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
var originalClip = new Region (driver.Screen);
|
||||
driver.Clip = originalClip.Clone ();
|
||||
|
||||
@@ -522,8 +787,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
|
||||
[Fact]
|
||||
public void ExcludeFromClip_ExcludesRegion ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver (80, 25);
|
||||
driver.Clip = new Region (driver.Screen);
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
driver.Clip = new (driver.Screen);
|
||||
|
||||
var view = new View
|
||||
{
|
||||
@@ -542,13 +807,12 @@ public class ViewDrawingClippingTests () : FakeDriverBase
|
||||
|
||||
Assert.NotNull (driver.Clip);
|
||||
Assert.False (driver.Clip.Contains (20, 20)); // Point inside excluded rect should not be in clip
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExcludeFromClip_WithNullClip_DoesNotThrow ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver (80, 25);
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
driver.Clip = null!;
|
||||
|
||||
var view = new View
|
||||
@@ -560,10 +824,9 @@ public class ViewDrawingClippingTests () : FakeDriverBase
|
||||
Driver = driver
|
||||
};
|
||||
|
||||
var exception = Record.Exception (() => view.ExcludeFromClip (new Rectangle (15, 15, 10, 10)));
|
||||
Exception? exception = Record.Exception (() => view.ExcludeFromClip (new Rectangle (15, 15, 10, 10)));
|
||||
|
||||
Assert.Null (exception);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -573,7 +836,7 @@ public class ViewDrawingClippingTests () : FakeDriverBase
|
||||
[Fact]
|
||||
public void SetClip_SetsDriverClip ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver (80, 25);
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
var view = new View
|
||||
{
|
||||
@@ -584,7 +847,7 @@ public class ViewDrawingClippingTests () : FakeDriverBase
|
||||
Driver = driver
|
||||
};
|
||||
|
||||
var newClip = new Region (new Rectangle (5, 5, 30, 30));
|
||||
var newClip = new Region (new (5, 5, 30, 30));
|
||||
view.SetClip (newClip);
|
||||
|
||||
Assert.Equal (newClip, driver.Clip);
|
||||
@@ -593,8 +856,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
|
||||
[Fact (Skip = "See BUGBUG in SetClip")]
|
||||
public void SetClip_WithNullClip_ClearsClip ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver (80, 25);
|
||||
driver.Clip = new Region (new Rectangle (10, 10, 20, 20));
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
driver.Clip = new (new (10, 10, 20, 20));
|
||||
|
||||
var view = new View
|
||||
{
|
||||
@@ -613,7 +876,7 @@ public class ViewDrawingClippingTests () : FakeDriverBase
|
||||
[Fact]
|
||||
public void Draw_Excludes_View_From_Clip ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver (80, 25);
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
var originalClip = new Region (driver.Screen);
|
||||
driver.Clip = originalClip.Clone ();
|
||||
|
||||
@@ -641,8 +904,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
|
||||
[Fact]
|
||||
public void Draw_EmptyViewport_DoesNotCrash ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver (80, 25);
|
||||
driver.Clip = new Region (driver.Screen);
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
driver.Clip = new (driver.Screen);
|
||||
|
||||
var view = new View
|
||||
{
|
||||
@@ -652,13 +915,13 @@ public class ViewDrawingClippingTests () : FakeDriverBase
|
||||
Height = 1,
|
||||
Driver = driver
|
||||
};
|
||||
view.Border!.Thickness = new Thickness (1);
|
||||
view.Border!.Thickness = new (1);
|
||||
view.BeginInit ();
|
||||
view.EndInit ();
|
||||
view.LayoutSubViews ();
|
||||
|
||||
// With border of 1, viewport should be empty (0x0 or negative)
|
||||
var exception = Record.Exception (() => view.Draw ());
|
||||
Exception? exception = Record.Exception (() => view.Draw ());
|
||||
|
||||
Assert.Null (exception);
|
||||
}
|
||||
@@ -666,8 +929,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
|
||||
[Fact]
|
||||
public void Draw_VeryLargeView_HandlesClippingCorrectly ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver (80, 25);
|
||||
driver.Clip = new Region (driver.Screen);
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
driver.Clip = new (driver.Screen);
|
||||
|
||||
var view = new View
|
||||
{
|
||||
@@ -681,7 +944,7 @@ public class ViewDrawingClippingTests () : FakeDriverBase
|
||||
view.EndInit ();
|
||||
view.LayoutSubViews ();
|
||||
|
||||
var exception = Record.Exception (() => view.Draw ());
|
||||
Exception? exception = Record.Exception (() => view.Draw ());
|
||||
|
||||
Assert.Null (exception);
|
||||
}
|
||||
@@ -689,8 +952,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
|
||||
[Fact]
|
||||
public void Draw_NegativeCoordinates_HandlesClippingCorrectly ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver (80, 25);
|
||||
driver.Clip = new Region (driver.Screen);
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
driver.Clip = new (driver.Screen);
|
||||
|
||||
var view = new View
|
||||
{
|
||||
@@ -704,7 +967,7 @@ public class ViewDrawingClippingTests () : FakeDriverBase
|
||||
view.EndInit ();
|
||||
view.LayoutSubViews ();
|
||||
|
||||
var exception = Record.Exception (() => view.Draw ());
|
||||
Exception? exception = Record.Exception (() => view.Draw ());
|
||||
|
||||
Assert.Null (exception);
|
||||
}
|
||||
@@ -712,8 +975,8 @@ public class ViewDrawingClippingTests () : FakeDriverBase
|
||||
[Fact]
|
||||
public void Draw_OutOfScreenBounds_HandlesClippingCorrectly ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver (80, 25);
|
||||
driver.Clip = new Region (driver.Screen);
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
driver.Clip = new (driver.Screen);
|
||||
|
||||
var view = new View
|
||||
{
|
||||
@@ -727,7 +990,7 @@ public class ViewDrawingClippingTests () : FakeDriverBase
|
||||
view.EndInit ();
|
||||
view.LayoutSubViews ();
|
||||
|
||||
var exception = Record.Exception (() => view.Draw ());
|
||||
Exception? exception = Record.Exception (() => view.Draw ());
|
||||
|
||||
Assert.Null (exception);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -32,9 +32,9 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
|
||||
};
|
||||
|
||||
Point? receivedPosition = null;
|
||||
var eventReceived = false;
|
||||
bool eventReceived = false;
|
||||
|
||||
view.MouseEvent += (sender, args) =>
|
||||
view.MouseEvent += (_, args) =>
|
||||
{
|
||||
eventReceived = true;
|
||||
receivedPosition = args.Position;
|
||||
@@ -90,9 +90,9 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
|
||||
};
|
||||
|
||||
Point? receivedPosition = null;
|
||||
var eventReceived = false;
|
||||
bool eventReceived = false;
|
||||
|
||||
view.MouseEvent += (sender, args) =>
|
||||
view.MouseEvent += (_, args) =>
|
||||
{
|
||||
eventReceived = true;
|
||||
receivedPosition = args.Position;
|
||||
@@ -100,7 +100,7 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
|
||||
|
||||
MouseEventArgs mouseEvent = new ()
|
||||
{
|
||||
Position = new Point (viewRelativeX, viewRelativeY),
|
||||
Position = new (viewRelativeX, viewRelativeY),
|
||||
Flags = MouseFlags.Button1Clicked
|
||||
};
|
||||
|
||||
@@ -146,9 +146,9 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
|
||||
superView.Add (subView);
|
||||
|
||||
Point? subViewReceivedPosition = null;
|
||||
var subViewEventReceived = false;
|
||||
bool subViewEventReceived = false;
|
||||
|
||||
subView.MouseEvent += (sender, args) =>
|
||||
subView.MouseEvent += (_, args) =>
|
||||
{
|
||||
subViewEventReceived = true;
|
||||
subViewReceivedPosition = args.Position;
|
||||
@@ -175,7 +175,7 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MouseClick_OnSubView_RaisesMouseClickEvent ()
|
||||
public void MouseClick_OnSubView_RaisesSelectingEvent ()
|
||||
{
|
||||
// Arrange
|
||||
View superView = new ()
|
||||
@@ -194,8 +194,8 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
|
||||
|
||||
superView.Add (subView);
|
||||
|
||||
var clickCount = 0;
|
||||
subView.MouseClick += (sender, args) => clickCount++;
|
||||
int selectingCount = 0;
|
||||
subView.Selecting += (_, _) => selectingCount++;
|
||||
|
||||
MouseEventArgs mouseEvent = new ()
|
||||
{
|
||||
@@ -207,7 +207,7 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
|
||||
subView.NewMouseEvent (mouseEvent);
|
||||
|
||||
// Assert
|
||||
Assert.Equal (1, clickCount);
|
||||
Assert.Equal (1, selectingCount);
|
||||
|
||||
subView.Dispose ();
|
||||
superView.Dispose ();
|
||||
@@ -222,20 +222,20 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
|
||||
{
|
||||
// Arrange
|
||||
View view = new () { Width = 10, Height = 10 };
|
||||
var handlerCalled = false;
|
||||
var clickHandlerCalled = false;
|
||||
bool handlerCalled = false;
|
||||
bool clickHandlerCalled = false;
|
||||
|
||||
view.MouseEvent += (sender, args) =>
|
||||
view.MouseEvent += (_, args) =>
|
||||
{
|
||||
handlerCalled = true;
|
||||
args.Handled = true; // Mark as handled
|
||||
};
|
||||
|
||||
view.MouseClick += (sender, args) => { clickHandlerCalled = true; };
|
||||
view.MouseEvent += (_, e) => { clickHandlerCalled = !e.IsSingleDoubleOrTripleClicked; ; };
|
||||
|
||||
MouseEventArgs mouseEvent = new ()
|
||||
{
|
||||
Position = new Point (5, 5),
|
||||
Position = new (5, 5),
|
||||
Flags = MouseFlags.Button1Clicked
|
||||
};
|
||||
|
||||
@@ -255,20 +255,17 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
|
||||
{
|
||||
// Arrange
|
||||
View view = new () { Width = 10, Height = 10 };
|
||||
var eventHandlerCalled = false;
|
||||
var clickHandlerCalled = false;
|
||||
bool eventHandlerCalled = false;
|
||||
|
||||
view.MouseEvent += (sender, args) =>
|
||||
view.MouseEvent += (_, _) =>
|
||||
{
|
||||
eventHandlerCalled = true;
|
||||
// Don't set Handled = true
|
||||
};
|
||||
|
||||
view.MouseClick += (sender, args) => { clickHandlerCalled = true; };
|
||||
|
||||
MouseEventArgs mouseEvent = new ()
|
||||
{
|
||||
Position = new Point (5, 5),
|
||||
Position = new (5, 5),
|
||||
Flags = MouseFlags.Button1Clicked
|
||||
};
|
||||
|
||||
@@ -277,7 +274,6 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
|
||||
|
||||
// Assert
|
||||
Assert.True (eventHandlerCalled);
|
||||
Assert.True (clickHandlerCalled); // Click handler should be called when event is not handled
|
||||
|
||||
view.Dispose ();
|
||||
}
|
||||
@@ -287,18 +283,17 @@ 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 };
|
||||
var pressedCount = 0;
|
||||
var releasedCount = 0;
|
||||
var clickedCount = 0;
|
||||
int pressedCount = 0;
|
||||
int releasedCount = 0;
|
||||
|
||||
view.MouseEvent += (sender, args) =>
|
||||
view.MouseEvent += (_, args) =>
|
||||
{
|
||||
if (args.Flags.HasFlag (MouseFlags.Button1Pressed))
|
||||
{
|
||||
@@ -311,11 +306,9 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
|
||||
}
|
||||
};
|
||||
|
||||
view.MouseClick += (sender, args) => { clickedCount++; };
|
||||
|
||||
MouseEventArgs mouseEvent = new ()
|
||||
{
|
||||
Position = new Point (5, 5),
|
||||
Position = new (5, 5),
|
||||
Flags = flags
|
||||
};
|
||||
|
||||
@@ -325,7 +318,6 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
|
||||
// Assert
|
||||
Assert.Equal (expectedPressed, pressedCount);
|
||||
Assert.Equal (expectedReleased, releasedCount);
|
||||
Assert.Equal (expectedClicked, clickedCount);
|
||||
|
||||
view.Dispose ();
|
||||
}
|
||||
@@ -339,13 +331,13 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
|
||||
{
|
||||
// Arrange
|
||||
View view = new () { Width = 10, Height = 10 };
|
||||
var clickCount = 0;
|
||||
int clickCount = 0;
|
||||
|
||||
view.MouseClick += (sender, args) => clickCount++;
|
||||
view.MouseEvent += (_, a) => clickCount += a.IsSingleDoubleOrTripleClicked ? 1 : 0;
|
||||
|
||||
MouseEventArgs mouseEvent = new ()
|
||||
{
|
||||
Position = new Point (5, 5),
|
||||
Position = new (5, 5),
|
||||
Flags = clickFlag
|
||||
};
|
||||
|
||||
@@ -373,12 +365,12 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
|
||||
Enabled = false
|
||||
};
|
||||
|
||||
var eventCalled = false;
|
||||
view.MouseEvent += (sender, args) => { eventCalled = true; };
|
||||
bool eventCalled = false;
|
||||
view.MouseEvent += (_, _) => { eventCalled = true; };
|
||||
|
||||
MouseEventArgs mouseEvent = new ()
|
||||
{
|
||||
Position = new Point (5, 5),
|
||||
Position = new (5, 5),
|
||||
Flags = MouseFlags.Button1Clicked
|
||||
};
|
||||
|
||||
@@ -392,7 +384,7 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void View_Disabled_DoesNotRaiseMouseClickEvent ()
|
||||
public void View_Disabled_DoesNotRaiseSelectingEvent ()
|
||||
{
|
||||
// Arrange
|
||||
View view = new ()
|
||||
@@ -402,12 +394,12 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
|
||||
Enabled = false
|
||||
};
|
||||
|
||||
var clickCalled = false;
|
||||
view.MouseClick += (sender, args) => { clickCalled = true; };
|
||||
bool selectingCalled = false;
|
||||
view.Selecting += (_, _) => { selectingCalled = true; };
|
||||
|
||||
MouseEventArgs mouseEvent = new ()
|
||||
{
|
||||
Position = new Point (5, 5),
|
||||
Position = new (5, 5),
|
||||
Flags = MouseFlags.Button1Clicked
|
||||
};
|
||||
|
||||
@@ -415,7 +407,7 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
|
||||
view.NewMouseEvent (mouseEvent);
|
||||
|
||||
// Assert
|
||||
Assert.False (clickCalled);
|
||||
Assert.False (selectingCalled);
|
||||
|
||||
view.Dispose ();
|
||||
}
|
||||
@@ -445,7 +437,7 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
|
||||
|
||||
MouseEventArgs mouseEvent = new ()
|
||||
{
|
||||
Position = new Point (2, 2),
|
||||
Position = new (2, 2),
|
||||
Flags = MouseFlags.Button1Clicked
|
||||
};
|
||||
|
||||
@@ -475,12 +467,12 @@ public class MouseEventRoutingTests (ITestOutputHelper output)
|
||||
|
||||
superView.Add (view);
|
||||
|
||||
var selectingCount = 0;
|
||||
view.Selecting += (sender, args) => selectingCount++;
|
||||
int selectingCount = 0;
|
||||
view.Selecting += (_, _) => selectingCount++;
|
||||
|
||||
MouseEventArgs mouseEvent = new ()
|
||||
{
|
||||
Position = new Point (5, 5),
|
||||
Position = new (5, 5),
|
||||
Flags = MouseFlags.Button1Clicked
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user