@constela/ui 0.2.4 → 0.3.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.
Files changed (39) hide show
  1. package/components/accordion/accordion-content.constela.json +20 -0
  2. package/components/accordion/accordion-item.constela.json +20 -0
  3. package/components/accordion/accordion-trigger.constela.json +21 -0
  4. package/components/accordion/accordion.constela.json +18 -0
  5. package/components/accordion/accordion.styles.json +54 -0
  6. package/components/accordion/accordion.test.ts +608 -0
  7. package/components/calendar/calendar.constela.json +195 -0
  8. package/components/calendar/calendar.styles.json +33 -0
  9. package/components/calendar/calendar.test.ts +458 -0
  10. package/components/chart/area-chart.constela.json +482 -0
  11. package/components/chart/bar-chart.constela.json +342 -0
  12. package/components/chart/chart-axis.constela.json +224 -0
  13. package/components/chart/chart-legend.constela.json +82 -0
  14. package/components/chart/chart-tooltip.constela.json +61 -0
  15. package/components/chart/chart.styles.json +183 -0
  16. package/components/chart/chart.test.ts +3260 -0
  17. package/components/chart/donut-chart.constela.json +369 -0
  18. package/components/chart/line-chart.constela.json +380 -0
  19. package/components/chart/pie-chart.constela.json +259 -0
  20. package/components/chart/radar-chart.constela.json +297 -0
  21. package/components/chart/scatter-chart.constela.json +300 -0
  22. package/components/data-table/data-table-cell.constela.json +22 -0
  23. package/components/data-table/data-table-header.constela.json +30 -0
  24. package/components/data-table/data-table-pagination.constela.json +19 -0
  25. package/components/data-table/data-table-row.constela.json +30 -0
  26. package/components/data-table/data-table.constela.json +32 -0
  27. package/components/data-table/data-table.styles.json +84 -0
  28. package/components/data-table/data-table.test.ts +873 -0
  29. package/components/datepicker/datepicker.constela.json +128 -0
  30. package/components/datepicker/datepicker.styles.json +47 -0
  31. package/components/datepicker/datepicker.test.ts +540 -0
  32. package/components/tree/tree-node.constela.json +26 -0
  33. package/components/tree/tree.constela.json +24 -0
  34. package/components/tree/tree.styles.json +50 -0
  35. package/components/tree/tree.test.ts +542 -0
  36. package/components/virtual-scroll/virtual-scroll.constela.json +27 -0
  37. package/components/virtual-scroll/virtual-scroll.styles.json +17 -0
  38. package/components/virtual-scroll/virtual-scroll.test.ts +345 -0
  39. package/package.json +2 -2
