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.
77 lines
2.0 KiB
Go
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)
|
|
}
|
|
}
|