mirror of
https://github.com/foomo/posh.git
synced 2026-10-06 06:57:03 +00:00
193 lines
5.8 KiB
Go
193 lines
5.8 KiB
Go
// Tests in this package mutate gookit's detected color level, so they save and
|
|
// restore it in t.Cleanup and must not call t.Parallel — a leaked mutation
|
|
// surfaces as a confusing failure in an unrelated package.
|
|
package theme_test
|
|
|
|
import (
|
|
"regexp"
|
|
"testing"
|
|
|
|
"github.com/foomo/posh/pkg/prompt/goprompt"
|
|
"github.com/foomo/posh/pkg/theme"
|
|
"github.com/gookit/color"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
var hexRe = regexp.MustCompile(`^#[0-9a-f]{6}$`)
|
|
|
|
// forceLevel pins gookit's color level for the duration of a test.
|
|
func forceLevel(t *testing.T, level color.Level) {
|
|
t.Helper()
|
|
|
|
prevLevel, prevEnable := color.TermColorLevel(), color.Enable
|
|
color.Enable = true
|
|
|
|
color.ForceSetColorLevel(level)
|
|
|
|
t.Cleanup(func() {
|
|
color.ForceSetColorLevel(prevLevel)
|
|
|
|
color.Enable = prevEnable
|
|
})
|
|
}
|
|
|
|
func TestPalettesAreComplete(t *testing.T) {
|
|
forceLevel(t, color.Level256)
|
|
|
|
for _, flavor := range theme.Flavors() {
|
|
t.Run(flavor.String(), func(t *testing.T) {
|
|
subject, err := theme.New(theme.WithFlavor(flavor))
|
|
require.NoError(t, err)
|
|
|
|
for _, role := range theme.Roles() {
|
|
assert.Regexp(t, hexRe, subject.Hex(role), "role %s has no hex", role)
|
|
assert.NotEqual(t, "unknown", role.String())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestPromptColorsAreComplete covers the tier the prompt actually renders: a
|
|
// missing entry would silently fall back to the terminal default.
|
|
func TestPromptColorsAreComplete(t *testing.T) {
|
|
forceLevel(t, color.Level256)
|
|
|
|
for _, flavor := range theme.Flavors() {
|
|
t.Run(flavor.String(), func(t *testing.T) {
|
|
subject, err := theme.New(theme.WithFlavor(flavor))
|
|
require.NoError(t, err)
|
|
|
|
for _, role := range theme.Roles() {
|
|
assert.NotEqual(t, goprompt.DefaultColor, subject.Prompt(role),
|
|
"role %s falls back to the default color", role)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestPromptColorsMatchCatppuccinANSI pins the 16-color mapping to Catppuccin's
|
|
// own published ANSI slots (the `ansiColors` block of catppuccin/palette).
|
|
//
|
|
// That mapping puts each palette's signature colors in the *normal* SGR slots
|
|
// and variant shades in the bright ones, so a role mapped to a bright slot
|
|
// renders a different shade than intended on a Catppuccin terminal — which is
|
|
// exactly the setup this tier exists to serve. An earlier version of the table
|
|
// used bright slots throughout for the dark flavors and was one shade off on
|
|
// six of the nine roles.
|
|
func TestPromptColorsMatchCatppuccinANSI(t *testing.T) {
|
|
forceLevel(t, color.Level256)
|
|
|
|
// The roles whose Catppuccin color IS the normal ANSI slot for that hue,
|
|
// so the expectation is exact rather than nearest-neighbour.
|
|
exact := map[theme.Role]goprompt.Color{
|
|
theme.RoleSuccess: goprompt.DarkGreen, // green
|
|
theme.RoleWarning: goprompt.Brown, // yellow
|
|
theme.RoleError: goprompt.DarkRed, // red
|
|
}
|
|
|
|
for _, flavor := range theme.Flavors() {
|
|
t.Run(flavor.String(), func(t *testing.T) {
|
|
subject, err := theme.New(theme.WithFlavor(flavor))
|
|
require.NoError(t, err)
|
|
|
|
for role, want := range exact {
|
|
assert.Equal(t, want, subject.Prompt(role),
|
|
"role %s must use the normal ANSI slot so a Catppuccin terminal renders the exact palette color", role)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestFlavorsDiffer pins the user-visible promise: a light flavor must not
|
|
// render like a dark one.
|
|
func TestFlavorsDiffer(t *testing.T) {
|
|
forceLevel(t, color.Level256)
|
|
|
|
mocha, err := theme.New(theme.WithFlavor(theme.FlavorMocha))
|
|
require.NoError(t, err)
|
|
|
|
latte, err := theme.New(theme.WithFlavor(theme.FlavorLatte))
|
|
require.NoError(t, err)
|
|
|
|
assert.True(t, mocha.IsDark())
|
|
assert.False(t, latte.IsDark())
|
|
|
|
for _, role := range []theme.Role{theme.RoleText, theme.RolePrimary, theme.RoleError, theme.RoleMuted} {
|
|
assert.NotEqual(t, mocha.Hex(role), latte.Hex(role), "role %s should differ between flavors", role)
|
|
}
|
|
|
|
// The text role in particular has to flip, or the input line is illegible
|
|
// on one of the two backgrounds.
|
|
assert.NotEqual(t, mocha.Prompt(theme.RoleText), latte.Prompt(theme.RoleText))
|
|
}
|
|
|
|
func TestNoColorYieldsDefaults(t *testing.T) {
|
|
forceLevel(t, color.Level256)
|
|
|
|
subject, err := theme.New(theme.WithFlavor(theme.FlavorMocha), theme.WithCapability(theme.CapabilityNone))
|
|
require.NoError(t, err)
|
|
|
|
for _, role := range theme.Roles() {
|
|
assert.Equal(t, goprompt.DefaultColor, subject.Prompt(role))
|
|
}
|
|
}
|
|
|
|
func TestParseFlavor(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
want theme.Flavor
|
|
wantErr bool
|
|
}{
|
|
{name: "lowercase", input: "mocha", want: theme.FlavorMocha},
|
|
{name: "mixed case", input: "Latte", want: theme.FlavorLatte},
|
|
{name: "surrounding space", input: " frappe ", want: theme.FlavorFrappe},
|
|
{name: "macchiato", input: "macchiato", want: theme.FlavorMacchiato},
|
|
{name: "unknown", input: "solarized", wantErr: true},
|
|
{name: "empty", input: "", wantErr: true},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, err := theme.ParseFlavor(tt.input)
|
|
if tt.wantErr {
|
|
require.Error(t, err)
|
|
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tt.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestResolveUnconfigured is the whole opt-in contract: no flavor means no
|
|
// theme, which callers use to keep their previous colors untouched.
|
|
func TestResolveUnconfigured(t *testing.T) {
|
|
forceLevel(t, color.Level256)
|
|
|
|
subject, err := theme.Resolve("")
|
|
require.NoError(t, err)
|
|
assert.Nil(t, subject)
|
|
}
|
|
|
|
func TestResolveConfigured(t *testing.T) {
|
|
forceLevel(t, color.Level256)
|
|
|
|
subject, err := theme.Resolve("latte")
|
|
require.NoError(t, err)
|
|
require.NotNil(t, subject)
|
|
assert.Equal(t, theme.FlavorLatte, subject.Flavor())
|
|
}
|
|
|
|
// TestResolveRejectsUnknown covers a typo in something the user wrote: it must
|
|
// surface rather than silently render a different theme.
|
|
func TestResolveRejectsUnknown(t *testing.T) {
|
|
forceLevel(t, color.Level256)
|
|
|
|
_, err := theme.Resolve("nord")
|
|
require.Error(t, err)
|
|
}
|