|
|
@@ -1,14 +1,14 @@
|
|
|
using System.Text;
|
|
|
-using Xunit.Abstractions;
|
|
|
|
|
|
namespace DriverTests;
|
|
|
|
|
|
/// <summary>
|
|
|
/// Tests for https://github.com/gui-cs/Terminal.Gui/issues/4466.
|
|
|
/// These tests validate that FillRect properly handles wide characters when overlapping existing content.
|
|
|
-/// Specifically, they ensure that wide characters are properly invalidated and replaced when a MessageBox border or similar UI element is drawn over them, preventing visual corruption.
|
|
|
+/// Specifically, they ensure that wide characters are properly invalidated and replaced when a MessageBox border or
|
|
|
+/// similar UI element is drawn over them, preventing visual corruption.
|
|
|
/// </summary>
|
|
|
-public class OutputBufferWideCharTests (ITestOutputHelper output)
|
|
|
+public class OutputBufferWideCharTests
|
|
|
{
|
|
|
/// <summary>
|
|
|
/// Tests that FillRect properly invalidates wide characters when overwriting them.
|
|
|
@@ -100,7 +100,7 @@ public class OutputBufferWideCharTests (ITestOutputHelper output)
|
|
|
// With the fix: The original wide character at col 2 should be invalidated
|
|
|
// because we're overwriting its second column
|
|
|
Assert.True (buffer.Contents [1, 2].IsDirty, "Wide char at col 2 should be invalidated when its second column is overwritten");
|
|
|
- Assert.Equal (buffer.Contents [1, 2].Grapheme, Rune.ReplacementChar.ToString ());
|
|
|
+ Assert.Equal (buffer.Contents [1, 2].Grapheme, Glyphs.WideGlyphReplacement.ToString ());
|
|
|
|
|
|
Assert.Equal ("│", buffer.Contents [1, 3].Grapheme);
|
|
|
Assert.True (buffer.Contents [1, 3].IsDirty);
|
|
|
@@ -154,7 +154,7 @@ public class OutputBufferWideCharTests (ITestOutputHelper output)
|
|
|
|
|
|
// The second character "好" at col 7 had its second column overwritten
|
|
|
// so it should be replaced with replacement char
|
|
|
- Assert.Equal (buffer.Contents [3, 7].Grapheme, Rune.ReplacementChar.ToString ());
|
|
|
+ Assert.Equal (buffer.Contents [3, 7].Grapheme, Glyphs.WideGlyphReplacement.ToString ());
|
|
|
Assert.True (buffer.Contents [3, 7].IsDirty, "Invalidated wide char should be marked dirty");
|
|
|
|
|
|
// The border should be drawn at col 8
|
|
|
@@ -356,4 +356,189 @@ public class OutputBufferWideCharTests (ITestOutputHelper output)
|
|
|
buffer.Contents [2, 7].IsDirty,
|
|
|
"Adjacent cell should be dirty after wide char replacement");
|
|
|
}
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Tests the edge case where a wide character's first column is outside the clip region
|
|
|
+ /// but the second column is inside.
|
|
|
+ /// IMPORTANT: This test documents that the code path in WriteWideGrapheme where:
|
|
|
+ /// - !Clip.Contains(col, row) is true (first column outside)
|
|
|
+ /// - Clip.Contains(col + 1, row) is true (second column inside)
|
|
|
+ /// is CURRENTLY UNREACHABLE because IsValidLocation checks Clip.Contains(col, row) and
|
|
|
+ /// returns false before WriteWideGrapheme is called. This test verifies the current behavior
|
|
|
+ /// (nothing is written when first column is outside clip).
|
|
|
+ /// If the behavior should change to write the second column with a replacement character,
|
|
|
+ /// the logic in IsValidLocation or AddGrapheme needs to be modified.
|
|
|
+ /// </summary>
|
|
|
+ [Fact]
|
|
|
+ [Trait ("Category", "Output")]
|
|
|
+ public void AddStr_WideChar_FirstColumnOutsideClip_SecondColumnInside_CurrentBehavior ()
|
|
|
+ {
|
|
|
+ // Arrange
|
|
|
+ OutputBufferImpl buffer = new ()
|
|
|
+ {
|
|
|
+ Rows = 5,
|
|
|
+ Cols = 10,
|
|
|
+ CurrentAttribute = new (Color.White, Color.Black)
|
|
|
+ };
|
|
|
+
|
|
|
+ // Set custom replacement characters to verify they're being used
|
|
|
+ Rune customColumn1Replacement = new ('◄');
|
|
|
+ Rune customColumn2Replacement = new ('►');
|
|
|
+ buffer.SetWideGlyphReplacement (customColumn1Replacement);
|
|
|
+
|
|
|
+ // Set clip region that starts at column 3 (odd column)
|
|
|
+ // This creates a scenario where col 2 is outside clip, but col 3 is inside
|
|
|
+ buffer.Clip = new (new (3, 1, 5, 3));
|
|
|
+
|
|
|
+ // Clear initial contents to ensure clean state
|
|
|
+ for (var r = 0; r < buffer.Rows; r++)
|
|
|
+ {
|
|
|
+ for (var c = 0; c < buffer.Cols; c++)
|
|
|
+ {
|
|
|
+ buffer.Contents! [r, c].IsDirty = false;
|
|
|
+ buffer.Contents [r, c].Grapheme = " ";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Act - Try to draw a wide character at column 2
|
|
|
+ // Column 2 is outside clip, but column 3 is inside clip
|
|
|
+ buffer.Move (2, 1);
|
|
|
+ buffer.AddStr ("你"); // Chinese character "you", 2 columns wide
|
|
|
+
|
|
|
+ // Assert
|
|
|
+ // CURRENT BEHAVIOR: IsValidLocation returns false when col 2 is outside clip,
|
|
|
+ // so NOTHING is written - neither column 2 nor column 3
|
|
|
+ Assert.Equal (" ", buffer.Contents! [1, 2].Grapheme);
|
|
|
+ Assert.False (buffer.Contents [1, 2].IsDirty, "Cell outside clip should not be marked dirty");
|
|
|
+
|
|
|
+ // Column 3 is also not written because IsValidLocation returned false
|
|
|
+ // The code path in WriteWideGrapheme that would write the replacement char
|
|
|
+ // to column 3 is never reached
|
|
|
+ Assert.Equal (" ", buffer.Contents [1, 3].Grapheme);
|
|
|
+
|
|
|
+ Assert.False (
|
|
|
+ buffer.Contents [1, 3].IsDirty,
|
|
|
+ "Currently, second column is not written when first column is outside clip");
|
|
|
+
|
|
|
+ // Verify Col has been advanced by only 1 (not by the wide character width)
|
|
|
+ // because the grapheme was not validated/processed when IsValidLocation returned false
|
|
|
+ Assert.Equal (3, buffer.Col);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Tests the complementary case: wide character's second column is outside clip
|
|
|
+ /// but first column is inside. This should use the column 1 replacement character.
|
|
|
+ /// </summary>
|
|
|
+ [Fact]
|
|
|
+ [Trait ("Category", "Output")]
|
|
|
+ public void AddStr_WideChar_SecondColumnOutsideClip_FirstColumnInside_UsesColumn1Replacement ()
|
|
|
+ {
|
|
|
+ // Arrange
|
|
|
+ OutputBufferImpl buffer = new ()
|
|
|
+ {
|
|
|
+ Rows = 5,
|
|
|
+ Cols = 10,
|
|
|
+ CurrentAttribute = new (Color.White, Color.Black)
|
|
|
+ };
|
|
|
+
|
|
|
+ // Set custom replacement characters
|
|
|
+ Rune customColumn1Replacement = new ('◄');
|
|
|
+ Rune customColumn2Replacement = new ('►');
|
|
|
+ buffer.SetWideGlyphReplacement (customColumn1Replacement);
|
|
|
+
|
|
|
+ // Set clip region that ends at column 6 (even column)
|
|
|
+ // This creates a scenario where col 5 is inside, but col 6 is outside
|
|
|
+ buffer.Clip = new (new (0, 1, 6, 3));
|
|
|
+
|
|
|
+ // Clear initial contents
|
|
|
+ for (var r = 0; r < buffer.Rows; r++)
|
|
|
+ {
|
|
|
+ for (var c = 0; c < buffer.Cols; c++)
|
|
|
+ {
|
|
|
+ buffer.Contents! [r, c].IsDirty = false;
|
|
|
+ buffer.Contents [r, c].Grapheme = " ";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Act - Try to draw a wide character at column 5
|
|
|
+ // Column 5 is inside clip, but column 6 is outside clip
|
|
|
+ buffer.Move (5, 1);
|
|
|
+ buffer.AddStr ("好"); // Chinese character, 2 columns wide
|
|
|
+
|
|
|
+ // Assert
|
|
|
+ // The first column (col 5) is inside clip but second column (6) is outside
|
|
|
+ // Should use column 1 replacement char to indicate it can't fit
|
|
|
+ Assert.Equal (
|
|
|
+ customColumn1Replacement.ToString (),
|
|
|
+ buffer.Contents! [1, 5].Grapheme);
|
|
|
+
|
|
|
+ Assert.True (
|
|
|
+ buffer.Contents [1, 5].IsDirty,
|
|
|
+ "First column should be marked dirty with replacement char when second column is clipped");
|
|
|
+
|
|
|
+ // The second column is outside clip boundaries entirely
|
|
|
+ Assert.Equal (" ", buffer.Contents [1, 6].Grapheme);
|
|
|
+ Assert.False (buffer.Contents [1, 6].IsDirty, "Cell outside clip should not be modified");
|
|
|
+
|
|
|
+ // Verify Col has been advanced by 2 (wide character width)
|
|
|
+ Assert.Equal (7, buffer.Col);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Tests that when both columns of a wide character are inside the clip,
|
|
|
+ /// the character is drawn normally without replacement characters.
|
|
|
+ /// </summary>
|
|
|
+ [Fact]
|
|
|
+ [Trait ("Category", "Output")]
|
|
|
+ public void AddStr_WideChar_BothColumnsInsideClip_DrawsNormally ()
|
|
|
+ {
|
|
|
+ // Arrange
|
|
|
+ OutputBufferImpl buffer = new ()
|
|
|
+ {
|
|
|
+ Rows = 5,
|
|
|
+ Cols = 10,
|
|
|
+ CurrentAttribute = new (Color.White, Color.Black)
|
|
|
+ };
|
|
|
+
|
|
|
+ // Set custom replacement characters (should NOT be used in this case)
|
|
|
+ Rune customColumn1Replacement = new ('◄');
|
|
|
+ Rune customColumn2Replacement = new ('►');
|
|
|
+ buffer.SetWideGlyphReplacement (customColumn1Replacement);
|
|
|
+
|
|
|
+ // Set clip region that includes columns 2-7
|
|
|
+ buffer.Clip = new (new (2, 1, 6, 3));
|
|
|
+
|
|
|
+ // Clear initial contents
|
|
|
+ for (var r = 0; r < buffer.Rows; r++)
|
|
|
+ {
|
|
|
+ for (var c = 0; c < buffer.Cols; c++)
|
|
|
+ {
|
|
|
+ buffer.Contents! [r, c].IsDirty = false;
|
|
|
+ buffer.Contents [r, c].Grapheme = " ";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Act - Draw a wide character at column 4 (both 4 and 5 are inside clip)
|
|
|
+ buffer.Move (4, 1);
|
|
|
+ buffer.AddStr ("山"); // Chinese character "mountain", 2 columns wide
|
|
|
+
|
|
|
+ // Assert
|
|
|
+ // Both columns are inside clip, so the wide character should be drawn normally
|
|
|
+ Assert.Equal ("山", buffer.Contents! [1, 4].Grapheme);
|
|
|
+ Assert.True (buffer.Contents [1, 4].IsDirty, "First column should be marked dirty");
|
|
|
+
|
|
|
+ // The second column should NOT be marked dirty by WriteWideGrapheme
|
|
|
+ // The wide glyph naturally renders across both columns without modifying column N+1
|
|
|
+ // See: https://github.com/gui-cs/Terminal.Gui/issues/4258
|
|
|
+ Assert.False (
|
|
|
+ buffer.Contents [1, 5].IsDirty,
|
|
|
+ "Adjacent cell should NOT be marked dirty when writing wide char (see #4258)");
|
|
|
+
|
|
|
+ // Verify no replacement characters were used
|
|
|
+ Assert.NotEqual (customColumn1Replacement.ToString (), buffer.Contents [1, 4].Grapheme);
|
|
|
+ Assert.NotEqual (customColumn2Replacement.ToString (), buffer.Contents [1, 5].Grapheme);
|
|
|
+
|
|
|
+ // Verify Col has been advanced by 2
|
|
|
+ Assert.Equal (6, buffer.Col);
|
|
|
+ }
|
|
|
}
|