Files
dualfroz 4e196d82d8 fix(whitespace): prevent wide runes from overflowing render width
whitespace.render checked the loop bound only before writing a rune,
then advanced by the rune width. A rune wider than the remaining space
was still written, overshooting the requested width by up to
runeWidth-1 cells, and this made the trailing space-fill unreachable.

Stop before a wide rune would overflow and let the existing space-fill
pad the remaining cells, so the result is always exactly the requested
width. This also fixes overflow surfaced through Place/PlaceHorizontal
with WithWhitespaceChars for multi-cell whitespace characters.
2026-09-02 09:44:11 +02:00

77 lines
2.0 KiB
Go

package lipgloss
import (
"testing"
"time"
"github.com/charmbracelet/x/ansi"
)
func TestWhitespaceRenderWithTab(t *testing.T) {
// This test verifies that rendering whitespace with tab characters
// doesn't cause an infinite loop (issue #108)
done := make(chan bool, 1)
go func() {
ws := newWhitespace(WithWhitespaceChars("\t"))
_ = ws.render(10)
done <- true
}()
select {
case <-done:
// Success - render completed
case <-time.After(2 * time.Second):
t.Fatal("whitespace.render() with tab character caused infinite loop")
}
}
func TestWhitespaceRenderWithZeroWidthChar(t *testing.T) {
// Test with zero-width joiner (another zero-width character)
done := make(chan bool, 1)
go func() {
ws := newWhitespace(WithWhitespaceChars("\u200d")) // zero-width joiner
_ = ws.render(5)
done <- true
}()
select {
case <-done:
// Success
case <-time.After(2 * time.Second):
t.Fatal("whitespace.render() with zero-width character caused infinite loop")
}
}
func TestWhitespaceRenderNormal(t *testing.T) {
// Verify normal behavior still works
ws := newWhitespace(WithWhitespaceChars("*"))
result := ws.render(5)
if len(result) != 5 {
t.Errorf("expected 5 characters, got %d", len(result))
}
}
func TestWhitespaceRenderWideRune(t *testing.T) {
// A whitespace char that is more than one cell wide must never render
// wider than the requested width; the trailing cells are padded with
// spaces instead of overflowing.
ws := newWhitespace(WithWhitespaceChars("東")) // width 2
for _, width := range []int{0, 1, 2, 3, 4, 5} {
got := ansi.StringWidth(ws.render(width))
if got != width {
t.Errorf("render(%d): expected rendered width %d, got %d", width, width, got)
}
}
}
func TestPlaceHorizontalWideWhitespace(t *testing.T) {
// Placing content in a box with a wide whitespace char must keep the
// box exactly the requested width.
got := PlaceHorizontal(5, Left, "ab", WithWhitespaceChars("東"))
if w := ansi.StringWidth(got); w != 5 {
t.Errorf("PlaceHorizontal width: expected 5, got %d (%q)", w, got)
}
}