diff --git a/Terminal.Gui/Terminal.Gui.csproj b/Terminal.Gui/Terminal.Gui.csproj
index b8c6bc784..d8efda92d 100644
--- a/Terminal.Gui/Terminal.Gui.csproj
+++ b/Terminal.Gui/Terminal.Gui.csproj
@@ -87,7 +87,7 @@
-
+
diff --git a/Terminal.sln b/Terminal.sln
index c233b37ad..65f3d7596 100644
--- a/Terminal.sln
+++ b/Terminal.sln
@@ -62,7 +62,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StressTests", "Tests\Stress
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests.Parallelizable", "Tests\UnitTestsParallelizable\UnitTests.Parallelizable.csproj", "{DE780834-190A-8277-51FD-750CC666E82D}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TerminalGuiFluentAssertions", "TerminalGuiFluentAssertions\TerminalGuiFluentAssertions.csproj", "{7C610F03-9E38-409F-9B21-A02D5569E16A}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TerminalGuiFluentTesting", "TerminalGuiFluentAssertions\TerminalGuiFluentTesting.csproj", "{7C610F03-9E38-409F-9B21-A02D5569E16A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
diff --git a/TerminalGuiFluentAssertions/Class1.cs b/TerminalGuiFluentAssertions/Class1.cs
deleted file mode 100644
index 7e0a078f4..000000000
--- a/TerminalGuiFluentAssertions/Class1.cs
+++ /dev/null
@@ -1,404 +0,0 @@
-using System.Collections.Concurrent;
-using System.Drawing;
-using FluentAssertions;
-using FluentAssertions.Numeric;
-using Terminal.Gui;
-using Terminal.Gui.ConsoleDrivers;
-using static Unix.Terminal.Curses;
-
-namespace TerminalGuiFluentAssertions;
-
-class FakeInput : IConsoleInput
-{
- private readonly CancellationToken _hardStopToken;
-
- private readonly CancellationTokenSource _timeoutCts;
- public FakeInput (CancellationToken hardStopToken)
- {
- _hardStopToken = hardStopToken;
-
- // Create a timeout-based cancellation token too to prevent tests ever fully hanging
- _timeoutCts = new (With.Timeout);
- }
- ///
- public void Dispose () { }
-
- ///
- public void Initialize (ConcurrentQueue inputBuffer) { InputBuffer = inputBuffer;}
-
- public ConcurrentQueue InputBuffer { get; set; }
-
- ///
- public void Run (CancellationToken token)
- {
- // Blocks until either the token or the hardStopToken is cancelled.
- WaitHandle.WaitAny (new [] { token.WaitHandle, _hardStopToken.WaitHandle, _timeoutCts.Token.WaitHandle });
- }
-}
-
-class FakeNetInput (CancellationToken hardStopToken) : FakeInput (hardStopToken), INetInput
-{
-
-}
-
-class FakeWindowsInput (CancellationToken hardStopToken) : FakeInput (hardStopToken), IWindowsInput
-{
-
-}
-
-class FakeOutput : IConsoleOutput
-{
- public IOutputBuffer LastBuffer { get; set; }
- public Size Size { get; set; }
-
- ///
- public void Dispose ()
- {
-
- }
-
- ///
- public void Write (ReadOnlySpan text)
- {
-
- }
-
- ///
- public void Write (IOutputBuffer buffer)
- {
- LastBuffer = buffer;
- }
-
-
- ///
- public Size GetWindowSize ()
- {
- return Size;
- }
-
- ///
- public void SetCursorVisibility (CursorVisibility visibility)
- {
-
- }
-
- ///
- public void SetCursorPosition (int col, int row)
- {
-
- }
-
-}
-///
-/// Entry point to fluent assertions.
-///
-public static class With
-{
- ///
- /// Entrypoint to fluent assertions
- ///
- ///
- ///
- ///
- public static GuiTestContext A (int width, int height) where T : Toplevel, new ()
- {
- return new (width,height);
- }
-
- ///
- /// The global timeout to allow for any given application to run for before shutting down.
- ///
- public static TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds (30);
-}
-public class GuiTestContext : IDisposable where T : Toplevel, new()
-{
- private readonly CancellationTokenSource _cts = new ();
- private readonly CancellationTokenSource _hardStop = new (With.Timeout);
- private readonly Task _runTask;
- private Exception _ex;
- private readonly FakeOutput _output = new ();
- private readonly FakeWindowsInput winInput;
- private View _lastView;
-
- internal GuiTestContext (int width, int height)
- {
- IApplication origApp = ApplicationImpl.Instance;
-
- var netInput = new FakeNetInput (_cts.Token);
- winInput = new FakeWindowsInput (_cts.Token);
-
- _output.Size = new (width, height);
-
- var v2 = new ApplicationV2(
- () => netInput,
- ()=>_output,
- () => winInput,
- () => _output);
-
- var booting = new SemaphoreSlim (0, 1);
-
- // Start the application in a background thread
- _runTask = Task.Run (() =>
- {
- try
- {
- ApplicationImpl.ChangeInstance (v2);
-
- v2.Init (null,"v2win");
-
- booting.Release ();
-
- Application.Run (); // This will block, but it's on a background thread now
-
- Application.Shutdown ();
- }
- catch (OperationCanceledException)
- { }
- catch (Exception ex)
- {
- _ex = ex;
- }
- finally
- {
- ApplicationImpl.ChangeInstance (origApp);
- }
- }, _cts.Token);
-
- // Wait for booting to complete with a timeout to avoid hangs
- if (!booting.WaitAsync (TimeSpan.FromSeconds (5)).Result)
- {
- throw new TimeoutException ("Application failed to start within the allotted time.");
- }
-
- WaitIteration ();
- }
-
- ///
- /// Stops the application and waits for the background thread to exit.
- ///
- public GuiTestContext Stop ()
- {
- if (_runTask.IsCompleted)
- {
- return this;
- }
-
- Application.Invoke (()=> Application.RequestStop ());
-
- // Wait for the application to stop, but give it a 1-second timeout
- if (!_runTask.Wait (TimeSpan.FromMilliseconds (1000)))
- {
- _cts.Cancel ();
- // Timeout occurred, force the task to stop
- _hardStop.Cancel ();
- throw new TimeoutException ("Application failed to stop within the allotted time.");
- }
- _cts.Cancel ();
-
- if (_ex != null)
- {
- throw _ex; // Propagate any exception that happened in the background task
- }
-
- return this;
- }
-
- // Cleanup to avoid state bleed between tests
- public void Dispose ()
- {
- Stop ();
-
- if (_hardStop.IsCancellationRequested)
- {
- throw new Exception (
- "Application was hard stopped, typically this means it timed out or did not shutdown gracefully. Ensure you call Stop in your test");
- }
- _hardStop.Cancel();
- }
-
- ///
- /// Adds the given to the current top level view
- /// and performs layout.
- ///
- ///
- ///
- public GuiTestContext Add (View v)
- {
- WaitIteration (
- () =>
- {
- var top = Application.Top ?? throw new Exception("Top was null so could not add view");
- top.Add (v);
- top.Layout ();
- _lastView = v;
- });
-
- return this;
- }
-
- public GuiTestContext ResizeConsole (int width, int height)
- {
- _output.Size = new Size (width,height);
-
- return WaitIteration ();
- }
- public GuiTestContext ScreenShot (string title, TextWriter writer)
- {
- writer.WriteLine(title +":");
- var text = Application.ToString ();
-
- writer.WriteLine(text);
-
- return WaitIteration ();
- }
- public GuiTestContext WaitIteration (Action? a = null)
- {
- a ??= () => { };
- var ctsLocal = new CancellationTokenSource ();
-
-
- Application.Invoke (()=>
- {
- a();
- ctsLocal.Cancel ();
- });
-
- // Blocks until either the token or the hardStopToken is cancelled.
- WaitHandle.WaitAny (new []
- {
- _cts.Token.WaitHandle,
- _hardStop.Token.WaitHandle,
- ctsLocal.Token.WaitHandle
- });
- return this;
- }
-
- public GuiTestContext Assert (AndConstraint be)
- {
- return this;
- }
-
- public GuiTestContext RightClick (int screenX, int screenY)
- {
- return Click (WindowsConsole.ButtonState.Button3Pressed,screenX, screenY);
- }
-
- public GuiTestContext LeftClick (int screenX, int screenY)
- {
- return Click (WindowsConsole.ButtonState.Button1Pressed, screenX, screenY);
- }
-
- private GuiTestContext Click (WindowsConsole.ButtonState btn, int screenX, int screenY)
- {
- winInput.InputBuffer.Enqueue (new WindowsConsole.InputRecord ()
- {
- EventType = WindowsConsole.EventType.Mouse,
- MouseEvent = new WindowsConsole.MouseEventRecord ()
- {
- ButtonState = btn,
- MousePosition = new WindowsConsole.Coord ((short)screenX, (short)screenY)
- }
- });
-
- winInput.InputBuffer.Enqueue (new WindowsConsole.InputRecord ()
- {
- EventType = WindowsConsole.EventType.Mouse,
- MouseEvent = new WindowsConsole.MouseEventRecord ()
- {
- ButtonState = WindowsConsole.ButtonState.NoButtonPressed,
- MousePosition = new WindowsConsole.Coord ((short)screenX, (short)screenY)
- }
- });
-
- WaitIteration ();
-
- return this;
- }
-
- public GuiTestContext Down ()
- {
- winInput.InputBuffer.Enqueue (new WindowsConsole.InputRecord ()
- {
- EventType = WindowsConsole.EventType.Key,
- KeyEvent = new WindowsConsole.KeyEventRecord
- {
- bKeyDown = true,
- wRepeatCount = 0,
- wVirtualKeyCode = ConsoleKeyMapping.VK.DOWN,
- wVirtualScanCode = 0,
- UnicodeChar = '\0',
- dwControlKeyState = WindowsConsole.ControlKeyState.NoControlKeyPressed
- }
- });
-
- winInput.InputBuffer.Enqueue (new WindowsConsole.InputRecord ()
- {
- EventType = WindowsConsole.EventType.Key,
- KeyEvent = new WindowsConsole.KeyEventRecord
- {
- bKeyDown = false,
- wRepeatCount = 0,
- wVirtualKeyCode = ConsoleKeyMapping.VK.DOWN,
- wVirtualScanCode = 0,
- UnicodeChar = '\0',
- dwControlKeyState = WindowsConsole.ControlKeyState.NoControlKeyPressed
- }
- });
-
-
- WaitIteration ();
-
- return this;
- }
- public GuiTestContext Enter ()
- {
- winInput.InputBuffer.Enqueue (new WindowsConsole.InputRecord ()
- {
- EventType = WindowsConsole.EventType.Key,
- KeyEvent = new WindowsConsole.KeyEventRecord
- {
- bKeyDown = true,
- wRepeatCount = 0,
- wVirtualKeyCode = ConsoleKeyMapping.VK.RETURN,
- wVirtualScanCode = 0,
- UnicodeChar = '\0',
- dwControlKeyState = WindowsConsole.ControlKeyState.NoControlKeyPressed
- }
- });
-
- winInput.InputBuffer.Enqueue (new WindowsConsole.InputRecord ()
- {
- EventType = WindowsConsole.EventType.Key,
- KeyEvent = new WindowsConsole.KeyEventRecord
- {
- bKeyDown = false,
- wRepeatCount = 0,
- wVirtualKeyCode = ConsoleKeyMapping.VK.RETURN,
- wVirtualScanCode = 0,
- UnicodeChar = '\0',
- dwControlKeyState = WindowsConsole.ControlKeyState.NoControlKeyPressed
- }
- });
-
- WaitIteration ();
-
- return this;
- }
- public GuiTestContext WithContextMenu (ContextMenu ctx, MenuBarItem menuItems)
- {
- LastView.MouseEvent += (s, e) =>
- {
- if (e.Flags.HasFlag (MouseFlags.Button3Clicked))
- {
- ctx.Show (menuItems);
- }
- };
-
- return this;
- }
-
- public View LastView => _lastView ?? Application.Top ?? throw new Exception ("Could not determine which view to add to");
-
-
-}
-
diff --git a/TerminalGuiFluentAssertions/ClassDiagram1.cd b/TerminalGuiFluentAssertions/ClassDiagram1.cd
new file mode 100644
index 000000000..d2965ee26
--- /dev/null
+++ b/TerminalGuiFluentAssertions/ClassDiagram1.cd
@@ -0,0 +1,59 @@
+
+
+
+
+
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAIAAAAAA=
+ With.cs
+
+
+
+
+
+ AQAAAAAAACAAAQEAAAAgAAAAAAAAAAAAAAAAAAAAAAI=
+ FakeInput.cs
+
+
+
+
+
+
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
+ FakeNetInput.cs
+
+
+
+
+
+
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
+ FakeWindowsInput.cs
+
+
+
+
+
+
+ AAAAAAAAgCAAgAAAAAAAAAAAAAAAQAAAMAAAAAEAAAA=
+ FakeOutput.cs
+
+
+
+
+
+
+
+
+
+ ABJAAAIAACBAAQAAgIAAAAAgABIEgAQAIAAIBACAIgA=
+ GuiTestContext.cs
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/TerminalGuiFluentAssertions/FakeInput.cs b/TerminalGuiFluentAssertions/FakeInput.cs
new file mode 100644
index 000000000..e90844382
--- /dev/null
+++ b/TerminalGuiFluentAssertions/FakeInput.cs
@@ -0,0 +1,34 @@
+using System.Collections.Concurrent;
+using Terminal.Gui;
+
+namespace TerminalGuiFluentTesting;
+
+internal class FakeInput : IConsoleInput
+{
+ private readonly CancellationToken _hardStopToken;
+
+ private readonly CancellationTokenSource _timeoutCts;
+
+ public FakeInput (CancellationToken hardStopToken)
+ {
+ _hardStopToken = hardStopToken;
+
+ // Create a timeout-based cancellation token too to prevent tests ever fully hanging
+ _timeoutCts = new (With.Timeout);
+ }
+
+ ///
+ public void Dispose () { }
+
+ ///
+ public void Initialize (ConcurrentQueue inputBuffer) { InputBuffer = inputBuffer; }
+
+ public ConcurrentQueue InputBuffer { get; set; }
+
+ ///
+ public void Run (CancellationToken token)
+ {
+ // Blocks until either the token or the hardStopToken is cancelled.
+ WaitHandle.WaitAny (new [] { token.WaitHandle, _hardStopToken.WaitHandle, _timeoutCts.Token.WaitHandle });
+ }
+}
diff --git a/TerminalGuiFluentAssertions/FakeNetInput.cs b/TerminalGuiFluentAssertions/FakeNetInput.cs
new file mode 100644
index 000000000..6ccede491
--- /dev/null
+++ b/TerminalGuiFluentAssertions/FakeNetInput.cs
@@ -0,0 +1,6 @@
+using Terminal.Gui;
+
+namespace TerminalGuiFluentTesting;
+
+internal class FakeNetInput (CancellationToken hardStopToken) : FakeInput (hardStopToken), INetInput
+{ }
diff --git a/TerminalGuiFluentAssertions/FakeOutput.cs b/TerminalGuiFluentAssertions/FakeOutput.cs
new file mode 100644
index 000000000..5f2b43bce
--- /dev/null
+++ b/TerminalGuiFluentAssertions/FakeOutput.cs
@@ -0,0 +1,28 @@
+using System.Drawing;
+using Terminal.Gui;
+
+namespace TerminalGuiFluentTesting;
+
+internal class FakeOutput : IConsoleOutput
+{
+ public IOutputBuffer LastBuffer { get; set; }
+ public Size Size { get; set; }
+
+ ///
+ public void Dispose () { }
+
+ ///
+ public void Write (ReadOnlySpan text) { }
+
+ ///
+ public void Write (IOutputBuffer buffer) { LastBuffer = buffer; }
+
+ ///
+ public Size GetWindowSize () { return Size; }
+
+ ///
+ public void SetCursorVisibility (CursorVisibility visibility) { }
+
+ ///
+ public void SetCursorPosition (int col, int row) { }
+}
diff --git a/TerminalGuiFluentAssertions/FakeWindowsInput.cs b/TerminalGuiFluentAssertions/FakeWindowsInput.cs
new file mode 100644
index 000000000..42ccf8098
--- /dev/null
+++ b/TerminalGuiFluentAssertions/FakeWindowsInput.cs
@@ -0,0 +1,6 @@
+using Terminal.Gui;
+
+namespace TerminalGuiFluentTesting;
+
+internal class FakeWindowsInput (CancellationToken hardStopToken) : FakeInput (hardStopToken), IWindowsInput
+{ }
diff --git a/TerminalGuiFluentAssertions/GuiTestContext.cs b/TerminalGuiFluentAssertions/GuiTestContext.cs
new file mode 100644
index 000000000..3e6570c72
--- /dev/null
+++ b/TerminalGuiFluentAssertions/GuiTestContext.cs
@@ -0,0 +1,304 @@
+using FluentAssertions;
+using Terminal.Gui;
+using Terminal.Gui.ConsoleDrivers;
+
+namespace TerminalGuiFluentTesting;
+
+public class GuiTestContext : IDisposable where T : Toplevel, new ()
+{
+ private readonly CancellationTokenSource _cts = new ();
+ private readonly CancellationTokenSource _hardStop = new (With.Timeout);
+ private readonly Task _runTask;
+ private Exception _ex;
+ private readonly FakeOutput _output = new ();
+ private readonly FakeWindowsInput _winInput;
+ private readonly FakeNetInput _netInput;
+ private View _lastView;
+
+ internal GuiTestContext (int width, int height)
+ {
+ IApplication origApp = ApplicationImpl.Instance;
+
+ _netInput = new (_cts.Token);
+ _winInput = new (_cts.Token);
+
+ _output.Size = new (width, height);
+
+ var v2 = new ApplicationV2 (
+ () => _netInput,
+ () => _output,
+ () => _winInput,
+ () => _output);
+
+ var booting = new SemaphoreSlim (0, 1);
+
+ // Start the application in a background thread
+ _runTask = Task.Run (
+ () =>
+ {
+ try
+ {
+ ApplicationImpl.ChangeInstance (v2);
+
+ v2.Init (null, "v2win");
+
+ booting.Release ();
+
+ Application.Run (); // This will block, but it's on a background thread now
+
+ Application.Shutdown ();
+ }
+ catch (OperationCanceledException)
+ { }
+ catch (Exception ex)
+ {
+ _ex = ex;
+ }
+ finally
+ {
+ ApplicationImpl.ChangeInstance (origApp);
+ }
+ },
+ _cts.Token);
+
+ // Wait for booting to complete with a timeout to avoid hangs
+ if (!booting.WaitAsync (TimeSpan.FromSeconds (5)).Result)
+ {
+ throw new TimeoutException ("Application failed to start within the allotted time.");
+ }
+
+ WaitIteration ();
+ }
+
+ ///
+ /// Stops the application and waits for the background thread to exit.
+ ///
+ public GuiTestContext Stop ()
+ {
+ if (_runTask.IsCompleted)
+ {
+ return this;
+ }
+
+ Application.Invoke (() => Application.RequestStop ());
+
+ // Wait for the application to stop, but give it a 1-second timeout
+ if (!_runTask.Wait (TimeSpan.FromMilliseconds (1000)))
+ {
+ _cts.Cancel ();
+
+ // Timeout occurred, force the task to stop
+ _hardStop.Cancel ();
+
+ throw new TimeoutException ("Application failed to stop within the allotted time.");
+ }
+
+ _cts.Cancel ();
+
+ if (_ex != null)
+ {
+ throw _ex; // Propagate any exception that happened in the background task
+ }
+
+ return this;
+ }
+
+ // Cleanup to avoid state bleed between tests
+ public void Dispose ()
+ {
+ Stop ();
+
+ if (_hardStop.IsCancellationRequested)
+ {
+ throw new (
+ "Application was hard stopped, typically this means it timed out or did not shutdown gracefully. Ensure you call Stop in your test");
+ }
+
+ _hardStop.Cancel ();
+ }
+
+ ///
+ /// Adds the given to the current top level view
+ /// and performs layout.
+ ///
+ ///
+ ///
+ public GuiTestContext Add (View v)
+ {
+ WaitIteration (
+ () =>
+ {
+ Toplevel top = Application.Top ?? throw new ("Top was null so could not add view");
+ top.Add (v);
+ top.Layout ();
+ _lastView = v;
+ });
+
+ return this;
+ }
+
+ public GuiTestContext ResizeConsole (int width, int height)
+ {
+ _output.Size = new (width, height);
+
+ return WaitIteration ();
+ }
+
+ public GuiTestContext ScreenShot (string title, TextWriter writer)
+ {
+ writer.WriteLine (title + ":");
+ var text = Application.ToString ();
+
+ writer.WriteLine (text);
+
+ return WaitIteration ();
+ }
+
+ public GuiTestContext WaitIteration (Action? a = null)
+ {
+ a ??= () => { };
+ var ctsLocal = new CancellationTokenSource ();
+
+ Application.Invoke (
+ () =>
+ {
+ a ();
+ ctsLocal.Cancel ();
+ });
+
+ // Blocks until either the token or the hardStopToken is cancelled.
+ WaitHandle.WaitAny (
+ new []
+ {
+ _cts.Token.WaitHandle,
+ _hardStop.Token.WaitHandle,
+ ctsLocal.Token.WaitHandle
+ });
+
+ return this;
+ }
+
+ public GuiTestContext Assert (AndConstraint be) { return this; }
+
+ public GuiTestContext RightClick (int screenX, int screenY) { return Click (WindowsConsole.ButtonState.Button3Pressed, screenX, screenY); }
+
+ public GuiTestContext LeftClick (int screenX, int screenY) { return Click (WindowsConsole.ButtonState.Button1Pressed, screenX, screenY); }
+
+ private GuiTestContext Click (WindowsConsole.ButtonState btn, int screenX, int screenY)
+ {
+ _winInput.InputBuffer.Enqueue (
+ new()
+ {
+ EventType = WindowsConsole.EventType.Mouse,
+ MouseEvent = new()
+ {
+ ButtonState = btn,
+ MousePosition = new ((short)screenX, (short)screenY)
+ }
+ });
+
+ _winInput.InputBuffer.Enqueue (
+ new()
+ {
+ EventType = WindowsConsole.EventType.Mouse,
+ MouseEvent = new()
+ {
+ ButtonState = WindowsConsole.ButtonState.NoButtonPressed,
+ MousePosition = new ((short)screenX, (short)screenY)
+ }
+ });
+
+ WaitIteration ();
+
+ return this;
+ }
+
+ public GuiTestContext Down ()
+ {
+ _winInput.InputBuffer.Enqueue (
+ new()
+ {
+ EventType = WindowsConsole.EventType.Key,
+ KeyEvent = new()
+ {
+ bKeyDown = true,
+ wRepeatCount = 0,
+ wVirtualKeyCode = ConsoleKeyMapping.VK.DOWN,
+ wVirtualScanCode = 0,
+ UnicodeChar = '\0',
+ dwControlKeyState = WindowsConsole.ControlKeyState.NoControlKeyPressed
+ }
+ });
+
+ _winInput.InputBuffer.Enqueue (
+ new()
+ {
+ EventType = WindowsConsole.EventType.Key,
+ KeyEvent = new()
+ {
+ bKeyDown = false,
+ wRepeatCount = 0,
+ wVirtualKeyCode = ConsoleKeyMapping.VK.DOWN,
+ wVirtualScanCode = 0,
+ UnicodeChar = '\0',
+ dwControlKeyState = WindowsConsole.ControlKeyState.NoControlKeyPressed
+ }
+ });
+
+ WaitIteration ();
+
+ return this;
+ }
+
+ public GuiTestContext Enter ()
+ {
+ _winInput.InputBuffer.Enqueue (
+ new()
+ {
+ EventType = WindowsConsole.EventType.Key,
+ KeyEvent = new()
+ {
+ bKeyDown = true,
+ wRepeatCount = 0,
+ wVirtualKeyCode = ConsoleKeyMapping.VK.RETURN,
+ wVirtualScanCode = 0,
+ UnicodeChar = '\0',
+ dwControlKeyState = WindowsConsole.ControlKeyState.NoControlKeyPressed
+ }
+ });
+
+ _winInput.InputBuffer.Enqueue (
+ new()
+ {
+ EventType = WindowsConsole.EventType.Key,
+ KeyEvent = new()
+ {
+ bKeyDown = false,
+ wRepeatCount = 0,
+ wVirtualKeyCode = ConsoleKeyMapping.VK.RETURN,
+ wVirtualScanCode = 0,
+ UnicodeChar = '\0',
+ dwControlKeyState = WindowsConsole.ControlKeyState.NoControlKeyPressed
+ }
+ });
+
+ WaitIteration ();
+
+ return this;
+ }
+
+ public GuiTestContext WithContextMenu (ContextMenu ctx, MenuBarItem menuItems)
+ {
+ LastView.MouseEvent += (s, e) =>
+ {
+ if (e.Flags.HasFlag (MouseFlags.Button3Clicked))
+ {
+ ctx.Show (menuItems);
+ }
+ };
+
+ return this;
+ }
+
+ public View LastView => _lastView ?? Application.Top ?? throw new ("Could not determine which view to add to");
+}
diff --git a/TerminalGuiFluentAssertions/TerminalGuiFluentAssertions.csproj b/TerminalGuiFluentAssertions/TerminalGuiFluentTesting.csproj
similarity index 100%
rename from TerminalGuiFluentAssertions/TerminalGuiFluentAssertions.csproj
rename to TerminalGuiFluentAssertions/TerminalGuiFluentTesting.csproj
diff --git a/TerminalGuiFluentAssertions/With.cs b/TerminalGuiFluentAssertions/With.cs
new file mode 100644
index 000000000..73165fbf8
--- /dev/null
+++ b/TerminalGuiFluentAssertions/With.cs
@@ -0,0 +1,22 @@
+using Terminal.Gui;
+
+namespace TerminalGuiFluentTesting;
+
+///
+/// Entry point to fluent assertions.
+///
+public static class With
+{
+ ///
+ /// Entrypoint to fluent assertions
+ ///
+ ///
+ ///
+ ///
+ public static GuiTestContext A (int width, int height) where T : Toplevel, new () { return new (width, height); }
+
+ ///
+ /// The global timeout to allow for any given application to run for before shutting down.
+ ///
+ public static TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds (30);
+}
diff --git a/Tests/UnitTests/FluentTests/BasicFluentAssertionTests.cs b/Tests/UnitTests/FluentTests/BasicFluentAssertionTests.cs
index 080b42464..14f24a58e 100644
--- a/Tests/UnitTests/FluentTests/BasicFluentAssertionTests.cs
+++ b/Tests/UnitTests/FluentTests/BasicFluentAssertionTests.cs
@@ -4,7 +4,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FluentAssertions;
-using TerminalGuiFluentAssertions;
+using TerminalGuiFluentTesting;
using Xunit.Abstractions;
namespace UnitTests.FluentTests;
diff --git a/Tests/UnitTests/UnitTests.csproj b/Tests/UnitTests/UnitTests.csproj
index 27dd519fa..9fa09fcad 100644
--- a/Tests/UnitTests/UnitTests.csproj
+++ b/Tests/UnitTests/UnitTests.csproj
@@ -45,7 +45,7 @@
-
+