package tui
import (
"strings"
"testing"
"unicode/utf8"
"github.com/charmbracelet/lipgloss"
"github.com/muesli/termenv"
"harness/internal/llm"
)
// forceColour makes styles emit real escape sequences. Without it the test
// renderer detects no terminal, strips every colour, and any assertion about
// what a style actually painted passes vacuously.
func forceColour(t *testing.T) {
t.Helper()
prev := lipgloss.ColorProfile()
lipgloss.SetColorProfile(termenv.TrueColor)
t.Cleanup(func() { lipgloss.SetColorProfile(prev) })
}
// unbackedCells returns the printable characters that were emitted with no
// background colour in force, and backedCells the ones that had one. Between
// them they say where a background actually landed: which cells a pill filled,
// and which cells a mouse selection marked.
func unbackedCells(s string) string { return cellsWithBackground(s, false) }
func backedCells(s string) string { return cellsWithBackground(s, true) }
func cellsWithBackground(s string, want bool) string {
var out []rune
active := ""
for i := 0; i < len(s); {
if s[i] == 0x1b && i+1 < len(s) && s[i+1] == '[' {
j := i + 2
for j < len(s) && s[j] != 'm' {
j++
}
if j >= len(s) {
break
}
if p := s[i+2 : j]; p == "" || p == "0" {
active = ""
} else {
active = p
}
i = j + 1
continue
}
r, size := utf8.DecodeRuneInString(s[i:])
if strings.Contains(active, "48;") == want {
out = append(out, r)
}
i += size
}
return string(out)
}
// A style nested inside a background ends with a reset, which drops the
// background for everything after it and tears a visible hole in the pill.
func TestPillsAreFilledEdgeToEdge(t *testing.T) {
forceColour(t)
m := newTestModel(t)
m.opts.ContextWindow = 200_000
m.Update(evUsage{usage: llm.Usage{InputTokens: 2_000, CacheReadInputTokens: 18_000}})
pill, ok := m.contextPill()
if !ok {
t.Fatal("no context pill after usage was reported")
}
if got := unbackedCells(pill); got != "" {
t.Errorf("context pill has unfilled cells %q in %q", got, pill)
}
if got := unbackedCells(styName.Render(username())); got != "" {
t.Errorf("name pill has unfilled cells %q", got)
}
}
func TestContextPillColoursByPressure(t *testing.T) {
forceColour(t)
cases := []struct {
tokens int
pct string
colour lipgloss.AdaptiveColor
}{
{10_000, "(10%)", colOK},
{65_000, "(65%)", colWarn},
{90_000, "(90%)", colErr},
}
for _, tc := range cases {
m := newTestModel(t)
m.opts.ContextWindow = 100_000
m.Update(evUsage{usage: llm.Usage{InputTokens: tc.tokens}})
pill, ok := m.contextPill()
if !ok {
t.Fatalf("%d tokens produced no pill", tc.tokens)
}
if !strings.Contains(pill, tc.pct) {
t.Errorf("%d tokens: %q does not report %s", tc.tokens, pill, tc.pct)
}
want := termenv.TrueColor.Color(tc.colour.Dark).Sequence(false)
if !strings.Contains(pill, want) {
t.Errorf("%d tokens: pill is not %s: %q", tc.tokens, tc.colour.Dark, pill)
}
}
}