Skip to content

Commit c7d26e6

Browse files
committed
WIP: Add fluent assertions and prototype the kind of things that should be possible
1 parent e483a03 commit c7d26e6

File tree

4 files changed

+147
-82
lines changed

4 files changed

+147
-82
lines changed

Directory.Packages.props

+43-50
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,45 @@
11
<Project>
2-
<PropertyGroup>
3-
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
4-
</PropertyGroup>
5-
<ItemGroup>
6-
<!-- Enable Nuget Source Link for github -->
7-
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="[8,9)" />
8-
9-
<PackageVersion Include="ColorHelper" Version="[1.8.1,2)" />
10-
<PackageVersion Include="JetBrains.Annotations" Version="[2024.3.0,)" />
11-
<PackageVersion Include="Microsoft.CodeAnalysis" Version="[4.13,5)" />
12-
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="[4.13,5)" />
13-
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="[4.13,5)" />
14-
<PackageVersion Include="Microsoft.Extensions.Logging" Version="[9.0.2,10)" />
15-
<PackageVersion Include="System.IO.Abstractions" Version="[22.0.11,23)" />
16-
<PackageVersion Include="System.Text.Json" Version="[8.0.5,9)" />
17-
<PackageVersion Include="Wcwidth" Version="[2,3)" />
18-
19-
<PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="[1.21.2,2)" />
20-
<PackageVersion Include="Serilog" Version="4.2.0" />
21-
<PackageVersion Include="Serilog.Extensions.Logging" Version="9.0.0" />
22-
<PackageVersion Include="Serilog.Sinks.Debug" Version="3.0.0" />
23-
<PackageVersion Include="Serilog.Sinks.File" Version="6.0.0" />
24-
<PackageVersion Include="SixLabors.ImageSharp" Version="[3.1.7,4)" />
25-
<PackageVersion Include="CsvHelper" Version="[33.0.1,34)" />
26-
<PackageVersion Include="Microsoft.DotNet.PlatformAbstractions" Version="[3.1.6,4)" />
27-
<PackageVersion Include="System.CommandLine" Version="[2.0.0-beta4.22272.1,3)" />
28-
29-
<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" />
30-
31-
<PackageVersion Include="CommunityToolkit.Mvvm" Version="[8.4.0,9)" />
32-
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="[9.0.2,10)" />
33-
<PackageVersion Include="ReactiveUI" Version="[20.1.63,21)" />
34-
<PackageVersion Include="ReactiveMarbles.ObservableEvents.SourceGenerator" Version="[1.3.1,2)" />
35-
<PackageVersion Include="ReactiveUI.SourceGenerators" Version="[2.1.8,3)"/>
36-
37-
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="[17.13,18)" />
38-
<PackageVersion Include="Moq" Version="[4.20.72,5)" />
39-
<PackageVersion Include="ReportGenerator" Version="[5.4.4,6)" />
40-
<PackageVersion Include="TestableIO.System.IO.Abstractions.TestingHelpers" Version="[22.0.11,23)" />
41-
<PackageVersion Include="xunit" Version="[2.9.3,3)" />
42-
<PackageVersion Include="Xunit.Combinatorial" Version="[1.6.24,2)" />
43-
<PackageVersion Include="xunit.runner.visualstudio" Version="[2.8.2,3)"/>
44-
<PackageVersion Include="coverlet.collector" Version="[6.0.4,7)" />
45-
46-
</ItemGroup>
47-
48-
<ItemGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
49-
<PackageVersion Include="Terminal.Gui" Version="2.0.0" />
50-
</ItemGroup>
51-
2+
<PropertyGroup>
3+
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
4+
</PropertyGroup>
5+
<ItemGroup>
6+
<!-- Enable Nuget Source Link for github -->
7+
<PackageVersion Include="FluentAssertions" Version="8.2.0" />
8+
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="[8,9)" />
9+
<PackageVersion Include="ColorHelper" Version="[1.8.1,2)" />
10+
<PackageVersion Include="JetBrains.Annotations" Version="[2024.3.0,)" />
11+
<PackageVersion Include="Microsoft.CodeAnalysis" Version="[4.13,5)" />
12+
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="[4.13,5)" />
13+
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="[4.13,5)" />
14+
<PackageVersion Include="Microsoft.Extensions.Logging" Version="[9.0.2,10)" />
15+
<PackageVersion Include="System.IO.Abstractions" Version="[22.0.11,23)" />
16+
<PackageVersion Include="System.Text.Json" Version="[8.0.5,9)" />
17+
<PackageVersion Include="Wcwidth" Version="[2,3)" />
18+
<PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="[1.21.2,2)" />
19+
<PackageVersion Include="Serilog" Version="4.2.0" />
20+
<PackageVersion Include="Serilog.Extensions.Logging" Version="9.0.0" />
21+
<PackageVersion Include="Serilog.Sinks.Debug" Version="3.0.0" />
22+
<PackageVersion Include="Serilog.Sinks.File" Version="6.0.0" />
23+
<PackageVersion Include="SixLabors.ImageSharp" Version="[3.1.7,4)" />
24+
<PackageVersion Include="CsvHelper" Version="[33.0.1,34)" />
25+
<PackageVersion Include="Microsoft.DotNet.PlatformAbstractions" Version="[3.1.6,4)" />
26+
<PackageVersion Include="System.CommandLine" Version="[2.0.0-beta4.22272.1,3)" />
27+
<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" />
28+
<PackageVersion Include="CommunityToolkit.Mvvm" Version="[8.4.0,9)" />
29+
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="[9.0.2,10)" />
30+
<PackageVersion Include="ReactiveUI" Version="[20.1.63,21)" />
31+
<PackageVersion Include="ReactiveMarbles.ObservableEvents.SourceGenerator" Version="[1.3.1,2)" />
32+
<PackageVersion Include="ReactiveUI.SourceGenerators" Version="[2.1.8,3)" />
33+
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="[17.13,18)" />
34+
<PackageVersion Include="Moq" Version="[4.20.72,5)" />
35+
<PackageVersion Include="ReportGenerator" Version="[5.4.4,6)" />
36+
<PackageVersion Include="TestableIO.System.IO.Abstractions.TestingHelpers" Version="[22.0.11,23)" />
37+
<PackageVersion Include="xunit" Version="[2.9.3,3)" />
38+
<PackageVersion Include="Xunit.Combinatorial" Version="[1.6.24,2)" />
39+
<PackageVersion Include="xunit.runner.visualstudio" Version="[2.8.2,3)" />
40+
<PackageVersion Include="coverlet.collector" Version="[6.0.4,7)" />
41+
</ItemGroup>
42+
<ItemGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
43+
<PackageVersion Include="Terminal.Gui" Version="2.0.0" />
44+
</ItemGroup>
5245
</Project>

