diff options
Diffstat (limited to 'internal/render/width_test.go')
| -rw-r--r-- | internal/render/width_test.go | 112 |
1 files changed, 112 insertions, 0 deletions
diff --git a/internal/render/width_test.go b/internal/render/width_test.go new file mode 100644 index 0000000..f20038e --- /dev/null +++ b/internal/render/width_test.go @@ -0,0 +1,112 @@ +package render + +import ( + "strings" + "testing" +) + +func TestDisplayWidth(t *testing.T) { + cases := []struct { + name string + in string + want int + }{ + {"ascii", "temp", 4}, + {"empty", "", 0}, + {"degree sign is one cell", "28°", 3}, + {"polish diacritics are one cell each", "słońce", 6}, + {"emoji with variation selector counts once", "☀️", 1}, + {"bare BMP symbol", "☀", 1}, + {"sun behind cloud is wide", "⛅", 2}, + {"rain cloud is wide", "\U0001F327", 2}, + {"nerd font glyph is one cell", "", 1}, + {"block drawing is one cell", "█", 1}, + {"combining acute adds nothing", "é", 1}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + if got := DisplayWidth(c.in); got != c.want { + t.Errorf("DisplayWidth(%q) = %d, want %d", c.in, got, c.want) + } + }) + } +} + +// The invariant that actually matters: whatever goes in a column, the padded +// result occupies exactly the requested number of cells. If this fails, every +// column to the right shears -- but only in a real terminal. +func TestPadReachesExactWidthForEveryIconSet(t *testing.T) { + samples := []string{ + "clear", "", "28°", "słońce", + "☀️", "⛅", "\U0001F327", "⛈", // emoji set + "", "", "", // nerd set + } + for _, s := range samples { + for _, w := range []int{1, 4, 8, 16} { + got := Pad(s, w) + if DisplayWidth(s) <= w && DisplayWidth(got) != w { + t.Errorf("Pad(%q, %d) has width %d, want %d", s, w, DisplayWidth(got), w) + } + if !strings.HasPrefix(got, s) { + t.Errorf("Pad(%q, %d) = %q, must not alter the content", s, w, got) + } + } + } +} + +func TestPadLeft(t *testing.T) { + if got := PadLeft("5", 3); got != " 5" { + t.Fatalf("PadLeft = %q, want %q", got, " 5") + } + if got := PadLeft("⛅", 4); DisplayWidth(got) != 4 { + t.Fatalf("PadLeft of a wide glyph has width %d, want 4", DisplayWidth(got)) + } +} + +func TestPadDoesNotShrink(t *testing.T) { + if got := Pad("conditions", 4); got != "conditions" { + t.Fatalf("Pad must never truncate: got %q", got) + } +} + +func TestTruncateNeverSplitsARune(t *testing.T) { + if got := Truncate("słońce", 3); got != "sło" { + t.Errorf("Truncate = %q, want %q", got, "sło") + } + // A wide glyph that does not fit is dropped whole, not halved. + if got := Truncate("a⛅", 2); got != "a" { + t.Errorf("Truncate = %q, want %q", got, "a") + } +} + +func TestPaintGroupsRuns(t *testing.T) { + c := Styler(true) + cells := []Cell{ + {Style: "31", Text: "a"}, {Style: "31", Text: "b"}, {Style: "31", Text: "c"}, + {Style: "33", Text: "d"}, + } + got := Paint(cells, c) + if n := strings.Count(got, "\033["); n != 4 { // 2 opens + 2 resets + t.Fatalf("expected one escape pair per run, got %d escapes in %q", n, got) + } + if strings.Count(got, "\033[31m") != 1 { + t.Errorf("the three red cells must share one escape: %q", got) + } +} + +func TestPaintWithoutColourIsPlain(t *testing.T) { + c := Styler(false) + got := Paint([]Cell{{Style: "31", Text: "a"}, {Style: "33", Text: "b"}}, c) + if got != "ab" { + t.Fatalf("colour off must yield plain text, got %q", got) + } +} + +// Colour must never reach for a 256-colour index. +func TestNoExtendedColourCodes(t *testing.T) { + for _, code := range []string{Blue, Cyan, Green, Yellow, Red, BrightBlue, BrightRed, BrightWhite} { + if strings.Contains(code, "38;5;") || strings.Contains(code, "48;5;") { + t.Errorf("%q is a 256-colour index; only slots 0-15 are allowed", code) + } + } +} |
