2
0
Эх сурвалжийг харах

Rename to TerminalGuiFluentTesting

tznind 4 сар өмнө
parent
commit
79f289ce8f

+ 1 - 1
Terminal.Gui/Terminal.Gui.csproj

@@ -87,7 +87,7 @@
         <InternalsVisibleTo Include="StressTests" />
         <InternalsVisibleTo Include="IntegrationTests" />
         <InternalsVisibleTo Include="TerminalGuiDesigner" />
-        <InternalsVisibleTo Include="TerminalGuiFluentAssertions" />
+        <InternalsVisibleTo Include="TerminalGuiFluentTesting" />
         <InternalsVisibleTo Include="DynamicProxyGenAssembly2" />
     </ItemGroup>
     <!-- =================================================================== -->

+ 1 - 1
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

+ 0 - 404
TerminalGuiFluentAssertions/Class1.cs

@@ -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<T> : IConsoleInput<T>
-{
-    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);
-    }
-    /// <inheritdoc />
-    public void Dispose () { }
-
-    /// <inheritdoc />
-    public void Initialize (ConcurrentQueue<T> inputBuffer) { InputBuffer = inputBuffer;}
-
-    public ConcurrentQueue<T> InputBuffer { get; set; }
-
-    /// <inheritdoc />
-    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<ConsoleKeyInfo> (hardStopToken), INetInput
-{
-
-}
-
-class FakeWindowsInput (CancellationToken hardStopToken) : FakeInput<WindowsConsole.InputRecord> (hardStopToken), IWindowsInput
-{
-
-}
-
-class FakeOutput : IConsoleOutput
-{
-    public IOutputBuffer LastBuffer { get; set; }
-    public Size Size { get; set; }
-
-    /// <inheritdoc />
-    public void Dispose ()
-    {
-
-    }
-
-    /// <inheritdoc />
-    public void Write (ReadOnlySpan<char> text)
-    {
-
-    }
-
-    /// <inheritdoc />
-    public void Write (IOutputBuffer buffer)
-    {
-        LastBuffer = buffer;
-    }
-
-
-    /// <inheritdoc />
-    public Size GetWindowSize ()
-    {
-        return Size;
-    }
-
-    /// <inheritdoc />
-    public void SetCursorVisibility (CursorVisibility visibility)
-    {
-
-    }
-
-    /// <inheritdoc />
-    public void SetCursorPosition (int col, int row)
-    {
-
-    }
-
-}
-/// <summary>
-/// Entry point to fluent assertions.
-/// </summary>
-public static class With
-{
-    /// <summary>
-    /// Entrypoint to fluent assertions
-    /// </summary>
-    /// <param name="width"></param>
-    /// <param name="height"></param>
-    /// <returns></returns>
-    public static GuiTestContext<T> A<T> (int width, int height) where T : Toplevel, new ()
-    {
-        return new (width,height);
-    }
-
-    /// <summary>
-    /// The global timeout to allow for any given application to run for before shutting down.
-    /// </summary>
-    public static TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds (30);
-}
-public class GuiTestContext<T> : 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<T> (); // 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 ();
-    }
-
-    /// <summary>
-    /// Stops the application and waits for the background thread to exit.
-    /// </summary>
-    public GuiTestContext<T> 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();
-    }
-
-    /// <summary>
-    /// Adds the given <paramref name="v"/> to the current top level view
-    /// and performs layout.
-    /// </summary>
-    /// <param name="v"></param>
-    /// <returns></returns>
-    public GuiTestContext<T> 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<T> ResizeConsole (int width, int height)
-    {
-        _output.Size = new Size (width,height);
-
-        return WaitIteration ();
-    }
-    public GuiTestContext<T> ScreenShot (string title, TextWriter writer)
-    {
-        writer.WriteLine(title +":");
-        var text = Application.ToString ();
-
-        writer.WriteLine(text);
-
-        return WaitIteration ();
-    }
-    public GuiTestContext<T> 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<T> Assert<T2> (AndConstraint<T2> be)
-    {
-        return this;
-    }
-
-    public GuiTestContext<T> RightClick (int screenX, int screenY)
-    {
-        return Click (WindowsConsole.ButtonState.Button3Pressed,screenX, screenY);
-    }
-
-    public GuiTestContext<T> LeftClick (int screenX, int screenY)
-    {
-        return Click (WindowsConsole.ButtonState.Button1Pressed, screenX, screenY);
-    }
-
-    private GuiTestContext<T> 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<T> 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<T> 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<T> 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");
-
-
-}
-

