@styleframe/core 1.0.1 → 1.0.2

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 (48) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/LICENSE +21 -0
  3. package/dist/styleframe.d.ts +301 -0
  4. package/dist/styleframe.js +528 -0
  5. package/dist/styleframe.umd.cjs +1 -0
  6. package/package.json +13 -3
  7. package/.tsbuildinfo +0 -1
  8. package/src/index.ts +0 -5
  9. package/src/styleframe.ts +0 -64
  10. package/src/tokens/atRule.test.ts +0 -1013
  11. package/src/tokens/atRule.ts +0 -67
  12. package/src/tokens/css.test.ts +0 -404
  13. package/src/tokens/css.ts +0 -23
  14. package/src/tokens/declarations.test.ts +0 -584
  15. package/src/tokens/declarations.ts +0 -71
  16. package/src/tokens/index.ts +0 -11
  17. package/src/tokens/modifier.test.ts +0 -90
  18. package/src/tokens/modifier.ts +0 -86
  19. package/src/tokens/recipe.test.ts +0 -105
  20. package/src/tokens/recipe.ts +0 -32
  21. package/src/tokens/ref.test.ts +0 -430
  22. package/src/tokens/ref.ts +0 -24
  23. package/src/tokens/root.test.ts +0 -70
  24. package/src/tokens/root.ts +0 -14
  25. package/src/tokens/selector.test.ts +0 -440
  26. package/src/tokens/selector.ts +0 -47
  27. package/src/tokens/theme.test.ts +0 -338
  28. package/src/tokens/theme.ts +0 -26
  29. package/src/tokens/utility.test.ts +0 -1456
  30. package/src/tokens/utility.ts +0 -92
  31. package/src/tokens/variable.test.ts +0 -235
  32. package/src/tokens/variable.ts +0 -42
  33. package/src/typeGuards.test.ts +0 -33
  34. package/src/typeGuards.ts +0 -98
  35. package/src/types/declarations.ts +0 -42
  36. package/src/types/index.ts +0 -3
  37. package/src/types/options.ts +0 -22
  38. package/src/types/tokens.ts +0 -149
  39. package/src/utils/capitalizeFirst.ts +0 -9
  40. package/src/utils/deepClone.ts +0 -317
  41. package/src/utils/getters.test.ts +0 -399
  42. package/src/utils/getters.ts +0 -36
  43. package/src/utils/index.ts +0 -4
  44. package/src/utils/merge.test.ts +0 -978
  45. package/src/utils/merge.ts +0 -73
  46. package/src/vite-env.d.ts +0 -1
  47. package/tsconfig.json +0 -7
  48. package/vite.config.ts +0 -5
