Add tmux status bar theming for Gas Town sessions: - Per-rig color themes auto-assigned via consistent hashing - 10 curated dark themes (ocean, forest, rust, plum, etc.) - Special gold/dark theme for Mayor - Dynamic status line showing current issue and mail count - Mayor status shows polecat/rig counts New commands: - gt theme --list: show available themes - gt theme apply: apply to running sessions - gt issue set/clear: agents update their current issue - gt status-line: internal command for tmux refresh Status bar format: - Left: [rig/worker] role - Right: <issue> | <mail> | HH:MM 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
128 lines
3.3 KiB
Go
128 lines
3.3 KiB
Go
package tmux
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
func TestAssignTheme_Deterministic(t *testing.T) {
|
|
// Same rig name should always get same theme
|
|
theme1 := AssignTheme("gastown")
|
|
theme2 := AssignTheme("gastown")
|
|
|
|
if theme1.Name != theme2.Name {
|
|
t.Errorf("AssignTheme not deterministic: got %s and %s for same input", theme1.Name, theme2.Name)
|
|
}
|
|
}
|
|
|
|
func TestAssignTheme_Distribution(t *testing.T) {
|
|
// Different rig names should (mostly) get different themes
|
|
// With 10 themes and good hashing, collisions should be rare
|
|
rigs := []string{"gastown", "beads", "myproject", "frontend", "backend", "api", "web", "mobile"}
|
|
themes := make(map[string]int)
|
|
|
|
for _, rig := range rigs {
|
|
theme := AssignTheme(rig)
|
|
themes[theme.Name]++
|
|
}
|
|
|
|
// We should have at least 4 different themes for 8 rigs
|
|
if len(themes) < 4 {
|
|
t.Errorf("Poor distribution: only %d different themes for %d rigs", len(themes), len(rigs))
|
|
}
|
|
}
|
|
|
|
func TestGetThemeByName(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
want bool
|
|
}{
|
|
{"ocean", true},
|
|
{"forest", true},
|
|
{"nonexistent", false},
|
|
{"", false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
theme := GetThemeByName(tt.name)
|
|
got := theme != nil
|
|
if got != tt.want {
|
|
t.Errorf("GetThemeByName(%q) = %v, want %v", tt.name, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestThemeStyle(t *testing.T) {
|
|
theme := Theme{Name: "test", BG: "#1e3a5f", FG: "#e0e0e0"}
|
|
want := "bg=#1e3a5f,fg=#e0e0e0"
|
|
got := theme.Style()
|
|
|
|
if got != want {
|
|
t.Errorf("Theme.Style() = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestMayorTheme(t *testing.T) {
|
|
theme := MayorTheme()
|
|
|
|
if theme.Name != "mayor" {
|
|
t.Errorf("MayorTheme().Name = %q, want %q", theme.Name, "mayor")
|
|
}
|
|
|
|
// Mayor should have distinct gold/dark colors
|
|
if theme.BG == "" || theme.FG == "" {
|
|
t.Error("MayorTheme() has empty colors")
|
|
}
|
|
}
|
|
|
|
func TestListThemeNames(t *testing.T) {
|
|
names := ListThemeNames()
|
|
|
|
if len(names) != len(DefaultPalette) {
|
|
t.Errorf("ListThemeNames() returned %d names, want %d", len(names), len(DefaultPalette))
|
|
}
|
|
|
|
// Check that known themes are in the list
|
|
found := make(map[string]bool)
|
|
for _, name := range names {
|
|
found[name] = true
|
|
}
|
|
|
|
for _, want := range []string{"ocean", "forest", "rust"} {
|
|
if !found[want] {
|
|
t.Errorf("ListThemeNames() missing %q", want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDefaultPaletteHasDistinctColors(t *testing.T) {
|
|
// Ensure no duplicate colors in the palette
|
|
bgColors := make(map[string]string)
|
|
for _, theme := range DefaultPalette {
|
|
if existing, ok := bgColors[theme.BG]; ok {
|
|
t.Errorf("Duplicate BG color %s used by %s and %s", theme.BG, existing, theme.Name)
|
|
}
|
|
bgColors[theme.BG] = theme.Name
|
|
}
|
|
}
|
|
|
|
func TestAssignThemeFromPalette_EmptyPalette(t *testing.T) {
|
|
// Empty palette should return first default theme
|
|
theme := AssignThemeFromPalette("test", []Theme{})
|
|
if theme.Name != DefaultPalette[0].Name {
|
|
t.Errorf("AssignThemeFromPalette with empty palette = %q, want %q", theme.Name, DefaultPalette[0].Name)
|
|
}
|
|
}
|
|
|
|
func TestAssignThemeFromPalette_CustomPalette(t *testing.T) {
|
|
custom := []Theme{
|
|
{Name: "custom1", BG: "#111", FG: "#fff"},
|
|
{Name: "custom2", BG: "#222", FG: "#fff"},
|
|
}
|
|
|
|
// Should only return themes from custom palette
|
|
theme := AssignThemeFromPalette("test", custom)
|
|
if theme.Name != "custom1" && theme.Name != "custom2" {
|
|
t.Errorf("AssignThemeFromPalette returned %q, want one of custom themes", theme.Name)
|
|
}
|
|
}
|