+ 59 - 0
TerminalGuiFluentAssertions/ClassDiagram1.cd

@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ClassDiagram MajorVersion="1" MinorVersion="1">
+  <Class Name="TerminalGuiFluentTesting.With" Collapsed="true">
+    <Position X="0.5" Y="1.5" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAIAAAAAA=</HashCode>
+      <FileName>With.cs</FileName>
+    </TypeIdentifier>
+  </Class>
+  <Class Name="TerminalGuiFluentTesting.FakeInput&lt;T&gt;" Collapsed="true">
+    <Position X="7.5" Y="1.5" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AQAAAAAAACAAAQEAAAAgAAAAAAAAAAAAAAAAAAAAAAI=</HashCode>
+      <FileName>FakeInput.cs</FileName>
+    </TypeIdentifier>
+    <Lollipop Position="0.2" />
+  </Class>
+  <Class Name="TerminalGuiFluentTesting.FakeNetInput" Collapsed="true">
+    <Position X="8.75" Y="2.75" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
+      <FileName>FakeNetInput.cs</FileName>
+    </TypeIdentifier>
+    <Lollipop Position="0.2" />
+  </Class>
+  <Class Name="TerminalGuiFluentTesting.FakeWindowsInput" Collapsed="true">
+    <Position X="6.5" Y="2.75" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
+      <FileName>FakeWindowsInput.cs</FileName>
+    </TypeIdentifier>
+    <Lollipop Position="0.2" />
+  </Class>
+  <Class Name="TerminalGuiFluentTesting.FakeOutput" Collapsed="true" BaseTypeListCollapsed="true">
+    <Position X="6" Y="0.5" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAAgCAAgAAAAAAAAAAAAAAAQAAAMAAAAAEAAAA=</HashCode>
+      <FileName>FakeOutput.cs</FileName>
+    </TypeIdentifier>
+    <Lollipop Position="0.2" />
+  </Class>
+  <Class Name="TerminalGuiFluentTesting.GuiTestContext&lt;T&gt;" BaseTypeListCollapsed="true">
+    <Position X="2.75" Y="0.5" Width="2.25" />
+    <Compartments>
+      <Compartment Name="Fields" Collapsed="true" />
+    </Compartments>
+    <TypeIdentifier>
+      <HashCode>ABJAAAIAACBAAQAAgIAAAAAgABIEgAQAIAAIBACAIgA=</HashCode>
+      <FileName>GuiTestContext.cs</FileName>
+    </TypeIdentifier>
+    <ShowAsAssociation>
+      <Field Name="_output" />
+      <Field Name="_winInput" />
+      <Field Name="_netInput" />
+    </ShowAsAssociation>
+    <Lollipop Position="0.2" />
+  </Class>
+  <Font Name="Segoe UI" Size="9" />
+</ClassDiagram>

+ 34 - 0
TerminalGuiFluentAssertions/FakeInput.cs

@@ -0,0 +1,34 @@
+using System.Collections.Concurrent;
+using Terminal.Gui;
+
+namespace TerminalGuiFluentTesting;
+
+internal class FakeInput<T> : IConsoleInput<T>
+{
+    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);
+    }
+
+    /// <inheritdoc/>
+    public void Dispose () { }
+
+    /// <inheritdoc/>
+    public void Initialize (ConcurrentQueue<T> inputBuffer) { InputBuffer = inputBuffer; }
+
+    public ConcurrentQueue<T> InputBuffer { get; set; }
+
+    /// <inheritdoc/>
+    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 });
+    }
+}

+ 6 - 0
TerminalGuiFluentAssertions/FakeNetInput.cs

@@ -0,0 +1,6 @@
+using Terminal.Gui;
+
+namespace TerminalGuiFluentTesting;
+
+internal class FakeNetInput (CancellationToken hardStopToken) : FakeInput<ConsoleKeyInfo> (hardStopToken), INetInput
+{ }

