Add the ability to force dark or light mode colors for CLI output, overriding the automatic terminal background detection. Changes: - Add CLITheme field to TownSettings for persisting preference - Add GetThemeMode() and HasDarkBackground() to ui package for theme detection with GT_THEME env var override - Add ApplyThemeMode() to explicitly set lipgloss dark background - Add 'gt theme cli' subcommand to view/set CLI theme preference - Initialize theme in CLI startup (persistentPreRun) - Add comprehensive tests for theme mode functionality Usage: - gt theme cli # show current theme - gt theme cli dark # force dark mode - gt theme cli light # force light mode - gt theme cli auto # auto-detect (default) - GT_THEME=dark gt status # per-command override Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
335 lines
7.6 KiB
Go
335 lines
7.6 KiB
Go
package ui
|
|
|
|
import (
|
|
"os"
|
|
"testing"
|
|
)
|
|
|
|
func TestIsTerminal(t *testing.T) {
|
|
// This test verifies the function doesn't panic
|
|
// The actual result depends on the test environment
|
|
result := IsTerminal()
|
|
// In test environment, this is usually false
|
|
// The important thing is it doesn't crash and returns a bool
|
|
var _ bool = result
|
|
}
|
|
|
|
func TestShouldUseColor_Default(t *testing.T) {
|
|
// Clean environment for this test
|
|
oldNoColor := os.Getenv("NO_COLOR")
|
|
oldClicolor := os.Getenv("CLICOLOR")
|
|
oldClicolorForce := os.Getenv("CLICOLOR_FORCE")
|
|
defer func() {
|
|
if oldNoColor != "" {
|
|
os.Setenv("NO_COLOR", oldNoColor)
|
|
} else {
|
|
os.Unsetenv("NO_COLOR")
|
|
}
|
|
if oldClicolor != "" {
|
|
os.Setenv("CLICOLOR", oldClicolor)
|
|
} else {
|
|
os.Unsetenv("CLICOLOR")
|
|
}
|
|
if oldClicolorForce != "" {
|
|
os.Setenv("CLICOLOR_FORCE", oldClicolorForce)
|
|
} else {
|
|
os.Unsetenv("CLICOLOR_FORCE")
|
|
}
|
|
}()
|
|
|
|
os.Unsetenv("NO_COLOR")
|
|
os.Unsetenv("CLICOLOR")
|
|
os.Unsetenv("CLICOLOR_FORCE")
|
|
|
|
result := ShouldUseColor()
|
|
// In non-TTY test environment, should be false
|
|
_ = result
|
|
}
|
|
|
|
func TestShouldUseColor_NO_COLOR(t *testing.T) {
|
|
oldNoColor := os.Getenv("NO_COLOR")
|
|
defer func() {
|
|
if oldNoColor != "" {
|
|
os.Setenv("NO_COLOR", oldNoColor)
|
|
} else {
|
|
os.Unsetenv("NO_COLOR")
|
|
}
|
|
}()
|
|
|
|
os.Setenv("NO_COLOR", "1")
|
|
if ShouldUseColor() {
|
|
t.Error("ShouldUseColor() should return false when NO_COLOR is set")
|
|
}
|
|
}
|
|
|
|
func TestShouldUseColor_NO_COLOR_AnyValue(t *testing.T) {
|
|
oldNoColor := os.Getenv("NO_COLOR")
|
|
defer func() {
|
|
if oldNoColor != "" {
|
|
os.Setenv("NO_COLOR", oldNoColor)
|
|
} else {
|
|
os.Unsetenv("NO_COLOR")
|
|
}
|
|
}()
|
|
|
|
// NO_COLOR with any value (even "0") should disable color
|
|
os.Setenv("NO_COLOR", "0")
|
|
if ShouldUseColor() {
|
|
t.Error("ShouldUseColor() should return false when NO_COLOR is set to any value")
|
|
}
|
|
}
|
|
|
|
func TestShouldUseColor_CLICOLOR_0(t *testing.T) {
|
|
oldNoColor := os.Getenv("NO_COLOR")
|
|
oldClicolor := os.Getenv("CLICOLOR")
|
|
defer func() {
|
|
if oldNoColor != "" {
|
|
os.Setenv("NO_COLOR", oldNoColor)
|
|
} else {
|
|
os.Unsetenv("NO_COLOR")
|
|
}
|
|
if oldClicolor != "" {
|
|
os.Setenv("CLICOLOR", oldClicolor)
|
|
} else {
|
|
os.Unsetenv("CLICOLOR")
|
|
}
|
|
}()
|
|
|
|
os.Unsetenv("NO_COLOR")
|
|
os.Setenv("CLICOLOR", "0")
|
|
if ShouldUseColor() {
|
|
t.Error("ShouldUseColor() should return false when CLICOLOR=0")
|
|
}
|
|
}
|
|
|
|
func TestShouldUseColor_CLICOLOR_FORCE(t *testing.T) {
|
|
oldNoColor := os.Getenv("NO_COLOR")
|
|
oldClicolorForce := os.Getenv("CLICOLOR_FORCE")
|
|
defer func() {
|
|
if oldNoColor != "" {
|
|
os.Setenv("NO_COLOR", oldNoColor)
|
|
} else {
|
|
os.Unsetenv("NO_COLOR")
|
|
}
|
|
if oldClicolorForce != "" {
|
|
os.Setenv("CLICOLOR_FORCE", oldClicolorForce)
|
|
} else {
|
|
os.Unsetenv("CLICOLOR_FORCE")
|
|
}
|
|
}()
|
|
|
|
os.Unsetenv("NO_COLOR")
|
|
os.Setenv("CLICOLOR_FORCE", "1")
|
|
if !ShouldUseColor() {
|
|
t.Error("ShouldUseColor() should return true when CLICOLOR_FORCE is set")
|
|
}
|
|
}
|
|
|
|
func TestShouldUseEmoji_Default(t *testing.T) {
|
|
oldNoEmoji := os.Getenv("GT_NO_EMOJI")
|
|
defer func() {
|
|
if oldNoEmoji != "" {
|
|
os.Setenv("GT_NO_EMOJI", oldNoEmoji)
|
|
} else {
|
|
os.Unsetenv("GT_NO_EMOJI")
|
|
}
|
|
}()
|
|
|
|
os.Unsetenv("GT_NO_EMOJI")
|
|
result := ShouldUseEmoji()
|
|
_ = result // Result depends on test environment
|
|
}
|
|
|
|
func TestShouldUseEmoji_GT_NO_EMOJI(t *testing.T) {
|
|
oldNoEmoji := os.Getenv("GT_NO_EMOJI")
|
|
defer func() {
|
|
if oldNoEmoji != "" {
|
|
os.Setenv("GT_NO_EMOJI", oldNoEmoji)
|
|
} else {
|
|
os.Unsetenv("GT_NO_EMOJI")
|
|
}
|
|
}()
|
|
|
|
os.Setenv("GT_NO_EMOJI", "1")
|
|
if ShouldUseEmoji() {
|
|
t.Error("ShouldUseEmoji() should return false when GT_NO_EMOJI is set")
|
|
}
|
|
}
|
|
|
|
func TestIsAgentMode_Default(t *testing.T) {
|
|
oldAgentMode := os.Getenv("GT_AGENT_MODE")
|
|
oldClaudeCode := os.Getenv("CLAUDE_CODE")
|
|
defer func() {
|
|
if oldAgentMode != "" {
|
|
os.Setenv("GT_AGENT_MODE", oldAgentMode)
|
|
} else {
|
|
os.Unsetenv("GT_AGENT_MODE")
|
|
}
|
|
if oldClaudeCode != "" {
|
|
os.Setenv("CLAUDE_CODE", oldClaudeCode)
|
|
} else {
|
|
os.Unsetenv("CLAUDE_CODE")
|
|
}
|
|
}()
|
|
|
|
os.Unsetenv("GT_AGENT_MODE")
|
|
os.Unsetenv("CLAUDE_CODE")
|
|
if IsAgentMode() {
|
|
t.Error("IsAgentMode() should return false by default")
|
|
}
|
|
}
|
|
|
|
func TestIsAgentMode_GT_AGENT_MODE(t *testing.T) {
|
|
oldAgentMode := os.Getenv("GT_AGENT_MODE")
|
|
defer func() {
|
|
if oldAgentMode != "" {
|
|
os.Setenv("GT_AGENT_MODE", oldAgentMode)
|
|
} else {
|
|
os.Unsetenv("GT_AGENT_MODE")
|
|
}
|
|
}()
|
|
|
|
os.Setenv("GT_AGENT_MODE", "1")
|
|
if !IsAgentMode() {
|
|
t.Error("IsAgentMode() should return true when GT_AGENT_MODE=1")
|
|
}
|
|
|
|
os.Setenv("GT_AGENT_MODE", "0")
|
|
if IsAgentMode() {
|
|
t.Error("IsAgentMode() should return false when GT_AGENT_MODE=0")
|
|
}
|
|
}
|
|
|
|
func TestIsAgentMode_CLAUDE_CODE(t *testing.T) {
|
|
oldAgentMode := os.Getenv("GT_AGENT_MODE")
|
|
oldClaudeCode := os.Getenv("CLAUDE_CODE")
|
|
defer func() {
|
|
if oldAgentMode != "" {
|
|
os.Setenv("GT_AGENT_MODE", oldAgentMode)
|
|
} else {
|
|
os.Unsetenv("GT_AGENT_MODE")
|
|
}
|
|
if oldClaudeCode != "" {
|
|
os.Setenv("CLAUDE_CODE", oldClaudeCode)
|
|
} else {
|
|
os.Unsetenv("CLAUDE_CODE")
|
|
}
|
|
}()
|
|
|
|
os.Unsetenv("GT_AGENT_MODE")
|
|
os.Setenv("CLAUDE_CODE", "1")
|
|
if !IsAgentMode() {
|
|
t.Error("IsAgentMode() should return true when CLAUDE_CODE is set")
|
|
}
|
|
}
|
|
|
|
func TestIsAgentMode_CLAUDE_CODE_AnyValue(t *testing.T) {
|
|
oldAgentMode := os.Getenv("GT_AGENT_MODE")
|
|
oldClaudeCode := os.Getenv("CLAUDE_CODE")
|
|
defer func() {
|
|
if oldAgentMode != "" {
|
|
os.Setenv("GT_AGENT_MODE", oldAgentMode)
|
|
} else {
|
|
os.Unsetenv("GT_AGENT_MODE")
|
|
}
|
|
if oldClaudeCode != "" {
|
|
os.Setenv("CLAUDE_CODE", oldClaudeCode)
|
|
} else {
|
|
os.Unsetenv("CLAUDE_CODE")
|
|
}
|
|
}()
|
|
|
|
os.Unsetenv("GT_AGENT_MODE")
|
|
os.Setenv("CLAUDE_CODE", "any-value")
|
|
if !IsAgentMode() {
|
|
t.Error("IsAgentMode() should return true when CLAUDE_CODE is set to any value")
|
|
}
|
|
}
|
|
|
|
func TestInitTheme_EnvOverridesConfig(t *testing.T) {
|
|
oldGTTheme := os.Getenv("GT_THEME")
|
|
defer func() {
|
|
if oldGTTheme != "" {
|
|
os.Setenv("GT_THEME", oldGTTheme)
|
|
} else {
|
|
os.Unsetenv("GT_THEME")
|
|
}
|
|
}()
|
|
|
|
// Test: env var overrides config
|
|
os.Setenv("GT_THEME", "dark")
|
|
InitTheme("light") // config says light
|
|
if GetThemeMode() != ThemeModeDark {
|
|
t.Errorf("Expected dark mode from env var, got %s", GetThemeMode())
|
|
}
|
|
|
|
os.Setenv("GT_THEME", "light")
|
|
InitTheme("dark") // config says dark
|
|
if GetThemeMode() != ThemeModeLight {
|
|
t.Errorf("Expected light mode from env var, got %s", GetThemeMode())
|
|
}
|
|
}
|
|
|
|
func TestInitTheme_ConfigUsedWhenNoEnv(t *testing.T) {
|
|
oldGTTheme := os.Getenv("GT_THEME")
|
|
defer func() {
|
|
if oldGTTheme != "" {
|
|
os.Setenv("GT_THEME", oldGTTheme)
|
|
} else {
|
|
os.Unsetenv("GT_THEME")
|
|
}
|
|
}()
|
|
|
|
os.Unsetenv("GT_THEME")
|
|
|
|
InitTheme("dark")
|
|
if GetThemeMode() != ThemeModeDark {
|
|
t.Errorf("Expected dark mode from config, got %s", GetThemeMode())
|
|
}
|
|
|
|
InitTheme("light")
|
|
if GetThemeMode() != ThemeModeLight {
|
|
t.Errorf("Expected light mode from config, got %s", GetThemeMode())
|
|
}
|
|
}
|
|
|
|
func TestInitTheme_DefaultsToAuto(t *testing.T) {
|
|
oldGTTheme := os.Getenv("GT_THEME")
|
|
defer func() {
|
|
if oldGTTheme != "" {
|
|
os.Setenv("GT_THEME", oldGTTheme)
|
|
} else {
|
|
os.Unsetenv("GT_THEME")
|
|
}
|
|
}()
|
|
|
|
os.Unsetenv("GT_THEME")
|
|
InitTheme("") // no config
|
|
if GetThemeMode() != ThemeModeAuto {
|
|
t.Errorf("Expected auto mode as default, got %s", GetThemeMode())
|
|
}
|
|
}
|
|
|
|
func TestHasDarkBackground_ForcedModes(t *testing.T) {
|
|
oldGTTheme := os.Getenv("GT_THEME")
|
|
defer func() {
|
|
if oldGTTheme != "" {
|
|
os.Setenv("GT_THEME", oldGTTheme)
|
|
} else {
|
|
os.Unsetenv("GT_THEME")
|
|
}
|
|
}()
|
|
|
|
os.Setenv("GT_THEME", "dark")
|
|
InitTheme("")
|
|
if !HasDarkBackground() {
|
|
t.Error("Expected HasDarkBackground() to return true when mode is dark")
|
|
}
|
|
|
|
os.Setenv("GT_THEME", "light")
|
|
InitTheme("")
|
|
if HasDarkBackground() {
|
|
t.Error("Expected HasDarkBackground() to return false when mode is light")
|
|
}
|
|
}
|