Skip to content

Commit 0a235e2

Browse files
committed
Update tests for VS16 emoji widening (width 1 → 2)
Keycap sequences (1️⃣, #️⃣) and narrow emoji with FE0F (✈️) now correctly produce 2 cells (base + continuation) instead of 1. Update 12 tests across 4 files to match the new correct behavior.
1 parent 6ee33c5 commit 0a235e2

4 files changed

Lines changed: 44 additions & 33 deletions

File tree

SharpConsoleUI.Tests/Helpers/UnicodeEdgeCaseTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,17 +129,17 @@ public void GetStringWidth_AllFiveSkinTones_Consistent()
129129
[Fact]
130130
public void GetStringWidth_KeycapSequence_CountsCorrectly()
131131
{
132-
// 1️⃣ = '1'(1) + FE0F(0) + U+20E3(0, combining enclosing keycap) = 1
132+
// 1️⃣ = '1'(1) + FE0F(+1 VS16 widening) + U+20E3(0, combining enclosing keycap) = 2
133133
string keycap = "1\uFE0F\u20E3";
134-
Assert.Equal(1, UnicodeWidth.GetStringWidth(keycap));
134+
Assert.Equal(2, UnicodeWidth.GetStringWidth(keycap));
135135
}
136136

137137
[Fact]
138138
public void GetStringWidth_HashKeycap_CountsCorrectly()
139139
{
140-
// #️⃣ = '#'(1) + FE0F(0) + U+20E3(0) = 1
140+
// #️⃣ = '#'(1) + FE0F(+1 VS16 widening) + U+20E3(0) = 2
141141
string keycap = "#\uFE0F\u20E3";
142-
Assert.Equal(1, UnicodeWidth.GetStringWidth(keycap));
142+
Assert.Equal(2, UnicodeWidth.GetStringWidth(keycap));
143143
}
144144

145145
[Fact]

SharpConsoleUI.Tests/Parsing/MarkupParserUnicodeEdgeCaseTests.cs

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -128,40 +128,45 @@ public void Parse_AllFiveSkinTones_SameCellCount()
128128
[Fact]
129129
public void Parse_KeycapSequence_FE0FAndKeycapAreCombiners()
130130
{
131-
// 1️⃣ = '1'(1 cell) + FE0F(combiner) + U+20E3(combiner) = 1 cell
131+
// 1️⃣ = '1'(1 cell) + FE0F(VS16 widens to 2) + U+20E3(combiner) = 2 cells
132132
var cells = MarkupParser.Parse("1\uFE0F\u20E3", Color.White, Color.Black);
133133

134-
Assert.Single(cells);
134+
Assert.Equal(2, cells.Count);
135135
Assert.Equal(new Rune('1'), cells[0].Character);
136136
Assert.Equal("\uFE0F\u20E3", cells[0].Combiners);
137+
Assert.True(cells[1].IsWideContinuation);
137138
}
138139

139140
[Fact]
140-
public void StripLength_KeycapSequence_Returns1()
141+
public void StripLength_KeycapSequence_Returns2()
141142
{
142-
// 1️⃣ = '1'(1) + FE0F(0) + U+20E3(0) = 1
143-
Assert.Equal(1, MarkupParser.StripLength("1\uFE0F\u20E3"));
143+
// 1️⃣ = '1'(1) + FE0F(+1 VS16 widening) + U+20E3(0) = 2
144+
Assert.Equal(2, MarkupParser.StripLength("1\uFE0F\u20E3"));
144145
}
145146

146147
[Fact]
147-
public void Parse_HashKeycap_SingleCell()
148+
public void Parse_HashKeycap_TwoCells()
148149
{
150+
// #️⃣ = '#'(1) + FE0F(VS16 widens to 2) + U+20E3(0) = 2 cells
149151
var cells = MarkupParser.Parse("#\uFE0F\u20E3", Color.White, Color.Black);
150-
Assert.Single(cells);
152+
Assert.Equal(2, cells.Count);
151153
Assert.Equal(new Rune('#'), cells[0].Character);
154+
Assert.True(cells[1].IsWideContinuation);
152155
}
153156

154157
[Fact]
155158
public void Parse_MultipleKeycaps_CorrectCellCount()
156159
{
157-
// "1️⃣2️⃣" = 1(1) + 2(1) = 2 cells (FE0F + 20E3 are combiners on each)
160+
// "1️⃣2️⃣" = 1(2) + 2(2) = 4 cells (VS16 widens each keycap base)
158161
var cells = MarkupParser.Parse(
159162
"1\uFE0F\u20E32\uFE0F\u20E3",
160163
Color.White, Color.Black);
161164

162-
Assert.Equal(2, cells.Count);
165+
Assert.Equal(4, cells.Count);
163166
Assert.Equal(new Rune('1'), cells[0].Character);
164-
Assert.Equal(new Rune('2'), cells[1].Character);
167+
Assert.True(cells[1].IsWideContinuation);
168+
Assert.Equal(new Rune('2'), cells[2].Character);
169+
Assert.True(cells[3].IsWideContinuation);
165170
}
166171

167172
#endregion
@@ -207,14 +212,15 @@ public void Parse_EmojiWithFE0F_AttachesToBaseNotContinuation()
207212
}
208213

