@fragments-sdk/cli 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/bin.js +18 -13
  2. package/dist/bin.js.map +1 -1
  3. package/dist/{chunk-MUZ6CM66.js → chunk-5JNME72P.js} +3 -3
  4. package/dist/{chunk-MUZ6CM66.js.map → chunk-5JNME72P.js.map} +1 -1
  5. package/dist/{chunk-XHNKNI6J.js → chunk-AW7MWOUH.js} +9 -1
  6. package/dist/chunk-AW7MWOUH.js.map +1 -0
  7. package/dist/{chunk-LY2CFFPY.js → chunk-FYIYMXGA.js} +2 -2
  8. package/dist/{chunk-3OTEW66K.js → chunk-LDKNZ55O.js} +4 -4
  9. package/dist/{chunk-BSCG3IP7.js → chunk-NOTYONHY.js} +2 -2
  10. package/dist/{chunk-ACIDZOYW.js → chunk-ODXQAQQX.js} +21 -8
  11. package/dist/chunk-ODXQAQQX.js.map +1 -0
  12. package/dist/{chunk-PMGI7ATF.js → chunk-OZQ7Z6C3.js} +31 -2
  13. package/dist/chunk-OZQ7Z6C3.js.map +1 -0
  14. package/dist/{core-DWKLGY4N.js → core-F3VT277E.js} +5 -3
  15. package/dist/{generate-3LBZANQ3.js → generate-PNIUR75D.js} +4 -4
  16. package/dist/index.d.ts +18 -0
  17. package/dist/index.js +6 -6
  18. package/dist/{init-NKIUCYTG.js → init-ON6WYG66.js} +4 -4
  19. package/dist/mcp-bin.js +8 -3
  20. package/dist/mcp-bin.js.map +1 -1
  21. package/dist/scan-E6U644RS.js +12 -0
  22. package/dist/{service-QSZMZJBJ.js → service-U7AR2PC2.js} +4 -4
  23. package/dist/{static-viewer-MIPGZ4Z7.js → static-viewer-QL2SCWYB.js} +4 -4
  24. package/dist/{test-ZCTR4LBB.js → test-PBPKJ4WJ.js} +3 -3
  25. package/dist/{tokens-5JQ5IOR2.js → tokens-4J4PRIGT.js} +5 -5
  26. package/dist/{viewer-D7QC4GM2.js → viewer-6VCZMA3T.js} +13 -13
  27. package/package.json +1 -1
  28. package/src/bin.ts +7 -1
  29. package/src/build.ts +16 -0
  30. package/src/core/index.ts +4 -0
  31. package/src/core/parser.ts +54 -1
  32. package/src/core/schema.ts +11 -0
  33. package/src/core/types.ts +27 -0
  34. package/src/mcp/server.ts +11 -1
  35. package/src/migrate/bin.ts +7 -1
  36. package/src/migrate/report.ts +1 -1
  37. package/src/service/report.ts +1 -1
  38. package/src/theme/__tests__/generator.test.ts +412 -0
  39. package/src/theme/__tests__/presets.test.ts +169 -0
  40. package/src/theme/__tests__/schema.test.ts +463 -0
  41. package/src/theme/__tests__/serializer.test.ts +326 -0
  42. package/src/theme/generator.ts +355 -0
  43. package/src/theme/index.ts +61 -0
  44. package/src/theme/presets.ts +189 -0
  45. package/src/theme/schema.ts +193 -0
  46. package/src/theme/serializer.ts +123 -0
  47. package/src/theme/types.ts +210 -0
  48. package/src/viewer/styles/globals.css +1 -1
  49. package/dist/chunk-ACIDZOYW.js.map +0 -1
  50. package/dist/chunk-PMGI7ATF.js.map +0 -1
  51. package/dist/chunk-XHNKNI6J.js.map +0 -1
  52. package/dist/scan-3ZAOVO4U.js +0 -12
  53. /package/dist/{chunk-LY2CFFPY.js.map → chunk-FYIYMXGA.js.map} +0 -0
  54. /package/dist/{chunk-3OTEW66K.js.map → chunk-LDKNZ55O.js.map} +0 -0
  55. /package/dist/{chunk-BSCG3IP7.js.map → chunk-NOTYONHY.js.map} +0 -0
  56. /package/dist/{core-DWKLGY4N.js.map → core-F3VT277E.js.map} +0 -0
  57. /package/dist/{generate-3LBZANQ3.js.map → generate-PNIUR75D.js.map} +0 -0
  58. /package/dist/{init-NKIUCYTG.js.map → init-ON6WYG66.js.map} +0 -0
  59. /package/dist/{scan-3ZAOVO4U.js.map → scan-E6U644RS.js.map} +0 -0
  60. /package/dist/{service-QSZMZJBJ.js.map → service-U7AR2PC2.js.map} +0 -0
  61. /package/dist/{static-viewer-MIPGZ4Z7.js.map → static-viewer-QL2SCWYB.js.map} +0 -0
  62. /package/dist/{test-ZCTR4LBB.js.map → test-PBPKJ4WJ.js.map} +0 -0
  63. /package/dist/{tokens-5JQ5IOR2.js.map → tokens-4J4PRIGT.js.map} +0 -0
  64. /package/dist/{viewer-D7QC4GM2.js.map → viewer-6VCZMA3T.js.map} +0 -0