@@ -1,1456 +0,0 @@
1
- import type { Container, ModifierFactory, Root, Utility } from "../types";
2
- import { createRoot } from "./root";
3
- import { createUtilityFunction } from "./utility";
4
- import { applyModifiers } from "./modifier";
5
- import { isUtility } from "../typeGuards";
6
-
7
- describe("createUtilityFunction", () => {
8
- let root: Root;
9
- let parent: Container;
10
- let utility: ReturnType<typeof createUtilityFunction>;
11
-
12
- beforeEach(() => {
13
- root = createRoot();
14
- parent = root;
15
- utility = createUtilityFunction(parent, root);
16
- });
17
-
18
- test("should create a utility function", () => {
19
- expect(utility).toBeTypeOf("function");
20
- });
21
-
22
- test("should create utility with proper structure", () => {
23
- const createMarginUtility = utility("margin", ({ value }) => ({
24
- margin: value,
25
- }));
26
-
27
- expect(createMarginUtility).toBeTypeOf("function");
28
- expect(root.utilities).toHaveLength(1); // Factory is registered immediately
29
- expect(root.utilities[0]).toMatchObject({
30
- type: "utility",
31
- name: "margin",
32
- });
33
- });
34
-
35
- test("should create utility instances without creating selectors", () => {
36
- const createPaddingUtility = utility("padding", ({ value }) => ({
37
- padding: value,
38
- }));
39
-
40
- createPaddingUtility({
41
- sm: "8px",
42
- md: "16px",
43
- lg: "24px",
44
- });
45
-
46
- // Three utility instances should be created in children
47
- expect(root.children).toHaveLength(3);
48
-
49
- const smUtility = root.children.find(
50
- (u) => isUtility(u) && u.name === "padding" && u.value === "sm",
51
- );
52
- const mdUtility = root.children.find(
53
- (u) => isUtility(u) && u.name === "padding" && u.value === "md",
54
- );
55
- const lgUtility = root.children.find(
56
- (u) => isUtility(u) && u.name === "padding" && u.value === "lg",
57
- );
58
-
59
- expect(smUtility).toEqual({
60
- type: "utility",
61
- name: "padding",
62
- value: "sm",
63
- declarations: { padding: "8px" },
64
- variables: [],
65
- children: [],
66
- modifiers: [],
67
- });
68
- expect(mdUtility).toEqual({
69
- type: "utility",
70
- name: "padding",
71
- value: "md",
72
- declarations: { padding: "16px" },
73
- variables: [],
74
- children: [],
75
- modifiers: [],
76
- });
77
- expect(lgUtility).toEqual({
78
- type: "utility",
79
- name: "padding",
80
- value: "lg",
81
- declarations: { padding: "24px" },
82
- variables: [],
83
- children: [],
84
- modifiers: [],
85
- });
86
- });
87
-
88
- test("should handle boolean values correctly", () => {
89
- const createHiddenUtility = utility("hidden", ({ value }) => ({
90
- display: value === true ? "none" : "block",
91
- }));
92
-
93
- createHiddenUtility({
94
- default: true,
95
- });
96
-
97
- // One utility instance should be created in children
98
- expect(root.children).toHaveLength(1);
99
-
100
- const hiddenUtility = root.children.find(
101
- (u) => isUtility(u) && u.name === "hidden",
102
- );
103
- expect(hiddenUtility).toEqual({
104
- type: "utility",
105
- name: "hidden",
106
- value: "default",
107
- declarations: { display: "none" },
108
- variables: [],
109
- children: [],
110
- modifiers: [],
111
- });
112
- });
113
-
114
- test("should store utility instances with modifiers", () => {
115
- const hoverModifier: ModifierFactory = {
116
- type: "modifier",
117
- key: ["hover"],
118
- factory: ({ declarations }) => ({
119
- "&:hover": declarations,
120
- }),
121
- };
122
-
123
- const createColorUtility = utility("color", ({ value }) => ({
124
- color: value,
125
- }));
126
-
127
- createColorUtility(
128
- {
129
- primary: "#007bff",
130
- secondary: "#6c757d",
131
- },
132
- [hoverModifier],
133
- );
134
-
135
- // Base utilities + modified variants in children
136
- // 2 base utilities + 2 modified = 4 total
137
- expect(root.children).toHaveLength(4);
138
-
139
- const primaryUtility = root.children.find(
140
- (u) =>
141
- isUtility(u) &&
142
- u.name === "color" &&
143
- u.value === "primary" &&
144
- u.modifiers.length === 0,
145
- );
146
- const secondaryUtility = root.children.find(
147
- (u) =>
148
- isUtility(u) &&
149
- u.name === "color" &&
150
- u.value === "secondary" &&
151
- u.modifiers.length === 0,
152
- );
153
-
154
- expect(primaryUtility).toEqual({
155
- type: "utility",
156
- name: "color",
157
- value: "primary",
158
- declarations: { color: "#007bff" },
159
- variables: [],
160
- children: [],
161
- modifiers: [],
162
- });
163
- expect(secondaryUtility).toEqual({
164
- type: "utility",
165
- name: "color",
166
- value: "secondary",
167
- declarations: { color: "#6c757d" },
168
- variables: [],
169
- children: [],
170
- modifiers: [],
171
- });
172
-
173
- // Check modified variants exist
174
- const primaryHover = root.children.find(
175
- (u) =>
176
- isUtility(u) &&
177
- u.name === "color" &&
178
- u.value === "primary" &&
179
- u.modifiers.includes("hover"),
180
- );
181
- const secondaryHover = root.children.find(
182
- (u) =>
183
- isUtility(u) &&
184
- u.name === "color" &&
185
- u.value === "secondary" &&
186
- u.modifiers.includes("hover"),
187
- );
188
-
189
- expect(primaryHover).toBeDefined();
190
- expect(secondaryHover).toBeDefined();
191
- });
192
-
193
- test("should store utility instances with multiple modifiers", () => {
194
- const hoverModifier: ModifierFactory = {
195
- type: "modifier",
196
- key: ["hover"],
197
- factory: ({ declarations }) => ({
198
- "&:hover": declarations,
199
- }),
200
- };
201
-
202
- const focusModifier: ModifierFactory = {
203
- type: "modifier",
204
- key: ["focus"],
205
- factory: ({ declarations }) => ({
206
- "&:focus": declarations,
207
- }),
208
- };
209
-
210
- const createBackgroundUtility = utility("background", ({ value }) => ({
211
- background: value,
212
- }));
213
-
214
- createBackgroundUtility(
215
- {
216
- primary: "#007bff",
217
- },
218
- [hoverModifier, focusModifier],
219
- );
220
-
221
- // 1 base utility + combinations of 2 modifiers
222
- // Combinations: [hover], [focus], [focus,hover] = 3 modified variants
223
- // Total: 1 base + 3 modified = 4
224
- expect(root.children).toHaveLength(4);
225
-
226
- const backgroundUtility = root.children.find(
227
- (u) =>
228
- isUtility(u) && u.name === "background" && u.modifiers.length === 0,
229
- );
230
- expect(backgroundUtility).toEqual({
231
- type: "utility",
232
- name: "background",
233
- value: "primary",
234
- declarations: { background: "#007bff" },
235
- variables: [],
236
- children: [],
237
- modifiers: [],
238
- });
239
-
240
- // Check modified variants exist
241
- const hoverVariant = root.children.find(
242
- (u) =>
243
- isUtility(u) &&
244
- u.name === "background" &&
245
- u.modifiers.includes("hover") &&
246
- !u.modifiers.includes("focus"),
247
- );
248
- const focusVariant = root.children.find(
249
- (u) =>
250
- isUtility(u) &&
251
- u.name === "background" &&
252
- u.modifiers.includes("focus") &&
253
- !u.modifiers.includes("hover"),
254
- );
255
- const bothVariant = root.children.find(
256
- (u) =>
257
- isUtility(u) &&
258
- u.name === "background" &&
259
- u.modifiers.includes("hover") &&
260
- u.modifiers.includes("focus"),
261
- );
262
-
263
- expect(hoverVariant).toBeDefined();
264
- expect(focusVariant).toBeDefined();
265
- expect(bothVariant).toBeDefined();
266
- });
267
-
268
- test("should handle multi-key modifiers correctly", () => {
269
- const breakpointModifier: ModifierFactory = {
270
- type: "modifier",
271
- key: ["sm", "md", "lg"],
272
- factory: ({ declarations }) => ({
273
- "@media screen and (min-width: 640px)": declarations,
274
- }),
275
- };
276
-
277
- const createTextUtility = utility("text", ({ value }) => ({
278
- fontSize: value,
279
- }));
280
-
281
- createTextUtility(
282
- {
283
- base: "14px",
284
- },
285
- [breakpointModifier],
286
- );
287
-
288
- // 1 base utility + combinations for multi-key modifier
289
- // Multi-key ["sm", "md", "lg"] creates: [sm], [md], [lg] = 3 variants
290
- // Total: 1 base + 3 modified = 4
291
- expect(root.children).toHaveLength(4);
292
-
293
- const textUtility = root.children.find(
294
- (u) => isUtility(u) && u.name === "text" && u.modifiers.length === 0,
295
- );
296
- expect(textUtility).toEqual({
297
- type: "utility",
298
- name: "text",
299
- value: "base",
300
- declarations: { fontSize: "14px" },
301
- variables: [],
302
- children: [],
303
- modifiers: [],
304
- });
305
-
306
- // Check breakpoint variants exist
307
- const smVariant = root.children.find(
308
- (u) => isUtility(u) && u.name === "text" && u.modifiers.includes("sm"),
309
- );
310
- const mdVariant = root.children.find(
311
- (u) => isUtility(u) && u.name === "text" && u.modifiers.includes("md"),
312
- );
313
- const lgVariant = root.children.find(
314
- (u) => isUtility(u) && u.name === "text" && u.modifiers.includes("lg"),
315
- );
316
-
317
- expect(smVariant).toBeDefined();
318
- expect(mdVariant).toBeDefined();
319
- expect(lgVariant).toBeDefined();
320
- });
321
-
322
- test("should handle empty modifiers array", () => {
323
- const createWidthUtility = utility("width", ({ value }) => ({
324
- width: value,
325
- }));
326
-
327
- createWidthUtility(
328
- {
329
- full: "100%",
330
- },
331
- [],
332
- );
333
-
334
- // One utility instance should be created with empty modifiers array
335
- expect(root.children).toHaveLength(1);
336
-
337
- const widthUtility = root.children.find(
338
- (u) => isUtility(u) && u.name === "width",
339
- );
340
- expect(widthUtility).toEqual({
341
- type: "utility",
342
- name: "width",
343
- value: "full",
344
- declarations: { width: "100%" },
345
- variables: [],
346
- children: [],
347
- modifiers: [],
348
- });
349
- });
350
-
351
- test("should handle undefined modifiers", () => {
352
- const createHeightUtility = utility("height", ({ value }) => ({
353
- height: value,
354
- }));
355
-
356
- createHeightUtility({
357
- screen: "100vh",
358
- });
359
-
360
- // One utility instance should be created with empty modifiers array (default)
361
- expect(root.children).toHaveLength(1);
362
-
363
- const heightUtility = root.children.find(
364
- (u) => isUtility(u) && u.name === "height",
365
- );
366
- expect(heightUtility).toEqual({
367
- type: "utility",
368
- name: "height",
369
- value: "screen",
370
- declarations: { height: "100vh" },
371
- variables: [],
372
- children: [],
373
- modifiers: [],
374
- });
375
- });
376
-
377
- test("should handle complex declarations", () => {
378
- const createFlexUtility = utility("flex", ({ value }) => ({
379
- display: "flex",
380
- flexDirection: value === "col" ? "column" : "row",
381
- gap: "1rem",
382
- }));
383
-
384
- createFlexUtility({
385
- row: "row",
386
- col: "col",
387
- });
388
-
389
- // Two utility instances should be created
390
- expect(root.children).toHaveLength(2);
391
-
392
- const rowUtility = root.children.find(
393
- (u) => isUtility(u) && u.name === "flex" && u.value === "row",
394
- );
395
- const colUtility = root.children.find(
396
- (u) => isUtility(u) && u.name === "flex" && u.value === "col",
397
- );
398
-
399
- expect(rowUtility).toEqual({
400
- type: "utility",
401
- name: "flex",
402
- value: "row",
403
- declarations: {
404
- display: "flex",
405
- flexDirection: "row",
406
- gap: "1rem",
407
- },
408
- variables: [],
409
- children: [],
410
- modifiers: [],
411
- });
412
- expect(colUtility).toEqual({
413
- type: "utility",
414
- name: "flex",
415
- value: "col",
416
- declarations: {
417
- display: "flex",
418
- flexDirection: "column",
419
- gap: "1rem",
420
- },
421
- variables: [],
422
- children: [],
423
- modifiers: [],
424
- });
425
- });
426
-
427
- test("should preserve utility order in root utilities array", () => {
428
- const createMarginUtility = utility("margin", ({ value }) => ({
429
- margin: value,
430
- }));
431
-
432
- const createPaddingUtility = utility("padding", ({ value }) => ({
433
- padding: value,
434
- }));
435
-
436
- // Create some utilities
437
- createMarginUtility({ sm: "8px" });
438
- createPaddingUtility({ md: "16px" });
439
-
440
- expect(root.utilities).toHaveLength(2);
441
- expect(root.utilities[0]?.name).toBe("margin");
442
- expect(root.utilities[1]?.name).toBe("padding");
443
- });
444
-
445
- test("should handle empty entries object", () => {
446
- const createEmptyUtility = utility("empty", ({ value }) => ({
447
- display: value,
448
- }));
449
-
450
- createEmptyUtility({});
451
-
452
- // No utility instances should be created for empty entries
453
- expect(root.children).toHaveLength(0);
454
- });
455
-
456
- test("should create multiple utility instances when called multiple times", () => {
457
- const createMarginUtility = utility("margin", ({ value }) => ({
458
- margin: value,
459
- }));
460
-
461
- createMarginUtility({
462
- sm: "8px",
463
- md: "16px",
464
- });
465
-
466
- expect(root.children).toHaveLength(2);
467
-
468
- const smUtility = root.children.find(
469
- (u) => isUtility(u) && u.name === "margin" && u.value === "sm",
470
- );
471
- const mdUtility = root.children.find(
472
- (u) => isUtility(u) && u.name === "margin" && u.value === "md",
473
- );
474
-
475
- expect(smUtility).toEqual({
476
- type: "utility",
477
- name: "margin",
478
- value: "sm",
479
- declarations: { margin: "8px" },
480
- variables: [],
481
- children: [],
482
- modifiers: [],
483
- });
484
- expect(mdUtility).toEqual({
485
- type: "utility",
486
- name: "margin",
487
- value: "md",
488
- declarations: { margin: "16px" },
489
- variables: [],
490
- children: [],
491
- modifiers: [],
492
- });
493
- });
494
-
495
- test("should create additional utilities when utility is called multiple times", () => {
496
- const createSpacingUtility = utility("spacing", ({ value }) => ({
497
- margin: value,
498
- }));
499
-
500
- // First call
501
- createSpacingUtility({
502
- sm: "8px",
503
- md: "16px",
504
- });
505
-
506
- // Second call - should create additional utilities
507
- createSpacingUtility({
508
- lg: "24px",
509
- xl: "32px",
510
- });
511
-
512
- expect(root.children).toHaveLength(4);
513
-
514
- const smUtility = root.children.find(
515
- (u) => isUtility(u) && u.name === "spacing" && u.value === "sm",
516
- );
517
- const mdUtility = root.children.find(
518
- (u) => isUtility(u) && u.name === "spacing" && u.value === "md",
519
- );
520
- const lgUtility = root.children.find(
521
- (u) => isUtility(u) && u.name === "spacing" && u.value === "lg",
522
- );
523
- const xlUtility = root.children.find(
524
- (u) => isUtility(u) && u.name === "spacing" && u.value === "xl",
525
- );
526
-
527
- expect(smUtility).toBeDefined();
528
- expect(mdUtility).toBeDefined();
529
- expect(lgUtility).toBeDefined();
530
- expect(xlUtility).toBeDefined();
531
- });
532
-
533
- test("should create additional utilities with same key", () => {
534
- const createColorUtility = utility("color", ({ value }) => ({
535
- color: value,
536
- }));
537
-
538
- // First call
539
- createColorUtility({
540
- primary: "#007bff",
541
- });
542
-
543
- // Second call with same key - should create another utility instance
544
- createColorUtility({
545
- primary: "#0056b3",
546
- });
547
-
548
- expect(root.children).toHaveLength(2);
549
-
550
- const utilities = root.children.filter(
551
- (u): u is Utility =>
552
- isUtility(u) && u.name === "color" && u.value === "primary",
553
- );
554
- expect(utilities).toHaveLength(2);
555
- expect(utilities[0]?.declarations?.color).toBe("#007bff");
556
- expect(utilities[1]?.declarations?.color).toBe("#0056b3");
557
- });
558
-
559
- test("should handle different modifiers on subsequent calls", () => {
560
- const hoverModifier: ModifierFactory = {
561
- type: "modifier",
562
- key: ["hover"],
563
- factory: ({ declarations }) => ({
564
- "&:hover": declarations,
565
- }),
566
- };
567
-
568
- const focusModifier: ModifierFactory = {
569
- type: "modifier",
570
- key: ["focus"],
571
- factory: ({ declarations }) => ({
572
- "&:focus": declarations,
573
- }),
574
- };
575
-
576
- const createButtonUtility = utility("button", ({ value }) => ({
577
- backgroundColor: value,
578
- }));
579
-
580
- // First call with hover modifier
581
- createButtonUtility(
582
- {
583
- primary: "#007bff",
584
- },
585
- [hoverModifier],
586
- );
587
-
588
- // Second call with focus modifier
589
- createButtonUtility(
590
- {
591
- secondary: "#6c757d",
592
- },
593
- [focusModifier],
594
- );
595
-
596
- // First call creates 1 base + 1 hover variant = 2
597
- // Second call creates 1 base + 1 focus variant = 2
598
- // Total: 4
599
- expect(root.children).toHaveLength(4);
600
-
601
- const primaryUtility = root.children.find(
602
- (u): u is Utility =>
603
- isUtility(u) && u.name === "button" && u.value === "primary",
604
- );
605
- const secondaryUtility = root.children.find(
606
- (u): u is Utility =>
607
- isUtility(u) && u.name === "button" && u.value === "secondary",
608
- );
609
-
610
- expect(primaryUtility?.modifiers).toEqual([]);
611
- expect(secondaryUtility?.modifiers).toEqual([]);
612
-
613
- // Check that modified variants were created
614
- const primaryHoverVariant = root.children.find(
615
- (u) =>
616
- isUtility(u) &&
617
- u.name === "button" &&
618
- u.value === "primary" &&
619
- u.modifiers.includes("hover"),
620
- );
621
- const secondaryFocusVariant = root.children.find(
622
- (u): u is Utility =>
623
- isUtility(u) &&
624
- u.name === "button" &&
625
- u.value === "secondary" &&
626
- u.modifiers.includes("focus"),
627
- );
628
-
629
- expect(primaryHoverVariant).toBeDefined();
630
- expect(secondaryFocusVariant).toBeDefined();
631
- });
632
-
633
- test("should support callback context functions", () => {
634
- const createAdvancedUtility = utility(
635
- "advanced",
636
- ({ value, variable, selector }) => {
637
- const colorVar = variable("color-var", value);
638
- selector(".nested", { color: colorVar.value });
639
-
640
- return {
641
- backgroundColor: colorVar.value,
642
- };
643
- },
644
- );
645
-
646
- createAdvancedUtility({
647
- primary: "#007bff",
648
- });
649
-
650
- expect(root.children).toHaveLength(1);
651
-
652
- const advancedUtility = root.children.find(
653
- (u): u is Utility => isUtility(u) && u.name === "advanced",
654
- );
655
- expect(advancedUtility?.declarations).toEqual({
656
- backgroundColor: "#007bff",
657
- });
658
- expect(advancedUtility?.variables).toHaveLength(1);
659
- expect(advancedUtility?.children).toHaveLength(1);
660
- });
661
- });
662
-
663
- describe("createModifiedUtilityFunction", () => {
664
- let root: Root;
665
-
666
- beforeEach(() => {
667
- root = createRoot();
668
- });
669
-
670
- test("should create a modified utility function", () => {
671
- expect(applyModifiers).toBeTypeOf("function");
672
- });
673
-
674
- test("should return a new utility instance with modified modifiers", () => {
675
- const baseUtility: Utility = {
676
- type: "utility",
677
- name: "padding",
678
- value: "sm",
679
- declarations: { padding: "8px" },
680
- variables: [],
681
- children: [],
682
- modifiers: [],
683
- };
684
-
685
- const hoverModifier: ModifierFactory = {
686
- type: "modifier",
687
- key: ["hover"],
688
- factory: ({ declarations }) => ({
689
- "&:hover": declarations,
690
- }),
691
- };
692
-
693
- const result = applyModifiers(
694
- baseUtility,
695
- root,
696
- new Map(Object.entries({ hover: hoverModifier })),
697
- );
698
-
699
- expect(result).not.toBe(baseUtility); // Should be a new instance
700
- expect(result.type).toBe("utility");
701
- expect(result.name).toBe("padding");
702
- expect(result.value).toBe("sm");
703
- expect(result.modifiers).toEqual(["hover"]);
704
- });
705
-
706
- test("should preserve base utility properties while updating modifiers", () => {
707
- const baseUtility: Utility = {
708
- type: "utility",
709
- name: "margin",
710
- value: "lg",
711
- declarations: { margin: "24px" },
712
- variables: [],
713
- children: [],
714
- modifiers: [],
715
- };
716
-
717
- const focusModifier: ModifierFactory = {
718
- type: "modifier",
719
- key: ["focus"],
720
- factory: ({ declarations }) => ({
721
- "&:focus": declarations,
722
- }),
723
- };
724
-
725
- const result = applyModifiers(
726
- baseUtility,
727
- root,
728
- new Map(Object.entries({ focus: focusModifier })),
729
- );
730
-
731
- expect(result.type).toBe("utility");
732
- expect(result.name).toBe("margin");
733
- expect(result.value).toBe("lg");
734
- expect(result.declarations).toEqual({ margin: "24px" });
735
- expect(result.modifiers).toEqual(["focus"]);
736
- });
737
-
738
- test("should apply multiple modifiers to utility", () => {
739
- const baseUtility: Utility = {
740
- type: "utility",
741
- name: "color",
742
- value: "primary",
743
- declarations: { color: "#007bff" },
744
- variables: [],
745
- children: [],
746
- modifiers: [],
747
- };
748
-
749
- const hoverModifier: ModifierFactory = {
750
- type: "modifier",
751
- key: ["hover"],
752
- factory: ({ declarations }) => ({
753
- "&:hover": declarations,
754
- }),
755
- };
756
-
757
- const focusModifier: ModifierFactory = {
758
- type: "modifier",
759
- key: ["focus"],
760
- factory: ({ declarations }) => ({
761
- "&:focus": declarations,
762
- }),
763
- };
764
-
765
- const result = applyModifiers(
766
- baseUtility,
767
- root,
768
- new Map([
769
- ["hover", hoverModifier],
770
- ["focus", focusModifier],
771
- ]),
772
- );
773
-
774
- expect(result.modifiers).toEqual(["hover", "focus"]);
775
- });
776
-
777
- test("should handle modifiers that add variables", () => {
778
- const baseUtility: Utility = {
779
- type: "utility",
780
- name: "background",
781
- value: "primary",
782
- declarations: { background: "#007bff" },
783
- variables: [],
784
- children: [],
785
- modifiers: [],
786
- };
787
-
788
- const variableModifier: ModifierFactory = {
789
- type: "modifier",
790
- key: ["var"],
791
- factory: ({ variable }) => {
792
- const colorVar = variable("bg-color", "#007bff");
793
- return {
794
- background: colorVar.value,
795
- };
796
- },
797
- };
798
-
799
- const result = applyModifiers(
800
- baseUtility,
801
- root,
802
- new Map(
803
- Object.entries({
804
- var: variableModifier,
805
- }),
806
- ),
807
- );
808
-
809
- expect(result.variables).toHaveLength(1);
810
- expect(result.variables[0]).toEqual({
811
- type: "variable",
812
- name: "bg-color",
813
- value: "#007bff",
814
- });
815
- expect(result.modifiers).toEqual(["var"]);
816
- });
817
-
818
- test("should handle modifiers that add child selectors", () => {
819
- const baseUtility: Utility = {
820
- type: "utility",
821
- name: "text",
822
- value: "underline",
823
- declarations: { textDecoration: "underline" },
824
- variables: [],
825
- children: [],
826
- modifiers: [],
827
- };
828
-
829
- const childModifier: ModifierFactory = {
830
- type: "modifier",
831
- key: ["child"],
832
- factory: ({ selector }) => {
833
- selector("& > span", { textDecoration: "inherit" });
834
- return {};
835
- },
836
- };
837
-
838
- const result = applyModifiers(
839
- baseUtility,
840
- root,
841
- new Map(Object.entries({ child: childModifier })),
842
- );
843
-
844
- expect(result.children).toHaveLength(1);
845
- expect(result.children[0]).toMatchObject({
846
- type: "selector",
847
- query: "& > span",
848
- declarations: { textDecoration: "inherit" },
849
- });
850
- expect(result.modifiers).toEqual(["child"]);
851
- });
852
-
853
- test("should handle empty modifiers array", () => {
854
- const baseUtility: Utility = {
855
- type: "utility",
856
- name: "display",
857
- value: "block",
858
- declarations: { display: "block" },
859
- variables: [],
860
- children: [],
861
- modifiers: [],
862
- };
863
-
864
- const result = applyModifiers(baseUtility, root, new Map());
865
-
866
- expect(result).not.toBe(baseUtility);
867
- expect(result.modifiers).toEqual([]);
868
- expect(result.declarations).toEqual({ display: "block" });
869
- });
870
-
871
- test("should handle modifier combination with different keys", () => {
872
- const baseUtility: Utility = {
873
- type: "utility",
874
- name: "opacity",
875
- value: "50",
876
- declarations: { opacity: "0.5" },
877
- variables: [],
878
- children: [],
879
- modifiers: [],
880
- };
881
-
882
- const hoverModifier: ModifierFactory = {
883
- type: "modifier",
884
- key: ["hover"],
885
- factory: ({ declarations }) => ({
886
- "&:hover": declarations,
887
- }),
888
- };
889
-
890
- const darkModeModifier: ModifierFactory = {
891
- type: "modifier",
892
- key: ["dark"],
893
- factory: ({ declarations }) => ({
894
- ".dark &": declarations,
895
- }),
896
- };
897
-
898
- const result = applyModifiers(
899
- baseUtility,
900
- root,
901
- new Map([
902
- ["dark", darkModeModifier],
903
- ["hover", hoverModifier],
904
- ]),
905
- );
906
-
907
- expect(result.modifiers).toEqual(["dark", "hover"]);
908
- });
909
-
910
- test("should apply modifiers in order", () => {
911
- const baseUtility: Utility = {
912
- type: "utility",
913
- name: "transform",
914
- value: "scale",
915
- declarations: { transform: "scale(1)" },
916
- variables: [],
917
- children: [],
918
- modifiers: [],
919
- };
920
-
921
- const executionOrder: string[] = [];
922
-
923
- const firstModifier: ModifierFactory = {
924
- type: "modifier",
925
- key: ["first"],
926
- factory: ({ variable }) => {
927
- executionOrder.push("first");
928
- variable("first-var", "1");
929
- return {};
930
- },
931
- };
932
-
933
- const secondModifier: ModifierFactory = {
934
- type: "modifier",
935
- key: ["second"],
936
- factory: ({ variable }) => {
937
- executionOrder.push("second");
938
- variable("second-var", "2");
939
- return {};
940
- },
941
- };
942
-
943
- const result = applyModifiers(
944
- baseUtility,
945
- root,
946
- new Map([
947
- ["first", firstModifier],
948
- ["second", secondModifier],
949
- ]),
950
- );
951
-
952
- expect(executionOrder).toEqual(["first", "second"]);
953
- expect(result.variables).toHaveLength(2);
954
- expect(result.variables[0]?.name).toBe("first-var");
955
- expect(result.variables[1]?.name).toBe("second-var");
956
- });
957
-
958
- test("should handle modifiers that modify existing declarations", () => {
959
- const baseUtility: Utility = {
960
- type: "utility",
961
- name: "border",
962
- value: "solid",
963
- declarations: { borderStyle: "solid" },
964
- variables: [],
965
- children: [],
966
- modifiers: [],
967
- };
968
-
969
- const widthModifier: ModifierFactory = {
970
- type: "modifier",
971
- key: ["thick"],
972
- factory: ({ declarations }) => ({
973
- ...declarations,
974
- borderWidth: "4px",
975
- }),
976
- };
977
-
978
- const result = applyModifiers(
979
- baseUtility,
980
- root,
981
- new Map([["thick", widthModifier]]),
982
- );
983
-
984
- // The original declarations should be preserved
985
- expect(result.declarations).toEqual({ borderStyle: "solid" });
986
- expect(result.modifiers).toEqual(["thick"]);
987
- });
988
-
989
- test("should handle modifiers with multi-key definitions", () => {
990
- const baseUtility: Utility = {
991
- type: "utility",
992
- name: "fontSize",
993
- value: "base",
994
- declarations: { fontSize: "16px" },
995
- variables: [],
996
- children: [],
997
- modifiers: [],
998
- };
999
-
1000
- const responsiveModifier: ModifierFactory = {
1001
- type: "modifier",
1002
- key: ["sm", "md", "lg"],
1003
- factory: ({ declarations }) => ({
1004
- "@media (min-width: 640px)": declarations,
1005
- }),
1006
- };
1007
-
1008
- const result = applyModifiers(
1009
- baseUtility,
1010
- root,
1011
- new Map([["md", responsiveModifier]]),
1012
- );
1013
-
1014
- expect(result.modifiers).toEqual(["md"]);
1015
- });
1016
-
1017
- test("should create independent instances for each modification", () => {
1018
- const baseUtility: Utility = {
1019
- type: "utility",
1020
- name: "gap",
1021
- value: "sm",
1022
- declarations: { gap: "8px" },
1023
- variables: [],
1024
- children: [],
1025
- modifiers: [],
1026
- };
1027
-
1028
- const hoverModifier: ModifierFactory = {
1029
- type: "modifier",
1030
- key: ["hover"],
1031
- factory: ({ variable }) => {
1032
- variable("hover-gap", "16px");
1033
- return {};
1034
- },
1035
- };
1036
-
1037
- const result1 = applyModifiers(
1038
- baseUtility,
1039
- root,
1040
- new Map(Object.entries({ hover: hoverModifier })),
1041
- );
1042
- const result2 = applyModifiers(
1043
- baseUtility,
1044
- root,
1045
- new Map(Object.entries({ hover: hoverModifier })),
1046
- );
1047
-
1048
- // The instances themselves should be different
1049
- expect(result1).not.toBe(result2);
1050
- // Note: variables and children arrays are shared from baseUtility due to spread operator
1051
- // This is the actual behavior of the function - it mutates the arrays
1052
- expect(result1.variables).toBe(result2.variables);
1053
- expect(result1.children).toBe(result2.children);
1054
- });
1055
-
1056
- test("should handle complex modifier factory with multiple operations", () => {
1057
- const baseUtility: Utility = {
1058
- type: "utility",
1059
- name: "button",
1060
- value: "primary",
1061
- declarations: { backgroundColor: "#007bff", color: "white" },
1062
- variables: [],
1063
- children: [],
1064
- modifiers: [],
1065
- };
1066
-
1067
- const complexModifier: ModifierFactory = {
1068
- type: "modifier",
1069
- key: ["interactive"],
1070
- factory: ({ variable, selector }) => {
1071
- const bgVar = variable("button-bg", "#007bff");
1072
- const textVar = variable("button-text", "white");
1073
-
1074
- selector("&:hover", {
1075
- backgroundColor: "darken(" + bgVar.value + ", 10%)",
1076
- });
1077
-
1078
- selector("&:focus", {
1079
- outline: "2px solid " + bgVar.value,
1080
- });
1081
-
1082
- selector("&:disabled", {
1083
- opacity: "0.5",
1084
- cursor: "not-allowed",
1085
- });
1086
-
1087
- return {
1088
- backgroundColor: bgVar.value,
1089
- color: textVar.value,
1090
- };
1091
- },
1092
- };
1093
-
1094
- const result = applyModifiers(
1095
- baseUtility,
1096
- root,
1097
- new Map([["interactive", complexModifier]]),
1098
- );
1099
-
1100
- expect(result.variables).toHaveLength(2);
1101
- expect(result.variables[0]?.name).toBe("button-bg");
1102
- expect(result.variables[1]?.name).toBe("button-text");
1103
-
1104
- expect(result.children).toHaveLength(3);
1105
- expect(result.children[0]).toMatchObject({
1106
- type: "selector",
1107
- query: "&:hover",
1108
- });
1109
- expect(result.children[1]).toMatchObject({
1110
- type: "selector",
1111
- query: "&:focus",
1112
- });
1113
- expect(result.children[2]).toMatchObject({
1114
- type: "selector",
1115
- query: "&:disabled",
1116
- });
1117
-
1118
- expect(result.modifiers).toEqual(["interactive"]);
1119
- });
1120
-
1121
- test("should mutate base utility's arrays when modifiers add items", () => {
1122
- const baseUtility: Utility = {
1123
- type: "utility",
1124
- name: "position",
1125
- value: "absolute",
1126
- declarations: { position: "absolute" },
1127
- variables: [],
1128
- children: [],
1129
- modifiers: [],
1130
- };
1131
-
1132
- const originalDeclarations = { ...baseUtility.declarations };
1133
- const originalModifiers = [...baseUtility.modifiers];
1134
-
1135
- const modifier: ModifierFactory = {
1136
- type: "modifier",
1137
- key: ["test"],
1138
- factory: ({ variable }) => {
1139
- variable("test-var", "value");
1140
- return { top: "0" };
1141
- },
1142
- };
1143
-
1144
- const result = applyModifiers(
1145
- baseUtility,
1146
- root,
1147
- new Map([["test", modifier]]),
1148
- );
1149
-
1150
- // The declarations object should remain unchanged
1151
- expect(baseUtility.declarations).toEqual(originalDeclarations);
1152
- // The modifiers array should remain unchanged
1153
- expect(baseUtility.modifiers).toEqual(originalModifiers);
1154
- // But variables array will be mutated due to spread operator shallow copy
1155
- expect(baseUtility.variables).toHaveLength(1);
1156
- expect(baseUtility.variables[0]).toEqual({
1157
- type: "variable",
1158
- name: "test-var",
1159
- value: "value",
1160
- });
1161
- // The result should have the combination in its modifiers
1162
- expect(result.modifiers).toEqual(["test"]);
1163
- });
1164
-
1165
- test("should handle modifier that returns null declarations", () => {
1166
- const baseUtility: Utility = {
1167
- type: "utility",
1168
- name: "display",
1169
- value: "flex",
1170
- declarations: { display: "flex" },
1171
- variables: [],
1172
- children: [],
1173
- modifiers: [],
1174
- };
1175
-
1176
- const nullModifier: ModifierFactory = {
1177
- type: "modifier",
1178
- key: ["null"],
1179
- factory: () => ({}),
1180
- };
1181
-
1182
- const result = applyModifiers(
1183
- baseUtility,
1184
- root,
1185
- new Map([["null", nullModifier]]),
1186
- );
1187
-
1188
- expect(result.declarations).toEqual({ display: "flex" });
1189
- expect(result.modifiers).toEqual(["null"]);
1190
- });
1191
-
1192
- test("should handle modifier that returns undefined declarations", () => {
1193
- const baseUtility: Utility = {
1194
- type: "utility",
1195
- name: "width",
1196
- value: "auto",
1197
- declarations: { width: "auto" },
1198
- variables: [],
1199
- children: [],
1200
- modifiers: [],
1201
- };
1202
-
1203
- const undefinedModifier: ModifierFactory = {
1204
- type: "modifier",
1205
- key: ["undefined"],
1206
- factory: () => undefined,
1207
- };
1208
-
1209
- const result = applyModifiers(
1210
- baseUtility,
1211
- root,
1212
- new Map([["undefined", undefinedModifier]]),
1213
- );
1214
-
1215
- expect(result.declarations).toEqual({ width: "auto" });
1216
- expect(result.modifiers).toEqual(["undefined"]);
1217
- });
1218
-
1219
- test("should handle modifier that returns empty object", () => {
1220
- const baseUtility: Utility = {
1221
- type: "utility",
1222
- name: "height",
1223
- value: "100vh",
1224
- declarations: { height: "100vh" },
1225
- variables: [],
1226
- children: [],
1227
- modifiers: [],
1228
- };
1229
-
1230
- const emptyModifier: ModifierFactory = {
1231
- type: "modifier",
1232
- key: ["empty"],
1233
- factory: () => ({}),
1234
- };
1235
-
1236
- const result = applyModifiers(
1237
- baseUtility,
1238
- root,
1239
- new Map([["empty", emptyModifier]]),
1240
- );
1241
-
1242
- expect(result.declarations).toEqual({ height: "100vh" });
1243
- expect(result.modifiers).toEqual(["empty"]);
1244
- });
1245
-
1246
- test("should handle combination array that is empty", () => {
1247
- const baseUtility: Utility = {
1248
- type: "utility",
1249
- name: "overflow",
1250
- value: "hidden",
1251
- declarations: { overflow: "hidden" },
1252
- variables: [],
1253
- children: [],
1254
- modifiers: [],
1255
- };
1256
-
1257
- const result = applyModifiers(baseUtility, root, new Map());
1258
-
1259
- expect(result.modifiers).toEqual([]);
1260
- expect(result.declarations).toEqual({ overflow: "hidden" });
1261
- });
1262
-
1263
- test("should handle nested selector creation within modifiers", () => {
1264
- const baseUtility: Utility = {
1265
- type: "utility",
1266
- name: "card",
1267
- value: "default",
1268
- declarations: { padding: "1rem" },
1269
- variables: [],
1270
- children: [],
1271
- modifiers: [],
1272
- };
1273
-
1274
- const nestedModifier: ModifierFactory = {
1275
- type: "modifier",
1276
- key: ["nested"],
1277
- factory: ({ selector }) => {
1278
- selector(".card-header", { fontWeight: "bold" });
1279
- selector(".card-body", { padding: "0.5rem" });
1280
- selector(".card-footer", { borderTop: "1px solid #ccc" });
1281
- return {};
1282
- },
1283
- };
1284
-
1285
- const result = applyModifiers(
1286
- baseUtility,
1287
- root,
1288
- new Map([["nested", nestedModifier]]),
1289
- );
1290
-
1291
- expect(result.children).toHaveLength(3);
1292
- expect(result.children[0]).toMatchObject({
1293
- type: "selector",
1294
- query: ".card-header",
1295
- declarations: { fontWeight: "bold" },
1296
- });
1297
- expect(result.children[1]).toMatchObject({
1298
- type: "selector",
1299
- query: ".card-body",
1300
- declarations: { padding: "0.5rem" },
1301
- });
1302
- expect(result.children[2]).toMatchObject({
1303
- type: "selector",
1304
- query: ".card-footer",
1305
- declarations: { borderTop: "1px solid #ccc" },
1306
- });
1307
- });
1308
-
1309
- test("should handle modifier with same key appearing in combination multiple times", () => {
1310
- const baseUtility: Utility = {
1311
- type: "utility",
1312
- name: "state",
1313
- value: "active",
1314
- declarations: { opacity: "1" },
1315
- variables: [],
1316
- children: [],
1317
- modifiers: [],
1318
- };
1319
-
1320
- const stateModifier: ModifierFactory = {
1321
- type: "modifier",
1322
- key: ["hover", "focus"],
1323
- factory: ({ variable }) => {
1324
- variable("state-opacity", "0.8");
1325
- return {};
1326
- },
1327
- };
1328
-
1329
- // Duplicate keys in combination
1330
- const result = applyModifiers(
1331
- baseUtility,
1332
- root,
1333
- new Map([["hover", stateModifier]]),
1334
- );
1335
-
1336
- expect(result.modifiers).toEqual(["hover"]);
1337
- expect(result.variables).toHaveLength(1);
1338
- });
1339
-
1340
- test("should handle utility with pre-existing variables and children", () => {
1341
- const existingVar = {
1342
- type: "variable" as const,
1343
- name: "existing-var",
1344
- value: "existing-value",
1345
- };
1346
-
1347
- const existingChild = {
1348
- type: "selector" as const,
1349
- query: ".existing",
1350
- declarations: { color: "red" },
1351
- variables: [],
1352
- children: [],
1353
- };
1354
-
1355
- const baseUtility: Utility = {
1356
- type: "utility",
1357
- name: "complex",
1358
- value: "base",
1359
- declarations: { margin: "0" },
1360
- variables: [existingVar],
1361
- children: [existingChild],
1362
- modifiers: [],
1363
- };
1364
-
1365
- const additionalModifier: ModifierFactory = {
1366
- type: "modifier",
1367
- key: ["additional"],
1368
- factory: ({ variable, selector }) => {
1369
- variable("new-var", "new-value");
1370
- selector(".new", { color: "blue" });
1371
- return {};
1372
- },
1373
- };
1374
-
1375
- const result = applyModifiers(
1376
- baseUtility,
1377
- root,
1378
- new Map([["additional", additionalModifier]]),
1379
- );
1380
-
1381
- expect(result.variables).toHaveLength(2);
1382
- expect(result.variables[0]).toEqual(existingVar);
1383
- expect(result.variables[1]).toEqual({
1384
- type: "variable",
1385
- name: "new-var",
1386
- value: "new-value",
1387
- });
1388
-
1389
- expect(result.children).toHaveLength(2);
1390
- expect(result.children[0]).toEqual(existingChild);
1391
- expect(result.children[1]).toMatchObject({
1392
- type: "selector",
1393
- query: ".new",
1394
- declarations: { color: "blue" },
1395
- });
1396
- });
1397
-
1398
- test("should handle deeply nested declarations from modifiers", () => {
1399
- const baseUtility: Utility = {
1400
- type: "utility",
1401
- name: "grid",
1402
- value: "layout",
1403
- declarations: { display: "grid" },
1404
- variables: [],
1405
- children: [],
1406
- modifiers: [],
1407
- };
1408
-
1409
- const deepModifier: ModifierFactory = {
1410
- type: "modifier",
1411
- key: ["deep"],
1412
- factory: ({ selector }) => {
1413
- selector("& > .row", { gridRow: "span 1" });
1414
- selector("& > .row > .col", { gridColumn: "span 1" });
1415
- return {
1416
- gridTemplateColumns: "repeat(12, 1fr)",
1417
- gridGap: "1rem",
1418
- };
1419
- },
1420
- };
1421
-
1422
- const result = applyModifiers(
1423
- baseUtility,
1424
- root,
1425
- new Map([["deep", deepModifier]]),
1426
- );
1427
-
1428
- expect(result.declarations).toEqual({ display: "grid" });
1429
- expect(result.children).toHaveLength(2);
1430
- expect(result.modifiers).toEqual(["deep"]);
1431
- });
1432
-
1433
- test("should handle modifier factory that throws an error", () => {
1434
- const baseUtility: Utility = {
1435
- type: "utility",
1436
- name: "error",
1437
- value: "test",
1438
- declarations: { display: "block" },
1439
- variables: [],
1440
- children: [],
1441
- modifiers: [],
1442
- };
1443
-
1444
- const errorModifier: ModifierFactory = {
1445
- type: "modifier",
1446
- key: ["error"],
1447
- factory: () => {
1448
- throw new Error("Test error");
1449
- },
1450
- };
1451
-
1452
- expect(() => {
1453
- applyModifiers(baseUtility, root, new Map([["error", errorModifier]]));
1454
- }).toThrow("Test error");
1455
- });
1456
- });