@servicetitan/hammer-token 2.5.0 → 3.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.
Files changed (147) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/README.md +332 -0
  3. package/build/web/core/component-variables.scss +1088 -131
  4. package/build/web/core/component.d.ts +558 -0
  5. package/build/web/core/component.js +6685 -249
  6. package/build/web/core/component.scss +557 -69
  7. package/build/web/core/css-utils/a2-border.css +47 -45
  8. package/build/web/core/css-utils/a2-color.css +443 -227
  9. package/build/web/core/css-utils/a2-font.css +0 -2
  10. package/build/web/core/css-utils/a2-spacing.css +476 -478
  11. package/build/web/core/css-utils/a2-utils.css +992 -772
  12. package/build/web/core/css-utils/border.css +47 -45
  13. package/build/web/core/css-utils/color.css +443 -227
  14. package/build/web/core/css-utils/font.css +0 -2
  15. package/build/web/core/css-utils/spacing.css +476 -478
  16. package/build/web/core/css-utils/utils.css +992 -772
  17. package/build/web/core/index.d.ts +6 -0
  18. package/build/web/core/index.js +1 -1
  19. package/build/web/core/primitive-variables.scss +148 -65
  20. package/build/web/core/primitive.d.ts +209 -0
  21. package/build/web/core/primitive.js +779 -61
  22. package/build/web/core/primitive.scss +207 -124
  23. package/build/web/core/semantic-variables.scss +363 -239
  24. package/build/web/core/semantic.d.ts +221 -0
  25. package/build/web/core/semantic.js +1613 -347
  26. package/build/web/core/semantic.scss +219 -137
  27. package/build/web/index.d.ts +3 -4
  28. package/build/web/index.js +0 -1
  29. package/build/web/types.d.ts +17 -0
  30. package/config.js +121 -497
  31. package/eslint.config.mjs +11 -1
  32. package/package.json +15 -5
  33. package/src/global/primitive/breakpoint.tokens.json +54 -0
  34. package/src/global/primitive/color.tokens.json +1092 -0
  35. package/src/global/primitive/duration.tokens.json +44 -0
  36. package/src/global/primitive/font.tokens.json +151 -0
  37. package/src/global/primitive/radius.tokens.json +94 -0
  38. package/src/global/primitive/size.tokens.json +174 -0
  39. package/src/global/primitive/transition.tokens.json +32 -0
  40. package/src/theme/core/background.tokens.json +1312 -0
  41. package/src/theme/core/border.tokens.json +192 -0
  42. package/src/theme/core/chart.tokens.json +982 -0
  43. package/src/theme/core/component/ai-mark.tokens.json +20 -0
  44. package/src/theme/core/component/alert.tokens.json +261 -0
  45. package/src/theme/core/component/announcement.tokens.json +460 -0
  46. package/src/theme/core/component/avatar.tokens.json +137 -0
  47. package/src/theme/core/component/badge.tokens.json +42 -0
  48. package/src/theme/core/component/breadcrumb.tokens.json +42 -0
  49. package/src/theme/core/component/button-toggle.tokens.json +428 -0
  50. package/src/theme/core/component/button.tokens.json +941 -0
  51. package/src/theme/core/component/calendar.tokens.json +391 -0
  52. package/src/theme/core/component/card.tokens.json +107 -0
  53. package/src/theme/core/component/checkbox.tokens.json +631 -0
  54. package/src/theme/core/component/chip.tokens.json +169 -0
  55. package/src/theme/core/component/combobox.tokens.json +269 -0
  56. package/src/theme/core/component/details.tokens.json +152 -0
  57. package/src/theme/core/component/dialog.tokens.json +87 -0
  58. package/src/theme/core/component/divider.tokens.json +23 -0
  59. package/src/theme/core/component/dnd.tokens.json +208 -0
  60. package/src/theme/core/component/drawer.tokens.json +61 -0
  61. package/src/theme/core/component/drilldown.tokens.json +61 -0
  62. package/src/theme/core/component/edit-card.tokens.json +381 -0
  63. package/src/theme/core/component/field-label.tokens.json +42 -0
  64. package/src/theme/core/component/field-message.tokens.json +74 -0
  65. package/src/theme/core/component/icon.tokens.json +42 -0
  66. package/src/theme/core/component/link.tokens.json +108 -0
  67. package/src/theme/core/component/list-view.tokens.json +82 -0
  68. package/src/theme/core/component/listbox.tokens.json +283 -0
  69. package/src/theme/core/component/menu.tokens.json +230 -0
  70. package/src/theme/core/component/overflow.tokens.json +84 -0
  71. package/src/theme/core/component/page.tokens.json +377 -0
  72. package/src/theme/core/component/pagination.tokens.json +63 -0
  73. package/src/theme/core/component/popover.tokens.json +122 -0
  74. package/src/theme/core/component/progress-bar.tokens.json +133 -0
  75. package/src/theme/core/component/radio.tokens.json +631 -0
  76. package/src/theme/core/component/segmented-control.tokens.json +175 -0
  77. package/src/theme/core/component/select-card.tokens.json +943 -0
  78. package/src/theme/core/component/side-nav.tokens.json +349 -0
  79. package/src/theme/core/component/skeleton.tokens.json +42 -0
  80. package/src/theme/core/component/spinner.tokens.json +96 -0
  81. package/src/theme/core/component/status-icon.tokens.json +164 -0
  82. package/src/theme/core/component/stepper.tokens.json +484 -0
  83. package/src/theme/core/component/switch.tokens.json +285 -0
  84. package/src/theme/core/component/tab.tokens.json +192 -0
  85. package/src/theme/core/component/text-field.tokens.json +160 -0
  86. package/src/theme/core/component/text.tokens.json +59 -0
  87. package/src/theme/core/component/toast.tokens.json +343 -0
  88. package/src/theme/core/component/toolbar.tokens.json +114 -0
  89. package/src/theme/core/component/tooltip.tokens.json +61 -0
  90. package/src/theme/core/focus.tokens.json +56 -0
  91. package/src/theme/core/foreground.tokens.json +416 -0
  92. package/src/theme/core/gradient.tokens.json +41 -0
  93. package/src/theme/core/opacity.tokens.json +25 -0
  94. package/src/theme/core/shadow.tokens.json +81 -0
  95. package/src/theme/core/status.tokens.json +74 -0
  96. package/src/theme/core/typography.tokens.json +163 -0
  97. package/src/utils/__tests__/css-utils-format-utils.test.js +312 -0
  98. package/src/utils/__tests__/sd-build-configs.test.js +306 -0
  99. package/src/utils/__tests__/sd-formats.test.js +950 -0
  100. package/src/utils/__tests__/sd-transforms.test.js +336 -0
  101. package/src/utils/__tests__/token-helpers.test.js +1160 -0
  102. package/src/utils/copy-css-utils-cli.js +13 -1
  103. package/src/utils/css-utils-format-utils.js +105 -176
  104. package/src/utils/figma/__tests__/sync-gradient.test.js +561 -0
  105. package/src/utils/figma/__tests__/token-conversion.test.js +117 -0
  106. package/src/utils/figma/__tests__/token-resolution.test.js +231 -0
  107. package/src/utils/figma/auth.js +355 -0
  108. package/src/utils/figma/constants.js +22 -0
  109. package/src/utils/figma/errors.js +80 -0
  110. package/src/utils/figma/figma-api.js +1069 -0
  111. package/src/utils/figma/get-token.js +348 -0
  112. package/src/utils/figma/sync-components.js +909 -0
  113. package/src/utils/figma/sync-main.js +692 -0
  114. package/src/utils/figma/sync-orchestration.js +683 -0
  115. package/src/utils/figma/sync-primitives.js +230 -0
  116. package/src/utils/figma/sync-semantic.js +1056 -0
  117. package/src/utils/figma/token-conversion.js +340 -0
  118. package/src/utils/figma/token-parsing.js +186 -0
  119. package/src/utils/figma/token-resolution.js +569 -0
  120. package/src/utils/figma/utils.js +199 -0
  121. package/src/utils/sd-build-configs.js +305 -0
  122. package/src/utils/sd-formats.js +965 -0
  123. package/src/utils/sd-transforms.js +165 -0
  124. package/src/utils/token-helpers.js +848 -0
  125. package/tsconfig.json +18 -0
  126. package/vitest.config.js +17 -0
  127. package/.turbo/turbo-build.log +0 -37
  128. package/build/web/core/raw.js +0 -229
  129. package/src/global/primitive/breakpoint.js +0 -19
  130. package/src/global/primitive/color.js +0 -231
  131. package/src/global/primitive/duration.js +0 -16
  132. package/src/global/primitive/font.js +0 -60
  133. package/src/global/primitive/radius.js +0 -31
  134. package/src/global/primitive/size.js +0 -55
  135. package/src/global/primitive/transition.js +0 -16
  136. package/src/theme/core/background.js +0 -170
  137. package/src/theme/core/border.js +0 -103
  138. package/src/theme/core/charts.js +0 -439
  139. package/src/theme/core/component/button.js +0 -708
  140. package/src/theme/core/component/checkbox.js +0 -405
  141. package/src/theme/core/focus.js +0 -35
  142. package/src/theme/core/foreground.js +0 -148
  143. package/src/theme/core/overlay.js +0 -137
  144. package/src/theme/core/shadow.js +0 -29
  145. package/src/theme/core/status.js +0 -49
  146. package/src/theme/core/typography.js +0 -82
  147. package/type/types.ts +0 -341