@@ -0,0 +1,169 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import {
3
+ DEFAULT_PRESET,
4
+ PRESETS,
5
+ getPreset,
6
+ listPresets,
7
+ isValidPresetName,
8
+ } from "../presets.js";
9
+ import { validateThemeConfig } from "../schema.js";
10
+
11
+ describe("Theme Presets", () => {
12
+ describe("DEFAULT_PRESET", () => {
13
+ it("should match _variables.scss values for colors", () => {
14
+ // These values come from libs/ui/src/tokens/_variables.scss
15
+ expect(DEFAULT_PRESET.colors?.accent).toBe("#6366f1");
16
+ expect(DEFAULT_PRESET.colors?.accentHover).toBe("#4f46e5");
17
+ expect(DEFAULT_PRESET.colors?.accentActive).toBe("#4338ca");
18
+ expect(DEFAULT_PRESET.colors?.danger).toBe("#ef4444");
19
+ expect(DEFAULT_PRESET.colors?.dangerHover).toBe("#dc2626");
20
+ expect(DEFAULT_PRESET.colors?.success).toBe("#22c55e");
21
+ expect(DEFAULT_PRESET.colors?.warning).toBe("#f59e0b");
22
+ expect(DEFAULT_PRESET.colors?.info).toBe("#3b82f6");
23
+ });
24
+
25
+ it("should match _variables.scss values for surfaces", () => {
26
+ expect(DEFAULT_PRESET.surfaces?.bgPrimary).toBe("#ffffff");
27
+ expect(DEFAULT_PRESET.surfaces?.bgSecondary).toBe("#f8fafc");
28
+ expect(DEFAULT_PRESET.surfaces?.bgTertiary).toBe("#f1f5f9");
29
+ expect(DEFAULT_PRESET.surfaces?.bgElevated).toBe("#ffffff");
30
+ });
31
+
32
+ it("should match _variables.scss values for text", () => {
33
+ expect(DEFAULT_PRESET.text?.primary).toBe("#0f172a");
34
+ expect(DEFAULT_PRESET.text?.secondary).toBe("#64748b");
35
+ expect(DEFAULT_PRESET.text?.tertiary).toBe("#94a3b8");
36
+ expect(DEFAULT_PRESET.text?.inverse).toBe("#ffffff");
37
+ });
38
+
39
+ it("should match _variables.scss values for borders", () => {
40
+ expect(DEFAULT_PRESET.borders?.default).toBe("#e2e8f0");
41
+ expect(DEFAULT_PRESET.borders?.strong).toBe("#cbd5e1");
42
+ });
43
+
44
+ it("should match _variables.scss values for radius", () => {
45
+ expect(DEFAULT_PRESET.radius?.sm).toBe("0.25rem");
46
+ expect(DEFAULT_PRESET.radius?.md).toBe("0.375rem");
47
+ expect(DEFAULT_PRESET.radius?.lg).toBe("0.5rem");
48
+ expect(DEFAULT_PRESET.radius?.full).toBe("9999px");
49
+ });
50
+
51
+ it("should match _variables.scss values for dark mode", () => {
52
+ expect(DEFAULT_PRESET.dark?.surfaces?.bgPrimary).toBe("#0f172a");
53
+ expect(DEFAULT_PRESET.dark?.surfaces?.bgSecondary).toBe("#1e293b");
54
+ expect(DEFAULT_PRESET.dark?.text?.primary).toBe("#f8fafc");
55
+ expect(DEFAULT_PRESET.dark?.borders?.default).toBe("#334155");
56
+ });
57
+
58
+ it("should be a valid ThemeConfig", () => {
59
+ const result = validateThemeConfig(DEFAULT_PRESET);
60
+ expect(result.success).toBe(true);
61
+ });
62
+ });
63
+
64
+ describe("all presets", () => {
65
+ it("all presets should be valid ThemeConfigs", () => {
66
+ for (const [name, preset] of Object.entries(PRESETS)) {
67
+ const result = validateThemeConfig(preset);
68
+ expect(result.success).toBe(true);
69
+ if (!result.success) {
70
+ console.error(`Preset "${name}" is invalid:`, result.error);
71
+ }
72
+ }
73
+ });
74
+
75
+ it("all presets should have a name", () => {
76
+ for (const [key, preset] of Object.entries(PRESETS)) {
77
+ expect(preset.name).toBeDefined();
78
+ expect(preset.name.length).toBeGreaterThan(0);
79
+ }
80
+ });
81
+
82
+ it("all presets should extend default", () => {
83
+ for (const [key, preset] of Object.entries(PRESETS)) {
84
+ if (key !== "default") {
85
+ expect(preset.extends).toBe("default");
86
+ }
87
+ }
88
+ });
89
+ });
90
+
91
+ describe("getPreset", () => {
92
+ it('should return DEFAULT_PRESET for "default"', () => {
93
+ const preset = getPreset("default");
94
+ expect(preset).toBe(DEFAULT_PRESET);
95
+ });
96
+
97
+ it('should return null for "unknown"', () => {
98
+ const preset = getPreset("unknown");
99
+ expect(preset).toBeNull();
100
+ });
101
+
102
+ it("should return the correct preset for each built-in name", () => {
103
+ const names = ["default", "neutral", "slate", "emerald", "rose"];
104
+ for (const name of names) {
105
+ const preset = getPreset(name);
106
+ expect(preset).not.toBeNull();
107
+ expect(preset?.name).toBeDefined();
108
+ }
109
+ });
110
+ });
111
+
112
+ describe("listPresets", () => {
113
+ it('should include "default"', () => {
114
+ const presets = listPresets();
115
+ expect(presets).toContain("default");
116
+ });
117
+
118
+ it("should return all built-in preset names", () => {
119
+ const presets = listPresets();
120
+ expect(presets).toContain("default");
121
+ expect(presets).toContain("neutral");
122
+ expect(presets).toContain("slate");
123
+ expect(presets).toContain("emerald");
124
+ expect(presets).toContain("rose");
125
+ });
126
+
127
+ it("should match the keys in PRESETS", () => {
128
+ const presets = listPresets();
129
+ const keys = Object.keys(PRESETS);
130
+ expect(presets.sort()).toEqual(keys.sort());
131
+ });
132
+ });
133
+
134
+ describe("isValidPresetName", () => {
135
+ it("should return true for valid preset names", () => {
136
+ expect(isValidPresetName("default")).toBe(true);
137
+ expect(isValidPresetName("neutral")).toBe(true);
138
+ expect(isValidPresetName("slate")).toBe(true);
139
+ expect(isValidPresetName("emerald")).toBe(true);
140
+ expect(isValidPresetName("rose")).toBe(true);
141
+ });
142
+
143
+ it("should return false for invalid preset names", () => {
144
+ expect(isValidPresetName("unknown")).toBe(false);
145
+ expect(isValidPresetName("")).toBe(false);
146
+ expect(isValidPresetName("Default")).toBe(false); // case sensitive
147
+ });
148
+ });
149
+
150
+ describe("preset variations", () => {
151
+ it("neutral preset should have gray-based accent", () => {
152
+ const neutral = getPreset("neutral");
153
+ expect(neutral?.colors?.accent).toBeDefined();
154
+ // Neutral uses gray/zinc colors
155
+ });
156
+
157
+ it("emerald preset should have green-based accent", () => {
158
+ const emerald = getPreset("emerald");
159
+ expect(emerald?.colors?.accent).toBeDefined();
160
+ // Emerald uses green colors
161
+ });
162
+
163
+ it("rose preset should have pink-based accent", () => {
164
+ const rose = getPreset("rose");
165
+ expect(rose?.colors?.accent).toBeDefined();
166
+ // Rose uses pink colors
167
+ });
168
+ });
169
+ });
@@ -0,0 +1,463 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import {
3
+ themeConfigSchema,
4
+ validateThemeConfig,
5
+ themeColorsSchema,
6
+ themeSurfacesSchema,
7
+ themeTextSchema,
8
+ themeBordersSchema,
9
+ themeTypographySchema,
10
+ themeRadiusSchema,
11
+ themeShadowsSchema,
12
+ themeDarkModeSchema,
13
+ } from "../schema.js";
14
+ import type { ThemeConfig } from "../types.js";
15
+
16
+ describe("ThemeConfig Schema", () => {
17
+ describe("validation", () => {
18
+ it("should accept valid minimal theme (name only)", () => {
19
+ const theme: ThemeConfig = {
20
+ name: "My Theme",
21
+ };
22
+
23
+ const result = themeConfigSchema.safeParse(theme);
24
+ expect(result.success).toBe(true);
25
+ });
26
+
27
+ it("should require name field", () => {
28
+ const theme = {};
29
+
30
+ const result = themeConfigSchema.safeParse(theme);
31
+ expect(result.success).toBe(false);
32
+ if (!result.success) {
33
+ expect(result.error.issues[0].path).toContain("name");
34
+ }
35
+ });
36
+
37
+ it("should validate hex color format (#rrggbb)", () => {
38
+ const theme: ThemeConfig = {
39
+ name: "Test",
40
+ colors: {
41
+ accent: "#6366f1",
42
+ },
43
+ };
44
+
45
+ const result = themeConfigSchema.safeParse(theme);
46
+ expect(result.success).toBe(true);
47
+ });
48
+
49
+ it("should validate 3-digit hex color format (#rgb)", () => {
50
+ const theme: ThemeConfig = {
51
+ name: "Test",
52
+ colors: {
53
+ accent: "#f00",
54
+ },
55
+ };
56
+
57
+ const result = themeConfigSchema.safeParse(theme);
58
+ expect(result.success).toBe(true);
59
+ });
60
+
61
+ it("should validate rgb/rgba color format", () => {
62
+ const theme: ThemeConfig = {
63
+ name: "Test",
64
+ colors: {
65
+ accent: "rgb(99, 102, 241)",
66
+ accentHover: "rgba(79, 70, 229, 0.9)",
67
+ },
68
+ };
69
+
70
+ const result = themeConfigSchema.safeParse(theme);
71
+ expect(result.success).toBe(true);
72
+ });
73
+
74
+ it("should validate hsl/hsla color format", () => {
75
+ const theme: ThemeConfig = {
76
+ name: "Test",
77
+ colors: {
78
+ accent: "hsl(239, 84%, 67%)",
79
+ accentHover: "hsla(243, 75%, 59%, 0.9)",
80
+ },
81
+ };
82
+
83
+ const result = themeConfigSchema.safeParse(theme);
84
+ expect(result.success).toBe(true);
85
+ });
86
+
87
+ it("should reject invalid color strings", () => {
88
+ const theme = {
89
+ name: "Test",
90
+ colors: {
91
+ accent: "not-a-color",
92
+ },
93
+ };
94
+
95
+ const result = themeConfigSchema.safeParse(theme);
96
+ expect(result.success).toBe(false);
97
+ });
98
+
99
+ it("should validate size formats (px, rem)", () => {
100
+ const theme: ThemeConfig = {
101
+ name: "Test",
102
+ radius: {
103
+ sm: "4px",
104
+ md: "0.375rem",
105
+ lg: "8px",
106
+ },
107
+ };
108
+
109
+ const result = themeConfigSchema.safeParse(theme);
110
+ expect(result.success).toBe(true);
111
+ });
112
+
113
+ it("should validate font weight range (100-900)", () => {
114
+ const theme: ThemeConfig = {
115
+ name: "Test",
116
+ typography: {
117
+ fontWeightNormal: 400,
118
+ fontWeightMedium: 500,
119
+ fontWeightSemibold: 600,
120
+ },
121
+ };
122
+
123
+ const result = themeConfigSchema.safeParse(theme);
124
+ expect(result.success).toBe(true);
125
+ });
126
+
127
+ it("should reject font weight outside range", () => {
128
+ const theme = {
129
+ name: "Test",
130
+ typography: {
131
+ fontWeightNormal: 1000,
132
+ },
133
+ };
134
+
135
+ const result = themeConfigSchema.safeParse(theme);
136
+ expect(result.success).toBe(false);
137
+ });
138
+
139
+ it("should accept extends: 'default'", () => {
140
+ const theme: ThemeConfig = {
141
+ name: "Test",
142
+ extends: "default",
143
+ };
144
+
145
+ const result = themeConfigSchema.safeParse(theme);
146
+ expect(result.success).toBe(true);
147
+ });
148
+
149
+ it("should accept version field", () => {
150
+ const theme: ThemeConfig = {
151
+ name: "Test",
152
+ version: "1.0.0",
153
+ };
154
+
155
+ const result = themeConfigSchema.safeParse(theme);
156
+ expect(result.success).toBe(true);
157
+ });
158
+ });
159
+
160
+ describe("colors schema", () => {
161
+ it("should validate all color fields", () => {
162
+ const colors = {
163
+ accent: "#6366f1",
164
+ accentHover: "#4f46e5",
165
+ accentActive: "#4338ca",
166
+ danger: "#ef4444",
167
+ dangerHover: "#dc2626",
168
+ success: "#22c55e",
169
+ warning: "#f59e0b",
170
+ info: "#3b82f6",
171
+ dangerBg: "rgba(239, 68, 68, 0.1)",
172
+ successBg: "rgba(34, 197, 94, 0.1)",
173
+ warningBg: "rgba(245, 158, 11, 0.1)",
174
+ infoBg: "rgba(59, 130, 246, 0.1)",
175
+ };
176
+
177
+ const result = themeColorsSchema.safeParse(colors);
178
+ expect(result.success).toBe(true);
179
+ });
180
+
181
+ it("should allow partial colors", () => {
182
+ const colors = {
183
+ accent: "#6366f1",
184
+ };
185
+
186
+ const result = themeColorsSchema.safeParse(colors);
187
+ expect(result.success).toBe(true);
188
+ });
189
+ });
190
+
191
+ describe("surfaces schema", () => {
192
+ it("should validate all surface fields", () => {
193
+ const surfaces = {
194
+ bgPrimary: "#ffffff",
195
+ bgSecondary: "#f8fafc",
196
+ bgTertiary: "#f1f5f9",
197
+ bgElevated: "#ffffff",
198
+ bgHover: "rgba(0, 0, 0, 0.04)",
199
+ bgActive: "rgba(0, 0, 0, 0.06)",
200
+ };
201
+
202
+ const result = themeSurfacesSchema.safeParse(surfaces);
203
+ expect(result.success).toBe(true);
204
+ });
205
+ });
206
+
207
+ describe("text schema", () => {
208
+ it("should validate all text fields", () => {
209
+ const text = {
210
+ primary: "#0f172a",
211
+ secondary: "#64748b",
212
+ tertiary: "#94a3b8",
213
+ inverse: "#ffffff",
214
+ };
215
+
216
+ const result = themeTextSchema.safeParse(text);
217
+ expect(result.success).toBe(true);
218
+ });
219
+ });
220
+
221
+ describe("borders schema", () => {
222
+ it("should validate all border fields", () => {
223
+ const borders = {
224
+ default: "#e2e8f0",
225
+ strong: "#cbd5e1",
226
+ };
227
+
228
+ const result = themeBordersSchema.safeParse(borders);
229
+ expect(result.success).toBe(true);
230
+ });
231
+ });
232
+
233
+ describe("typography schema", () => {
234
+ it("should validate all typography fields", () => {
235
+ const typography = {
236
+ fontSans: "system-ui, -apple-system, sans-serif",
237
+ fontMono: "'SF Mono', monospace",
238
+ fontWeightNormal: 400,
239
+ fontWeightMedium: 500,
240
+ fontWeightSemibold: 600,
241
+ };
242
+
243
+ const result = themeTypographySchema.safeParse(typography);
244
+ expect(result.success).toBe(true);
245
+ });
246
+ });
247
+
248
+ describe("radius schema", () => {
249
+ it("should validate all radius fields", () => {
250
+ const radius = {
251
+ sm: "0.25rem",
252
+ md: "0.375rem",
253
+ lg: "0.5rem",
254
+ full: "9999px",
255
+ };
256
+
257
+ const result = themeRadiusSchema.safeParse(radius);
258
+ expect(result.success).toBe(true);
259
+ });
260
+ });
261
+
262
+ describe("shadows schema", () => {
263
+ it("should validate all shadow fields", () => {
264
+ const shadows = {
265
+ sm: "0 1px 2px 0 rgba(0, 0, 0, 0.05)",
266
+ md: "0 4px 6px -1px rgba(0, 0, 0, 0.1)",
267
+ };
268
+
269
+ const result = themeShadowsSchema.safeParse(shadows);
270
+ expect(result.success).toBe(true);
271
+ });
272
+ });
273
+
274
+ describe("dark mode", () => {
275
+ it("should validate dark mode surface overrides", () => {
276
+ const theme: ThemeConfig = {
277
+ name: "Test",
278
+ dark: {
279
+ surfaces: {
280
+ bgPrimary: "#0f172a",
281
+ bgSecondary: "#1e293b",
282
+ },
283
+ },
284
+ };
285
+
286
+ const result = themeConfigSchema.safeParse(theme);
287
+ expect(result.success).toBe(true);
288
+ });
289
+
290
+ it("should validate dark mode text overrides", () => {
291
+ const theme: ThemeConfig = {
292
+ name: "Test",
293
+ dark: {
294
+ text: {
295
+ primary: "#f8fafc",
296
+ secondary: "#94a3b8",
297
+ },
298
+ },
299
+ };
300
+
301
+ const result = themeConfigSchema.safeParse(theme);
302
+ expect(result.success).toBe(true);
303
+ });
304
+
305
+ it("should validate full dark mode config", () => {
306
+ const darkMode = {
307
+ surfaces: {
308
+ bgPrimary: "#0f172a",
309
+ bgSecondary: "#1e293b",
310
+ bgTertiary: "#334155",
311
+ bgElevated: "#1e293b",
312
+ bgHover: "rgba(255, 255, 255, 0.06)",
313
+ bgActive: "rgba(255, 255, 255, 0.08)",
314
+ },
315
+ text: {
316
+ primary: "#f8fafc",
317
+ secondary: "#94a3b8",
318
+ tertiary: "#64748b",
319
+ inverse: "#0f172a",
320
+ },
321
+ borders: {
322
+ default: "#334155",
323
+ strong: "#475569",
324
+ },
325
+ shadows: {
326
+ sm: "0 1px 2px 0 rgba(0, 0, 0, 0.3)",
327
+ md: "0 4px 6px -1px rgba(0, 0, 0, 0.4)",
328
+ },
329
+ dangerBg: "rgba(239, 68, 68, 0.15)",
330
+ successBg: "rgba(34, 197, 94, 0.15)",
331
+ warningBg: "rgba(245, 158, 11, 0.15)",
332
+ infoBg: "rgba(59, 130, 246, 0.15)",
333
+ backdrop: "rgba(0, 0, 0, 0.7)",
334
+ };
335
+
336
+ const result = themeDarkModeSchema.safeParse(darkMode);
337
+ expect(result.success).toBe(true);
338
+ });
339
+ });
340
+
341
+ describe("validateThemeConfig helper", () => {
342
+ it("should return parsed theme for valid input", () => {
343
+ const theme = {
344
+ name: "Test Theme",
345
+ colors: {
346
+ accent: "#6366f1",
347
+ },
348
+ };
349
+
350
+ const result = validateThemeConfig(theme);
351
+ expect(result.success).toBe(true);
352
+ if (result.success) {
353
+ expect(result.data.name).toBe("Test Theme");
354
+ expect(result.data.colors?.accent).toBe("#6366f1");
355
+ }
356
+ });
357
+
358
+ it("should return error for invalid input", () => {
359
+ const theme = {
360
+ colors: {
361
+ accent: "invalid",
362
+ },
363
+ };
364
+
365
+ const result = validateThemeConfig(theme);
366
+ expect(result.success).toBe(false);
367
+ if (!result.success) {
368
+ expect(result.error).toBeDefined();
369
+ }
370
+ });
371
+ });
372
+
373
+ describe("complex theme validation", () => {
374
+ it("should validate a complete theme config", () => {
375
+ const theme: ThemeConfig = {
376
+ name: "Complete Theme",
377
+ version: "1.0.0",
378
+ extends: "default",
379
+ colors: {
380
+ accent: "#6366f1",
381
+ accentHover: "#4f46e5",
382
+ accentActive: "#4338ca",
383
+ danger: "#ef4444",
384
+ dangerHover: "#dc2626",
385
+ success: "#22c55e",
386
+ warning: "#f59e0b",
387
+ info: "#3b82f6",
388
+ dangerBg: "rgba(239, 68, 68, 0.1)",
389
+ successBg: "rgba(34, 197, 94, 0.1)",
390
+ warningBg: "rgba(245, 158, 11, 0.1)",
391
+ infoBg: "rgba(59, 130, 246, 0.1)",
392
+ },
393
+ surfaces: {
394
+ bgPrimary: "#ffffff",
395
+ bgSecondary: "#f8fafc",
396
+ bgTertiary: "#f1f5f9",
397
+ bgElevated: "#ffffff",
398
+ bgHover: "rgba(0, 0, 0, 0.04)",
399
+ bgActive: "rgba(0, 0, 0, 0.06)",
400
+ },
401
+ text: {
402
+ primary: "#0f172a",
403
+ secondary: "#64748b",
404
+ tertiary: "#94a3b8",
405
+ inverse: "#ffffff",
406
+ },
407
+ borders: {
408
+ default: "#e2e8f0",
409
+ strong: "#cbd5e1",
410
+ },
411
+ typography: {
412
+ fontSans: "system-ui, -apple-system, sans-serif",
413
+ fontMono: "'SF Mono', monospace",
414
+ fontWeightNormal: 400,
415
+ fontWeightMedium: 500,
416
+ fontWeightSemibold: 600,
417
+ },
418
+ radius: {
419
+ sm: "0.25rem",
420
+ md: "0.375rem",
421
+ lg: "0.5rem",
422
+ full: "9999px",
423
+ },
424
+ shadows: {
425
+ sm: "0 1px 2px 0 rgba(0, 0, 0, 0.05)",
426
+ md: "0 4px 6px -1px rgba(0, 0, 0, 0.1)",
427
+ },
428
+ dark: {
429
+ surfaces: {
430
+ bgPrimary: "#0f172a",
431
+ bgSecondary: "#1e293b",
432
+ bgTertiary: "#334155",
433
+ bgElevated: "#1e293b",
434
+ bgHover: "rgba(255, 255, 255, 0.06)",
435
+ bgActive: "rgba(255, 255, 255, 0.08)",
436
+ },
437
+ text: {
438
+ primary: "#f8fafc",
439
+ secondary: "#94a3b8",
440
+ tertiary: "#64748b",
441
+ inverse: "#0f172a",
442
+ },
443
+ borders: {
444
+ default: "#334155",
445
+ strong: "#475569",
446
+ },
447
+ shadows: {
448
+ sm: "0 1px 2px 0 rgba(0, 0, 0, 0.3)",
449
+ md: "0 4px 6px -1px rgba(0, 0, 0, 0.4)",
450
+ },
451
+ dangerBg: "rgba(239, 68, 68, 0.15)",
452
+ successBg: "rgba(34, 197, 94, 0.15)",
453
+ warningBg: "rgba(245, 158, 11, 0.15)",
454
+ infoBg: "rgba(59, 130, 246, 0.15)",
455
+ backdrop: "rgba(0, 0, 0, 0.7)",
456
+ },
457
+ };
458
+
459
+ const result = themeConfigSchema.safeParse(theme);
460
+ expect(result.success).toBe(true);
461
+ });
462
+ });
463
+ });