@usefragments/core 1.0.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/LICENSE +21 -0
- package/dist/chunk-243QYRUF.js +2145 -0
- package/dist/chunk-243QYRUF.js.map +1 -0
- package/dist/chunk-3LLRNCPX.js +111 -0
- package/dist/chunk-3LLRNCPX.js.map +1 -0
- package/dist/chunk-EIYNNS77.js +48 -0
- package/dist/chunk-EIYNNS77.js.map +1 -0
- package/dist/chunk-JNBFJ34I.js +326 -0
- package/dist/chunk-JNBFJ34I.js.map +1 -0
- package/dist/chunk-R3X7UBHU.js +228 -0
- package/dist/chunk-R3X7UBHU.js.map +1 -0
- package/dist/chunk-SH4KPIYH.js +393 -0
- package/dist/chunk-SH4KPIYH.js.map +1 -0
- package/dist/chunk-V4VQB57N.js +56 -0
- package/dist/chunk-V4VQB57N.js.map +1 -0
- package/dist/chunk-X34IA4LR.js +319 -0
- package/dist/chunk-X34IA4LR.js.map +1 -0
- package/dist/chunk-YF65VYRY.js +493 -0
- package/dist/chunk-YF65VYRY.js.map +1 -0
- package/dist/codes/index.d.ts +40 -0
- package/dist/codes/index.js +23 -0
- package/dist/codes/index.js.map +1 -0
- package/dist/compiled-types/index.d.ts +5 -0
- package/dist/compiled-types/index.js +1 -0
- package/dist/compiled-types/index.js.map +1 -0
- package/dist/generate/index.d.ts +47 -0
- package/dist/generate/index.js +11 -0
- package/dist/generate/index.js.map +1 -0
- package/dist/governance-BOa3KyiJ.d.ts +3279 -0
- package/dist/graph/index.d.ts +66 -0
- package/dist/graph/index.js +17 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/index.d.ts +11388 -0
- package/dist/index.js +8711 -0
- package/dist/index.js.map +1 -0
- package/dist/package-identity.d.ts +42 -0
- package/dist/package-identity.js +23 -0
- package/dist/package-identity.js.map +1 -0
- package/dist/preview/index.d.ts +484 -0
- package/dist/preview/index.js +1129 -0
- package/dist/preview/index.js.map +1 -0
- package/dist/react-types.d.ts +22 -0
- package/dist/react-types.js +1 -0
- package/dist/react-types.js.map +1 -0
- package/dist/registry.d.ts +2367 -0
- package/dist/registry.js +62 -0
- package/dist/registry.js.map +1 -0
- package/dist/schemas/index.d.ts +2584 -0
- package/dist/schemas/index.js +55 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/severity.d.ts +18 -0
- package/dist/severity.js +27 -0
- package/dist/severity.js.map +1 -0
- package/dist/test-utils.d.ts +14 -0
- package/dist/test-utils.js +63 -0
- package/dist/test-utils.js.map +1 -0
- package/dist/topology/index.d.ts +48 -0
- package/dist/topology/index.js +7 -0
- package/dist/topology/index.js.map +1 -0
- package/dist/types-xJ2xyp_G.d.ts +119 -0
- package/package.json +120 -0
- package/src/__tests__/contract-parser.test.ts +614 -0
- package/src/__tests__/dtcg-generators.test.ts +439 -0
- package/src/__tests__/dtcg-parser.test.ts +884 -0
- package/src/__tests__/preview-runtime.test.tsx +111 -0
- package/src/__tests__/schema.test.ts +31 -0
- package/src/__tests__/severity.test.ts +34 -0
- package/src/__tests__/style-comparison.test.ts +662 -0
- package/src/__tests__/tokens-compat.test.ts +51 -0
- package/src/agent-format.test.ts +105 -0
- package/src/agent-format.ts +141 -0
- package/src/bundle.ts +149 -0
- package/src/codes/__tests__/codes.test.ts +73 -0
- package/src/codes/codes.ts +463 -0
- package/src/codes/index.ts +7 -0
- package/src/codes/source-finding-bridge.ts +113 -0
- package/src/compiled-types/index.ts +293 -0
- package/src/component-contract.ts +231 -0
- package/src/component-discovery.ts +80 -0
- package/src/composition.test.ts +262 -0
- package/src/composition.ts +318 -0
- package/src/config-paths.test.ts +78 -0
- package/src/config-paths.ts +47 -0
- package/src/config.ts +18 -0
- package/src/conform-prove.test.ts +274 -0
- package/src/conform-prove.ts +326 -0
- package/src/conform.ts +160 -0
- package/src/constants.ts +114 -0
- package/src/context.ts +2 -0
- package/src/contract/fixture.ts +110 -0
- package/src/contract/hash.test.ts +86 -0
- package/src/contract/hash.ts +195 -0
- package/src/contract/index.ts +43 -0
- package/src/contract/preimage.test.ts +254 -0
- package/src/contract/preimage.ts +365 -0
- package/src/contract/stamp.test.ts +100 -0
- package/src/contract/stamp.ts +105 -0
- package/src/contract-parser.ts +141 -0
- package/src/defineFragment.test.ts +257 -0
- package/src/defineFragment.ts +293 -0
- package/src/dtcg-generators.ts +324 -0
- package/src/dtcg-parser.ts +1 -0
- package/src/dtcg.ts +237 -0
- package/src/facts/__snapshots__/facts.test.ts.snap +124 -0
- package/src/facts/builders.ts +835 -0
- package/src/facts/compile.ts +422 -0
- package/src/facts/fact-index.ts +394 -0
- package/src/facts/facts.test.ts +494 -0
- package/src/facts/ids.ts +125 -0
- package/src/facts/index.ts +119 -0
- package/src/facts/types.ts +614 -0
- package/src/figma.ts +263 -0
- package/src/fragment-types.ts +214 -0
- package/src/generate/generate.test.ts +594 -0
- package/src/generate/index.ts +438 -0
- package/src/governance-integrity.test.ts +186 -0
- package/src/governance-integrity.ts +282 -0
- package/src/governance.test.ts +300 -0
- package/src/governance.ts +605 -0
- package/src/governance.type-test.ts +75 -0
- package/src/graph/engine.ts +446 -0
- package/src/graph/graph.test.ts +746 -0
- package/src/graph/index.ts +32 -0
- package/src/graph/serialization.ts +55 -0
- package/src/graph/types.ts +178 -0
- package/src/index.ts +891 -0
- package/src/local-canonical.ts +102 -0
- package/src/mcp-snapshot.ts +222 -0
- package/src/package-identity-match.ts +33 -0
- package/src/package-identity.generated.ts +233 -0
- package/src/package-identity.test.ts +99 -0
- package/src/package-identity.ts +157 -0
- package/src/performance-presets.ts +142 -0
- package/src/preview/comparison-artifact.test.ts +228 -0
- package/src/preview/comparison-artifact.ts +181 -0
- package/src/preview/comparison-context.ts +93 -0
- package/src/preview/comparison-evidence.ts +112 -0
- package/src/preview/comparison-format.ts +21 -0
- package/src/preview/comparison-presentation.ts +187 -0
- package/src/preview/comparison-references.test.ts +430 -0
- package/src/preview/comparison-references.ts +72 -0
- package/src/preview/comparison-score.ts +59 -0
- package/src/preview/comparison-styles.ts +197 -0
- package/src/preview/comparison.test.ts +559 -0
- package/src/preview/comparison.ts +488 -0
- package/src/preview/index.ts +10 -0
- package/src/preview/types.ts +309 -0
- package/src/preview/validation.test.ts +290 -0
- package/src/preview/validation.ts +356 -0
- package/src/preview-runtime.tsx +144 -0
- package/src/raw-html-canonical.test.ts +41 -0
- package/src/raw-html-canonical.ts +179 -0
- package/src/react-types.ts +21 -0
- package/src/registry-install-plan.ts +344 -0
- package/src/registry.test.ts +245 -0
- package/src/registry.ts +369 -0
- package/src/rules/a11y-required-accessible-name.ts +93 -0
- package/src/rules/color-math.test.ts +72 -0
- package/src/rules/color-math.ts +192 -0
- package/src/rules/components-forbidden-prop-value.ts +108 -0
- package/src/rules/components-prefer-library.ts +943 -0
- package/src/rules/components-unknown-prop.ts +94 -0
- package/src/rules/composition-pattern-schema.ts +125 -0
- package/src/rules/composition-pattern.test.ts +320 -0
- package/src/rules/composition-pattern.ts +271 -0
- package/src/rules/emit-gate.test.ts +42 -0
- package/src/rules/emit-gate.ts +60 -0
- package/src/rules/finding.ts +54 -0
- package/src/rules/index.ts +235 -0
- package/src/rules/jsx-preferred-component.ts +68 -0
- package/src/rules/jsx-preferred-import-path.ts +67 -0
- package/src/rules/presets.ts +57 -0
- package/src/rules/props-invalid-value.ts +115 -0
- package/src/rules/rules.test.ts +3636 -0
- package/src/rules/spacing-resolution.ts +184 -0
- package/src/rules/styles-no-raw-color.ts +329 -0
- package/src/rules/styles-no-raw-dimensions.ts +357 -0
- package/src/rules/styles-no-raw-spacing.ts +236 -0
- package/src/rules/styles-no-raw-typography.ts +237 -0
- package/src/rules/tailwind-arbitrary-color.test.ts +128 -0
- package/src/rules/tailwind-arbitrary-color.ts +93 -0
- package/src/rules/tailwind-arbitrary-spacing.test.ts +157 -0
- package/src/rules/tailwind-arbitrary-spacing.ts +120 -0
- package/src/rules/tailwind-forbidden-palette.test.ts +116 -0
- package/src/rules/tailwind-forbidden-palette.ts +107 -0
- package/src/rules/tailwind-off-scale-spacing-token.test.ts +100 -0
- package/src/rules/tailwind-off-scale-spacing-token.ts +108 -0
- package/src/rules/tailwind-raw-color-via-token.test.ts +108 -0
- package/src/rules/tailwind-raw-color-via-token.ts +82 -0
- package/src/rules/tailwind-rule-utils.ts +106 -0
- package/src/rules/tailwind-unknown-class.test.ts +157 -0
- package/src/rules/tailwind-unknown-class.ts +121 -0
- package/src/rules/taxonomy.test.ts +238 -0
- package/src/rules/theme-no-theme-coupled-literal.ts +132 -0
- package/src/rules/tiers.ts +111 -0
- package/src/rules/tokens-css-vars-must-be-defined.test.ts +244 -0
- package/src/rules/tokens-css-vars-must-be-defined.ts +156 -0
- package/src/rules/tokens-require-dual-fallback.ts +78 -0
- package/src/rules/types.ts +27 -0
- package/src/rules/utils.test.ts +110 -0
- package/src/rules/utils.ts +599 -0
- package/src/schema.ts +427 -0
- package/src/schemas/index.ts +275 -0
- package/src/schemas/schemas.test.ts +124 -0
- package/src/severity.ts +54 -0
- package/src/storyAdapter.test.ts +571 -0
- package/src/storyAdapter.ts +761 -0
- package/src/storyFilters.test.ts +350 -0
- package/src/storyFilters.ts +253 -0
- package/src/storybook-csf.ts +11 -0
- package/src/style-comparison.ts +423 -0
- package/src/test-utils.ts +68 -0
- package/src/token-parser.ts +2 -0
- package/src/token-types.ts +287 -0
- package/src/tokens/categories.ts +131 -0
- package/src/tokens/color.ts +193 -0
- package/src/tokens/design-token-parser.test.ts +159 -0
- package/src/tokens/design-token-parser.ts +402 -0
- package/src/tokens/dtcg.ts +428 -0
- package/src/tokens/index.ts +130 -0
- package/src/tokens/lookup.ts +19 -0
- package/src/tokens/scss.ts +171 -0
- package/src/tokens/tailwind-v4.ts +187 -0
- package/src/tokens/types.ts +43 -0
- package/src/topology/index.ts +9 -0
- package/src/topology/resolve-area.test.ts +174 -0
- package/src/topology/resolve-area.ts +215 -0
- package/src/types.ts +1057 -0
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
generateCSSCustomProperties,
|
|
4
|
+
generateSCSSVariables,
|
|
5
|
+
generateTailwindConfig,
|
|
6
|
+
generateFigmaVariables,
|
|
7
|
+
} from '../dtcg-generators.js';
|
|
8
|
+
import type { DTCGTokenFile } from '../dtcg.js';
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Fixtures
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
function makeColorTokens(): DTCGTokenFile {
|
|
15
|
+
return {
|
|
16
|
+
color: {
|
|
17
|
+
$type: 'color',
|
|
18
|
+
primary: { $value: '#3b82f6' },
|
|
19
|
+
secondary: { $value: '#64748b' },
|
|
20
|
+
danger: { $value: '#ef4444' },
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function makeSpacingTokens(): DTCGTokenFile {
|
|
26
|
+
return {
|
|
27
|
+
spacing: {
|
|
28
|
+
$type: 'dimension',
|
|
29
|
+
sm: { $value: { value: 4, unit: 'px' } },
|
|
30
|
+
md: { $value: { value: 8, unit: 'px' } },
|
|
31
|
+
lg: { $value: { value: 16, unit: 'px' } },
|
|
32
|
+
xl: { $value: { value: 32, unit: 'px' } },
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function makeMixedTokens(): DTCGTokenFile {
|
|
38
|
+
return {
|
|
39
|
+
ds: {
|
|
40
|
+
color: {
|
|
41
|
+
$type: 'color',
|
|
42
|
+
brand: { $value: '#ff5500' },
|
|
43
|
+
surface: { $value: '#ffffff' },
|
|
44
|
+
},
|
|
45
|
+
spacing: {
|
|
46
|
+
$type: 'dimension',
|
|
47
|
+
sm: { $value: '4px' },
|
|
48
|
+
md: { $value: '8px' },
|
|
49
|
+
},
|
|
50
|
+
shadow: {
|
|
51
|
+
$type: 'shadow',
|
|
52
|
+
sm: {
|
|
53
|
+
$value: {
|
|
54
|
+
offsetX: '0px',
|
|
55
|
+
offsetY: '1px',
|
|
56
|
+
blur: '3px',
|
|
57
|
+
spread: '0px',
|
|
58
|
+
color: 'rgba(0, 0, 0, 0.1)',
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
radius: {
|
|
63
|
+
$type: 'dimension',
|
|
64
|
+
sm: { $value: '4px' },
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// CSS Custom Properties Generator
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
describe('generateCSSCustomProperties', () => {
|
|
75
|
+
it('generates valid CSS with :root selector', () => {
|
|
76
|
+
const output = generateCSSCustomProperties(makeColorTokens());
|
|
77
|
+
|
|
78
|
+
expect(output).toContain(':root {');
|
|
79
|
+
expect(output).toContain('}');
|
|
80
|
+
expect(output).toContain('#3b82f6');
|
|
81
|
+
expect(output).toContain('#64748b');
|
|
82
|
+
expect(output).toContain('#ef4444');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('generates CSS custom property declarations', () => {
|
|
86
|
+
const output = generateCSSCustomProperties(makeColorTokens());
|
|
87
|
+
|
|
88
|
+
// Should contain -- prefixed declarations
|
|
89
|
+
expect(output).toMatch(/--[\w-]+:\s*#3b82f6;/);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('supports custom selector', () => {
|
|
93
|
+
const output = generateCSSCustomProperties(makeColorTokens(), {
|
|
94
|
+
selector: '[data-theme="light"]',
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
expect(output).toContain('[data-theme="light"] {');
|
|
98
|
+
expect(output).not.toContain(':root');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('supports custom prefix', () => {
|
|
102
|
+
const output = generateCSSCustomProperties(makeColorTokens(), {
|
|
103
|
+
prefix: 'mui',
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
expect(output).toContain('--mui-');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('groups tokens by category with comments', () => {
|
|
110
|
+
const output = generateCSSCustomProperties(makeMixedTokens());
|
|
111
|
+
|
|
112
|
+
expect(output).toContain('/* colors */');
|
|
113
|
+
expect(output).toContain('/* spacing */');
|
|
114
|
+
expect(output).toContain('/* shadows */');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('handles token descriptions as comments', () => {
|
|
118
|
+
const tokens: DTCGTokenFile = {
|
|
119
|
+
color: {
|
|
120
|
+
$type: 'color',
|
|
121
|
+
brand: {
|
|
122
|
+
$value: '#ff5500',
|
|
123
|
+
$description: 'Main brand color',
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const output = generateCSSCustomProperties(tokens);
|
|
129
|
+
expect(output).toContain('/* Main brand color */');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('handles empty token file', () => {
|
|
133
|
+
const output = generateCSSCustomProperties({});
|
|
134
|
+
|
|
135
|
+
expect(output).toContain(':root {');
|
|
136
|
+
expect(output).toContain('}');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('produces parseable CSS (no syntax errors)', () => {
|
|
140
|
+
const output = generateCSSCustomProperties(makeMixedTokens());
|
|
141
|
+
|
|
142
|
+
// Every line inside the block should be a comment, empty, or valid declaration
|
|
143
|
+
const lines = output.split('\n').filter((l) => l.trim() && l.trim() !== ':root {' && l.trim() !== '}');
|
|
144
|
+
for (const line of lines) {
|
|
145
|
+
const trimmed = line.trim();
|
|
146
|
+
expect(
|
|
147
|
+
trimmed.startsWith('/*') || trimmed.startsWith('--') || trimmed === '',
|
|
148
|
+
).toBe(true);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
// SCSS Variables Generator
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
describe('generateSCSSVariables', () => {
|
|
158
|
+
it('generates SCSS variables with !default flag', () => {
|
|
159
|
+
const output = generateSCSSVariables(makeColorTokens());
|
|
160
|
+
|
|
161
|
+
expect(output).toContain('!default;');
|
|
162
|
+
expect(output).toContain('#3b82f6');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('converts CSS custom property names to SCSS variable names', () => {
|
|
166
|
+
const output = generateSCSSVariables(makeColorTokens());
|
|
167
|
+
|
|
168
|
+
// Should use $ prefix instead of --
|
|
169
|
+
expect(output).toMatch(/\$[\w-]+:\s*#3b82f6/);
|
|
170
|
+
expect(output).not.toMatch(/--[\w-]+:/);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('includes auto-generated header comment', () => {
|
|
174
|
+
const output = generateSCSSVariables(makeColorTokens());
|
|
175
|
+
|
|
176
|
+
expect(output).toContain('Auto-generated from DTCG');
|
|
177
|
+
expect(output).toContain('Do not edit directly');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('groups by category with comments', () => {
|
|
181
|
+
const output = generateSCSSVariables(makeMixedTokens());
|
|
182
|
+
|
|
183
|
+
expect(output).toContain('// colors');
|
|
184
|
+
expect(output).toContain('// spacing');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('supports custom prefix', () => {
|
|
188
|
+
const output = generateSCSSVariables(makeColorTokens(), { prefix: 'brand' });
|
|
189
|
+
|
|
190
|
+
expect(output).toContain('$brand-');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('handles spacing tokens correctly', () => {
|
|
194
|
+
const output = generateSCSSVariables(makeSpacingTokens());
|
|
195
|
+
|
|
196
|
+
expect(output).toContain('4px');
|
|
197
|
+
expect(output).toContain('8px');
|
|
198
|
+
expect(output).toContain('16px');
|
|
199
|
+
expect(output).toContain('32px');
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('does not prefix sibling DTCG category groups with the first category', () => {
|
|
203
|
+
const output = generateSCSSVariables({
|
|
204
|
+
color: {
|
|
205
|
+
$type: 'color',
|
|
206
|
+
primary: { $value: '#3366ff' },
|
|
207
|
+
},
|
|
208
|
+
spacing: {
|
|
209
|
+
$type: 'dimension',
|
|
210
|
+
xs: { $value: '4px' },
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
expect(output).toContain('$color-primary: #3366ff');
|
|
215
|
+
expect(output).toContain('$spacing-xs: 4px');
|
|
216
|
+
expect(output).not.toContain('$color-spacing-xs');
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
// Tailwind Config Generator
|
|
222
|
+
// ---------------------------------------------------------------------------
|
|
223
|
+
|
|
224
|
+
describe('generateTailwindConfig', () => {
|
|
225
|
+
it('generates a valid Tailwind config object', () => {
|
|
226
|
+
const config = generateTailwindConfig(makeColorTokens());
|
|
227
|
+
|
|
228
|
+
expect(config).toHaveProperty('theme');
|
|
229
|
+
expect(config.theme).toHaveProperty('extend');
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('maps color tokens to colors key', () => {
|
|
233
|
+
const config = generateTailwindConfig(makeColorTokens());
|
|
234
|
+
const extend = (config.theme as Record<string, unknown>).extend as Record<string, unknown>;
|
|
235
|
+
|
|
236
|
+
expect(extend).toHaveProperty('colors');
|
|
237
|
+
const colors = extend.colors as Record<string, string>;
|
|
238
|
+
expect(Object.keys(colors).length).toBeGreaterThan(0);
|
|
239
|
+
|
|
240
|
+
// Values should be CSS variable references
|
|
241
|
+
const firstValue = Object.values(colors)[0];
|
|
242
|
+
expect(firstValue).toMatch(/^var\(--/);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('maps spacing tokens to spacing key', () => {
|
|
246
|
+
const config = generateTailwindConfig(makeSpacingTokens());
|
|
247
|
+
const extend = (config.theme as Record<string, unknown>).extend as Record<string, unknown>;
|
|
248
|
+
|
|
249
|
+
expect(extend).toHaveProperty('spacing');
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('maps shadow tokens to boxShadow key', () => {
|
|
253
|
+
const config = generateTailwindConfig(makeMixedTokens());
|
|
254
|
+
const extend = (config.theme as Record<string, unknown>).extend as Record<string, unknown>;
|
|
255
|
+
|
|
256
|
+
expect(extend).toHaveProperty('boxShadow');
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('maps radius tokens to borderRadius key', () => {
|
|
260
|
+
const config = generateTailwindConfig(makeMixedTokens());
|
|
261
|
+
const extend = (config.theme as Record<string, unknown>).extend as Record<string, unknown>;
|
|
262
|
+
|
|
263
|
+
expect(extend).toHaveProperty('borderRadius');
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('handles empty token file', () => {
|
|
267
|
+
const config = generateTailwindConfig({});
|
|
268
|
+
const extend = (config.theme as Record<string, unknown>).extend as Record<string, unknown>;
|
|
269
|
+
|
|
270
|
+
expect(extend).toBeDefined();
|
|
271
|
+
expect(Object.keys(extend)).toHaveLength(0);
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// ---------------------------------------------------------------------------
|
|
276
|
+
// Figma Variables Generator
|
|
277
|
+
// ---------------------------------------------------------------------------
|
|
278
|
+
|
|
279
|
+
describe('generateFigmaVariables', () => {
|
|
280
|
+
it('generates FigmaVariableCollection array', () => {
|
|
281
|
+
const collections = generateFigmaVariables(makeColorTokens());
|
|
282
|
+
|
|
283
|
+
expect(Array.isArray(collections)).toBe(true);
|
|
284
|
+
expect(collections.length).toBeGreaterThan(0);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('groups color tokens into Colors collection', () => {
|
|
288
|
+
const collections = generateFigmaVariables(makeColorTokens());
|
|
289
|
+
const colorCollection = collections.find((c) => c.name === 'Colors');
|
|
290
|
+
|
|
291
|
+
expect(colorCollection).toBeDefined();
|
|
292
|
+
expect(colorCollection!.variables.length).toBeGreaterThan(0);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('groups spacing tokens into Dimensions collection', () => {
|
|
296
|
+
const collections = generateFigmaVariables(makeSpacingTokens());
|
|
297
|
+
const dimCollection = collections.find((c) => c.name === 'Dimensions');
|
|
298
|
+
|
|
299
|
+
expect(dimCollection).toBeDefined();
|
|
300
|
+
expect(dimCollection!.variables.length).toBeGreaterThan(0);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('sets correct Figma variable types for colors', () => {
|
|
304
|
+
const collections = generateFigmaVariables(makeColorTokens());
|
|
305
|
+
const colorCollection = collections.find((c) => c.name === 'Colors')!;
|
|
306
|
+
|
|
307
|
+
for (const v of colorCollection.variables) {
|
|
308
|
+
expect(v.type).toBe('COLOR');
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('sets correct Figma variable types for dimensions', () => {
|
|
313
|
+
const collections = generateFigmaVariables(makeSpacingTokens());
|
|
314
|
+
const dimCollection = collections.find((c) => c.name === 'Dimensions')!;
|
|
315
|
+
|
|
316
|
+
for (const v of dimCollection.variables) {
|
|
317
|
+
expect(v.type).toBe('FLOAT');
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('converts hex colors to Figma RGBA objects', () => {
|
|
322
|
+
const collections = generateFigmaVariables(makeColorTokens());
|
|
323
|
+
const colorCollection = collections.find((c) => c.name === 'Colors')!;
|
|
324
|
+
const firstVar = colorCollection.variables[0];
|
|
325
|
+
|
|
326
|
+
const value = firstVar.value as Record<string, number>;
|
|
327
|
+
expect(value).toHaveProperty('r');
|
|
328
|
+
expect(value).toHaveProperty('g');
|
|
329
|
+
expect(value).toHaveProperty('b');
|
|
330
|
+
expect(value).toHaveProperty('a');
|
|
331
|
+
expect(value.a).toBe(1);
|
|
332
|
+
expect(value.r).toBeGreaterThanOrEqual(0);
|
|
333
|
+
expect(value.r).toBeLessThanOrEqual(1);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it('converts dimension values to numbers', () => {
|
|
337
|
+
const collections = generateFigmaVariables(makeSpacingTokens());
|
|
338
|
+
const dimCollection = collections.find((c) => c.name === 'Dimensions')!;
|
|
339
|
+
|
|
340
|
+
for (const v of dimCollection.variables) {
|
|
341
|
+
expect(typeof v.value).toBe('number');
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it('uses slash-separated names', () => {
|
|
346
|
+
const collections = generateFigmaVariables(makeColorTokens());
|
|
347
|
+
const colorCollection = collections.find((c) => c.name === 'Colors')!;
|
|
348
|
+
|
|
349
|
+
for (const v of colorCollection.variables) {
|
|
350
|
+
expect(v.name).toContain('/');
|
|
351
|
+
expect(v.name).not.toContain('--');
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('includes default mode in each collection', () => {
|
|
356
|
+
const collections = generateFigmaVariables(makeColorTokens());
|
|
357
|
+
|
|
358
|
+
for (const collection of collections) {
|
|
359
|
+
expect(collection.modes).toHaveLength(1);
|
|
360
|
+
expect(collection.modes[0].name).toBe('Default');
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('sets appropriate scopes for color variables', () => {
|
|
365
|
+
const collections = generateFigmaVariables(makeColorTokens());
|
|
366
|
+
const colorCollection = collections.find((c) => c.name === 'Colors')!;
|
|
367
|
+
|
|
368
|
+
for (const v of colorCollection.variables) {
|
|
369
|
+
expect(v.scopes).toBeDefined();
|
|
370
|
+
expect(v.scopes!.length).toBeGreaterThan(0);
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it('handles empty token file', () => {
|
|
375
|
+
const collections = generateFigmaVariables({});
|
|
376
|
+
expect(collections).toHaveLength(0);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
it('handles mixed token file with multiple collections', () => {
|
|
380
|
+
const collections = generateFigmaVariables(makeMixedTokens());
|
|
381
|
+
|
|
382
|
+
const collectionNames = collections.map((c) => c.name);
|
|
383
|
+
expect(collectionNames).toContain('Colors');
|
|
384
|
+
expect(collectionNames).toContain('Dimensions');
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
// ---------------------------------------------------------------------------
|
|
389
|
+
// Cross-generator consistency
|
|
390
|
+
// ---------------------------------------------------------------------------
|
|
391
|
+
|
|
392
|
+
describe('cross-generator consistency', () => {
|
|
393
|
+
it('all generators process the same token file without errors', () => {
|
|
394
|
+
const tokens = makeMixedTokens();
|
|
395
|
+
|
|
396
|
+
expect(() => generateCSSCustomProperties(tokens)).not.toThrow();
|
|
397
|
+
expect(() => generateSCSSVariables(tokens)).not.toThrow();
|
|
398
|
+
expect(() => generateTailwindConfig(tokens)).not.toThrow();
|
|
399
|
+
expect(() => generateFigmaVariables(tokens)).not.toThrow();
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
it('CSS and SCSS generators produce same number of tokens', () => {
|
|
403
|
+
const tokens = makeColorTokens();
|
|
404
|
+
|
|
405
|
+
const css = generateCSSCustomProperties(tokens);
|
|
406
|
+
const scss = generateSCSSVariables(tokens);
|
|
407
|
+
|
|
408
|
+
// Count declarations (lines containing : and ;)
|
|
409
|
+
const cssCount = css.split('\n').filter((l) => l.includes(':') && l.includes(';') && !l.trim().startsWith('/*')).length;
|
|
410
|
+
const scssCount = scss.split('\n').filter((l) => l.includes(':') && l.includes(';')).length;
|
|
411
|
+
|
|
412
|
+
expect(cssCount).toBe(scssCount);
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
it('handles token file with all types', () => {
|
|
416
|
+
const allTypes: DTCGTokenFile = {
|
|
417
|
+
colors: { $type: 'color', brand: { $value: '#ff5500' } },
|
|
418
|
+
spacing: { $type: 'dimension', sm: { $value: '4px' } },
|
|
419
|
+
fonts: { $type: 'fontFamily', sans: { $value: ['Inter', 'sans-serif'] } },
|
|
420
|
+
weights: { $type: 'fontWeight', bold: { $value: 700 } },
|
|
421
|
+
durations: { $type: 'duration', fast: { $value: '100ms' } },
|
|
422
|
+
easing: { $type: 'cubicBezier', standard: { $value: [0.4, 0, 0.2, 1] } },
|
|
423
|
+
numbers: { $type: 'number', ratio: { $value: 1.5 } },
|
|
424
|
+
shadows: {
|
|
425
|
+
$type: 'shadow',
|
|
426
|
+
sm: { $value: { offsetX: '0px', offsetY: '1px', blur: '3px', color: '#000' } },
|
|
427
|
+
},
|
|
428
|
+
borders: {
|
|
429
|
+
$type: 'border',
|
|
430
|
+
default: { $value: { color: '#ccc', width: '1px', style: 'solid' } },
|
|
431
|
+
},
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
expect(() => generateCSSCustomProperties(allTypes)).not.toThrow();
|
|
435
|
+
expect(() => generateSCSSVariables(allTypes)).not.toThrow();
|
|
436
|
+
expect(() => generateTailwindConfig(allTypes)).not.toThrow();
|
|
437
|
+
expect(() => generateFigmaVariables(allTypes)).not.toThrow();
|
|
438
|
+
});
|
|
439
|
+
});
|