@@ -0,0 +1,950 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { registerFormats } from "../sd-formats.js";
3
+
4
+ // ---------------------------------------------------------------------------
5
+ // Helpers
6
+ // ---------------------------------------------------------------------------
7
+
8
+ /**
9
+ * Creates a mock StyleDictionary that captures registered format functions
10
+ * so we can call them directly in tests.
11
+ */
12
+ const createMockSD = () => {
13
+ const formats = {};
14
+ return {
15
+ registerFormat: vi.fn(({ name, format }) => {
16
+ formats[name] = format;
17
+ }),
18
+ _formats: formats,
19
+ };
20
+ };
21
+
22
+ const mockUsesReferences = (value) =>
23
+ typeof value === "string" && value.includes("{");
24
+
25
+ const mockResolveReferences = (val) => val;
26
+
27
+ const makeToken = (overrides = {}) => ({
28
+ path: ["color", "primary"],
29
+ $value: "#ffffff",
30
+ $type: "color",
31
+ original: { $value: "#ffffff" },
32
+ ...overrides,
33
+ });
34
+
35
+ const makeDictionary = (tokens = []) => ({
36
+ allTokens: tokens,
37
+ unfilteredAllTokens: tokens,
38
+ tokens: {},
39
+ unfilteredTokens: {},
40
+ });
41
+
42
+ let sd;
43
+ beforeEach(() => {
44
+ sd = createMockSD();
45
+ registerFormats(sd, mockUsesReferences, mockResolveReferences, "a2-");
46
+ });
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // Registration checks
50
+ // ---------------------------------------------------------------------------
51
+
52
+ describe("registerFormats", () => {
53
+ it("registers custom/scss-variables", () => {
54
+ expect(sd._formats["custom/scss-variables"]).toBeDefined();
55
+ });
56
+
57
+ it("registers custom/scss-variables-map", () => {
58
+ expect(sd._formats["custom/scss-variables-map"]).toBeDefined();
59
+ });
60
+
61
+ it("registers custom/es6-variable", () => {
62
+ expect(sd._formats["custom/es6-variable"]).toBeDefined();
63
+ });
64
+
65
+ it("registers custom/CSSVariables", () => {
66
+ expect(sd._formats["custom/CSSVariables"]).toBeDefined();
67
+ });
68
+
69
+ it("registers CSS utils formats for both prefixes", () => {
70
+ expect(sd._formats["custom/CSSUtils/All"]).toBeDefined();
71
+ expect(sd._formats["custom/CSSUtils/a2-All"]).toBeDefined();
72
+ expect(sd._formats["custom/CSSUtils/Colors"]).toBeDefined();
73
+ expect(sd._formats["custom/CSSUtils/a2-Colors"]).toBeDefined();
74
+ expect(sd._formats["custom/CSSUtils/Borders"]).toBeDefined();
75
+ expect(sd._formats["custom/CSSUtils/a2-Borders"]).toBeDefined();
76
+ expect(sd._formats["custom/CSSUtils/Fonts"]).toBeDefined();
77
+ expect(sd._formats["custom/CSSUtils/a2-Fonts"]).toBeDefined();
78
+ expect(sd._formats["custom/CSSUtils/Spacing"]).toBeDefined();
79
+ expect(sd._formats["custom/CSSUtils/a2-Spacing"]).toBeDefined();
80
+ });
81
+ });
82
+
83
+ // ---------------------------------------------------------------------------
84
+ // buildCssUtilsOutput (tested via the format output)
85
+ // ---------------------------------------------------------------------------
86
+
87
+ describe("buildCssUtilsOutput structure (via Fonts format)", () => {
88
+ it("output contains @layer declaration", () => {
89
+ const format = sd._formats["custom/CSSUtils/Fonts"];
90
+ const dict = makeDictionary([
91
+ makeToken({
92
+ path: ["typography", "body", "font-family"],
93
+ $value: "Arial",
94
+ }),
95
+ ]);
96
+ const output = format({ dictionary: dict });
97
+ expect(output).toContain("@layer");
98
+ expect(output).toContain("starter");
99
+ });
100
+
101
+ it("output contains @layer application block", () => {
102
+ const format = sd._formats["custom/CSSUtils/Fonts"];
103
+ const dict = makeDictionary([]);
104
+ const output = format({ dictionary: dict });
105
+ expect(output).toContain("@layer application");
106
+ });
107
+
108
+ it("does NOT include @supports block when no dark-aware classes", () => {
109
+ const format = sd._formats["custom/CSSUtils/Fonts"];
110
+ const dict = makeDictionary([
111
+ makeToken({
112
+ path: ["typography", "body", "font-family"],
113
+ $value: "Arial",
114
+ }),
115
+ ]);
116
+ const output = format({ dictionary: dict });
117
+ expect(output).not.toContain("@supports");
118
+ });
119
+ });
120
+
121
+ // ---------------------------------------------------------------------------
122
+ // custom/CSSVariables format
123
+ // ---------------------------------------------------------------------------
124
+
125
+ describe("custom/CSSVariables", () => {
126
+ const format = () => sd._formats["custom/CSSVariables"];
127
+
128
+ it("wraps output in :root { ... }", () => {
129
+ const dict = makeDictionary([
130
+ makeToken({ path: ["color", "primary"], $value: "#ffffff" }),
131
+ ]);
132
+ const output = format()({ dictionary: dict });
133
+ expect(output).toMatch(/^:root \{/);
134
+ expect(output).toMatch(/\}$/);
135
+ });
136
+
137
+ it("outputs CSS custom properties for each token", () => {
138
+ const dict = makeDictionary([
139
+ makeToken({ path: ["size", "4"], $value: "1rem", $type: "dimension" }),
140
+ ]);
141
+ const output = format()({ dictionary: dict });
142
+ expect(output).toContain("--a2-size-4: 1rem;");
143
+ });
144
+
145
+ it("includes light-dark() for tokens with dark value", () => {
146
+ const dict = makeDictionary([
147
+ makeToken({
148
+ path: ["color", "primary"],
149
+ $value: "#ffffff",
150
+ $extensions: { appearance: { dark: { $value: "#000000" } } },
151
+ }),
152
+ ]);
153
+ const output = format()({ dictionary: dict });
154
+ expect(output).toContain("light-dark(#ffffff, #000000)");
155
+ });
156
+
157
+ it("strips -default suffix from token name", () => {
158
+ const dict = makeDictionary([
159
+ makeToken({
160
+ path: ["background", "color", "default"],
161
+ $value: "#fff",
162
+ }),
163
+ ]);
164
+ const output = format()({ dictionary: dict });
165
+ expect(output).toContain("--a2-background-color:");
166
+ expect(output).not.toContain("--a2-background-color-default:");
167
+ });
168
+ });
169
+
170
+ // ---------------------------------------------------------------------------
171
+ // custom/scss-variables format
172
+ // ---------------------------------------------------------------------------
173
+
174
+ describe("custom/scss-variables", () => {
175
+ const format = () => sd._formats["custom/scss-variables"];
176
+
177
+ it("generates $name: var(--prefix-name, fallback); for regular tokens", () => {
178
+ const dict = makeDictionary([
179
+ makeToken({ path: ["color", "primary"], $value: "#ffffff" }),
180
+ ]);
181
+ const output = format()({ dictionary: dict });
182
+ expect(output).toContain("$color-primary: var(--a2-color-primary,");
183
+ });
184
+
185
+ it("generates plain value for overlay-color tokens (no CSS var)", () => {
186
+ const dict = makeDictionary([
187
+ makeToken({
188
+ path: ["overlay", "color", "scrim"],
189
+ $value: "rgba(0,0,0,0.5)",
190
+ }),
191
+ ]);
192
+ const output = format()({ dictionary: dict });
193
+ expect(output).toContain("$overlay-color-scrim: rgba(0,0,0,0.5);");
194
+ expect(output).not.toContain("var(--");
195
+ });
196
+ });
197
+
198
+ // ---------------------------------------------------------------------------
199
+ // custom/scss-variables-map format
200
+ // ---------------------------------------------------------------------------
201
+
202
+ describe("custom/scss-variables-map", () => {
203
+ const format = () => sd._formats["custom/scss-variables-map"];
204
+
205
+ it("generates $token map when no tokens have dark variants", () => {
206
+ const dict = makeDictionary([
207
+ makeToken({ path: ["size", "4"], $value: "1rem", $type: "dimension" }),
208
+ ]);
209
+ const output = format()({ dictionary: dict, file: {} });
210
+ expect(output).toContain("$token: (");
211
+ });
212
+
213
+ it("generates $light, $dark, $nonColor maps when tokens have dark variants", () => {
214
+ const dict = makeDictionary([
215
+ makeToken({
216
+ path: ["color", "primary"],
217
+ $value: "#ffffff",
218
+ $type: "color",
219
+ original: {
220
+ $value: "#ffffff",
221
+ $extensions: { appearance: { dark: { $value: "#000000" } } },
222
+ },
223
+ $extensions: { appearance: { dark: { $value: "#000000" } } },
224
+ }),
225
+ ]);
226
+ const output = format()({ dictionary: dict, file: {} });
227
+ expect(output).toContain("$light: (");
228
+ expect(output).toContain("$dark: (");
229
+ expect(output).toContain("$nonColor: (");
230
+ });
231
+
232
+ it("detects component build via file.destination containing 'component'", () => {
233
+ const dict = makeDictionary([
234
+ makeToken({
235
+ path: ["color", "primary"],
236
+ $value: "#ffffff",
237
+ $type: "color",
238
+ original: {
239
+ $value: "#ffffff",
240
+ $extensions: { appearance: { dark: { $value: "#000000" } } },
241
+ },
242
+ $extensions: { appearance: { dark: { $value: "#000000" } } },
243
+ filePath: "src/theme/core/component/button.tokens.json",
244
+ }),
245
+ ]);
246
+ // Should not throw for component build
247
+ expect(() =>
248
+ format()({
249
+ dictionary: dict,
250
+ file: { destination: "component-variables.scss" },
251
+ }),
252
+ ).not.toThrow();
253
+ });
254
+ });
255
+
256
+ // ---------------------------------------------------------------------------
257
+ // custom/es6-variable format
258
+ // ---------------------------------------------------------------------------
259
+
260
+ describe("custom/es6-variable", () => {
261
+ const format = () => sd._formats["custom/es6-variable"];
262
+
263
+ it("generates TokenValue export for tokens without dark variant", () => {
264
+ const dict = makeDictionary([
265
+ makeToken({ path: ["size", "4"], $value: "1rem", $type: "dimension" }),
266
+ ]);
267
+ const output = format()({ dictionary: dict });
268
+ expect(output).toContain("@type {TokenValue}");
269
+ expect(output).toContain('export const Size4 = { value: "1rem" };');
270
+ });
271
+
272
+ it("generates TokenWithAppearance export for tokens with dark variant", () => {
273
+ const dict = makeDictionary([
274
+ makeToken({
275
+ path: ["color", "primary"],
276
+ $value: "#ffffff",
277
+ $type: "color",
278
+ original: {
279
+ $value: "#ffffff",
280
+ $extensions: { appearance: { dark: { $value: "#000000" } } },
281
+ },
282
+ $extensions: { appearance: { dark: { $value: "#000000" } } },
283
+ }),
284
+ ]);
285
+ const output = format()({ dictionary: dict });
286
+ expect(output).toContain("@type {TokenWithAppearance}");
287
+ expect(output).toContain("extensions");
288
+ expect(output).toContain("appearance");
289
+ expect(output).toContain("dark");
290
+ });
291
+
292
+ it("converts token name to PascalCase export name", () => {
293
+ const dict = makeDictionary([
294
+ makeToken({
295
+ path: ["background", "color", "primary"],
296
+ $value: "#fff",
297
+ }),
298
+ ]);
299
+ const output = format()({ dictionary: dict });
300
+ expect(output).toContain("export const BackgroundColorPrimary");
301
+ });
302
+
303
+ it("includes type definition header", () => {
304
+ const dict = makeDictionary([]);
305
+ const output = format()({ dictionary: dict });
306
+ expect(output).toContain("@typedef {Object} TokenValue");
307
+ expect(output).toContain("@typedef {Object} TokenWithAppearance");
308
+ });
309
+
310
+ it("includes @description JSDoc tag when token has $description", () => {
311
+ const dict = makeDictionary([
312
+ makeToken({
313
+ path: ["size", "4"],
314
+ $value: "1rem",
315
+ $type: "dimension",
316
+ $description: "Base spacing unit",
317
+ }),
318
+ ]);
319
+ const output = format()({ dictionary: dict });
320
+ expect(output).toContain("@description Base spacing unit");
321
+ });
322
+
323
+ it("omits @description tag when token has no $description", () => {
324
+ const dict = makeDictionary([
325
+ makeToken({ path: ["size", "4"], $value: "1rem", $type: "dimension" }),
326
+ ]);
327
+ const output = format()({ dictionary: dict });
328
+ expect(output).not.toContain("@description");
329
+ });
330
+ });
331
+
332
+ // ---------------------------------------------------------------------------
333
+ // custom/CSSVariables — $description comment
334
+ // ---------------------------------------------------------------------------
335
+
336
+ describe("custom/CSSVariables — $description comments", () => {
337
+ const format = () => sd._formats["custom/CSSVariables"];
338
+
339
+ it("prepends /* description */ comment when token has $description", () => {
340
+ const dict = makeDictionary([
341
+ makeToken({
342
+ path: ["color", "primary"],
343
+ $value: "#ffffff",
344
+ $description: "Primary brand color",
345
+ }),
346
+ ]);
347
+ const output = format()({ dictionary: dict });
348
+ expect(output).toContain("/* Primary brand color */");
349
+ });
350
+
351
+ it("omits comment when token has no $description", () => {
352
+ const dict = makeDictionary([
353
+ makeToken({ path: ["color", "primary"], $value: "#ffffff" }),
354
+ ]);
355
+ const output = format()({ dictionary: dict });
356
+ expect(output).not.toContain("/*");
357
+ });
358
+ });
359
+
360
+ // ---------------------------------------------------------------------------
361
+ // custom/scss-variables — $description comment
362
+ // ---------------------------------------------------------------------------
363
+
364
+ describe("custom/scss-variables — $description comments", () => {
365
+ const format = () => sd._formats["custom/scss-variables"];
366
+
367
+ it("prepends // description comment when token has $description", () => {
368
+ const dict = makeDictionary([
369
+ makeToken({
370
+ path: ["color", "primary"],
371
+ $value: "#ffffff",
372
+ $description: "Primary brand color",
373
+ }),
374
+ ]);
375
+ const output = format()({ dictionary: dict });
376
+ expect(output).toContain("// Primary brand color");
377
+ });
378
+
379
+ it("prepends // comment for overlay-color tokens with $description", () => {
380
+ const dict = makeDictionary([
381
+ makeToken({
382
+ path: ["overlay", "color", "scrim"],
383
+ $value: "rgba(0,0,0,0.5)",
384
+ $description: "Scrim overlay",
385
+ }),
386
+ ]);
387
+ const output = format()({ dictionary: dict });
388
+ expect(output).toContain("// Scrim overlay");
389
+ expect(output).toContain("$overlay-color-scrim:");
390
+ });
391
+
392
+ it("omits comment when token has no $description", () => {
393
+ const dict = makeDictionary([
394
+ makeToken({ path: ["color", "primary"], $value: "#ffffff" }),
395
+ ]);
396
+ const output = format()({ dictionary: dict });
397
+ expect(output).not.toContain("//");
398
+ });
399
+ });
400
+
401
+ // ---------------------------------------------------------------------------
402
+ // custom/CSSUtils/Colors format
403
+ // ---------------------------------------------------------------------------
404
+
405
+ describe("custom/CSSUtils/Colors", () => {
406
+ const format = () => sd._formats["custom/CSSUtils/Colors"];
407
+
408
+ it("output contains @layer declaration", () => {
409
+ const dict = makeDictionary([]);
410
+ const output = format()({ dictionary: dict });
411
+ expect(output).toContain("@layer");
412
+ });
413
+
414
+ it("generates .bg-* class for background-color tokens", () => {
415
+ const dict = makeDictionary([
416
+ makeToken({
417
+ path: ["background", "color", "primary"],
418
+ $value: "#ffffff",
419
+ original: { $value: "#ffffff" },
420
+ }),
421
+ ]);
422
+ const output = format()({ dictionary: dict });
423
+ expect(output).toContain(".bg-primary");
424
+ expect(output).toContain("background-color:");
425
+ });
426
+
427
+ it("includes @supports block for tokens with dark variants", () => {
428
+ const dict = makeDictionary([
429
+ makeToken({
430
+ path: ["background", "color", "primary"],
431
+ $value: "#ffffff",
432
+ original: {
433
+ $value: "#ffffff",
434
+ $extensions: { appearance: { dark: { $value: "#111111" } } },
435
+ },
436
+ $extensions: { appearance: { dark: { $value: "#111111" } } },
437
+ }),
438
+ ]);
439
+ const output = format()({ dictionary: dict });
440
+ expect(output).toContain("@supports (color: light-dark(#fff, #000))");
441
+ });
442
+ });
443
+
444
+ // ---------------------------------------------------------------------------
445
+ // custom/CSSUtils/Borders format
446
+ // ---------------------------------------------------------------------------
447
+
448
+ describe("custom/CSSUtils/Borders", () => {
449
+ const format = () => sd._formats["custom/CSSUtils/Borders"];
450
+
451
+ it("output contains @layer declaration", () => {
452
+ const dict = makeDictionary([]);
453
+ const output = format()({ dictionary: dict });
454
+ expect(output).toContain("@layer");
455
+ });
456
+
457
+ it("generates border-radius class", () => {
458
+ const dict = makeDictionary([
459
+ makeToken({
460
+ path: ["border", "radius", "small"],
461
+ $value: "4px",
462
+ $type: "dimension",
463
+ original: { $value: "4px" },
464
+ }),
465
+ ]);
466
+ const output = format()({ dictionary: dict });
467
+ expect(output).toContain("border-radius:");
468
+ expect(output).toContain("4px");
469
+ });
470
+
471
+ it("includes @supports block for border-color tokens with dark variants", () => {
472
+ const dict = makeDictionary([
473
+ makeToken({
474
+ path: ["border", "color", "default"],
475
+ $value: "#cccccc",
476
+ $type: "color",
477
+ original: {
478
+ $value: "#cccccc",
479
+ $extensions: { appearance: { dark: { $value: "#444444" } } },
480
+ },
481
+ $extensions: { appearance: { dark: { $value: "#444444" } } },
482
+ }),
483
+ ]);
484
+ const output = format()({ dictionary: dict });
485
+ expect(output).toContain("@supports (color: light-dark(#fff, #000))");
486
+ });
487
+ });
488
+
489
+ // ---------------------------------------------------------------------------
490
+ // custom/CSSUtils/Spacing format
491
+ // ---------------------------------------------------------------------------
492
+
493
+ describe("custom/CSSUtils/Spacing", () => {
494
+ const format = () => sd._formats["custom/CSSUtils/Spacing"];
495
+
496
+ it("output contains @layer declaration", () => {
497
+ const dict = makeDictionary([]);
498
+ const output = format()({ dictionary: dict });
499
+ expect(output).toContain("@layer");
500
+ });
501
+
502
+ it("generates spacing classes for size tokens", () => {
503
+ const dict = makeDictionary([
504
+ makeToken({
505
+ path: ["size", "4"],
506
+ $value: "1rem",
507
+ $type: "dimension",
508
+ original: { $value: "1rem" },
509
+ }),
510
+ ]);
511
+ const output = format()({ dictionary: dict });
512
+ expect(output).toContain("margin");
513
+ expect(output).toContain("padding");
514
+ expect(output).toContain("-4");
515
+ });
516
+
517
+ it("ignores non-size tokens", () => {
518
+ const dict = makeDictionary([
519
+ makeToken({
520
+ path: ["color", "primary"],
521
+ $value: "#fff",
522
+ original: { $value: "#fff" },
523
+ }),
524
+ ]);
525
+ const output = format()({ dictionary: dict });
526
+ // Should not contain color-related properties
527
+ expect(output).not.toContain("background-color");
528
+ });
529
+ });
530
+
531
+ // ---------------------------------------------------------------------------
532
+ // custom/CSSUtils/All format
533
+ // ---------------------------------------------------------------------------
534
+
535
+ describe("custom/CSSUtils/All", () => {
536
+ const format = () => sd._formats["custom/CSSUtils/All"];
537
+
538
+ it("output contains @layer declaration", () => {
539
+ const dict = makeDictionary([]);
540
+ const output = format()({ dictionary: dict });
541
+ expect(output).toContain("@layer");
542
+ });
543
+
544
+ it("includes .sr-only utility class", () => {
545
+ const dict = makeDictionary([]);
546
+ const output = format()({ dictionary: dict });
547
+ expect(output).toContain(".sr-only");
548
+ });
549
+
550
+ it("generates color classes for background-color tokens", () => {
551
+ const dict = makeDictionary([
552
+ makeToken({
553
+ path: ["background", "color", "primary"],
554
+ $value: "#ffffff",
555
+ original: { $value: "#ffffff" },
556
+ }),
557
+ ]);
558
+ const output = format()({ dictionary: dict });
559
+ expect(output).toContain("background-color");
560
+ });
561
+
562
+ it("generates spacing classes for size tokens", () => {
563
+ const dict = makeDictionary([
564
+ makeToken({
565
+ path: ["size", "4"],
566
+ $value: "1rem",
567
+ $type: "dimension",
568
+ original: { $value: "1rem" },
569
+ }),
570
+ ]);
571
+ const output = format()({ dictionary: dict });
572
+ expect(output).toContain("margin");
573
+ expect(output).toContain("padding");
574
+ });
575
+
576
+ it("generates font classes for typography tokens", () => {
577
+ const dict = makeDictionary([
578
+ makeToken({
579
+ path: ["typography", "body", "font-family"],
580
+ $value: "Arial",
581
+ $type: "fontFamily",
582
+ original: { $value: "Arial" },
583
+ }),
584
+ ]);
585
+ const output = format()({ dictionary: dict });
586
+ expect(output).toContain("font-family");
587
+ });
588
+
589
+ it("includes @supports block for color tokens with dark variants", () => {
590
+ const dict = makeDictionary([
591
+ makeToken({
592
+ path: ["background", "color", "primary"],
593
+ $value: "#ffffff",
594
+ original: {
595
+ $value: "#ffffff",
596
+ $extensions: { appearance: { dark: { $value: "#000000" } } },
597
+ },
598
+ $extensions: { appearance: { dark: { $value: "#000000" } } },
599
+ }),
600
+ ]);
601
+ const output = format()({ dictionary: dict });
602
+ expect(output).toContain("@supports (color: light-dark(#fff, #000))");
603
+ });
604
+ });
605
+
606
+ // ---------------------------------------------------------------------------
607
+ // Gradient tokens in custom/CSSVariables
608
+ // ---------------------------------------------------------------------------
609
+
610
+ describe("custom/CSSVariables — gradient tokens", () => {
611
+ const format = () => sd._formats["custom/CSSVariables"];
612
+
613
+ const makeGradientToken = (hasDark = true) => ({
614
+ path: ["gradient", "primary"],
615
+ $type: "gradient",
616
+ $value: {
617
+ type: "linear",
618
+ angle: 90,
619
+ stops: [
620
+ {
621
+ position: 0,
622
+ color: {
623
+ $type: "color",
624
+ $value: "#0265dc",
625
+ $extensions: {
626
+ appearance: {
627
+ light: { $type: "color", $value: "#0265dc" },
628
+ ...(hasDark
629
+ ? { dark: { $type: "color", $value: "#45d8f2" } }
630
+ : {}),
631
+ },
632
+ },
633
+ },
634
+ },
635
+ {
636
+ position: 1,
637
+ color: {
638
+ $type: "color",
639
+ $value: "#45d8f2",
640
+ $extensions: {
641
+ appearance: {
642
+ light: { $type: "color", $value: "#45d8f2" },
643
+ ...(hasDark
644
+ ? { dark: { $type: "color", $value: "#cdf4fb" } }
645
+ : {}),
646
+ },
647
+ },
648
+ },
649
+ },
650
+ ],
651
+ },
652
+ original: {
653
+ $value: {
654
+ type: "linear",
655
+ angle: 90,
656
+ stops: [
657
+ {
658
+ position: 0,
659
+ color: {
660
+ $type: "color",
661
+ $value: "#0265dc",
662
+ $extensions: {
663
+ appearance: {
664
+ light: { $type: "color", $value: "#0265dc" },
665
+ ...(hasDark
666
+ ? { dark: { $type: "color", $value: "#45d8f2" } }
667
+ : {}),
668
+ },
669
+ },
670
+ },
671
+ },
672
+ {
673
+ position: 1,
674
+ color: {
675
+ $type: "color",
676
+ $value: "#45d8f2",
677
+ $extensions: {
678
+ appearance: {
679
+ light: { $type: "color", $value: "#45d8f2" },
680
+ ...(hasDark
681
+ ? { dark: { $type: "color", $value: "#cdf4fb" } }
682
+ : {}),
683
+ },
684
+ },
685
+ },
686
+ },
687
+ ],
688
+ },
689
+ },
690
+ });
691
+
692
+ it("emits CSS variable with linear-gradient() for gradient token", () => {
693
+ const dict = makeDictionary([makeGradientToken(false)]);
694
+ const output = format()({ dictionary: dict });
695
+ expect(output).toContain("--a2-gradient-primary:");
696
+ expect(output).toContain("linear-gradient(");
697
+ });
698
+
699
+ it("emits light-dark() wrapper for gradient token with dark stop variants", () => {
700
+ const dict = makeDictionary([makeGradientToken(true)]);
701
+ const output = format()({ dictionary: dict });
702
+ expect(output).toContain("light-dark(");
703
+ expect(output).toContain("linear-gradient(");
704
+ });
705
+
706
+ it("includes $description as CSS comment for gradient token", () => {
707
+ const token = {
708
+ ...makeGradientToken(false),
709
+ $description: "Primary brand gradient",
710
+ };
711
+ const dict = makeDictionary([token]);
712
+ const output = format()({ dictionary: dict });
713
+ expect(output).toContain("/* Primary brand gradient */");
714
+ });
715
+ });
716
+
717
+ // ---------------------------------------------------------------------------
718
+ // Gradient tokens in custom/scss-variables
719
+ // ---------------------------------------------------------------------------
720
+
721
+ describe("custom/scss-variables — gradient tokens", () => {
722
+ const format = () => sd._formats["custom/scss-variables"];
723
+
724
+ const makeGradientToken = () => ({
725
+ path: ["gradient", "primary"],
726
+ $type: "gradient",
727
+ $value: {
728
+ type: "linear",
729
+ angle: 90,
730
+ stops: [
731
+ {
732
+ position: 0,
733
+ color: {
734
+ $type: "color",
735
+ $value: "#0265dc",
736
+ $extensions: {
737
+ appearance: {
738
+ light: { $type: "color", $value: "#0265dc" },
739
+ dark: { $type: "color", $value: "#45d8f2" },
740
+ },
741
+ },
742
+ },
743
+ },
744
+ ],
745
+ },
746
+ original: {
747
+ $value: {
748
+ type: "linear",
749
+ angle: 90,
750
+ stops: [
751
+ {
752
+ position: 0,
753
+ color: {
754
+ $type: "color",
755
+ $value: "#0265dc",
756
+ $extensions: {
757
+ appearance: {
758
+ light: { $type: "color", $value: "#0265dc" },
759
+ dark: { $type: "color", $value: "#45d8f2" },
760
+ },
761
+ },
762
+ },
763
+ },
764
+ ],
765
+ },
766
+ },
767
+ });
768
+
769
+ it("emits $name: var(--prefix-name, linear-gradient(...)) for gradient tokens", () => {
770
+ const dict = makeDictionary([makeGradientToken()]);
771
+ const output = format()({ dictionary: dict });
772
+ expect(output).toContain("$gradient-primary:");
773
+ expect(output).toContain("linear-gradient(");
774
+ });
775
+
776
+ it("includes $description as SCSS comment for gradient token", () => {
777
+ const token = {
778
+ ...makeGradientToken(),
779
+ $description: "Primary brand gradient",
780
+ };
781
+ const dict = makeDictionary([token]);
782
+ const output = format()({ dictionary: dict });
783
+ expect(output).toContain("// Primary brand gradient");
784
+ });
785
+ });
786
+
787
+ // ---------------------------------------------------------------------------
788
+ // Gradient tokens in custom/es6-variable
789
+ // ---------------------------------------------------------------------------
790
+
791
+ describe("custom/es6-variable — gradient tokens", () => {
792
+ const format = () => sd._formats["custom/es6-variable"];
793
+
794
+ const makeGradientToken = () => ({
795
+ path: ["gradient", "primary"],
796
+ $type: "gradient",
797
+ $value: {
798
+ type: "linear",
799
+ angle: 90,
800
+ stops: [
801
+ {
802
+ position: 0,
803
+ color: {
804
+ $type: "color",
805
+ $value: "#0265dc",
806
+ $extensions: {
807
+ appearance: {
808
+ light: { $type: "color", $value: "#0265dc" },
809
+ dark: { $type: "color", $value: "#45d8f2" },
810
+ },
811
+ },
812
+ },
813
+ },
814
+ {
815
+ position: 1,
816
+ color: {
817
+ $type: "color",
818
+ $value: "#45d8f2",
819
+ $extensions: {
820
+ appearance: {
821
+ light: { $type: "color", $value: "#45d8f2" },
822
+ dark: { $type: "color", $value: "#cdf4fb" },
823
+ },
824
+ },
825
+ },
826
+ },
827
+ ],
828
+ },
829
+ original: {
830
+ $value: {
831
+ type: "linear",
832
+ angle: 90,
833
+ stops: [
834
+ {
835
+ position: 0,
836
+ color: {
837
+ $type: "color",
838
+ $value: "#0265dc",
839
+ $extensions: {
840
+ appearance: {
841
+ light: { $type: "color", $value: "#0265dc" },
842
+ dark: { $type: "color", $value: "#45d8f2" },
843
+ },
844
+ },
845
+ },
846
+ },
847
+ {
848
+ position: 1,
849
+ color: {
850
+ $type: "color",
851
+ $value: "#45d8f2",
852
+ $extensions: {
853
+ appearance: {
854
+ light: { $type: "color", $value: "#45d8f2" },
855
+ dark: { $type: "color", $value: "#cdf4fb" },
856
+ },
857
+ },
858
+ },
859
+ },
860
+ ],
861
+ },
862
+ },
863
+ });
864
+
865
+ it("emits TokenWithAppearance export for gradient tokens with dark stops", () => {
866
+ const dict = makeDictionary([makeGradientToken()]);
867
+ const output = format()({ dictionary: dict });
868
+ expect(output).toContain("@type {TokenWithAppearance}");
869
+ expect(output).toContain("GradientPrimary");
870
+ });
871
+
872
+ it("includes both light and dark gradient string values", () => {
873
+ const dict = makeDictionary([makeGradientToken()]);
874
+ const output = format()({ dictionary: dict });
875
+ expect(output).toContain("linear-gradient(");
876
+ // es6-variable uses { value: "<light>", extensions: { appearance: { dark: { value: "<dark>" } } } }
877
+ expect(output).toContain("value:");
878
+ expect(output).toContain("dark:");
879
+ });
880
+
881
+ it("includes $description as @description JSDoc tag for gradient token", () => {
882
+ const token = {
883
+ ...makeGradientToken(),
884
+ $description: "Primary brand gradient",
885
+ };
886
+ const dict = makeDictionary([token]);
887
+ const output = format()({ dictionary: dict });
888
+ expect(output).toContain("@description Primary brand gradient");
889
+ });
890
+ });
891
+
892
+ // ---------------------------------------------------------------------------
893
+ // Gradient tokens in custom/scss-variables-map
894
+ // ---------------------------------------------------------------------------
895
+
896
+ describe("custom/scss-variables-map — gradient tokens", () => {
897
+ const format = () => sd._formats["custom/scss-variables-map"];
898
+
899
+ const makeGradientToken = () => ({
900
+ path: ["gradient", "primary"],
901
+ $type: "gradient",
902
+ $value: {
903
+ type: "linear",
904
+ angle: 90,
905
+ stops: [
906
+ {
907
+ position: 0,
908
+ color: {
909
+ $type: "color",
910
+ $value: "#0265dc",
911
+ $extensions: {
912
+ appearance: {
913
+ light: { $type: "color", $value: "#0265dc" },
914
+ dark: { $type: "color", $value: "#45d8f2" },
915
+ },
916
+ },
917
+ },
918
+ },
919
+ ],
920
+ },
921
+ original: {
922
+ $value: {
923
+ type: "linear",
924
+ angle: 90,
925
+ stops: [
926
+ {
927
+ position: 0,
928
+ color: {
929
+ $type: "color",
930
+ $value: "#0265dc",
931
+ $extensions: {
932
+ appearance: {
933
+ light: { $type: "color", $value: "#0265dc" },
934
+ dark: { $type: "color", $value: "#45d8f2" },
935
+ },
936
+ },
937
+ },
938
+ },
939
+ ],
940
+ },
941
+ },
942
+ });
943
+
944
+ it("includes gradient in the nonColor map as a quoted string", () => {
945
+ const dict = makeDictionary([makeGradientToken()]);
946
+ const output = format()({ dictionary: dict, file: {} });
947
+ expect(output).toContain("gradient-primary");
948
+ expect(output).toContain("linear-gradient(");
949
+ });
950
+ });