WIP: Add fluent assertions and prototype the kind of things that should be possible

This commit is contained in:
tznind
2025-03-16 14:50:58 +00:00
committed by Tig
parent c511101dfe
commit 323c624046
4 changed files with 147 additions and 82 deletions

View File

@@ -1,52 +1,45 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<!-- Enable Nuget Source Link for github -->
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="[8,9)" />
<PackageVersion Include="ColorHelper" Version="[1.8.1,2)" />
<PackageVersion Include="JetBrains.Annotations" Version="[2024.3.0,)" />
<PackageVersion Include="Microsoft.CodeAnalysis" Version="[4.13,5)" />
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="[4.13,5)" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="[4.13,5)" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="[9.0.2,10)" />
<PackageVersion Include="System.IO.Abstractions" Version="[22.0.11,23)" />
<PackageVersion Include="System.Text.Json" Version="[8.0.5,9)" />
<PackageVersion Include="Wcwidth" Version="[2,3)" />
<PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="[1.21.2,2)" />
<PackageVersion Include="Serilog" Version="4.2.0" />
<PackageVersion Include="Serilog.Extensions.Logging" Version="9.0.0" />
<PackageVersion Include="Serilog.Sinks.Debug" Version="3.0.0" />
<PackageVersion Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageVersion Include="SixLabors.ImageSharp" Version="[3.1.7,4)" />
<PackageVersion Include="CsvHelper" Version="[33.0.1,34)" />
<PackageVersion Include="Microsoft.DotNet.PlatformAbstractions" Version="[3.1.6,4)" />
<PackageVersion Include="System.CommandLine" Version="[2.0.0-beta4.22272.1,3)" />
<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" />
<PackageVersion Include="CommunityToolkit.Mvvm" Version="[8.4.0,9)" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="[9.0.2,10)" />
<PackageVersion Include="ReactiveUI" Version="[20.1.63,21)" />
<PackageVersion Include="ReactiveMarbles.ObservableEvents.SourceGenerator" Version="[1.3.1,2)" />
<PackageVersion Include="ReactiveUI.SourceGenerators" Version="[2.1.8,3)"/>
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="[17.13,18)" />
<PackageVersion Include="Moq" Version="[4.20.72,5)" />
<PackageVersion Include="ReportGenerator" Version="[5.4.4,6)" />
<PackageVersion Include="TestableIO.System.IO.Abstractions.TestingHelpers" Version="[22.0.11,23)" />
<PackageVersion Include="xunit" Version="[2.9.3,3)" />
<PackageVersion Include="Xunit.Combinatorial" Version="[1.6.24,2)" />
<PackageVersion Include="xunit.runner.visualstudio" Version="[2.8.2,3)"/>
<PackageVersion Include="coverlet.collector" Version="[6.0.4,7)" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<PackageVersion Include="Terminal.Gui" Version="2.0.0" />
</ItemGroup>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<!-- Enable Nuget Source Link for github -->
<PackageVersion Include="FluentAssertions" Version="8.2.0" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="[8,9)" />
<PackageVersion Include="ColorHelper" Version="[1.8.1,2)" />
<PackageVersion Include="JetBrains.Annotations" Version="[2024.3.0,)" />
<PackageVersion Include="Microsoft.CodeAnalysis" Version="[4.13,5)" />
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="[4.13,5)" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="[4.13,5)" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="[9.0.2,10)" />
<PackageVersion Include="System.IO.Abstractions" Version="[22.0.11,23)" />
<PackageVersion Include="System.Text.Json" Version="[8.0.5,9)" />
<PackageVersion Include="Wcwidth" Version="[2,3)" />
<PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="[1.21.2,2)" />
<PackageVersion Include="Serilog" Version="4.2.0" />
<PackageVersion Include="Serilog.Extensions.Logging" Version="9.0.0" />
<PackageVersion Include="Serilog.Sinks.Debug" Version="3.0.0" />
<PackageVersion Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageVersion Include="SixLabors.ImageSharp" Version="[3.1.7,4)" />
<PackageVersion Include="CsvHelper" Version="[33.0.1,34)" />
<PackageVersion Include="Microsoft.DotNet.PlatformAbstractions" Version="[3.1.6,4)" />
<PackageVersion Include="System.CommandLine" Version="[2.0.0-beta4.22272.1,3)" />
<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" />
<PackageVersion Include="CommunityToolkit.Mvvm" Version="[8.4.0,9)" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="[9.0.2,10)" />
<PackageVersion Include="ReactiveUI" Version="[20.1.63,21)" />
<PackageVersion Include="ReactiveMarbles.ObservableEvents.SourceGenerator" Version="[1.3.1,2)" />
<PackageVersion Include="ReactiveUI.SourceGenerators" Version="[2.1.8,3)" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="[17.13,18)" />
<PackageVersion Include="Moq" Version="[4.20.72,5)" />
<PackageVersion Include="ReportGenerator" Version="[5.4.4,6)" />
<PackageVersion Include="TestableIO.System.IO.Abstractions.TestingHelpers" Version="[22.0.11,23)" />
<PackageVersion Include="xunit" Version="[2.9.3,3)" />
<PackageVersion Include="Xunit.Combinatorial" Version="[1.6.24,2)" />
<PackageVersion Include="xunit.runner.visualstudio" Version="[2.8.2,3)" />
<PackageVersion Include="coverlet.collector" Version="[6.0.4,7)" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<PackageVersion Include="Terminal.Gui" Version="2.0.0" />
</ItemGroup>
</Project>

