@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,884 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { parseDTCGFile, isDTCGFile } from '../dtcg-parser.js';
|
|
3
|
+
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Helpers
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
function makeTokenFile(obj: Record<string, unknown>): string {
|
|
9
|
+
return JSON.stringify(obj);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// isDTCGFile
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
describe('isDTCGFile', () => {
|
|
17
|
+
it('detects .tokens.json extension', () => {
|
|
18
|
+
expect(isDTCGFile('design.tokens.json')).toBe(true);
|
|
19
|
+
expect(isDTCGFile('src/tokens/brand.tokens.json')).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('detects .tokens extension', () => {
|
|
23
|
+
expect(isDTCGFile('brand.tokens')).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('rejects non-DTCG files', () => {
|
|
27
|
+
expect(isDTCGFile('variables.scss')).toBe(false);
|
|
28
|
+
expect(isDTCGFile('tokens.css')).toBe(false);
|
|
29
|
+
expect(isDTCGFile('package.json')).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('accepts common unqualified DTCG token filenames', () => {
|
|
33
|
+
expect(isDTCGFile('tokens.json')).toBe(true);
|
|
34
|
+
expect(isDTCGFile('design-tokens.json')).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Basic parsing
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
describe('parseDTCGFile', () => {
|
|
43
|
+
describe('basic token parsing', () => {
|
|
44
|
+
it('parses a simple color token', () => {
|
|
45
|
+
const content = makeTokenFile({
|
|
46
|
+
color: {
|
|
47
|
+
$type: 'color',
|
|
48
|
+
primary: {
|
|
49
|
+
$value: '#ff5500',
|
|
50
|
+
$description: 'Primary brand color',
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
56
|
+
|
|
57
|
+
expect(result.total).toBe(1);
|
|
58
|
+
expect(result.categories.colors).toBeDefined();
|
|
59
|
+
expect(result.categories.colors).toHaveLength(1);
|
|
60
|
+
expect(result.categories.colors[0].value).toBe('#ff5500');
|
|
61
|
+
expect(result.categories.colors[0].description).toBe('Primary brand color');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('parses multiple tokens across groups', () => {
|
|
65
|
+
const content = makeTokenFile({
|
|
66
|
+
color: {
|
|
67
|
+
$type: 'color',
|
|
68
|
+
primary: { $value: '#ff5500' },
|
|
69
|
+
secondary: { $value: '#0055ff' },
|
|
70
|
+
},
|
|
71
|
+
spacing: {
|
|
72
|
+
$type: 'dimension',
|
|
73
|
+
sm: { $value: { value: 4, unit: 'px' } },
|
|
74
|
+
md: { $value: { value: 8, unit: 'px' } },
|
|
75
|
+
lg: { $value: { value: 16, unit: 'px' } },
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
80
|
+
|
|
81
|
+
expect(result.total).toBe(5);
|
|
82
|
+
expect(result.categories.colors).toHaveLength(2);
|
|
83
|
+
expect(result.categories.spacing).toHaveLength(3);
|
|
84
|
+
expect(result.categories.colors[0].name).toBe('--color-primary');
|
|
85
|
+
expect(result.categories.spacing[0].name).toBe('--spacing-sm');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('returns zero tokens for an empty file', () => {
|
|
89
|
+
const result = parseDTCGFile('{}', 'test.tokens.json');
|
|
90
|
+
|
|
91
|
+
expect(result.total).toBe(0);
|
|
92
|
+
expect(Object.keys(result.categories)).toHaveLength(0);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('skips tokens without a resolvable type', () => {
|
|
96
|
+
const content = makeTokenFile({
|
|
97
|
+
mystery: {
|
|
98
|
+
unknown: { $value: 'something' },
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
103
|
+
expect(result.total).toBe(0);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// All 13 DTCG token types
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
describe('token type handling', () => {
|
|
112
|
+
it('parses color tokens with hex string value', () => {
|
|
113
|
+
const content = makeTokenFile({
|
|
114
|
+
color: {
|
|
115
|
+
$type: 'color',
|
|
116
|
+
brand: { $value: '#e63946' },
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
120
|
+
expect(result.categories.colors[0].value).toBe('#e63946');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('parses color tokens with object value (hex field)', () => {
|
|
124
|
+
const content = makeTokenFile({
|
|
125
|
+
color: {
|
|
126
|
+
$type: 'color',
|
|
127
|
+
brand: {
|
|
128
|
+
$value: { colorSpace: 'srgb', components: [0.9, 0.22, 0.27], hex: '#e63845' },
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
133
|
+
expect(result.categories.colors[0].value).toBe('#e63845');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('parses color tokens with object value (components only)', () => {
|
|
137
|
+
const content = makeTokenFile({
|
|
138
|
+
color: {
|
|
139
|
+
$type: 'color',
|
|
140
|
+
brand: {
|
|
141
|
+
$value: { colorSpace: 'srgb', components: [1, 0, 0] },
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
146
|
+
expect(result.categories.colors[0].value).toBe('rgb(255, 0, 0)');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('parses color with alpha from components', () => {
|
|
150
|
+
const content = makeTokenFile({
|
|
151
|
+
color: {
|
|
152
|
+
$type: 'color',
|
|
153
|
+
overlay: {
|
|
154
|
+
$value: { colorSpace: 'srgb', components: [0, 0, 0], hex: '#000000', alpha: 0.5 },
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
159
|
+
expect(result.categories.colors[0].value).toBe('rgba(0, 0, 0, 0.5)');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('parses dimension tokens with object value', () => {
|
|
163
|
+
const content = makeTokenFile({
|
|
164
|
+
spacing: {
|
|
165
|
+
$type: 'dimension',
|
|
166
|
+
sm: { $value: { value: 4, unit: 'px' } },
|
|
167
|
+
md: { $value: { value: 1, unit: 'rem' } },
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
171
|
+
expect(result.categories.spacing).toHaveLength(2);
|
|
172
|
+
expect(result.categories.spacing[0].value).toBe('4px');
|
|
173
|
+
expect(result.categories.spacing[1].value).toBe('1rem');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('parses dimension tokens with string value', () => {
|
|
177
|
+
const content = makeTokenFile({
|
|
178
|
+
spacing: {
|
|
179
|
+
$type: 'dimension',
|
|
180
|
+
gap: { $value: '16px' },
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
184
|
+
expect(result.categories.spacing[0].value).toBe('16px');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('maps dimension tokens with "radius" in name to radius category', () => {
|
|
188
|
+
const content = makeTokenFile({
|
|
189
|
+
radius: {
|
|
190
|
+
$type: 'dimension',
|
|
191
|
+
sm: { $value: { value: 4, unit: 'px' } },
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
195
|
+
expect(result.categories.radius).toBeDefined();
|
|
196
|
+
expect(result.categories.radius).toHaveLength(1);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('parses fontFamily tokens', () => {
|
|
200
|
+
const content = makeTokenFile({
|
|
201
|
+
font: {
|
|
202
|
+
$type: 'fontFamily',
|
|
203
|
+
sans: { $value: ['Inter', 'Helvetica', 'sans-serif'] },
|
|
204
|
+
mono: { $value: 'JetBrains Mono' },
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
208
|
+
expect(result.categories.typography).toHaveLength(2);
|
|
209
|
+
expect(result.categories.typography[0].value).toBe('Inter, Helvetica, sans-serif');
|
|
210
|
+
expect(result.categories.typography[1].value).toBe('JetBrains Mono');
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('parses fontWeight tokens', () => {
|
|
214
|
+
const content = makeTokenFile({
|
|
215
|
+
weight: {
|
|
216
|
+
$type: 'fontWeight',
|
|
217
|
+
regular: { $value: 400 },
|
|
218
|
+
bold: { $value: 700 },
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
222
|
+
expect(result.categories.typography).toHaveLength(2);
|
|
223
|
+
expect(result.categories.typography[0].value).toBe('400');
|
|
224
|
+
expect(result.categories.typography[1].value).toBe('700');
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('parses duration tokens', () => {
|
|
228
|
+
const content = makeTokenFile({
|
|
229
|
+
motion: {
|
|
230
|
+
$type: 'duration',
|
|
231
|
+
fast: { $value: '100ms' },
|
|
232
|
+
slow: { $value: '500ms' },
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
236
|
+
expect(result.categories.transitions).toHaveLength(2);
|
|
237
|
+
expect(result.categories.transitions[0].value).toBe('100ms');
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('parses cubicBezier tokens', () => {
|
|
241
|
+
const content = makeTokenFile({
|
|
242
|
+
easing: {
|
|
243
|
+
$type: 'cubicBezier',
|
|
244
|
+
standard: { $value: [0.4, 0, 0.2, 1] },
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
248
|
+
expect(result.categories.transitions).toHaveLength(1);
|
|
249
|
+
expect(result.categories.transitions[0].value).toBe('cubic-bezier(0.4, 0, 0.2, 1)');
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('parses number tokens', () => {
|
|
253
|
+
const content = makeTokenFile({
|
|
254
|
+
scale: {
|
|
255
|
+
$type: 'number',
|
|
256
|
+
ratio: { $value: 1.5 },
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
260
|
+
expect(result.categories.other).toHaveLength(1);
|
|
261
|
+
expect(result.categories.other[0].value).toBe('1.5');
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('parses shadow tokens (single)', () => {
|
|
265
|
+
const content = makeTokenFile({
|
|
266
|
+
shadow: {
|
|
267
|
+
$type: 'shadow',
|
|
268
|
+
sm: {
|
|
269
|
+
$value: {
|
|
270
|
+
offsetX: '0px',
|
|
271
|
+
offsetY: '1px',
|
|
272
|
+
blur: '3px',
|
|
273
|
+
spread: '0px',
|
|
274
|
+
color: 'rgba(0, 0, 0, 0.1)',
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
280
|
+
expect(result.categories.shadows).toHaveLength(1);
|
|
281
|
+
expect(result.categories.shadows[0].value).toContain('0px 1px 3px');
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('parses shadow tokens (multiple layers)', () => {
|
|
285
|
+
const content = makeTokenFile({
|
|
286
|
+
shadow: {
|
|
287
|
+
$type: 'shadow',
|
|
288
|
+
lg: {
|
|
289
|
+
$value: [
|
|
290
|
+
{ offsetX: '0px', offsetY: '4px', blur: '8px', color: 'rgba(0, 0, 0, 0.1)' },
|
|
291
|
+
{ offsetX: '0px', offsetY: '2px', blur: '4px', color: 'rgba(0, 0, 0, 0.06)' },
|
|
292
|
+
],
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
297
|
+
expect(result.categories.shadows[0].value).toContain(',');
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it('parses shadow tokens with inset', () => {
|
|
301
|
+
const content = makeTokenFile({
|
|
302
|
+
shadow: {
|
|
303
|
+
$type: 'shadow',
|
|
304
|
+
inner: {
|
|
305
|
+
$value: {
|
|
306
|
+
offsetX: '0px',
|
|
307
|
+
offsetY: '2px',
|
|
308
|
+
blur: '4px',
|
|
309
|
+
color: 'rgba(0, 0, 0, 0.05)',
|
|
310
|
+
inset: true,
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
316
|
+
expect(result.categories.shadows[0].value).toMatch(/^inset /);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('parses border tokens', () => {
|
|
320
|
+
const content = makeTokenFile({
|
|
321
|
+
border: {
|
|
322
|
+
$type: 'border',
|
|
323
|
+
default: {
|
|
324
|
+
$value: {
|
|
325
|
+
color: '#e0e0e0',
|
|
326
|
+
width: '1px',
|
|
327
|
+
style: 'solid',
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
333
|
+
expect(result.categories.borders).toHaveLength(1);
|
|
334
|
+
expect(result.categories.borders[0].value).toBe('1px solid #e0e0e0');
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it('parses strokeStyle tokens', () => {
|
|
338
|
+
const content = makeTokenFile({
|
|
339
|
+
stroke: {
|
|
340
|
+
$type: 'strokeStyle',
|
|
341
|
+
dashed: { $value: 'dashed' },
|
|
342
|
+
},
|
|
343
|
+
});
|
|
344
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
345
|
+
expect(result.categories.borders).toHaveLength(1);
|
|
346
|
+
expect(result.categories.borders[0].value).toBe('dashed');
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it('parses transition tokens', () => {
|
|
350
|
+
const content = makeTokenFile({
|
|
351
|
+
motion: {
|
|
352
|
+
$type: 'transition',
|
|
353
|
+
fade: {
|
|
354
|
+
$value: {
|
|
355
|
+
duration: '200ms',
|
|
356
|
+
timingFunction: [0.4, 0, 0.2, 1],
|
|
357
|
+
delay: '0ms',
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
});
|
|
362
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
363
|
+
expect(result.categories.transitions).toHaveLength(1);
|
|
364
|
+
expect(result.categories.transitions[0].value).toContain('200ms');
|
|
365
|
+
expect(result.categories.transitions[0].value).toContain('cubic-bezier');
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it('parses gradient tokens', () => {
|
|
369
|
+
const content = makeTokenFile({
|
|
370
|
+
gradient: {
|
|
371
|
+
$type: 'gradient',
|
|
372
|
+
brand: {
|
|
373
|
+
$value: [
|
|
374
|
+
{ color: '#ff5500', position: 0 },
|
|
375
|
+
{ color: '#0055ff', position: 1 },
|
|
376
|
+
],
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
});
|
|
380
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
381
|
+
expect(result.categories.colors).toHaveLength(1);
|
|
382
|
+
expect(result.categories.colors[0].value).toContain('linear-gradient');
|
|
383
|
+
expect(result.categories.colors[0].value).toContain('#ff5500');
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it('parses typography composite tokens', () => {
|
|
387
|
+
const content = makeTokenFile({
|
|
388
|
+
type: {
|
|
389
|
+
$type: 'typography',
|
|
390
|
+
heading: {
|
|
391
|
+
$value: {
|
|
392
|
+
fontFamily: 'Inter',
|
|
393
|
+
fontSize: '24px',
|
|
394
|
+
fontWeight: 700,
|
|
395
|
+
lineHeight: 1.2,
|
|
396
|
+
},
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
});
|
|
400
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
401
|
+
expect(result.categories.typography).toHaveLength(1);
|
|
402
|
+
expect(result.categories.typography[0].value).toContain('700');
|
|
403
|
+
expect(result.categories.typography[0].value).toContain('24px');
|
|
404
|
+
expect(result.categories.typography[0].value).toContain('Inter');
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
// ---------------------------------------------------------------------------
|
|
409
|
+
// $type inheritance
|
|
410
|
+
// ---------------------------------------------------------------------------
|
|
411
|
+
|
|
412
|
+
describe('$type inheritance', () => {
|
|
413
|
+
it('inherits $type from parent group', () => {
|
|
414
|
+
const content = makeTokenFile({
|
|
415
|
+
color: {
|
|
416
|
+
$type: 'color',
|
|
417
|
+
brand: {
|
|
418
|
+
primary: { $value: '#ff5500' },
|
|
419
|
+
secondary: { $value: '#0055ff' },
|
|
420
|
+
},
|
|
421
|
+
surface: {
|
|
422
|
+
default: { $value: '#ffffff' },
|
|
423
|
+
},
|
|
424
|
+
},
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
428
|
+
|
|
429
|
+
expect(result.total).toBe(3);
|
|
430
|
+
expect(result.categories.colors).toHaveLength(3);
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
it('child $type overrides inherited $type', () => {
|
|
434
|
+
const content = makeTokenFile({
|
|
435
|
+
tokens: {
|
|
436
|
+
$type: 'color',
|
|
437
|
+
accent: { $value: '#ff5500' },
|
|
438
|
+
spacing: {
|
|
439
|
+
$type: 'dimension',
|
|
440
|
+
sm: { $value: '4px' },
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
446
|
+
|
|
447
|
+
expect(result.categories.colors).toHaveLength(1);
|
|
448
|
+
expect(result.categories.spacing).toHaveLength(1);
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
it('inherits $type through multiple nesting levels', () => {
|
|
452
|
+
const content = makeTokenFile({
|
|
453
|
+
ds: {
|
|
454
|
+
$type: 'color',
|
|
455
|
+
brand: {
|
|
456
|
+
primary: {
|
|
457
|
+
light: { $value: '#ffaa80' },
|
|
458
|
+
dark: { $value: '#cc4400' },
|
|
459
|
+
},
|
|
460
|
+
},
|
|
461
|
+
},
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
465
|
+
|
|
466
|
+
expect(result.total).toBe(2);
|
|
467
|
+
expect(result.categories.colors).toHaveLength(2);
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
// ---------------------------------------------------------------------------
|
|
472
|
+
// Alias resolution
|
|
473
|
+
// ---------------------------------------------------------------------------
|
|
474
|
+
|
|
475
|
+
describe('alias resolution', () => {
|
|
476
|
+
it('resolves simple aliases', () => {
|
|
477
|
+
const content = makeTokenFile({
|
|
478
|
+
color: {
|
|
479
|
+
$type: 'color',
|
|
480
|
+
brand: { $value: '#ff5500' },
|
|
481
|
+
accent: { $value: '{color.brand}' },
|
|
482
|
+
},
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
486
|
+
|
|
487
|
+
expect(result.total).toBe(2);
|
|
488
|
+
const accent = result.categories.colors.find((t) => t.name.includes('accent'));
|
|
489
|
+
expect(accent).toBeDefined();
|
|
490
|
+
expect(accent!.value).toBe('#ff5500');
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
it('resolves chained aliases', () => {
|
|
494
|
+
const content = makeTokenFile({
|
|
495
|
+
color: {
|
|
496
|
+
$type: 'color',
|
|
497
|
+
base: { $value: '#e63946' },
|
|
498
|
+
brand: { $value: '{color.base}' },
|
|
499
|
+
primary: { $value: '{color.brand}' },
|
|
500
|
+
},
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
504
|
+
|
|
505
|
+
const primary = result.categories.colors.find((t) => t.name.includes('primary'));
|
|
506
|
+
expect(primary).toBeDefined();
|
|
507
|
+
expect(primary!.value).toBe('#e63946');
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
it('resolves aliases across groups', () => {
|
|
511
|
+
const content = makeTokenFile({
|
|
512
|
+
primitive: {
|
|
513
|
+
$type: 'color',
|
|
514
|
+
blue500: { $value: '#3b82f6' },
|
|
515
|
+
},
|
|
516
|
+
semantic: {
|
|
517
|
+
$type: 'color',
|
|
518
|
+
primary: { $value: '{primitive.blue500}' },
|
|
519
|
+
},
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
523
|
+
|
|
524
|
+
const primary = result.categories.colors.find((t) => t.name.includes('primary'));
|
|
525
|
+
expect(primary!.value).toBe('#3b82f6');
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
it('detects circular aliases and throws', () => {
|
|
529
|
+
const content = makeTokenFile({
|
|
530
|
+
color: {
|
|
531
|
+
$type: 'color',
|
|
532
|
+
a: { $value: '{color.b}' },
|
|
533
|
+
b: { $value: '{color.a}' },
|
|
534
|
+
},
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
// Should not throw — circular aliases are caught and the raw value is kept
|
|
538
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
539
|
+
expect(result.total).toBe(2);
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
it('detects self-referencing aliases', () => {
|
|
543
|
+
const content = makeTokenFile({
|
|
544
|
+
color: {
|
|
545
|
+
$type: 'color',
|
|
546
|
+
recursive: { $value: '{color.recursive}' },
|
|
547
|
+
},
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
551
|
+
expect(result.total).toBe(1);
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
it('detects deeply nested circular aliases (exceeding max depth)', () => {
|
|
555
|
+
// Create a chain of 12 aliases that will exceed MAX_ALIAS_DEPTH of 10
|
|
556
|
+
const tokens: Record<string, unknown> = { $type: 'color' };
|
|
557
|
+
for (let i = 0; i < 12; i++) {
|
|
558
|
+
tokens[`t${i}`] = { $value: `{chain.t${(i + 1) % 12}}` };
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const content = makeTokenFile({ chain: tokens });
|
|
562
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
563
|
+
expect(result.total).toBe(12);
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
it('handles alias to non-existent path gracefully', () => {
|
|
567
|
+
const content = makeTokenFile({
|
|
568
|
+
color: {
|
|
569
|
+
$type: 'color',
|
|
570
|
+
broken: { $value: '{does.not.exist}' },
|
|
571
|
+
},
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
575
|
+
expect(result.total).toBe(1);
|
|
576
|
+
// Value should be the unresolved alias string
|
|
577
|
+
expect(result.categories.colors[0].value).toBe('{does.not.exist}');
|
|
578
|
+
});
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
// ---------------------------------------------------------------------------
|
|
582
|
+
// $extends group inheritance
|
|
583
|
+
// ---------------------------------------------------------------------------
|
|
584
|
+
|
|
585
|
+
describe('$extends group inheritance', () => {
|
|
586
|
+
it('resolves $extends from another group', () => {
|
|
587
|
+
const content = makeTokenFile({
|
|
588
|
+
base: {
|
|
589
|
+
$type: 'color',
|
|
590
|
+
primary: { $value: '#ff5500' },
|
|
591
|
+
secondary: { $value: '#0055ff' },
|
|
592
|
+
},
|
|
593
|
+
theme: {
|
|
594
|
+
$extends: 'base',
|
|
595
|
+
primary: { $value: '#ee4400' }, // Override
|
|
596
|
+
},
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
600
|
+
|
|
601
|
+
// base has 2, theme inherits secondary and overrides primary = 2 more
|
|
602
|
+
const themeTokens = result.categories.colors.filter((t) => t.name.includes('theme'));
|
|
603
|
+
expect(themeTokens.length).toBeGreaterThanOrEqual(2);
|
|
604
|
+
|
|
605
|
+
// The overridden primary should have the new value
|
|
606
|
+
const themePrimary = themeTokens.find((t) => t.name.includes('primary'));
|
|
607
|
+
expect(themePrimary).toBeDefined();
|
|
608
|
+
expect(themePrimary!.value).toBe('#ee4400');
|
|
609
|
+
});
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
// ---------------------------------------------------------------------------
|
|
613
|
+
// $deprecated propagation
|
|
614
|
+
// ---------------------------------------------------------------------------
|
|
615
|
+
|
|
616
|
+
describe('$deprecated handling', () => {
|
|
617
|
+
it('preserves $deprecated boolean on tokens', () => {
|
|
618
|
+
const content = makeTokenFile({
|
|
619
|
+
color: {
|
|
620
|
+
$type: 'color',
|
|
621
|
+
old: { $value: '#ff0000', $deprecated: true },
|
|
622
|
+
},
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
626
|
+
expect(result.total).toBe(1);
|
|
627
|
+
// Deprecated tokens are still parsed — consumers can filter
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
it('preserves $deprecated string message on tokens', () => {
|
|
631
|
+
const content = makeTokenFile({
|
|
632
|
+
color: {
|
|
633
|
+
$type: 'color',
|
|
634
|
+
legacy: {
|
|
635
|
+
$value: '#ff0000',
|
|
636
|
+
$deprecated: 'Use color.brand instead',
|
|
637
|
+
},
|
|
638
|
+
},
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
642
|
+
expect(result.total).toBe(1);
|
|
643
|
+
});
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
// ---------------------------------------------------------------------------
|
|
647
|
+
// $extensions preservation
|
|
648
|
+
// ---------------------------------------------------------------------------
|
|
649
|
+
|
|
650
|
+
describe('$extensions handling', () => {
|
|
651
|
+
it('reads prefix from $extensions["com.usefragments"].prefix', () => {
|
|
652
|
+
const content = makeTokenFile({
|
|
653
|
+
$extensions: {
|
|
654
|
+
'com.usefragments': { prefix: 'mui' },
|
|
655
|
+
},
|
|
656
|
+
color: {
|
|
657
|
+
$type: 'color',
|
|
658
|
+
primary: { $value: '#1976d2' },
|
|
659
|
+
},
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
663
|
+
expect(result.prefix).toBe('--mui-');
|
|
664
|
+
});
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
// ---------------------------------------------------------------------------
|
|
668
|
+
// Prefix detection
|
|
669
|
+
// ---------------------------------------------------------------------------
|
|
670
|
+
|
|
671
|
+
describe('prefix detection', () => {
|
|
672
|
+
it('detects prefix from single top-level group', () => {
|
|
673
|
+
const content = makeTokenFile({
|
|
674
|
+
ds: {
|
|
675
|
+
$type: 'color',
|
|
676
|
+
primary: { $value: '#ff5500' },
|
|
677
|
+
},
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
681
|
+
expect(result.prefix).toBe('--ds-');
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
it('detects prefix from $extensions over top-level group', () => {
|
|
685
|
+
const content = makeTokenFile({
|
|
686
|
+
$extensions: {
|
|
687
|
+
'com.usefragments': { prefix: 'brand' },
|
|
688
|
+
},
|
|
689
|
+
ds: {
|
|
690
|
+
$type: 'color',
|
|
691
|
+
primary: { $value: '#ff5500' },
|
|
692
|
+
},
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
696
|
+
expect(result.prefix).toBe('--brand-');
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
it('falls back to -- for empty files', () => {
|
|
700
|
+
const result = parseDTCGFile('{}', 'test.tokens.json');
|
|
701
|
+
expect(result.prefix).toBe('--');
|
|
702
|
+
});
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
// ---------------------------------------------------------------------------
|
|
706
|
+
// CSS variable naming
|
|
707
|
+
// ---------------------------------------------------------------------------
|
|
708
|
+
|
|
709
|
+
describe('CSS variable naming', () => {
|
|
710
|
+
it('converts dot paths to hyphenated CSS names', () => {
|
|
711
|
+
const content = makeTokenFile({
|
|
712
|
+
color: {
|
|
713
|
+
$type: 'color',
|
|
714
|
+
brand: {
|
|
715
|
+
primary: { $value: '#ff5500' },
|
|
716
|
+
},
|
|
717
|
+
},
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
721
|
+
const token = result.categories.colors[0];
|
|
722
|
+
expect(token.name).toContain('brand-primary');
|
|
723
|
+
expect(token.name).toMatch(/^--/);
|
|
724
|
+
});
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
// ---------------------------------------------------------------------------
|
|
728
|
+
// Edge cases
|
|
729
|
+
// ---------------------------------------------------------------------------
|
|
730
|
+
|
|
731
|
+
describe('edge cases', () => {
|
|
732
|
+
it('handles deeply nested token trees', () => {
|
|
733
|
+
const content = makeTokenFile({
|
|
734
|
+
level1: {
|
|
735
|
+
$type: 'color',
|
|
736
|
+
level2: {
|
|
737
|
+
level3: {
|
|
738
|
+
level4: {
|
|
739
|
+
deep: { $value: '#001122' },
|
|
740
|
+
},
|
|
741
|
+
},
|
|
742
|
+
},
|
|
743
|
+
},
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
747
|
+
expect(result.total).toBe(1);
|
|
748
|
+
expect(result.categories.colors[0].name).toContain('level2');
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
it('ignores non-object children', () => {
|
|
752
|
+
const content = makeTokenFile({
|
|
753
|
+
color: {
|
|
754
|
+
$type: 'color',
|
|
755
|
+
primary: { $value: '#ff5500' },
|
|
756
|
+
},
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
760
|
+
expect(result.total).toBe(1);
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
it('handles large token files efficiently', () => {
|
|
764
|
+
const tokens: Record<string, unknown> = { $type: 'color' };
|
|
765
|
+
for (let i = 0; i < 500; i++) {
|
|
766
|
+
tokens[`color-${i}`] = { $value: `#${String(i).padStart(6, '0')}` };
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
const content = makeTokenFile({ palette: tokens });
|
|
770
|
+
const start = performance.now();
|
|
771
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
772
|
+
const elapsed = performance.now() - start;
|
|
773
|
+
|
|
774
|
+
expect(result.total).toBe(500);
|
|
775
|
+
expect(elapsed).toBeLessThan(1000); // Should parse 500 tokens in under 1 second
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
it('throws on invalid JSON', () => {
|
|
779
|
+
expect(() => parseDTCGFile('not json', 'test.tokens.json')).toThrow();
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
it('handles tokens with special characters in keys', () => {
|
|
783
|
+
const content = makeTokenFile({
|
|
784
|
+
'my-brand': {
|
|
785
|
+
$type: 'color',
|
|
786
|
+
'dark-mode': {
|
|
787
|
+
'bg-primary': { $value: '#1a1a2e' },
|
|
788
|
+
},
|
|
789
|
+
},
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
793
|
+
expect(result.total).toBe(1);
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
it('handles mixed groups with both tokens and sub-groups', () => {
|
|
797
|
+
const content = makeTokenFile({
|
|
798
|
+
color: {
|
|
799
|
+
$type: 'color',
|
|
800
|
+
primary: { $value: '#ff5500' },
|
|
801
|
+
shades: {
|
|
802
|
+
light: { $value: '#ffaa80' },
|
|
803
|
+
dark: { $value: '#cc4400' },
|
|
804
|
+
},
|
|
805
|
+
},
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
809
|
+
expect(result.total).toBe(3);
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
it('handles dimension tokens with zero value', () => {
|
|
813
|
+
const content = makeTokenFile({
|
|
814
|
+
spacing: {
|
|
815
|
+
$type: 'dimension',
|
|
816
|
+
none: { $value: { value: 0, unit: 'px' } },
|
|
817
|
+
},
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
821
|
+
expect(result.categories.spacing[0].value).toBe('0px');
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
it('handles color with all-zero components', () => {
|
|
825
|
+
const content = makeTokenFile({
|
|
826
|
+
color: {
|
|
827
|
+
$type: 'color',
|
|
828
|
+
black: {
|
|
829
|
+
$value: { colorSpace: 'srgb', components: [0, 0, 0] },
|
|
830
|
+
},
|
|
831
|
+
},
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
835
|
+
expect(result.categories.colors[0].value).toBe('rgb(0, 0, 0)');
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
it('handles color with non-normalized components (0-255 range)', () => {
|
|
839
|
+
const content = makeTokenFile({
|
|
840
|
+
color: {
|
|
841
|
+
$type: 'color',
|
|
842
|
+
red: {
|
|
843
|
+
$value: { colorSpace: 'srgb', components: [255, 0, 0] },
|
|
844
|
+
},
|
|
845
|
+
},
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
849
|
+
expect(result.categories.colors[0].value).toBe('rgb(255, 0, 0)');
|
|
850
|
+
});
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
// ---------------------------------------------------------------------------
|
|
854
|
+
// Output shape compatibility
|
|
855
|
+
// ---------------------------------------------------------------------------
|
|
856
|
+
|
|
857
|
+
describe('TokenParseOutput compatibility', () => {
|
|
858
|
+
it('returns the same shape as the SCSS parser', () => {
|
|
859
|
+
const content = makeTokenFile({
|
|
860
|
+
ds: {
|
|
861
|
+
$type: 'color',
|
|
862
|
+
primary: { $value: '#ff5500' },
|
|
863
|
+
},
|
|
864
|
+
});
|
|
865
|
+
|
|
866
|
+
const result = parseDTCGFile(content, 'test.tokens.json');
|
|
867
|
+
|
|
868
|
+
// Verify shape matches TokenParseOutput
|
|
869
|
+
expect(result).toHaveProperty('prefix');
|
|
870
|
+
expect(result).toHaveProperty('categories');
|
|
871
|
+
expect(result).toHaveProperty('total');
|
|
872
|
+
expect(typeof result.prefix).toBe('string');
|
|
873
|
+
expect(typeof result.total).toBe('number');
|
|
874
|
+
expect(typeof result.categories).toBe('object');
|
|
875
|
+
|
|
876
|
+
// Verify individual token shape matches ParsedToken
|
|
877
|
+
const token = result.categories.colors[0];
|
|
878
|
+
expect(token).toHaveProperty('name');
|
|
879
|
+
expect(token).toHaveProperty('category');
|
|
880
|
+
expect(typeof token.name).toBe('string');
|
|
881
|
+
expect(typeof token.category).toBe('string');
|
|
882
|
+
});
|
|
883
|
+
});
|
|
884
|
+
});
|