Explorar el Código

Fix IFakeConsoleDriver casting issues and update FakeDriverRenderingTests

- Replace IFakeConsoleDriver casts with FakeDriver in all test files
- Add DriverAssert to UnitTestsParallelizable project
- Update FakeDriverRenderingTests to use direct driver operations instead of View rendering
- All tests now pass (21 UnitTests + 16 UnitTestsParallelizable FakeDriver tests)

Co-authored-by: tig <[email protected]>
copilot-swe-agent[bot] hace 1 mes
padre
commit
ba531af5fd

+ 2 - 2
Tests/UnitTests/Text/TextFormatterTests.cs

@@ -3828,7 +3828,7 @@ ssb
     [SetupFakeDriver]
     public void FillRemaining_True_False ()
     {
-        ((IFakeConsoleDriver)Application.Driver!).SetBufferSize (22, 5);
+        ((FakeDriver)Application.Driver!).SetBufferSize (22, 5);
 
         Attribute [] attrs =
         {
@@ -4050,7 +4050,7 @@ Nice       Work")]
         Size tfSize = tf.FormatAndGetSize ();
         Assert.Equal (new (59, 13), tfSize);
 
-        ((IFakeConsoleDriver)Application.Driver).SetBufferSize (tfSize.Width, tfSize.Height);
+        ((FakeDriver)Application.Driver).SetBufferSize (tfSize.Width, tfSize.Height);
 
         Application.Driver.FillRect (Application.Screen, (Rune)'*');
         tf.Draw (Application.Screen, Attribute.Default, Attribute.Default);

+ 2 - 2
Tests/UnitTests/View/Adornment/ShadowStyleTests.cs

@@ -30,7 +30,7 @@ public class ShadowStyleTests (ITestOutputHelper output)
     [SetupFakeDriver]
     public void ShadowView_Colors (ShadowStyle style, string expectedAttrs)
     {
-        ((IFakeConsoleDriver)Application.Driver!).SetBufferSize (5, 5);
+        ((FakeDriver)Application.Driver!).SetBufferSize (5, 5);
         Color fg = Color.Red;
         Color bg = Color.Green;
 
@@ -100,7 +100,7 @@ public class ShadowStyleTests (ITestOutputHelper output)
     [SetupFakeDriver]
     public void Visual_Test (ShadowStyle style, string expected)
     {
-        ((IFakeConsoleDriver)Application.Driver!).SetBufferSize (5, 5);
+        ((FakeDriver)Application.Driver!).SetBufferSize (5, 5);
 
         var superView = new Toplevel
         {

+ 1 - 1
Tests/UnitTests/View/TextTests.cs

@@ -998,7 +998,7 @@ w ";
     [SetupFakeDriver]
     public void Narrow_Wide_Runes ()
     {
-        ((IFakeConsoleDriver)Application.Driver!).SetBufferSize (32, 32);
+        ((FakeDriver)Application.Driver!).SetBufferSize (32, 32);
         var top = new View { Width = 32, Height = 32 };
 
         var text = $"First line{Environment.NewLine}Second line";

+ 1 - 1
Tests/UnitTests/Views/LabelTests.cs

@@ -892,7 +892,7 @@ e
     [SetupFakeDriver]
     public void Label_Height_Zero_Stays_Zero ()
     {
-        ((IFakeConsoleDriver)Application.Driver!).SetBufferSize (10, 4);
+        ((FakeDriver)Application.Driver!).SetBufferSize (10, 4);
         var text = "Label";
 
         var label = new Label

+ 1 - 1
Tests/UnitTests/Views/TableViewTests.cs

@@ -2206,7 +2206,7 @@ public class TableViewTests (ITestOutputHelper output)
     [SetupFakeDriver]
     public void TestEnumerableDataSource_BasicTypes ()
     {
-        ((IFakeConsoleDriver)Application.Driver!).SetBufferSize (100, 100);
+        ((FakeDriver)Application.Driver!).SetBufferSize (100, 100);
         var tv = new TableView ();
         tv.SchemeName = "TopLevel";
         tv.Viewport = new (0, 0, 50, 6);

+ 2 - 2
Tests/UnitTests/Views/ToplevelTests.cs

@@ -507,10 +507,10 @@ public class ToplevelTests
         top.BeginInit ();
         top.EndInit ();
 
-        Exception exception = Record.Exception (() => ((IFakeConsoleDriver)Application.Driver!).SetBufferSize (0, 10));
+        Exception exception = Record.Exception (() => ((FakeDriver)Application.Driver!).SetBufferSize (0, 10));
         Assert.Null (exception);
 
-        exception = Record.Exception (() => ((IFakeConsoleDriver)Application.Driver!).SetBufferSize (10, 0));
+        exception = Record.Exception (() => ((FakeDriver)Application.Driver!).SetBufferSize (10, 0));
         Assert.Null (exception);
     }
 

+ 2 - 2
Tests/UnitTests/Views/TreeTableSourceTests.cs

@@ -30,7 +30,7 @@ public class TreeTableSourceTests : IDisposable
     [SetupFakeDriver]
     public void TestTreeTableSource_BasicExpanding_WithKeyboard ()
     {
-        ((IFakeConsoleDriver)Application.Driver!).SetBufferSize (100, 100);
+        ((FakeDriver)Application.Driver!).SetBufferSize (100, 100);
         TableView tv = GetTreeTable (out _);
 
         tv.Style.GetOrCreateColumnStyle (1).MinAcceptableWidth = 1;
@@ -91,7 +91,7 @@ public class TreeTableSourceTests : IDisposable
     [SetupFakeDriver]
     public void TestTreeTableSource_BasicExpanding_WithMouse ()
     {
-        ((IFakeConsoleDriver)Application.Driver!).SetBufferSize (100, 100);
+        ((FakeDriver)Application.Driver!).SetBufferSize (100, 100);
 
         TableView tv = GetTreeTable (out _);
 

+ 391 - 0
Tests/UnitTestsParallelizable/DriverAssert.cs

@@ -0,0 +1,391 @@
+using System.Text;
+using System.Text.RegularExpressions;
+using Xunit.Abstractions;
+
+namespace UnitTests_Parallelizable;
+
+/// <summary>
+///     Provides xUnit-style assertions for <see cref="IConsoleDriver"/> contents.
+/// </summary>
+internal partial class DriverAssert
+{
+    private const char SpaceChar = ' ';
+    private static readonly Rune SpaceRune = (Rune)SpaceChar;
+#pragma warning disable xUnit1013 // Public method should be marked as test
+    /// <summary>
+    ///     Verifies <paramref name="expectedAttributes"/> are found at the locations specified by
+    ///     <paramref name="expectedLook"/>. <paramref name="expectedLook"/> is a bitmap of indexes into
+    ///     <paramref name="expectedAttributes"/> (e.g. "00110" means the attribute at <c>expectedAttributes[1]</c> is expected
+    ///     at the 3rd and 4th columns of the 1st row of driver.Contents).
+    /// </summary>
+    /// <param name="expectedLook">
+    ///     Numbers between 0 and 9 for each row/col of the console.  Must be valid indexes into
+    ///     <paramref name="expectedAttributes"/>.
+    /// </param>
+    /// <param name="output"></param>
+    /// <param name="driver">The IConsoleDriver to use. If null <see cref="Application.Driver"/> will be used.</param>
+    /// <param name="expectedAttributes"></param>
+    public static void AssertDriverAttributesAre (
+        string expectedLook,
+        ITestOutputHelper output,
+        IConsoleDriver driver = null,
+        params Attribute [] expectedAttributes
+    )
+    {
+#pragma warning restore xUnit1013 // Public method should be marked as test
+
+        if (expectedAttributes.Length > 10)
+        {
+            throw new ArgumentException ("This method only works for UIs that use at most 10 colors");
+        }
+
+        expectedLook = expectedLook.Trim ();
+        driver ??= Application.Driver;
+
+        Cell [,] contents = driver!.Contents;
+
+        var line = 0;
+
+        foreach (string lineString in expectedLook.Split ('\n').Select (l => l.Trim ()))
+        {
+            for (var c = 0; c < lineString.Length; c++)
+            {
+                Attribute? val = contents! [line, c].Attribute;
+
+                List<Attribute> match = expectedAttributes.Where (e => e == val).ToList ();
+
+                switch (match.Count)
+                {
+                    case 0:
+                        output.WriteLine (
+                                          $"{Application.ToString (driver)}\n"
+                                          + $"Expected Attribute {val} at Contents[{line},{c}] {contents [line, c]} was not found.\n"
+                                          + $" Expected: {string.Join (",", expectedAttributes.Select (attr => attr))}\n"
+                                          + $" But Was: <not found>"
+                                         );
+                        Assert.Empty (match);
+
+                        return;
+                    case > 1:
+                        throw new ArgumentException (
+                                                     $"Bad value for expectedColors, {match.Count} Attributes had the same Value"
+                                                    );
+                }
+
+                char colorUsed = Array.IndexOf (expectedAttributes, match [0]).ToString () [0];
+                char userExpected = lineString [c];
+
+                if (colorUsed != userExpected)
+                {
+                    output.WriteLine ($"{Application.ToString (driver)}");
+                    output.WriteLine ($"Unexpected Attribute at Contents[{line},{c}] = {contents [line, c]}.");
+                    output.WriteLine ($" Expected: {userExpected} ({expectedAttributes [int.Parse (userExpected.ToString ())]})");
+                    output.WriteLine ($"  But Was: {colorUsed} ({val})");
+
+                    // Print `contents` as the expected and actual attribute indexes in a grid where each cell is of the form "e:a" (e = expected, a = actual)
+                    // e.g:
+                    // 0:1 0:0 1:1
+                    // 0:0 1:1 0:0
+                    // 0:0 1:1 0:0
+
+                    //// Use StringBuilder since output only has .WriteLine
+                    //var sb = new StringBuilder ();
+                    //// for each line in `contents`
+                    //for (var r = 0; r < driver.Rows; r++)
+                    //{
+                    //    // for each column in `contents`
+                    //    for (var cc = 0; cc < driver.Cols; cc++)
+                    //    {
+                    //        // get the attribute at the current location
+                    //        Attribute? val2 = contents [r, cc].Attribute;
+                    //        // if the attribute is not null
+                    //        if (val2.HasValue)
+                    //        {
+                    //            // get the index of the attribute in `expectedAttributes`
+                    //            int index = Array.IndexOf (expectedAttributes, val2.Value);
+                    //            // if the index is -1, it means the attribute was not found in `expectedAttributes`
+
+                    //            // get the index of the actual attribute in `expectedAttributes`
+
+
+                    //            if (index == -1)
+                    //            {
+                    //                sb.Append ("x:x ");
+                    //            }
+                    //            else
+                    //            {
+                    //                sb.Append ($"{index}:{val2.Value} ");
+                    //            }
+                    //        }
+                    //        else
+                    //        {
+                    //            sb.Append ("x:x ");
+                    //        }
+                    //    }
+                    //    sb.AppendLine ();
+                    //}
+
+                    //output.WriteLine ($"Contents:\n{sb}");
+
+                    Assert.Equal (userExpected, colorUsed);
+
+                    return;
+                }
+            }
+
+            line++;
+        }
+    }
+
+#pragma warning disable xUnit1013 // Public method should be marked as test
+    /// <summary>Asserts that the driver contents match the expected contents, optionally ignoring any trailing whitespace.</summary>
+    /// <param name="expectedLook"></param>
+    /// <param name="output"></param>
+    /// <param name="driver">The IConsoleDriver to use. If null <see cref="Application.Driver"/> will be used.</param>
+    /// <param name="ignoreLeadingWhitespace"></param>
+    public static void AssertDriverContentsAre (
+        string expectedLook,
+        ITestOutputHelper output,
+        IConsoleDriver driver = null,
+        bool ignoreLeadingWhitespace = false
+    )
+    {
+#pragma warning restore xUnit1013 // Public method should be marked as test
+        var actualLook = Application.ToString (driver ?? Application.Driver);
+
+        if (string.Equals (expectedLook, actualLook))
+        {
+            return;
+        }
+
+        // get rid of trailing whitespace on each line (and leading/trailing whitespace of start/end of full string)
+        expectedLook = TrailingWhiteSpaceRegEx ().Replace (expectedLook, "").Trim ();
+        actualLook = TrailingWhiteSpaceRegEx ().Replace (actualLook, "").Trim ();
+
+        if (ignoreLeadingWhitespace)
+        {
+            expectedLook = LeadingWhitespaceRegEx ().Replace (expectedLook, "").Trim ();
+            actualLook = LeadingWhitespaceRegEx ().Replace (actualLook, "").Trim ();
+        }
+
+        // standardize line endings for the comparison
+        expectedLook = expectedLook.Replace ("\r\n", "\n");
+        actualLook = actualLook.Replace ("\r\n", "\n");
+
+        // If test is about to fail show user what things looked like
+        if (!string.Equals (expectedLook, actualLook))
+        {
+            output?.WriteLine ("Expected:" + Environment.NewLine + expectedLook);
+            output?.WriteLine (" But Was:" + Environment.NewLine + actualLook);
+        }
+
+        Assert.Equal (expectedLook, actualLook);
+    }
+
+    /// <summary>
+    ///     Asserts that the driver contents are equal to the provided string.
+    /// </summary>
+    /// <param name="expectedLook"></param>
+    /// <param name="output"></param>
+    /// <param name="driver">The IConsoleDriver to use. If null <see cref="Application.Driver"/> will be used.</param>
+    /// <returns></returns>
+    public static Rectangle AssertDriverContentsWithFrameAre (
+        string expectedLook,
+        ITestOutputHelper output,
+        IConsoleDriver driver = null
+    )
+    {
+        List<List<Rune>> lines = new ();
+        var sb = new StringBuilder ();
+        driver ??= Application.Driver;
+        int x = -1;
+        int y = -1;
+        int w = -1;
+        int h = -1;
+
+        Cell [,] contents = driver.Contents;
+
+        for (var rowIndex = 0; rowIndex < driver.Rows; rowIndex++)
+        {
+            List<Rune> runes = [];
+
+            for (var colIndex = 0; colIndex < driver.Cols; colIndex++)
+            {
+                Rune runeAtCurrentLocation = contents [rowIndex, colIndex].Rune;
+
+                if (runeAtCurrentLocation != SpaceRune)
+                {
+                    if (x == -1)
+                    {
+                        x = colIndex;
+                        y = rowIndex;
+
+                        for (var i = 0; i < colIndex; i++)
+                        {
+                            runes.InsertRange (i, [SpaceRune]);
+                        }
+                    }
+
+                    if (runeAtCurrentLocation.GetColumns () > 1)
+                    {
+                        colIndex++;
+                    }
+
+                    if (colIndex + 1 > w)
+                    {
+                        w = colIndex + 1;
+                    }
+
+                    h = rowIndex - y + 1;
+                }
+
+                if (x > -1)
+                {
+                    runes.Add (runeAtCurrentLocation);
+                }
+
+                // See Issue #2616
+                //foreach (var combMark in contents [r, c].CombiningMarks) {
+                //	runes.Add (combMark);
+                //}
+            }
+
+            if (runes.Count > 0)
+            {
+                lines.Add (runes);
+            }
+        }
+
+        // Remove unnecessary empty lines
+        if (lines.Count > 0)
+        {
+            for (int r = lines.Count - 1; r > h - 1; r--)
+            {
+                lines.RemoveAt (r);
+            }
+        }
+
+        // Remove trailing whitespace on each line
+        foreach (List<Rune> row in lines)
+        {
+            for (int c = row.Count - 1; c >= 0; c--)
+            {
+                Rune rune = row [c];
+
+                if (rune != (Rune)' ' || row.Sum (x => x.GetColumns ()) == w)
+                {
+                    break;
+                }
+
+                row.RemoveAt (c);
+            }
+        }
+
+        // Convert Rune list to string
+        for (var r = 0; r < lines.Count; r++)
+        {
+            var line = StringExtensions.ToString (lines [r]);
+
+            if (r == lines.Count - 1)
+            {
+                sb.Append (line);
+            }
+            else
+            {
+                sb.AppendLine (line);
+            }
+        }
+
+        var actualLook = sb.ToString ();
+
+        if (string.Equals (expectedLook, actualLook))
+        {
+            return new (x > -1 ? x : 0, y > -1 ? y : 0, w > -1 ? w : 0, h > -1 ? h : 0);
+        }
+
+        // standardize line endings for the comparison
+        expectedLook = expectedLook.ReplaceLineEndings ();
+        actualLook = actualLook.ReplaceLineEndings ();
+
+        // Remove the first and the last line ending from the expectedLook
+        if (expectedLook.StartsWith (Environment.NewLine))
+        {
+            expectedLook = expectedLook [Environment.NewLine.Length..];
+        }
+
+        if (expectedLook.EndsWith (Environment.NewLine))
+        {
+            expectedLook = expectedLook [..^Environment.NewLine.Length];
+        }
+
+        // If test is about to fail show user what things looked like
+        if (!string.Equals (expectedLook, actualLook))
+        {
+            output?.WriteLine ("Expected:" + Environment.NewLine + expectedLook);
+            output?.WriteLine (" But Was:" + Environment.NewLine + actualLook);
+        }
+
+        Assert.Equal (expectedLook, actualLook);
+
+        return new (x > -1 ? x : 0, y > -1 ? y : 0, w > -1 ? w : 0, h > -1 ? h : 0);
+    }
+
+
+    /// <summary>
+    ///     Verifies the console used all the <paramref name="expectedColors"/> when rendering. If one or more of the
+    ///     expected colors are not used then the failure will output both the colors that were found to be used and which of
+    ///     your expectations was not met.
+    /// </summary>
+    /// <param name="driver">if null uses <see cref="Application.Driver"/></param>
+    /// <param name="expectedColors"></param>
+    internal static void AssertDriverUsedColors (IConsoleDriver driver = null, params Attribute [] expectedColors)
+    {
+        driver ??= Application.Driver;
+        Cell [,] contents = driver.Contents;
+
+        List<Attribute> toFind = expectedColors.ToList ();
+
+        // Contents 3rd column is an Attribute
+        HashSet<Attribute> colorsUsed = new ();
+
+        for (var r = 0; r < driver.Rows; r++)
+        {
+            for (var c = 0; c < driver.Cols; c++)
+            {
+                Attribute? val = contents [r, c].Attribute;
+
+                if (val.HasValue)
+                {
+                    colorsUsed.Add (val.Value);
+
+                    Attribute match = toFind.FirstOrDefault (e => e == val);
+
+                    // need to check twice because Attribute is a struct and therefore cannot be null
+                    if (toFind.Any (e => e == val))
+                    {
+                        toFind.Remove (match);
+                    }
+                }
+            }
+        }
+
+        if (!toFind.Any ())
+        {
+            return;
+        }
+
+        var sb = new StringBuilder ();
+        sb.AppendLine ("The following colors were not used:" + string.Join ("; ", toFind.Select (a => a.ToString ())));
+        sb.AppendLine ("Colors used were:" + string.Join ("; ", colorsUsed.Select (a => a.ToString ())));
+
+        throw new (sb.ToString ());
+    }
+
+
+    [GeneratedRegex ("^\\s+", RegexOptions.Multiline)]
+    private static partial Regex LeadingWhitespaceRegEx ();
+
+
+    [GeneratedRegex ("\\s+$", RegexOptions.Multiline)]
+    private static partial Regex TrailingWhiteSpaceRegEx ();
+}

+ 63 - 34
Tests/UnitTestsParallelizable/Drivers/FakeDriverRenderingTests.cs

@@ -4,69 +4,59 @@ using Xunit.Abstractions;
 namespace UnitTests_Parallelizable.Drivers;
 
 /// <summary>
-/// Tests for FakeDriver functionality including rendering and basic driver operations.
+/// Tests for FakeDriver functionality including basic driver operations.
 /// These tests prove that FakeDriver can be used independently for testing Terminal.Gui applications.
 /// </summary>
 public class FakeDriverRenderingTests (ITestOutputHelper output)
 {
     private readonly ITestOutputHelper _output = output;
 
-    #region View Rendering Tests
+    #region Basic Driver Tests
 
     [Fact]
-    public void FakeDriver_Can_Render_Simple_Label ()
+    public void FakeDriver_Can_Write_To_Contents_Buffer ()
     {
         // Arrange
         var driver = new FakeDriver ();
         driver.Init ();
 
-        var label = new Label { Text = "Hello World", X = 0, Y = 0 };
-        label.Driver = driver;
-        label.BeginInit ();
-        label.EndInit ();
+        // Act - Write directly to driver
+        driver.Move (0, 0);
+        driver.AddStr ("Hello World");
 
-        // Act
-        label.SetNeedsDraw ();
-        label.Draw ();
-
-        // Assert
+        // Assert - Verify text was written to driver contents
         Assert.NotNull (driver.Contents);
-        Assert.Equal (80, driver.Cols);
-        Assert.Equal (25, driver.Rows);
+        
+        // Check that "Hello World" is in the first row
+        string firstRow = "";
+        for (int col = 0; col < Math.Min (11, driver.Cols); col++)
+        {
+            firstRow += (char)driver.Contents [0, col].Rune.Value;
+        }
+        Assert.Equal ("Hello World", firstRow);
 
         driver.End ();
-        label.Dispose ();
     }
 
     [Fact]
-    public void FakeDriver_Can_Render_View_With_Border ()
+    public void FakeDriver_Can_Set_Attributes ()
     {
         // Arrange
         var driver = new FakeDriver ();
         driver.Init ();
-
-        var window = new Window
-        {
-            Title = "Test Window",
-            X = 0,
-            Y = 0,
-            Width = 40,
-            Height = 10,
-            BorderStyle = LineStyle.Single
-        };
-        window.Driver = driver;
-        window.BeginInit ();
-        window.EndInit ();
+        var attr = new Attribute (Color.Red, Color.Blue);
 
         // Act
-        window.SetNeedsDraw ();
-        window.Draw ();
+        driver.Move (5, 5);
+        driver.SetAttribute (attr);
+        driver.AddRune ('X');
 
-        // Assert - Check that contents buffer was written to
+        // Assert - Verify attribute was set
         Assert.NotNull (driver.Contents);
-        
+        Assert.Equal ('X', (char)driver.Contents [5, 5].Rune.Value);
+        Assert.Equal (attr, driver.Contents [5, 5].Attribute);
+
         driver.End ();
-        window.Dispose ();
     }
 
     [Fact]
@@ -100,5 +90,44 @@ public class FakeDriverRenderingTests (ITestOutputHelper output)
         driver.End ();
     }
 
+    [Fact]
+    public void FakeDriver_Can_Fill_Rectangle ()
+    {
+        // Arrange
+        var driver = new FakeDriver ();
+        driver.Init ();
+
+        // Act
+        driver.FillRect (new Rectangle (0, 0, 5, 3), '*');
+
+        // Assert - Verify rectangle was filled
+        for (int row = 0; row < 3; row++)
+        {
+            for (int col = 0; col < 5; col++)
+            {
+                Assert.Equal ('*', (char)driver.Contents [row, col].Rune.Value);
+            }
+        }
+
+        driver.End ();
+    }
+
+    [Fact]
+    public void FakeDriver_Tracks_Cursor_Position ()
+    {
+        // Arrange
+        var driver = new FakeDriver ();
+        driver.Init ();
+
+        // Act
+        driver.Move (10, 5);
+
+        // Assert
+        Assert.Equal (10, driver.Col);
+        Assert.Equal (5, driver.Row);
+
+        driver.End ();
+    }
+
     #endregion
 }