@servicetitan/hammer-token 2.5.2 → 3.0.1

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