209214
[Fact]
210-
public void Parse_NarrowCharWithFE0F_SingleCellWithCombiner()
215+
public void Parse_NarrowCharWithFE0F_WidenedToTwoCells()
211216
{
212-
// ✈ (U+2708, narrow 1 cell) + FE0F = 1 cell with combiner
217+
// ✈ (U+2708, narrow 1 cell) + FE0F (VS16 widens to 2) = 2 cells
213218
var cells = MarkupParser.Parse("\u2708\uFE0F", Color.White, Color.Black);
214219

215-
Assert.Single(cells);
220+
Assert.Equal(2, cells.Count);
216221
Assert.Equal(new Rune('\u2708'), cells[0].Character);
217222
Assert.Contains("\uFE0F", cells[0].Combiners);
223+
Assert.True(cells[1].IsWideContinuation);
218224
}
219225

220226
[Fact]
@@ -456,10 +462,11 @@ public void Parse_KeycapInMarkup_CorrectCells()
456462
// [bold]1️⃣[/] — the literal text path in MarkupParser handles this
457463
var cells = MarkupParser.Parse("[bold]1\uFE0F\u20E3[/]", Color.White, Color.Black);
458464

459-
// '1' is base cell, FE0F and 20E3 are zero-width combiners
460-
Assert.Single(cells);
465+
// '1' is base cell, FE0F widens it (VS16), 20E3 is combiner = 2 cells
466+
Assert.Equal(2, cells.Count);
461467
Assert.Equal(new Rune('1'), cells[0].Character);
462468
Assert.NotNull(cells[0].Combiners);
469+
Assert.True(cells[1].IsWideContinuation);
463470
}
464471

465472
[Fact]
@@ -524,9 +531,9 @@ public void Truncate_EmojiWithFE0F_PreservesTrailingCombiner()
524531
[Fact]
525532
public void Truncate_KeycapSequence_PreservesCombiners()
526533
{
527-
// "1️⃣AB" = 1(1)+FE0F(0)+20E3(0)+A(1)+B(1) = 3
528-
// Truncate to 1: "1️⃣" (1 + trailing combiners)
529-
var result = MarkupParser.Truncate("1\uFE0F\u20E3AB", 1);
534+
// "1️⃣AB" = 1(1)+FE0F(+1 VS16 widening)+20E3(0)+A(1)+B(1) = 4
535+
// Truncate to 2: "1️⃣" (keycap is 2 cols wide with VS16)
536+
var result = MarkupParser.Truncate("1\uFE0F\u20E3AB", 2);
530537

531538
Assert.Equal("1\uFE0F\u20E3", result);
532539
}

SharpConsoleUI.Tests/Parsing/MarkupParserWideCharTests.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -338,13 +338,14 @@ public void Parse_ZWJ_AttachesToPreviousCell()
338338
[Fact]
339339
public void Parse_EmojiWithFE0F_CorrectCellCount()
340340
{
341-
// ✈️ = U+2708 (narrow, 1 cell) + U+FE0F (zero-width, combiner)
341+
// ✈️ = U+2708 (narrow, 1 cell) + U+FE0F (VS16 widens to 2 cells)
342342
var cells = MarkupParser.Parse("\u2708\uFE0F", Color.White, Color.Black);
343343

344-
Assert.Single(cells);
344+
Assert.Equal(2, cells.Count);
345345
Assert.Equal(new Rune('\u2708'), cells[0].Character);
346346
Assert.NotNull(cells[0].Combiners);
347347
Assert.Contains("\uFE0F", cells[0].Combiners);
348+
Assert.True(cells[1].IsWideContinuation);
348349
}
349350

