clava 0.4.2 → 0.6.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.
@@ -71,7 +71,7 @@ for (const config of Object.values(CONFIGS)) {
71
71
  });
72
72
  });
73
73
 
74
- test("refine re-runs when setDefaultVariants changes variants", () => {
74
+ test("refine re-runs when computed defaultVariants change variants", () => {
75
75
  const component = getModeComponent(
76
76
  mode,
77
77
  cv({
@@ -79,11 +79,12 @@ for (const config of Object.values(CONFIGS)) {
79
79
  size: { sm: "sm", lg: "lg" },
80
80
  color: { red: "red", blue: "blue" },
81
81
  },
82
- refine: ({ variants, setDefaultVariants, addClass }) => {
83
- setDefaultVariants({ color: "red" });
84
- if (variants.color === "red") {
85
- setDefaultVariants({ size: "lg" });
86
- }
82
+ defaultVariants: {
83
+ color: () => "red" as const,
84
+ size: (defaultValue, variants) =>
85
+ variants.color === "red" ? "lg" : defaultValue,
86
+ },
87
+ refine: ({ variants, addClass }) => {
87
88
  if (variants.size === "lg") {
88
89
  addClass("refine-lg");
89
90
  }
@@ -96,16 +97,18 @@ for (const config of Object.values(CONFIGS)) {
96
97
  });
97
98
  });
98
99
 
99
- test("refine converges with NaN setDefaultVariants", () => {
100
- const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
100
+ test("refine converges with NaN computed defaultVariants", () => {
101
+ using warn = vi.spyOn(console, "warn").mockImplementation(() => {});
101
102
  const component = getModeComponent(
102
103
  mode,
103
104
  cv({
104
105
  variants: {
105
106
  value: (value: number) => (Number.isNaN(value) ? "nan" : null),
106
107
  },
107
- refine: ({ variants, setDefaultVariants, addClass }) => {
108
- setDefaultVariants({ value: Number.NaN });
108
+ defaultVariants: {
109
+ value: () => Number.NaN,
110
+ },
111
+ refine: ({ variants, addClass }) => {
109
112
  if (Number.isNaN(variants.value)) {
110
113
  addClass("refine-nan");
111
114
  }
@@ -113,20 +116,16 @@ for (const config of Object.values(CONFIGS)) {
113
116
  }),
114
117
  );
115
118
 
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
- }
119
+ const props = component();
120
+ expect(getStyleClass(props)).toEqual({
121
+ class: cls("nan refine-nan"),
122
+ });
123
+ expect(component.getVariants()).toEqual({ value: Number.NaN });
124
+ expect(warn).not.toHaveBeenCalled();
126
125
  });
127
126
 
128
- test("refine converges with undefined setDefaultVariants", () => {
129
- const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
127
+ test("computed defaultVariants can clear inherited defaults", () => {
128
+ using warn = vi.spyOn(console, "warn").mockImplementation(() => {});
130
129
  const base = cv({
131
130
  variants: {
132
131
  invert: { true: "invert" },
@@ -135,32 +134,25 @@ for (const config of Object.values(CONFIGS)) {
135
134
  push: (value: number | undefined) =>
136
135
  value === undefined ? undefined : `push-${value}`,
137
136
  },
138
- refine: ({ variants, setDefaultVariants }) => {
139
- setDefaultVariants({
140
- offset: !variants.invert,
141
- push: variants.invert ? 20 : undefined,
142
- });
137
+ defaultVariants: {
138
+ offset: (_, variants) => !variants.invert,
139
+ push: (_, variants) => (variants.invert ? 20 : undefined),
143
140
  },
144
141
  });
145
142
  const component = getModeComponent(mode, cv({ extend: [base] }));
146
143
 
147
- try {
148
- const props = component();
149
- expect(getStyleClass(props)).toEqual({
150
- class: cls("offset"),
151
- });
152
- expect(component.getVariants()).toEqual({
153
- offset: true,
154
- push: undefined,
155
- });
156
- expect(warn).not.toHaveBeenCalled();
157
- } finally {
158
- warn.mockRestore();
159
- }
144
+ const props = component();
145
+ expect(getStyleClass(props)).toEqual({
146
+ class: cls("offset"),
147
+ });
148
+ expect(component.getVariants()).toEqual({
149
+ offset: true,
150
+ });
151
+ expect(warn).not.toHaveBeenCalled();
160
152
  });
161
153
 
162
- test("refine converges with undefined setDefaultVariants in nested extends", () => {
163
- const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
154
+ test("computed defaultVariants use inherited defaults as defaultValue", () => {
155
+ using warn = vi.spyOn(console, "warn").mockImplementation(() => {});
164
156
  const calls = {
165
157
  layer: 0,
166
158
  frame: 0,
@@ -177,13 +169,11 @@ for (const config of Object.values(CONFIGS)) {
177
169
  },
178
170
  defaultVariants: {
179
171
  layer: true,
180
- },
181
- refine: ({ variants, setDefaultVariants }) => {
182
- calls.layer += 1;
183
- setDefaultVariants({
184
- offset: !variants.invert,
185
- push: variants.invert ? 20 : undefined,
186
- });
172
+ offset: (defaultValue, variants) => {
173
+ calls.layer += 1;
174
+ return variants.invert ? false : defaultValue;
175
+ },
176
+ push: (_, variants) => (variants.invert ? 20 : undefined),
187
177
  },
188
178
  });
189
179
  const frame = cv({
@@ -213,38 +203,23 @@ for (const config of Object.values(CONFIGS)) {
213
203
  });
214
204
  const component = getModeComponent(mode, cv({ extend: [control] }));
215
205
 
216
- try {
217
- const props = component();
218
- expect(getStyleClass(props)).toEqual({
219
- class: cls("layer offset frame control"),
220
- });
221
- expect(calls).toEqual({
222
- layer: 2,
223
- frame: 2,
224
- control: 2,
225
- });
226
- calls.layer = 0;
227
- calls.frame = 0;
228
- calls.control = 0;
229
- expect(component.getVariants()).toEqual({
230
- layer: true,
231
- offset: true,
232
- push: undefined,
233
- frame: true,
234
- control: true,
235
- });
236
- expect(calls).toEqual({
237
- layer: 2,
238
- frame: 2,
239
- control: 2,
240
- });
241
- expect(warn).not.toHaveBeenCalled();
242
- } finally {
243
- warn.mockRestore();
244
- }
206
+ const props = component();
207
+ expect(getStyleClass(props)).toEqual({
208
+ class: cls("layer offset frame control"),
209
+ });
210
+ expect(component.getVariants()).toEqual({
211
+ layer: true,
212
+ offset: true,
213
+ frame: true,
214
+ control: true,
215
+ });
216
+ expect(calls.layer).toBeGreaterThan(0);
217
+ expect(calls.frame).toBeGreaterThan(0);
218
+ expect(calls.control).toBeGreaterThan(0);
219
+ expect(warn).not.toHaveBeenCalled();
245
220
  });
246
221
 
247
- test("refine with setDefaultVariants", () => {
222
+ test("computed defaultVariants", () => {
248
223
  const component = getModeComponent(
249
224
  mode,
250
225
  cv({
@@ -252,10 +227,9 @@ for (const config of Object.values(CONFIGS)) {
252
227
  size: { sm: "sm", lg: "lg" },
253
228
  color: { red: "red", blue: "blue" },
254
229
  },
255
- refine: ({ variants, setDefaultVariants }) => {
256
- if (variants.size === "lg") {
257
- setDefaultVariants({ color: "red" });
258
- }
230
+ defaultVariants: {
231
+ color: (defaultValue, variants) =>
232
+ variants.size === "lg" ? "red" : defaultValue,
259
233
  },
260
234
  }),
261
235
  );
@@ -263,7 +237,7 @@ for (const config of Object.values(CONFIGS)) {
263
237
  expect(getStyleClass(props)).toEqual({ class: cls("lg red") });
264
238
  });
265
239
 
266
- test("refine setDefaultVariants does not override props", () => {
240
+ test("computed defaultVariants do not override props", () => {
267
241
  const component = getModeComponent(
268
242
  mode,
269
243
  cv({
@@ -271,8 +245,8 @@ for (const config of Object.values(CONFIGS)) {
271
245
  size: { sm: "sm", lg: "lg" },
272
246
  color: { red: "red", blue: "blue" },
273
247
  },
274
- refine: ({ setDefaultVariants }) => {
275
- setDefaultVariants({ color: "red" });
248
+ defaultVariants: {
249
+ color: () => "red" as const,
276
250
  },
277
251
  }),
278
252
  );
@@ -280,25 +254,7 @@ for (const config of Object.values(CONFIGS)) {
280
254
  expect(getStyleClass(props)).toEqual({ class: cls("lg blue") });
281
255
  });
282
256
 
283
- test("refine setDefaultVariants overrides defaultVariants", () => {
284
- const component = getModeComponent(
285
- mode,
286
- cv({
287
- variants: {
288
- size: { sm: "sm", lg: "lg" },
289
- color: { red: "red", blue: "blue" },
290
- },
291
- defaultVariants: { size: "sm", color: "red" },
292
- refine: ({ setDefaultVariants }) => {
293
- setDefaultVariants({ color: "blue" });
294
- },
295
- }),
296
- );
297
- const props = component();
298
- expect(getStyleClass(props)).toEqual({ class: cls("sm blue") });
299
- });
300
-
301
- test("refine setDefaultVariants overrides extended defaultVariants", () => {
257
+ test("computed defaultVariants override extended defaultVariants", () => {
302
258
  const base = cv({
303
259
  variants: { color: { red: "red", blue: "blue" } },
304
260
  defaultVariants: { color: "red" },
@@ -308,9 +264,9 @@ for (const config of Object.values(CONFIGS)) {
308
264
  cv({
309
265
  extend: [base],
310
266
  variants: { size: { sm: "sm", lg: "lg" } },
311
- defaultVariants: { size: "sm" },
312
- refine: ({ setDefaultVariants }) => {
313
- setDefaultVariants({ color: "blue" });
267
+ defaultVariants: {
268
+ size: "sm",
269
+ color: () => "blue" as const,
314
270
  },
315
271
  }),
316
272
  );
@@ -318,31 +274,11 @@ for (const config of Object.values(CONFIGS)) {
318
274
  expect(getStyleClass(props)).toEqual({ class: cls("blue sm") });
319
275
  });
320
276
 
321
- test("refine setDefaultVariants overrides child defaultVariants", () => {
277
+ test("parent computed defaultVariants can override child defaultVariants", () => {
322
278
  const base = cv({
323
279
  variants: { size: { sm: "sm", lg: "lg" } },
324
- });
325
- const component = getModeComponent(
326
- mode,
327
- cv({
328
- extend: [base],
329
- variants: { color: { red: "red", blue: "blue" } },
330
- defaultVariants: { size: "sm", color: "red" },
331
- refine: ({ setDefaultVariants }) => {
332
- setDefaultVariants({ size: "lg" });
333
- },
334
- }),
335
- );
336
- const props = component();
337
- expect(getStyleClass(props)).toEqual({ class: cls("lg red") });
338
- });
339
-
340
- test("refine setDefaultVariants from parent overrides child defaultVariants", () => {
341
- const base = cv({
342
- variants: { size: { sm: "sm", lg: "lg" } },
343
- defaultVariants: { size: "sm" },
344
- refine: ({ setDefaultVariants }) => {
345
- setDefaultVariants({ size: "lg" });
280
+ defaultVariants: {
281
+ size: () => "lg" as const,
346
282
  },
347
283
  });
348
284
  const component = getModeComponent(
@@ -357,13 +293,12 @@ for (const config of Object.values(CONFIGS)) {
357
293
  expect(getStyleClass(props)).toEqual({ class: cls("lg red") });
358
294
  });
359
295
 
360
- test("refine setDefaultVariants from parent overrides child defaultVariants based on props", () => {
296
+ test("parent computed defaultVariants can depend on child defaults", () => {
361
297
  const base = cv({
362
- variants: { size: { sm: "sm", lg: "lg" }, enabled: "" },
363
- defaultVariants: { size: "sm" },
364
- refine: ({ variants, setDefaultVariants }) => {
365
- if (!variants.enabled) return;
366
- setDefaultVariants({ size: "lg" });
298
+ variants: { size: { sm: "sm", lg: "lg" }, large: "" },
299
+ defaultVariants: {
300
+ size: (defaultValue, variants) =>
301
+ variants.large ? "lg" : defaultValue,
367
302
  },
368
303
  });
369
304
  const component = getModeComponent(
@@ -371,97 +306,68 @@ for (const config of Object.values(CONFIGS)) {
371
306
  cv({
372
307
  extend: [base],
373
308
  variants: { color: { red: "red", blue: "blue" } },
374
- defaultVariants: { size: "sm", color: "red" },
309
+ defaultVariants: { size: "sm", color: "red", large: true },
375
310
  }),
376
311
  );
377
- const props = component({ enabled: true });
312
+ const props = component();
378
313
  expect(getStyleClass(props)).toEqual({ class: cls("lg red") });
379
314
  });
380
315
 
381
- test("refine receives default variants from child", () => {
382
- const base = cv({
383
- variants: { size: { sm: "sm", lg: "lg" }, large: "" },
384
- defaultVariants: { size: "sm" },
385
- refine: ({ variants, setDefaultVariants }) => {
386
- if (variants.large) {
387
- setDefaultVariants({ size: "lg" });
388
- }
316
+ test("computed defaultVariants preserve intermediate defaults", () => {
317
+ const parent = cv({
318
+ variants: { size: { sm: "sm", lg: "lg" } },
319
+ defaultVariants: {
320
+ size: (defaultValue, variants) =>
321
+ variants.size ? defaultValue : "lg",
389
322
  },
390
323
  });
391
- const component = getModeComponent(
392
- mode,
393
- cv({
394
- extend: [base],
395
- variants: { color: { red: "red", blue: "blue" } },
396
- defaultVariants: { size: "sm", color: "red", large: true },
397
- }),
398
- );
324
+ const child = cv({ extend: [parent], defaultVariants: { size: "sm" } });
325
+ const component = getModeComponent(mode, cv({ extend: [child] }));
399
326
  const props = component();
400
- expect(getStyleClass(props)).toEqual({ class: cls("lg red") });
327
+ expect(getStyleClass(props)).toEqual({ class: cls("sm") });
401
328
  });
402
329
 
403
- test("refine receives default variants from grandchild", () => {
330
+ test("child computed defaultVariants override parent computed defaultVariants", () => {
404
331
  const base = cv({
405
- variants: { size: { sm: "sm", lg: "lg" }, large: "" },
406
- defaultVariants: { size: "sm" },
407
- refine: ({ variants, setDefaultVariants }) => {
408
- if (variants.large) {
409
- setDefaultVariants({ size: "lg" });
410
- }
332
+ variants: { size: { sm: "sm", lg: "lg" } },
333
+ defaultVariants: {
334
+ size: () => "lg" as const,
411
335
  },
412
336
  });
413
- const base2 = cv({ extend: [base] });
414
337
  const component = getModeComponent(
415
338
  mode,
416
339
  cv({
417
- extend: [base2],
340
+ extend: [base],
418
341
  variants: { color: { red: "red", blue: "blue" } },
419
- defaultVariants: { size: "sm", color: "red", large: true },
342
+ defaultVariants: {
343
+ size: () => "sm" as const,
344
+ color: "red",
345
+ },
420
346
  }),
421
347
  );
422
348
  const props = component();
423
- expect(getStyleClass(props)).toEqual({ class: cls("lg red") });
424
- });
425
-
426
- test("refine receives default variants from intermediate component", () => {
427
- const parent = cv({
428
- variants: { size: { sm: "sm", lg: "lg" } },
429
- refine: ({ variants, setDefaultVariants }) => {
430
- if (!variants.size) {
431
- setDefaultVariants({ size: "lg" });
432
- }
433
- },
434
- });
435
- const child = cv({ extend: [parent], defaultVariants: { size: "sm" } });
436
- const component = getModeComponent(mode, cv({ extend: [child] }));
437
- const props = component();
438
- expect(getStyleClass(props)).toEqual({ class: cls("sm") });
349
+ expect(getStyleClass(props)).toEqual({ class: cls("sm red") });
439
350
  });
440
351
 
441
- test("child refine setDefaultVariants overrides parent refine setDefaultVariants", () => {
352
+ test("child computed defaultVariants can preserve parent computed defaultVariants", () => {
442
353
  const base = cv({
443
354
  variants: { size: { sm: "sm", lg: "lg" } },
444
- defaultVariants: { size: "sm" },
445
- refine: ({ setDefaultVariants }) => {
446
- setDefaultVariants({ size: "lg" });
355
+ defaultVariants: {
356
+ size: () => "lg" as const,
447
357
  },
448
358
  });
449
359
  const component = getModeComponent(
450
360
  mode,
451
361
  cv({
452
362
  extend: [base],
453
- variants: { color: { red: "red", blue: "blue" } },
454
- defaultVariants: { size: "sm", color: "red" },
455
- refine: ({ setDefaultVariants }) => {
456
- setDefaultVariants({ size: "sm" });
363
+ defaultVariants: {
364
+ size: (defaultValue) => defaultValue,
457
365
  },
458
366
  }),
459
367
  );
460
368
  const props = component();
461
- // Order: parent defaultVariants (sm) -> child defaultVariants (sm)
462
- // -> parent refine.setDefaultVariants (lg)
463
- // -> child refine.setDefaultVariants (sm)
464
- expect(getStyleClass(props)).toEqual({ class: cls("sm red") });
369
+ expect(getStyleClass(props)).toEqual({ class: cls("lg") });
370
+ expect(component.getVariants()).toEqual({ size: "lg" });
465
371
  });
466
372
 
467
373
  test("refine in extended component does not see foreign variant keys", () => {
@@ -484,6 +390,7 @@ for (const config of Object.values(CONFIGS)) {
484
390
  cv({
485
391
  extend: [base],
486
392
  variants: { color: { red: "red", blue: "blue" } },
393
+ defaultVariants: { size: "sm" },
487
394
  }),
488
395
  );
489
396
  const props = component({ color: "red" });
@@ -492,18 +399,14 @@ for (const config of Object.values(CONFIGS)) {
492
399
  });
493
400
  });
494
401
 
495
- test("setDefaultVariants in extended component does not branch on foreign variant keys", () => {
496
- // Same shape as the test above, but the base's `refine` reaches
497
- // `setDefaultVariants` — covers the resolveDefaults pass (driving
498
- // both class output and `getVariants`) rather than the render-time
499
- // compute path.
402
+ test("computed defaultVariants in extended component do not branch on foreign variant keys", () => {
500
403
  const base = cv({
501
404
  variants: { size: { sm: "sm", lg: "lg" } },
502
- defaultVariants: { size: "sm" },
503
- refine: ({ variants, setDefaultVariants }) => {
504
- if ("color" in (variants as Record<string, unknown>)) {
505
- setDefaultVariants({ size: "lg" });
506
- }
405
+ defaultVariants: {
406
+ size: (defaultValue, variants) =>
407
+ "color" in (variants as Record<string, unknown>)
408
+ ? "lg"
409
+ : defaultValue,
507
410
  },
508
411
  });
509
412
  const component = getModeComponent(
@@ -511,6 +414,7 @@ for (const config of Object.values(CONFIGS)) {
511
414
  cv({
512
415
  extend: [base],
513
416
  variants: { color: { red: "red", blue: "blue" } },
417
+ defaultVariants: { size: "sm" },
514
418
  }),
515
419
  );
516
420
  const props = component({ color: "red" });
@@ -521,11 +425,11 @@ for (const config of Object.values(CONFIGS)) {
521
425
  });
522
426
  });
523
427
 
524
- test("child setDefaultVariants receives refined variants from parent", () => {
428
+ test("child computed defaultVariants receive refined variants from parent", () => {
525
429
  const base = cv({
526
430
  variants: { size: { sm: "sm", lg: "lg" }, small: "" },
527
- refine: ({ setDefaultVariants }) => {
528
- setDefaultVariants({ small: true });
431
+ refine: ({ setVariants }) => {
432
+ setVariants({ small: true });
529
433
  },
530
434
  });
531
435
  const component = getModeComponent(
@@ -533,11 +437,10 @@ for (const config of Object.values(CONFIGS)) {
533
437
  cv({
534
438
  extend: [base],
535
439
  variants: { color: { red: "red", blue: "blue" } },
536
- defaultVariants: { size: "lg", color: "red" },
537
- refine: ({ variants, setDefaultVariants }) => {
538
- if (variants.small) {
539
- setDefaultVariants({ size: "sm" });
540
- }
440
+ defaultVariants: {
441
+ size: (defaultValue, variants) =>
442
+ variants.small ? "sm" : defaultValue,
443
+ color: "red",
541
444
  },
542
445
  }),
543
446
  );
@@ -571,21 +474,21 @@ for (const config of Object.values(CONFIGS)) {
571
474
  expect(getStyleClass(props)).toEqual({ class: cls("lg red") });
572
475
  });
573
476
 
574
- test("base refine setDefaultVariants works after its own setVariants re-run", () => {
477
+ test("computed defaultVariants work after a setVariants re-run", () => {
575
478
  const base = cv({
576
479
  variants: {
577
480
  size: { sm: "sm", lg: "lg" },
578
481
  active: "",
579
482
  mode: { on: "on" },
580
483
  },
581
- defaultVariants: { size: "sm" },
582
- refine: ({ variants, setVariants, setDefaultVariants }) => {
484
+ defaultVariants: {
485
+ size: (defaultValue, variants) =>
486
+ variants.mode === "on" ? "lg" : defaultValue,
487
+ },
488
+ refine: ({ variants, setVariants }) => {
583
489
  if (variants.active) {
584
490
  setVariants({ mode: "on" });
585
491
  }
586
- if (variants.mode === "on") {
587
- setDefaultVariants({ size: "lg" });
588
- }
589
492
  },
590
493
  });
591
494
  const component = getModeComponent(mode, cv({ extend: [base] }));
@@ -635,11 +538,11 @@ for (const config of Object.values(CONFIGS)) {
635
538
  expect(getStyleClass(getProps())).toEqual({ class: cls("red") });
636
539
  });
637
540
 
638
- test("child setVariants keeps overriding base setDefaultVariants across re-runs", () => {
541
+ test("child setVariants keeps overriding base computed defaultVariants across re-runs", () => {
639
542
  const base = cv({
640
543
  variants: { color: { red: "red", blue: "blue" } },
641
- refine: ({ setDefaultVariants }) => {
642
- setDefaultVariants({ color: "blue" });
544
+ defaultVariants: {
545
+ color: () => "blue" as const,
643
546
  },
644
547
  });
645
548
  const component = getModeComponent(
@@ -659,11 +562,11 @@ for (const config of Object.values(CONFIGS)) {
659
562
  expect(getStyleClass(props)).toEqual({ class: cls("red sm") });
660
563
  });
661
564
 
662
- test("refine setVariants sticks across re-runs", () => {
565
+ test("refine setVariants sticks across computed default re-runs", () => {
663
566
  const base = cv({
664
567
  variants: { color: { red: "red", blue: "blue" } },
665
- refine: ({ setDefaultVariants }) => {
666
- setDefaultVariants({ color: "blue" });
568
+ defaultVariants: {
569
+ color: () => "blue" as const,
667
570
  },
668
571
  });
669
572
  const component = getModeComponent(
@@ -682,20 +585,21 @@ for (const config of Object.values(CONFIGS)) {
682
585
  expect(getStyleClass(props)).toEqual({ class: cls("red") });
683
586
  });
684
587
 
685
- test("base refine setDefaultVariants can override child static defaults after a re-run", () => {
588
+ test("base computed defaultVariants can override child static defaults after a re-run", () => {
686
589
  const base = cv({
687
590
  variants: {
688
591
  size: { sm: "sm", lg: "lg" },
689
592
  active: "",
690
593
  mode: { on: "on" },
691
594
  },
692
- refine: ({ variants, setVariants, setDefaultVariants }) => {
595
+ defaultVariants: {
596
+ size: (defaultValue, variants) =>
597
+ variants.mode === "on" ? "lg" : defaultValue,
598
+ },
599
+ refine: ({ variants, setVariants }) => {
693
600
  if (variants.active) {
694
601
  setVariants({ mode: "on" });
695
602
  }
696
- if (variants.mode === "on") {
697
- setDefaultVariants({ size: "lg" });
698
- }
699
603
  },
700
604
  });
701
605
  const component = getModeComponent(
@@ -706,7 +610,32 @@ for (const config of Object.values(CONFIGS)) {
706
610
  expect(getStyleClass(props)).toEqual({ class: cls("lg on") });
707
611
  });
708
612
 
709
- test("setVariants from earlier extends overrides setDefaultVariants from later extends", () => {
613
+ test("parent computed defaultVariants fall back to child defaults after a dependency changes", () => {
614
+ const layer = cv({
615
+ variants: {
616
+ a: { one: "one", two: "two" },
617
+ b: { true: "b-true", false: "b-false" },
618
+ },
619
+ defaultVariants: {
620
+ b: true,
621
+ a: (defaultValue, variants) => (variants.b ? "one" : defaultValue),
622
+ },
623
+ refine: ({ variants, setVariants }) => {
624
+ if (variants.b) {
625
+ setVariants({ b: false });
626
+ }
627
+ },
628
+ });
629
+ const component = getModeComponent(
630
+ mode,
631
+ cv({ extend: [layer], defaultVariants: { a: "two" } }),
632
+ );
633
+ const props = component();
634
+ expect(getStyleClass(props)).toEqual({ class: cls("two b-false") });
635
+ expect(component.getVariants()).toEqual({ a: "two", b: false });
636
+ });
637
+
638
+ test("setVariants from earlier extends overrides computed defaultVariants from later extends", () => {
710
639
  const first = cv({
711
640
  variants: { color: { red: "first-red", blue: "first-blue" } },
712
641
  refine: ({ setVariants }) => {
@@ -715,8 +644,8 @@ for (const config of Object.values(CONFIGS)) {
715
644
  });
716
645
  const second = cv({
717
646
  variants: { color: { red: "second-red", blue: "second-blue" } },
718
- refine: ({ setDefaultVariants }) => {
719
- setDefaultVariants({ color: "blue" });
647
+ defaultVariants: {
648
+ color: () => "blue" as const,
720
649
  },
721
650
  });
722
651
  const component = getModeComponent(mode, cv({ extend: [first, second] }));
@@ -726,17 +655,17 @@ for (const config of Object.values(CONFIGS)) {
726
655
  });
727
656
  });
728
657
 
729
- test("setDefaultVariants from later extends overrides setDefaultVariants from earlier extends", () => {
658
+ test("computed defaultVariants from later extends apply to final output", () => {
730
659
  const first = cv({
731
660
  variants: { color: { red: "first-red", blue: "first-blue" } },
732
- refine: ({ setDefaultVariants }) => {
733
- setDefaultVariants({ color: "red" });
661
+ defaultVariants: {
662
+ color: () => "red" as const,
734
663
  },
735
664
  });
736
665
  const second = cv({
737
666
  variants: { color: { red: "second-red", blue: "second-blue" } },
738
- refine: ({ setDefaultVariants }) => {
739
- setDefaultVariants({ color: "blue" });
667
+ defaultVariants: {
668
+ color: () => "blue" as const,
740
669
  },
741
670
  });
742
671
  const component = getModeComponent(mode, cv({ extend: [first, second] }));
@@ -746,7 +675,7 @@ for (const config of Object.values(CONFIGS)) {
746
675
  });
747
676
  });
748
677
 
749
- test("setDefaultVariants does not override stable setVariants on later passes", () => {
678
+ test("computed defaultVariants do not override stable setVariants on later passes", () => {
750
679
  const base = cv({
751
680
  variants: { color: { red: "base-red", blue: "base-blue" } },
752
681
  refine: ({ setVariants }) => {
@@ -758,10 +687,9 @@ for (const config of Object.values(CONFIGS)) {
758
687
  cv({
759
688
  extend: [base],
760
689
  variants: { color: { red: "child-red", blue: "child-blue" } },
761
- refine: ({ variants, setDefaultVariants }) => {
762
- if (variants.color === "red") {
763
- setDefaultVariants({ color: "blue" });
764
- }
690
+ defaultVariants: {
691
+ color: (defaultValue, variants) =>
692
+ variants.color === "red" ? "blue" : defaultValue,
765
693
  },
766
694
  }),
767
695
  );
@@ -771,7 +699,7 @@ for (const config of Object.values(CONFIGS)) {
771
699
  });
772
700
  });
773
701
 
774
- test("setDefaultVariants does not override setVariants from a previous pass", () => {
702
+ test("computed defaultVariants do not override setVariants from a previous pass", () => {
775
703
  const component = getModeComponent(
776
704
  mode,
777
705
  cv({
@@ -779,13 +707,14 @@ for (const config of Object.values(CONFIGS)) {
779
707
  color: { red: "red", blue: "blue" },
780
708
  done: "",
781
709
  },
782
- refine: ({ variants, setVariants, setDefaultVariants }) => {
710
+ defaultVariants: {
711
+ color: (defaultValue, variants) =>
712
+ variants.done ? "blue" : defaultValue,
713
+ },
714
+ refine: ({ variants, setVariants }) => {
783
715
  if (!variants.done) {
784
716
  setVariants({ color: "red", done: true });
785
717
  }
786
- if (variants.done) {
787
- setDefaultVariants({ color: "blue" });
788
- }
789
718
  },
790
719
  }),
791
720
  );
@@ -794,7 +723,7 @@ for (const config of Object.values(CONFIGS)) {
794
723
  });
795
724
 
796
725
  test("refine warns when variants keep changing", () => {
797
- const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
726
+ using warn = vi.spyOn(console, "warn").mockImplementation(() => {});
798
727
  const component = getModeComponent(
799
728
  mode,
800
729
  cv({
@@ -806,18 +735,14 @@ for (const config of Object.values(CONFIGS)) {
806
735
  }),
807
736
  );
808
737
 
809
- try {
810
- component();
811
- expect(warn).toHaveBeenCalledWith(
812
- expect.stringContaining("Maximum refine iterations exceeded"),
813
- );
814
- } finally {
815
- warn.mockRestore();
816
- }
738
+ component();
739
+ expect(warn).toHaveBeenCalledWith(
740
+ expect.stringContaining("Maximum refine iterations exceeded"),
741
+ );
817
742
  });
818
743
 
819
744
  test("refine warning is shared across extended components", () => {
820
- const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
745
+ using warn = vi.spyOn(console, "warn").mockImplementation(() => {});
821
746
  const base = cv({
822
747
  variants: { size: { sm: "sm", lg: "lg" } },
823
748
  defaultVariants: { size: "sm" },
@@ -827,32 +752,28 @@ for (const config of Object.values(CONFIGS)) {
827
752
  });
828
753
  const component = getModeComponent(mode, cv({ extend: [base] }));
829
754
 
830
- try {
831
- component();
832
- expect(warn).toHaveBeenCalledTimes(1);
833
- expect(warn).toHaveBeenCalledWith(
834
- expect.stringContaining("Maximum refine iterations exceeded"),
835
- );
836
- expect(warn).toHaveBeenCalledWith(
837
- expect.stringMatching(
838
- /Variant\(s\) that did not stabilize: [^\n]*\bsize\b/,
839
- ),
840
- );
841
- expect(warn).toHaveBeenCalledWith(
842
- expect.stringMatching(
843
- /Latest variant changes before warning: [^\n]*\bsize: "(sm|lg)" -> "(sm|lg)"/,
844
- ),
845
- );
846
- expect(warn).toHaveBeenCalledWith(
847
- expect.stringContaining("Component created at:"),
848
- );
849
- } finally {
850
- warn.mockRestore();
851
- }
755
+ component();
756
+ expect(warn).toHaveBeenCalledTimes(1);
757
+ expect(warn).toHaveBeenCalledWith(
758
+ expect.stringContaining("Maximum refine iterations exceeded"),
759
+ );
760
+ expect(warn).toHaveBeenCalledWith(
761
+ expect.stringMatching(
762
+ /Variant\(s\) that did not stabilize: [^\n]*\bsize\b/,
763
+ ),
764
+ );
765
+ expect(warn).toHaveBeenCalledWith(
766
+ expect.stringMatching(
767
+ /Latest variant changes before warning: [^\n]*\bsize: "(sm|lg)" -> "(sm|lg)"/,
768
+ ),
769
+ );
770
+ expect(warn).toHaveBeenCalledWith(
771
+ expect.stringContaining("Component created at:"),
772
+ );
852
773
  });
853
774
 
854
775
  test("getVariants warns when variants keep changing", () => {
855
- const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
776
+ using warn = vi.spyOn(console, "warn").mockImplementation(() => {});
856
777
  const component = getModeComponent(
857
778
  mode,
858
779
  cv({
@@ -864,31 +785,27 @@ for (const config of Object.values(CONFIGS)) {
864
785
  }),
865
786
  );
866
787
 
867
- try {
868
- component.getVariants();
869
- expect(warn).toHaveBeenCalledWith(
870
- expect.stringContaining("Maximum refine iterations exceeded"),
871
- );
872
- expect(warn).toHaveBeenCalledWith(
873
- expect.stringMatching(
874
- /Variant\(s\) that did not stabilize: [^\n]*\bsize\b/,
875
- ),
876
- );
877
- expect(warn).toHaveBeenCalledWith(
878
- expect.stringMatching(
879
- /Latest variant changes before warning: [^\n]*\bsize: "(sm|lg)" -> "(sm|lg)"/,
880
- ),
881
- );
882
- expect(warn).toHaveBeenCalledWith(
883
- expect.stringContaining("Component created at:"),
884
- );
885
- } finally {
886
- warn.mockRestore();
887
- }
788
+ component.getVariants();
789
+ expect(warn).toHaveBeenCalledWith(
790
+ expect.stringContaining("Maximum refine iterations exceeded"),
791
+ );
792
+ expect(warn).toHaveBeenCalledWith(
793
+ expect.stringMatching(
794
+ /Variant\(s\) that did not stabilize: [^\n]*\bsize\b/,
795
+ ),
796
+ );
797
+ expect(warn).toHaveBeenCalledWith(
798
+ expect.stringMatching(
799
+ /Latest variant changes before warning: [^\n]*\bsize: "(sm|lg)" -> "(sm|lg)"/,
800
+ ),
801
+ );
802
+ expect(warn).toHaveBeenCalledWith(
803
+ expect.stringContaining("Component created at:"),
804
+ );
888
805
  });
889
806
 
890
807
  test("refine warning is omitted in production", () => {
891
- const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
808
+ using warn = vi.spyOn(console, "warn").mockImplementation(() => {});
892
809
  vi.stubEnv("NODE_ENV", "production");
893
810
  const component = getModeComponent(
894
811
  mode,
@@ -906,12 +823,11 @@ for (const config of Object.values(CONFIGS)) {
906
823
  expect(warn).not.toHaveBeenCalled();
907
824
  } finally {
908
825
  vi.unstubAllEnvs();
909
- warn.mockRestore();
910
826
  }
911
827
  });
912
828
 
913
829
  test("refine warning names the variant key that did not stabilize", () => {
914
- const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
830
+ using warn = vi.spyOn(console, "warn").mockImplementation(() => {});
915
831
  const component = getModeComponent(
916
832
  mode,
917
833
  cv({
@@ -926,25 +842,21 @@ for (const config of Object.values(CONFIGS)) {
926
842
  }),
927
843
  );
928
844
 
929
- try {
930
- component();
931
- expect(warn).toHaveBeenCalledWith(
932
- expect.stringMatching(
933
- /Variant\(s\) that did not stabilize: [^\n]*\bsize\b/,
934
- ),
935
- );
936
- expect(warn).toHaveBeenCalledWith(
937
- expect.not.stringMatching(
938
- /Variant\(s\) that did not stabilize: [^\n]*\bcolor\b/,
939
- ),
940
- );
941
- } finally {
942
- warn.mockRestore();
943
- }
845
+ component();
846
+ expect(warn).toHaveBeenCalledWith(
847
+ expect.stringMatching(
848
+ /Variant\(s\) that did not stabilize: [^\n]*\bsize\b/,
849
+ ),
850
+ );
851
+ expect(warn).toHaveBeenCalledWith(
852
+ expect.not.stringMatching(
853
+ /Variant\(s\) that did not stabilize: [^\n]*\bcolor\b/,
854
+ ),
855
+ );
944
856
  });
945
857
 
946
858
  test("refine warning reports keys that oscillate at different cadences", () => {
947
- const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
859
+ using warn = vi.spyOn(console, "warn").mockImplementation(() => {});
948
860
  // `size` flips every iteration, `color` flips every other iteration, so
949
861
  // the final pair may agree on one of them — the warning must still name
950
862
  // both keys because each contributed to a transition.
@@ -970,30 +882,26 @@ for (const config of Object.values(CONFIGS)) {
970
882
  }),
971
883
  );
972
884
 
973
- try {
974
- component();
975
- expect(warn).toHaveBeenCalledWith(
976
- expect.stringMatching(
977
- /Variant\(s\) that did not stabilize: [^\n]*\bsize\b/,
978
- ),
979
- );
980
- expect(warn).toHaveBeenCalledWith(
981
- expect.stringMatching(
982
- /Variant\(s\) that did not stabilize: [^\n]*\bcolor\b/,
983
- ),
984
- );
985
- expect(warn).toHaveBeenCalledWith(
986
- expect.stringMatching(
987
- /Latest variant changes before warning: [^\n]*\bsize\b[^\n]*\bcolor\b/,
988
- ),
989
- );
990
- } finally {
991
- warn.mockRestore();
992
- }
885
+ component();
886
+ expect(warn).toHaveBeenCalledWith(
887
+ expect.stringMatching(
888
+ /Variant\(s\) that did not stabilize: [^\n]*\bsize\b/,
889
+ ),
890
+ );
891
+ expect(warn).toHaveBeenCalledWith(
892
+ expect.stringMatching(
893
+ /Variant\(s\) that did not stabilize: [^\n]*\bcolor\b/,
894
+ ),
895
+ );
896
+ expect(warn).toHaveBeenCalledWith(
897
+ expect.stringMatching(
898
+ /Latest variant changes before warning: [^\n]*\bsize\b[^\n]*\bcolor\b/,
899
+ ),
900
+ );
993
901
  });
994
902
 
995
903
  test("refine warning includes the component creation stack", () => {
996
- const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
904
+ using warn = vi.spyOn(console, "warn").mockImplementation(() => {});
997
905
  const component = getModeComponent(
998
906
  mode,
999
907
  cv({
@@ -1005,20 +913,16 @@ for (const config of Object.values(CONFIGS)) {
1005
913
  }),
1006
914
  );
1007
915
 
1008
- try {
1009
- component();
1010
- expect(warn).toHaveBeenCalledWith(
1011
- expect.stringContaining("Component created at:"),
1012
- );
1013
- expect(warn).toHaveBeenCalledWith(
1014
- expect.stringContaining("refine.test.ts"),
1015
- );
1016
- expect(warn).toHaveBeenCalledWith(
1017
- expect.not.stringContaining("node_modules"),
1018
- );
1019
- } finally {
1020
- warn.mockRestore();
1021
- }
916
+ component();
917
+ expect(warn).toHaveBeenCalledWith(
918
+ expect.stringContaining("Component created at:"),
919
+ );
920
+ expect(warn).toHaveBeenCalledWith(
921
+ expect.stringContaining("refine.test.ts"),
922
+ );
923
+ expect(warn).toHaveBeenCalledWith(
924
+ expect.not.stringContaining("node_modules"),
925
+ );
1022
926
  });
1023
927
 
1024
928
  test("refine warning fallback stack skips internal creation frames", () => {
@@ -1030,7 +934,7 @@ for (const config of Object.values(CONFIGS)) {
1030
934
  };
1031
935
  const captureStackTrace = ErrorWithCaptureStackTrace.captureStackTrace;
1032
936
  Reflect.deleteProperty(ErrorWithCaptureStackTrace, "captureStackTrace");
1033
- const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
937
+ using warn = vi.spyOn(console, "warn").mockImplementation(() => {});
1034
938
  const component = getModeComponent(
1035
939
  mode,
1036
940
  cv({
@@ -1057,17 +961,11 @@ for (const config of Object.values(CONFIGS)) {
1057
961
  if (captureStackTrace) {
1058
962
  ErrorWithCaptureStackTrace.captureStackTrace = captureStackTrace;
1059
963
  }
1060
- warn.mockRestore();
1061
964
  }
1062
965
  });
1063
966
 
1064
967
  test("refine warning omits the creation stack when cv ran in production", () => {
1065
- let captured = "";
1066
- const warn = vi
1067
- .spyOn(console, "warn")
1068
- .mockImplementation((message: unknown) => {
1069
- captured = String(message);
1070
- });
968
+ using warn = vi.spyOn(console, "warn").mockImplementation(() => {});
1071
969
  vi.stubEnv("NODE_ENV", "production");
1072
970
  const component = getModeComponent(
1073
971
  mode,
@@ -1083,15 +981,15 @@ for (const config of Object.values(CONFIGS)) {
1083
981
  try {
1084
982
  vi.unstubAllEnvs();
1085
983
  component();
1086
- expect(captured).toContain("Maximum refine iterations exceeded");
1087
- expect(captured).not.toContain("Component created at:");
984
+ const message = String(warn.mock.calls.at(-1)?.[0]);
985
+ expect(message).toContain("Maximum refine iterations exceeded");
986
+ expect(message).not.toContain("Component created at:");
1088
987
  } finally {
1089
988
  vi.unstubAllEnvs();
1090
- warn.mockRestore();
1091
989
  }
1092
990
  });
1093
991
 
1094
- test("refine setDefaultVariants when explicitly passing undefined", () => {
992
+ test("computed defaultVariants run when explicitly passing undefined", () => {
1095
993
  const component = getModeComponent(
1096
994
  mode,
1097
995
  cv({
@@ -1099,8 +997,8 @@ for (const config of Object.values(CONFIGS)) {
1099
997
  size: { sm: "sm", lg: "lg" },
1100
998
  color: { red: "red", blue: "blue" },
1101
999
  },
1102
- refine: ({ setDefaultVariants }) => {
1103
- setDefaultVariants({ color: "red" });
1000
+ defaultVariants: {
1001
+ color: () => "red" as const,
1104
1002
  },
1105
1003
  }),
1106
1004
  );