1package components
2
3import (
4 "regexp"
5 "strings"
6 "testing"
7
8 "github.com/charmbracelet/lipgloss"
9 "github.com/mattn/go-runewidth"
10 "github.com/stretchr/testify/assert"
11 "github.com/stretchr/testify/require"
12)
13
14func TestTableRender_AppliesConfiguredColumnWidth(t *testing.T) {
15 tableModel := NewTable(
16 WithColumns([]TableColumn{{Title: "ID", Width: 5}}),
17 WithRows([][]string{{"abc"}}),
18 WithHeaderStyle(lipgloss.NewStyle()),
19 WithCellStyle(lipgloss.NewStyle()),
20 )
21
22 rendered := stripANSI(tableModel.Render())
23 lines := strings.Split(rendered, "\n")
24
25 var rowLine string
26 for _, line := range lines {
27 if strings.Contains(line, "abc") {
28 rowLine = line
29 break
30 }
31 }
32
33 require.NotEmpty(t, rowLine)
34 assert.Contains(t, rowLine, "abc ")
35}
36
37func TestTableRender_TruncatesLongCellTextWithEllipsis(t *testing.T) {
38 tableModel := NewTable(
39 WithColumns([]TableColumn{{Title: "ID", Width: 5}}),
40 WithRows([][]string{{"abcdef"}}),
41 WithHeaderStyle(lipgloss.NewStyle()),
42 WithCellStyle(lipgloss.NewStyle()),
43 )
44
45 rendered := stripANSI(tableModel.Render())
46 assert.Contains(t, rendered, "ab...")
47 assert.NotContains(t, rendered, "abcdef")
48}
49
50func TestTruncateCell_LeavesShortTextUnchanged(t *testing.T) {
51 assert.Equal(t, "abc", truncateCell("abc", 5))
52}
53
54func TestTruncateCell_EdgeCases(t *testing.T) {
55 tests := []struct {
56 name string
57 value string
58 maxWidth int
59 expected string
60 }{
61 {name: "zero width passthrough", value: "abcdef", maxWidth: 0, expected: "abcdef"},
62 {name: "width three all dots", value: "abcdef", maxWidth: 3, expected: "..."},
63 {name: "ascii truncates", value: "abcdef", maxWidth: 5, expected: "ab..."},
64 {name: "cjk truncates by display width", value: "你好世界", maxWidth: 5, expected: "你..."},
65 {name: "grapheme-safe emoji truncation", value: "👨<U+200D>👩<U+200D>👧<U+200D>👦abcd", maxWidth: 5, expected: "👨<U+200D>👩<U+200D>👧<U+200D>👦..."},
66 }
67
68 for _, tt := range tests {
69 tt := tt
70 t.Run(tt.name, func(t *testing.T) {
71 got := truncateCell(tt.value, tt.maxWidth)
72 assert.Equal(t, tt.expected, got)
73 if tt.maxWidth > 0 {
74 assert.LessOrEqual(t, runewidth.StringWidth(got), tt.maxWidth)
75 }
76 })
77 }
78}
79
80func TestTruncateCell_AnsiInputPassthrough(t *testing.T) {
81 styled := "\x1b[32mactive\x1b[0m"
82 got := truncateCell(styled, 3)
83 assert.Equal(t, styled, got)
84 assert.Contains(t, got, "\x1b[")
85}
86
87func stripANSI(input string) string {
88 ansiPattern := regexp.MustCompile(`\x1b\[[0-9;]*[A-Za-z]`)
89 return ansiPattern.ReplaceAllString(input, "")
90}