go/pkg/cli/styles_test.go
Claude 228170d610
feat(cli): TUI components, Frame AppShell, command builders (#11-15)
Issues #11-13: WithAppName for variant binaries, NewPassthrough builder
for flag.FlagSet commands, RegisterCommands test coverage with resetGlobals
helper. Fix pre-existing daemon_test.go break.

Issue #14: Rich Table with box-drawing borders (Normal, Rounded, Heavy,
Double), per-column CellStyleFn, WithMaxWidth responsive truncation.
Tree renderer with box-drawing connectors and styled nodes. Parallel
TaskTracker with braille spinners, thread-safe concurrent updates, and
non-TTY fallback. Streaming text renderer with word-wrap and channel
pattern support.

Issue #15: Frame live compositional AppShell using HLCRF regions with
Model interface, Navigate/Back content swapping, alt-screen live mode,
graceful non-TTY fallback. Built-in region components: StatusLine,
KeyHints, Breadcrumb. Zero new dependencies — pure ANSI + x/term.

68 tests, all passing with -race.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 19:45:23 +00:00

206 lines
5.3 KiB
Go

package cli
import (
"strings"
"testing"
"unicode/utf8"
"github.com/stretchr/testify/assert"
)
func TestTable_Good(t *testing.T) {
t.Run("plain table unchanged", func(t *testing.T) {
SetColorEnabled(false)
defer SetColorEnabled(true)
tbl := NewTable("NAME", "AGE")
tbl.AddRow("Alice", "30")
tbl.AddRow("Bob", "25")
out := tbl.String()
assert.Contains(t, out, "NAME")
assert.Contains(t, out, "Alice")
assert.Contains(t, out, "Bob")
})
t.Run("bordered normal", func(t *testing.T) {
SetColorEnabled(false)
defer SetColorEnabled(true)
tbl := NewTable("A", "B").WithBorders(BorderNormal)
tbl.AddRow("x", "y")
out := tbl.String()
assert.True(t, strings.HasPrefix(out, "┌"))
assert.Contains(t, out, "┐")
assert.Contains(t, out, "│")
assert.Contains(t, out, "├")
assert.Contains(t, out, "┤")
assert.Contains(t, out, "└")
assert.Contains(t, out, "┘")
})
t.Run("bordered rounded", func(t *testing.T) {
SetColorEnabled(false)
defer SetColorEnabled(true)
tbl := NewTable("REPO", "STATUS").WithBorders(BorderRounded)
tbl.AddRow("core", "clean")
out := tbl.String()
lines := strings.Split(strings.TrimRight(out, "\n"), "\n")
assert.True(t, strings.HasPrefix(lines[0], "╭"))
assert.True(t, strings.HasSuffix(lines[0], "╮"))
assert.True(t, strings.HasPrefix(lines[len(lines)-1], "╰"))
assert.True(t, strings.HasSuffix(lines[len(lines)-1], "╯"))
})
t.Run("bordered heavy", func(t *testing.T) {
SetColorEnabled(false)
defer SetColorEnabled(true)
tbl := NewTable("X").WithBorders(BorderHeavy)
tbl.AddRow("v")
out := tbl.String()
assert.Contains(t, out, "┏")
assert.Contains(t, out, "┓")
assert.Contains(t, out, "┃")
})
t.Run("bordered double", func(t *testing.T) {
SetColorEnabled(false)
defer SetColorEnabled(true)
tbl := NewTable("X").WithBorders(BorderDouble)
tbl.AddRow("v")
out := tbl.String()
assert.Contains(t, out, "╔")
assert.Contains(t, out, "╗")
assert.Contains(t, out, "║")
})
t.Run("bordered structure", func(t *testing.T) {
SetColorEnabled(false)
defer SetColorEnabled(true)
tbl := NewTable("A", "B").WithBorders(BorderRounded)
tbl.AddRow("x", "y")
tbl.AddRow("1", "2")
lines := strings.Split(strings.TrimRight(tbl.String(), "\n"), "\n")
// Top border, header, separator, 2 data rows, bottom border = 6 lines
assert.Equal(t, 6, len(lines), "expected 6 lines: border, header, sep, 2 rows, border")
})
t.Run("cell style function", func(t *testing.T) {
SetColorEnabled(false)
defer SetColorEnabled(true)
called := false
tbl := NewTable("STATUS").
WithCellStyle(0, func(val string) *AnsiStyle {
called = true
if val == "ok" {
return SuccessStyle
}
return ErrorStyle
})
tbl.AddRow("ok")
tbl.AddRow("fail")
_ = tbl.String()
assert.True(t, called, "cell style function should be called")
})
t.Run("cell style with borders", func(t *testing.T) {
SetColorEnabled(false)
defer SetColorEnabled(true)
tbl := NewTable("NAME", "STATUS").
WithBorders(BorderRounded).
WithCellStyle(1, func(val string) *AnsiStyle {
return nil // fallback to default
})
tbl.AddRow("core", "ok")
out := tbl.String()
assert.Contains(t, out, "core")
assert.Contains(t, out, "ok")
})
t.Run("max width truncates", func(t *testing.T) {
SetColorEnabled(false)
defer SetColorEnabled(true)
tbl := NewTable("LONG_HEADER", "SHORT").WithMaxWidth(25)
tbl.AddRow("very_long_value_here", "x")
out := tbl.String()
lines := strings.Split(strings.TrimRight(out, "\n"), "\n")
for _, line := range lines {
w := utf8.RuneCountInString(line)
assert.LessOrEqual(t, w, 25, "line should not exceed max width: %q", line)
}
})
t.Run("max width with borders", func(t *testing.T) {
SetColorEnabled(false)
defer SetColorEnabled(true)
tbl := NewTable("A", "B").WithBorders(BorderNormal).WithMaxWidth(20)
tbl.AddRow("hello", "world")
out := tbl.String()
lines := strings.Split(strings.TrimRight(out, "\n"), "\n")
for _, line := range lines {
w := utf8.RuneCountInString(line)
assert.LessOrEqual(t, w, 20, "bordered line should not exceed max width: %q", line)
}
})
t.Run("empty table returns empty", func(t *testing.T) {
tbl := NewTable()
assert.Equal(t, "", tbl.String())
})
t.Run("no headers with borders", func(t *testing.T) {
SetColorEnabled(false)
defer SetColorEnabled(true)
tbl := NewTable().WithBorders(BorderNormal)
tbl.Rows = [][]string{{"a", "b"}, {"c", "d"}}
out := tbl.String()
assert.Contains(t, out, "┌")
// No header separator since no headers
lines := strings.Split(strings.TrimRight(out, "\n"), "\n")
// Top border, 2 data rows, bottom border = 4 lines (no header separator)
assert.Equal(t, 4, len(lines))
})
}
func TestTable_Bad(t *testing.T) {
t.Run("short rows padded", func(t *testing.T) {
SetColorEnabled(false)
defer SetColorEnabled(true)
tbl := NewTable("A", "B", "C")
tbl.AddRow("x") // only 1 cell, 3 columns
out := tbl.String()
assert.Contains(t, out, "x")
})
}
func TestTruncate_Good(t *testing.T) {
assert.Equal(t, "hel...", Truncate("hello world", 6))
assert.Equal(t, "hi", Truncate("hi", 6))
assert.Equal(t, "he", Truncate("hello", 2))
}
func TestPad_Good(t *testing.T) {
assert.Equal(t, "hi ", Pad("hi", 5))
assert.Equal(t, "hello", Pad("hello", 3))
}