feat(ui): add StatusIcon helper for colored icon rendering
- commit
- 52e5e4dd04f2031de3310c417c8736fb9e95ed43
- author
- brice · · 2026-03-24 05:36 UTC
- parent
- 9f328d9
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +59 -0
M internal/adapters/in/cli/ui/components/status.go +7 -0
| @@ -188,3 +188,10 @@ func ContainerStatusBadge(status string) string { | |||
| 188 | 188 | parsed := ParseStatus(status) | |
| 189 | 189 | return RenderStatusBadge(parsed) | |
| 190 | 190 | } | |
| 191 | + | ||
| 192 | + | // StatusIcon renders an icon string colored by status. | |
| 193 | + | // Green for success/running, red for error/stopped/exited, muted for unknown/pending. | |
| 194 | + | func StatusIcon(icon string, status Status) string { | |
| 195 | + | config := DefaultStatusConfigs[status] | |
| 196 | + | return config.Style.Render(icon) | |
| 197 | + | } | |
| @@ -188,3 +188,10 @@ func ContainerStatusBadge(status string) string { | |||||
| 188 | parsed := ParseStatus(status) | 188 | parsed := ParseStatus(status) | ||
| 189 | return RenderStatusBadge(parsed) | 189 | return RenderStatusBadge(parsed) | ||
| 190 | } | 190 | } | ||
| 191 | + | ||||
| 192 | + | // StatusIcon renders an icon string colored by status. | |||
| 193 | + | // Green for success/running, red for error/stopped/exited, muted for unknown/pending. | |||
| 194 | + | func StatusIcon(icon string, status Status) string { | |||
| 195 | + | config := DefaultStatusConfigs[status] | |||
| 196 | + | return config.Style.Render(icon) | |||
| 197 | + | } | |||
A internal/adapters/in/cli/ui/components/status_test.go +52 -0
| @@ -0,0 +1,52 @@ | |||
| 1 | + | package components | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "os" | |
| 5 | + | "testing" | |
| 6 | + | ||
| 7 | + | "github.com/bnema/gordon/internal/adapters/in/cli/ui/styles" | |
| 8 | + | "github.com/charmbracelet/lipgloss" | |
| 9 | + | "github.com/muesli/termenv" | |
| 10 | + | "github.com/stretchr/testify/assert" | |
| 11 | + | ) | |
| 12 | + | ||
| 13 | + | func TestMain(m *testing.M) { | |
| 14 | + | // Force TrueColor so lipgloss renders ANSI color codes in tests | |
| 15 | + | // (without a TTY, lipgloss strips all styling). | |
| 16 | + | lipgloss.SetColorProfile(termenv.TrueColor) | |
| 17 | + | os.Exit(m.Run()) | |
| 18 | + | } | |
| 19 | + | ||
| 20 | + | func TestStatusIcon_ReturnsStyledIcon(t *testing.T) { | |
| 21 | + | tests := []struct { | |
| 22 | + | name string | |
| 23 | + | icon string | |
| 24 | + | status Status | |
| 25 | + | }{ | |
| 26 | + | {"success renders without error", styles.IconContainerStatus, StatusSuccess}, | |
| 27 | + | {"running renders without error", styles.IconContainerStatus, StatusRunning}, | |
| 28 | + | {"error renders without error", styles.IconHTTPStatus, StatusError}, | |
| 29 | + | {"stopped renders without error", styles.IconContainerStatus, StatusStopped}, | |
| 30 | + | {"exited renders without error", styles.IconContainerStatus, StatusExited}, | |
| 31 | + | {"unknown renders without error", styles.IconHTTPStatus, StatusUnknown}, | |
| 32 | + | {"pending renders without error", styles.IconHTTPStatus, StatusPending}, | |
| 33 | + | } | |
| 34 | + | ||
| 35 | + | for _, tt := range tests { | |
| 36 | + | t.Run(tt.name, func(t *testing.T) { | |
| 37 | + | result := StatusIcon(tt.icon, tt.status) | |
| 38 | + | assert.NotEmpty(t, result) | |
| 39 | + | assert.Contains(t, result, tt.icon) | |
| 40 | + | }) | |
| 41 | + | } | |
| 42 | + | } | |
| 43 | + | ||
| 44 | + | func TestStatusIcon_DifferentStatusesProduceDifferentOutput(t *testing.T) { | |
| 45 | + | success := StatusIcon(styles.IconContainerStatus, StatusRunning) | |
| 46 | + | failure := StatusIcon(styles.IconContainerStatus, StatusError) | |
| 47 | + | unknown := StatusIcon(styles.IconContainerStatus, StatusUnknown) | |
| 48 | + | ||
| 49 | + | assert.NotEqual(t, success, failure) | |
| 50 | + | assert.NotEqual(t, success, unknown) | |
| 51 | + | assert.NotEqual(t, failure, unknown) | |
| 52 | + | } | |
| @@ -0,0 +1,52 @@ | |||||
| 1 | + | package components | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "os" | |||
| 5 | + | "testing" | |||
| 6 | + | ||||
| 7 | + | "github.com/bnema/gordon/internal/adapters/in/cli/ui/styles" | |||
| 8 | + | "github.com/charmbracelet/lipgloss" | |||
| 9 | + | "github.com/muesli/termenv" | |||
| 10 | + | "github.com/stretchr/testify/assert" | |||
| 11 | + | ) | |||
| 12 | + | ||||
| 13 | + | func TestMain(m *testing.M) { | |||
| 14 | + | // Force TrueColor so lipgloss renders ANSI color codes in tests | |||
| 15 | + | // (without a TTY, lipgloss strips all styling). | |||
| 16 | + | lipgloss.SetColorProfile(termenv.TrueColor) | |||
| 17 | + | os.Exit(m.Run()) | |||
| 18 | + | } | |||
| 19 | + | ||||
| 20 | + | func TestStatusIcon_ReturnsStyledIcon(t *testing.T) { | |||
| 21 | + | tests := []struct { | |||
| 22 | + | name string | |||
| 23 | + | icon string | |||
| 24 | + | status Status | |||
| 25 | + | }{ | |||
| 26 | + | {"success renders without error", styles.IconContainerStatus, StatusSuccess}, | |||
| 27 | + | {"running renders without error", styles.IconContainerStatus, StatusRunning}, | |||
| 28 | + | {"error renders without error", styles.IconHTTPStatus, StatusError}, | |||
| 29 | + | {"stopped renders without error", styles.IconContainerStatus, StatusStopped}, | |||
| 30 | + | {"exited renders without error", styles.IconContainerStatus, StatusExited}, | |||
| 31 | + | {"unknown renders without error", styles.IconHTTPStatus, StatusUnknown}, | |||
| 32 | + | {"pending renders without error", styles.IconHTTPStatus, StatusPending}, | |||
| 33 | + | } | |||
| 34 | + | ||||
| 35 | + | for _, tt := range tests { | |||
| 36 | + | t.Run(tt.name, func(t *testing.T) { | |||
| 37 | + | result := StatusIcon(tt.icon, tt.status) | |||
| 38 | + | assert.NotEmpty(t, result) | |||
| 39 | + | assert.Contains(t, result, tt.icon) | |||
| 40 | + | }) | |||
| 41 | + | } | |||
| 42 | + | } | |||
| 43 | + | ||||
| 44 | + | func TestStatusIcon_DifferentStatusesProduceDifferentOutput(t *testing.T) { | |||
| 45 | + | success := StatusIcon(styles.IconContainerStatus, StatusRunning) | |||
| 46 | + | failure := StatusIcon(styles.IconContainerStatus, StatusError) | |||
| 47 | + | unknown := StatusIcon(styles.IconContainerStatus, StatusUnknown) | |||
| 48 | + | ||||
| 49 | + | assert.NotEqual(t, success, failure) | |||
| 50 | + | assert.NotEqual(t, success, unknown) | |||
| 51 | + | assert.NotEqual(t, failure, unknown) | |||
| 52 | + | } | |||