Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions internal/cli/home.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -717,21 +717,21 @@ func renderHome(p *ui.Printer, m homeModel) {
// don't claim tracebloc's view — it may be heartbeating fine while only
// our probe failed — say we couldn't confirm.
if m.confirmedNotOnline {
p.WarnLine("%s · running, but tracebloc hasn't heard from it — run %s %s",
envLabel, m.inv, doctorPath)
p.WarnLine("%s · running, but tracebloc hasn't heard from it — run %s",
envLabel, p.Command(m.inv+" "+doctorPath))
} else {
p.WarnLine("%s · running — couldn't confirm it's connected to tracebloc — run %s %s",
envLabel, m.inv, doctorPath)
p.WarnLine("%s · running — couldn't confirm it's connected to tracebloc — run %s",
envLabel, p.Command(m.inv+" "+doctorPath))
}
case homeStarting:
p.WarnLine("%s · starting up, not ready yet — run %s %s",
envLabel, m.inv, doctorPath)
p.WarnLine("%s · starting up, not ready yet — run %s",
envLabel, p.Command(m.inv+" "+doctorPath))
case homeOffline:
// One honest line for both causes: a stopped/unreachable cluster AND a
// reachable cluster that doesn't host this release from the current
// kube-context. "can't reach it from here" is true either way.
p.CrossLine("%s · can't reach it from here — run %s %s",
envLabel, m.inv, doctorPath)
p.CrossLine("%s · can't reach it from here — run %s",
envLabel, p.Command(m.inv+" "+doctorPath))
case homeNoEnv:
p.WarnLine("No secure environment on this machine yet — run the installer to set one up.")
}
Expand Down
56 changes: 56 additions & 0 deletions internal/ui/brand_tones_test.go
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
package ui

import (
"bytes"
"strings"
"testing"
)

// renderTone builds a colorized Printer with a pinned mode/bg (independent of the
// runner's COLORTERM/COLORFGBG) and returns what f writes — so the brand-SGR
// assertions below are deterministic on any CI runner.
func renderTone(m colorMode, bg termBg, f func(*Printer)) string {
var b bytes.Buffer
p := New(&b, WithColor(true))
p.mode, p.bg = m, bg
f(p)
return b.String()
}

// TestBrandTones_Truecolor pins the exact 24-bit brand SGR the style system
// emits, through the real reachable methods: Section renders primary cyan
// #01a5cc, a MenuRow command renders secondary lime #91e947, and on a light
// terminal both drop to their deep shades (primary.700 / secondary.700) so they
// stay legible on white. These values come straight from the design-system
// tokens — a drift here is a brand regression, caught in CI not the field.
func TestBrandTones_Truecolor(t *testing.T) {
cases := []struct {
name string
bg termBg
render func(*Printer)
want string
}{
{"heading · dark · cyan #01a5cc", bgDark, func(p *Printer) { p.Section("x") }, "38;2;1;165;204"},
{"command · dark · lime #91e947", bgDark, func(p *Printer) { p.MenuRow(3, "cmd", "d") }, "38;2;145;233;71"},
{"heading · light · deep cyan #01637a", bgLight, func(p *Printer) { p.Section("x") }, "38;2;1;99;122"},
{"command · light · deep lime #578c2b", bgLight, func(p *Printer) { p.MenuRow(3, "cmd", "d") }, "38;2;87;140;43"},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
if out := renderTone(modeTrue, c.bg, c.render); !strings.Contains(out, c.want) {
t.Errorf("want SGR %q in %q", c.want, out)
}
})
}
}

// TestBrandTones_Fallback16: without truecolor, brand hues degrade to the nearest
// ANSI-16 (cyan → 36, green → 32) and never emit a raw 24-bit escape.
func TestBrandTones_Fallback16(t *testing.T) {
if out := renderTone(mode16, bgDark, func(p *Printer) { p.Section("x") }); !strings.Contains(out, "36") || strings.Contains(out, "38;2") {
t.Errorf("16-color heading should use cyan (36), no truecolor, got %q", out)
}
if out := renderTone(mode16, bgDark, func(p *Printer) { p.MenuRow(3, "cmd", "d") }); !strings.Contains(out, "32") || strings.Contains(out, "38;2") {
t.Errorf("16-color command should use green (32), no truecolor, got %q", out)
}
}
16 changes: 8 additions & 8 deletions internal/ui/spinner_coverage_test.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,26 +26,26 @@ func TestAutoColor(t *testing.T) {
}
})

// NO_COLOR set → false regardless of the writer.
// NO_COLOR set → modeNone regardless of the writer.
_ = os.Setenv("NO_COLOR", "1")
if autoColor(os.Stdout) {
t.Error("NO_COLOR set → autoColor must be false")
if detectMode(os.Stdout) != modeNone {
t.Error("NO_COLOR set → detectMode must be modeNone")
}

// NO_COLOR unset from here on.
_ = os.Unsetenv("NO_COLOR")
// A non-*os.File writer (a buffer) → false.
if autoColor(&bytes.Buffer{}) {
t.Error("a non-*os.File writer → false")
// A non-*os.File writer (a buffer) → modeNone.
if detectMode(&bytes.Buffer{}) != modeNone {
t.Error("a non-*os.File writer → modeNone")
}
// An *os.File that isn't a terminal (a regular temp file) → false.
f, err := os.CreateTemp(t.TempDir(), "ui-*")
if err != nil {
t.Fatal(err)
}
defer func() { _ = f.Close() }()
if autoColor(f) {
t.Error("a non-terminal *os.File → false")
if detectMode(f) != modeNone {
t.Error("a non-terminal *os.File → modeNone")
}
}

Expand Down
Loading
Loading