@constela/ui 0.2.4 → 0.3.1
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.
- package/README.md +174 -3
- package/components/accordion/accordion-content.constela.json +20 -0
- package/components/accordion/accordion-item.constela.json +20 -0
- package/components/accordion/accordion-trigger.constela.json +21 -0
- package/components/accordion/accordion.constela.json +18 -0
- package/components/accordion/accordion.styles.json +54 -0
- package/components/accordion/accordion.test.ts +608 -0
- package/components/calendar/calendar.constela.json +195 -0
- package/components/calendar/calendar.styles.json +33 -0
- package/components/calendar/calendar.test.ts +458 -0
- package/components/chart/area-chart.constela.json +482 -0
- package/components/chart/bar-chart.constela.json +342 -0
- package/components/chart/chart-axis.constela.json +224 -0
- package/components/chart/chart-legend.constela.json +82 -0
- package/components/chart/chart-tooltip.constela.json +61 -0
- package/components/chart/chart.styles.json +183 -0
- package/components/chart/chart.test.ts +3260 -0
- package/components/chart/donut-chart.constela.json +369 -0
- package/components/chart/line-chart.constela.json +380 -0
- package/components/chart/pie-chart.constela.json +259 -0
- package/components/chart/radar-chart.constela.json +297 -0
- package/components/chart/scatter-chart.constela.json +300 -0
- package/components/data-table/data-table-cell.constela.json +22 -0
- package/components/data-table/data-table-header.constela.json +30 -0
- package/components/data-table/data-table-pagination.constela.json +19 -0
- package/components/data-table/data-table-row.constela.json +30 -0
- package/components/data-table/data-table.constela.json +32 -0
- package/components/data-table/data-table.styles.json +84 -0
- package/components/data-table/data-table.test.ts +873 -0
- package/components/datepicker/datepicker.constela.json +128 -0
- package/components/datepicker/datepicker.styles.json +47 -0
- package/components/datepicker/datepicker.test.ts +540 -0
- package/components/tree/tree-node.constela.json +26 -0
- package/components/tree/tree.constela.json +24 -0
- package/components/tree/tree.styles.json +50 -0
- package/components/tree/tree.test.ts +542 -0
- package/components/virtual-scroll/virtual-scroll.constela.json +27 -0
- package/components/virtual-scroll/virtual-scroll.styles.json +17 -0
- package/components/virtual-scroll/virtual-scroll.test.ts +345 -0
- package/package.json +2 -2
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test suite for DatePicker component
|
|
3
|
+
*
|
|
4
|
+
* @constela/ui DatePicker component tests following TDD methodology.
|
|
5
|
+
* These tests verify the DatePicker component structure, params, styles, and integration.
|
|
6
|
+
*
|
|
7
|
+
* Coverage:
|
|
8
|
+
* - Component structure validation (input + popover + calendar integration)
|
|
9
|
+
* - Params definition validation
|
|
10
|
+
* - Style preset validation (container, input, icon)
|
|
11
|
+
* - Input display and formatting
|
|
12
|
+
* - Calendar popover integration
|
|
13
|
+
* - Form integration
|
|
14
|
+
* - Disabled state handling
|
|
15
|
+
* - Accessibility attributes
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { describe, it, expect, beforeAll } from 'vitest';
|
|
19
|
+
import {
|
|
20
|
+
loadComponentForTesting,
|
|
21
|
+
assertValidComponent,
|
|
22
|
+
assertValidStylePreset,
|
|
23
|
+
hasParams,
|
|
24
|
+
isOptionalParam,
|
|
25
|
+
hasParamType,
|
|
26
|
+
getRootTag,
|
|
27
|
+
hasVariants,
|
|
28
|
+
hasVariantOptions,
|
|
29
|
+
hasDefaultVariants,
|
|
30
|
+
findPropInView,
|
|
31
|
+
hasAriaAttribute,
|
|
32
|
+
type ComponentTestContext,
|
|
33
|
+
} from '../../tests/helpers/test-utils.js';
|
|
34
|
+
|
|
35
|
+
describe('DatePicker Component', () => {
|
|
36
|
+
let ctx: ComponentTestContext;
|
|
37
|
+
|
|
38
|
+
beforeAll(() => {
|
|
39
|
+
ctx = loadComponentForTesting('datepicker');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// ==================== Component Structure Tests ====================
|
|
43
|
+
|
|
44
|
+
describe('Component Structure', () => {
|
|
45
|
+
it('should have valid component structure', () => {
|
|
46
|
+
assertValidComponent(ctx.component);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should have div as root element', () => {
|
|
50
|
+
const rootTag = getRootTag(ctx.component);
|
|
51
|
+
expect(rootTag).toBe('div');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should have className using StyleExpr with datepickerStyles preset', () => {
|
|
55
|
+
const className = findPropInView(ctx.component.view, 'className');
|
|
56
|
+
expect(className).not.toBeNull();
|
|
57
|
+
expect(className).toMatchObject({
|
|
58
|
+
expr: 'style',
|
|
59
|
+
preset: 'datepickerStyles',
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should contain input element for displaying selected date', () => {
|
|
64
|
+
const view = ctx.component.view;
|
|
65
|
+
expect(view.kind).toBe('element');
|
|
66
|
+
if (view.kind === 'element') {
|
|
67
|
+
expect(view.children).toBeDefined();
|
|
68
|
+
// Should have at least input and popover/calendar structure
|
|
69
|
+
expect(view.children!.length).toBeGreaterThan(0);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should contain hidden input for form submission', () => {
|
|
74
|
+
// Hidden input with name attribute for form data
|
|
75
|
+
const view = ctx.component.view;
|
|
76
|
+
expect(view).toBeDefined();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should have aria-label for accessibility', () => {
|
|
80
|
+
expect(hasAriaAttribute(ctx.component.view, 'label')).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// ==================== Params Validation Tests ====================
|
|
85
|
+
|
|
86
|
+
describe('Params Validation', () => {
|
|
87
|
+
const expectedParams = [
|
|
88
|
+
'value',
|
|
89
|
+
'placeholder',
|
|
90
|
+
'format',
|
|
91
|
+
'min',
|
|
92
|
+
'max',
|
|
93
|
+
'disabled',
|
|
94
|
+
'required',
|
|
95
|
+
'name',
|
|
96
|
+
'id',
|
|
97
|
+
'locale',
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
it('should have all expected params', () => {
|
|
101
|
+
expect(hasParams(ctx.component, expectedParams)).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('param: value', () => {
|
|
105
|
+
it('should be optional', () => {
|
|
106
|
+
expect(isOptionalParam(ctx.component, 'value')).toBe(true);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should have type string', () => {
|
|
110
|
+
expect(hasParamType(ctx.component, 'value', 'string')).toBe(true);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('param: placeholder', () => {
|
|
115
|
+
it('should be optional', () => {
|
|
116
|
+
expect(isOptionalParam(ctx.component, 'placeholder')).toBe(true);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should have type string', () => {
|
|
120
|
+
expect(hasParamType(ctx.component, 'placeholder', 'string')).toBe(true);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe('param: format', () => {
|
|
125
|
+
it('should be optional', () => {
|
|
126
|
+
expect(isOptionalParam(ctx.component, 'format')).toBe(true);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should have type string', () => {
|
|
130
|
+
expect(hasParamType(ctx.component, 'format', 'string')).toBe(true);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe('param: min', () => {
|
|
135
|
+
it('should be optional', () => {
|
|
136
|
+
expect(isOptionalParam(ctx.component, 'min')).toBe(true);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should have type string', () => {
|
|
140
|
+
expect(hasParamType(ctx.component, 'min', 'string')).toBe(true);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe('param: max', () => {
|
|
145
|
+
it('should be optional', () => {
|
|
146
|
+
expect(isOptionalParam(ctx.component, 'max')).toBe(true);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should have type string', () => {
|
|
150
|
+
expect(hasParamType(ctx.component, 'max', 'string')).toBe(true);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe('param: disabled', () => {
|
|
155
|
+
it('should be optional', () => {
|
|
156
|
+
expect(isOptionalParam(ctx.component, 'disabled')).toBe(true);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('should have type boolean', () => {
|
|
160
|
+
expect(hasParamType(ctx.component, 'disabled', 'boolean')).toBe(true);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
describe('param: required', () => {
|
|
165
|
+
it('should be optional', () => {
|
|
166
|
+
expect(isOptionalParam(ctx.component, 'required')).toBe(true);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should have type boolean', () => {
|
|
170
|
+
expect(hasParamType(ctx.component, 'required', 'boolean')).toBe(true);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe('param: name', () => {
|
|
175
|
+
it('should be optional', () => {
|
|
176
|
+
expect(isOptionalParam(ctx.component, 'name')).toBe(true);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('should have type string', () => {
|
|
180
|
+
expect(hasParamType(ctx.component, 'name', 'string')).toBe(true);
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
describe('param: id', () => {
|
|
185
|
+
it('should be optional', () => {
|
|
186
|
+
expect(isOptionalParam(ctx.component, 'id')).toBe(true);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('should have type string', () => {
|
|
190
|
+
expect(hasParamType(ctx.component, 'id', 'string')).toBe(true);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe('param: locale', () => {
|
|
195
|
+
it('should be optional', () => {
|
|
196
|
+
expect(isOptionalParam(ctx.component, 'locale')).toBe(true);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('should have type string', () => {
|
|
200
|
+
expect(hasParamType(ctx.component, 'locale', 'string')).toBe(true);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// ==================== Style Preset Tests: datepickerStyles ====================
|
|
206
|
+
|
|
207
|
+
describe('Style Preset: datepickerStyles', () => {
|
|
208
|
+
it('should have valid style preset structure', () => {
|
|
209
|
+
const datepickerStyles = ctx.styles['datepickerStyles'];
|
|
210
|
+
expect(datepickerStyles).toBeDefined();
|
|
211
|
+
assertValidStylePreset(datepickerStyles);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('should have base classes for container', () => {
|
|
215
|
+
const datepickerStyles = ctx.styles['datepickerStyles'];
|
|
216
|
+
expect(datepickerStyles.base).toBeDefined();
|
|
217
|
+
expect(typeof datepickerStyles.base).toBe('string');
|
|
218
|
+
expect(datepickerStyles.base.length).toBeGreaterThan(0);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('should include relative positioning for popover anchor', () => {
|
|
222
|
+
const datepickerStyles = ctx.styles['datepickerStyles'];
|
|
223
|
+
expect(datepickerStyles.base).toContain('relative');
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe('size variants', () => {
|
|
227
|
+
const sizeOptions = ['default', 'sm', 'lg'];
|
|
228
|
+
|
|
229
|
+
it('should have size variants', () => {
|
|
230
|
+
const datepickerStyles = ctx.styles['datepickerStyles'];
|
|
231
|
+
expect(hasVariants(datepickerStyles, ['size'])).toBe(true);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it.each(sizeOptions)('should have %s size option', (option) => {
|
|
235
|
+
const datepickerStyles = ctx.styles['datepickerStyles'];
|
|
236
|
+
expect(hasVariantOptions(datepickerStyles, 'size', [option])).toBe(true);
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe('default variants', () => {
|
|
241
|
+
it('should have default size set to default', () => {
|
|
242
|
+
const datepickerStyles = ctx.styles['datepickerStyles'];
|
|
243
|
+
expect(hasDefaultVariants(datepickerStyles, { size: 'default' })).toBe(true);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// ==================== Style Preset Tests: datepickerInputStyles ====================
|
|
249
|
+
|
|
250
|
+
describe('Style Preset: datepickerInputStyles', () => {
|
|
251
|
+
it('should have datepickerInputStyles preset', () => {
|
|
252
|
+
const datepickerInputStyles = ctx.styles['datepickerInputStyles'];
|
|
253
|
+
expect(datepickerInputStyles).toBeDefined();
|
|
254
|
+
assertValidStylePreset(datepickerInputStyles);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('should have base classes for input field', () => {
|
|
258
|
+
const datepickerInputStyles = ctx.styles['datepickerInputStyles'];
|
|
259
|
+
expect(datepickerInputStyles.base).toBeDefined();
|
|
260
|
+
expect(typeof datepickerInputStyles.base).toBe('string');
|
|
261
|
+
expect(datepickerInputStyles.base.length).toBeGreaterThan(0);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('should include padding for icon space', () => {
|
|
265
|
+
const datepickerInputStyles = ctx.styles['datepickerInputStyles'];
|
|
266
|
+
// Should have right padding for calendar icon
|
|
267
|
+
expect(datepickerInputStyles.base).toMatch(/pr-\d+/);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
describe('state variants', () => {
|
|
271
|
+
const stateOptions = ['default', 'focused', 'disabled', 'error'];
|
|
272
|
+
|
|
273
|
+
it('should have state variants', () => {
|
|
274
|
+
const datepickerInputStyles = ctx.styles['datepickerInputStyles'];
|
|
275
|
+
expect(hasVariants(datepickerInputStyles, ['state'])).toBe(true);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it.each(stateOptions)('should have %s state option', (option) => {
|
|
279
|
+
const datepickerInputStyles = ctx.styles['datepickerInputStyles'];
|
|
280
|
+
expect(hasVariantOptions(datepickerInputStyles, 'state', [option])).toBe(true);
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
describe('size variants', () => {
|
|
285
|
+
const sizeOptions = ['default', 'sm', 'lg'];
|
|
286
|
+
|
|
287
|
+
it('should have size variants', () => {
|
|
288
|
+
const datepickerInputStyles = ctx.styles['datepickerInputStyles'];
|
|
289
|
+
expect(hasVariants(datepickerInputStyles, ['size'])).toBe(true);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it.each(sizeOptions)('should have %s size option', (option) => {
|
|
293
|
+
const datepickerInputStyles = ctx.styles['datepickerInputStyles'];
|
|
294
|
+
expect(hasVariantOptions(datepickerInputStyles, 'size', [option])).toBe(true);
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// ==================== Style Preset Tests: datepickerIconStyles ====================
|
|
300
|
+
|
|
301
|
+
describe('Style Preset: datepickerIconStyles', () => {
|
|
302
|
+
it('should have datepickerIconStyles preset', () => {
|
|
303
|
+
const datepickerIconStyles = ctx.styles['datepickerIconStyles'];
|
|
304
|
+
expect(datepickerIconStyles).toBeDefined();
|
|
305
|
+
assertValidStylePreset(datepickerIconStyles);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('should have base classes for calendar icon', () => {
|
|
309
|
+
const datepickerIconStyles = ctx.styles['datepickerIconStyles'];
|
|
310
|
+
expect(datepickerIconStyles.base).toBeDefined();
|
|
311
|
+
expect(typeof datepickerIconStyles.base).toBe('string');
|
|
312
|
+
expect(datepickerIconStyles.base.length).toBeGreaterThan(0);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('should include absolute positioning', () => {
|
|
316
|
+
const datepickerIconStyles = ctx.styles['datepickerIconStyles'];
|
|
317
|
+
expect(datepickerIconStyles.base).toContain('absolute');
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('should include right alignment', () => {
|
|
321
|
+
const datepickerIconStyles = ctx.styles['datepickerIconStyles'];
|
|
322
|
+
expect(datepickerIconStyles.base).toMatch(/right-\d+/);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('should include pointer cursor for clickable icon', () => {
|
|
326
|
+
const datepickerIconStyles = ctx.styles['datepickerIconStyles'];
|
|
327
|
+
expect(datepickerIconStyles.base).toContain('cursor-pointer');
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
describe('state variants', () => {
|
|
331
|
+
const stateOptions = ['default', 'disabled'];
|
|
332
|
+
|
|
333
|
+
it('should have state variants', () => {
|
|
334
|
+
const datepickerIconStyles = ctx.styles['datepickerIconStyles'];
|
|
335
|
+
expect(hasVariants(datepickerIconStyles, ['state'])).toBe(true);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it.each(stateOptions)('should have %s state option', (option) => {
|
|
339
|
+
const datepickerIconStyles = ctx.styles['datepickerIconStyles'];
|
|
340
|
+
expect(hasVariantOptions(datepickerIconStyles, 'state', [option])).toBe(true);
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// ==================== Input Display Tests ====================
|
|
346
|
+
|
|
347
|
+
describe('Input Display', () => {
|
|
348
|
+
it('should show placeholder when no value is set', () => {
|
|
349
|
+
const placeholderParam = ctx.component.params?.['placeholder'];
|
|
350
|
+
expect(placeholderParam).toBeDefined();
|
|
351
|
+
expect(placeholderParam?.type).toBe('string');
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it('should support format param for date display', () => {
|
|
355
|
+
const formatParam = ctx.component.params?.['format'];
|
|
356
|
+
expect(formatParam).toBeDefined();
|
|
357
|
+
expect(formatParam?.type).toBe('string');
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it('should support locale param for localized display', () => {
|
|
361
|
+
const localeParam = ctx.component.params?.['locale'];
|
|
362
|
+
expect(localeParam).toBeDefined();
|
|
363
|
+
expect(localeParam?.type).toBe('string');
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it('should display value when set', () => {
|
|
367
|
+
const valueParam = ctx.component.params?.['value'];
|
|
368
|
+
expect(valueParam).toBeDefined();
|
|
369
|
+
// value should be used to display formatted date in input
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it('should make input read-only (date selection via calendar only)', () => {
|
|
373
|
+
// Input should be readonly to prevent manual text entry
|
|
374
|
+
const view = ctx.component.view;
|
|
375
|
+
expect(view).toBeDefined();
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
// ==================== Calendar Popover Integration Tests ====================
|
|
380
|
+
|
|
381
|
+
describe('Calendar Popover Integration', () => {
|
|
382
|
+
it('should integrate with popover component', () => {
|
|
383
|
+
// Should contain popover element or component reference
|
|
384
|
+
const view = ctx.component.view;
|
|
385
|
+
expect(view).toBeDefined();
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it('should integrate with calendar component', () => {
|
|
389
|
+
// Should contain calendar element or component reference
|
|
390
|
+
const view = ctx.component.view;
|
|
391
|
+
expect(view).toBeDefined();
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it('should pass min prop to calendar', () => {
|
|
395
|
+
const minParam = ctx.component.params?.['min'];
|
|
396
|
+
expect(minParam).toBeDefined();
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it('should pass max prop to calendar', () => {
|
|
400
|
+
const maxParam = ctx.component.params?.['max'];
|
|
401
|
+
expect(maxParam).toBeDefined();
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it('should pass locale prop to calendar', () => {
|
|
405
|
+
const localeParam = ctx.component.params?.['locale'];
|
|
406
|
+
expect(localeParam).toBeDefined();
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
it('should have internal state for calendar open/close', () => {
|
|
410
|
+
// Component should manage open state for popover
|
|
411
|
+
const view = ctx.component.view;
|
|
412
|
+
expect(view).toBeDefined();
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
// ==================== Form Integration Tests ====================
|
|
417
|
+
|
|
418
|
+
describe('Form Integration', () => {
|
|
419
|
+
it('should support name param for form submission', () => {
|
|
420
|
+
const nameParam = ctx.component.params?.['name'];
|
|
421
|
+
expect(nameParam).toBeDefined();
|
|
422
|
+
expect(nameParam?.type).toBe('string');
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
it('should support required param', () => {
|
|
426
|
+
const requiredParam = ctx.component.params?.['required'];
|
|
427
|
+
expect(requiredParam).toBeDefined();
|
|
428
|
+
expect(requiredParam?.type).toBe('boolean');
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
it('should support id param', () => {
|
|
432
|
+
const idParam = ctx.component.params?.['id'];
|
|
433
|
+
expect(idParam).toBeDefined();
|
|
434
|
+
expect(idParam?.type).toBe('string');
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
it('should have hidden input with ISO format value for form data', () => {
|
|
438
|
+
// Hidden input should always contain YYYY-MM-DD format
|
|
439
|
+
const view = ctx.component.view;
|
|
440
|
+
expect(view).toBeDefined();
|
|
441
|
+
});
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
// ==================== Disabled State Tests ====================
|
|
445
|
+
|
|
446
|
+
describe('Disabled State', () => {
|
|
447
|
+
it('should support disabled param', () => {
|
|
448
|
+
const disabledParam = ctx.component.params?.['disabled'];
|
|
449
|
+
expect(disabledParam).toBeDefined();
|
|
450
|
+
expect(disabledParam?.type).toBe('boolean');
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
it('should apply disabled styling when disabled is true', () => {
|
|
454
|
+
// Input and icon should reflect disabled state
|
|
455
|
+
const view = ctx.component.view;
|
|
456
|
+
expect(view).toBeDefined();
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
it('should prevent calendar opening when disabled', () => {
|
|
460
|
+
// Click handlers should check disabled state
|
|
461
|
+
const view = ctx.component.view;
|
|
462
|
+
expect(view).toBeDefined();
|
|
463
|
+
});
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
// ==================== Accessibility Tests ====================
|
|
467
|
+
|
|
468
|
+
describe('Accessibility', () => {
|
|
469
|
+
it('should have aria-label for date picker', () => {
|
|
470
|
+
expect(hasAriaAttribute(ctx.component.view, 'label')).toBe(true);
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
it('should have aria-haspopup attribute', () => {
|
|
474
|
+
// Input should indicate it opens a popup
|
|
475
|
+
expect(hasAriaAttribute(ctx.component.view, 'haspopup')).toBe(true);
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
it('should have aria-expanded attribute', () => {
|
|
479
|
+
// Should reflect whether calendar is open
|
|
480
|
+
expect(hasAriaAttribute(ctx.component.view, 'expanded')).toBe(true);
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
it('should support keyboard navigation', () => {
|
|
484
|
+
// Should be focusable and support Enter/Space to open
|
|
485
|
+
const view = ctx.component.view;
|
|
486
|
+
expect(view).toBeDefined();
|
|
487
|
+
});
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
// ==================== View Props Tests ====================
|
|
491
|
+
|
|
492
|
+
describe('View Props', () => {
|
|
493
|
+
it('should pass value to component', () => {
|
|
494
|
+
const params = ctx.component.params;
|
|
495
|
+
expect(params?.['value']).toBeDefined();
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
it('should pass placeholder to input', () => {
|
|
499
|
+
const params = ctx.component.params;
|
|
500
|
+
expect(params?.['placeholder']).toBeDefined();
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
it('should pass disabled to component', () => {
|
|
504
|
+
const params = ctx.component.params;
|
|
505
|
+
expect(params?.['disabled']).toBeDefined();
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
it('should pass format to component', () => {
|
|
509
|
+
const params = ctx.component.params;
|
|
510
|
+
expect(params?.['format']).toBeDefined();
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
it('should pass locale to component', () => {
|
|
514
|
+
const params = ctx.component.params;
|
|
515
|
+
expect(params?.['locale']).toBeDefined();
|
|
516
|
+
});
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
// ==================== Events Tests ====================
|
|
520
|
+
|
|
521
|
+
describe('Events', () => {
|
|
522
|
+
it('should emit @change event when date is selected', () => {
|
|
523
|
+
// Component should have event handler for date selection
|
|
524
|
+
const view = ctx.component.view;
|
|
525
|
+
expect(view).toBeDefined();
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
it('should emit @open event when calendar opens', () => {
|
|
529
|
+
// Component should emit event on popover open
|
|
530
|
+
const view = ctx.component.view;
|
|
531
|
+
expect(view).toBeDefined();
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
it('should emit @close event when calendar closes', () => {
|
|
535
|
+
// Component should emit event on popover close
|
|
536
|
+
const view = ctx.component.view;
|
|
537
|
+
expect(view).toBeDefined();
|
|
538
|
+
});
|
|
539
|
+
});
|
|
540
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"params": {
|
|
3
|
+
"node": { "type": "object", "required": true },
|
|
4
|
+
"level": { "type": "number", "required": true },
|
|
5
|
+
"expandedNodes": { "type": "list", "required": true },
|
|
6
|
+
"selectedNodes": { "type": "list", "required": true },
|
|
7
|
+
"expanded": { "type": "boolean", "required": false },
|
|
8
|
+
"selected": { "type": "boolean", "required": false },
|
|
9
|
+
"selectable": { "type": "boolean", "required": false }
|
|
10
|
+
},
|
|
11
|
+
"view": {
|
|
12
|
+
"kind": "element",
|
|
13
|
+
"tag": "li",
|
|
14
|
+
"props": {
|
|
15
|
+
"role": { "expr": "lit", "value": "treeitem" },
|
|
16
|
+
"aria-expanded": { "expr": "param", "name": "expanded" },
|
|
17
|
+
"aria-selected": { "expr": "param", "name": "selected" },
|
|
18
|
+
"aria-level": { "expr": "param", "name": "level" },
|
|
19
|
+
"className": {
|
|
20
|
+
"expr": "style",
|
|
21
|
+
"preset": "treeNodeStyles"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"children": [{ "kind": "slot" }]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"params": {
|
|
3
|
+
"items": { "type": "list", "required": true },
|
|
4
|
+
"selectable": { "type": "boolean", "required": false },
|
|
5
|
+
"multiSelect": { "type": "boolean", "required": false }
|
|
6
|
+
},
|
|
7
|
+
"localState": {
|
|
8
|
+
"expandedNodes": { "type": "list", "initial": [] },
|
|
9
|
+
"selectedNodes": { "type": "list", "initial": [] }
|
|
10
|
+
},
|
|
11
|
+
"view": {
|
|
12
|
+
"kind": "element",
|
|
13
|
+
"tag": "ul",
|
|
14
|
+
"props": {
|
|
15
|
+
"role": { "expr": "lit", "value": "tree" },
|
|
16
|
+
"aria-multiselectable": { "expr": "param", "name": "multiSelect" },
|
|
17
|
+
"className": {
|
|
18
|
+
"expr": "style",
|
|
19
|
+
"preset": "treeStyles"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"children": [{ "kind": "slot" }]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"treeStyles": {
|
|
3
|
+
"base": "list-none space-y-1"
|
|
4
|
+
},
|
|
5
|
+
"treeNodeStyles": {
|
|
6
|
+
"base": "relative"
|
|
7
|
+
},
|
|
8
|
+
"treeNodeContentStyles": {
|
|
9
|
+
"base": "flex items-center gap-1 rounded-md px-2 py-1",
|
|
10
|
+
"variants": {
|
|
11
|
+
"selected": {
|
|
12
|
+
"true": "bg-accent text-accent-foreground",
|
|
13
|
+
"false": ""
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"defaultVariants": {
|
|
17
|
+
"selected": "false"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"treeToggleStyles": {
|
|
21
|
+
"base": "inline-flex h-4 w-4 items-center justify-center"
|
|
22
|
+
},
|
|
23
|
+
"treeChevronStyles": {
|
|
24
|
+
"base": "h-4 w-4 shrink-0 transition-transform",
|
|
25
|
+
"variants": {
|
|
26
|
+
"expanded": {
|
|
27
|
+
"true": "rotate-90",
|
|
28
|
+
"false": "rotate-0"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"defaultVariants": {
|
|
32
|
+
"expanded": "false"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"treeLabelStyles": {
|
|
36
|
+
"base": "flex-1 truncate",
|
|
37
|
+
"variants": {
|
|
38
|
+
"selected": {
|
|
39
|
+
"true": "font-medium",
|
|
40
|
+
"false": ""
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"defaultVariants": {
|
|
44
|
+
"selected": "false"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"treeGroupStyles": {
|
|
48
|
+
"base": "pl-4"
|
|
49
|
+
}
|
|
50
|
+
}
|