Files
tinty-wrapper/main_test.go
T
2026-09-18 00:21:20 +02:00

244 lines
8.2 KiB
Go

package main
import (
"bytes"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"testing"
)
func TestStateTransitions(t *testing.T) {
value := state{
Current: currentPair{Light: "old-light", Dark: "old-dark"},
Catalog: catalog{
FavoritePairs: []themePair{{Name: "swamp", Dark: "swamp-dark", Light: "swamp-light"}},
FavoriteThemes: []string{"favorite"},
OtherPairs: []themePair{{Name: "nord", Dark: "nord-dark", Light: "nord-light"}},
OtherThemes: []string{"swamp-dark", "nord-light", "other"},
},
}
value.Catalog.normalize()
if len(value.Catalog.OtherThemes) != 1 || value.Catalog.OtherThemes[0] != "other" {
t.Fatalf("pair themes leaked into other themes: %#v", value.Catalog.OtherThemes)
}
if !value.Catalog.favorite("nord") {
t.Fatal("named pair was treated as a theme")
}
value.Catalog.configurePair(themePair{Name: "nord", Dark: "new-dark", Light: "new-light"})
pair, favorite, found := value.Catalog.findPair("nord")
if !found || !favorite || pair.Dark != "new-dark" {
t.Fatalf("reconfigured pair lost favorite state: %#v", value.Catalog)
}
if !value.Catalog.unfavorite("nord") {
t.Fatal("favorite pair was treated as a theme")
}
path := filepath.Join(t.TempDir(), "state.toml")
if err := saveState(path, value); err != nil {
t.Fatal(err)
}
roundTrip, err := loadState(path)
if err != nil {
t.Fatal(err)
}
if roundTrip.Current != value.Current {
t.Fatalf("current pair changed after round trip: %#v", roundTrip.Current)
}
if _, favorite, found := roundTrip.Catalog.findPair("nord"); !found || favorite {
t.Fatalf("pair did not remain in other pairs: %#v", roundTrip.Catalog)
}
config := readFile(t, path)
if strings.Contains(config, "\n ") {
t.Fatalf("configuration is unnecessarily indented:\n%s", config)
}
order := []string{"[current]", "[[favorites.pairs]]", "[favorites]", "[[others.pairs]]", "[others]"}
last := -1
for _, section := range order {
index := strings.Index(config, section)
if index <= last {
t.Fatalf("configuration section order is wrong:\n%s", config)
}
last = index
}
if strings.Contains(config, "current =") || strings.Contains(config, "[[favorite_themes]]") || strings.Contains(config, "[[other_themes]]") {
t.Fatalf("state entries used the wrong TOML shape:\n%s", config)
}
}
func TestCLIUpdatesAndAppliesPair(t *testing.T) {
tmp := t.TempDir()
bin := filepath.Join(tmp, "bin")
if err := os.Mkdir(bin, 0o755); err != nil {
t.Fatal(err)
}
log := filepath.Join(tmp, "applied")
writeExecutable(t, filepath.Join(bin, "tinty"), `#!/bin/sh
case "$1" in
info) [ "$2" != missing ] ;;
apply) printf '%s\n' "$2" >> "$TINTY_LOG" ;;
*) exit 2 ;;
esac
`)
statePath := filepath.Join(tmp, "state.toml")
t.Setenv("PATH", bin+":"+os.Getenv("PATH"))
t.Setenv("TINTY_LOG", log)
t.Setenv("TINT_STATE_FILE", statePath)
t.Setenv("TINTY_PAIR_APPEARANCE", "dark")
if err := saveState(statePath, state{Current: currentPair{Light: "old-light", Dark: "old-dark"}}); err != nil {
t.Fatal(err)
}
var stdout, stderr bytes.Buffer
if err := runCLI([]string{"dark:new-dark"}, &stdout, &stderr); err != nil {
t.Fatalf("update failed: %v: %s", err, stderr.String())
}
value, err := loadState(statePath)
if err != nil || value.Current.Dark != "new-dark" || value.Current.Light != "old-light" {
t.Fatalf("wrong current pair: %#v, %v", value.Current, err)
}
if got := strings.TrimSpace(readFile(t, log)); got != "new-dark" {
t.Fatalf("wrong applied theme: %q", got)
}
if err := runCLI([]string{"pair", "nord", "dark:nord-dark", "light:nord-light"}, &stdout, &stderr); err != nil {
t.Fatal(err)
}
if err := runCLI([]string{"favorite", "nord"}, &stdout, &stderr); err != nil {
t.Fatal(err)
}
if err := runCLI([]string{"nord"}, &stdout, &stderr); err != nil {
t.Fatal(err)
}
value, err = loadState(statePath)
if err != nil || value.Current.Dark != "nord-dark" || value.Current.Light != "nord-light" {
t.Fatalf("named pair not applied: %#v, %v", value.Current, err)
}
if _, favorite, found := value.Catalog.findPair("nord"); !found || !favorite {
t.Fatalf("pair not favorited: %#v", value.Catalog)
}
stdout.Reset()
if err := runCLI([]string{"config"}, &stdout, &stderr); err != nil {
t.Fatal(err)
}
if stdout.String() != readFile(t, statePath) {
t.Fatal("tint config did not print the state file verbatim")
}
}
func TestApplyAppsUsesTinted8Semantics(t *testing.T) {
tmp := t.TempDir()
bin := filepath.Join(tmp, "bin")
if err := os.Mkdir(bin, 0o755); err != nil {
t.Fatal(err)
}
palette := map[string]tintyColor{
"black-normal": {Hex: "#000000"}, "black-bright": {Hex: "#111111"},
"gray-dim": {Hex: "#222222"}, "gray-normal": {Hex: "#333333"}, "gray-bright": {Hex: "#444444"},
"white-normal": {Hex: "#eeeeee"}, "white-bright": {Hex: "#ffffff"},
"red-normal": {Hex: "#aa0000"}, "orange-normal": {Hex: "#bb5500"},
"yellow-normal": {Hex: "#aaaa00"}, "green-normal": {Hex: "#00aa00"},
"cyan-normal": {Hex: "#00aaaa"}, "blue-normal": {Hex: "#0000aa"},
"magenta-normal": {Hex: "#aa00aa"}, "brown-normal": {Hex: "#663300"},
}
scheme := tintyScheme{
ID: "tinted8-test", System: "tinted8", Variant: "dark", Palette: palette,
UI: map[string]tintyColor{
"accent.normal": {Hex: "#123456"}, "global.background.normal": {Hex: "#010101"},
"global.foreground.normal": {Hex: "#fefefe"},
},
Syntax: map[string]tintyColor{"entity.name.function": {Hex: "#654321"}},
}
listPath := filepath.Join(tmp, "schemes.json")
data, err := json.Marshal([]tintyScheme{scheme})
if err != nil {
t.Fatal(err)
}
if err := os.WriteFile(listPath, data, 0o644); err != nil {
t.Fatal(err)
}
writeExecutable(t, filepath.Join(bin, "tinty"), `#!/bin/sh
case "$1" in
current) printf '%s\n' "$TINTY_TEST_SCHEME" ;;
list) cat "$TINTY_TEST_LIST" ;;
*) exit 2 ;;
esac
`)
t.Setenv("HOME", tmp)
t.Setenv("PATH", bin+":"+os.Getenv("PATH"))
t.Setenv("TINTY_TEST_SCHEME", scheme.ID)
t.Setenv("TINTY_TEST_LIST", listPath)
if err := os.MkdirAll(filepath.Join(tmp, ".config", "herdr"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(tmp, ".config", "herdr", "config.toml"), []byte("onboarding = false\n"), 0o644); err != nil {
t.Fatal(err)
}
zed := filepath.Join(tmp, "zed.json")
if err := os.WriteFile(zed, []byte(`{"name":"old","themes":[{"name":"old"}]}`), 0o644); err != nil {
t.Fatal(err)
}
if err := applyApps(zed); err != nil {
t.Fatal(err)
}
var omp struct {
Colors map[string]string `json:"colors"`
}
if err := json.Unmarshal([]byte(readFile(t, filepath.Join(tmp, ".omp", "agent", "themes", "tinty-current.json"))), &omp); err != nil {
t.Fatal(err)
}
if omp.Colors["accent"] != "#123456" || omp.Colors["syntaxFunction"] != "#654321" {
t.Fatalf("semantic colors ignored: %#v", omp.Colors)
}
herdr := readFile(t, filepath.Join(tmp, ".config", "herdr", "config.toml"))
if !strings.Contains(herdr, `accent = "#123456"`) {
t.Fatalf("semantic Herdr accent missing:\n%s", herdr)
}
zedOutput := readFile(t, filepath.Join(tmp, ".config", "zed", "themes", "tinty-current.json"))
if !strings.Contains(zedOutput, `"name": "Tinty Current"`) {
t.Fatalf("Zed theme was not renamed:\n%s", zedOutput)
}
}
func TestBase16SemanticFallback(t *testing.T) {
palette := make(map[string]tintyColor)
for i, value := range []string{
"#000000", "#111111", "#222222", "#333333",
"#444444", "#555555", "#666666", "#777777",
"#880000", "#990000", "#aaaa00", "#00bb00",
"#00cccc", "#0000dd", "#ee00ee", "#ff00ff",
} {
palette[fmt.Sprintf("base%02X", i)] = tintyColor{Hex: value}
}
colors, err := semanticColors(tintyScheme{System: "base16", Palette: palette})
if err != nil {
t.Fatal(err)
}
omp, herdr := semanticTheme(colors, nil, nil)
if omp["accent"] != colors["base0D"] || omp["syntaxFunction"] != colors["base0D"] {
t.Fatalf("Base16 OMP fallback changed: %#v", omp)
}
if herdr["panel_bg"] != colors["base00"] || herdr["text"] != colors["base05"] {
t.Fatalf("Base16 Herdr fallback changed: %#v", herdr)
}
}
func writeExecutable(t *testing.T, path, content string) {
t.Helper()
if err := os.WriteFile(path, []byte(content), 0o755); err != nil {
t.Fatal(err)
}
}
func readFile(t *testing.T, path string) string {
t.Helper()
data, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
return string(data)
}