clava 0.2.4 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { describe, expect, test } from "vitest";
1
+ import { describe, expect, test, vi } from "vitest";
2
2
  import {
3
3
  CONFIGS,
4
4
  createCVFromConfig,
@@ -15,20 +15,20 @@ for (const config of Object.values(CONFIGS)) {
15
15
  const cls = getConfigTransformClass(config);
16
16
 
17
17
  describe(getConfigDescription(config), () => {
18
- test("computed", () => {
18
+ test("refine", () => {
19
19
  const component = getModeComponent(
20
20
  mode,
21
21
  cv({
22
22
  variants: { size: { sm: "sm", lg: "lg" } },
23
- computed: ({ variants }) =>
24
- variants.size === "lg" ? "computed-lg" : null,
23
+ refine: ({ variants }) =>
24
+ variants.size === "lg" ? "refine-lg" : null,
25
25
  }),
26
26
  );
27
27
  const props = component({ size: "lg" });
28
- expect(getStyleClass(props)).toEqual({ class: cls("lg computed-lg") });
28
+ expect(getStyleClass(props)).toEqual({ class: cls("lg refine-lg") });
29
29
  });
30
30
 
31
- test("computed with setVariants", () => {
31
+ test("refine with setVariants", () => {
32
32
  const component = getModeComponent(
33
33
  mode,
34
34
  cv({
@@ -36,7 +36,7 @@ for (const config of Object.values(CONFIGS)) {
36
36
  size: { sm: "sm", lg: "lg" },
37
37
  color: { red: "red", blue: "blue" },
38
38
  },
39
- computed: ({ variants, setVariants }) => {
39
+ refine: ({ variants, setVariants }) => {
40
40
  if (variants.size === "lg") {
41
41
  setVariants({ color: "red" });
42
42
  }
@@ -47,7 +47,7 @@ for (const config of Object.values(CONFIGS)) {
47
47
  expect(getStyleClass(props)).toEqual({ class: cls("lg red") });
48
48
  });
49
49
 
50
- test("computed with setDefaultVariants", () => {
50
+ test("refine re-runs when it changes variants", () => {
51
51
  const component = getModeComponent(
52
52
  mode,
53
53
  cv({
@@ -55,7 +55,85 @@ for (const config of Object.values(CONFIGS)) {
55
55
  size: { sm: "sm", lg: "lg" },
56
56
  color: { red: "red", blue: "blue" },
57
57
  },
58
- computed: ({ variants, setDefaultVariants }) => {
58
+ refine: ({ variants, setVariants, addClass }) => {
59
+ if (variants.size === "lg") {
60
+ setVariants({ color: "red" });
61
+ }
62
+ if (variants.color === "red") {
63
+ addClass("refine-red");
64
+ }
65
+ },
66
+ }),
67
+ );
68
+ const props = component({ size: "lg" });
69
+ expect(getStyleClass(props)).toEqual({
70
+ class: cls("lg red refine-red"),
71
+ });
72
+ });
73
+
74
+ test("refine re-runs when setDefaultVariants changes variants", () => {
75
+ const component = getModeComponent(
76
+ mode,
77
+ cv({
78
+ variants: {
79
+ size: { sm: "sm", lg: "lg" },
80
+ color: { red: "red", blue: "blue" },
81
+ },
82
+ refine: ({ variants, setDefaultVariants, addClass }) => {
83
+ setDefaultVariants({ color: "red" });
84
+ if (variants.color === "red") {
85
+ setDefaultVariants({ size: "lg" });
86
+ }
87
+ if (variants.size === "lg") {
88
+ addClass("refine-lg");
89
+ }
90
+ },
91
+ }),
92
+ );
93
+ const props = component();
94
+ expect(getStyleClass(props)).toEqual({
95
+ class: cls("lg red refine-lg"),
96
+ });
97
+ });
98
+
99
+ test("refine converges with NaN setDefaultVariants", () => {
100
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
101
+ const component = getModeComponent(
102
+ mode,
103
+ cv({
104
+ variants: {
105
+ value: (value: number) => (Number.isNaN(value) ? "nan" : null),
106
+ },
107
+ refine: ({ variants, setDefaultVariants, addClass }) => {
108
+ setDefaultVariants({ value: Number.NaN });
109
+ if (Number.isNaN(variants.value)) {
110
+ addClass("refine-nan");
111
+ }
112
+ },
113
+ }),
114
+ );
115
+
116
+ try {
117
+ const props = component();
118
+ expect(getStyleClass(props)).toEqual({
119
+ class: cls("nan refine-nan"),
120
+ });
121
+ expect(component.getVariants()).toEqual({ value: Number.NaN });
122
+ expect(warn).not.toHaveBeenCalled();
123
+ } finally {
124
+ warn.mockRestore();
125
+ }
126
+ });
127
+
128
+ test("refine with setDefaultVariants", () => {
129
+ const component = getModeComponent(
130
+ mode,
131
+ cv({
132
+ variants: {
133
+ size: { sm: "sm", lg: "lg" },
134
+ color: { red: "red", blue: "blue" },
135
+ },
136
+ refine: ({ variants, setDefaultVariants }) => {
59
137
  if (variants.size === "lg") {
60
138
  setDefaultVariants({ color: "red" });
61
139
  }
@@ -66,7 +144,7 @@ for (const config of Object.values(CONFIGS)) {
66
144
  expect(getStyleClass(props)).toEqual({ class: cls("lg red") });
67
145
  });
68
146
 
69
- test("computed setDefaultVariants does not override props", () => {
147
+ test("refine setDefaultVariants does not override props", () => {
70
148
  const component = getModeComponent(
71
149
  mode,
72
150
  cv({
@@ -74,7 +152,7 @@ for (const config of Object.values(CONFIGS)) {
74
152
  size: { sm: "sm", lg: "lg" },
75
153
  color: { red: "red", blue: "blue" },
76
154
  },
77
- computed: ({ setDefaultVariants }) => {
155
+ refine: ({ setDefaultVariants }) => {
78
156
  setDefaultVariants({ color: "red" });
79
157
  },
80
158
  }),
@@ -83,7 +161,7 @@ for (const config of Object.values(CONFIGS)) {
83
161
  expect(getStyleClass(props)).toEqual({ class: cls("lg blue") });
84
162
  });
85
163
 
86
- test("computed setDefaultVariants overrides defaultVariants", () => {
164
+ test("refine setDefaultVariants overrides defaultVariants", () => {
87
165
  const component = getModeComponent(
88
166
  mode,
89
167
  cv({
@@ -92,7 +170,7 @@ for (const config of Object.values(CONFIGS)) {
92
170
  color: { red: "red", blue: "blue" },
93
171
  },
94
172
  defaultVariants: { size: "sm", color: "red" },
95
- computed: ({ setDefaultVariants }) => {
173
+ refine: ({ setDefaultVariants }) => {
96
174
  setDefaultVariants({ color: "blue" });
97
175
  },
98
176
  }),
@@ -101,7 +179,7 @@ for (const config of Object.values(CONFIGS)) {
101
179
  expect(getStyleClass(props)).toEqual({ class: cls("sm blue") });
102
180
  });
103
181
 
104
- test("computed setDefaultVariants overrides extended defaultVariants", () => {
182
+ test("refine setDefaultVariants overrides extended defaultVariants", () => {
105
183
  const base = cv({
106
184
  variants: { color: { red: "red", blue: "blue" } },
107
185
  defaultVariants: { color: "red" },
@@ -112,7 +190,7 @@ for (const config of Object.values(CONFIGS)) {
112
190
  extend: [base],
113
191
  variants: { size: { sm: "sm", lg: "lg" } },
114
192
  defaultVariants: { size: "sm" },
115
- computed: ({ setDefaultVariants }) => {
193
+ refine: ({ setDefaultVariants }) => {
116
194
  setDefaultVariants({ color: "blue" });
117
195
  },
118
196
  }),
@@ -121,7 +199,7 @@ for (const config of Object.values(CONFIGS)) {
121
199
  expect(getStyleClass(props)).toEqual({ class: cls("blue sm") });
122
200
  });
123
201
 
124
- test("computed setDefaultVariants overrides child defaultVariants", () => {
202
+ test("refine setDefaultVariants overrides child defaultVariants", () => {
125
203
  const base = cv({
126
204
  variants: { size: { sm: "sm", lg: "lg" } },
127
205
  });
@@ -131,7 +209,7 @@ for (const config of Object.values(CONFIGS)) {
131
209
  extend: [base],
132
210
  variants: { color: { red: "red", blue: "blue" } },
133
211
  defaultVariants: { size: "sm", color: "red" },
134
- computed: ({ setDefaultVariants }) => {
212
+ refine: ({ setDefaultVariants }) => {
135
213
  setDefaultVariants({ size: "lg" });
136
214
  },
137
215
  }),
@@ -140,11 +218,11 @@ for (const config of Object.values(CONFIGS)) {
140
218
  expect(getStyleClass(props)).toEqual({ class: cls("lg red") });
141
219
  });
142
220
 
143
- test("computed setDefaultVariants from parent overrides child defaultVariants", () => {
221
+ test("refine setDefaultVariants from parent overrides child defaultVariants", () => {
144
222
  const base = cv({
145
223
  variants: { size: { sm: "sm", lg: "lg" } },
146
224
  defaultVariants: { size: "sm" },
147
- computed: ({ setDefaultVariants }) => {
225
+ refine: ({ setDefaultVariants }) => {
148
226
  setDefaultVariants({ size: "lg" });
149
227
  },
150
228
  });
@@ -160,11 +238,11 @@ for (const config of Object.values(CONFIGS)) {
160
238
  expect(getStyleClass(props)).toEqual({ class: cls("lg red") });
161
239
  });
162
240
 
163
- test("computed setDefaultVariants from parent overrides child defaultVariants based on props", () => {
241
+ test("refine setDefaultVariants from parent overrides child defaultVariants based on props", () => {
164
242
  const base = cv({
165
243
  variants: { size: { sm: "sm", lg: "lg" }, enabled: "" },
166
244
  defaultVariants: { size: "sm" },
167
- computed: ({ variants, setDefaultVariants }) => {
245
+ refine: ({ variants, setDefaultVariants }) => {
168
246
  if (!variants.enabled) return;
169
247
  setDefaultVariants({ size: "lg" });
170
248
  },
@@ -181,11 +259,11 @@ for (const config of Object.values(CONFIGS)) {
181
259
  expect(getStyleClass(props)).toEqual({ class: cls("lg red") });
182
260
  });
183
261
 
184
- test("computed receives default variants from child", () => {
262
+ test("refine receives default variants from child", () => {
185
263
  const base = cv({
186
264
  variants: { size: { sm: "sm", lg: "lg" }, large: "" },
187
265
  defaultVariants: { size: "sm" },
188
- computed: ({ variants, setDefaultVariants }) => {
266
+ refine: ({ variants, setDefaultVariants }) => {
189
267
  if (variants.large) {
190
268
  setDefaultVariants({ size: "lg" });
191
269
  }
@@ -203,11 +281,11 @@ for (const config of Object.values(CONFIGS)) {
203
281
  expect(getStyleClass(props)).toEqual({ class: cls("lg red") });
204
282
  });
205
283
 
206
- test("computed receives default variants from grandchild", () => {
284
+ test("refine receives default variants from grandchild", () => {
207
285
  const base = cv({
208
286
  variants: { size: { sm: "sm", lg: "lg" }, large: "" },
209
287
  defaultVariants: { size: "sm" },
210
- computed: ({ variants, setDefaultVariants }) => {
288
+ refine: ({ variants, setDefaultVariants }) => {
211
289
  if (variants.large) {
212
290
  setDefaultVariants({ size: "lg" });
213
291
  }
@@ -226,10 +304,10 @@ for (const config of Object.values(CONFIGS)) {
226
304
  expect(getStyleClass(props)).toEqual({ class: cls("lg red") });
227
305
  });
228
306
 
229
- test("computed receives default variants from intermediate component", () => {
307
+ test("refine receives default variants from intermediate component", () => {
230
308
  const parent = cv({
231
309
  variants: { size: { sm: "sm", lg: "lg" } },
232
- computed: ({ variants, setDefaultVariants }) => {
310
+ refine: ({ variants, setDefaultVariants }) => {
233
311
  if (!variants.size) {
234
312
  setDefaultVariants({ size: "lg" });
235
313
  }
@@ -241,11 +319,11 @@ for (const config of Object.values(CONFIGS)) {
241
319
  expect(getStyleClass(props)).toEqual({ class: cls("sm") });
242
320
  });
243
321
 
244
- test("child computed setDefaultVariants overrides parent computed setDefaultVariants", () => {
322
+ test("child refine setDefaultVariants overrides parent refine setDefaultVariants", () => {
245
323
  const base = cv({
246
324
  variants: { size: { sm: "sm", lg: "lg" } },
247
325
  defaultVariants: { size: "sm" },
248
- computed: ({ setDefaultVariants }) => {
326
+ refine: ({ setDefaultVariants }) => {
249
327
  setDefaultVariants({ size: "lg" });
250
328
  },
251
329
  });
@@ -255,23 +333,23 @@ for (const config of Object.values(CONFIGS)) {
255
333
  extend: [base],
256
334
  variants: { color: { red: "red", blue: "blue" } },
257
335
  defaultVariants: { size: "sm", color: "red" },
258
- computed: ({ setDefaultVariants }) => {
336
+ refine: ({ setDefaultVariants }) => {
259
337
  setDefaultVariants({ size: "sm" });
260
338
  },
261
339
  }),
262
340
  );
263
341
  const props = component();
264
342
  // Order: parent defaultVariants (sm) -> child defaultVariants (sm)
265
- // -> parent computed.setDefaultVariants (lg)
266
- // -> child computed.setDefaultVariants (sm)
343
+ // -> parent refine.setDefaultVariants (lg)
344
+ // -> child refine.setDefaultVariants (sm)
267
345
  expect(getStyleClass(props)).toEqual({ class: cls("sm red") });
268
346
  });
269
347
 
270
- test("computed in extended component does not see foreign variant keys", () => {
348
+ test("refine in extended component does not see foreign variant keys", () => {
271
349
  const base = cv({
272
350
  variants: { size: { sm: "sm", lg: "lg" } },
273
351
  defaultVariants: { size: "sm" },
274
- computed: ({ variants, addClass }) => {
352
+ refine: ({ variants, addClass }) => {
275
353
  // `color` is added by the parent component below — it must not
276
354
  // leak into base's `ctx.variants`, whose shape is declared as
277
355
  // `{ size }` only.
@@ -296,14 +374,14 @@ for (const config of Object.values(CONFIGS)) {
296
374
  });
297
375
 
298
376
  test("setDefaultVariants in extended component does not branch on foreign variant keys", () => {
299
- // Same shape as the test above, but the base's `computed` reaches
377
+ // Same shape as the test above, but the base's `refine` reaches
300
378
  // `setDefaultVariants` — covers the resolveDefaults pass (driving
301
379
  // both class output and `getVariants`) rather than the render-time
302
380
  // compute path.
303
381
  const base = cv({
304
382
  variants: { size: { sm: "sm", lg: "lg" } },
305
383
  defaultVariants: { size: "sm" },
306
- computed: ({ variants, setDefaultVariants }) => {
384
+ refine: ({ variants, setDefaultVariants }) => {
307
385
  if ("color" in (variants as Record<string, unknown>)) {
308
386
  setDefaultVariants({ size: "lg" });
309
387
  }
@@ -324,10 +402,10 @@ for (const config of Object.values(CONFIGS)) {
324
402
  });
325
403
  });
326
404
 
327
- test("child setDefaultVariants receives computed variants from parent", () => {
405
+ test("child setDefaultVariants receives refined variants from parent", () => {
328
406
  const base = cv({
329
407
  variants: { size: { sm: "sm", lg: "lg" }, small: "" },
330
- computed: ({ setDefaultVariants }) => {
408
+ refine: ({ setDefaultVariants }) => {
331
409
  setDefaultVariants({ small: true });
332
410
  },
333
411
  });
@@ -337,7 +415,7 @@ for (const config of Object.values(CONFIGS)) {
337
415
  extend: [base],
338
416
  variants: { color: { red: "red", blue: "blue" } },
339
417
  defaultVariants: { size: "lg", color: "red" },
340
- computed: ({ variants, setDefaultVariants }) => {
418
+ refine: ({ variants, setDefaultVariants }) => {
341
419
  if (variants.small) {
342
420
  setDefaultVariants({ size: "sm" });
343
421
  }
@@ -348,7 +426,346 @@ for (const config of Object.values(CONFIGS)) {
348
426
  expect(getStyleClass(props)).toEqual({ class: cls("sm red") });
349
427
  });
350
428
 
351
- test("computed setDefaultVariants when explicitly passing undefined", () => {
429
+ test("refine re-runs when base component refine changes variants", () => {
430
+ const base = cv({
431
+ variants: { size: { sm: "sm", lg: "lg" }, active: "" },
432
+ defaultVariants: { size: "sm" },
433
+ refine: ({ variants, setVariants }) => {
434
+ if (variants.active) {
435
+ setVariants({ size: "lg" });
436
+ }
437
+ },
438
+ });
439
+ const component = getModeComponent(
440
+ mode,
441
+ cv({
442
+ extend: [base],
443
+ variants: { color: { red: "red", blue: "blue" } },
444
+ refine: ({ variants, setVariants }) => {
445
+ if (variants.size === "lg") {
446
+ setVariants({ color: "red" });
447
+ }
448
+ },
449
+ }),
450
+ );
451
+ const props = component({ active: true });
452
+ expect(getStyleClass(props)).toEqual({ class: cls("lg red") });
453
+ });
454
+
455
+ test("base refine setDefaultVariants works after its own setVariants re-run", () => {
456
+ const base = cv({
457
+ variants: {
458
+ size: { sm: "sm", lg: "lg" },
459
+ active: "",
460
+ mode: { on: "on" },
461
+ },
462
+ defaultVariants: { size: "sm" },
463
+ refine: ({ variants, setVariants, setDefaultVariants }) => {
464
+ if (variants.active) {
465
+ setVariants({ mode: "on" });
466
+ }
467
+ if (variants.mode === "on") {
468
+ setDefaultVariants({ size: "lg" });
469
+ }
470
+ },
471
+ });
472
+ const component = getModeComponent(mode, cv({ extend: [base] }));
473
+ const props = component({ active: true });
474
+ expect(getStyleClass(props)).toEqual({ class: cls("lg on") });
475
+ });
476
+
477
+ test("refine setVariants uses the latest pending value", () => {
478
+ const component = getModeComponent(
479
+ mode,
480
+ cv({
481
+ variants: { size: { sm: "sm", lg: "lg" } },
482
+ defaultVariants: { size: "sm" },
483
+ refine: ({ setVariants }) => {
484
+ setVariants({ size: "lg" });
485
+ setVariants({ size: "sm" });
486
+ },
487
+ }),
488
+ );
489
+ const props = component();
490
+ expect(getStyleClass(props)).toEqual({ class: cls("sm") });
491
+ });
492
+
493
+ test("refine setVariants does not mutate props with plain extends", () => {
494
+ const base = cv({
495
+ variants: { color: { red: "red", blue: "blue" } },
496
+ });
497
+ const component = getModeComponent(
498
+ mode,
499
+ cv({
500
+ extend: [base],
501
+ variants: { active: "" },
502
+ refine: ({ variants, setVariants }) => {
503
+ if (variants.active) {
504
+ setVariants({ color: "red" });
505
+ }
506
+ },
507
+ }),
508
+ );
509
+ const mutableProps = { active: true };
510
+ component(mutableProps);
511
+ expect(mutableProps).toEqual({ active: true });
512
+
513
+ const frozenProps = Object.freeze({ active: true });
514
+ const getProps = () => component(frozenProps);
515
+ expect(getProps).not.toThrow();
516
+ expect(getStyleClass(getProps())).toEqual({ class: cls("red") });
517
+ });
518
+
519
+ test("child setVariants keeps overriding base setDefaultVariants across re-runs", () => {
520
+ const base = cv({
521
+ variants: { color: { red: "red", blue: "blue" } },
522
+ refine: ({ setDefaultVariants }) => {
523
+ setDefaultVariants({ color: "blue" });
524
+ },
525
+ });
526
+ const component = getModeComponent(
527
+ mode,
528
+ cv({
529
+ extend: [base],
530
+ variants: { size: { sm: "sm", lg: "lg" } },
531
+ defaultVariants: { size: "sm" },
532
+ refine: ({ variants, setVariants }) => {
533
+ if (variants.size === "sm") {
534
+ setVariants({ color: "red" });
535
+ }
536
+ },
537
+ }),
538
+ );
539
+ const props = component();
540
+ expect(getStyleClass(props)).toEqual({ class: cls("red sm") });
541
+ });
542
+
543
+ test("refine setVariants sticks across re-runs", () => {
544
+ const base = cv({
545
+ variants: { color: { red: "red", blue: "blue" } },
546
+ refine: ({ setDefaultVariants }) => {
547
+ setDefaultVariants({ color: "blue" });
548
+ },
549
+ });
550
+ const component = getModeComponent(
551
+ mode,
552
+ cv({
553
+ extend: [base],
554
+ variants: { color: { red: "", blue: "" }, done: "" },
555
+ refine: ({ variants, setVariants }) => {
556
+ if (!variants.done) {
557
+ setVariants({ color: "red", done: true });
558
+ }
559
+ },
560
+ }),
561
+ );
562
+ const props = component();
563
+ expect(getStyleClass(props)).toEqual({ class: cls("red") });
564
+ });
565
+
566
+ test("base refine setDefaultVariants can override child static defaults after a re-run", () => {
567
+ const base = cv({
568
+ variants: {
569
+ size: { sm: "sm", lg: "lg" },
570
+ active: "",
571
+ mode: { on: "on" },
572
+ },
573
+ refine: ({ variants, setVariants, setDefaultVariants }) => {
574
+ if (variants.active) {
575
+ setVariants({ mode: "on" });
576
+ }
577
+ if (variants.mode === "on") {
578
+ setDefaultVariants({ size: "lg" });
579
+ }
580
+ },
581
+ });
582
+ const component = getModeComponent(
583
+ mode,
584
+ cv({ extend: [base], defaultVariants: { size: "sm" } }),
585
+ );
586
+ const props = component({ active: true });
587
+ expect(getStyleClass(props)).toEqual({ class: cls("lg on") });
588
+ });
589
+
590
+ test("setVariants from earlier extends overrides setDefaultVariants from later extends", () => {
591
+ const first = cv({
592
+ variants: { color: { red: "first-red", blue: "first-blue" } },
593
+ refine: ({ setVariants }) => {
594
+ setVariants({ color: "red" });
595
+ },
596
+ });
597
+ const second = cv({
598
+ variants: { color: { red: "second-red", blue: "second-blue" } },
599
+ refine: ({ setDefaultVariants }) => {
600
+ setDefaultVariants({ color: "blue" });
601
+ },
602
+ });
603
+ const component = getModeComponent(mode, cv({ extend: [first, second] }));
604
+ const props = component();
605
+ expect(getStyleClass(props)).toEqual({
606
+ class: cls("first-red second-red"),
607
+ });
608
+ });
609
+
610
+ test("setDefaultVariants from later extends overrides setDefaultVariants from earlier extends", () => {
611
+ const first = cv({
612
+ variants: { color: { red: "first-red", blue: "first-blue" } },
613
+ refine: ({ setDefaultVariants }) => {
614
+ setDefaultVariants({ color: "red" });
615
+ },
616
+ });
617
+ const second = cv({
618
+ variants: { color: { red: "second-red", blue: "second-blue" } },
619
+ refine: ({ setDefaultVariants }) => {
620
+ setDefaultVariants({ color: "blue" });
621
+ },
622
+ });
623
+ const component = getModeComponent(mode, cv({ extend: [first, second] }));
624
+ const props = component();
625
+ expect(getStyleClass(props)).toEqual({
626
+ class: cls("first-blue second-blue"),
627
+ });
628
+ });
629
+
630
+ test("setDefaultVariants does not override stable setVariants on later passes", () => {
631
+ const base = cv({
632
+ variants: { color: { red: "base-red", blue: "base-blue" } },
633
+ refine: ({ setVariants }) => {
634
+ setVariants({ color: "red" });
635
+ },
636
+ });
637
+ const component = getModeComponent(
638
+ mode,
639
+ cv({
640
+ extend: [base],
641
+ variants: { color: { red: "child-red", blue: "child-blue" } },
642
+ refine: ({ variants, setDefaultVariants }) => {
643
+ if (variants.color === "red") {
644
+ setDefaultVariants({ color: "blue" });
645
+ }
646
+ },
647
+ }),
648
+ );
649
+ const props = component();
650
+ expect(getStyleClass(props)).toEqual({
651
+ class: cls("base-red child-red"),
652
+ });
653
+ });
654
+
655
+ test("setDefaultVariants does not override setVariants from a previous pass", () => {
656
+ const component = getModeComponent(
657
+ mode,
658
+ cv({
659
+ variants: {
660
+ color: { red: "red", blue: "blue" },
661
+ done: "",
662
+ },
663
+ refine: ({ variants, setVariants, setDefaultVariants }) => {
664
+ if (!variants.done) {
665
+ setVariants({ color: "red", done: true });
666
+ }
667
+ if (variants.done) {
668
+ setDefaultVariants({ color: "blue" });
669
+ }
670
+ },
671
+ }),
672
+ );
673
+ const props = component();
674
+ expect(getStyleClass(props)).toEqual({ class: cls("red") });
675
+ });
676
+
677
+ test("refine warns when variants keep changing", () => {
678
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
679
+ const component = getModeComponent(
680
+ mode,
681
+ cv({
682
+ variants: { size: { sm: "sm", lg: "lg" } },
683
+ defaultVariants: { size: "sm" },
684
+ refine: ({ variants, setVariants }) => {
685
+ setVariants({ size: variants.size === "sm" ? "lg" : "sm" });
686
+ },
687
+ }),
688
+ );
689
+
690
+ try {
691
+ component();
692
+ expect(warn).toHaveBeenCalledWith(
693
+ expect.stringContaining("Maximum refine iterations exceeded"),
694
+ );
695
+ } finally {
696
+ warn.mockRestore();
697
+ }
698
+ });
699
+
700
+ test("refine warning is shared across extended components", () => {
701
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
702
+ const base = cv({
703
+ variants: { size: { sm: "sm", lg: "lg" } },
704
+ defaultVariants: { size: "sm" },
705
+ refine: ({ variants, setVariants }) => {
706
+ setVariants({ size: variants.size === "sm" ? "lg" : "sm" });
707
+ },
708
+ });
709
+ const component = getModeComponent(mode, cv({ extend: [base] }));
710
+
711
+ try {
712
+ component();
713
+ expect(warn).toHaveBeenCalledTimes(1);
714
+ expect(warn).toHaveBeenCalledWith(
715
+ expect.stringContaining("Maximum refine iterations exceeded"),
716
+ );
717
+ } finally {
718
+ warn.mockRestore();
719
+ }
720
+ });
721
+
722
+ test("getVariants warns when variants keep changing", () => {
723
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
724
+ const component = getModeComponent(
725
+ mode,
726
+ cv({
727
+ variants: { size: { sm: "sm", lg: "lg" } },
728
+ defaultVariants: { size: "sm" },
729
+ refine: ({ variants, setVariants }) => {
730
+ setVariants({ size: variants.size === "sm" ? "lg" : "sm" });
731
+ },
732
+ }),
733
+ );
734
+
735
+ try {
736
+ component.getVariants();
737
+ expect(warn).toHaveBeenCalledWith(
738
+ expect.stringContaining("Maximum refine iterations exceeded"),
739
+ );
740
+ } finally {
741
+ warn.mockRestore();
742
+ }
743
+ });
744
+
745
+ test("refine warning is omitted in production", () => {
746
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
747
+ vi.stubEnv("NODE_ENV", "production");
748
+ const component = getModeComponent(
749
+ mode,
750
+ cv({
751
+ variants: { size: { sm: "sm", lg: "lg" } },
752
+ defaultVariants: { size: "sm" },
753
+ refine: ({ variants, setVariants }) => {
754
+ setVariants({ size: variants.size === "sm" ? "lg" : "sm" });
755
+ },
756
+ }),
757
+ );
758
+
759
+ try {
760
+ component();
761
+ expect(warn).not.toHaveBeenCalled();
762
+ } finally {
763
+ vi.unstubAllEnvs();
764
+ warn.mockRestore();
765
+ }
766
+ });
767
+
768
+ test("refine setDefaultVariants when explicitly passing undefined", () => {
352
769
  const component = getModeComponent(
353
770
  mode,
354
771
  cv({
@@ -356,7 +773,7 @@ for (const config of Object.values(CONFIGS)) {
356
773
  size: { sm: "sm", lg: "lg" },
357
774
  color: { red: "red", blue: "blue" },
358
775
  },
359
- computed: ({ setDefaultVariants }) => {
776
+ refine: ({ setDefaultVariants }) => {
360
777
  setDefaultVariants({ color: "red" });
361
778
  },
362
779
  }),
@@ -365,7 +782,7 @@ for (const config of Object.values(CONFIGS)) {
365
782
  expect(getStyleClass(props)).toEqual({ class: cls("lg red") });
366
783
  });
367
784
 
368
- test("computed with defaultVariants", () => {
785
+ test("refine with defaultVariants", () => {
369
786
  const component = getModeComponent(
370
787
  mode,
371
788
  cv({
@@ -374,15 +791,15 @@ for (const config of Object.values(CONFIGS)) {
374
791
  color: { red: "red", blue: "blue" },
375
792
  },
376
793
  defaultVariants: { size: "lg" },
377
- computed: ({ variants }) =>
378
- variants.size === "lg" ? "computed-lg" : null,
794
+ refine: ({ variants }) =>
795
+ variants.size === "lg" ? "refine-lg" : null,
379
796
  }),
380
797
  );
381
798
  const props = component();
382
- expect(getStyleClass(props)).toEqual({ class: cls("lg computed-lg") });
799
+ expect(getStyleClass(props)).toEqual({ class: cls("lg refine-lg") });
383
800
  });
384
801
 
385
- test("computed with defaultVariants from extended", () => {
802
+ test("refine with defaultVariants from extended", () => {
386
803
  const base = cv({
387
804
  variants: { size: { sm: "sm", lg: "lg" } },
388
805
  defaultVariants: { size: "lg" },
@@ -391,22 +808,22 @@ for (const config of Object.values(CONFIGS)) {
391
808
  mode,
392
809
  cv({
393
810
  extend: [base],
394
- computed: ({ variants }) =>
395
- variants.size === "lg" ? "computed-lg" : null,
811
+ refine: ({ variants }) =>
812
+ variants.size === "lg" ? "refine-lg" : null,
396
813
  }),
397
814
  );
398
815
  const props = component();
399
- expect(getStyleClass(props)).toEqual({ class: cls("lg computed-lg") });
816
+ expect(getStyleClass(props)).toEqual({ class: cls("lg refine-lg") });
400
817
  });
401
818
 
402
- test("computed from parent receives boolean default value from overridden variant in child", () => {
819
+ test("refine from parent receives boolean default value from overridden variant in child", () => {
403
820
  const base = cv({
404
821
  variants: {
405
822
  size: { sm: "sm", lg: "lg" },
406
823
  border: { default: "default", true: "border", false: "" },
407
824
  },
408
825
  defaultVariants: { size: "lg" },
409
- computed: ({ variants, setVariants }) => {
826
+ refine: ({ variants, setVariants }) => {
410
827
  expect(variants.border).toBe(false);
411
828
  if (!variants.border) {
412
829
  setVariants({ size: "sm" });
@@ -417,7 +834,7 @@ for (const config of Object.values(CONFIGS)) {
417
834
  mode,
418
835
  cv({
419
836
  extend: [base],
420
- computedVariants: {
837
+ variants: {
421
838
  border: (_: boolean) => {},
422
839
  },
423
840
  defaultVariants: { border: false },
@@ -427,14 +844,14 @@ for (const config of Object.values(CONFIGS)) {
427
844
  expect(getStyleClass(props)).toEqual({ class: cls("sm") });
428
845
  });
429
846
 
430
- test("computed from parent receives boolean default value from overridden variant in grandchild", () => {
847
+ test("refine from parent receives boolean default value from overridden variant in grandchild", () => {
431
848
  const base = cv({
432
849
  variants: {
433
850
  size: { sm: "sm", lg: "lg" },
434
851
  border: { default: "default", true: "border", false: "" },
435
852
  },
436
853
  defaultVariants: { size: "lg" },
437
- computed: ({ variants, setVariants }) => {
854
+ refine: ({ variants, setVariants }) => {
438
855
  expect(variants.border).toBe(false);
439
856
  if (!variants.border) {
440
857
  setVariants({ size: "sm" });
@@ -446,7 +863,7 @@ for (const config of Object.values(CONFIGS)) {
446
863
  mode,
447
864
  cv({
448
865
  extend: [base2],
449
- computedVariants: {
866
+ variants: {
450
867
  border: (_: boolean) => {},
451
868
  },
452
869
  defaultVariants: { border: false },
@@ -456,14 +873,14 @@ for (const config of Object.values(CONFIGS)) {
456
873
  expect(getStyleClass(props)).toEqual({ class: cls("sm") });
457
874
  });
458
875
 
459
- test("computed from parent receives false prop from overridden variant in child", () => {
876
+ test("refine from parent receives false prop from overridden variant in child", () => {
460
877
  const base = cv({
461
878
  variants: {
462
879
  size: { sm: "sm", lg: "lg" },
463
880
  border: { default: "default", true: "border", false: "" },
464
881
  },
465
882
  defaultVariants: { size: "lg" },
466
- computed: ({ variants, setVariants }) => {
883
+ refine: ({ variants, setVariants }) => {
467
884
  expect(variants.border).toBe(false);
468
885
  if (!variants.border) {
469
886
  setVariants({ size: "sm" });
@@ -474,7 +891,7 @@ for (const config of Object.values(CONFIGS)) {
474
891
  mode,
475
892
  cv({
476
893
  extend: [base],
477
- computedVariants: {
894
+ variants: {
478
895
  border: (_: boolean) => {},
479
896
  },
480
897
  }),
@@ -483,14 +900,14 @@ for (const config of Object.values(CONFIGS)) {
483
900
  expect(getStyleClass(props)).toEqual({ class: cls("sm") });
484
901
  });
485
902
 
486
- test("computed from parent receives true prop from overridden variant in child", () => {
903
+ test("refine from parent receives true prop from overridden variant in child", () => {
487
904
  const base = cv({
488
905
  variants: {
489
906
  size: { sm: "sm", lg: "lg" },
490
907
  border: { default: "default", true: "border", false: "" },
491
908
  },
492
909
  defaultVariants: { size: "lg" },
493
- computed: ({ variants, setVariants }) => {
910
+ refine: ({ variants, setVariants }) => {
494
911
  expect(variants.border).toBe(true);
495
912
  if (variants.border) {
496
913
  setVariants({ size: "sm" });
@@ -501,7 +918,7 @@ for (const config of Object.values(CONFIGS)) {
501
918
  mode,
502
919
  cv({
503
920
  extend: [base],
504
- computedVariants: {
921
+ variants: {
505
922
  border: (_: boolean) => {},
506
923
  },
507
924
  }),
@@ -510,14 +927,14 @@ for (const config of Object.values(CONFIGS)) {
510
927
  expect(getStyleClass(props)).toEqual({ class: cls("sm") });
511
928
  });
512
929
 
513
- test("computed from parent receives true prop from overridden variant in grandchild", () => {
930
+ test("refine from parent receives true prop from overridden variant in grandchild", () => {
514
931
  const base = cv({
515
932
  variants: {
516
933
  size: { sm: "sm", lg: "lg" },
517
934
  border: { default: "default", true: "border", false: "" },
518
935
  },
519
936
  defaultVariants: { size: "lg" },
520
- computed: ({ variants, setVariants }) => {
937
+ refine: ({ variants, setVariants }) => {
521
938
  expect(variants.border).toBe(true);
522
939
  if (variants.border) {
523
940
  setVariants({ size: "sm" });
@@ -529,7 +946,7 @@ for (const config of Object.values(CONFIGS)) {
529
946
  mode,
530
947
  cv({
531
948
  extend: [base2],
532
- computedVariants: {
949
+ variants: {
533
950
  border: (_: boolean) => {},
534
951
  },
535
952
  }),
@@ -538,12 +955,12 @@ for (const config of Object.values(CONFIGS)) {
538
955
  expect(getStyleClass(props)).toEqual({ class: cls("sm") });
539
956
  });
540
957
 
541
- test("computed with style", () => {
958
+ test("refine with style", () => {
542
959
  const component = getModeComponent(
543
960
  mode,
544
961
  cv({
545
962
  variants: { size: { sm: "sm", lg: "lg" } },
546
- computed: ({ variants }) =>
963
+ refine: ({ variants }) =>
547
964
  variants.size === "lg" ? { style: { fontSize: "20px" } } : null,
548
965
  }),
549
966
  );
@@ -554,34 +971,34 @@ for (const config of Object.values(CONFIGS)) {
554
971
  });
555
972
  });
556
973
 
557
- test("computed with class and style", () => {
974
+ test("refine with class and style", () => {
558
975
  const component = getModeComponent(
559
976
  mode,
560
977
  cv({
561
978
  variants: { size: { sm: "sm", lg: "lg" } },
562
- computed: ({ variants }) =>
979
+ refine: ({ variants }) =>
563
980
  variants.size === "lg"
564
- ? { class: "computed-lg", style: { fontSize: "20px" } }
981
+ ? { class: "refine-lg", style: { fontSize: "20px" } }
565
982
  : null,
566
983
  }),
567
984
  );
568
985
  const props = component({ size: "lg" });
569
986
  expect(getStyleClass(props)).toEqual({
570
- class: cls("lg computed-lg"),
987
+ class: cls("lg refine-lg"),
571
988
  fontSize: "20px",
572
989
  });
573
990
  });
574
991
 
575
- test("computed style does not accept numbers", () => {
992
+ test("refine style does not accept numbers", () => {
576
993
  const component = getModeComponent(
577
994
  mode,
578
995
  cv({
579
996
  variants: { size: { sm: "sm", lg: "lg" } },
580
997
  // @ts-expect-error
581
- computed: ({ variants }) =>
998
+ refine: ({ variants }) =>
582
999
  variants.size === "lg"
583
1000
  ? {
584
- class: "computed-lg",
1001
+ class: "refine-lg",
585
1002
  style: { fontSize: 20 },
586
1003
  }
587
1004
  : null,
@@ -589,17 +1006,17 @@ for (const config of Object.values(CONFIGS)) {
589
1006
  );
590
1007
  const props = component({ size: "lg" });
591
1008
  expect(getStyleClass(props)).toEqual({
592
- class: cls("lg computed-lg"),
1009
+ class: cls("lg refine-lg"),
593
1010
  fontSize: expect.toBeOneOf(["20", "20px"]),
594
1011
  });
595
1012
  });
596
1013
 
597
- test("computed setVariants does not accept invalid keys", () => {
1014
+ test("refine setVariants does not accept invalid keys", () => {
598
1015
  const component = getModeComponent(
599
1016
  mode,
600
1017
  cv({
601
1018
  variants: { size: { sm: "sm", lg: "lg" } },
602
- computed: ({ setVariants }) => {
1019
+ refine: ({ setVariants }) => {
603
1020
  setVariants({
604
1021
  // @ts-expect-error
605
1022
  invalidKey: "value",
@@ -611,12 +1028,12 @@ for (const config of Object.values(CONFIGS)) {
611
1028
  expect(getStyleClass(props)).toEqual({ class: cls("lg") });
612
1029
  });
613
1030
 
614
- test("computed setVariants does not accept invalid values", () => {
1031
+ test("refine setVariants does not accept invalid values", () => {
615
1032
  const component = getModeComponent(
616
1033
  mode,
617
1034
  cv({
618
1035
  variants: { size: { sm: "sm", lg: "lg" } },
619
- computed: ({ setVariants }) => {
1036
+ refine: ({ setVariants }) => {
620
1037
  setVariants({
621
1038
  // @ts-expect-error invalid value
622
1039
  size:
@@ -631,12 +1048,12 @@ for (const config of Object.values(CONFIGS)) {
631
1048
  expect(getStyleClass(props)).toEqual({ class: "" });
632
1049
  });
633
1050
 
634
- test("computed addClass with string", () => {
1051
+ test("refine addClass with string", () => {
635
1052
  const component = getModeComponent(
636
1053
  mode,
637
1054
  cv({
638
1055
  variants: { size: { sm: "sm", lg: "lg" } },
639
- computed: ({ variants, addClass }) => {
1056
+ refine: ({ variants, addClass }) => {
640
1057
  if (variants.size === "lg") {
641
1058
  addClass("added-lg");
642
1059
  }
@@ -647,12 +1064,12 @@ for (const config of Object.values(CONFIGS)) {
647
1064
  expect(getStyleClass(props)).toEqual({ class: cls("lg added-lg") });
648
1065
  });
649
1066
 
650
- test("computed addClass with array", () => {
1067
+ test("refine addClass with array", () => {
651
1068
  const component = getModeComponent(
652
1069
  mode,
653
1070
  cv({
654
1071
  variants: { size: { sm: "sm", lg: "lg" } },
655
- computed: ({ variants, addClass }) => {
1072
+ refine: ({ variants, addClass }) => {
656
1073
  if (variants.size === "lg") {
657
1074
  addClass(["added-lg", "extra-class"]);
658
1075
  }
@@ -665,12 +1082,12 @@ for (const config of Object.values(CONFIGS)) {
665
1082
  });
666
1083
  });
667
1084
 
668
- test("computed addStyle", () => {
1085
+ test("refine addStyle", () => {
669
1086
  const component = getModeComponent(
670
1087
  mode,
671
1088
  cv({
672
1089
  variants: { size: { sm: "sm", lg: "lg" } },
673
- computed: ({ variants, addStyle }) => {
1090
+ refine: ({ variants, addStyle }) => {
674
1091
  if (variants.size === "lg") {
675
1092
  addStyle({ fontSize: "20px" });
676
1093
  }
@@ -684,12 +1101,12 @@ for (const config of Object.values(CONFIGS)) {
684
1101
  });
685
1102
  });
686
1103
 
687
- test("computed addClass combined with return value", () => {
1104
+ test("refine addClass combined with return value", () => {
688
1105
  const component = getModeComponent(
689
1106
  mode,
690
1107
  cv({
691
1108
  variants: { size: { sm: "sm", lg: "lg" } },
692
- computed: ({ variants, addClass }) => {
1109
+ refine: ({ variants, addClass }) => {
693
1110
  if (variants.size === "lg") {
694
1111
  addClass("added-class");
695
1112
  }
@@ -703,12 +1120,12 @@ for (const config of Object.values(CONFIGS)) {
703
1120
  });
704
1121
  });
705
1122
 
706
- test("computed addStyle combined with return value", () => {
1123
+ test("refine addStyle combined with return value", () => {
707
1124
  const component = getModeComponent(
708
1125
  mode,
709
1126
  cv({
710
1127
  variants: { size: { sm: "sm", lg: "lg" } },
711
- computed: ({ variants, addStyle }) => {
1128
+ refine: ({ variants, addStyle }) => {
712
1129
  if (variants.size === "lg") {
713
1130
  addStyle({ fontSize: "20px" });
714
1131
  }
@@ -724,12 +1141,12 @@ for (const config of Object.values(CONFIGS)) {
724
1141
  });
725
1142
  });
726
1143
 
727
- test("computed addClass and addStyle together", () => {
1144
+ test("refine addClass and addStyle together", () => {
728
1145
  const component = getModeComponent(
729
1146
  mode,
730
1147
  cv({
731
1148
  variants: { size: { sm: "sm", lg: "lg" } },
732
- computed: ({ variants, addClass, addStyle }) => {
1149
+ refine: ({ variants, addClass, addStyle }) => {
733
1150
  if (variants.size === "lg") {
734
1151
  addClass("added-lg");
735
1152
  addStyle({ fontSize: "20px" });
@@ -744,12 +1161,12 @@ for (const config of Object.values(CONFIGS)) {
744
1161
  });
745
1162
  });
746
1163
 
747
- test("computed addClass and addStyle with return value", () => {
1164
+ test("refine addClass and addStyle with return value", () => {
748
1165
  const component = getModeComponent(
749
1166
  mode,
750
1167
  cv({
751
1168
  variants: { size: { sm: "sm", lg: "lg" } },
752
- computed: ({ variants, addClass, addStyle }) => {
1169
+ refine: ({ variants, addClass, addStyle }) => {
753
1170
  if (variants.size === "lg") {
754
1171
  addClass("added-lg");
755
1172
  addStyle({ fontSize: "20px" });
@@ -769,12 +1186,12 @@ for (const config of Object.values(CONFIGS)) {
769
1186
  });
770
1187
  });
771
1188
 
772
- test("computed addClass multiple calls", () => {
1189
+ test("refine addClass multiple calls", () => {
773
1190
  const component = getModeComponent(
774
1191
  mode,
775
1192
  cv({
776
1193
  variants: { size: { sm: "sm", lg: "lg" } },
777
- computed: ({ variants, addClass }) => {
1194
+ refine: ({ variants, addClass }) => {
778
1195
  if (variants.size === "lg") {
779
1196
  addClass("first");
780
1197
  addClass("second");
@@ -789,12 +1206,12 @@ for (const config of Object.values(CONFIGS)) {
789
1206
  });
790
1207
  });
791
1208
 
792
- test("computed addStyle multiple calls merges styles", () => {
1209
+ test("refine addStyle multiple calls merges styles", () => {
793
1210
  const component = getModeComponent(
794
1211
  mode,
795
1212
  cv({
796
1213
  variants: { size: { sm: "sm", lg: "lg" } },
797
- computed: ({ variants, addStyle }) => {
1214
+ refine: ({ variants, addStyle }) => {
798
1215
  if (variants.size === "lg") {
799
1216
  addStyle({ fontSize: "20px" });
800
1217
  addStyle({ backgroundColor: "red" });
@@ -812,12 +1229,12 @@ for (const config of Object.values(CONFIGS)) {
812
1229
  });
813
1230
  });
814
1231
 
815
- test("computed addStyle later call overrides earlier", () => {
1232
+ test("refine addStyle later call overrides earlier", () => {
816
1233
  const component = getModeComponent(
817
1234
  mode,
818
1235
  cv({
819
1236
  variants: { size: { sm: "sm", lg: "lg" } },
820
- computed: ({ variants, addStyle }) => {
1237
+ refine: ({ variants, addStyle }) => {
821
1238
  if (variants.size === "lg") {
822
1239
  addStyle({ fontSize: "16px" });
823
1240
  addStyle({ fontSize: "20px" });
@@ -832,12 +1249,12 @@ for (const config of Object.values(CONFIGS)) {
832
1249
  });
833
1250
  });
834
1251
 
835
- test("computed addStyle does not accept numbers", () => {
1252
+ test("refine addStyle does not accept numbers", () => {
836
1253
  const component = getModeComponent(
837
1254
  mode,
838
1255
  cv({
839
1256
  variants: { size: { sm: "sm", lg: "lg" } },
840
- computed: ({ variants, addStyle }) => {
1257
+ refine: ({ variants, addStyle }) => {
841
1258
  if (variants.size === "lg") {
842
1259
  addStyle({
843
1260
  // @ts-expect-error