Files
arr-tui/internal/ui/terminal_test.go
T
nilsandClaude Opus 5.5 67f5cf6ce7 Switch the theme live
Widgets copy their colors when they are created, so they now get role
tokens (one unique RGB value per role) instead of colors, and a wrapper
around the tcell screen replaces the tokens with the current theme's
colors in SetContent. Changing the theme in the Config view swaps that
table; the next draw recolors everything. Poster colors that equal a
token are nudged off it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019reK4DUGYqDUjniWYJvwUW
2026-10-21 12:11:19 +02:00

133 lines
4.2 KiB
Go

package ui
import (
"image/color"
"testing"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
)
func TestParseTerminalAnswers(t *testing.T) {
for _, tc := range []struct {
name string
got string
graphics bool
bg color.RGBA
hasBG bool
}{
{"ghostty", "\x1b_Gi=31;OK\x1b\\\x1b]11;rgb:1e1e/1e1e/2e2e\x1b\\\x1b[?62;22c", true, color.RGBA{30, 30, 46, 255}, true},
{"text color too", "\x1b]10;rgb:4c4c/4f4f/6969\x1b\\\x1b]11;rgb:efef/f1f1/f5f5\x1b\\\x1b[?62c", false, color.RGBA{239, 241, 245, 255}, true},
{"bel and two digits", "\x1b]11;rgb:ff/80/00\a\x1b[?1;2c", false, color.RGBA{255, 128, 0, 255}, true},
{"one digit", "\x1b]11;rgb:f/0/8\x1b\\\x1b[?6c", false, color.RGBA{255, 0, 136, 255}, true},
{"only device attributes", "\x1b[?1;2c", false, color.RGBA{}, false},
{"garbage", "\x1b]11;rgb:zz/00/00\x1b\\\x1b[?6c", false, color.RGBA{}, false},
{"nothing", "", false, color.RGBA{}, false},
} {
info := parseTerminalAnswers([]byte(tc.got))
if info.graphics != tc.graphics || info.hasBG != tc.hasBG || info.bg != tc.bg {
t.Errorf("%s: %+v", tc.name, info)
}
}
}
func TestChooseTheme(t *testing.T) {
dark := terminalInfo{bg: color.RGBA{30, 30, 46, 255}, hasBG: true}
light := terminalInfo{bg: color.RGBA{239, 241, 245, 255}, hasBG: true}
for _, tc := range []struct {
setting string
info terminalInfo
want string
}{
{"", light, "light"},
{"auto", dark, "dark"},
{"auto", terminalInfo{}, "dark"},
{"dark", light, "dark"},
{"light", dark, "light"},
} {
if th, note := chooseTheme(tc.setting, tc.info); th.name != tc.want {
t.Errorf("%q with %+v: %s (%s)", tc.setting, tc.info, th.name, note)
}
}
}
func TestUseTokens(t *testing.T) {
oldStyles := tview.Styles
oldNames := map[string]tcell.Color{}
for k, v := range tcell.ColorNames {
oldNames[k] = v
}
defer func() {
tview.Styles = oldStyles
for k, v := range oldNames {
tcell.ColorNames[k] = v
}
}()
useTokens()
if tview.Styles.PrimitiveBackgroundColor != tokens.bg || tview.Styles.PrimaryTextColor != tokens.text {
t.Error("tview styles not set")
}
// Color names in texts mean the roles.
if named("yellow") != tokens.yellow || tcell.GetColor("black") != tokens.bg || tcell.GetColor("white") != tokens.text {
t.Error("color names not set")
}
}
func TestThemedScreenSwitchesLive(t *testing.T) {
sim := tcell.NewSimulationScreen("")
if err := sim.Init(); err != nil {
t.Fatal(err)
}
defer sim.Fini()
sim.SetSize(4, 1)
s := newThemedScreen(sim, darkTheme)
poster := tcell.NewRGBColor(10, 20, 30)
draw := func() {
s.SetContent(0, 0, 'a', nil, tcell.StyleDefault.Foreground(tokens.yellow).Background(tokens.bg))
s.SetContent(1, 0, 'b', nil, tcell.StyleDefault.Foreground(poster).Background(poster))
}
check := func(th theme) {
_, _, st, _ := sim.GetContent(0, 0)
if fg, bg, _ := st.Decompose(); fg != th.yellow || bg != th.bg {
t.Errorf("%s: role colors drawn as %v on %v", th.name, fg, bg)
}
_, _, st, _ = sim.GetContent(1, 0)
if fg, bg, _ := st.Decompose(); fg != poster || bg != poster {
t.Errorf("%s: other colors changed to %v on %v", th.name, fg, bg)
}
}
draw()
check(darkTheme)
s.setTheme(lightTheme)
draw()
check(lightTheme)
// A poster color that equals a role color is nudged off it.
if c := notToken(tokens.red); c == tokens.red {
t.Error("role color in a picture not nudged")
}
if notToken(poster) != poster {
t.Error("ordinary color changed")
}
}
func TestThemeColorsSurvive256Colors(t *testing.T) {
// Every theme color is its own nearest match in the 256-color palette, so
// a terminal without 24-bit color shows it exactly and never picks one of
// the first 16 colors, which color themes change.
palette := make([]tcell.Color, 256)
for i := range palette {
palette[i] = tcell.PaletteColor(i)
}
for _, th := range []theme{darkTheme, lightTheme} {
for _, c := range []tcell.Color{th.bg, th.text, th.border, th.field, th.fieldMuted, th.list, th.pressed, th.toast,
th.gray, th.yellow, th.green, th.red, th.blue, th.purple} {
if got := tcell.FindColor(c, palette); got < tcell.PaletteColor(16) {
t.Errorf("%s theme: %v maps to palette color %d", th.name, c, got-tcell.ColorValid)
}
}
}
}