TerminalGuiFluentAssertions/Class1.cs

+82-21
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
using System.Collections.Concurrent;
22
using System.Drawing;
3+
using FluentAssertions;
4+
using FluentAssertions.Numeric;
35
using Terminal.Gui;
46

57
namespace TerminalGuiFluentAssertions;
68

7-
class FakeInput<T> : IConsoleInput<T>
9+
class FakeInput<T>(CancellationToken hardStopToken) : IConsoleInput<T>
810
{
911
/// <inheritdoc />
1012
public void Dispose () { }
@@ -15,17 +17,17 @@ public void Initialize (ConcurrentQueue<T> inputBuffer) { }
1517
/// <inheritdoc />
1618
public void Run (CancellationToken token)
1719
{
18-
// Simulate an infinite loop that checks for cancellation
19-
token.WaitHandle.WaitOne (); // Blocks until the token is cancelled
20+
// Blocks until either the token or the hardStopToken is cancelled.
21+
WaitHandle.WaitAny (new [] { token.WaitHandle, hardStopToken.WaitHandle });
2022
}
2123
}
2224

23-
class FakeNetInput : FakeInput<ConsoleKeyInfo>, INetInput
25+
class FakeNetInput (CancellationToken hardStopToken) : FakeInput<ConsoleKeyInfo> (hardStopToken), INetInput
2426
{
2527

2628
}
2729

28-
class FakeWindowsInput : FakeInput<WindowsConsole.InputRecord>, IWindowsInput
30+
class FakeWindowsInput (CancellationToken hardStopToken) : FakeInput<WindowsConsole.InputRecord> (hardStopToken), IWindowsInput
2931
{
3032

3133
}
@@ -87,29 +89,29 @@ public static class With
8789
return new GuiTestContext<T> (width,height);
8890
}
8991
}
90-
public class GuiTestContext<T> where T : Toplevel, new()
92+
public class GuiTestContext<T> : IDisposable where T : Toplevel, new()
9193
{
92-
private readonly CancellationTokenSource _cts;
94+
private readonly CancellationTokenSource _cts = new ();
95+
private readonly CancellationTokenSource _hardStop = new ();
9396
private readonly Task _runTask;
97+
private Exception _ex;
98+
private readonly FakeOutput _output = new ();
9499

95100
internal GuiTestContext (int width, int height)
96101
{
97102
IApplication origApp = ApplicationImpl.Instance;
98103

99-
var netInput = new FakeNetInput ();
100-
var winInput = new FakeWindowsInput ();
101-
var output = new FakeOutput ();
104+
var netInput = new FakeNetInput (_cts.Token);
105+
var winInput = new FakeWindowsInput (_cts.Token);
102106

103-
output.Size = new (width, height);
107+
_output.Size = new (width, height);
104108

105109
var v2 = new ApplicationV2(
106110
() => netInput,
107-
()=>output,
111+
()=>_output,
108112
() => winInput,
109-
() => output);
113+
() => _output);
110114

111-
// Create a cancellation token
112-
_cts = new ();
113115

114116
// Start the application in a background thread
115117
_runTask = Task.Run (() =>
@@ -125,31 +127,90 @@ internal GuiTestContext (int width, int height)
125127
Application.Shutdown ();
126128
}
127129
catch (OperationCanceledException)
130+
{ }
131+
catch (Exception ex)
128132
{
129-
133+
_ex = ex;
130134
}
131135
finally
132136
{
133137
ApplicationImpl.ChangeInstance (origApp);
134138
}
135139
}, _cts.Token);
136-
137-
Application.Shutdown ();
138140
}
139141

