@fragments-sdk/cli 0.3.3 → 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.
- package/dist/bin.js +18 -13
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-MUZ6CM66.js → chunk-5JNME72P.js} +3 -3
- package/dist/{chunk-MUZ6CM66.js.map → chunk-5JNME72P.js.map} +1 -1
- package/dist/{chunk-XHNKNI6J.js → chunk-AW7MWOUH.js} +9 -1
- package/dist/chunk-AW7MWOUH.js.map +1 -0
- package/dist/{chunk-LY2CFFPY.js → chunk-FYIYMXGA.js} +2 -2
- package/dist/{chunk-3OTEW66K.js → chunk-LDKNZ55O.js} +4 -4
- package/dist/{chunk-BSCG3IP7.js → chunk-NOTYONHY.js} +2 -2
- package/dist/{chunk-D6VXWI45.js → chunk-ODXQAQQX.js} +8 -6
- package/dist/chunk-ODXQAQQX.js.map +1 -0
- package/dist/{chunk-PMGI7ATF.js → chunk-OZQ7Z6C3.js} +31 -2
- package/dist/chunk-OZQ7Z6C3.js.map +1 -0
- package/dist/{core-DWKLGY4N.js → core-F3VT277E.js} +5 -3
- package/dist/{generate-3LBZANQ3.js → generate-PNIUR75D.js} +4 -4
- package/dist/index.d.ts +16 -0
- package/dist/index.js +6 -6
- package/dist/{init-NKIUCYTG.js → init-ON6WYG66.js} +4 -4
- package/dist/mcp-bin.js +3 -3
- package/dist/scan-E6U644RS.js +12 -0
- package/dist/{service-QSZMZJBJ.js → service-U7AR2PC2.js} +4 -4
- package/dist/{static-viewer-MIPGZ4Z7.js → static-viewer-QL2SCWYB.js} +4 -4
- package/dist/{test-ZCTR4LBB.js → test-PBPKJ4WJ.js} +3 -3
- package/dist/{tokens-5JQ5IOR2.js → tokens-4J4PRIGT.js} +5 -5
- package/dist/{viewer-D7QC4GM2.js → viewer-6VCZMA3T.js} +13 -13
- package/package.json +1 -1
- package/src/bin.ts +7 -1
- package/src/build.ts +2 -0
- package/src/core/index.ts +4 -0
- package/src/core/parser.ts +54 -1
- package/src/core/schema.ts +11 -0
- package/src/core/types.ts +24 -0
- package/src/migrate/bin.ts +7 -1
- package/src/migrate/report.ts +1 -1
- package/src/service/report.ts +1 -1
- package/src/theme/__tests__/generator.test.ts +412 -0
- package/src/theme/__tests__/presets.test.ts +169 -0
- package/src/theme/__tests__/schema.test.ts +463 -0
- package/src/theme/__tests__/serializer.test.ts +326 -0
- package/src/theme/generator.ts +355 -0
- package/src/theme/index.ts +61 -0
- package/src/theme/presets.ts +189 -0
- package/src/theme/schema.ts +193 -0
- package/src/theme/serializer.ts +123 -0
- package/src/theme/types.ts +210 -0
- package/src/viewer/styles/globals.css +1 -1
- package/dist/chunk-D6VXWI45.js.map +0 -1
- package/dist/chunk-PMGI7ATF.js.map +0 -1
- package/dist/chunk-XHNKNI6J.js.map +0 -1
- package/dist/scan-3ZAOVO4U.js +0 -12
- /package/dist/{chunk-LY2CFFPY.js.map → chunk-FYIYMXGA.js.map} +0 -0
- /package/dist/{chunk-3OTEW66K.js.map → chunk-LDKNZ55O.js.map} +0 -0
- /package/dist/{chunk-BSCG3IP7.js.map → chunk-NOTYONHY.js.map} +0 -0
- /package/dist/{core-DWKLGY4N.js.map → core-F3VT277E.js.map} +0 -0
- /package/dist/{generate-3LBZANQ3.js.map → generate-PNIUR75D.js.map} +0 -0
- /package/dist/{init-NKIUCYTG.js.map → init-ON6WYG66.js.map} +0 -0
- /package/dist/{scan-3ZAOVO4U.js.map → scan-E6U644RS.js.map} +0 -0
- /package/dist/{service-QSZMZJBJ.js.map → service-U7AR2PC2.js.map} +0 -0
- /package/dist/{static-viewer-MIPGZ4Z7.js.map → static-viewer-QL2SCWYB.js.map} +0 -0
- /package/dist/{test-ZCTR4LBB.js.map → test-PBPKJ4WJ.js.map} +0 -0
- /package/dist/{tokens-5JQ5IOR2.js.map → tokens-4J4PRIGT.js.map} +0 -0
- /package/dist/{viewer-D7QC4GM2.js.map → viewer-6VCZMA3T.js.map} +0 -0
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { mkdir, rm, readFile, access } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import {
|
|
6
|
+
generateScssTokens,
|
|
7
|
+
generateCssTokens,
|
|
8
|
+
generateTokenFiles,
|
|
9
|
+
} from "../generator.js";
|
|
10
|
+
import type { ThemeConfig } from "../types.js";
|
|
11
|
+
|
|
12
|
+
describe("Theme Generator", () => {
|
|
13
|
+
let testDir: string;
|
|
14
|
+
|
|
15
|
+
beforeEach(async () => {
|
|
16
|
+
testDir = join(
|
|
17
|
+
tmpdir(),
|
|
18
|
+
`theme-generator-test-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
19
|
+
);
|
|
20
|
+
await mkdir(testDir, { recursive: true });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterEach(async () => {
|
|
24
|
+
try {
|
|
25
|
+
await rm(testDir, { recursive: true, force: true });
|
|
26
|
+
} catch {
|
|
27
|
+
// Ignore cleanup errors
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe("SCSS generation", () => {
|
|
32
|
+
it("should generate valid SCSS with custom accent color", () => {
|
|
33
|
+
const theme: ThemeConfig = {
|
|
34
|
+
name: "Test Theme",
|
|
35
|
+
colors: {
|
|
36
|
+
accent: "#ff6600",
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const scss = generateScssTokens(theme);
|
|
41
|
+
|
|
42
|
+
expect(scss).toContain("$fui-color-accent: #ff6600 !default;");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("should include !default flag for all variables", () => {
|
|
46
|
+
const theme: ThemeConfig = {
|
|
47
|
+
name: "Test Theme",
|
|
48
|
+
colors: {
|
|
49
|
+
accent: "#6366f1",
|
|
50
|
+
danger: "#ef4444",
|
|
51
|
+
},
|
|
52
|
+
radius: {
|
|
53
|
+
sm: "4px",
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const scss = generateScssTokens(theme);
|
|
58
|
+
|
|
59
|
+
// All variable definitions should have !default
|
|
60
|
+
const lines = scss.split("\n").filter((l) => l.includes("$fui-"));
|
|
61
|
+
for (const line of lines) {
|
|
62
|
+
expect(line).toContain("!default;");
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("should generate dark mode variables", () => {
|
|
67
|
+
const theme: ThemeConfig = {
|
|
68
|
+
name: "Test Theme",
|
|
69
|
+
dark: {
|
|
70
|
+
surfaces: {
|
|
71
|
+
bgPrimary: "#0f172a",
|
|
72
|
+
},
|
|
73
|
+
text: {
|
|
74
|
+
primary: "#f8fafc",
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const scss = generateScssTokens(theme);
|
|
80
|
+
|
|
81
|
+
expect(scss).toContain("$fui-dark-bg-primary: #0f172a !default;");
|
|
82
|
+
expect(scss).toContain("$fui-dark-text-primary: #f8fafc !default;");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("should include header comment with theme name", () => {
|
|
86
|
+
const theme: ThemeConfig = {
|
|
87
|
+
name: "My Custom Theme",
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const scss = generateScssTokens(theme);
|
|
91
|
+
|
|
92
|
+
expect(scss).toContain("// Theme: My Custom Theme");
|
|
93
|
+
expect(scss).toContain("// Auto-generated by @fragments-sdk/cli");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("should generate all token categories", () => {
|
|
97
|
+
const theme: ThemeConfig = {
|
|
98
|
+
name: "Complete Theme",
|
|
99
|
+
colors: {
|
|
100
|
+
accent: "#6366f1",
|
|
101
|
+
},
|
|
102
|
+
surfaces: {
|
|
103
|
+
bgPrimary: "#ffffff",
|
|
104
|
+
},
|
|
105
|
+
text: {
|
|
106
|
+
primary: "#0f172a",
|
|
107
|
+
},
|
|
108
|
+
borders: {
|
|
109
|
+
default: "#e2e8f0",
|
|
110
|
+
},
|
|
111
|
+
typography: {
|
|
112
|
+
fontSans: "system-ui, sans-serif",
|
|
113
|
+
fontWeightNormal: 400,
|
|
114
|
+
},
|
|
115
|
+
radius: {
|
|
116
|
+
sm: "4px",
|
|
117
|
+
},
|
|
118
|
+
shadows: {
|
|
119
|
+
sm: "0 1px 2px rgba(0,0,0,0.05)",
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const scss = generateScssTokens(theme);
|
|
124
|
+
|
|
125
|
+
// Check for section comments
|
|
126
|
+
expect(scss).toContain("// Colors");
|
|
127
|
+
expect(scss).toContain("// Surfaces");
|
|
128
|
+
expect(scss).toContain("// Text");
|
|
129
|
+
expect(scss).toContain("// Borders");
|
|
130
|
+
expect(scss).toContain("// Typography");
|
|
131
|
+
expect(scss).toContain("// Border Radius");
|
|
132
|
+
expect(scss).toContain("// Shadows");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("should only include overridden tokens", () => {
|
|
136
|
+
const theme: ThemeConfig = {
|
|
137
|
+
name: "Minimal Theme",
|
|
138
|
+
colors: {
|
|
139
|
+
accent: "#ff0000",
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const scss = generateScssTokens(theme);
|
|
144
|
+
|
|
145
|
+
// Should include accent
|
|
146
|
+
expect(scss).toContain("$fui-color-accent: #ff0000");
|
|
147
|
+
|
|
148
|
+
// Should NOT include non-overridden tokens
|
|
149
|
+
expect(scss).not.toContain("$fui-color-danger:");
|
|
150
|
+
expect(scss).not.toContain("$fui-bg-primary:");
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe("CSS generation", () => {
|
|
155
|
+
it("should generate CSS custom properties", () => {
|
|
156
|
+
const theme: ThemeConfig = {
|
|
157
|
+
name: "Test Theme",
|
|
158
|
+
colors: {
|
|
159
|
+
accent: "#ff6600",
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const css = generateCssTokens(theme);
|
|
164
|
+
|
|
165
|
+
expect(css).toContain("--fui-color-accent: #ff6600;");
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("should wrap in :root selector", () => {
|
|
169
|
+
const theme: ThemeConfig = {
|
|
170
|
+
name: "Test Theme",
|
|
171
|
+
colors: {
|
|
172
|
+
accent: "#6366f1",
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const css = generateCssTokens(theme);
|
|
177
|
+
|
|
178
|
+
expect(css).toContain(":root {");
|
|
179
|
+
expect(css).toContain("}");
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("should generate dark mode with .dark and [data-theme] selectors", () => {
|
|
183
|
+
const theme: ThemeConfig = {
|
|
184
|
+
name: "Test Theme",
|
|
185
|
+
dark: {
|
|
186
|
+
surfaces: {
|
|
187
|
+
bgPrimary: "#0f172a",
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const css = generateCssTokens(theme);
|
|
193
|
+
|
|
194
|
+
expect(css).toContain(':root.dark,\n:root[data-theme="dark"] {');
|
|
195
|
+
expect(css).toContain("--fui-bg-primary: #0f172a;");
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("should include header comment with theme name", () => {
|
|
199
|
+
const theme: ThemeConfig = {
|
|
200
|
+
name: "My CSS Theme",
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const css = generateCssTokens(theme);
|
|
204
|
+
|
|
205
|
+
expect(css).toContain("/* Theme: My CSS Theme */");
|
|
206
|
+
expect(css).toContain("/* Auto-generated by @fragments-sdk/cli */");
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
describe("file generation", () => {
|
|
211
|
+
it("should create SCSS file at specified path", async () => {
|
|
212
|
+
const theme: ThemeConfig = {
|
|
213
|
+
name: "Test Theme",
|
|
214
|
+
colors: {
|
|
215
|
+
accent: "#6366f1",
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const result = await generateTokenFiles(theme, {
|
|
220
|
+
format: "scss",
|
|
221
|
+
outputDir: testDir,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
expect(result.success).toBe(true);
|
|
225
|
+
expect(result.scssPath).toBeDefined();
|
|
226
|
+
|
|
227
|
+
// Verify file exists and contains content
|
|
228
|
+
const content = await readFile(result.scssPath!, "utf-8");
|
|
229
|
+
expect(content).toContain("$fui-color-accent: #6366f1");
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("should create CSS file at specified path", async () => {
|
|
233
|
+
const theme: ThemeConfig = {
|
|
234
|
+
name: "Test Theme",
|
|
235
|
+
colors: {
|
|
236
|
+
accent: "#6366f1",
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const result = await generateTokenFiles(theme, {
|
|
241
|
+
format: "css",
|
|
242
|
+
outputDir: testDir,
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
expect(result.success).toBe(true);
|
|
246
|
+
expect(result.cssPath).toBeDefined();
|
|
247
|
+
|
|
248
|
+
// Verify file exists and contains content
|
|
249
|
+
const content = await readFile(result.cssPath!, "utf-8");
|
|
250
|
+
expect(content).toContain("--fui-color-accent: #6366f1");
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('should create both when format is "both"', async () => {
|
|
254
|
+
const theme: ThemeConfig = {
|
|
255
|
+
name: "Test Theme",
|
|
256
|
+
colors: {
|
|
257
|
+
accent: "#6366f1",
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const result = await generateTokenFiles(theme, {
|
|
262
|
+
format: "both",
|
|
263
|
+
outputDir: testDir,
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
expect(result.success).toBe(true);
|
|
267
|
+
expect(result.scssPath).toBeDefined();
|
|
268
|
+
expect(result.cssPath).toBeDefined();
|
|
269
|
+
|
|
270
|
+
// Verify both files exist
|
|
271
|
+
await expect(access(result.scssPath!)).resolves.toBeUndefined();
|
|
272
|
+
await expect(access(result.cssPath!)).resolves.toBeUndefined();
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it("should use custom file prefix", async () => {
|
|
276
|
+
const theme: ThemeConfig = {
|
|
277
|
+
name: "Test Theme",
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const result = await generateTokenFiles(theme, {
|
|
281
|
+
format: "scss",
|
|
282
|
+
outputDir: testDir,
|
|
283
|
+
filePrefix: "_custom-tokens",
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
expect(result.success).toBe(true);
|
|
287
|
+
expect(result.scssPath).toContain("_custom-tokens.scss");
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it("should create output directory if needed", async () => {
|
|
291
|
+
const theme: ThemeConfig = {
|
|
292
|
+
name: "Test Theme",
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const nestedDir = join(testDir, "nested", "path");
|
|
296
|
+
|
|
297
|
+
const result = await generateTokenFiles(theme, {
|
|
298
|
+
format: "scss",
|
|
299
|
+
outputDir: nestedDir,
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
expect(result.success).toBe(true);
|
|
303
|
+
await expect(access(nestedDir)).resolves.toBeUndefined();
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it("should use default file prefix when not specified", async () => {
|
|
307
|
+
const theme: ThemeConfig = {
|
|
308
|
+
name: "Test Theme",
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
const result = await generateTokenFiles(theme, {
|
|
312
|
+
format: "scss",
|
|
313
|
+
outputDir: testDir,
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
expect(result.success).toBe(true);
|
|
317
|
+
expect(result.scssPath).toContain("_theme-tokens.scss");
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
describe("token mapping", () => {
|
|
322
|
+
it("should map colors.accent to $fui-color-accent", () => {
|
|
323
|
+
const theme: ThemeConfig = {
|
|
324
|
+
name: "Test",
|
|
325
|
+
colors: { accent: "#123456" },
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
const scss = generateScssTokens(theme);
|
|
329
|
+
expect(scss).toContain("$fui-color-accent: #123456");
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it("should map surfaces.bgPrimary to $fui-bg-primary", () => {
|
|
333
|
+
const theme: ThemeConfig = {
|
|
334
|
+
name: "Test",
|
|
335
|
+
surfaces: { bgPrimary: "#ffffff" },
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
const scss = generateScssTokens(theme);
|
|
339
|
+
expect(scss).toContain("$fui-bg-primary: #ffffff");
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it("should map text.primary to $fui-text-primary", () => {
|
|
343
|
+
const theme: ThemeConfig = {
|
|
344
|
+
name: "Test",
|
|
345
|
+
text: { primary: "#000000" },
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
const scss = generateScssTokens(theme);
|
|
349
|
+
expect(scss).toContain("$fui-text-primary: #000000");
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it("should map borders.default to $fui-border", () => {
|
|
353
|
+
const theme: ThemeConfig = {
|
|
354
|
+
name: "Test",
|
|
355
|
+
borders: { default: "#cccccc" },
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const scss = generateScssTokens(theme);
|
|
359
|
+
expect(scss).toContain("$fui-border: #cccccc");
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it("should map typography.fontSans to $fui-font-sans", () => {
|
|
363
|
+
const theme: ThemeConfig = {
|
|
364
|
+
name: "Test",
|
|
365
|
+
typography: { fontSans: "Arial, sans-serif" },
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
const scss = generateScssTokens(theme);
|
|
369
|
+
expect(scss).toContain("$fui-font-sans: Arial, sans-serif");
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it("should map radius.sm to $fui-radius-sm", () => {
|
|
373
|
+
const theme: ThemeConfig = {
|
|
374
|
+
name: "Test",
|
|
375
|
+
radius: { sm: "2px" },
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
const scss = generateScssTokens(theme);
|
|
379
|
+
expect(scss).toContain("$fui-radius-sm: 2px");
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it("should map shadows.md to $fui-shadow-md", () => {
|
|
383
|
+
const theme: ThemeConfig = {
|
|
384
|
+
name: "Test",
|
|
385
|
+
shadows: { md: "0 4px 8px rgba(0,0,0,0.1)" },
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
const scss = generateScssTokens(theme);
|
|
389
|
+
expect(scss).toContain("$fui-shadow-md: 0 4px 8px rgba(0,0,0,0.1)");
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it("should map dark.surfaces.bgPrimary to $fui-dark-bg-primary", () => {
|
|
393
|
+
const theme: ThemeConfig = {
|
|
394
|
+
name: "Test",
|
|
395
|
+
dark: { surfaces: { bgPrimary: "#1a1a1a" } },
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
const scss = generateScssTokens(theme);
|
|
399
|
+
expect(scss).toContain("$fui-dark-bg-primary: #1a1a1a");
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
it("should map dark.dangerBg to $fui-dark-color-danger-bg", () => {
|
|
403
|
+
const theme: ThemeConfig = {
|
|
404
|
+
name: "Test",
|
|
405
|
+
dark: { dangerBg: "rgba(239, 68, 68, 0.15)" },
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
const scss = generateScssTokens(theme);
|
|
409
|
+
expect(scss).toContain("$fui-dark-color-danger-bg: rgba(239, 68, 68, 0.15)");
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
});
|
|
@@ -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
|
+
});
|