Rename to TerminalGuiFluentTesting

This commit is contained in:
tznind
2025-03-16 20:03:06 +00:00
committed by Tig
parent 491140a39a
commit 79f289ce8f
13 changed files with 463 additions and 408 deletions

View File

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

View File

@@ -62,7 +62,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StressTests", "Tests\Stress
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests.Parallelizable", "Tests\UnitTestsParallelizable\UnitTests.Parallelizable.csproj", "{DE780834-190A-8277-51FD-750CC666E82D}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests.Parallelizable", "Tests\UnitTestsParallelizable\UnitTests.Parallelizable.csproj", "{DE780834-190A-8277-51FD-750CC666E82D}"
EndProject 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 EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution

View File

@@ -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");
}

View File

@@ -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>

View File

@@ -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 });
}
}

View File

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

View File

@@ -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) { }
}

View File

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

View File

@@ -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");
}

View File

@@ -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);
}

View File

@@ -4,7 +4,7 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using FluentAssertions; using FluentAssertions;
using TerminalGuiFluentAssertions; using TerminalGuiFluentTesting;
using Xunit.Abstractions; using Xunit.Abstractions;
namespace UnitTests.FluentTests; namespace UnitTests.FluentTests;

View File

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