+ 28 - 0
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; }
+
+    /// <inheritdoc/>
+    public void Dispose () { }
+
+    /// <inheritdoc/>
+    public void Write (ReadOnlySpan<char> text) { }
+
+    /// <inheritdoc/>
+    public void Write (IOutputBuffer buffer) { LastBuffer = buffer; }
+
+    /// <inheritdoc/>
+    public Size GetWindowSize () { return Size; }
+
+    /// <inheritdoc/>
+    public void SetCursorVisibility (CursorVisibility visibility) { }
+
+    /// <inheritdoc/>
+    public void SetCursorPosition (int col, int row) { }
+}

+ 6 - 0
TerminalGuiFluentAssertions/FakeWindowsInput.cs

@@ -0,0 +1,6 @@
+using Terminal.Gui;
+
+namespace TerminalGuiFluentTesting;
+
+internal class FakeWindowsInput (CancellationToken hardStopToken) : FakeInput<WindowsConsole.InputRecord> (hardStopToken), IWindowsInput
+{ }

+ 304 - 0
TerminalGuiFluentAssertions/GuiTestContext.cs

@@ -0,0 +1,304 @@
+using FluentAssertions;
+using Terminal.Gui;
+using Terminal.Gui.ConsoleDrivers;
+
+namespace TerminalGuiFluentTesting;
+
+public class GuiTestContext<T> : 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<T> (); // 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 ();
+    }
+
+    /// <summary>
+    ///     Stops the application and waits for the background thread to exit.
+    /// </summary>
+    public GuiTestContext<T> 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 ();
+    }
+
+    /// <summary>
+    ///     Adds the given <paramref name="v"/> to the current top level view
+    ///     and performs layout.
+    /// </summary>
+    /// <param name="v"></param>
+    /// <returns></returns>
+    public GuiTestContext<T> 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<T> ResizeConsole (int width, int height)
+    {
+        _output.Size = new (width, height);
+
+        return WaitIteration ();
+    }
+
+    public GuiTestContext<T> ScreenShot (string title, TextWriter writer)
+    {
+        writer.WriteLine (title + ":");
+        var text = Application.ToString ();
+
+        writer.WriteLine (text);
+
+        return WaitIteration ();
+    }
+
+    public GuiTestContext<T> 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<T> Assert<T2> (AndConstraint<T2> be) { return this; }
+
+    public GuiTestContext<T> RightClick (int screenX, int screenY) { return Click (WindowsConsole.ButtonState.Button3Pressed, screenX, screenY); }
+
+    public GuiTestContext<T> LeftClick (int screenX, int screenY) { return Click (WindowsConsole.ButtonState.Button1Pressed, screenX, screenY); }
+
+    private GuiTestContext<T> 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<T> 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<T> 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<T> 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");
+}

+ 0 - 0
TerminalGuiFluentAssertions/TerminalGuiFluentAssertions.csproj → TerminalGuiFluentAssertions/TerminalGuiFluentTesting.csproj


+ 22 - 0
TerminalGuiFluentAssertions/With.cs

@@ -0,0 +1,22 @@
+using Terminal.Gui;
+
+namespace TerminalGuiFluentTesting;
+
+/// <summary>
+///     Entry point to fluent assertions.
+/// </summary>
+public static class With
+{
+    /// <summary>
+    ///     Entrypoint to fluent assertions
+    /// </summary>
+    /// <param name="width"></param>
+    /// <param name="height"></param>
+    /// <returns></returns>
+    public static GuiTestContext<T> A<T> (int width, int height) where T : Toplevel, new () { return new (width, height); }
+
+    /// <summary>
+    ///     The global timeout to allow for any given application to run for before shutting down.
+    /// </summary>
+    public static TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds (30);
+}

+ 1 - 1
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;

+ 1 - 1
Tests/UnitTests/UnitTests.csproj

@@ -45,7 +45,7 @@
 	</ItemGroup>
 	<ItemGroup>
 		<ProjectReference Include="..\..\Terminal.Gui\Terminal.Gui.csproj" />
-		<ProjectReference Include="..\..\TerminalGuiFluentAssertions\TerminalGuiFluentAssertions.csproj" />
+		<ProjectReference Include="..\..\TerminalGuiFluentTesting\TerminalGuiFluentTesting.csproj" />
 		<ProjectReference Include="..\..\UICatalog\UICatalog.csproj" />
 	</ItemGroup>
 	<ItemGroup>