mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
Fixes #4387. Runes should not be used on a cell, but rather should use a single grapheme rendering 1 or 2 columns (#4388)
* Fixes #4382. StringExtensions.GetColumns method should only return the total text width and not the sum of all runes width * Trying to fix unit test error * Update StringExtensions.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Resolving merge conflicts * Prevents Runes throwing if Grapheme is null * Add unit test to prove that null and empty string doesn't not throws anything. * Fix unit test failure * Fix IsValidLocation for wide graphemes * Add more combining * Prevent set invalid graphemes * Fix unit tests * Grapheme doesn't support invalid code points like lone surrogates * Fixes more unit tests * Fix unit test * Seems all test are fixed now * Adjust CharMap scenario with graphemes * Upgrade Wcwidth to version 4.0.0 * Reformat * Trying fix CheckDefaultState assertion * Revert "Trying fix CheckDefaultState assertion" This reverts commitc9b46b796a. * Forgot to include driver.End in the test * Reapply "Trying fix CheckDefaultState assertion" This reverts commit1060ac9b63. * Remove ToString * Fix merge errors * Change to conditional expression * Assertion to prove that no exception throws during cell initialization. * Remove unnecessary assignment * Remove assignment to end * Replace string concatenation with 'StringBuilder'. * Replace more string concatenation with 'StringBuilder' * Remove redundant call to 'ToString' because Rune cast to a String object. * Replace foreach loop with Sum linq --------- Co-authored-by: Tig <tig@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -88,7 +88,7 @@ public class RuneTests
|
||||
1
|
||||
)] // the letters 법 join to form the Korean word for "rice:" U+BC95 법 (read from top left to bottom right)
|
||||
[InlineData ("\U0001F468\u200D\U0001F469\u200D\U0001F467", "👨👩👧", 8, 2, 8)] // Man, Woman and Girl emoji.
|
||||
[InlineData ("\u0915\u093f", "कि", 2, 1, 2)] // Hindi कि with DEVANAGARI LETTER KA and DEVANAGARI VOWEL SIGN I
|
||||
//[InlineData ("\u0915\u093f", "कि", 2, 2, 2)] // Hindi कि with DEVANAGARI LETTER KA and DEVANAGARI VOWEL SIGN I
|
||||
[InlineData (
|
||||
"\u0e4d\u0e32",
|
||||
"ํา",
|
||||
@@ -213,7 +213,7 @@ public class RuneTests
|
||||
[InlineData (
|
||||
'\u1161',
|
||||
"ᅡ",
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
3
|
||||
)] // ᅡ Hangul Jungseong A - Unicode Hangul Jamo for join with column width equal to 0 alone.
|
||||
@@ -231,7 +231,7 @@ public class RuneTests
|
||||
)] // ䷀Hexagram For The Creative Heaven - U+4dc0 - https://github.com/microsoft/terminal/blob/main/src/types/unicode_width_overrides.xml
|
||||
// See https://github.com/microsoft/terminal/issues/19389
|
||||
|
||||
[InlineData ('\ud7b0', "ힰ", 1, 1, 3)] // ힰ ┤Hangul Jungseong O-Yeo - ힰ U+d7b0')]
|
||||
[InlineData ('\ud7b0', "ힰ", 0, 1, 3)] // ힰ ┤Hangul Jungseong O-Yeo - ힰ U+d7b0')]
|
||||
[InlineData ('\uf61e', "", 1, 1, 3)] // Private Use Area
|
||||
[InlineData ('\u23f0', "⏰", 2, 1, 3)] // Alarm Clock - ⏰ U+23f0
|
||||
[InlineData ('\u1100', "ᄀ", 2, 1, 3)] // ᄀ Hangul Choseong Kiyeok
|
||||
@@ -365,6 +365,42 @@ public class RuneTests
|
||||
[InlineData ('\ud801')]
|
||||
public void Rune_Exceptions_Integers (int code) { Assert.Throws<ArgumentOutOfRangeException> (() => new Rune (code)); }
|
||||
|
||||
[Theory]
|
||||
// Control characters (should be mapped to Control Pictures)
|
||||
[InlineData ('\u0000', 0x2400)] // NULL → ␀
|
||||
[InlineData ('\u0009', 0x2409)] // TAB → ␉
|
||||
[InlineData ('\u000A', 0x240A)] // LF → ␊
|
||||
[InlineData ('\u000D', 0x240D)] // CR → ␍
|
||||
|
||||
// Printable characters (should remain unchanged)
|
||||
[InlineData ('A', 'A')]
|
||||
[InlineData (' ', ' ')]
|
||||
[InlineData ('~', '~')]
|
||||
public void MakePrintable_ReturnsExpected (char inputChar, int expectedCodePoint)
|
||||
{
|
||||
// Arrange
|
||||
Rune input = new Rune (inputChar);
|
||||
|
||||
// Act
|
||||
Rune result = input.MakePrintable ();
|
||||
|
||||
// Assert
|
||||
Assert.Equal (expectedCodePoint, result.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MakePrintable_SupplementaryRune_RemainsUnchanged ()
|
||||
{
|
||||
// Arrange: supplementary character outside BMP (not a control)
|
||||
Rune input = new Rune (0x1F600); // 😀 grinning face emoji
|
||||
|
||||
// Act
|
||||
Rune result = input.MakePrintable ();
|
||||
|
||||
// Assert
|
||||
Assert.Equal (input.Value, result.Value);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData (new [] { '\ud799', '\udc21' })]
|
||||
public void Rune_Exceptions_Utf16_Encode (char [] code)
|
||||
@@ -954,11 +990,9 @@ public class RuneTests
|
||||
Assert.Equal (runeCount, us.GetRuneCount ());
|
||||
Assert.Equal (stringCount, s.Length);
|
||||
|
||||
TextElementEnumerator enumerator = StringInfo.GetTextElementEnumerator (s);
|
||||
|
||||
var textElementCount = 0;
|
||||
|
||||
while (enumerator.MoveNext ())
|
||||
foreach (string _ in GraphemeHelper.GetGraphemes (s))
|
||||
{
|
||||
textElementCount++; // For versions prior to Net5.0 the StringInfo class might handle some grapheme clusters incorrectly.
|
||||
}
|
||||
@@ -1064,4 +1098,95 @@ public class RuneTests
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData (0x0041, new byte [] { 0x41 })] // 'A', ASCII
|
||||
[InlineData (0x00E9, new byte [] { 0xC3, 0xA9 })] // 'é', 2-byte UTF-8
|
||||
[InlineData (0x20AC, new byte [] { 0xE2, 0x82, 0xAC })] // '€', 3-byte UTF-8
|
||||
[InlineData (0x1F600, new byte [] { 0xF0, 0x9F, 0x98, 0x80 })] // 😀 emoji, 4-byte UTF-8
|
||||
public void Encode_WritesExpectedBytes (int codePoint, byte [] expectedBytes)
|
||||
{
|
||||
// Arrange
|
||||
Rune rune = new Rune (codePoint);
|
||||
byte [] buffer = new byte [10]; // extra space
|
||||
for (int i = 0; i < buffer.Length; i++)
|
||||
{
|
||||
buffer [i] = 0xFF;
|
||||
}
|
||||
|
||||
// Act
|
||||
int written = rune.Encode (buffer);
|
||||
|
||||
// Assert
|
||||
Assert.Equal (expectedBytes.Length, written);
|
||||
for (int i = 0; i < written; i++)
|
||||
{
|
||||
Assert.Equal (expectedBytes [i], buffer [i]);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Encode_WithStartAndCount_WritesPartialBytes ()
|
||||
{
|
||||
// Arrange: U+1F600 😀 (4 bytes)
|
||||
Rune rune = new Rune (0x1F600);
|
||||
byte [] buffer = new byte [10];
|
||||
for (int i = 0; i < buffer.Length; i++)
|
||||
{
|
||||
buffer [i] = 0xFF;
|
||||
}
|
||||
|
||||
// Act: write starting at index 2, limit count to 2 bytes
|
||||
int written = rune.Encode (buffer, start: 2, count: 2);
|
||||
|
||||
// Assert
|
||||
Assert.Equal (2, written);
|
||||
// Original UTF-8 bytes: F0 9F 98 80
|
||||
Assert.Equal (0xF0, buffer [2]);
|
||||
Assert.Equal (0x9F, buffer [3]);
|
||||
// Remaining buffer untouched
|
||||
Assert.Equal (0xFF, buffer [0]);
|
||||
Assert.Equal (0xFF, buffer [1]);
|
||||
Assert.Equal (0xFF, buffer [4]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Encode_WithCountGreaterThanRuneBytes_WritesAllBytes ()
|
||||
{
|
||||
// Arrange: é → C3 A9
|
||||
Rune rune = new Rune ('é');
|
||||
byte [] buffer = new byte [10];
|
||||
for (int i = 0; i < buffer.Length; i++)
|
||||
{
|
||||
buffer [i] = 0xFF;
|
||||
}
|
||||
|
||||
// Act: count larger than needed
|
||||
int written = rune.Encode (buffer, start: 1, count: 10);
|
||||
|
||||
// Assert
|
||||
Assert.Equal (2, written);
|
||||
Assert.Equal (0xC3, buffer [1]);
|
||||
Assert.Equal (0xA9, buffer [2]);
|
||||
Assert.Equal (0xFF, buffer [3]); // next byte untouched
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Encode_ZeroCount_WritesNothing ()
|
||||
{
|
||||
Rune rune = new Rune ('A');
|
||||
byte [] buffer = new byte [5];
|
||||
for (int i = 0; i < buffer.Length; i++)
|
||||
{
|
||||
buffer [i] = 0xFF;
|
||||
}
|
||||
|
||||
int written = rune.Encode (buffer, start: 0, count: 0);
|
||||
|
||||
Assert.Equal (0, written);
|
||||
foreach (var b in buffer)
|
||||
{
|
||||
Assert.Equal (0xFF, b); // buffer untouched
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,13 @@
|
||||
|
||||
public class StringTests
|
||||
{
|
||||
[Fact]
|
||||
public void TestGetColumns_Null ()
|
||||
{
|
||||
string? str = null;
|
||||
Assert.Equal (0, str!.GetColumns ());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestGetColumns_Empty ()
|
||||
{
|
||||
@@ -11,6 +18,20 @@ public class StringTests
|
||||
Assert.Equal (0, str.GetColumns ());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestGetColumns_SingleRune ()
|
||||
{
|
||||
var str = "a";
|
||||
Assert.Equal (1, str.GetColumns ());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestGetColumns_Zero_Width ()
|
||||
{
|
||||
var str = "\u200D";
|
||||
Assert.Equal (0, str.GetColumns ());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData ("a", 1)]
|
||||
[InlineData ("á", 1)]
|
||||
@@ -30,39 +51,37 @@ public class StringTests
|
||||
|
||||
// Test known wide codepoints
|
||||
[Theory]
|
||||
[InlineData ("🙂", 2)]
|
||||
[InlineData ("a🙂", 3)]
|
||||
[InlineData ("🙂a", 3)]
|
||||
[InlineData ("👨👩👦👦", 2)]
|
||||
[InlineData ("👨👩👦👦🙂", 4)]
|
||||
[InlineData ("👨👩👦👦🙂a", 5)]
|
||||
[InlineData ("👨👩👦👦a🙂", 5)]
|
||||
[InlineData ("👨👩👦👦👨👩👦👦", 4)]
|
||||
[InlineData ("山", 2)] // The character for "mountain" in Chinese/Japanese/Korean (山), Unicode U+5C71
|
||||
[InlineData ("山🙂", 4)] // The character for "mountain" in Chinese/Japanese/Korean (山), Unicode U+5C71
|
||||
//[InlineData ("\ufe20\ufe21", 2)] // Combining Ligature Left Half ︠ - U+fe20 -https://github.com/microsoft/terminal/blob/main/src/types/unicode_width_overrides.xml
|
||||
// // Combining Ligature Right Half - U+fe21 -https://github.com/microsoft/terminal/blob/main/src/types/unicode_width_overrides.xml
|
||||
public void TestGetColumns_MultiRune_WideBMP (string str, int expected) { Assert.Equal (expected, str.GetColumns ()); }
|
||||
|
||||
[Fact]
|
||||
public void TestGetColumns_Null ()
|
||||
[InlineData ("🙂", 2, 1, 2)]
|
||||
[InlineData ("a🙂", 3, 2, 3)]
|
||||
[InlineData ("🙂a", 3, 2, 3)]
|
||||
[InlineData ("👨👩👦👦", 8, 1, 2)]
|
||||
[InlineData ("👨👩👦👦🙂", 10, 2, 4)]
|
||||
[InlineData ("👨👩👦👦🙂a", 11, 3, 5)]
|
||||
[InlineData ("👨👩👦👦a🙂", 11, 3, 5)]
|
||||
[InlineData ("👨👩👦👦👨👩👦👦", 16, 2, 4)]
|
||||
[InlineData ("าำ", 2, 1, 2)] // า U+0E32 - THAI CHARACTER SARA AA with ำ U+0E33 - THAI CHARACTER SARA AM
|
||||
[InlineData ("山", 2, 1, 2)] // The character for "mountain" in Chinese/Japanese/Korean (山), Unicode U+5C71
|
||||
[InlineData ("山🙂", 4, 2, 4)] // The character for "mountain" in Chinese/Japanese/Korean (山), Unicode U+5C71
|
||||
[InlineData ("a\ufe20e\ufe21", 2, 2, 2)] // Combining Ligature Left Half ︠ - U+fe20 -https://github.com/microsoft/terminal/blob/main/src/types/unicode_width_overrides.xml
|
||||
// Combining Ligature Right Half - U+fe21 -https://github.com/microsoft/terminal/blob/main/src/types/unicode_width_overrides.xml
|
||||
//[InlineData ("क", 1, 1, 1)] // क U+0915 Devanagari Letter Ka
|
||||
//[InlineData ("ि", 1, 1, 1)] // U+093F Devanagari Vowel Sign I ि (i-kar).
|
||||
//[InlineData ("कि", 2, 1, 2)] // "कि" is U+0915 for the base consonant "क" with U+093F for the vowel sign "ि" (i-kar).
|
||||
[InlineData ("ᄀ", 2, 1, 2)] // ᄀ U+1100 HANGUL CHOSEONG KIYEOK (consonant)
|
||||
[InlineData ("ᅡ", 0, 1, 0)] // ᅡ U+1161 HANGUL JUNGSEONG A (vowel)
|
||||
[InlineData ("가", 2, 1, 2)] // ᄀ U+1100 HANGUL CHOSEONG KIYEOK (consonant) with ᅡ U+1161 HANGUL JUNGSEONG A (vowel)
|
||||
[InlineData ("ᄒ", 2, 1, 2)] // ᄒ U+1112 Hangul Choseong Hieuh
|
||||
[InlineData ("ᅵ", 0, 1, 0)] // ᅵ U+1175 Hangul Jungseong I
|
||||
[InlineData ("ᇂ", 0, 1, 0)] // ᇂ U+11C2 Hangul Jongseong Hieuh
|
||||
[InlineData ("힣", 2, 1, 2)] // ᄒ (choseong h) + ᅵ (jungseong i) + ᇂ (jongseong h)
|
||||
[InlineData ("ힰ", 0, 1, 0)] // U+D7B0 ힰ Hangul Jungseong O-Yeo
|
||||
[InlineData ("ᄀힰ", 2, 1, 2)] // ᄀ U+1100 HANGUL CHOSEONG KIYEOK (consonant) with U+D7B0 ힰ Hangul Jungseong O-Yeo
|
||||
//[InlineData ("षि", 2, 1, 2)] // U+0937 ष DEVANAGARI LETTER SSA with U+093F ि COMBINING DEVANAGARI VOWEL SIGN I
|
||||
public void TestGetColumns_MultiRune_WideBMP_Graphemes (string str, int expectedRunesWidth, int expectedGraphemesCount, int expectedWidth)
|
||||
{
|
||||
string? str = null;
|
||||
Assert.Equal (0, str!.GetColumns ());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestGetColumns_SingleRune ()
|
||||
{
|
||||
var str = "a";
|
||||
Assert.Equal (1, str.GetColumns ());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestGetColumns_Zero_Width ()
|
||||
{
|
||||
var str = "\u200D";
|
||||
Assert.Equal (0, str.GetColumns ());
|
||||
Assert.Equal (expectedRunesWidth, str.EnumerateRunes ().Sum (r => r.GetColumns ()));
|
||||
Assert.Equal (expectedGraphemesCount, GraphemeHelper.GetGraphemes (str).ToArray ().Length);
|
||||
Assert.Equal (expectedWidth, str.GetColumns ());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -70,13 +89,124 @@ public class StringTests
|
||||
[InlineData ("")]
|
||||
public void TestGetColumns_Does_Not_Throws_With_Null_And_Empty_String (string? text)
|
||||
{
|
||||
if (text is null)
|
||||
// ReSharper disable once InvokeAsExtensionMethod
|
||||
Assert.Equal (0, StringExtensions.GetColumns (text!));
|
||||
}
|
||||
|
||||
public class ReadOnlySpanExtensionsTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData ("12345", true)] // all ASCII digits
|
||||
[InlineData ("0", true)] // single ASCII digit
|
||||
[InlineData ("", false)] // empty span
|
||||
[InlineData ("12a45", false)] // contains a letter
|
||||
[InlineData ("123", false)] // full-width Unicode digits (not ASCII)
|
||||
[InlineData ("12 34", false)] // contains space
|
||||
[InlineData ("١٢٣", false)] // Arabic-Indic digits
|
||||
public void IsAllAsciiDigits_WorksAsExpected (string input, bool expected)
|
||||
{
|
||||
Assert.Equal (0, StringExtensions.GetColumns (text!));
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal (0, text.GetColumns ());
|
||||
// Arrange
|
||||
ReadOnlySpan<char> span = input.AsSpan ();
|
||||
|
||||
// Act
|
||||
bool result = span.IsAllAsciiDigits ();
|
||||
|
||||
// Assert
|
||||
Assert.Equal (expected, result);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData ("0", true)]
|
||||
[InlineData ("9", true)]
|
||||
[InlineData ("A", true)]
|
||||
[InlineData ("F", true)]
|
||||
[InlineData ("a", true)]
|
||||
[InlineData ("f", true)]
|
||||
[InlineData ("123ABC", true)]
|
||||
[InlineData ("abcdef", true)]
|
||||
[InlineData ("G", false)] // 'G' not hex
|
||||
[InlineData ("Z9", false)] // 'Z' not hex
|
||||
[InlineData ("12 34", false)] // space not hex
|
||||
[InlineData ("", false)] // empty string
|
||||
[InlineData ("123", false)] // full-width digits, not ASCII
|
||||
[InlineData ("0xFF", false)] // includes 'x'
|
||||
public void IsAllAsciiHexDigits_ReturnsExpected (string input, bool expected)
|
||||
{
|
||||
// Arrange
|
||||
ReadOnlySpan<char> span = input.AsSpan ();
|
||||
|
||||
// Act
|
||||
bool result = span.IsAllAsciiHexDigits ();
|
||||
|
||||
// Assert
|
||||
Assert.Equal (expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData (nameof (GetStringConcatCases))]
|
||||
public void ToString_ReturnsExpected (IEnumerable<string> input, string expected)
|
||||
{
|
||||
// Act
|
||||
string result = StringExtensions.ToString (input);
|
||||
|
||||
// Assert
|
||||
Assert.Equal (expected, result);
|
||||
}
|
||||
|
||||
public static IEnumerable<object []> GetStringConcatCases ()
|
||||
{
|
||||
yield return [new string [] { }, string.Empty]; // Empty sequence
|
||||
yield return [new [] { "" }, string.Empty]; // Single empty string
|
||||
yield return [new [] { "A" }, "A"]; // Single element
|
||||
yield return [new [] { "A", "B" }, "AB"]; // Simple concatenation
|
||||
yield return [new [] { "Hello", " ", "World" }, "Hello World"]; // Multiple parts
|
||||
yield return [new [] { "123", "456", "789" }, "123456789"]; // Numeric strings
|
||||
yield return [new [] { "👩", "🧒" }, "👩🧒"]; // Grapheme sequence
|
||||
yield return [new [] { "α", "β", "γ" }, "αβγ"]; // Unicode letters
|
||||
yield return [new [] { "A", null, "B" }, "AB"]; // Null ignored by string.Concat
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData ("", false)] // Empty string
|
||||
[InlineData ("A", false)] // Single BMP character
|
||||
[InlineData ("AB", false)] // Two BMP chars, not a surrogate pair
|
||||
[InlineData ("👩", true)] // Single emoji surrogate pair (U+1F469)
|
||||
[InlineData ("🧒", true)] // Another emoji surrogate pair (U+1F9D2)
|
||||
[InlineData ("𐍈", true)] // Gothic letter hwair (U+10348)
|
||||
[InlineData ("A👩", false)] // One BMP + one surrogate half
|
||||
[InlineData ("👩", false)] // Surrogate pair + ZWJ (length != 2)
|
||||
public void IsSurrogatePair_ReturnsExpected (string input, bool expected)
|
||||
{
|
||||
// Act
|
||||
bool result = input.IsSurrogatePair ();
|
||||
|
||||
// Assert
|
||||
Assert.Equal (expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
// Control characters (should be replaced with the "Control Pictures" block)
|
||||
[InlineData ("\u0000", "\u2400")] // NULL → ␀
|
||||
[InlineData ("\u0009", "\u2409")] // TAB → ␉
|
||||
[InlineData ("\u000A", "\u240A")] // LF → ␊
|
||||
[InlineData ("\u000D", "\u240D")] // CR → ␍
|
||||
|
||||
// Printable characters (should remain unchanged)
|
||||
[InlineData ("A", "A")]
|
||||
[InlineData (" ", " ")]
|
||||
[InlineData ("~", "~")]
|
||||
|
||||
// Multi-character string (should return unchanged)
|
||||
[InlineData ("AB", "AB")]
|
||||
[InlineData ("Hello", "Hello")]
|
||||
[InlineData ("\u0009A", "\u0009A")] // includes a control char, but length > 1
|
||||
public void MakePrintable_ReturnsExpected (string input, string expected)
|
||||
{
|
||||
// Act
|
||||
string result = input.MakePrintable ();
|
||||
|
||||
// Assert
|
||||
Assert.Equal (expected, result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -658,5 +658,32 @@ Nice Work")]
|
||||
DriverAssert.AssertDriverContentsWithFrameAre (expectedDraw, output, driver);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData ("\U0001F468\u200D\U0001F469\u200D\U0001F467\u200D\U0001F466", 2, 1, TextDirection.LeftRight_TopBottom, "👨👩👧👦")]
|
||||
[InlineData ("\U0001F468\u200D\U0001F469\u200D\U0001F467\u200D\U0001F466", 2, 1, TextDirection.TopBottom_LeftRight, "👨👩👧👦")]
|
||||
public void Draw_Emojis_With_Zero_Width_Joiner (
|
||||
string text,
|
||||
int width,
|
||||
int height,
|
||||
TextDirection direction,
|
||||
string expectedDraw
|
||||
)
|
||||
{
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
TextFormatter tf = new ()
|
||||
{
|
||||
Direction = direction,
|
||||
ConstrainToSize = new (width, height),
|
||||
Text = text,
|
||||
WordWrap = false
|
||||
};
|
||||
Assert.Equal (width, text.GetColumns ());
|
||||
|
||||
tf.Draw (driver, new (0, 0, width, height), Attribute.Default, Attribute.Default);
|
||||
|
||||
DriverAssert.AssertDriverContentsWithFrameAre (expectedDraw, output, driver);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -792,19 +792,16 @@ public class TextFormatterTests (ITestOutputHelper output) : FakeDriverBase
|
||||
[MemberData (nameof (CMGlyphs))]
|
||||
public void GetLengthThatFits_List_Simple_And_Wide_Runes (string text, int columns, int expectedLength)
|
||||
{
|
||||
List<Rune> runes = text.ToRuneList ();
|
||||
Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (runes, columns));
|
||||
Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (text, columns));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData ("test", 3, 3)]
|
||||
[InlineData ("test", 4, 4)]
|
||||
[InlineData ("test", 10, 4)]
|
||||
public void GetLengthThatFits_Runelist (string text, int columns, int expectedLength)
|
||||
public void GetLengthThatFits_For_String (string text, int columns, int expectedLength)
|
||||
{
|
||||
List<Rune> runes = text.ToRuneList ();
|
||||
|
||||
Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (runes, columns));
|
||||
Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (text, columns));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -833,7 +830,8 @@ public class TextFormatterTests (ITestOutputHelper output) : FakeDriverBase
|
||||
public void GetLengthThatFits_With_Combining_Runes ()
|
||||
{
|
||||
var text = "Les Mise\u0328\u0301rables";
|
||||
Assert.Equal (16, TextFormatter.GetLengthThatFits (text, 14));
|
||||
Assert.Equal (14, TextFormatter.GetLengthThatFits (text, 14));
|
||||
Assert.Equal ("Les Misę́rables", text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -841,14 +839,18 @@ public class TextFormatterTests (ITestOutputHelper output) : FakeDriverBase
|
||||
{
|
||||
List<string> text = new () { "Les Mis", "e\u0328\u0301", "rables" };
|
||||
Assert.Equal (1, TextFormatter.GetMaxColsForWidth (text, 1));
|
||||
Assert.Equal ("Les Mis", text [0]);
|
||||
Assert.Equal ("ę́", text [1]);
|
||||
Assert.Equal ("rables", text [^1]);
|
||||
}
|
||||
|
||||
//[Fact]
|
||||
//public void GetWidestLineLength_With_Combining_Runes ()
|
||||
//{
|
||||
// var text = "Les Mise\u0328\u0301rables";
|
||||
// Assert.Equal (1, TextFormatter.GetWidestLineLength (text, 1, 1));
|
||||
//}
|
||||
[Fact]
|
||||
public void GetWidestLineLength_With_Combining_Runes ()
|
||||
{
|
||||
var text = "Les Mise\u0328\u0301rables";
|
||||
Assert.Equal (14, TextFormatter.GetWidestLineLength (text, 1));
|
||||
Assert.Equal ("Les Misę́rables", text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Internal_Tests ()
|
||||
@@ -2451,6 +2453,7 @@ public class TextFormatterTests (ITestOutputHelper output) : FakeDriverBase
|
||||
Assert.Equal (expected, breakLines);
|
||||
|
||||
// Double space Complex example - this is how VS 2022 does it
|
||||
// which I think is not correct.
|
||||
//text = "A sentence has words. ";
|
||||
//breakLines = "";
|
||||
//wrappedLines = TextFormatter.WordWrapText (text, width, preserveTrailingSpaces: true);
|
||||
@@ -2762,8 +2765,7 @@ public class TextFormatterTests (ITestOutputHelper output) : FakeDriverBase
|
||||
"ฮ",
|
||||
"ฯ",
|
||||
"ะั",
|
||||
"า",
|
||||
"ำ"
|
||||
"าำ"
|
||||
}
|
||||
)]
|
||||
public void WordWrap_Unicode_SingleWordLine (
|
||||
@@ -2798,7 +2800,17 @@ public class TextFormatterTests (ITestOutputHelper output) : FakeDriverBase
|
||||
Assert.True (
|
||||
expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0)
|
||||
);
|
||||
Assert.Equal (resultLines, wrappedLines);
|
||||
|
||||
if (maxWidth == 1)
|
||||
{
|
||||
List<string> newResultLines = resultLines.ToList ();
|
||||
newResultLines [^1] = "";
|
||||
Assert.Equal (newResultLines, wrappedLines);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal (resultLines, wrappedLines);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>WordWrap strips CRLF</summary>
|
||||
@@ -3075,8 +3087,8 @@ public class TextFormatterTests (ITestOutputHelper output) : FakeDriverBase
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData (14, 1, TextDirection.LeftRight_TopBottom, "Les Misęrables")]
|
||||
[InlineData (1, 14, TextDirection.TopBottom_LeftRight, "L\ne\ns\n \nM\ni\ns\nę\nr\na\nb\nl\ne\ns")]
|
||||
[InlineData (14, 1, TextDirection.LeftRight_TopBottom, "Les Misę́rables")]
|
||||
[InlineData (1, 14, TextDirection.TopBottom_LeftRight, "L\ne\ns\n \nM\ni\ns\nę́\nr\na\nb\nl\ne\ns")]
|
||||
[InlineData (
|
||||
4,
|
||||
4,
|
||||
@@ -3085,7 +3097,7 @@ public class TextFormatterTests (ITestOutputHelper output) : FakeDriverBase
|
||||
LMre
|
||||
eias
|
||||
ssb
|
||||
ęl "
|
||||
ę́l "
|
||||
)]
|
||||
public void Draw_With_Combining_Runes (int width, int height, TextDirection textDirection, string expected)
|
||||
{
|
||||
@@ -3111,7 +3123,6 @@ ssb
|
||||
driver.End ();
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[InlineData (17, 1, TextDirection.LeftRight_TopBottom, 4, "This is a Tab")]
|
||||
[InlineData (1, 17, TextDirection.TopBottom_LeftRight, 4, "T\nh\ni\ns\n \ni\ns\n \na\n \n \n \n \n \nT\na\nb")]
|
||||
@@ -3187,7 +3198,6 @@ ssb
|
||||
driver.End ();
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[InlineData (17, 1, TextDirection.LeftRight_TopBottom, 4, "This is a Tab")]
|
||||
[InlineData (1, 17, TextDirection.TopBottom_LeftRight, 4, "T\nh\ni\ns\n \ni\ns\n \na\n \n \n \n \n \nT\na\nb")]
|
||||
@@ -3224,5 +3234,4 @@ ssb
|
||||
|
||||
driver.End ();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user