Browse Source

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

tznind 4 months ago
parent
commit
323c624046

+ 43 - 50
Directory.Packages.props

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

+ 82 - 21
TerminalGuiFluentAssertions/Class1.cs

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

+ 4 - 0
TerminalGuiFluentAssertions/TerminalGuiFluentAssertions.csproj

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

+ 18 - 11
Tests/UnitTests/FluentTests/BasicFluentAssertionTests.cs

@@ -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);
+    }
 
-        /*
-        using var ctx = With.A<Window> (20, 10)
-                            .Add (myView, 3, 2)
-                            .Focus (myView)
-                            .Type ("Hello");
+    [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 ();
 
-        Assert.Equal ("Hello", myView.Text);*/
     }
 }