@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,275 @@
1
+ /**
2
+ * Test suite for Checkbox component
3
+ *
4
+ * @constela/ui Checkbox component tests following TDD methodology.
5
+ * These tests verify the Checkbox 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('Checkbox Component', () => {
32
+ let ctx: ComponentTestContext;
33
+
34
+ beforeAll(() => {
35
+ ctx = loadComponentForTesting('checkbox');
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="checkbox" attribute', () => {
51
+ const type = findPropInView(ctx.component.view, 'type');
52
+ expect(type).not.toBeNull();
53
+ // Type should be a literal 'checkbox'
54
+ expect(type).toMatchObject({
55
+ expr: 'lit',
56
+ value: 'checkbox',
57
+ });
58
+ });
59
+
60
+ it('should NOT contain a slot (checkbox 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: 'checkboxStyles',
71
+ });
72
+ });
73
+
74
+ it('should support checked attribute for localState usage', () => {
75
+ const checked = findPropInView(ctx.component.view, 'checked');
76
+ expect(checked).not.toBeNull();
77
+ // Should reference the checked param
78
+ expect(checked).toMatchObject({
79
+ expr: 'param',
80
+ name: 'checked',
81
+ });
82
+ });
83
+ });
84
+
85
+ // ==================== Params Validation Tests ====================
86
+
87
+ describe('Params Validation', () => {
88
+ const expectedParams = [
89
+ 'checked',
90
+ 'disabled',
91
+ 'required',
92
+ 'name',
93
+ 'id',
94
+ 'ariaLabel',
95
+ 'value',
96
+ ];
97
+
98
+ it('should have all expected params', () => {
99
+ expect(hasParams(ctx.component, expectedParams)).toBe(true);
100
+ });
101
+
102
+ describe('param: checked', () => {
103
+ it('should be optional', () => {
104
+ expect(isOptionalParam(ctx.component, 'checked')).toBe(true);
105
+ });
106
+
107
+ it('should have type boolean', () => {
108
+ expect(hasParamType(ctx.component, 'checked', 'boolean')).toBe(true);
109
+ });
110
+ });
111
+
112
+ describe('param: disabled', () => {
113
+ it('should be optional', () => {
114
+ expect(isOptionalParam(ctx.component, 'disabled')).toBe(true);
115
+ });
116
+
117
+ it('should have type boolean', () => {
118
+ expect(hasParamType(ctx.component, 'disabled', 'boolean')).toBe(true);
119
+ });
120
+ });
121
+
122
+ describe('param: required', () => {
123
+ it('should be optional', () => {
124
+ expect(isOptionalParam(ctx.component, 'required')).toBe(true);
125
+ });
126
+
127
+ it('should have type boolean', () => {
128
+ expect(hasParamType(ctx.component, 'required', 'boolean')).toBe(true);
129
+ });
130
+ });
131
+
132
+ describe('param: name', () => {
133
+ it('should be optional', () => {
134
+ expect(isOptionalParam(ctx.component, 'name')).toBe(true);
135
+ });
136
+
137
+ it('should have type string', () => {
138
+ expect(hasParamType(ctx.component, 'name', 'string')).toBe(true);
139
+ });
140
+ });
141
+
142
+ describe('param: id', () => {
143
+ it('should be optional', () => {
144
+ expect(isOptionalParam(ctx.component, 'id')).toBe(true);
145
+ });
146
+
147
+ it('should have type string', () => {
148
+ expect(hasParamType(ctx.component, 'id', 'string')).toBe(true);
149
+ });
150
+ });
151
+
152
+ describe('param: ariaLabel', () => {
153
+ it('should be optional', () => {
154
+ expect(isOptionalParam(ctx.component, 'ariaLabel')).toBe(true);
155
+ });
156
+
157
+ it('should have type string', () => {
158
+ expect(hasParamType(ctx.component, 'ariaLabel', 'string')).toBe(true);
159
+ });
160
+ });
161
+
162
+ describe('param: value', () => {
163
+ it('should be optional', () => {
164
+ expect(isOptionalParam(ctx.component, 'value')).toBe(true);
165
+ });
166
+
167
+ it('should have type string', () => {
168
+ expect(hasParamType(ctx.component, 'value', 'string')).toBe(true);
169
+ });
170
+ });
171
+ });
172
+
173
+ // ==================== Style Preset Tests ====================
174
+
175
+ describe('Style Preset', () => {
176
+ it('should have valid style preset structure', () => {
177
+ const checkboxStyles = ctx.styles['checkboxStyles'];
178
+ expect(checkboxStyles).toBeDefined();
179
+ assertValidStylePreset(checkboxStyles);
180
+ });
181
+
182
+ it('should have base classes', () => {
183
+ const checkboxStyles = ctx.styles['checkboxStyles'];
184
+ expect(checkboxStyles.base).toBeDefined();
185
+ expect(typeof checkboxStyles.base).toBe('string');
186
+ expect(checkboxStyles.base.length).toBeGreaterThan(0);
187
+ });
188
+
189
+ describe('size options', () => {
190
+ const sizeOptions = ['default', 'sm', 'lg'];
191
+
192
+ it('should have size variants', () => {
193
+ const checkboxStyles = ctx.styles['checkboxStyles'];
194
+ expect(hasVariants(checkboxStyles, ['size'])).toBe(true);
195
+ });
196
+
197
+ it.each(sizeOptions)('should have %s size option', (option) => {
198
+ const checkboxStyles = ctx.styles['checkboxStyles'];
199
+ expect(hasVariantOptions(checkboxStyles, 'size', [option])).toBe(true);
200
+ });
201
+ });
202
+
203
+ describe('default variants', () => {
204
+ it('should have default size set to default', () => {
205
+ const checkboxStyles = ctx.styles['checkboxStyles'];
206
+ expect(hasDefaultVariants(checkboxStyles, { size: 'default' })).toBe(true);
207
+ });
208
+ });
209
+ });
210
+
211
+ // ==================== Accessibility Tests ====================
212
+
213
+ describe('Accessibility', () => {
214
+ it('should support aria-label attribute', () => {
215
+ const ariaLabel = findPropInView(ctx.component.view, 'aria-label');
216
+ expect(ariaLabel).not.toBeNull();
217
+ // Should reference the ariaLabel param
218
+ expect(ariaLabel).toMatchObject({
219
+ expr: 'param',
220
+ name: 'ariaLabel',
221
+ });
222
+ });
223
+
224
+ it('should support disabled attribute', () => {
225
+ const disabled = findPropInView(ctx.component.view, 'disabled');
226
+ expect(disabled).not.toBeNull();
227
+ // Should reference the disabled param
228
+ expect(disabled).toMatchObject({
229
+ expr: 'param',
230
+ name: 'disabled',
231
+ });
232
+ });
233
+
234
+ it('should support required attribute', () => {
235
+ const required = findPropInView(ctx.component.view, 'required');
236
+ expect(required).not.toBeNull();
237
+ // Should reference the required param
238
+ expect(required).toMatchObject({
239
+ expr: 'param',
240
+ name: 'required',
241
+ });
242
+ });
243
+ });
244
+
245
+ // ==================== View Props Tests ====================
246
+
247
+ describe('View Props', () => {
248
+ it('should pass name to checkbox', () => {
249
+ const name = findPropInView(ctx.component.view, 'name');
250
+ expect(name).not.toBeNull();
251
+ expect(name).toMatchObject({
252
+ expr: 'param',
253
+ name: 'name',
254
+ });
255
+ });
256
+
257
+ it('should pass id to checkbox', () => {
258
+ const id = findPropInView(ctx.component.view, 'id');
259
+ expect(id).not.toBeNull();
260
+ expect(id).toMatchObject({
261
+ expr: 'param',
262
+ name: 'id',
263
+ });
264
+ });
265
+
266
+ it('should pass value to checkbox', () => {
267
+ const value = findPropInView(ctx.component.view, 'value');
268
+ expect(value).not.toBeNull();
269
+ expect(value).toMatchObject({
270
+ expr: 'param',
271
+ name: 'value',
272
+ });
273
+ });
274
+ });
275
+ });
@@ -0,0 +1,21 @@
1
+ {
2
+ "params": {
3
+ "maxWidth": { "type": "string", "required": false },
4
+ "className": { "type": "string", "required": false }
5
+ },
6
+ "view": {
7
+ "kind": "element",
8
+ "tag": "div",
9
+ "props": {
10
+ "className": {
11
+ "expr": "style",
12
+ "preset": "containerStyles",
13
+ "props": {
14
+ "maxWidth": { "expr": "param", "name": "maxWidth" },
15
+ "className": { "expr": "param", "name": "className" }
16
+ }
17
+ }
18
+ },
19
+ "children": [{ "kind": "slot" }]
20
+ }
21
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "containerStyles": {
3
+ "base": "mx-auto w-full px-4 sm:px-6 lg:px-8",
4
+ "variants": {
5
+ "maxWidth": {
6
+ "sm": "max-w-screen-sm",
7
+ "md": "max-w-screen-md",
8
+ "lg": "max-w-screen-lg",
9
+ "xl": "max-w-screen-xl",
10
+ "2xl": "max-w-screen-2xl",
11
+ "full": "max-w-full"
12
+ }
13
+ },
14
+ "defaultVariants": {
15
+ "maxWidth": "lg"
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Test suite for Container component
3
+ *
4
+ * @constela/ui Container component tests following TDD methodology.
5
+ * These tests verify the Container component structure, params, and styles.
6
+ *
7
+ * Coverage:
8
+ * - Component structure validation
9
+ * - Params definition validation
10
+ * - Style preset validation
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('Container Component', () => {
31
+ let ctx: ComponentTestContext;
32
+
33
+ beforeAll(() => {
34
+ ctx = loadComponentForTesting('container');
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 content', () => {
50
+ expect(hasSlot(ctx.component.view)).toBe(true);
51
+ });
52
+
53
+ it('should have className using StyleExpr with containerStyles preset', () => {
54
+ const className = findPropInView(ctx.component.view, 'className');
55
+ expect(className).not.toBeNull();
56
+ expect(className).toMatchObject({
57
+ expr: 'style',
58
+ preset: 'containerStyles',
59
+ });
60
+ });
61
+ });
62
+
63
+ // ==================== Params Validation Tests ====================
64
+
65
+ describe('Params Validation', () => {
66
+ const expectedParams = ['maxWidth', 'className'];
67
+
68
+ it('should have all expected params', () => {
69
+ expect(hasParams(ctx.component, expectedParams)).toBe(true);
70
+ });
71
+
72
+ describe('param: maxWidth', () => {
73
+ it('should be optional', () => {
74
+ expect(isOptionalParam(ctx.component, 'maxWidth')).toBe(true);
75
+ });
76
+
77
+ it('should have type string', () => {
78
+ expect(hasParamType(ctx.component, 'maxWidth', 'string')).toBe(true);
79
+ });
80
+ });
81
+
82
+ describe('param: className', () => {
83
+ it('should be optional', () => {
84
+ expect(isOptionalParam(ctx.component, 'className')).toBe(true);
85
+ });
86
+
87
+ it('should have type string', () => {
88
+ expect(hasParamType(ctx.component, 'className', 'string')).toBe(true);
89
+ });
90
+ });
91
+ });
92
+
93
+ // ==================== Style Preset Tests ====================
94
+
95
+ describe('Style Preset', () => {
96
+ it('should have valid style preset structure', () => {
97
+ const containerStyles = ctx.styles['containerStyles'];
98
+ expect(containerStyles).toBeDefined();
99
+ assertValidStylePreset(containerStyles);
100
+ });
101
+
102
+ it('should have base classes for common container styles', () => {
103
+ const containerStyles = ctx.styles['containerStyles'];
104
+ expect(containerStyles.base).toBeDefined();
105
+ expect(typeof containerStyles.base).toBe('string');
106
+ expect(containerStyles.base.length).toBeGreaterThan(0);
107
+ });
108
+
109
+ it('should include mx-auto in base classes', () => {
110
+ const containerStyles = ctx.styles['containerStyles'];
111
+ expect(containerStyles.base).toContain('mx-auto');
112
+ });
113
+
114
+ it('should include px-4 in base classes', () => {
115
+ const containerStyles = ctx.styles['containerStyles'];
116
+ expect(containerStyles.base).toContain('px-4');
117
+ });
118
+
119
+ describe('maxWidth variants', () => {
120
+ const maxWidthOptions = ['sm', 'md', 'lg', 'xl', '2xl', 'full'];
121
+
122
+ it('should have maxWidth variants', () => {
123
+ const containerStyles = ctx.styles['containerStyles'];
124
+ expect(hasVariants(containerStyles, ['maxWidth'])).toBe(true);
125
+ });
126
+
127
+ it.each(maxWidthOptions)('should have %s maxWidth option', (option) => {
128
+ const containerStyles = ctx.styles['containerStyles'];
129
+ expect(hasVariantOptions(containerStyles, 'maxWidth', [option])).toBe(true);
130
+ });
131
+ });
132
+
133
+ describe('default variants', () => {
134
+ it('should have default maxWidth set to lg', () => {
135
+ const containerStyles = ctx.styles['containerStyles'];
136
+ expect(hasDefaultVariants(containerStyles, { maxWidth: 'lg' })).toBe(true);
137
+ });
138
+ });
139
+ });
140
+
141
+ // ==================== View Props Tests ====================
142
+
143
+ describe('View Props', () => {
144
+ it('should pass maxWidth to StyleExpr', () => {
145
+ const className = findPropInView(ctx.component.view, 'className');
146
+ expect(className).toMatchObject({
147
+ expr: 'style',
148
+ props: expect.objectContaining({
149
+ maxWidth: expect.objectContaining({ expr: 'param', name: 'maxWidth' }),
150
+ }),
151
+ });
152
+ });
153
+
154
+ it('should pass className to StyleExpr', () => {
155
+ const className = findPropInView(ctx.component.view, 'className');
156
+ expect(className).toMatchObject({
157
+ expr: 'style',
158
+ props: expect.objectContaining({
159
+ className: expect.objectContaining({ expr: 'param', name: 'className' }),
160
+ }),
161
+ });
162
+ });
163
+ });
164
+ });
@@ -0,0 +1,19 @@
1
+ {
2
+ "params": {
3
+ "open": { "type": "boolean", "required": false },
4
+ "title": { "type": "string", "required": false }
5
+ },
6
+ "view": {
7
+ "kind": "element",
8
+ "tag": "div",
9
+ "props": {
10
+ "role": { "expr": "lit", "value": "dialog" },
11
+ "aria-modal": { "expr": "lit", "value": "true" },
12
+ "className": {
13
+ "expr": "style",
14
+ "preset": "dialogStyles"
15
+ }
16
+ },
17
+ "children": [{ "kind": "slot" }]
18
+ }
19
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "dialogStyles": {
3
+ "base": "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg"
4
+ }
5
+ }
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Test suite for Dialog component
3
+ *
4
+ * @constela/ui Dialog component tests following TDD methodology.
5
+ * These tests verify the Dialog component structure, params, styles, and accessibility.
6
+ *
7
+ * Coverage:
8
+ * - Component structure validation
9
+ * - Params definition validation
10
+ * - Style preset validation
11
+ * - Accessibility attributes (role="dialog", aria-modal="true")
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
+ hasSlot,
24
+ findPropInView,
25
+ hasRole,
26
+ hasAriaAttribute,
27
+ type ComponentTestContext,
28
+ } from '../../tests/helpers/test-utils.js';
29
+
30
+ describe('Dialog Component', () => {
31
+ let ctx: ComponentTestContext;
32
+
33
+ beforeAll(() => {
34
+ ctx = loadComponentForTesting('dialog');
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 have role="dialog" attribute', () => {
50
+ expect(hasRole(ctx.component.view, 'dialog')).toBe(true);
51
+ });
52
+
53
+ it('should have aria-modal="true" attribute', () => {
54
+ expect(hasAriaAttribute(ctx.component.view, 'aria-modal')).toBe(true);
55
+ });
56
+
57
+ it('should contain a slot for dialog content', () => {
58
+ expect(hasSlot(ctx.component.view)).toBe(true);
59
+ });
60
+
61
+ it('should have className using StyleExpr with dialogStyles preset', () => {
62
+ const className = findPropInView(ctx.component.view, 'className');
63
+ expect(className).not.toBeNull();
64
+ expect(className).toMatchObject({
65
+ expr: 'style',
66
+ preset: 'dialogStyles',
67
+ });
68
+ });
69
+ });
70
+
71
+ // ==================== Params Validation Tests ====================
72
+
73
+ describe('Params Validation', () => {
74
+ const expectedParams = ['open', 'title'];
75
+
76
+ it('should have all expected params', () => {
77
+ expect(hasParams(ctx.component, expectedParams)).toBe(true);
78
+ });
79
+
80
+ describe('param: open', () => {
81
+ it('should be optional', () => {
82
+ expect(isOptionalParam(ctx.component, 'open')).toBe(true);
83
+ });
84
+
85
+ it('should have type boolean', () => {
86
+ expect(hasParamType(ctx.component, 'open', 'boolean')).toBe(true);
87
+ });
88
+ });
89
+
90
+ describe('param: title', () => {
91
+ it('should be optional', () => {
92
+ expect(isOptionalParam(ctx.component, 'title')).toBe(true);
93
+ });
94
+
95
+ it('should have type string', () => {
96
+ expect(hasParamType(ctx.component, 'title', 'string')).toBe(true);
97
+ });
98
+ });
99
+ });
100
+
101
+ // ==================== Style Preset Tests ====================
102
+
103
+ describe('Style Preset', () => {
104
+ it('should have valid style preset structure', () => {
105
+ const dialogStyles = ctx.styles['dialogStyles'];
106
+ expect(dialogStyles).toBeDefined();
107
+ assertValidStylePreset(dialogStyles);
108
+ });
109
+
110
+ it('should have base classes for common dialog styles', () => {
111
+ const dialogStyles = ctx.styles['dialogStyles'];
112
+ expect(dialogStyles.base).toBeDefined();
113
+ expect(typeof dialogStyles.base).toBe('string');
114
+ expect(dialogStyles.base.length).toBeGreaterThan(0);
115
+ });
116
+ });
117
+
118
+ // ==================== Accessibility Tests ====================
119
+
120
+ describe('Accessibility', () => {
121
+ it('should have role="dialog" for screen readers', () => {
122
+ const role = findPropInView(ctx.component.view, 'role');
123
+ expect(role).not.toBeNull();
124
+ expect(role).toMatchObject({
125
+ expr: 'lit',
126
+ value: 'dialog',
127
+ });
128
+ });
129
+
130
+ it('should have aria-modal="true" to indicate modal behavior', () => {
131
+ const ariaModal = findPropInView(ctx.component.view, 'aria-modal');
132
+ expect(ariaModal).not.toBeNull();
133
+ expect(ariaModal).toMatchObject({
134
+ expr: 'lit',
135
+ value: 'true',
136
+ });
137
+ });
138
+ });
139
+ });
@@ -0,0 +1,23 @@
1
+ {
2
+ "params": {
3
+ "cols": { "type": "string", "required": false },
4
+ "gap": { "type": "string", "required": false },
5
+ "className": { "type": "string", "required": false }
6
+ },
7
+ "view": {
8
+ "kind": "element",
9
+ "tag": "div",
10
+ "props": {
11
+ "className": {
12
+ "expr": "style",
13
+ "preset": "gridStyles",
14
+ "props": {
15
+ "cols": { "expr": "param", "name": "cols" },
16
+ "gap": { "expr": "param", "name": "gap" },
17
+ "className": { "expr": "param", "name": "className" }
18
+ }
19
+ }
20
+ },
21
+ "children": [{ "kind": "slot" }]
22
+ }
23
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "gridStyles": {
3
+ "base": "grid",
4
+ "variants": {
5
+ "cols": {
6
+ "1": "grid-cols-1",
7
+ "2": "grid-cols-2",
8
+ "3": "grid-cols-3",
9
+ "4": "grid-cols-4",
10
+ "6": "grid-cols-6",
11
+ "12": "grid-cols-12"
12
+ },
13
+ "gap": {
14
+ "none": "gap-0",
15
+ "sm": "gap-2",
16
+ "md": "gap-4",
17
+ "lg": "gap-8"
18
+ }
19
+ },
20
+ "defaultVariants": {
21
+ "cols": "1",
22
+ "gap": "md"
23
+ }
24
+ }
25
+ }