@@ -0,0 +1,608 @@
1
+ /**
2
+ * Test suite for Accordion Component Suite
3
+ *
4
+ * @constela/ui Accordion component tests following TDD methodology.
5
+ * These tests verify the Accordion, AccordionItem, AccordionTrigger, and AccordionContent
6
+ * components structure, params, styles, and accessibility.
7
+ *
8
+ * Coverage:
9
+ * - Component structure validation
10
+ * - Params definition validation (including required params)
11
+ * - Local state validation
12
+ * - Style preset validation
13
+ * - Accessibility attributes (aria-expanded, aria-controls, role="region")
14
+ */
15
+
16
+ import { describe, it, expect, beforeAll } from 'vitest';
17
+ import { readFileSync } from 'node:fs';
18
+ import { join, dirname } from 'node:path';
19
+ import { fileURLToPath } from 'node:url';
20
+ import type { ComponentDef, StylePreset } from '@constela/core';
21
+ import {
22
+ assertValidComponent,
23
+ assertValidStylePreset,
24
+ hasParams,
25
+ isOptionalParam,
26
+ hasParamType,
27
+ getRootTag,
28
+ hasVariants,
29
+ hasVariantOptions,
30
+ hasDefaultVariants,
31
+ hasSlot,
32
+ findPropInView,
33
+ hasRole,
34
+ hasAriaAttribute,
35
+ } from '../../tests/helpers/test-utils.js';
36
+
37
+ // ==================== Test Utilities ====================
38
+
39
+ /**
40
+ * Get the path to a component file in the accordion directory
41
+ */
42
+ function getAccordionComponentPath(fileName: string): string {
43
+ const __dirname = dirname(fileURLToPath(import.meta.url));
44
+ return join(__dirname, fileName);
45
+ }
46
+
47
+ /**
48
+ * Load a specific accordion sub-component
49
+ */
50
+ function loadAccordionComponent(componentName: string): ComponentDef {
51
+ const path = getAccordionComponentPath(`${componentName}.constela.json`);
52
+ const content = readFileSync(path, 'utf-8');
53
+ return JSON.parse(content) as ComponentDef;
54
+ }
55
+
56
+ /**
57
+ * Load accordion styles
58
+ */
59
+ function loadAccordionStyles(): Record<string, StylePreset> {
60
+ const path = getAccordionComponentPath('accordion.styles.json');
61
+ const content = readFileSync(path, 'utf-8');
62
+ return JSON.parse(content) as Record<string, StylePreset>;
63
+ }
64
+
65
+ /**
66
+ * Check if a param is required (required: true or required not specified)
67
+ */
68
+ function isRequiredParam(component: ComponentDef, paramName: string): boolean {
69
+ if (!component.params || !(paramName in component.params)) {
70
+ return false;
71
+ }
72
+ const param = component.params[paramName];
73
+ // In Constela, params are required by default unless explicitly set to false
74
+ return param.required !== false;
75
+ }
76
+
77
+ /**
78
+ * Check if a component has local state with a specific field
79
+ */
80
+ function hasLocalState(component: ComponentDef, fieldName: string): boolean {
81
+ if (!component.localState) {
82
+ return false;
83
+ }
84
+ return fieldName in component.localState;
85
+ }
86
+
87
+ /**
88
+ * Check if a local state field has a specific type
89
+ */
90
+ function hasLocalStateType(
91
+ component: ComponentDef,
92
+ fieldName: string,
93
+ expectedType: 'string' | 'number' | 'boolean' | 'list' | 'object'
94
+ ): boolean {
95
+ if (!component.localState || !(fieldName in component.localState)) {
96
+ return false;
97
+ }
98
+ return component.localState[fieldName].type === expectedType;
99
+ }
100
+
101
+ /**
102
+ * Check if a local state field has a specific initial value
103
+ */
104
+ function hasLocalStateInitial(
105
+ component: ComponentDef,
106
+ fieldName: string,
107
+ expectedInitial: unknown
108
+ ): boolean {
109
+ if (!component.localState || !(fieldName in component.localState)) {
110
+ return false;
111
+ }
112
+ return component.localState[fieldName].initial === expectedInitial;
113
+ }
114
+
115
+ // ==================== Test Contexts ====================
116
+
117
+ interface AccordionTestContext {
118
+ accordion: ComponentDef;
119
+ accordionItem: ComponentDef;
120
+ accordionTrigger: ComponentDef;
121
+ accordionContent: ComponentDef;
122
+ styles: Record<string, StylePreset>;
123
+ }
124
+
125
+ describe('Accordion Component Suite', () => {
126
+ let ctx: AccordionTestContext;
127
+
128
+ beforeAll(() => {
129
+ ctx = {
130
+ accordion: loadAccordionComponent('accordion'),
131
+ accordionItem: loadAccordionComponent('accordion-item'),
132
+ accordionTrigger: loadAccordionComponent('accordion-trigger'),
133
+ accordionContent: loadAccordionComponent('accordion-content'),
134
+ styles: loadAccordionStyles(),
135
+ };
136
+ });
137
+
138
+ // ==================== Accordion (Container) Tests ====================
139
+
140
+ describe('Accordion (Container)', () => {
141
+ // ==================== Component Structure Tests ====================
142
+
143
+ describe('Component Structure', () => {
144
+ it('should have valid component structure', () => {
145
+ assertValidComponent(ctx.accordion);
146
+ });
147
+
148
+ it('should have div as root element', () => {
149
+ const rootTag = getRootTag(ctx.accordion);
150
+ expect(rootTag).toBe('div');
151
+ });
152
+
153
+ it('should contain a slot for AccordionItems', () => {
154
+ expect(hasSlot(ctx.accordion.view)).toBe(true);
155
+ });
156
+
157
+ it('should have className using StyleExpr with accordionStyles preset', () => {
158
+ const className = findPropInView(ctx.accordion.view, 'className');
159
+ expect(className).not.toBeNull();
160
+ expect(className).toMatchObject({
161
+ expr: 'style',
162
+ preset: 'accordionStyles',
163
+ });
164
+ });
165
+ });
166
+
167
+ // ==================== Params Validation Tests ====================
168
+
169
+ describe('Params Validation', () => {
170
+ const expectedParams = ['type', 'collapsible', 'disabled'];
171
+
172
+ it('should have all expected params', () => {
173
+ expect(hasParams(ctx.accordion, expectedParams)).toBe(true);
174
+ });
175
+
176
+ describe('param: type', () => {
177
+ it('should be optional', () => {
178
+ expect(isOptionalParam(ctx.accordion, 'type')).toBe(true);
179
+ });
180
+
181
+ it('should have type string', () => {
182
+ expect(hasParamType(ctx.accordion, 'type', 'string')).toBe(true);
183
+ });
184
+ });
185
+
186
+ describe('param: collapsible', () => {
187
+ it('should be optional', () => {
188
+ expect(isOptionalParam(ctx.accordion, 'collapsible')).toBe(true);
189
+ });
190
+
191
+ it('should have type boolean', () => {
192
+ expect(hasParamType(ctx.accordion, 'collapsible', 'boolean')).toBe(true);
193
+ });
194
+ });
195
+
196
+ describe('param: disabled', () => {
197
+ it('should be optional', () => {
198
+ expect(isOptionalParam(ctx.accordion, 'disabled')).toBe(true);
199
+ });
200
+
201
+ it('should have type boolean', () => {
202
+ expect(hasParamType(ctx.accordion, 'disabled', 'boolean')).toBe(true);
203
+ });
204
+ });
205
+ });
206
+
207
+ // ==================== Style Preset Tests ====================
208
+
209
+ describe('Style Preset (accordionStyles)', () => {
210
+ it('should have valid style preset structure', () => {
211
+ const accordionStyles = ctx.styles['accordionStyles'];
212
+ expect(accordionStyles).toBeDefined();
213
+ assertValidStylePreset(accordionStyles);
214
+ });
215
+
216
+ it('should have base classes for flex column layout with dividers', () => {
217
+ const accordionStyles = ctx.styles['accordionStyles'];
218
+ expect(accordionStyles.base).toBeDefined();
219
+ expect(typeof accordionStyles.base).toBe('string');
220
+ expect(accordionStyles.base).toContain('flex');
221
+ expect(accordionStyles.base).toContain('flex-col');
222
+ });
223
+ });
224
+ });
225
+
226
+ // ==================== AccordionItem Tests ====================
227
+
228
+ describe('AccordionItem', () => {
229
+ // ==================== Component Structure Tests ====================
230
+
231
+ describe('Component Structure', () => {
232
+ it('should have valid component structure', () => {
233
+ assertValidComponent(ctx.accordionItem);
234
+ });
235
+
236
+ it('should have div as root element', () => {
237
+ const rootTag = getRootTag(ctx.accordionItem);
238
+ expect(rootTag).toBe('div');
239
+ });
240
+
241
+ it('should contain a slot for trigger and content', () => {
242
+ expect(hasSlot(ctx.accordionItem.view)).toBe(true);
243
+ });
244
+
245
+ it('should have className using StyleExpr with accordionItemStyles preset', () => {
246
+ const className = findPropInView(ctx.accordionItem.view, 'className');
247
+ expect(className).not.toBeNull();
248
+ expect(className).toMatchObject({
249
+ expr: 'style',
250
+ preset: 'accordionItemStyles',
251
+ });
252
+ });
253
+ });
254
+
255
+ // ==================== Params Validation Tests ====================
256
+
257
+ describe('Params Validation', () => {
258
+ const expectedParams = ['value', 'disabled'];
259
+
260
+ it('should have all expected params', () => {
261
+ expect(hasParams(ctx.accordionItem, expectedParams)).toBe(true);
262
+ });
263
+
264
+ describe('param: value', () => {
265
+ it('should be required', () => {
266
+ expect(isRequiredParam(ctx.accordionItem, 'value')).toBe(true);
267
+ });
268
+
269
+ it('should have type string', () => {
270
+ expect(hasParamType(ctx.accordionItem, 'value', 'string')).toBe(true);
271
+ });
272
+ });
273
+
274
+ describe('param: disabled', () => {
275
+ it('should be optional', () => {
276
+ expect(isOptionalParam(ctx.accordionItem, 'disabled')).toBe(true);
277
+ });
278
+
279
+ it('should have type boolean', () => {
280
+ expect(hasParamType(ctx.accordionItem, 'disabled', 'boolean')).toBe(true);
281
+ });
282
+ });
283
+ });
284
+
285
+ // ==================== Local State Tests ====================
286
+
287
+ describe('Local State', () => {
288
+ it('should have isExpanded local state', () => {
289
+ expect(hasLocalState(ctx.accordionItem, 'isExpanded')).toBe(true);
290
+ });
291
+
292
+ it('should have isExpanded as boolean type', () => {
293
+ expect(hasLocalStateType(ctx.accordionItem, 'isExpanded', 'boolean')).toBe(true);
294
+ });
295
+
296
+ it('should have isExpanded initial value as false', () => {
297
+ expect(hasLocalStateInitial(ctx.accordionItem, 'isExpanded', false)).toBe(true);
298
+ });
299
+ });
300
+
301
+ // ==================== Style Preset Tests ====================
302
+
303
+ describe('Style Preset (accordionItemStyles)', () => {
304
+ it('should have valid style preset structure', () => {
305
+ const accordionItemStyles = ctx.styles['accordionItemStyles'];
306
+ expect(accordionItemStyles).toBeDefined();
307
+ assertValidStylePreset(accordionItemStyles);
308
+ });
309
+
310
+ it('should have base classes with border-b', () => {
311
+ const accordionItemStyles = ctx.styles['accordionItemStyles'];
312
+ expect(accordionItemStyles.base).toBeDefined();
313
+ expect(typeof accordionItemStyles.base).toBe('string');
314
+ expect(accordionItemStyles.base).toContain('border-b');
315
+ });
316
+
317
+ describe('variant options', () => {
318
+ const stateOptions = ['default', 'disabled'];
319
+
320
+ it('should have state variants', () => {
321
+ const accordionItemStyles = ctx.styles['accordionItemStyles'];
322
+ expect(hasVariants(accordionItemStyles, ['state'])).toBe(true);
323
+ });
324
+
325
+ it.each(stateOptions)('should have %s state option', (option) => {
326
+ const accordionItemStyles = ctx.styles['accordionItemStyles'];
327
+ expect(hasVariantOptions(accordionItemStyles, 'state', [option])).toBe(true);
328
+ });
329
+ });
330
+ });
331
+ });
332
+
333
+ // ==================== AccordionTrigger Tests ====================
334
+
335
+ describe('AccordionTrigger', () => {
336
+ // ==================== Component Structure Tests ====================
337
+
338
+ describe('Component Structure', () => {
339
+ it('should have valid component structure', () => {
340
+ assertValidComponent(ctx.accordionTrigger);
341
+ });
342
+
343
+ it('should have button as root element', () => {
344
+ const rootTag = getRootTag(ctx.accordionTrigger);
345
+ expect(rootTag).toBe('button');
346
+ });
347
+
348
+ it('should have type="button" attribute', () => {
349
+ const type = findPropInView(ctx.accordionTrigger.view, 'type');
350
+ expect(type).not.toBeNull();
351
+ expect(type).toMatchObject({
352
+ expr: 'lit',
353
+ value: 'button',
354
+ });
355
+ });
356
+
357
+ it('should have className using StyleExpr with accordionTriggerStyles preset', () => {
358
+ const className = findPropInView(ctx.accordionTrigger.view, 'className');
359
+ expect(className).not.toBeNull();
360
+ expect(className).toMatchObject({
361
+ expr: 'style',
362
+ preset: 'accordionTriggerStyles',
363
+ });
364
+ });
365
+ });
366
+
367
+ // ==================== Params Validation Tests ====================
368
+
369
+ describe('Params Validation', () => {
370
+ const expectedParams = ['contentId', 'disabled'];
371
+
372
+ it('should have all expected params', () => {
373
+ expect(hasParams(ctx.accordionTrigger, expectedParams)).toBe(true);
374
+ });
375
+
376
+ describe('param: contentId', () => {
377
+ it('should be optional', () => {
378
+ expect(isOptionalParam(ctx.accordionTrigger, 'contentId')).toBe(true);
379
+ });
380
+
381
+ it('should have type string', () => {
382
+ expect(hasParamType(ctx.accordionTrigger, 'contentId', 'string')).toBe(true);
383
+ });
384
+ });
385
+
386
+ describe('param: disabled', () => {
387
+ it('should be optional', () => {
388
+ expect(isOptionalParam(ctx.accordionTrigger, 'disabled')).toBe(true);
389
+ });
390
+
391
+ it('should have type boolean', () => {
392
+ expect(hasParamType(ctx.accordionTrigger, 'disabled', 'boolean')).toBe(true);
393
+ });
394
+ });
395
+ });
396
+
397
+ // ==================== Accessibility Tests ====================
398
+
399
+ describe('Accessibility', () => {
400
+ it('should have aria-expanded attribute', () => {
401
+ expect(hasAriaAttribute(ctx.accordionTrigger.view, 'aria-expanded')).toBe(true);
402
+ });
403
+
404
+ it('should have aria-controls attribute', () => {
405
+ expect(hasAriaAttribute(ctx.accordionTrigger.view, 'aria-controls')).toBe(true);
406
+ });
407
+ });
408
+
409
+ // ==================== Style Preset Tests ====================
410
+
411
+ describe('Style Preset (accordionTriggerStyles)', () => {
412
+ it('should have valid style preset structure', () => {
413
+ const accordionTriggerStyles = ctx.styles['accordionTriggerStyles'];
414
+ expect(accordionTriggerStyles).toBeDefined();
415
+ assertValidStylePreset(accordionTriggerStyles);
416
+ });
417
+
418
+ it('should have base classes for flex layout with spacing', () => {
419
+ const accordionTriggerStyles = ctx.styles['accordionTriggerStyles'];
420
+ expect(accordionTriggerStyles.base).toBeDefined();
421
+ expect(typeof accordionTriggerStyles.base).toBe('string');
422
+ expect(accordionTriggerStyles.base).toContain('flex');
423
+ expect(accordionTriggerStyles.base).toContain('items-center');
424
+ expect(accordionTriggerStyles.base).toContain('justify-between');
425
+ });
426
+
427
+ describe('variant options', () => {
428
+ const stateOptions = ['default', 'disabled', 'hover'];
429
+
430
+ it('should have state variants', () => {
431
+ const accordionTriggerStyles = ctx.styles['accordionTriggerStyles'];
432
+ expect(hasVariants(accordionTriggerStyles, ['state'])).toBe(true);
433
+ });
434
+
435
+ it.each(stateOptions)('should have %s state option', (option) => {
436
+ const accordionTriggerStyles = ctx.styles['accordionTriggerStyles'];
437
+ expect(hasVariantOptions(accordionTriggerStyles, 'state', [option])).toBe(true);
438
+ });
439
+ });
440
+ });
441
+
442
+ describe('Style Preset (accordionChevronStyles)', () => {
443
+ it('should have valid style preset structure', () => {
444
+ const accordionChevronStyles = ctx.styles['accordionChevronStyles'];
445
+ expect(accordionChevronStyles).toBeDefined();
446
+ assertValidStylePreset(accordionChevronStyles);
447
+ });
448
+
449
+ it('should have base classes for icon sizing and transition', () => {
450
+ const accordionChevronStyles = ctx.styles['accordionChevronStyles'];
451
+ expect(accordionChevronStyles.base).toBeDefined();
452
+ expect(typeof accordionChevronStyles.base).toBe('string');
453
+ expect(accordionChevronStyles.base).toContain('h-4');
454
+ expect(accordionChevronStyles.base).toContain('w-4');
455
+ expect(accordionChevronStyles.base).toContain('transition-transform');
456
+ });
457
+
458
+ describe('variant options', () => {
459
+ it('should have expanded variants', () => {
460
+ const accordionChevronStyles = ctx.styles['accordionChevronStyles'];
461
+ expect(hasVariants(accordionChevronStyles, ['expanded'])).toBe(true);
462
+ });
463
+
464
+ it('should have true expanded option with rotate-180', () => {
465
+ const accordionChevronStyles = ctx.styles['accordionChevronStyles'];
466
+ expect(hasVariantOptions(accordionChevronStyles, 'expanded', ['true'])).toBe(true);
467
+ expect(accordionChevronStyles.variants?.expanded?.['true']).toContain('rotate-180');
468
+ });
469
+
470
+ it('should have false expanded option with rotate-0', () => {
471
+ const accordionChevronStyles = ctx.styles['accordionChevronStyles'];
472
+ expect(hasVariantOptions(accordionChevronStyles, 'expanded', ['false'])).toBe(true);
473
+ expect(accordionChevronStyles.variants?.expanded?.['false']).toContain('rotate-0');
474
+ });
475
+ });
476
+
477
+ describe('default variants', () => {
478
+ it('should have default expanded set to false', () => {
479
+ const accordionChevronStyles = ctx.styles['accordionChevronStyles'];
480
+ expect(hasDefaultVariants(accordionChevronStyles, { expanded: 'false' })).toBe(true);
481
+ });
482
+ });
483
+ });
484
+ });
485
+
486
+ // ==================== AccordionContent Tests ====================
487
+
488
+ describe('AccordionContent', () => {
489
+ // ==================== Component Structure Tests ====================
490
+
491
+ describe('Component Structure', () => {
492
+ it('should have valid component structure', () => {
493
+ assertValidComponent(ctx.accordionContent);
494
+ });
495
+
496
+ it('should have div as root element', () => {
497
+ const rootTag = getRootTag(ctx.accordionContent);
498
+ expect(rootTag).toBe('div');
499
+ });
500
+
501
+ it('should have role="region" attribute', () => {
502
+ expect(hasRole(ctx.accordionContent.view, 'region')).toBe(true);
503
+ });
504
+
505
+ it('should contain a slot for content', () => {
506
+ expect(hasSlot(ctx.accordionContent.view)).toBe(true);
507
+ });
508
+
509
+ it('should have className using StyleExpr with accordionContentStyles preset', () => {
510
+ const className = findPropInView(ctx.accordionContent.view, 'className');
511
+ expect(className).not.toBeNull();
512
+ expect(className).toMatchObject({
513
+ expr: 'style',
514
+ preset: 'accordionContentStyles',
515
+ });
516
+ });
517
+ });
518
+
519
+ // ==================== Params Validation Tests ====================
520
+
521
+ describe('Params Validation', () => {
522
+ const expectedParams = ['id', 'triggerId'];
523
+
524
+ it('should have all expected params', () => {
525
+ expect(hasParams(ctx.accordionContent, expectedParams)).toBe(true);
526
+ });
527
+
528
+ describe('param: id', () => {
529
+ it('should be optional', () => {
530
+ expect(isOptionalParam(ctx.accordionContent, 'id')).toBe(true);
531
+ });
532
+
533
+ it('should have type string', () => {
534
+ expect(hasParamType(ctx.accordionContent, 'id', 'string')).toBe(true);
535
+ });
536
+ });
537
+
538
+ describe('param: triggerId', () => {
539
+ it('should be optional', () => {
540
+ expect(isOptionalParam(ctx.accordionContent, 'triggerId')).toBe(true);
541
+ });
542
+
543
+ it('should have type string', () => {
544
+ expect(hasParamType(ctx.accordionContent, 'triggerId', 'string')).toBe(true);
545
+ });
546
+ });
547
+ });
548
+
549
+ // ==================== Accessibility Tests ====================
550
+
551
+ describe('Accessibility', () => {
552
+ it('should have role="region" for screen readers', () => {
553
+ const role = findPropInView(ctx.accordionContent.view, 'role');
554
+ expect(role).not.toBeNull();
555
+ expect(role).toMatchObject({
556
+ expr: 'lit',
557
+ value: 'region',
558
+ });
559
+ });
560
+
561
+ it('should have aria-labelledby attribute', () => {
562
+ expect(hasAriaAttribute(ctx.accordionContent.view, 'aria-labelledby')).toBe(true);
563
+ });
564
+ });
565
+
566
+ // ==================== Style Preset Tests ====================
567
+
568
+ describe('Style Preset (accordionContentStyles)', () => {
569
+ it('should have valid style preset structure', () => {
570
+ const accordionContentStyles = ctx.styles['accordionContentStyles'];
571
+ expect(accordionContentStyles).toBeDefined();
572
+ assertValidStylePreset(accordionContentStyles);
573
+ });
574
+
575
+ it('should have base classes for overflow and transition', () => {
576
+ const accordionContentStyles = ctx.styles['accordionContentStyles'];
577
+ expect(accordionContentStyles.base).toBeDefined();
578
+ expect(typeof accordionContentStyles.base).toBe('string');
579
+ expect(accordionContentStyles.base).toContain('overflow-hidden');
580
+ expect(accordionContentStyles.base).toContain('text-sm');
581
+ });
582
+
583
+ describe('variant options', () => {
584
+ const stateOptions = ['open', 'closed'];
585
+
586
+ it('should have state variants', () => {
587
+ const accordionContentStyles = ctx.styles['accordionContentStyles'];
588
+ expect(hasVariants(accordionContentStyles, ['state'])).toBe(true);
589
+ });
590
+
591
+ it.each(stateOptions)('should have %s state option', (option) => {
592
+ const accordionContentStyles = ctx.styles['accordionContentStyles'];
593
+ expect(hasVariantOptions(accordionContentStyles, 'state', [option])).toBe(true);
594
+ });
595
+
596
+ it('should have open state with animate-accordion-down', () => {
597
+ const accordionContentStyles = ctx.styles['accordionContentStyles'];
598
+ expect(accordionContentStyles.variants?.state?.['open']).toContain('animate-accordion-down');
599
+ });
600
+
601
+ it('should have closed state with animate-accordion-up', () => {
602
+ const accordionContentStyles = ctx.styles['accordionContentStyles'];
603
+ expect(accordionContentStyles.variants?.state?.['closed']).toContain('animate-accordion-up');
604
+ });
605
+ });
606
+ });
607
+ });
608
+ });