350351
[Fact]

SharpConsoleUI.Tests/Rendering/Unit/TopLayer/CharacterBufferUnicodeEdgeCaseTests.cs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -80,33 +80,35 @@ public void WriteString_EmojiWithSkinTone_ModifierAttachesAsCombiner()
8080
#region Keycap Sequences
8181

8282
[Fact]
83-
public void WriteString_KeycapSequence_SingleColumn()
83+
public void WriteString_KeycapSequence_TwoColumns()
8484
{
8585
var buffer = new CharacterBuffer(20, 5);
8686

87-
// 1️⃣ = '1'(1) + FE0F(combiner) + U+20E3(combiner) = 1 column
87+
// 1️⃣ = '1'(1) + FE0F(VS16 widens to 2) + U+20E3(combiner) = 2 columns
8888
buffer.WriteString(0, 0, "1\uFE0F\u20E3", Color.White, Color.Black);
8989

9090
var cell = buffer.GetCell(0, 0);
9191
Assert.Equal(new Rune('1'), cell.Character);
9292
Assert.NotNull(cell.Combiners);
9393
Assert.Equal("\uFE0F\u20E3", cell.Combiners);
9494

95-
// Column 1 should be untouched
96-
Assert.Equal(new Rune(' '), buffer.GetCell(1, 0).Character);
95+
// Column 1 should be a continuation cell from VS16 widening
96+
Assert.True(buffer.GetCell(1, 0).IsWideContinuation);
9797
}
9898

9999
[Fact]
100100
public void WriteString_MultipleKeycaps_CorrectPositions()
101101
{
102102
var buffer = new CharacterBuffer(20, 5);
103103

104-
// "1️⃣2️⃣A" = 1(col 0) + 2(col 1) + A(col 2) = 3 columns
104+
// "1️⃣2️⃣A" = 1(cols 0-1) + 2(cols 2-3) + A(col 4) = 5 columns
105105
buffer.WriteString(0, 0, "1\uFE0F\u20E32\uFE0F\u20E3A", Color.White, Color.Black);
106106

107107
Assert.Equal(new Rune('1'), buffer.GetCell(0, 0).Character);
108-
Assert.Equal(new Rune('2'), buffer.GetCell(1, 0).Character);
109-
Assert.Equal(new Rune('A'), buffer.GetCell(2, 0).Character);
108+
Assert.True(buffer.GetCell(1, 0).IsWideContinuation);
109+
Assert.Equal(new Rune('2'), buffer.GetCell(2, 0).Character);
110+
Assert.True(buffer.GetCell(3, 0).IsWideContinuation);
111+
Assert.Equal(new Rune('A'), buffer.GetCell(4, 0).Character);
110112
}
111113

112114
#endregion
@@ -286,12 +288,13 @@ public void WriteStringClipped_KeycapInClip_CombiersPreserved()
286288
var buffer = new CharacterBuffer(20, 5);
287289
var clipRect = new LayoutRect(0, 0, 5, 5);
288290

289-
// "1️⃣AB" = 1(1)+A(1)+B(1) = 3 columns
291+
// "1️⃣AB" = 1(2, VS16 widened)+A(1)+B(1) = 4 columns
290292
buffer.WriteStringClipped(0, 0, "1\uFE0F\u20E3AB", Color.White, Color.Black, clipRect);
291293

292294
Assert.Equal(new Rune('1'), buffer.GetCell(0, 0).Character);
293295
Assert.Contains("\uFE0F", buffer.GetCell(0, 0).Combiners);
294-
Assert.Equal(new Rune('A'), buffer.GetCell(1, 0).Character);
296+
Assert.True(buffer.GetCell(1, 0).IsWideContinuation);
297+
Assert.Equal(new Rune('A'), buffer.GetCell(2, 0).Character);
295298
}
296299

297300
[Fact]

0 commit comments

Comments
 (0)