View File

@@ -1,10 +1,12 @@
using System.Collections.Concurrent;
using System.Drawing;
using FluentAssertions;
using FluentAssertions.Numeric;
using Terminal.Gui;
namespace TerminalGuiFluentAssertions;
class FakeInput<T> : IConsoleInput<T>
class FakeInput<T>(CancellationToken hardStopToken) : IConsoleInput<T>
{
/// <inheritdoc />
public void Dispose () { }
@@ -15,17 +17,17 @@ class FakeInput<T> : IConsoleInput<T>
/// <inheritdoc />
public void Run (CancellationToken token)
{
// Simulate an infinite loop that checks for cancellation
token.WaitHandle.WaitOne (); // Blocks until the token is cancelled
// Blocks until either the token or the hardStopToken is cancelled.
WaitHandle.WaitAny (new [] { token.WaitHandle, hardStopToken.WaitHandle });
}
}
class FakeNetInput : FakeInput<ConsoleKeyInfo>, INetInput
class FakeNetInput (CancellationToken hardStopToken) : FakeInput<ConsoleKeyInfo> (hardStopToken), INetInput
{
}
class FakeWindowsInput : FakeInput<WindowsConsole.InputRecord>, IWindowsInput
class FakeWindowsInput (CancellationToken hardStopToken) : FakeInput<WindowsConsole.InputRecord> (hardStopToken), IWindowsInput
{
}
@@ -87,29 +89,29 @@ public static class With
return new GuiTestContext<T> (width,height);
}
}
public class GuiTestContext<T> where T : Toplevel, new()
public class GuiTestContext<T> : IDisposable where T : Toplevel, new()
{
private readonly CancellationTokenSource _cts;
private readonly CancellationTokenSource _cts = new ();
private readonly CancellationTokenSource _hardStop = new ();
private readonly Task _runTask;
private Exception _ex;
private readonly FakeOutput _output = new ();
internal GuiTestContext (int width, int height)
{
IApplication origApp = ApplicationImpl.Instance;
var netInput = new FakeNetInput ();
var winInput = new FakeWindowsInput ();
var output = new FakeOutput ();
var netInput = new FakeNetInput (_cts.Token);
var winInput = new FakeWindowsInput (_cts.Token);
output.Size = new (width, height);
_output.Size = new (width, height);
var v2 = new ApplicationV2(
() => netInput,
()=>output,
()=>_output,
() => winInput,
() => output);
() => _output);
// Create a cancellation token
_cts = new ();
// Start the application in a background thread
_runTask = Task.Run (() =>
@@ -125,31 +127,90 @@ public class GuiTestContext<T> where T : Toplevel, new()
Application.Shutdown ();
}
catch (OperationCanceledException)
{ }
catch (Exception ex)
{
_ex = ex;
}
finally
{
ApplicationImpl.ChangeInstance (origApp);
}
}, _cts.Token);
Application.Shutdown ();
}
/// <summary>
/// Stops the application and waits for the background thread to exit.
/// </summary>
public void Stop ()
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 ();
Application.Invoke (()=>Application.RequestStop());
_runTask.Wait (); // Ensure the background thread exits
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 ();
_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)
{
Application.Invoke (
() =>
{
var top = Application.Top ?? throw new Exception("Top was null so could not add view");
top.Add (v);
top.Layout ();
});
return this;
}
public GuiTestContext<T> ResizeConsole (int width, int height)
{
_output.Size = new Size (width,height);
return WaitIteration ();
}
public GuiTestContext<T> WaitIteration ()
{
Application.Invoke (() => { });
return this;
}
public GuiTestContext<T> Assert<T2> (AndConstraint<T2> be)
{
return this;
}
}

View File

@@ -6,6 +6,10 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Terminal.Gui\Terminal.Gui.csproj" />
</ItemGroup>

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FluentAssertions;
using TerminalGuiFluentAssertions;
namespace UnitTests.FluentTests;
@@ -11,25 +12,31 @@ public class BasicFluentAssertionTests
[Fact]
public void GuiTestContext_StartsAndStopsWithoutError ()
{
var context = With.A<Window> (40, 10);
using var context = With.A<Window> (40, 10);
// No actual assertions are needed — if no exceptions are thrown, it's working
context.Stop ();
}
[Fact]
public void Test ()
public void GuiTestContext_ForgotToStop ()
{
var myView = new TextField () { Width = 10 };
using var context = With.A<Window> (40, 10);
}
[Fact]
public void TestWindowsResize ()
{
var lbl = new Label ()
{
Width = Dim.Fill ()
};
using var c = With.A<Window> (40, 10)
.Add (lbl )
.Assert (lbl.Frame.Width.Should().Be(40))
.ResizeConsole (20,20)
.Assert (lbl.Frame.Width.Should ().Be (20))
.Stop ();
/*
using var ctx = With.A<Window> (20, 10)
.Add (myView, 3, 2)
.Focus (myView)
.Type ("Hello");
Assert.Equal ("Hello", myView.Text);*/
}
}