@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,978 +0,0 @@
1
- import { describe, expect, it, beforeEach } from "vitest";
2
- import { styleframe } from "../styleframe";
3
- import type { Styleframe } from "../styleframe";
4
- import {
5
- merge,
6
- mergeVariablesArray,
7
- mergeThemesArray,
8
- mergeContainers,
9
- } from "./merge";
10
- import type { Container, Root } from "../types";
11
- import { createRoot } from "../tokens/root";
12
- import { createVariableFunction } from "../tokens/variable";
13
- import { createThemeFunction } from "../tokens/theme";
14
-
15
- describe("mergeVariablesArray", () => {
16
- let root: Root;
17
- let variable: ReturnType<typeof createVariableFunction>;
18
-
19
- beforeEach(() => {
20
- root = createRoot();
21
- variable = createVariableFunction(root, root);
22
- });
23
-
24
- describe("basic functionality", () => {
25
- it("should merge two variable arrays", () => {
26
- const varA = variable("color-primary", "#3b82f6");
27
- const varB = variable("color-secondary", "#64748b");
28
-
29
- const result = mergeVariablesArray([varA], [varB]);
30
-
31
- expect(result).toHaveLength(2);
32
- expect(result[0]).toEqual(varA);
33
- expect(result[1]).toEqual(varB);
34
- });
35
-
36
- it("should override existing variable values", () => {
37
- const varA1 = variable("color-primary", "#3b82f6");
38
- const varA2 = variable("color-secondary", "#64748b");
39
- const varB = variable("color-primary", "#ef4444");
40
-
41
- const result = mergeVariablesArray([varA1, varA2], [varB]);
42
-
43
- expect(result).toHaveLength(2);
44
- expect(result[0]).toEqual({
45
- type: "variable",
46
- name: "color-primary",
47
- value: "#ef4444",
48
- });
49
- expect(result[1]).toEqual({
50
- type: "variable",
51
- name: "color-secondary",
52
- value: "#64748b",
53
- });
54
- });
55
-
56
- it("should not modify the original arrays", () => {
57
- const varA = variable("spacing", "1rem");
58
- const varB = variable("padding", "2rem");
59
-
60
- const a = [varA];
61
- const b = [varB];
62
- const originalALength = a.length;
63
- const originalBLength = b.length;
64
-
65
- mergeVariablesArray(a, b);
66
-
67
- expect(a).toHaveLength(originalALength);
68
- expect(b).toHaveLength(originalBLength);
69
- });
70
- });
71
-
72
- describe("edge cases", () => {
73
- it("should handle empty first array", () => {
74
- const varB = variable("color", "red");
75
-
76
- const result = mergeVariablesArray([], [varB]);
77
-
78
- expect(result).toHaveLength(1);
79
- expect(result[0]).toEqual(varB);
80
- });
81
-
82
- it("should handle empty second array", () => {
83
- const varA = variable("color", "blue");
84
-
85
- const result = mergeVariablesArray([varA], []);
86
-
87
- expect(result).toHaveLength(1);
88
- expect(result[0]).toEqual(varA);
89
- });
90
-
91
- it("should handle both arrays empty", () => {
92
- const result = mergeVariablesArray([], []);
93
-
94
- expect(result).toHaveLength(0);
95
- });
96
-
97
- it("should handle multiple overrides", () => {
98
- const varA1 = variable("var1", "a");
99
- const varA2 = variable("var2", "b");
100
- const varA3 = variable("var3", "c");
101
- const varB1 = variable("var1", "x");
102
- const varB2 = variable("var3", "z");
103
-
104
- const result = mergeVariablesArray([varA1, varA2, varA3], [varB1, varB2]);
105
-
106
- expect(result).toHaveLength(3);
107
- expect(result[0]).toMatchObject({ value: "x" });
108
- expect(result[1]).toMatchObject({ value: "b" });
109
- expect(result[2]).toMatchObject({ value: "z" });
110
- });
111
- });
112
-
113
- describe("variable matching", () => {
114
- it("should match variables by name", () => {
115
- const varA = variable("test-var", "old");
116
- const varB = variable("test-var", "new");
117
-
118
- const result = mergeVariablesArray([varA], [varB]);
119
-
120
- expect(result).toHaveLength(1);
121
- expect(result[0]).toMatchObject({ value: "new" });
122
- });
123
-
124
- it("should be case-sensitive when matching", () => {
125
- const varA = variable("Color", "red");
126
- const varB = variable("color", "blue");
127
-
128
- const result = mergeVariablesArray([varA], [varB]);
129
-
130
- expect(result).toHaveLength(2);
131
- });
132
-
133
- it("should handle special characters in variable names", () => {
134
- const varA = variable("color--primary-500", "#000");
135
- const varB = variable("color--primary-500", "#fff");
136
-
137
- const result = mergeVariablesArray([varA], [varB]);
138
-
139
- expect(result).toHaveLength(1);
140
- expect(result[0]).toMatchObject({ value: "#fff" });
141
- });
142
- });
143
- });
144
-
145
- describe("mergeThemesArray", () => {
146
- let root: Root;
147
- let theme: ReturnType<typeof createThemeFunction>;
148
-
149
- beforeEach(() => {
150
- root = createRoot();
151
- theme = createThemeFunction(root, root);
152
- });
153
-
154
- describe("basic functionality", () => {
155
- it("should merge two theme arrays", () => {
156
- const themeA = theme("light", () => {});
157
- const themeB = theme("dark", () => {});
158
-
159
- const result = mergeThemesArray([themeA], [themeB]);
160
-
161
- expect(result).toHaveLength(2);
162
- expect(result[0]).toMatchObject({ name: "light" });
163
- expect(result[1]).toMatchObject({ name: "dark" });
164
- });
165
-
166
- it("should merge themes with the same name", () => {
167
- const themeA = theme("dark", (ctx) => {
168
- ctx.variable("bg", "#000");
169
- });
170
- // Manually add declarations to test merge
171
- themeA.declarations.color = "white";
172
-
173
- const themeB = theme("dark", (ctx) => {
174
- ctx.variable("text", "#fff");
175
- });
176
- themeB.declarations.backgroundColor = "black";
177
-
178
- const result = mergeThemesArray([themeA], [themeB]);
179
-
180
- expect(result).toHaveLength(1);
181
- expect(result[0]).toMatchObject({
182
- name: "dark",
183
- declarations: {
184
- color: "white",
185
- backgroundColor: "black",
186
- },
187
- });
188
- expect(result[0]).toHaveProperty("variables");
189
- expect(result[0]?.variables).toHaveLength(2);
190
- });
191
-
192
- it("should not modify the original arrays", () => {
193
- const themeA = theme("light", () => {});
194
- const themeB = theme("dark", () => {});
195
-
196
- const a = [themeA];
197
- const b = [themeB];
198
- const originalALength = a.length;
199
- const originalBLength = b.length;
200
-
201
- mergeThemesArray(a, b);
202
-
203
- expect(a).toHaveLength(originalALength);
204
- expect(b).toHaveLength(originalBLength);
205
- });
206
- });
207
-
208
- describe("edge cases", () => {
209
- it("should handle empty first array", () => {
210
- const themeB = theme("test", () => {});
211
-
212
- const result = mergeThemesArray([], [themeB]);
213
-
214
- expect(result).toHaveLength(1);
215
- expect(result[0]).toMatchObject({ name: "test" });
216
- });
217
-
218
- it("should handle empty second array", () => {
219
- const themeA = theme("test", () => {});
220
-
221
- const result = mergeThemesArray([themeA], []);
222
-
223
- expect(result).toHaveLength(1);
224
- expect(result[0]).toMatchObject({ name: "test" });
225
- });
226
-
227
- it("should handle both arrays empty", () => {
228
- const result = mergeThemesArray([], []);
229
-
230
- expect(result).toHaveLength(0);
231
- });
232
- });
233
-
234
- describe("theme matching", () => {
235
- it("should match themes by name", () => {
236
- const themeA = theme("primary", () => {});
237
- themeA.declarations.old = "value";
238
-
239
- const themeB = theme("primary", () => {});
240
- themeB.declarations.new = "value";
241
-
242
- const result = mergeThemesArray([themeA], [themeB]);
243
-
244
- expect(result).toHaveLength(1);
245
- expect(result[0]).toMatchObject({
246
- declarations: {
247
- old: "value",
248
- new: "value",
249
- },
250
- });
251
- });
252
-
253
- it("should be case-sensitive when matching", () => {
254
- const themeA = theme("Dark", () => {});
255
- const themeB = theme("dark", () => {});
256
-
257
- const result = mergeThemesArray([themeA], [themeB]);
258
-
259
- expect(result).toHaveLength(2);
260
- });
261
- });
262
- });
263
-
264
- describe("mergeContainers", () => {
265
- describe("basic functionality", () => {
266
- it("should merge two containers", () => {
267
- const a: Container = {
268
- variables: [{ type: "variable", name: "var1", value: "a" }],
269
- declarations: { color: "red" },
270
- children: [],
271
- };
272
- const b: Container = {
273
- variables: [{ type: "variable", name: "var2", value: "b" }],
274
- declarations: { backgroundColor: "blue" },
275
- children: [],
276
- };
277
-
278
- const result = mergeContainers(a, b);
279
-
280
- expect(result.variables).toHaveLength(2);
281
- expect(result.declarations).toEqual({
282
- color: "red",
283
- backgroundColor: "blue",
284
- });
285
- });
286
-
287
- it("should merge declarations by spreading", () => {
288
- const a: Container = {
289
- variables: [],
290
- declarations: { color: "red", padding: "10px" },
291
- children: [],
292
- };
293
- const b: Container = {
294
- variables: [],
295
- declarations: { color: "blue", margin: "20px" },
296
- children: [],
297
- };
298
-
299
- const result = mergeContainers(a, b);
300
-
301
- expect(result.declarations).toEqual({
302
- color: "blue",
303
- padding: "10px",
304
- margin: "20px",
305
- });
306
- });
307
-
308
- it("should concatenate children arrays", () => {
309
- const selector1 = {
310
- type: "selector" as const,
311
- query: ".button",
312
- declarations: {},
313
- variables: [],
314
- children: [],
315
- };
316
- const selector2 = {
317
- type: "selector" as const,
318
- query: ".card",
319
- declarations: {},
320
- variables: [],
321
- children: [],
322
- };
323
-
324
- const a: Container = {
325
- variables: [],
326
- declarations: {},
327
- children: [selector1],
328
- };
329
- const b: Container = {
330
- variables: [],
331
- declarations: {},
332
- children: [selector2],
333
- };
334
-
335
- const result = mergeContainers(a, b);
336
-
337
- expect(result.children).toHaveLength(2);
338
- expect(result.children[0]).toBe(selector1);
339
- expect(result.children[1]).toBe(selector2);
340
- });
341
- });
342
-
343
- describe("with Root containers", () => {
344
- it("should merge themes on Root containers", () => {
345
- const a: Root = {
346
- type: "root",
347
- declarations: {},
348
- utilities: [],
349
- modifiers: [],
350
- recipes: [],
351
- variables: [],
352
- children: [],
353
- themes: [
354
- {
355
- type: "theme",
356
- name: "light",
357
- declarations: {},
358
- variables: [],
359
- children: [],
360
- },
361
- ],
362
- };
363
- const b: Root = {
364
- type: "root",
365
- declarations: {},
366
- utilities: [],
367
- modifiers: [],
368
- recipes: [],
369
- variables: [],
370
- children: [],
371
- themes: [
372
- {
373
- type: "theme",
374
- name: "dark",
375
- declarations: {},
376
- variables: [],
377
- children: [],
378
- },
379
- ],
380
- };
381
-
382
- const result = mergeContainers(a, b);
383
-
384
- expect(result.themes).toHaveLength(2);
385
- expect(result.themes[0]).toMatchObject({ name: "light" });
386
- expect(result.themes[1]).toMatchObject({ name: "dark" });
387
- });
388
-
389
- it("should concatenate utilities arrays", () => {
390
- const a: Root = {
391
- type: "root",
392
- declarations: {},
393
- utilities: [
394
- {
395
- type: "utility",
396
- name: "padding",
397
- factory: () => {},
398
- },
399
- ],
400
- modifiers: [],
401
- recipes: [],
402
- variables: [],
403
- children: [],
404
- themes: [],
405
- };
406
- const b: Root = {
407
- type: "root",
408
- declarations: {},
409
- utilities: [
410
- {
411
- type: "utility",
412
- name: "margin",
413
- factory: () => {},
414
- },
415
- ],
416
- modifiers: [],
417
- recipes: [],
418
- variables: [],
419
- children: [],
420
- themes: [],
421
- };
422
-
423
- const result = mergeContainers(a, b);
424
-
425
- expect(result.utilities).toHaveLength(2);
426
- expect(result.utilities[0]).toMatchObject({ name: "padding" });
427
- expect(result.utilities[1]).toMatchObject({ name: "margin" });
428
- });
429
-
430
- it("should concatenate modifiers arrays", () => {
431
- const a: Root = {
432
- type: "root",
433
- declarations: {},
434
- utilities: [],
435
- modifiers: [
436
- {
437
- type: "modifier",
438
- key: ["hover"],
439
- factory: () => {},
440
- },
441
- ],
442
- recipes: [],
443
- variables: [],
444
- children: [],
445
- themes: [],
446
- };
447
- const b: Root = {
448
- type: "root",
449
- declarations: {},
450
- utilities: [],
451
- modifiers: [
452
- {
453
- type: "modifier",
454
- key: ["focus"],
455
- factory: () => {},
456
- },
457
- ],
458
- recipes: [],
459
- variables: [],
460
- children: [],
461
- themes: [],
462
- };
463
-
464
- const result = mergeContainers(a, b);
465
-
466
- expect(result.modifiers).toHaveLength(2);
467
- expect(result.modifiers[0]).toMatchObject({ key: ["hover"] });
468
- expect(result.modifiers[1]).toMatchObject({ key: ["focus"] });
469
- });
470
-
471
- it("should concatenate recipes arrays", () => {
472
- const a: Root = {
473
- type: "root",
474
- declarations: {},
475
- utilities: [],
476
- modifiers: [],
477
- recipes: [
478
- {
479
- type: "recipe",
480
- name: "button",
481
- defaults: {},
482
- variants: {},
483
- },
484
- ],
485
- variables: [],
486
- children: [],
487
- themes: [],
488
- };
489
- const b: Root = {
490
- type: "root",
491
- declarations: {},
492
- utilities: [],
493
- modifiers: [],
494
- recipes: [
495
- {
496
- type: "recipe",
497
- name: "card",
498
- defaults: {},
499
- variants: {},
500
- },
501
- ],
502
- variables: [],
503
- children: [],
504
- themes: [],
505
- };
506
-
507
- const result = mergeContainers(a, b);
508
-
509
- expect(result.recipes).toHaveLength(2);
510
- expect(result.recipes[0]).toMatchObject({ name: "button" });
511
- expect(result.recipes[1]).toMatchObject({ name: "card" });
512
- });
513
- });
514
-
515
- describe("edge cases", () => {
516
- it("should handle empty containers", () => {
517
- const a: Container = {
518
- variables: [],
519
- declarations: {},
520
- children: [],
521
- };
522
- const b: Container = {
523
- variables: [],
524
- declarations: {},
525
- children: [],
526
- };
527
-
528
- const result = mergeContainers(a, b);
529
-
530
- expect(result.variables).toHaveLength(0);
531
- expect(result.declarations).toEqual({});
532
- expect(result.children).toHaveLength(0);
533
- });
534
-
535
- it("should preserve non-container properties", () => {
536
- const a: Root = {
537
- type: "root",
538
- declarations: {},
539
- utilities: [],
540
- modifiers: [],
541
- recipes: [],
542
- variables: [],
543
- children: [],
544
- themes: [],
545
- };
546
- const b: Root = {
547
- type: "root",
548
- declarations: {},
549
- utilities: [],
550
- modifiers: [],
551
- recipes: [],
552
- variables: [],
553
- children: [],
554
- themes: [],
555
- };
556
-
557
- const result = mergeContainers(a, b);
558
-
559
- expect(result.type).toBe("root");
560
- });
561
- });
562
- });
563
-
564
- describe("merge", () => {
565
- let base: Styleframe;
566
- let extension: Styleframe;
567
-
568
- beforeEach(() => {
569
- base = styleframe();
570
- extension = styleframe();
571
- });
572
-
573
- describe("basic usage", () => {
574
- it("should merge two Styleframe instances", () => {
575
- base.variable("color-primary", "#3b82f6");
576
- extension.variable("color-secondary", "#64748b");
577
-
578
- const result = merge(base, extension);
579
-
580
- expect(result.root.variables).toHaveLength(2);
581
- expect(result.root.variables[0]).toMatchObject({
582
- name: "color-primary",
583
- });
584
- expect(result.root.variables[1]).toMatchObject({
585
- name: "color-secondary",
586
- });
587
- });
588
-
589
- it("should return a new Styleframe instance", () => {
590
- const result = merge(base, extension);
591
-
592
- expect(result).not.toBe(base);
593
- expect(result).not.toBe(extension);
594
- });
595
-
596
- it("should not modify original instances", () => {
597
- base.variable("original", "value");
598
- extension.variable("new", "value");
599
-
600
- const originalBaseVarCount = base.root.variables.length;
601
-
602
- merge(base, extension);
603
-
604
- expect(base.root.variables).toHaveLength(originalBaseVarCount);
605
- });
606
- });
607
-
608
- describe("merging multiple instances", () => {
609
- it("should merge three instances", () => {
610
- const colors = styleframe();
611
- const typography = styleframe();
612
- const spacing = styleframe();
613
-
614
- colors.variable("color-primary", "#3b82f6");
615
- typography.variable("font-sans", "Inter, sans-serif");
616
- spacing.variable("spacing-md", "1rem");
617
-
618
- const result = merge(colors, typography, spacing);
619
-
620
- expect(result.root.variables).toHaveLength(3);
621
- expect(result.root.variables[0]).toMatchObject({
622
- name: "color-primary",
623
- });
624
- expect(result.root.variables[1]).toMatchObject({
625
- name: "font-sans",
626
- });
627
- expect(result.root.variables[2]).toMatchObject({
628
- name: "spacing-md",
629
- });
630
- });
631
-
632
- it("should merge many instances", () => {
633
- const instances = Array.from({ length: 5 }, () => styleframe());
634
-
635
- instances.forEach((instance, i) => {
636
- instance.variable(`var-${i}`, `value-${i}`);
637
- });
638
-
639
- const [first, ...rest] = instances;
640
- if (!first) throw new Error("First instance is undefined");
641
- const result = merge(first, ...rest);
642
-
643
- expect(result.root.variables).toHaveLength(5);
644
- });
645
- });
646
-
647
- describe("variable override behavior", () => {
648
- it("should override variables with later declarations", () => {
649
- base.variable("color-primary", "#3b82f6");
650
- extension.variable("color-primary", "#ef4444");
651
-
652
- const result = merge(base, extension);
653
-
654
- expect(result.root.variables).toHaveLength(1);
655
- expect(result.root.variables[0]).toMatchObject({
656
- value: "#ef4444",
657
- });
658
- });
659
-
660
- it("should apply overrides in order from left to right", () => {
661
- const s1 = styleframe();
662
- const s2 = styleframe();
663
- const s3 = styleframe();
664
-
665
- s1.variable("color", "red");
666
- s2.variable("color", "blue");
667
- s3.variable("color", "green");
668
-
669
- const result = merge(s1, s2, s3);
670
-
671
- expect(result.root.variables).toHaveLength(1);
672
- expect(result.root.variables[0]).toMatchObject({ value: "green" });
673
- });
674
-
675
- it("should keep non-overridden variables", () => {
676
- base.variable("color-primary", "#3b82f6");
677
- base.variable("color-secondary", "#64748b");
678
- extension.variable("color-primary", "#ef4444");
679
-
680
- const result = merge(base, extension);
681
-
682
- expect(result.root.variables).toHaveLength(2);
683
- expect(result.root.variables[0]).toMatchObject({
684
- value: "#ef4444",
685
- });
686
- expect(result.root.variables[1]).toMatchObject({
687
- value: "#64748b",
688
- });
689
- });
690
- });
691
-
692
- describe("declarations merge behavior", () => {
693
- it("should merge declarations from both instances", () => {
694
- base.selector(".button", {
695
- padding: "0.5rem 1rem",
696
- });
697
- extension.selector(".card", {
698
- borderRadius: "0.5rem",
699
- });
700
-
701
- const result = merge(base, extension);
702
-
703
- expect(result.root.children).toHaveLength(2);
704
- });
705
-
706
- it("should override declarations with same property", () => {
707
- base.selector(".element", {
708
- color: "red",
709
- padding: "10px",
710
- });
711
- extension.selector(".element", {
712
- color: "blue",
713
- });
714
-
715
- const result = merge(base, extension);
716
-
717
- expect(result.root.children).toHaveLength(2);
718
- });
719
- });
720
-
721
- describe("utilities concatenation", () => {
722
- it("should concatenate utilities from both instances", () => {
723
- base.utility("text", (ctx) => {
724
- ctx.selector("&.text\\:sm", { fontSize: "0.875rem" });
725
- ctx.selector("&.text\\:md", { fontSize: "1rem" });
726
- });
727
- extension.utility("text", (ctx) => {
728
- ctx.selector("&.text\\:lg", { fontSize: "1.125rem" });
729
- ctx.selector("&.text\\:xl", { fontSize: "1.25rem" });
730
- });
731
-
732
- const result = merge(base, extension);
733
-
734
- expect(result.root.utilities).toHaveLength(2);
735
- expect(result.root.utilities[0]).toMatchObject({ name: "text" });
736
- expect(result.root.utilities[1]).toMatchObject({ name: "text" });
737
- });
738
-
739
- it("should preserve all utilities even with same name", () => {
740
- base.utility("spacing", (ctx) => {
741
- ctx.selector("&.spacing\\:sm", { padding: "0.5rem" });
742
- });
743
- extension.utility("spacing", (ctx) => {
744
- ctx.selector("&.spacing\\:lg", { padding: "2rem" });
745
- });
746
-
747
- const result = merge(base, extension);
748
-
749
- expect(result.root.utilities).toHaveLength(2);
750
- });
751
- });
752
-
753
- describe("modifiers concatenation", () => {
754
- it("should concatenate modifiers from both instances", () => {
755
- base.modifier(["hover", "h"], (ctx) => {
756
- ctx.selector("&:hover", {});
757
- });
758
- extension.modifier(["focus", "f"], (ctx) => {
759
- ctx.selector("&:focus", {});
760
- });
761
-
762
- const result = merge(base, extension);
763
-
764
- expect(result.root.modifiers).toHaveLength(2);
765
- });
766
- });
767
-
768
- describe("recipes concatenation", () => {
769
- it("should concatenate recipes from both instances", () => {
770
- base.recipe(
771
- "button",
772
- {},
773
- {
774
- size: {
775
- sm: { fontSize: "0.875rem" },
776
- lg: { fontSize: "1.125rem" },
777
- },
778
- },
779
- );
780
- extension.recipe(
781
- "card",
782
- {},
783
- {
784
- elevation: {
785
- low: { boxShadow: "0 1px 2px rgba(0,0,0,0.1)" },
786
- high: { boxShadow: "0 4px 6px rgba(0,0,0,0.1)" },
787
- },
788
- },
789
- );
790
-
791
- const result = merge(base, extension);
792
-
793
- expect(result.root.recipes).toHaveLength(2);
794
- expect(result.root.recipes[0]).toMatchObject({ name: "button" });
795
- expect(result.root.recipes[1]).toMatchObject({ name: "card" });
796
- });
797
- });
798
-
799
- describe("themes merge behavior", () => {
800
- it("should merge different themes", () => {
801
- base.theme("light", (ctx) => {
802
- ctx.variable("bg-primary", "#ffffff");
803
- });
804
- extension.theme("dark", (ctx) => {
805
- ctx.variable("bg-primary", "#1f2937");
806
- });
807
-
808
- const result = merge(base, extension);
809
-
810
- expect(result.root.themes).toHaveLength(2);
811
- expect(result.root.themes[0]).toMatchObject({ name: "light" });
812
- expect(result.root.themes[1]).toMatchObject({ name: "dark" });
813
- });
814
-
815
- it("should merge themes with same name", () => {
816
- base.theme("dark", (ctx) => {
817
- ctx.variable("bg", "#000");
818
- });
819
- extension.theme("dark", (ctx) => {
820
- ctx.variable("text", "#fff");
821
- });
822
-
823
- const result = merge(base, extension);
824
-
825
- expect(result.root.themes).toHaveLength(1);
826
- expect(result.root.themes[0]).toMatchObject({ name: "dark" });
827
- expect(result.root.themes[0]).toHaveProperty("variables");
828
- expect(result.root.themes[0]?.variables).toHaveLength(2);
829
- });
830
- });
831
-
832
- describe("complex merging scenarios", () => {
833
- it("should handle merging with all property types", () => {
834
- base.variable("base-color", "#000");
835
- base.selector(".base", {});
836
- base.utility("base-util", () => {});
837
- base.modifier(["base"], () => {});
838
- base.recipe("base-recipe", {}, {});
839
- base.theme("base-theme", () => {});
840
-
841
- extension.variable("ext-color", "#fff");
842
- extension.selector(".ext", {});
843
- extension.utility("ext-util", () => {});
844
- extension.modifier(["ext"], () => {});
845
- extension.recipe("ext-recipe", {}, {});
846
- extension.theme("ext-theme", () => {});
847
-
848
- const result = merge(base, extension);
849
-
850
- expect(result.root.variables).toHaveLength(2);
851
- expect(result.root.children).toHaveLength(2);
852
- expect(result.root.utilities).toHaveLength(2);
853
- expect(result.root.modifiers).toHaveLength(2);
854
- expect(result.root.recipes).toHaveLength(2);
855
- expect(result.root.themes).toHaveLength(2);
856
- });
857
-
858
- it("should preserve order of merge operations", () => {
859
- const s1 = styleframe();
860
- const s2 = styleframe();
861
- const s3 = styleframe();
862
-
863
- s1.variable("order", "1");
864
- s2.variable("order", "2");
865
- s3.variable("order", "3");
866
-
867
- const result = merge(s1, s2, s3);
868
-
869
- expect(result.root.variables[0]).toMatchObject({ value: "3" });
870
- });
871
- });
872
-
873
- describe("options preservation", () => {
874
- it("should preserve options from merged instances", () => {
875
- const baseWithOptions = styleframe();
876
- const ext = styleframe();
877
-
878
- const result = merge(baseWithOptions, ext);
879
-
880
- expect(result.options).toBeDefined();
881
- });
882
- });
883
-
884
- describe("edge cases", () => {
885
- it("should handle merging with empty instances", () => {
886
- const empty1 = styleframe();
887
- const empty2 = styleframe();
888
-
889
- const result = merge(empty1, empty2);
890
-
891
- expect(result.root.variables).toHaveLength(0);
892
- expect(result.root.children).toHaveLength(0);
893
- });
894
-
895
- it("should handle merging single instance", () => {
896
- base.variable("test", "value");
897
-
898
- const result = merge(base);
899
-
900
- expect(result.root.variables).toHaveLength(1);
901
- expect(result.root.variables[0]).toMatchObject({ value: "value" });
902
- });
903
-
904
- it("should handle nested selectors", () => {
905
- base.selector(".parent", (ctx) => {
906
- ctx.selector(".child", {});
907
- });
908
- extension.selector(".other", (ctx) => {
909
- ctx.selector(".nested", {});
910
- });
911
-
912
- const result = merge(base, extension);
913
-
914
- expect(result.root.children).toHaveLength(2);
915
- });
916
- });
917
-
918
- describe("practical examples from documentation", () => {
919
- it("should merge base and extension as shown in basic usage", () => {
920
- const baseInstance = styleframe();
921
- baseInstance.variable("color--primary", "#3b82f6");
922
- baseInstance.selector(".button", {
923
- padding: "0.5rem 1rem",
924
- borderRadius: "0.25rem",
925
- });
926
-
927
- const extensionInstance = styleframe();
928
- extensionInstance.variable("color--secondary", "#64748b");
929
- extensionInstance.selector(".card", {
930
- padding: "1rem",
931
- borderRadius: "0.5rem",
932
- });
933
-
934
- const result = merge(baseInstance, extensionInstance);
935
-
936
- expect(result.root.variables).toHaveLength(2);
937
- expect(result.root.children).toHaveLength(2);
938
- });
939
-
940
- it("should merge colors, typography, and spacing modules", () => {
941
- const colors = styleframe();
942
- colors.variable("color--primary", "#3b82f6");
943
- colors.variable("color--secondary", "#64748b");
944
-
945
- const typography = styleframe();
946
- typography.variable("font--sans", "Inter, system-ui, sans-serif");
947
- typography.variable("font--mono", "Fira Code, monospace");
948
-
949
- const spacing = styleframe();
950
- spacing.variable("spacing--sm", "0.5rem");
951
- spacing.variable("spacing--md", "1rem");
952
- spacing.variable("spacing--lg", "2rem");
953
-
954
- const result = merge(colors, typography, spacing);
955
-
956
- expect(result.root.variables).toHaveLength(7);
957
- });
958
-
959
- it("should override base variables with extension", () => {
960
- const baseInstance = styleframe();
961
- baseInstance.variable("color--primary", "#3b82f6");
962
- baseInstance.variable("color--secondary", "#64748b");
963
-
964
- const override = styleframe();
965
- override.variable("color--primary", "#ef4444");
966
-
967
- const result = merge(baseInstance, override);
968
-
969
- expect(result.root.variables).toHaveLength(2);
970
- expect(result.root.variables[0]).toMatchObject({
971
- value: "#ef4444",
972
- });
973
- expect(result.root.variables[1]).toMatchObject({
974
- value: "#64748b",
975
- });
976
- });
977
- });
978
- });