@constela/ui 0.2.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 (72) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +160 -0
  3. package/components/alert/alert.constela.json +22 -0
  4. package/components/alert/alert.styles.json +14 -0
  5. package/components/alert/alert.test.ts +173 -0
  6. package/components/avatar/avatar.constela.json +32 -0
  7. package/components/avatar/avatar.styles.json +15 -0
  8. package/components/avatar/avatar.test.ts +197 -0
  9. package/components/badge/badge.constela.json +19 -0
  10. package/components/badge/badge.styles.json +16 -0
  11. package/components/badge/badge.test.ts +135 -0
  12. package/components/breadcrumb/breadcrumb.constela.json +17 -0
  13. package/components/breadcrumb/breadcrumb.styles.json +5 -0
  14. package/components/breadcrumb/breadcrumb.test.ts +149 -0
  15. package/components/button/README.md +164 -0
  16. package/components/button/button.constela.json +27 -0
  17. package/components/button/button.styles.json +25 -0
  18. package/components/button/button.test.ts +233 -0
  19. package/components/card/card.constela.json +21 -0
  20. package/components/card/card.styles.json +14 -0
  21. package/components/card/card.test.ts +154 -0
  22. package/components/checkbox/checkbox.constela.json +33 -0
  23. package/components/checkbox/checkbox.styles.json +15 -0
  24. package/components/checkbox/checkbox.test.ts +275 -0
  25. package/components/container/container.constela.json +21 -0
  26. package/components/container/container.styles.json +18 -0
  27. package/components/container/container.test.ts +164 -0
  28. package/components/dialog/dialog.constela.json +19 -0
  29. package/components/dialog/dialog.styles.json +5 -0
  30. package/components/dialog/dialog.test.ts +139 -0
  31. package/components/grid/grid.constela.json +23 -0
  32. package/components/grid/grid.styles.json +25 -0
  33. package/components/grid/grid.test.ts +193 -0
  34. package/components/input/input.constela.json +34 -0
  35. package/components/input/input.styles.json +19 -0
  36. package/components/input/input.test.ts +301 -0
  37. package/components/pagination/pagination.constela.json +22 -0
  38. package/components/pagination/pagination.styles.json +15 -0
  39. package/components/pagination/pagination.test.ts +170 -0
  40. package/components/popover/popover.constela.json +20 -0
  41. package/components/popover/popover.styles.json +16 -0
  42. package/components/popover/popover.test.ts +165 -0
  43. package/components/radio/radio.constela.json +31 -0
  44. package/components/radio/radio.styles.json +15 -0
  45. package/components/radio/radio.test.ts +253 -0
  46. package/components/select/select.constela.json +32 -0
  47. package/components/select/select.styles.json +15 -0
  48. package/components/select/select.test.ts +257 -0
  49. package/components/skeleton/skeleton.constela.json +24 -0
  50. package/components/skeleton/skeleton.styles.json +5 -0
  51. package/components/skeleton/skeleton.test.ts +164 -0
  52. package/components/stack/stack.constela.json +27 -0
  53. package/components/stack/stack.styles.json +33 -0
  54. package/components/stack/stack.test.ts +261 -0
  55. package/components/switch/switch.constela.json +29 -0
  56. package/components/switch/switch.styles.json +15 -0
  57. package/components/switch/switch.test.ts +224 -0
  58. package/components/tabs/tabs.constela.json +21 -0
  59. package/components/tabs/tabs.styles.json +14 -0
  60. package/components/tabs/tabs.test.ts +163 -0
  61. package/components/textarea/textarea.constela.json +34 -0
  62. package/components/textarea/textarea.styles.json +15 -0
  63. package/components/textarea/textarea.test.ts +290 -0
  64. package/components/toast/toast.constela.json +23 -0
  65. package/components/toast/toast.styles.json +17 -0
  66. package/components/toast/toast.test.ts +183 -0
  67. package/components/tooltip/tooltip.constela.json +21 -0
  68. package/components/tooltip/tooltip.styles.json +16 -0
  69. package/components/tooltip/tooltip.test.ts +164 -0
  70. package/dist/index.d.ts +54 -0
  71. package/dist/index.js +83 -0
  72. package/package.json +39 -0
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Test suite for Pagination component
3
+ *
4
+ * @constela/ui Pagination component tests following TDD methodology.
5
+ * These tests verify the Pagination component structure, params, styles, and accessibility.
6
+ *
7
+ * Coverage:
8
+ * - Component structure validation
9
+ * - Params definition validation
10
+ * - Style preset validation
11
+ * - Accessibility attributes
12
+ */
13
+
14
+ import { describe, it, expect, beforeAll } from 'vitest';
15
+ import {
16
+ loadComponentForTesting,
17
+ assertValidComponent,
18
+ assertValidStylePreset,
19
+ hasParams,
20
+ isOptionalParam,
21
+ hasParamType,
22
+ getRootTag,
23
+ hasVariants,
24
+ hasVariantOptions,
25
+ hasDefaultVariants,
26
+ hasSlot,
27
+ findPropInView,
28
+ type ComponentTestContext,
29
+ } from '../../tests/helpers/test-utils.js';
30
+
31
+ describe('Pagination Component', () => {
32
+ let ctx: ComponentTestContext;
33
+
34
+ beforeAll(() => {
35
+ ctx = loadComponentForTesting('pagination');
36
+ });
37
+
38
+ // ==================== Component Structure Tests ====================
39
+
40
+ describe('Component Structure', () => {
41
+ it('should have valid component structure', () => {
42
+ assertValidComponent(ctx.component);
43
+ });
44
+
45
+ it('should have nav as root element', () => {
46
+ const rootTag = getRootTag(ctx.component);
47
+ expect(rootTag).toBe('nav');
48
+ });
49
+
50
+ it('should contain a slot for pagination items', () => {
51
+ expect(hasSlot(ctx.component.view)).toBe(true);
52
+ });
53
+
54
+ it('should have className using StyleExpr with preset paginationStyles', () => {
55
+ const className = findPropInView(ctx.component.view, 'className');
56
+ expect(className).not.toBeNull();
57
+ // StyleExpr should have expr: 'style' and preset reference
58
+ expect(className).toMatchObject({
59
+ expr: 'style',
60
+ preset: 'paginationStyles',
61
+ });
62
+ });
63
+ });
64
+
65
+ // ==================== Params Validation Tests ====================
66
+
67
+ describe('Params Validation', () => {
68
+ const expectedParams = ['currentPage', 'totalPages', 'size'];
69
+
70
+ it('should have all expected params', () => {
71
+ expect(hasParams(ctx.component, expectedParams)).toBe(true);
72
+ });
73
+
74
+ describe('param: currentPage', () => {
75
+ it('should be optional', () => {
76
+ expect(isOptionalParam(ctx.component, 'currentPage')).toBe(true);
77
+ });
78
+
79
+ it('should have type number', () => {
80
+ expect(hasParamType(ctx.component, 'currentPage', 'number')).toBe(true);
81
+ });
82
+ });
83
+
84
+ describe('param: totalPages', () => {
85
+ it('should be optional', () => {
86
+ expect(isOptionalParam(ctx.component, 'totalPages')).toBe(true);
87
+ });
88
+
89
+ it('should have type number', () => {
90
+ expect(hasParamType(ctx.component, 'totalPages', 'number')).toBe(true);
91
+ });
92
+ });
93
+
94
+ describe('param: size', () => {
95
+ it('should be optional', () => {
96
+ expect(isOptionalParam(ctx.component, 'size')).toBe(true);
97
+ });
98
+
99
+ it('should have type string', () => {
100
+ expect(hasParamType(ctx.component, 'size', 'string')).toBe(true);
101
+ });
102
+ });
103
+ });
104
+
105
+ // ==================== Style Preset Tests ====================
106
+
107
+ describe('Style Preset', () => {
108
+ it('should have valid style preset structure', () => {
109
+ const paginationStyles = ctx.styles['paginationStyles'];
110
+ expect(paginationStyles).toBeDefined();
111
+ assertValidStylePreset(paginationStyles);
112
+ });
113
+
114
+ it('should have base classes for common pagination styles', () => {
115
+ const paginationStyles = ctx.styles['paginationStyles'];
116
+ expect(paginationStyles.base).toBeDefined();
117
+ expect(typeof paginationStyles.base).toBe('string');
118
+ expect(paginationStyles.base.length).toBeGreaterThan(0);
119
+ });
120
+
121
+ describe('size options', () => {
122
+ const sizeOptions = ['default', 'sm', 'lg'];
123
+
124
+ it('should have size variants', () => {
125
+ const paginationStyles = ctx.styles['paginationStyles'];
126
+ expect(hasVariants(paginationStyles, ['size'])).toBe(true);
127
+ });
128
+
129
+ it.each(sizeOptions)('should have %s size option', (option) => {
130
+ const paginationStyles = ctx.styles['paginationStyles'];
131
+ expect(hasVariantOptions(paginationStyles, 'size', [option])).toBe(true);
132
+ });
133
+ });
134
+
135
+ describe('default variants', () => {
136
+ it('should have default size set to default', () => {
137
+ const paginationStyles = ctx.styles['paginationStyles'];
138
+ expect(hasDefaultVariants(paginationStyles, { size: 'default' })).toBe(true);
139
+ });
140
+ });
141
+ });
142
+
143
+ // ==================== Accessibility Tests ====================
144
+
145
+ describe('Accessibility', () => {
146
+ it('should have aria-label="Pagination" on root element', () => {
147
+ const ariaLabel = findPropInView(ctx.component.view, 'aria-label');
148
+ expect(ariaLabel).not.toBeNull();
149
+ // Should be a literal "Pagination" value
150
+ expect(ariaLabel).toMatchObject({
151
+ expr: 'lit',
152
+ value: 'Pagination',
153
+ });
154
+ });
155
+ });
156
+
157
+ // ==================== View Props Tests ====================
158
+
159
+ describe('View Props', () => {
160
+ it('should pass size to StyleExpr', () => {
161
+ const className = findPropInView(ctx.component.view, 'className');
162
+ expect(className).toMatchObject({
163
+ expr: 'style',
164
+ props: expect.objectContaining({
165
+ size: expect.objectContaining({ expr: 'param', name: 'size' }),
166
+ }),
167
+ });
168
+ });
169
+ });
170
+ });
@@ -0,0 +1,20 @@
1
+ {
2
+ "params": {
3
+ "open": { "type": "boolean", "required": false },
4
+ "position": { "type": "string", "required": false }
5
+ },
6
+ "view": {
7
+ "kind": "element",
8
+ "tag": "div",
9
+ "props": {
10
+ "className": {
11
+ "expr": "style",
12
+ "preset": "popoverStyles",
13
+ "props": {
14
+ "position": { "expr": "param", "name": "position" }
15
+ }
16
+ }
17
+ },
18
+ "children": [{ "kind": "slot" }]
19
+ }
20
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "popoverStyles": {
3
+ "base": "absolute z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
4
+ "variants": {
5
+ "position": {
6
+ "top": "bottom-full mb-2",
7
+ "right": "left-full ml-2",
8
+ "bottom": "top-full mt-2",
9
+ "left": "right-full mr-2"
10
+ }
11
+ },
12
+ "defaultVariants": {
13
+ "position": "bottom"
14
+ }
15
+ }
16
+ }
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Test suite for Popover component
3
+ *
4
+ * @constela/ui Popover component tests following TDD methodology.
5
+ * These tests verify the Popover component structure, params, styles, and slots.
6
+ *
7
+ * Coverage:
8
+ * - Component structure validation
9
+ * - Params definition validation (open, position)
10
+ * - Style preset validation (base, position variants, default variants)
11
+ */
12
+
13
+ import { describe, it, expect, beforeAll } from 'vitest';
14
+ import {
15
+ loadComponentForTesting,
16
+ assertValidComponent,
17
+ assertValidStylePreset,
18
+ hasParams,
19
+ isOptionalParam,
20
+ hasParamType,
21
+ getRootTag,
22
+ hasVariants,
23
+ hasVariantOptions,
24
+ hasDefaultVariants,
25
+ hasSlot,
26
+ findPropInView,
27
+ type ComponentTestContext,
28
+ } from '../../tests/helpers/test-utils.js';
29
+
30
+ describe('Popover Component', () => {
31
+ let ctx: ComponentTestContext;
32
+
33
+ beforeAll(() => {
34
+ ctx = loadComponentForTesting('popover');
35
+ });
36
+
37
+ // ==================== Component Structure Tests ====================
38
+
39
+ describe('Component Structure', () => {
40
+ it('should have valid component structure', () => {
41
+ assertValidComponent(ctx.component);
42
+ });
43
+
44
+ it('should have div as root element', () => {
45
+ const rootTag = getRootTag(ctx.component);
46
+ expect(rootTag).toBe('div');
47
+ });
48
+
49
+ it('should contain a slot for popover content', () => {
50
+ expect(hasSlot(ctx.component.view)).toBe(true);
51
+ });
52
+
53
+ it('should have className using StyleExpr with preset popoverStyles', () => {
54
+ const className = findPropInView(ctx.component.view, 'className');
55
+ expect(className).not.toBeNull();
56
+ // StyleExpr should have expr: 'style' and preset reference
57
+ expect(className).toMatchObject({
58
+ expr: 'style',
59
+ preset: 'popoverStyles',
60
+ });
61
+ });
62
+ });
63
+
64
+ // ==================== Params Validation Tests ====================
65
+
66
+ describe('Params Validation', () => {
67
+ const expectedParams = ['open', 'position'];
68
+
69
+ it('should have all expected params', () => {
70
+ expect(hasParams(ctx.component, expectedParams)).toBe(true);
71
+ });
72
+
73
+ describe('param: open', () => {
74
+ it('should be optional', () => {
75
+ expect(isOptionalParam(ctx.component, 'open')).toBe(true);
76
+ });
77
+
78
+ it('should have type boolean', () => {
79
+ expect(hasParamType(ctx.component, 'open', 'boolean')).toBe(true);
80
+ });
81
+ });
82
+
83
+ describe('param: position', () => {
84
+ it('should be optional', () => {
85
+ expect(isOptionalParam(ctx.component, 'position')).toBe(true);
86
+ });
87
+
88
+ it('should have type string', () => {
89
+ expect(hasParamType(ctx.component, 'position', 'string')).toBe(true);
90
+ });
91
+ });
92
+ });
93
+
94
+ // ==================== Style Preset Tests ====================
95
+
96
+ describe('Style Preset', () => {
97
+ it('should have valid style preset structure', () => {
98
+ const popoverStyles = ctx.styles['popoverStyles'];
99
+ expect(popoverStyles).toBeDefined();
100
+ assertValidStylePreset(popoverStyles);
101
+ });
102
+
103
+ it('should have base classes for common popover styles', () => {
104
+ const popoverStyles = ctx.styles['popoverStyles'];
105
+ expect(popoverStyles.base).toBeDefined();
106
+ expect(typeof popoverStyles.base).toBe('string');
107
+ expect(popoverStyles.base.length).toBeGreaterThan(0);
108
+ });
109
+
110
+ it('should include absolute positioning in base styles', () => {
111
+ const popoverStyles = ctx.styles['popoverStyles'];
112
+ expect(popoverStyles.base).toContain('absolute');
113
+ });
114
+
115
+ it('should include z-index in base styles', () => {
116
+ const popoverStyles = ctx.styles['popoverStyles'];
117
+ expect(popoverStyles.base).toMatch(/z-\d+/);
118
+ });
119
+
120
+ it('should include rounded corners in base styles', () => {
121
+ const popoverStyles = ctx.styles['popoverStyles'];
122
+ expect(popoverStyles.base).toMatch(/rounded/);
123
+ });
124
+
125
+ it('should include shadow in base styles', () => {
126
+ const popoverStyles = ctx.styles['popoverStyles'];
127
+ expect(popoverStyles.base).toMatch(/shadow/);
128
+ });
129
+
130
+ describe('position variants', () => {
131
+ const positionOptions = ['top', 'right', 'bottom', 'left'];
132
+
133
+ it('should have position variants', () => {
134
+ const popoverStyles = ctx.styles['popoverStyles'];
135
+ expect(hasVariants(popoverStyles, ['position'])).toBe(true);
136
+ });
137
+
138
+ it.each(positionOptions)('should have %s position option', (option) => {
139
+ const popoverStyles = ctx.styles['popoverStyles'];
140
+ expect(hasVariantOptions(popoverStyles, 'position', [option])).toBe(true);
141
+ });
142
+ });
143
+
144
+ describe('default variants', () => {
145
+ it('should have default position set to bottom', () => {
146
+ const popoverStyles = ctx.styles['popoverStyles'];
147
+ expect(hasDefaultVariants(popoverStyles, { position: 'bottom' })).toBe(true);
148
+ });
149
+ });
150
+ });
151
+
152
+ // ==================== View Props Tests ====================
153
+
154
+ describe('View Props', () => {
155
+ it('should pass position to StyleExpr', () => {
156
+ const className = findPropInView(ctx.component.view, 'className');
157
+ expect(className).toMatchObject({
158
+ expr: 'style',
159
+ props: expect.objectContaining({
160
+ position: expect.objectContaining({ expr: 'param', name: 'position' }),
161
+ }),
162
+ });
163
+ });
164
+ });
165
+ });
@@ -0,0 +1,31 @@
1
+ {
2
+ "params": {
3
+ "checked": { "type": "boolean", "required": false },
4
+ "disabled": { "type": "boolean", "required": false },
5
+ "name": { "type": "string", "required": false },
6
+ "value": { "type": "string", "required": false },
7
+ "id": { "type": "string", "required": false },
8
+ "ariaLabel": { "type": "string", "required": false },
9
+ "size": { "type": "string", "required": false }
10
+ },
11
+ "view": {
12
+ "kind": "element",
13
+ "tag": "input",
14
+ "props": {
15
+ "type": { "expr": "lit", "value": "radio" },
16
+ "className": {
17
+ "expr": "style",
18
+ "preset": "radioStyles",
19
+ "props": {
20
+ "size": { "expr": "param", "name": "size" }
21
+ }
22
+ },
23
+ "checked": { "expr": "param", "name": "checked" },
24
+ "disabled": { "expr": "param", "name": "disabled" },
25
+ "name": { "expr": "param", "name": "name" },
26
+ "value": { "expr": "param", "name": "value" },
27
+ "id": { "expr": "param", "name": "id" },
28
+ "aria-label": { "expr": "param", "name": "ariaLabel" }
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "radioStyles": {
3
+ "base": "aspect-square rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
4
+ "variants": {
5
+ "size": {
6
+ "default": "h-4 w-4",
7
+ "sm": "h-3 w-3",
8
+ "lg": "h-5 w-5"
9
+ }
10
+ },
11
+ "defaultVariants": {
12
+ "size": "default"
13
+ }
14
+ }
15
+ }
@@ -0,0 +1,253 @@
1
+ /**
2
+ * Test suite for Radio component
3
+ *
4
+ * @constela/ui Radio component tests following TDD methodology.
5
+ * These tests verify the Radio component structure, params, styles, and accessibility.
6
+ *
7
+ * Coverage:
8
+ * - Component structure validation
9
+ * - Params definition validation
10
+ * - Style preset validation
11
+ * - Accessibility attributes
12
+ */
13
+
14
+ import { describe, it, expect, beforeAll } from 'vitest';
15
+ import {
16
+ loadComponentForTesting,
17
+ assertValidComponent,
18
+ assertValidStylePreset,
19
+ hasParams,
20
+ isOptionalParam,
21
+ hasParamType,
22
+ getRootTag,
23
+ hasVariants,
24
+ hasVariantOptions,
25
+ hasDefaultVariants,
26
+ hasSlot,
27
+ findPropInView,
28
+ type ComponentTestContext,
29
+ } from '../../tests/helpers/test-utils.js';
30
+
31
+ describe('Radio Component', () => {
32
+ let ctx: ComponentTestContext;
33
+
34
+ beforeAll(() => {
35
+ ctx = loadComponentForTesting('radio');
36
+ });
37
+
38
+ // ==================== Component Structure Tests ====================
39
+
40
+ describe('Component Structure', () => {
41
+ it('should have valid component structure', () => {
42
+ assertValidComponent(ctx.component);
43
+ });
44
+
45
+ it('should have input as root element', () => {
46
+ const rootTag = getRootTag(ctx.component);
47
+ expect(rootTag).toBe('input');
48
+ });
49
+
50
+ it('should have type="radio" attribute', () => {
51
+ const type = findPropInView(ctx.component.view, 'type');
52
+ expect(type).not.toBeNull();
53
+ // Should be a literal value 'radio'
54
+ expect(type).toMatchObject({
55
+ expr: 'lit',
56
+ value: 'radio',
57
+ });
58
+ });
59
+
60
+ it('should NOT contain a slot (radio is self-closing)', () => {
61
+ expect(hasSlot(ctx.component.view)).toBe(false);
62
+ });
63
+
64
+ it('should have className using StyleExpr', () => {
65
+ const className = findPropInView(ctx.component.view, 'className');
66
+ expect(className).not.toBeNull();
67
+ // StyleExpr should have expr: 'style' and preset reference
68
+ expect(className).toMatchObject({
69
+ expr: 'style',
70
+ preset: 'radioStyles',
71
+ });
72
+ });
73
+ });
74
+
75
+ // ==================== Params Validation Tests ====================
76
+
77
+ describe('Params Validation', () => {
78
+ const expectedParams = [
79
+ 'checked',
80
+ 'disabled',
81
+ 'name',
82
+ 'value',
83
+ 'id',
84
+ 'ariaLabel',
85
+ ];
86
+
87
+ it('should have all expected params', () => {
88
+ expect(hasParams(ctx.component, expectedParams)).toBe(true);
89
+ });
90
+
91
+ describe('param: checked', () => {
92
+ it('should be optional', () => {
93
+ expect(isOptionalParam(ctx.component, 'checked')).toBe(true);
94
+ });
95
+
96
+ it('should have type boolean', () => {
97
+ expect(hasParamType(ctx.component, 'checked', 'boolean')).toBe(true);
98
+ });
99
+ });
100
+
101
+ describe('param: disabled', () => {
102
+ it('should be optional', () => {
103
+ expect(isOptionalParam(ctx.component, 'disabled')).toBe(true);
104
+ });
105
+
106
+ it('should have type boolean', () => {
107
+ expect(hasParamType(ctx.component, 'disabled', 'boolean')).toBe(true);
108
+ });
109
+ });
110
+
111
+ describe('param: name', () => {
112
+ it('should be optional', () => {
113
+ expect(isOptionalParam(ctx.component, 'name')).toBe(true);
114
+ });
115
+
116
+ it('should have type string', () => {
117
+ expect(hasParamType(ctx.component, 'name', 'string')).toBe(true);
118
+ });
119
+ });
120
+
121
+ describe('param: value', () => {
122
+ it('should be optional', () => {
123
+ expect(isOptionalParam(ctx.component, 'value')).toBe(true);
124
+ });
125
+
126
+ it('should have type string', () => {
127
+ expect(hasParamType(ctx.component, 'value', 'string')).toBe(true);
128
+ });
129
+ });
130
+
131
+ describe('param: id', () => {
132
+ it('should be optional', () => {
133
+ expect(isOptionalParam(ctx.component, 'id')).toBe(true);
134
+ });
135
+
136
+ it('should have type string', () => {
137
+ expect(hasParamType(ctx.component, 'id', 'string')).toBe(true);
138
+ });
139
+ });
140
+
141
+ describe('param: ariaLabel', () => {
142
+ it('should be optional', () => {
143
+ expect(isOptionalParam(ctx.component, 'ariaLabel')).toBe(true);
144
+ });
145
+
146
+ it('should have type string', () => {
147
+ expect(hasParamType(ctx.component, 'ariaLabel', 'string')).toBe(true);
148
+ });
149
+ });
150
+ });
151
+
152
+ // ==================== Style Preset Tests ====================
153
+
154
+ describe('Style Preset', () => {
155
+ it('should have valid style preset structure', () => {
156
+ const radioStyles = ctx.styles['radioStyles'];
157
+ expect(radioStyles).toBeDefined();
158
+ assertValidStylePreset(radioStyles);
159
+ });
160
+
161
+ it('should have base classes', () => {
162
+ const radioStyles = ctx.styles['radioStyles'];
163
+ expect(radioStyles.base).toBeDefined();
164
+ expect(typeof radioStyles.base).toBe('string');
165
+ expect(radioStyles.base.length).toBeGreaterThan(0);
166
+ });
167
+
168
+ describe('size options', () => {
169
+ const sizeOptions = ['default', 'sm', 'lg'];
170
+
171
+ it('should have size variants', () => {
172
+ const radioStyles = ctx.styles['radioStyles'];
173
+ expect(hasVariants(radioStyles, ['size'])).toBe(true);
174
+ });
175
+
176
+ it.each(sizeOptions)('should have %s size option', (option) => {
177
+ const radioStyles = ctx.styles['radioStyles'];
178
+ expect(hasVariantOptions(radioStyles, 'size', [option])).toBe(true);
179
+ });
180
+ });
181
+
182
+ describe('default variants', () => {
183
+ it('should have default size set to default', () => {
184
+ const radioStyles = ctx.styles['radioStyles'];
185
+ expect(hasDefaultVariants(radioStyles, { size: 'default' })).toBe(true);
186
+ });
187
+ });
188
+ });
189
+
190
+ // ==================== Accessibility Tests ====================
191
+
192
+ describe('Accessibility', () => {
193
+ it('should support aria-label attribute', () => {
194
+ const ariaLabel = findPropInView(ctx.component.view, 'aria-label');
195
+ expect(ariaLabel).not.toBeNull();
196
+ // Should reference the ariaLabel param
197
+ expect(ariaLabel).toMatchObject({
198
+ expr: 'param',
199
+ name: 'ariaLabel',
200
+ });
201
+ });
202
+
203
+ it('should support disabled attribute', () => {
204
+ const disabled = findPropInView(ctx.component.view, 'disabled');
205
+ expect(disabled).not.toBeNull();
206
+ // Should reference the disabled param
207
+ expect(disabled).toMatchObject({
208
+ expr: 'param',
209
+ name: 'disabled',
210
+ });
211
+ });
212
+ });
213
+
214
+ // ==================== View Props Tests ====================
215
+
216
+ describe('View Props', () => {
217
+ it('should pass checked to input', () => {
218
+ const checked = findPropInView(ctx.component.view, 'checked');
219
+ expect(checked).not.toBeNull();
220
+ expect(checked).toMatchObject({
221
+ expr: 'param',
222
+ name: 'checked',
223
+ });
224
+ });
225
+
226
+ it('should pass name to input', () => {
227
+ const name = findPropInView(ctx.component.view, 'name');
228
+ expect(name).not.toBeNull();
229
+ expect(name).toMatchObject({
230
+ expr: 'param',
231
+ name: 'name',
232
+ });
233
+ });
234
+
235
+ it('should pass value to input', () => {
236
+ const value = findPropInView(ctx.component.view, 'value');
237
+ expect(value).not.toBeNull();
238
+ expect(value).toMatchObject({
239
+ expr: 'param',
240
+ name: 'value',
241
+ });
242
+ });
243
+
244
+ it('should pass id to input', () => {
245
+ const id = findPropInView(ctx.component.view, 'id');
246
+ expect(id).not.toBeNull();
247
+ expect(id).toMatchObject({
248
+ expr: 'param',
249
+ name: 'id',
250
+ });
251
+ });
252
+ });
253
+ });