@primitiv-ui/tokens 0.1.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/README.md +120 -0
- package/package.json +38 -0
- package/src/context.json +5314 -0
- package/src/dtcg.test.ts +619 -0
- package/src/dtcg.ts +297 -0
- package/src/index.ts +14 -0
- package/src/intent.json +486 -0
- package/src/interaction.json +32 -0
- package/src/palette.json +302 -0
- package/src/primitives.json +624 -0
- package/src/serve.ts +27 -0
- package/src/server.test.ts +159 -0
- package/src/server.ts +89 -0
package/src/dtcg.test.ts
ADDED
|
@@ -0,0 +1,619 @@
|
|
|
1
|
+
import { collectionToDtcg, figmaVarsToDtcg } from './dtcg'
|
|
2
|
+
import type { FigmaCollection, FigmaVariable } from './dtcg'
|
|
3
|
+
|
|
4
|
+
const PRIMITIVES: FigmaCollection = {
|
|
5
|
+
id: 'c1',
|
|
6
|
+
name: 'Primitives',
|
|
7
|
+
modes: [{ modeId: 'm1', name: 'Value' }],
|
|
8
|
+
defaultModeId: 'm1',
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function variable(overrides: Partial<FigmaVariable>): FigmaVariable {
|
|
12
|
+
return {
|
|
13
|
+
id: 'v1',
|
|
14
|
+
name: 'token',
|
|
15
|
+
resolvedType: 'STRING',
|
|
16
|
+
variableCollectionId: 'c1',
|
|
17
|
+
valuesByMode: { m1: '' },
|
|
18
|
+
...overrides,
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe('collectionToDtcg', () => {
|
|
23
|
+
it('emits a string token for a STRING variable', () => {
|
|
24
|
+
const result = collectionToDtcg(PRIMITIVES, [
|
|
25
|
+
variable({
|
|
26
|
+
id: 'v1',
|
|
27
|
+
name: 'font-family/heading',
|
|
28
|
+
resolvedType: 'STRING',
|
|
29
|
+
valuesByMode: { m1: 'Asta Sans' },
|
|
30
|
+
}),
|
|
31
|
+
])
|
|
32
|
+
|
|
33
|
+
expect(result).toEqual({
|
|
34
|
+
'font-family': {
|
|
35
|
+
heading: { $type: 'string', $value: 'Asta Sans' },
|
|
36
|
+
},
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('emits a number token for a FLOAT variable', () => {
|
|
41
|
+
const result = collectionToDtcg(PRIMITIVES, [
|
|
42
|
+
variable({
|
|
43
|
+
id: 'v1',
|
|
44
|
+
name: 'font-size/40',
|
|
45
|
+
resolvedType: 'FLOAT',
|
|
46
|
+
valuesByMode: { m1: 40 },
|
|
47
|
+
}),
|
|
48
|
+
])
|
|
49
|
+
|
|
50
|
+
expect(result).toEqual({
|
|
51
|
+
'font-size': {
|
|
52
|
+
'40': { $type: 'number', $value: 40 },
|
|
53
|
+
},
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('emits a boolean token for a BOOLEAN variable', () => {
|
|
58
|
+
const result = collectionToDtcg(PRIMITIVES, [
|
|
59
|
+
variable({
|
|
60
|
+
id: 'v1',
|
|
61
|
+
name: 'flags/enabled',
|
|
62
|
+
resolvedType: 'BOOLEAN',
|
|
63
|
+
valuesByMode: { m1: true },
|
|
64
|
+
}),
|
|
65
|
+
])
|
|
66
|
+
|
|
67
|
+
expect(result).toEqual({
|
|
68
|
+
flags: {
|
|
69
|
+
enabled: { $type: 'boolean', $value: true },
|
|
70
|
+
},
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('emits a color token as hex for a fully opaque COLOR variable', () => {
|
|
75
|
+
const result = collectionToDtcg(PRIMITIVES, [
|
|
76
|
+
variable({
|
|
77
|
+
id: 'v1',
|
|
78
|
+
name: 'color/red',
|
|
79
|
+
resolvedType: 'COLOR',
|
|
80
|
+
valuesByMode: { m1: { r: 1, g: 0, b: 0, a: 1 } },
|
|
81
|
+
}),
|
|
82
|
+
])
|
|
83
|
+
|
|
84
|
+
expect(result).toEqual({
|
|
85
|
+
color: {
|
|
86
|
+
red: { $type: 'color', $value: '#ff0000' },
|
|
87
|
+
},
|
|
88
|
+
})
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('appends alpha to the hex when a COLOR variable is translucent', () => {
|
|
92
|
+
const result = collectionToDtcg(PRIMITIVES, [
|
|
93
|
+
variable({
|
|
94
|
+
id: 'v1',
|
|
95
|
+
name: 'color/red-50',
|
|
96
|
+
resolvedType: 'COLOR',
|
|
97
|
+
valuesByMode: { m1: { r: 1, g: 0, b: 0, a: 0.5 } },
|
|
98
|
+
}),
|
|
99
|
+
])
|
|
100
|
+
|
|
101
|
+
expect(result).toEqual({
|
|
102
|
+
color: {
|
|
103
|
+
'red-50': { $type: 'color', $value: '#ff000080' },
|
|
104
|
+
},
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('nests multiple slash-separated names under shared parents', () => {
|
|
109
|
+
const result = collectionToDtcg(PRIMITIVES, [
|
|
110
|
+
variable({
|
|
111
|
+
id: 'v1',
|
|
112
|
+
name: 'font-family/heading',
|
|
113
|
+
resolvedType: 'STRING',
|
|
114
|
+
valuesByMode: { m1: 'Asta Sans' },
|
|
115
|
+
}),
|
|
116
|
+
variable({
|
|
117
|
+
id: 'v2',
|
|
118
|
+
name: 'font-family/text',
|
|
119
|
+
resolvedType: 'STRING',
|
|
120
|
+
valuesByMode: { m1: 'Crimson Pro' },
|
|
121
|
+
}),
|
|
122
|
+
variable({
|
|
123
|
+
id: 'v3',
|
|
124
|
+
name: 'font-size/40',
|
|
125
|
+
resolvedType: 'FLOAT',
|
|
126
|
+
valuesByMode: { m1: 40 },
|
|
127
|
+
}),
|
|
128
|
+
])
|
|
129
|
+
|
|
130
|
+
expect(result).toEqual({
|
|
131
|
+
'font-family': {
|
|
132
|
+
heading: { $type: 'string', $value: 'Asta Sans' },
|
|
133
|
+
text: { $type: 'string', $value: 'Crimson Pro' },
|
|
134
|
+
},
|
|
135
|
+
'font-size': {
|
|
136
|
+
'40': { $type: 'number', $value: 40 },
|
|
137
|
+
},
|
|
138
|
+
})
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it('ignores variables that belong to a different collection', () => {
|
|
142
|
+
const result = collectionToDtcg(PRIMITIVES, [
|
|
143
|
+
variable({
|
|
144
|
+
id: 'v1',
|
|
145
|
+
name: 'font-family/heading',
|
|
146
|
+
resolvedType: 'STRING',
|
|
147
|
+
valuesByMode: { m1: 'Asta Sans' },
|
|
148
|
+
}),
|
|
149
|
+
variable({
|
|
150
|
+
id: 'v2',
|
|
151
|
+
name: 'display/xl/font-size',
|
|
152
|
+
resolvedType: 'FLOAT',
|
|
153
|
+
variableCollectionId: 'c2',
|
|
154
|
+
valuesByMode: { m1: 40 },
|
|
155
|
+
}),
|
|
156
|
+
])
|
|
157
|
+
|
|
158
|
+
expect(result).toEqual({
|
|
159
|
+
'font-family': {
|
|
160
|
+
heading: { $type: 'string', $value: 'Asta Sans' },
|
|
161
|
+
},
|
|
162
|
+
})
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('reads the value from the collection default mode', () => {
|
|
166
|
+
const result = collectionToDtcg(
|
|
167
|
+
{
|
|
168
|
+
...PRIMITIVES,
|
|
169
|
+
modes: [
|
|
170
|
+
{ modeId: 'm1', name: 'Value' },
|
|
171
|
+
{ modeId: 'm2', name: 'Other' },
|
|
172
|
+
],
|
|
173
|
+
defaultModeId: 'm2',
|
|
174
|
+
},
|
|
175
|
+
[
|
|
176
|
+
variable({
|
|
177
|
+
id: 'v1',
|
|
178
|
+
name: 'font-family/heading',
|
|
179
|
+
resolvedType: 'STRING',
|
|
180
|
+
valuesByMode: { m1: 'ignored', m2: 'Asta Sans' },
|
|
181
|
+
}),
|
|
182
|
+
],
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
expect(result).toEqual({
|
|
186
|
+
'font-family': {
|
|
187
|
+
heading: { $type: 'string', $value: 'Asta Sans' },
|
|
188
|
+
},
|
|
189
|
+
})
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
it('reads from an explicit modeId rather than the collection default', () => {
|
|
193
|
+
const multiMode = {
|
|
194
|
+
...PRIMITIVES,
|
|
195
|
+
modes: [
|
|
196
|
+
{ modeId: 'dense', name: 'Dense' },
|
|
197
|
+
{ modeId: 'comfortable', name: 'Comfortable' },
|
|
198
|
+
],
|
|
199
|
+
defaultModeId: 'comfortable',
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const result = collectionToDtcg(
|
|
203
|
+
multiMode,
|
|
204
|
+
[
|
|
205
|
+
variable({
|
|
206
|
+
id: 'v1',
|
|
207
|
+
name: 'framed-control/md/height',
|
|
208
|
+
resolvedType: 'FLOAT',
|
|
209
|
+
valuesByMode: { dense: 24, comfortable: 40 },
|
|
210
|
+
}),
|
|
211
|
+
],
|
|
212
|
+
undefined,
|
|
213
|
+
'dense',
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
expect(result).toEqual({
|
|
217
|
+
'framed-control': { md: { height: { $type: 'number', $value: 24 } } },
|
|
218
|
+
})
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
describe('aliases', () => {
|
|
222
|
+
it('emits a DTCG reference for an alias to another variable in the same collection', () => {
|
|
223
|
+
const result = collectionToDtcg(PRIMITIVES, [
|
|
224
|
+
variable({
|
|
225
|
+
id: 'v1',
|
|
226
|
+
name: 'font-family/heading',
|
|
227
|
+
resolvedType: 'STRING',
|
|
228
|
+
valuesByMode: { m1: 'Asta Sans' },
|
|
229
|
+
}),
|
|
230
|
+
variable({
|
|
231
|
+
id: 'v2',
|
|
232
|
+
name: 'font-family/default',
|
|
233
|
+
resolvedType: 'STRING',
|
|
234
|
+
valuesByMode: { m1: { type: 'VARIABLE_ALIAS', id: 'v1' } },
|
|
235
|
+
}),
|
|
236
|
+
])
|
|
237
|
+
|
|
238
|
+
expect(result).toEqual({
|
|
239
|
+
'font-family': {
|
|
240
|
+
heading: { $type: 'string', $value: 'Asta Sans' },
|
|
241
|
+
default: { $type: 'string', $value: '{font-family.heading}' },
|
|
242
|
+
},
|
|
243
|
+
})
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
it('preserves the $type of the source variable for an aliased FLOAT', () => {
|
|
247
|
+
const result = collectionToDtcg(PRIMITIVES, [
|
|
248
|
+
variable({
|
|
249
|
+
id: 'v1',
|
|
250
|
+
name: 'font-size/40',
|
|
251
|
+
resolvedType: 'FLOAT',
|
|
252
|
+
valuesByMode: { m1: 40 },
|
|
253
|
+
}),
|
|
254
|
+
variable({
|
|
255
|
+
id: 'v2',
|
|
256
|
+
name: 'font-size/default',
|
|
257
|
+
resolvedType: 'FLOAT',
|
|
258
|
+
valuesByMode: { m1: { type: 'VARIABLE_ALIAS', id: 'v1' } },
|
|
259
|
+
}),
|
|
260
|
+
])
|
|
261
|
+
|
|
262
|
+
expect(result['font-size']).toEqual({
|
|
263
|
+
'40': { $type: 'number', $value: 40 },
|
|
264
|
+
default: { $type: 'number', $value: '{font-size.40}' },
|
|
265
|
+
})
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
it('uses the resolver to look up an alias that targets another collection', () => {
|
|
269
|
+
const resolveAlias = (id: string): string[] => {
|
|
270
|
+
if (id === 'primitives-v1') return ['font-family', 'heading']
|
|
271
|
+
throw new Error(`unexpected lookup ${id}`)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const compact: FigmaCollection = {
|
|
275
|
+
id: 'c-typo-compact',
|
|
276
|
+
name: 'Typography / Compact',
|
|
277
|
+
modes: [{ modeId: 'm1', name: 'Value' }],
|
|
278
|
+
defaultModeId: 'm1',
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const result = collectionToDtcg(
|
|
282
|
+
compact,
|
|
283
|
+
[
|
|
284
|
+
variable({
|
|
285
|
+
id: 'compact-v1',
|
|
286
|
+
name: 'display/xl/font-family',
|
|
287
|
+
resolvedType: 'STRING',
|
|
288
|
+
variableCollectionId: 'c-typo-compact',
|
|
289
|
+
valuesByMode: {
|
|
290
|
+
m1: { type: 'VARIABLE_ALIAS', id: 'primitives-v1' },
|
|
291
|
+
},
|
|
292
|
+
}),
|
|
293
|
+
],
|
|
294
|
+
resolveAlias,
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
expect(result).toEqual({
|
|
298
|
+
display: {
|
|
299
|
+
xl: {
|
|
300
|
+
'font-family': {
|
|
301
|
+
$type: 'string',
|
|
302
|
+
$value: '{font-family.heading}',
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
})
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
it('throws when an alias points to a variable the resolver cannot find', () => {
|
|
310
|
+
expect(() =>
|
|
311
|
+
collectionToDtcg(PRIMITIVES, [
|
|
312
|
+
variable({
|
|
313
|
+
id: 'v1',
|
|
314
|
+
name: 'font-family/default',
|
|
315
|
+
resolvedType: 'STRING',
|
|
316
|
+
valuesByMode: { m1: { type: 'VARIABLE_ALIAS', id: 'missing' } },
|
|
317
|
+
}),
|
|
318
|
+
]),
|
|
319
|
+
).toThrow(/missing/)
|
|
320
|
+
})
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
it('skips variables whose names are in the exclude set', () => {
|
|
324
|
+
const result = collectionToDtcg(
|
|
325
|
+
PRIMITIVES,
|
|
326
|
+
[
|
|
327
|
+
variable({ id: 'v1', name: 'color/keep', resolvedType: 'COLOR', valuesByMode: { m1: { r: 1, g: 0, b: 0, a: 1 } } }),
|
|
328
|
+
variable({ id: 'v2', name: 'color/skip', resolvedType: 'COLOR', valuesByMode: { m1: { r: 0, g: 1, b: 0, a: 1 } } }),
|
|
329
|
+
],
|
|
330
|
+
undefined,
|
|
331
|
+
undefined,
|
|
332
|
+
new Set(['color/skip']),
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
expect(result).toEqual({
|
|
336
|
+
color: { keep: { $type: 'color', $value: '#ff0000' } },
|
|
337
|
+
})
|
|
338
|
+
})
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
describe('figmaVarsToDtcg', () => {
|
|
342
|
+
const PRIMITIVES_COLL: FigmaCollection = {
|
|
343
|
+
id: 'cp',
|
|
344
|
+
name: 'Primitives',
|
|
345
|
+
modes: [{ modeId: 'mp', name: 'Value' }],
|
|
346
|
+
defaultModeId: 'mp',
|
|
347
|
+
}
|
|
348
|
+
const INTERACTION_COLL: FigmaCollection = {
|
|
349
|
+
id: 'ci',
|
|
350
|
+
name: 'Interaction',
|
|
351
|
+
modes: [{ modeId: 'mi', name: 'Value' }],
|
|
352
|
+
defaultModeId: 'mi',
|
|
353
|
+
}
|
|
354
|
+
const PALETTE_COLL: FigmaCollection = {
|
|
355
|
+
id: 'cpal',
|
|
356
|
+
name: 'Primitives / Palette',
|
|
357
|
+
modes: [
|
|
358
|
+
{ modeId: 'light', name: 'Light' },
|
|
359
|
+
{ modeId: 'dark', name: 'Dark' },
|
|
360
|
+
],
|
|
361
|
+
defaultModeId: 'light',
|
|
362
|
+
}
|
|
363
|
+
const INTENT_COLL: FigmaCollection = {
|
|
364
|
+
id: 'cint',
|
|
365
|
+
name: 'Intent',
|
|
366
|
+
modes: [
|
|
367
|
+
{ modeId: 'light', name: 'Light' },
|
|
368
|
+
{ modeId: 'dark', name: 'Dark' },
|
|
369
|
+
],
|
|
370
|
+
defaultModeId: 'light',
|
|
371
|
+
}
|
|
372
|
+
const FOREGROUND_COLL: FigmaCollection = {
|
|
373
|
+
id: 'cfg',
|
|
374
|
+
name: 'Primitives / Foreground',
|
|
375
|
+
modes: [
|
|
376
|
+
{ modeId: 'light', name: 'Light' },
|
|
377
|
+
{ modeId: 'dark', name: 'Dark' },
|
|
378
|
+
],
|
|
379
|
+
defaultModeId: 'light',
|
|
380
|
+
}
|
|
381
|
+
const CONTEXT_COLL: FigmaCollection = {
|
|
382
|
+
id: 'ctx',
|
|
383
|
+
name: 'Context',
|
|
384
|
+
modes: [
|
|
385
|
+
{ modeId: 'dense', name: 'Dense' },
|
|
386
|
+
{ modeId: 'compact', name: 'Compact' },
|
|
387
|
+
{ modeId: 'comfortable', name: 'Comfortable' },
|
|
388
|
+
{ modeId: 'spacious', name: 'Spacious' },
|
|
389
|
+
],
|
|
390
|
+
defaultModeId: 'comfortable',
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
it('returns six empty groups when given no collections', () => {
|
|
394
|
+
expect(figmaVarsToDtcg([], [])).toEqual({
|
|
395
|
+
primitives: {},
|
|
396
|
+
palette: {},
|
|
397
|
+
foreground: {},
|
|
398
|
+
intent: {},
|
|
399
|
+
context: {},
|
|
400
|
+
interaction: {},
|
|
401
|
+
})
|
|
402
|
+
})
|
|
403
|
+
|
|
404
|
+
it('routes a Primitives variable into primitives without a prefix', () => {
|
|
405
|
+
const result = figmaVarsToDtcg(
|
|
406
|
+
[PRIMITIVES_COLL],
|
|
407
|
+
[{ id: 'v1', name: 'font-family/heading', resolvedType: 'STRING', variableCollectionId: 'cp', valuesByMode: { mp: 'Asta Sans' } }],
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
expect(result.primitives).toEqual({
|
|
411
|
+
'font-family': { heading: { $type: 'string', $value: 'Asta Sans' } },
|
|
412
|
+
})
|
|
413
|
+
expect(result.palette).toEqual({})
|
|
414
|
+
expect(result.intent).toEqual({})
|
|
415
|
+
expect(result.context).toEqual({})
|
|
416
|
+
expect(result.interaction).toEqual({})
|
|
417
|
+
})
|
|
418
|
+
|
|
419
|
+
it('routes an Interaction variable into interaction without a prefix', () => {
|
|
420
|
+
const result = figmaVarsToDtcg(
|
|
421
|
+
[INTERACTION_COLL],
|
|
422
|
+
[
|
|
423
|
+
{ id: 'v1', name: 'hover/opacity', resolvedType: 'FLOAT', variableCollectionId: 'ci', valuesByMode: { mi: 0.9 } },
|
|
424
|
+
{ id: 'v2', name: 'focus/ring/width', resolvedType: 'FLOAT', variableCollectionId: 'ci', valuesByMode: { mi: 2 } },
|
|
425
|
+
],
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
expect(result.interaction).toEqual({
|
|
429
|
+
hover: { opacity: { $type: 'number', $value: 0.9 } },
|
|
430
|
+
focus: { ring: { width: { $type: 'number', $value: 2 } } },
|
|
431
|
+
})
|
|
432
|
+
expect(result.primitives).toEqual({})
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
it('routes Primitives / Palette per-mode into palette with mode name as top-level key', () => {
|
|
436
|
+
const result = figmaVarsToDtcg(
|
|
437
|
+
[PALETTE_COLL],
|
|
438
|
+
[
|
|
439
|
+
{
|
|
440
|
+
id: 'v1',
|
|
441
|
+
name: 'color/neutral/50',
|
|
442
|
+
resolvedType: 'COLOR',
|
|
443
|
+
variableCollectionId: 'cpal',
|
|
444
|
+
valuesByMode: {
|
|
445
|
+
light: { r: 0.98, g: 0.98, b: 0.98, a: 1 },
|
|
446
|
+
dark: { r: 0.05, g: 0.05, b: 0.05, a: 1 },
|
|
447
|
+
},
|
|
448
|
+
},
|
|
449
|
+
],
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
expect(result.palette).toEqual({
|
|
453
|
+
light: { color: { neutral: { '50': { $type: 'color', $value: '#fafafa' } } } },
|
|
454
|
+
dark: { color: { neutral: { '50': { $type: 'color', $value: '#0d0d0d' } } } },
|
|
455
|
+
})
|
|
456
|
+
expect(result.primitives).toEqual({})
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
it('routes Intent per-mode into intent with mode name as top-level key', () => {
|
|
460
|
+
const result = figmaVarsToDtcg(
|
|
461
|
+
[INTENT_COLL],
|
|
462
|
+
[
|
|
463
|
+
{
|
|
464
|
+
id: 'v1',
|
|
465
|
+
name: 'action/primary/default',
|
|
466
|
+
resolvedType: 'COLOR',
|
|
467
|
+
variableCollectionId: 'cint',
|
|
468
|
+
valuesByMode: {
|
|
469
|
+
light: { r: 0.2, g: 0.4, b: 0.8, a: 1 },
|
|
470
|
+
dark: { r: 0.1, g: 0.2, b: 0.5, a: 1 },
|
|
471
|
+
},
|
|
472
|
+
},
|
|
473
|
+
],
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
expect(result.intent).toEqual({
|
|
477
|
+
light: { action: { primary: { default: { $type: 'color', $value: '#3366cc' } } } },
|
|
478
|
+
dark: { action: { primary: { default: { $type: 'color', $value: '#1a3380' } } } },
|
|
479
|
+
})
|
|
480
|
+
})
|
|
481
|
+
|
|
482
|
+
it('routes Primitives / Foreground per-mode into foreground, resolving aliases', () => {
|
|
483
|
+
const result = figmaVarsToDtcg(
|
|
484
|
+
[FOREGROUND_COLL],
|
|
485
|
+
[
|
|
486
|
+
{ id: 'pal900', name: 'color/brand/900', resolvedType: 'COLOR', variableCollectionId: 'cpal', valuesByMode: {} },
|
|
487
|
+
{ id: 'palwhite', name: 'color/white', resolvedType: 'COLOR', variableCollectionId: 'cpal', valuesByMode: {} },
|
|
488
|
+
{
|
|
489
|
+
id: 'fg1',
|
|
490
|
+
name: 'foreground/brand/500',
|
|
491
|
+
resolvedType: 'COLOR',
|
|
492
|
+
variableCollectionId: 'cfg',
|
|
493
|
+
valuesByMode: {
|
|
494
|
+
light: { type: 'VARIABLE_ALIAS', id: 'pal900' },
|
|
495
|
+
dark: { type: 'VARIABLE_ALIAS', id: 'palwhite' },
|
|
496
|
+
},
|
|
497
|
+
},
|
|
498
|
+
],
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
expect(result.foreground).toEqual({
|
|
502
|
+
light: { foreground: { brand: { '500': { $type: 'color', $value: '{color.brand.900}' } } } },
|
|
503
|
+
dark: { foreground: { brand: { '500': { $type: 'color', $value: '{color.white}' } } } },
|
|
504
|
+
})
|
|
505
|
+
})
|
|
506
|
+
|
|
507
|
+
it('routes Context per-mode into context with mode name as top-level key', () => {
|
|
508
|
+
const result = figmaVarsToDtcg(
|
|
509
|
+
[CONTEXT_COLL],
|
|
510
|
+
[
|
|
511
|
+
{
|
|
512
|
+
id: 'v1',
|
|
513
|
+
name: 'framed-control/md/height',
|
|
514
|
+
resolvedType: 'FLOAT',
|
|
515
|
+
variableCollectionId: 'ctx',
|
|
516
|
+
valuesByMode: { dense: 24, compact: 32, comfortable: 40, spacious: 48 },
|
|
517
|
+
},
|
|
518
|
+
],
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
expect(result.context).toEqual({
|
|
522
|
+
dense: { 'framed-control': { md: { height: { $type: 'number', $value: 24 } } } },
|
|
523
|
+
compact: { 'framed-control': { md: { height: { $type: 'number', $value: 32 } } } },
|
|
524
|
+
comfortable: { 'framed-control': { md: { height: { $type: 'number', $value: 40 } } } },
|
|
525
|
+
spacious: { 'framed-control': { md: { height: { $type: 'number', $value: 48 } } } },
|
|
526
|
+
})
|
|
527
|
+
})
|
|
528
|
+
|
|
529
|
+
it('resolves a cross-collection alias using the target variable natural name path', () => {
|
|
530
|
+
const result = figmaVarsToDtcg(
|
|
531
|
+
[PRIMITIVES_COLL, CONTEXT_COLL],
|
|
532
|
+
[
|
|
533
|
+
{ id: 'pv1', name: 'space/8', resolvedType: 'FLOAT', variableCollectionId: 'cp', valuesByMode: { mp: 8 } },
|
|
534
|
+
{
|
|
535
|
+
id: 'cv1',
|
|
536
|
+
name: 'framed-control/md/padding-inline',
|
|
537
|
+
resolvedType: 'FLOAT',
|
|
538
|
+
variableCollectionId: 'ctx',
|
|
539
|
+
valuesByMode: { dense: { type: 'VARIABLE_ALIAS', id: 'pv1' }, compact: { type: 'VARIABLE_ALIAS', id: 'pv1' }, comfortable: { type: 'VARIABLE_ALIAS', id: 'pv1' }, spacious: { type: 'VARIABLE_ALIAS', id: 'pv1' } },
|
|
540
|
+
},
|
|
541
|
+
],
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
expect(result.context.comfortable).toEqual({
|
|
545
|
+
'framed-control': { md: { 'padding-inline': { $type: 'number', $value: '{space.8}' } } },
|
|
546
|
+
})
|
|
547
|
+
})
|
|
548
|
+
|
|
549
|
+
it('resolves an Intent → Palette cross-collection alias', () => {
|
|
550
|
+
const result = figmaVarsToDtcg(
|
|
551
|
+
[PALETTE_COLL, INTENT_COLL],
|
|
552
|
+
[
|
|
553
|
+
{ id: 'pal1', name: 'color/brand/500', resolvedType: 'COLOR', variableCollectionId: 'cpal', valuesByMode: { light: { r: 0.1, g: 0.5, b: 0.4, a: 1 }, dark: { r: 0.2, g: 0.6, b: 0.5, a: 1 } } },
|
|
554
|
+
{ id: 'int1', name: 'action/primary/default', resolvedType: 'COLOR', variableCollectionId: 'cint', valuesByMode: { light: { type: 'VARIABLE_ALIAS', id: 'pal1' }, dark: { type: 'VARIABLE_ALIAS', id: 'pal1' } } },
|
|
555
|
+
],
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
expect(result.intent.light).toEqual({
|
|
559
|
+
action: { primary: { default: { $type: 'color', $value: '{color.brand.500}' } } },
|
|
560
|
+
})
|
|
561
|
+
expect(result.intent.dark).toEqual({
|
|
562
|
+
action: { primary: { default: { $type: 'color', $value: '{color.brand.500}' } } },
|
|
563
|
+
})
|
|
564
|
+
})
|
|
565
|
+
|
|
566
|
+
it('silently drops variables whose collection is not in the payload', () => {
|
|
567
|
+
const result = figmaVarsToDtcg(
|
|
568
|
+
[PRIMITIVES_COLL],
|
|
569
|
+
[{ id: 'orphan', name: 'orphan/token', resolvedType: 'STRING', variableCollectionId: 'missing-coll', valuesByMode: { mp: 'unused' } }],
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
expect(result).toEqual({ primitives: {}, palette: {}, foreground: {}, intent: {}, context: {}, interaction: {} })
|
|
573
|
+
})
|
|
574
|
+
|
|
575
|
+
it('excludes color/absolute-white and color/absolute-black from the palette output', () => {
|
|
576
|
+
const result = figmaVarsToDtcg(
|
|
577
|
+
[PALETTE_COLL],
|
|
578
|
+
[
|
|
579
|
+
{ id: 'v1', name: 'color/neutral/50', resolvedType: 'COLOR', variableCollectionId: 'cpal', valuesByMode: { light: { r: 0.98, g: 0.98, b: 0.98, a: 1 }, dark: { r: 0.05, g: 0.05, b: 0.05, a: 1 } } },
|
|
580
|
+
{ id: 'v2', name: 'color/absolute-white', resolvedType: 'COLOR', variableCollectionId: 'cpal', valuesByMode: { light: { r: 1, g: 1, b: 1, a: 1 }, dark: { r: 1, g: 1, b: 1, a: 1 } } },
|
|
581
|
+
{ id: 'v3', name: 'color/absolute-black', resolvedType: 'COLOR', variableCollectionId: 'cpal', valuesByMode: { light: { r: 0, g: 0, b: 0, a: 1 }, dark: { r: 0, g: 0, b: 0, a: 1 } } },
|
|
582
|
+
],
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
expect(result.palette.light).toEqual({
|
|
586
|
+
color: { neutral: { '50': { $type: 'color', $value: '#fafafa' } } },
|
|
587
|
+
})
|
|
588
|
+
expect(result.palette.dark).toEqual({
|
|
589
|
+
color: { neutral: { '50': { $type: 'color', $value: '#0d0d0d' } } },
|
|
590
|
+
})
|
|
591
|
+
})
|
|
592
|
+
|
|
593
|
+
it('throws when a variable aliases an id not present in the payload', () => {
|
|
594
|
+
expect(() =>
|
|
595
|
+
figmaVarsToDtcg(
|
|
596
|
+
[PRIMITIVES_COLL],
|
|
597
|
+
[{ id: 'pv1', name: 'font-family/default', resolvedType: 'STRING', variableCollectionId: 'cp', valuesByMode: { mp: { type: 'VARIABLE_ALIAS', id: 'missing-id' } } }],
|
|
598
|
+
),
|
|
599
|
+
).toThrow(/missing-id/)
|
|
600
|
+
})
|
|
601
|
+
|
|
602
|
+
it('silently drops unrecognised collections', () => {
|
|
603
|
+
const result = figmaVarsToDtcg(
|
|
604
|
+
[
|
|
605
|
+
PRIMITIVES_COLL,
|
|
606
|
+
{ id: 'cx', name: 'Mystery', modes: [{ modeId: 'mx', name: 'Value' }], defaultModeId: 'mx' },
|
|
607
|
+
],
|
|
608
|
+
[
|
|
609
|
+
{ id: 'v1', name: 'font-family/heading', resolvedType: 'STRING', variableCollectionId: 'cp', valuesByMode: { mp: 'Asta Sans' } },
|
|
610
|
+
{ id: 'v2', name: 'orphan/token', resolvedType: 'STRING', variableCollectionId: 'cx', valuesByMode: { mx: 'ignored' } },
|
|
611
|
+
],
|
|
612
|
+
)
|
|
613
|
+
|
|
614
|
+
expect(result.primitives).toEqual({
|
|
615
|
+
'font-family': { heading: { $type: 'string', $value: 'Asta Sans' } },
|
|
616
|
+
})
|
|
617
|
+
expect(result).toEqual(expect.objectContaining({ palette: {}, intent: {}, context: {}, interaction: {} }))
|
|
618
|
+
})
|
|
619
|
+
})
|