package main import ( "os" "path/filepath" "testing" ) // minimalValidPreset returns a presets.json that satisfies the strict decoder // (all known fields, correct types, required id+name). const minimalValidPresetJSON = `[ { "id": "test-preset", "name": "Test Preset", "description": "A minimal test preset", "theme": { "lightColors": { "background": "0 0% 100%", "foreground": "240 10% 3.9%", "card": "0 0% 100%", "cardForeground": "240 10% 3.9%", "popover": "0 0% 100%", "popoverForeground": "240 10% 3.9%", "primary": "240 5.9% 10%", "primaryForeground": "0 0% 98%", "secondary": "240 4.8% 95.9%", "secondaryForeground": "240 5.9% 10%", "muted": "240 4.8% 95.9%", "mutedForeground": "240 3.8% 46.1%", "accent": "240 4.8% 95.9%", "accentForeground": "240 5.9% 10%", "destructive": "0 84.2% 60.2%", "destructiveForeground": "0 0% 98%", "border": "240 5.9% 90%", "input": "240 5.9% 90%", "ring": "240 5.9% 10%" }, "darkColors": { "background": "240 10% 3.9%", "foreground": "0 0% 98%", "card": "240 10% 3.9%", "cardForeground": "0 0% 98%", "popover": "240 10% 3.9%", "popoverForeground": "0 0% 98%", "primary": "0 0% 98%", "primaryForeground": "240 5.9% 10%", "secondary": "240 3.7% 15.9%", "secondaryForeground": "0 0% 98%", "muted": "240 3.7% 15.9%", "mutedForeground": "240 5% 64.9%", "accent": "240 3.7% 15.9%", "accentForeground": "0 0% 98%", "destructive": "0 62.8% 30.6%", "destructiveForeground": "0 0% 98%", "border": "240 3.7% 15.9%", "input": "240 3.7% 15.9%", "ring": "240 4.9% 83.9%" }, "typography": { "fontHeading": "Inter", "fontBody": "Inter", "fontMono": "JetBrains Mono", "fontSizeBase": "1rem", "lineHeightBase": "1.5", "fontWeightBase": "400" }, "spacing": { "radius": "0.5rem", "buttonRadius": "0.5rem", "cardRadius": "0.5rem", "inputRadius": "0.5rem", "pillButtons": false, "borderWidth": "1px", "containerWidth": "1280px", "spacingScale": "1" }, "effects": { "shadowIntensity": 20, "shadowColor": "220 3% 15%", "shadowBlur": "8px", "elevationPreset": "soft", "transitionSpeed": "200ms", "reduceMotion": false, "hoverIntensity": "medium" }, "mode": "light" } } ]` func TestCheckPresets_ValidFile(t *testing.T) { dir := t.TempDir() writeFile(t, dir, "presets.json", minimalValidPresetJSON) violations := checkPresets(dir) if len(violations) != 0 { t.Fatalf("checkPresets() returned %d violations for valid presets.json, want 0: %#v", len(violations), violations) } } func TestCheckPresets_EmptyArray(t *testing.T) { dir := t.TempDir() writeFile(t, dir, "presets.json", `[]`) violations := checkPresets(dir) if len(violations) != 0 { t.Fatalf("checkPresets() returned %d violations for empty array, want 0", len(violations)) } } func TestCheckPresets_MissingID(t *testing.T) { dir := t.TempDir() // Preset with no "id" field (empty string after decode) writeFile(t, dir, "presets.json", `[ { "id": "", "name": "My Preset", "theme": {} } ]`) violations := checkPresets(dir) if len(violations) == 0 { t.Fatal("checkPresets() returned 0 violations for preset with empty id, want >= 1") } found := false for _, v := range violations { if containsStr(v.message, "missing \"id\"") { found = true } } if !found { t.Fatalf("expected 'missing id' violation, got: %#v", violations) } } func TestCheckPresets_MissingName(t *testing.T) { dir := t.TempDir() writeFile(t, dir, "presets.json", `[ { "id": "my-id", "name": "", "theme": {} } ]`) violations := checkPresets(dir) if len(violations) == 0 { t.Fatal("checkPresets() returned 0 violations for preset with empty name, want >= 1") } found := false for _, v := range violations { if containsStr(v.message, "missing \"name\"") { found = true } } if !found { t.Fatalf("expected 'missing name' violation, got: %#v", violations) } } func TestCheckPresets_BothIDAndNameMissing(t *testing.T) { dir := t.TempDir() writeFile(t, dir, "presets.json", `[ { "id": "", "name": "", "theme": {} } ]`) violations := checkPresets(dir) if len(violations) < 2 { t.Fatalf("checkPresets() returned %d violations, want at least 2 (id+name): %#v", len(violations), violations) } } func TestCheckPresets_InvalidJSON(t *testing.T) { dir := t.TempDir() writeFile(t, dir, "presets.json", `{ this is not valid json`) violations := checkPresets(dir) if len(violations) == 0 { t.Fatal("checkPresets() returned 0 violations for invalid JSON, want >= 1") } found := false for _, v := range violations { if containsStr(v.message, "invalid JSON") { found = true } } if !found { t.Fatalf("expected 'invalid JSON' violation, got: %#v", violations) } } func TestCheckPresets_UnknownField(t *testing.T) { dir := t.TempDir() // Valid except for an extra unknown field — triggers "stale preset data" path writeFile(t, dir, "presets.json", `[ { "id": "test", "name": "Test", "unknownExtraField": "oops", "theme": {} } ]`) violations := checkPresets(dir) if len(violations) == 0 { t.Fatal("checkPresets() returned 0 violations for unknown field, want >= 1") } found := false for _, v := range violations { if containsStr(v.message, "unknown fields") || containsStr(v.message, "stale preset data") { found = true } } if !found { t.Fatalf("expected 'unknown fields' violation, got: %#v", violations) } } func TestCheckPresets_NoPresetsFileReturnsClean(t *testing.T) { dir := t.TempDir() // No presets.json at all — should return 0 violations violations := checkPresets(dir) if len(violations) != 0 { t.Fatalf("checkPresets() returned %d violations for dir without presets.json, want 0", len(violations)) } } func TestFindPresetsFiles_SkipsVendorAndWeb(t *testing.T) { root := t.TempDir() // Legitimate presets.json at root level writeFile(t, root, "presets.json", `[]`) // Should be skipped (vendor/) vendorDir := filepath.Join(root, "vendor", "somelib") if err := os.MkdirAll(vendorDir, 0755); err != nil { t.Fatalf("mkdir vendor: %v", err) } writeFile(t, vendorDir, "presets.json", `[]`) // Should be skipped (web/) webDir := filepath.Join(root, "web", "src") if err := os.MkdirAll(webDir, 0755); err != nil { t.Fatalf("mkdir web: %v", err) } writeFile(t, webDir, "presets.json", `[]`) files := findPresetsFiles(root) if len(files) != 1 { t.Fatalf("findPresetsFiles() returned %d files, want 1 (only root-level): %v", len(files), files) } } func TestFindPresetsFiles_FindsInSubdir(t *testing.T) { root := t.TempDir() pluginDir := filepath.Join(root, "internal", "plugins", "myplugin") if err := os.MkdirAll(pluginDir, 0755); err != nil { t.Fatalf("mkdir plugin: %v", err) } writeFile(t, pluginDir, "presets.json", `[]`) files := findPresetsFiles(root) if len(files) != 1 { t.Fatalf("findPresetsFiles() returned %d files, want 1: %v", len(files), files) } } // containsStr is a helper to test substring containment. func containsStr(s, substr string) bool { return len(s) > 0 && len(substr) > 0 && func() bool { for i := 0; i <= len(s)-len(substr); i++ { if s[i:i+len(substr)] == substr { return true } } return false }() }