140142
/// <summary>
141143
/// Stops the application and waits for the background thread to exit.
142144
/// </summary>
143-
public void Stop ()
145+
public GuiTestContext<T> Stop ()
144146
{
147+
if (_runTask.IsCompleted)
148+
{
149+
return this;
150+
}
151+
152+
Application.Invoke (()=> Application.RequestStop ());
153+
154+
// Wait for the application to stop, but give it a 1-second timeout
155+
if (!_runTask.Wait (TimeSpan.FromMilliseconds (1000)))
156+
{
157+
_cts.Cancel ();
158+
// Timeout occurred, force the task to stop
159+
_hardStop.Cancel ();
160+
throw new TimeoutException ("Application failed to stop within the allotted time.");
161+
}
145162
_cts.Cancel ();
146-
Application.Invoke (()=>Application.RequestStop());
147-
_runTask.Wait (); // Ensure the background thread exits
163+
164+
if (_ex != null)
165+
{
166+
throw _ex; // Propagate any exception that happened in the background task
167+
}
168+
169+
return this;
148170
}
149171

150172
// Cleanup to avoid state bleed between tests
151173
public void Dispose ()
152174
{
175+
Stop ();
176+
_hardStop.Cancel();
177+
}
178+
179+
/// <summary>
180+
/// Adds the given <paramref name="v"/> to the current top level view
181+
/// and performs layout.
182+
/// </summary>
183+
/// <param name="v"></param>
184+
/// <returns></returns>
185+
public GuiTestContext<T> Add (View v)
186+
{
187+
Application.Invoke (
188+
() =>
189+
{
190+
var top = Application.Top ?? throw new Exception("Top was null so could not add view");
191+
top.Add (v);
192+
top.Layout ();
193+
});
194+
195+
return this;
196+
}
197+
198+
public GuiTestContext<T> ResizeConsole (int width, int height)
199+
{
200+
_output.Size = new Size (width,height);
201+
202+
return WaitIteration ();
203+
}
204+
public GuiTestContext<T> WaitIteration ()
205+
{
206+
Application.Invoke (() => { });
207+
208+
return this;
209+
}
210+
211+
public GuiTestContext<T> Assert<T2> (AndConstraint<T2> be)
212+
{
213+
return this;
153214
}
154215
}
155216

TerminalGuiFluentAssertions/TerminalGuiFluentAssertions.csproj

+4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
<Nullable>enable</Nullable>
77
</PropertyGroup>
88

9+
<ItemGroup>
10+
<PackageReference Include="FluentAssertions" />
11+
</ItemGroup>
12+
913
<ItemGroup>
1014
<ProjectReference Include="..\Terminal.Gui\Terminal.Gui.csproj" />
1115
</ItemGroup>

Tests/UnitTests/FluentTests/BasicFluentAssertionTests.cs

+18-11
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Linq;
44
using System.Text;
55
using System.Threading.Tasks;
6+
using FluentAssertions;
67
using TerminalGuiFluentAssertions;
78

89
namespace UnitTests.FluentTests;
@@ -11,25 +12,31 @@ public class BasicFluentAssertionTests
1112
[Fact]
1213
public void GuiTestContext_StartsAndStopsWithoutError ()
1314
{
14-
var context = With.A<Window> (40, 10);
15+
using var context = With.A<Window> (40, 10);
1516

1617
// No actual assertions are needed — if no exceptions are thrown, it's working
1718
context.Stop ();
1819
}
1920

2021
[Fact]
21-
public void Test ()
22+
public void GuiTestContext_ForgotToStop ()
2223
{
23-
var myView = new TextField () { Width = 10 };
24-
25-
24+
using var context = With.A<Window> (40, 10);
25+
}
2626

27-
/*
28-
using var ctx = With.A<Window> (20, 10)
29-
.Add (myView, 3, 2)
30-
.Focus (myView)
31-
.Type ("Hello");
27+
[Fact]
28+
public void TestWindowsResize ()
29+
{
30+
var lbl = new Label ()
31+
{
32+
Width = Dim.Fill ()
33+
};
34+
using var c = With.A<Window> (40, 10)
35+
.Add (lbl )
36+
.Assert (lbl.Frame.Width.Should().Be(40))
37+
.ResizeConsole (20,20)
38+
.Assert (lbl.Frame.Width.Should ().Be (20))
39+
.Stop ();
3240

33-
Assert.Equal ("Hello", myView.Text);*/
3441
}
3542
}

0 commit comments

Comments
 (0)