@adia-ai/web-components 0.8.19 → 0.8.21

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 (128) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/components/card/card.a2ui.json +6 -3
  3. package/components/card/card.css +15 -1
  4. package/components/card/card.yaml +5 -3
  5. package/components/nav-group/nav-group.a2ui.json +5 -0
  6. package/components/nav-group/nav-group.class.js +14 -6
  7. package/components/nav-group/nav-group.css +5 -2
  8. package/components/nav-group/nav-group.d.ts +2 -0
  9. package/components/nav-group/nav-group.yaml +4 -0
  10. package/components/select/select.a2ui.json +3 -2
  11. package/components/select/select.class.js +14 -1
  12. package/components/select/select.css +13 -1
  13. package/components/select/select.yaml +5 -4
  14. package/components/table/table.class.js +7 -3
  15. package/components/table/table.css +7 -5
  16. package/dist/theme-provider.min.js +1 -1
  17. package/dist/web-components.min.css +1 -1
  18. package/dist/web-components.min.js +19 -19
  19. package/dist/web-components.sheet.js +1 -1
  20. package/package.json +2 -1
  21. package/components/adia-mark/adia-mark.test.js +0 -69
  22. package/components/adia-wordmark/adia-wordmark.test.js +0 -69
  23. package/components/agent-artifact/agent-artifact.test.js +0 -61
  24. package/components/alert/alert.test.js +0 -364
  25. package/components/color-picker/color-picker.test.js +0 -96
  26. package/components/combobox/combobox.test.js +0 -256
  27. package/components/command/command.test.js +0 -99
  28. package/components/date-range-picker/date-range-picker.test.js +0 -387
  29. package/components/datetime-picker/datetime-picker.test.js +0 -454
  30. package/components/demo-toggle/demo-toggle.test.js +0 -102
  31. package/components/description-list/description-list.test.js +0 -72
  32. package/components/display-field/display-field.test.js +0 -115
  33. package/components/empty-state/empty-state.test.js +0 -77
  34. package/components/field/field.test.js +0 -260
  35. package/components/fields/fields.test.js +0 -88
  36. package/components/inline-message/inline-message.test.js +0 -203
  37. package/components/input/input.test.js +0 -335
  38. package/components/integration-card/integration-card.test.js +0 -318
  39. package/components/link/link.test.js +0 -143
  40. package/components/list/list.test.js +0 -106
  41. package/components/list-window/list-window.test.js +0 -303
  42. package/components/loading-overlay/loading-overlay.test.js +0 -257
  43. package/components/menu/menu.test.js +0 -176
  44. package/components/nav/nav.test.js +0 -428
  45. package/components/nav-group/nav-group.test.js +0 -67
  46. package/components/option-card/option-card.test.js +0 -32
  47. package/components/pagination/pagination.test.js +0 -195
  48. package/components/select/select.test.js +0 -481
  49. package/components/slider/slider.test.js +0 -202
  50. package/components/spinner/spinner.test.js +0 -272
  51. package/components/stat/stat.test.js +0 -108
  52. package/components/step-progress/step-progress.test.js +0 -129
  53. package/components/swatch/swatch.test.js +0 -563
  54. package/components/table/table.test.js +0 -479
  55. package/components/table-toolbar/table-toolbar.test.js +0 -152
  56. package/components/tag/tag.test.js +0 -137
  57. package/components/tags-input/tags-input.test.js +0 -477
  58. package/components/text/text.test.js +0 -202
  59. package/components/theme-provider/theme-provider.test.js +0 -94
  60. package/components/time-picker/time-picker.test.js +0 -287
  61. package/components/timeline/timeline.test.js +0 -31
  62. package/components/toggle-scheme/toggle-scheme.test.js +0 -110
  63. package/components/tree/tree.test.js +0 -312
  64. package/core/element.test.js +0 -389
  65. package/core/icons.test.js +0 -187
  66. package/core/logical-children.test.js +0 -54
  67. package/core/markdown.test.js +0 -47
  68. package/core/responsive.test.js +0 -121
  69. package/core/template.test.js +0 -611
  70. package/traits/active-state/active-state.test.js +0 -28
  71. package/traits/anchor-positioning/anchor-positioning.test.js +0 -122
  72. package/traits/announcer/announcer.test.js +0 -268
  73. package/traits/arrow-grid-nav/arrow-grid-nav.test.js +0 -375
  74. package/traits/attention-pulse/attention-pulse.test.js +0 -26
  75. package/traits/confetti/confetti.test.js +0 -46
  76. package/traits/confetti-burst/confetti-burst.test.js +0 -46
  77. package/traits/count-up/count-up.test.js +0 -54
  78. package/traits/declarative.test.js +0 -138
  79. package/traits/dirty-state/dirty-state.test.js +0 -45
  80. package/traits/drag-ghost/drag-ghost.test.js +0 -19
  81. package/traits/draggable/draggable.test.js +0 -60
  82. package/traits/draggable-list-item/draggable-list-item.test.js +0 -51
  83. package/traits/drop-target/drop-target.test.js +0 -241
  84. package/traits/droppable/droppable.test.js +0 -54
  85. package/traits/droppable-collection/droppable-collection.test.js +0 -99
  86. package/traits/error-shake/error-shake.test.js +0 -114
  87. package/traits/fade-presence/fade-presence.test.js +0 -20
  88. package/traits/focus-restore/focus-restore.test.js +0 -202
  89. package/traits/focus-trap/focus-trap.test.js +0 -42
  90. package/traits/focusable/focusable.test.js +0 -53
  91. package/traits/glow-focus/glow-focus.test.js +0 -31
  92. package/traits/gradient-shift/gradient-shift.test.js +0 -22
  93. package/traits/haptic-feedback/haptic-feedback.test.js +0 -52
  94. package/traits/hotkey/hotkey.test.js +0 -61
  95. package/traits/hoverable/hoverable.test.js +0 -24
  96. package/traits/inertia-drag/inertia-drag.test.js +0 -33
  97. package/traits/input-mask/input-mask.test.js +0 -151
  98. package/traits/intersection-observer/intersection-observer.test.js +0 -38
  99. package/traits/keyboard-nav/keyboard-nav.test.js +0 -41
  100. package/traits/keyboard-reorderable/keyboard-reorderable.test.js +0 -45
  101. package/traits/layout-animation/layout-animation.test.js +0 -114
  102. package/traits/long-press/long-press.test.js +0 -244
  103. package/traits/magnetic-hover/magnetic-hover.test.js +0 -30
  104. package/traits/noise-texture/noise-texture.test.js +0 -20
  105. package/traits/parallax/parallax.test.js +0 -26
  106. package/traits/portal/portal.test.js +0 -30
  107. package/traits/pressable/pressable.test.js +0 -73
  108. package/traits/resettable/resettable.test.js +0 -100
  109. package/traits/resizable/resizable.test.js +0 -39
  110. package/traits/resize-observer/resize-observer.test.js +0 -38
  111. package/traits/ripple/ripple.test.js +0 -32
  112. package/traits/roving-tabindex/roving-tabindex.test.js +0 -28
  113. package/traits/scale-press/scale-press.test.js +0 -39
  114. package/traits/scroll-lock/scroll-lock.test.js +0 -45
  115. package/traits/scroll-progress/scroll-progress.test.js +0 -182
  116. package/traits/shimmer-loading/shimmer-loading.test.js +0 -43
  117. package/traits/smoke.test.js +0 -111
  118. package/traits/snap-to-grid/snap-to-grid.test.js +0 -40
  119. package/traits/sound-feedback/sound-feedback.test.js +0 -26
  120. package/traits/spring-animate/spring-animate.test.js +0 -28
  121. package/traits/success-checkmark/success-checkmark.test.js +0 -120
  122. package/traits/tilt-hover/tilt-hover.test.js +0 -32
  123. package/traits/tossable/tossable.test.js +0 -31
  124. package/traits/traits-host.test.js +0 -73
  125. package/traits/typeahead/typeahead.test.js +0 -38
  126. package/traits/typewriter/typewriter.test.js +0 -47
  127. package/traits/validation/validation.test.js +0 -93
  128. package/traits/view-transition/view-transition.test.js +0 -268
@@ -1,454 +0,0 @@
1
- /**
2
- * datetime-picker-ui — unit tests covering the SPEC-038 contract.
3
- *
4
- * Composition: <calendar-picker-ui> (date pane) + <time-picker-ui>
5
- * (time pane) inside a single <popover>. The host emits an ISO 8601
6
- * datetime string on commit.
7
- *
8
- * Run under happy-dom (per vitest.config.js). showPopover/hidePopover
9
- * are stubbed to no-ops by `packages/web-components/test-setup.js`, so
10
- * popover state is tracked via the `open` reflected attribute and the
11
- * `<popover>` div's presence, not via `:popover-open` selector matching.
12
- */
13
-
14
- import { describe, it, expect, beforeEach } from 'vitest';
15
- import '../../core/element.js';
16
- import '../button/button.js';
17
- import '../popover/popover.js';
18
- import '../calendar-picker/calendar-picker.js';
19
- import '../time-picker/time-picker.js';
20
- import '../divider/divider.js';
21
- import './datetime-picker.js';
22
-
23
- const tick = () => new Promise((r) => queueMicrotask(r));
24
-
25
- function mount(html) {
26
- const wrap = document.createElement('div');
27
- wrap.innerHTML = html.trim();
28
- document.body.appendChild(wrap);
29
- return wrap.firstElementChild;
30
- }
31
-
32
- describe('datetime-picker-ui — structure + defaults', () => {
33
- beforeEach(() => { document.body.innerHTML = ''; });
34
-
35
- it('registers the custom element', () => {
36
- expect(customElements.get('datetime-picker-ui')).toBeTruthy();
37
- });
38
-
39
- it('stamps a default <button-ui slot="trigger"> when no trigger is authored', async () => {
40
- const el = mount('<datetime-picker-ui></datetime-picker-ui>');
41
- await tick();
42
- const trigger = el.querySelector(':scope > [slot="trigger"]');
43
- expect(trigger).not.toBeNull();
44
- expect(trigger.localName).toBe('button-ui');
45
- });
46
-
47
- it('stamps a popover container with role=dialog', async () => {
48
- const el = mount('<datetime-picker-ui></datetime-picker-ui>');
49
- await tick();
50
- const popover = el.querySelector(':scope > [slot="popover"]');
51
- expect(popover).not.toBeNull();
52
- expect(popover.getAttribute('role')).toBe('dialog');
53
- });
54
-
55
- it('stamps a single <calendar-grid-ui> as the date pane inside the popover', async () => {
56
- const el = mount('<datetime-picker-ui></datetime-picker-ui>');
57
- await tick();
58
- const popover = el.querySelector(':scope > [slot="popover"]');
59
- const cal = popover.querySelector(':scope > calendar-grid-ui[data-cal-pane]');
60
- expect(cal).not.toBeNull();
61
- });
62
-
63
- it('stamps a single <time-picker-ui> as the time pane inside the popover', async () => {
64
- const el = mount('<datetime-picker-ui></datetime-picker-ui>');
65
- await tick();
66
- const popover = el.querySelector(':scope > [slot="popover"]');
67
- const time = popover.querySelector(':scope > time-picker-ui[data-time-pane]');
68
- expect(time).not.toBeNull();
69
- });
70
-
71
- it('stamps a vertical <divider-ui> between the panes', async () => {
72
- const el = mount('<datetime-picker-ui></datetime-picker-ui>');
73
- await tick();
74
- const popover = el.querySelector(':scope > [slot="popover"]');
75
- const div = popover.querySelector(':scope > divider-ui[data-pane-divider]');
76
- expect(div).not.toBeNull();
77
- expect(div.hasAttribute('vertical')).toBe(true);
78
- });
79
-
80
- it('host carries role=combobox + aria-haspopup=dialog + aria-expanded=false', async () => {
81
- const el = mount('<datetime-picker-ui></datetime-picker-ui>');
82
- await tick();
83
- expect(el.getAttribute('role')).toBe('combobox');
84
- expect(el.getAttribute('aria-haspopup')).toBe('dialog');
85
- expect(el.getAttribute('aria-expanded')).toBe('false');
86
- });
87
-
88
- it('renders the placeholder in the trigger when value is empty', async () => {
89
- const el = mount('<datetime-picker-ui></datetime-picker-ui>');
90
- await tick();
91
- const trigger = el.querySelector(':scope > [slot="trigger"]');
92
- expect(trigger.getAttribute('text')).toBe('Select date and time');
93
- });
94
-
95
- it('uses a custom placeholder when supplied', async () => {
96
- const el = mount('<datetime-picker-ui placeholder="Pick a moment"></datetime-picker-ui>');
97
- await tick();
98
- const trigger = el.querySelector(':scope > [slot="trigger"]');
99
- expect(trigger.getAttribute('text')).toBe('Pick a moment');
100
- });
101
- });
102
-
103
- describe('datetime-picker-ui — value reflection', () => {
104
- beforeEach(() => { document.body.innerHTML = ''; });
105
-
106
- it('parses ISO datetime value into date + time', async () => {
107
- const el = mount('<datetime-picker-ui value="2026-05-24T14:30"></datetime-picker-ui>');
108
- await tick();
109
- expect(el.datetimeValue).toEqual({ date: '2026-05-24', time: '14:30' });
110
- });
111
-
112
- it('reflects value into the calendar pane (date portion)', async () => {
113
- const el = mount('<datetime-picker-ui value="2026-05-24T14:30"></datetime-picker-ui>');
114
- await tick();
115
- const cal = el.querySelector('calendar-grid-ui[data-cal-pane]');
116
- expect(cal.value).toBe('2026-05-24');
117
- });
118
-
119
- it('reflects value into the time pane (time portion)', async () => {
120
- const el = mount('<datetime-picker-ui value="2026-05-24T14:30"></datetime-picker-ui>');
121
- await tick();
122
- const time = el.querySelector('time-picker-ui[data-time-pane]');
123
- expect(time.value).toBe('14:30');
124
- });
125
-
126
- it('renders formatted datetime in the trigger when value is set', async () => {
127
- const el = mount('<datetime-picker-ui value="2026-05-24T14:30"></datetime-picker-ui>');
128
- await tick();
129
- const trigger = el.querySelector(':scope > [slot="trigger"]');
130
- expect(trigger.getAttribute('text')).toContain('May 24, 2026');
131
- expect(trigger.getAttribute('text')).toContain('14:30');
132
- });
133
-
134
- it('format="long" renders the long-form month', async () => {
135
- const el = mount('<datetime-picker-ui format="long" value="2026-05-24T14:30"></datetime-picker-ui>');
136
- await tick();
137
- const trigger = el.querySelector(':scope > [slot="trigger"]');
138
- expect(trigger.getAttribute('text')).toContain('May 24, 2026');
139
- });
140
-
141
- it('format="iso" renders the raw ISO date portion', async () => {
142
- const el = mount('<datetime-picker-ui format="iso" value="2026-05-24T14:30"></datetime-picker-ui>');
143
- await tick();
144
- const trigger = el.querySelector(':scope > [slot="trigger"]');
145
- expect(trigger.getAttribute('text')).toContain('2026-05-24');
146
- });
147
-
148
- it('reflects [min] onto the calendar pane (date portion)', async () => {
149
- const el = mount('<datetime-picker-ui min="2026-01-01T00:00"></datetime-picker-ui>');
150
- await tick();
151
- const cal = el.querySelector('calendar-grid-ui[data-cal-pane]');
152
- expect(cal.getAttribute('min')).toBe('2026-01-01');
153
- });
154
-
155
- it('reflects [max] onto the calendar pane (date portion)', async () => {
156
- const el = mount('<datetime-picker-ui max="2026-12-31T23:59"></datetime-picker-ui>');
157
- await tick();
158
- const cal = el.querySelector('calendar-grid-ui[data-cal-pane]');
159
- expect(cal.getAttribute('max')).toBe('2026-12-31');
160
- });
161
-
162
- it('reflects [precision] onto the time pane', async () => {
163
- const el = mount('<datetime-picker-ui precision="second"></datetime-picker-ui>');
164
- await tick();
165
- const time = el.querySelector('time-picker-ui[data-time-pane]');
166
- expect(time.getAttribute('precision')).toBe('second');
167
- });
168
-
169
- it('precision="second" stamps a seconds segment in the time pane', async () => {
170
- const el = mount('<datetime-picker-ui precision="second" value="2026-05-24T14:30:45"></datetime-picker-ui>');
171
- await tick();
172
- const time = el.querySelector('time-picker-ui[data-time-pane]');
173
- expect(time.querySelector('[data-segment="second"]')).not.toBeNull();
174
- });
175
-
176
- it('hour-cycle="h12" stamps an AM/PM segment in the time pane', async () => {
177
- const el = mount('<datetime-picker-ui hour-cycle="h12" value="2026-05-24T14:30"></datetime-picker-ui>');
178
- await tick();
179
- const time = el.querySelector('time-picker-ui[data-time-pane]');
180
- expect(time.querySelector('[data-segment="meridiem"]')).not.toBeNull();
181
- });
182
-
183
- it('hour-cycle="h23" hides the AM/PM segment in the time pane', async () => {
184
- const el = mount('<datetime-picker-ui hour-cycle="h23" value="2026-05-24T14:30"></datetime-picker-ui>');
185
- await tick();
186
- const time = el.querySelector('time-picker-ui[data-time-pane]');
187
- expect(time.querySelector('[data-segment="meridiem"]')).toBeNull();
188
- });
189
-
190
- it('reflects [step] onto the time pane', async () => {
191
- const el = mount('<datetime-picker-ui step="900"></datetime-picker-ui>');
192
- await tick();
193
- const time = el.querySelector('time-picker-ui[data-time-pane]');
194
- expect(time.getAttribute('step')).toBe('900');
195
- });
196
- });
197
-
198
- describe('datetime-picker-ui — open / close behavior', () => {
199
- beforeEach(() => { document.body.innerHTML = ''; });
200
-
201
- it('opens on trigger click + emits `open` event', async () => {
202
- const el = mount('<datetime-picker-ui></datetime-picker-ui>');
203
- await tick();
204
- let openDetail = null;
205
- el.addEventListener('open', (e) => { openDetail = e.detail; });
206
- const trigger = el.querySelector(':scope > [slot="trigger"]');
207
- trigger.click();
208
- await tick();
209
- expect(el.open).toBe(true);
210
- expect(el.hasAttribute('open')).toBe(true);
211
- expect(el.getAttribute('aria-expanded')).toBe('true');
212
- expect(openDetail?.trigger).toBe('click');
213
- });
214
-
215
- it('closes on second trigger click + emits `close`', async () => {
216
- const el = mount('<datetime-picker-ui></datetime-picker-ui>');
217
- await tick();
218
- const trigger = el.querySelector(':scope > [slot="trigger"]');
219
- let closeDetail = null;
220
- el.addEventListener('close', (e) => { closeDetail = e.detail; });
221
- trigger.click();
222
- await tick();
223
- trigger.click();
224
- await tick();
225
- expect(el.open).toBe(false);
226
- expect(closeDetail).not.toBeNull();
227
- });
228
-
229
- it('Escape closes the popover + emits close with reason "escape"', async () => {
230
- const el = mount('<datetime-picker-ui></datetime-picker-ui>');
231
- await tick();
232
- el.open = true;
233
- await tick();
234
- let reason = null;
235
- el.addEventListener('close', (e) => { reason = e.detail?.reason; });
236
- const popover = el.querySelector(':scope > [slot="popover"]');
237
- const ev = new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, cancelable: true });
238
- popover.dispatchEvent(ev);
239
- await tick();
240
- expect(el.open).toBe(false);
241
- expect(reason).toBe('escape');
242
- });
243
-
244
- it('Enter on the trigger opens with trigger=keyboard', async () => {
245
- const el = mount('<datetime-picker-ui></datetime-picker-ui>');
246
- await tick();
247
- const trigger = el.querySelector(':scope > [slot="trigger"]');
248
- let openDetail = null;
249
- el.addEventListener('open', (e) => { openDetail = e.detail; });
250
- trigger.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true, cancelable: true }));
251
- await tick();
252
- expect(el.open).toBe(true);
253
- expect(openDetail?.trigger).toBe('keyboard');
254
- });
255
-
256
- it('[disabled] blocks the trigger from opening', async () => {
257
- const el = mount('<datetime-picker-ui disabled></datetime-picker-ui>');
258
- await tick();
259
- const trigger = el.querySelector(':scope > [slot="trigger"]');
260
- trigger.click();
261
- await tick();
262
- expect(el.open).toBe(false);
263
- });
264
-
265
- it('[readonly] blocks the popover from opening', async () => {
266
- const el = mount('<datetime-picker-ui readonly></datetime-picker-ui>');
267
- await tick();
268
- const trigger = el.querySelector(':scope > [slot="trigger"]');
269
- trigger.click();
270
- await tick();
271
- expect(el.open).toBe(false);
272
- });
273
-
274
- it('openPopover() opens programmatically with trigger=programmatic', async () => {
275
- const el = mount('<datetime-picker-ui></datetime-picker-ui>');
276
- await tick();
277
- let openDetail = null;
278
- el.addEventListener('open', (e) => { openDetail = e.detail; });
279
- el.openPopover();
280
- await tick();
281
- expect(el.open).toBe(true);
282
- expect(openDetail?.trigger).toBe('programmatic');
283
- });
284
- });
285
-
286
- describe('datetime-picker-ui — selection + commit', () => {
287
- beforeEach(() => { document.body.innerHTML = ''; });
288
-
289
- it('emits `input` (partial state) when only the date is picked', async () => {
290
- const el = mount('<datetime-picker-ui></datetime-picker-ui>');
291
- await tick();
292
- let inputDetail = null;
293
- let changeFired = false;
294
- el.addEventListener('input', (e) => { inputDetail = e.detail; });
295
- el.addEventListener('change', () => { changeFired = true; });
296
- const cal = el.querySelector('calendar-grid-ui[data-cal-pane]');
297
- cal.value = '2026-03-15';
298
- cal.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: '2026-03-15' } }));
299
- await tick();
300
- expect(inputDetail).not.toBeNull();
301
- expect(inputDetail.value).toContain('2026-03-15');
302
- expect(changeFired).toBe(false);
303
- });
304
-
305
- it('commits `change` when both panes have selections', async () => {
306
- const el = mount('<datetime-picker-ui></datetime-picker-ui>');
307
- await tick();
308
- let detail = null;
309
- el.addEventListener('change', (e) => { detail = e.detail; });
310
- const cal = el.querySelector('calendar-grid-ui[data-cal-pane]');
311
- const time = el.querySelector('time-picker-ui[data-time-pane]');
312
- cal.value = '2026-03-01';
313
- cal.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: '2026-03-01' } }));
314
- await tick();
315
- time.value = '14:30';
316
- time.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: '14:30', segment: 'minute' } }));
317
- await tick();
318
- expect(detail).not.toBeNull();
319
- expect(detail.value).toBe('2026-03-01T14:30');
320
- expect(el.value).toBe('2026-03-01T14:30');
321
- });
322
-
323
- it('time-pane change with existing date in value commits without re-picking date', async () => {
324
- const el = mount('<datetime-picker-ui value="2026-03-01T09:00"></datetime-picker-ui>');
325
- await tick();
326
- let detail = null;
327
- el.addEventListener('change', (e) => { detail = e.detail; });
328
- const time = el.querySelector('time-picker-ui[data-time-pane]');
329
- time.value = '14:30';
330
- time.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: '14:30', segment: 'hour' } }));
331
- await tick();
332
- expect(detail.value).toBe('2026-03-01T14:30');
333
- });
334
-
335
- it('out-of-range commit fires `invalid`, not `change`', async () => {
336
- const el = mount('<datetime-picker-ui min="2026-06-01T00:00" max="2026-06-30T23:59"></datetime-picker-ui>');
337
- await tick();
338
- let invalidDetail = null;
339
- let changeFired = false;
340
- el.addEventListener('invalid', (e) => { invalidDetail = e.detail; });
341
- el.addEventListener('change', () => { changeFired = true; });
342
- const cal = el.querySelector('calendar-grid-ui[data-cal-pane]');
343
- const time = el.querySelector('time-picker-ui[data-time-pane]');
344
- cal.value = '2026-05-01';
345
- cal.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: '2026-05-01' } }));
346
- await tick();
347
- time.value = '12:00';
348
- time.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: '12:00', segment: 'hour' } }));
349
- await tick();
350
- expect(invalidDetail).not.toBeNull();
351
- expect(invalidDetail.reason).toBe('below-min');
352
- expect(changeFired).toBe(false);
353
- });
354
-
355
- it('precision="second" emits HH:mm:ss-formatted value on commit', async () => {
356
- const el = mount('<datetime-picker-ui precision="second" step="1"></datetime-picker-ui>');
357
- await tick();
358
- let detail = null;
359
- el.addEventListener('change', (e) => { detail = e.detail; });
360
- const cal = el.querySelector('calendar-grid-ui[data-cal-pane]');
361
- const time = el.querySelector('time-picker-ui[data-time-pane]');
362
- cal.value = '2026-03-01';
363
- cal.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: '2026-03-01' } }));
364
- await tick();
365
- time.value = '14:30:45';
366
- time.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: '14:30:45', segment: 'second' } }));
367
- await tick();
368
- expect(detail.value).toBe('2026-03-01T14:30:45');
369
- });
370
-
371
- it('clear() resets the value', async () => {
372
- const el = mount('<datetime-picker-ui value="2026-05-24T14:30"></datetime-picker-ui>');
373
- await tick();
374
- el.clear();
375
- await tick();
376
- expect(el.value).toBe('');
377
- expect(el.datetimeValue).toBeNull();
378
- });
379
-
380
- it('change event carries source field', async () => {
381
- const el = mount('<datetime-picker-ui></datetime-picker-ui>');
382
- await tick();
383
- let detail = null;
384
- el.addEventListener('change', (e) => { detail = e.detail; });
385
- const cal = el.querySelector('calendar-grid-ui[data-cal-pane]');
386
- const time = el.querySelector('time-picker-ui[data-time-pane]');
387
- cal.value = '2026-03-01';
388
- cal.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: '2026-03-01' } }));
389
- await tick();
390
- time.value = '14:30';
391
- time.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: '14:30', segment: 'hour' } }));
392
- await tick();
393
- expect(detail.source).toBe('click');
394
- });
395
- });
396
-
397
- describe('datetime-picker-ui — form participation', () => {
398
- beforeEach(() => { document.body.innerHTML = ''; });
399
-
400
- it('is form-associated', () => {
401
- expect(customElements.get('datetime-picker-ui').formAssociated).toBe(true);
402
- });
403
-
404
- it('exposes ElementInternals via the host', async () => {
405
- const el = mount('<datetime-picker-ui name="appointment"></datetime-picker-ui>');
406
- await tick();
407
- expect(el.internals).toBeDefined();
408
- expect(typeof el.internals.setFormValue).toBe('function');
409
- });
410
-
411
- it('syncValue writes the ISO datetime to the form', async () => {
412
- const el = mount('<datetime-picker-ui name="appointment" value="2026-05-24T14:30"></datetime-picker-ui>');
413
- await tick();
414
- expect(() => el.syncValue('2026-05-24T14:30')).not.toThrow();
415
- });
416
-
417
- it('invalid datetime string does not throw under syncValue', async () => {
418
- const el = mount('<datetime-picker-ui name="appointment"></datetime-picker-ui>');
419
- await tick();
420
- expect(() => el.syncValue('not-a-datetime')).not.toThrow();
421
- });
422
- });
423
-
424
- describe('datetime-picker-ui — slots', () => {
425
- beforeEach(() => { document.body.innerHTML = ''; });
426
-
427
- it('consumer-supplied [slot="trigger"] is preserved', async () => {
428
- const el = mount(`
429
- <datetime-picker-ui>
430
- <button-ui slot="trigger" variant="primary" text="Pick"></button-ui>
431
- </datetime-picker-ui>
432
- `);
433
- await tick();
434
- const triggers = el.querySelectorAll(':scope > [slot="trigger"]');
435
- expect(triggers.length).toBe(1);
436
- expect(triggers[0].getAttribute('variant')).toBe('primary');
437
- });
438
-
439
- it('consumer-supplied [slot="footer"] is lifted into the popover', async () => {
440
- const el = mount(`
441
- <datetime-picker-ui>
442
- <div slot="footer">
443
- <button-ui data-action="cancel" text="Cancel"></button-ui>
444
- <button-ui data-action="apply" variant="primary" text="Apply"></button-ui>
445
- </div>
446
- </datetime-picker-ui>
447
- `);
448
- await tick();
449
- const popover = el.querySelector(':scope > [slot="popover"]');
450
- const footer = popover.querySelector(':scope > [slot="footer"]');
451
- expect(footer).not.toBeNull();
452
- expect(footer.children.length).toBe(2);
453
- });
454
- });
@@ -1,102 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import '../../core/element.js';
3
- import './demo-toggle.js';
4
-
5
- const tick = () => new Promise((r) => queueMicrotask(r));
6
-
7
- function mount(html) {
8
- const wrap = document.createElement('div');
9
- wrap.innerHTML = html;
10
- document.body.appendChild(wrap);
11
- return wrap.firstElementChild;
12
- }
13
-
14
- describe('demo-toggle-ui', () => {
15
- beforeEach(() => { document.body.innerHTML = ''; });
16
-
17
- it('registers demo-toggle-ui as a custom element', () => {
18
- expect(customElements.get('demo-toggle-ui')).toBeDefined();
19
- });
20
-
21
- it('defaults to state="off" when [initial] is not set', async () => {
22
- const el = mount('<demo-toggle-ui></demo-toggle-ui>');
23
- await tick();
24
- expect(el.state).toBe('off');
25
- expect(el.getAttribute('state')).toBe('off');
26
- });
27
-
28
- it('honors [initial="on"] on connect', async () => {
29
- const el = mount('<demo-toggle-ui initial="on"></demo-toggle-ui>');
30
- await tick();
31
- expect(el.state).toBe('on');
32
- });
33
-
34
- it('renders the auto-stamped header bar with switch + label', async () => {
35
- const el = mount('<demo-toggle-ui label-on="Foo" label-off="Bar"></demo-toggle-ui>');
36
- await tick();
37
- const bar = el.querySelector('[slot="bar"]');
38
- const sw = el.querySelector('[data-demo-toggle-switch]');
39
- const label = el.querySelector('[data-demo-toggle-label]');
40
- expect(bar).toBeTruthy();
41
- expect(sw).toBeTruthy();
42
- expect(label).toBeTruthy();
43
- // state defaults to "off"; label-off should be active
44
- expect(label.textContent).toBe('Bar');
45
- });
46
-
47
- it('toggle() flips state and emits change with detail.state', async () => {
48
- const el = mount('<demo-toggle-ui></demo-toggle-ui>');
49
- await tick();
50
- const events = [];
51
- el.addEventListener('change', (e) => events.push(e.detail));
52
-
53
- el.toggle();
54
- expect(el.state).toBe('on');
55
- expect(events).toHaveLength(1);
56
- expect(events[0]).toEqual({ state: 'on' });
57
-
58
- el.toggle();
59
- expect(el.state).toBe('off');
60
- expect(events).toHaveLength(2);
61
- expect(events[1]).toEqual({ state: 'off' });
62
- });
63
-
64
- it('clicking the embedded switch flips host state and bubbles change', async () => {
65
- const el = mount('<demo-toggle-ui></demo-toggle-ui>');
66
- await tick();
67
- const sw = el.querySelector('[data-demo-toggle-switch]');
68
-
69
- const events = [];
70
- el.addEventListener('change', (e) => events.push(e.detail));
71
-
72
- sw.click();
73
- await tick();
74
- expect(el.state).toBe('on');
75
- expect(events).toHaveLength(1);
76
- expect(events[0]).toEqual({ state: 'on' });
77
- });
78
-
79
- it('reflects state to the [state] attribute', async () => {
80
- const el = mount('<demo-toggle-ui initial="on"></demo-toggle-ui>');
81
- await tick();
82
- expect(el.getAttribute('state')).toBe('on');
83
- el.toggle();
84
- expect(el.getAttribute('state')).toBe('off');
85
- });
86
-
87
- it('coerces an invalid [state] attribute to "off"', async () => {
88
- const el = mount('<demo-toggle-ui state="garbage"></demo-toggle-ui>');
89
- await tick();
90
- expect(el.state).toBe('off');
91
- });
92
-
93
- it('updates the active label when state flips', async () => {
94
- const el = mount('<demo-toggle-ui label-on="With trait" label-off="Without trait"></demo-toggle-ui>');
95
- await tick();
96
- const label = el.querySelector('[data-demo-toggle-label]');
97
- expect(label.textContent).toBe('Without trait');
98
- el.toggle();
99
- await tick();
100
- expect(label.textContent).toBe('With trait');
101
- });
102
- });
@@ -1,72 +0,0 @@
1
- /**
2
- * <description-list-ui> behavioral tests.
3
- *
4
- * FB-51 #3 (v0.5.19): align="stretch" lets <dd> fill the remaining
5
- * inline-grid track width so block-level form controls inside (e.g.
6
- * <slider-ui>, <select-ui>, <color-picker-ui>) reach the column edge
7
- * rather than shrink-wrapping to content.
8
- */
9
-
10
- import { describe, it, expect, beforeAll, beforeEach, afterEach } from 'vitest';
11
- import { readFileSync } from 'node:fs';
12
- import { resolve, dirname } from 'node:path';
13
- import { fileURLToPath } from 'node:url';
14
-
15
- const HERE = dirname(fileURLToPath(import.meta.url));
16
-
17
- beforeAll(async () => {
18
- await import('./description-list.js');
19
- });
20
-
21
- describe('<description-list-ui> align="stretch" (FB-51 #3)', () => {
22
- let host;
23
- let dl;
24
-
25
- beforeEach(async () => {
26
- host = document.createElement('div');
27
- host.style.width = '600px';
28
- document.body.appendChild(host);
29
-
30
- dl = document.createElement('description-list-ui');
31
- dl.setAttribute('layout', 'inline');
32
- dl.setAttribute('align', 'stretch');
33
-
34
- const dt = document.createElement('dt');
35
- dt.textContent = 'Source hue';
36
- const dd = document.createElement('dd');
37
- dd.id = 'fb51-stretch-dd';
38
- const probe = document.createElement('div');
39
- probe.id = 'fb51-stretch-probe';
40
- probe.textContent = 'inner';
41
- dd.appendChild(probe);
42
- dl.appendChild(dt);
43
- dl.appendChild(dd);
44
-
45
- host.appendChild(dl);
46
- await new Promise((r) => setTimeout(r, 50));
47
- });
48
-
49
- afterEach(() => {
50
- host.remove();
51
- });
52
-
53
- it('CSS source declares justify-self: stretch + width: 100% for align="stretch" inline layout', () => {
54
- const cssText = readFileSync(resolve(HERE, 'description-list.css'), 'utf8');
55
- expect(cssText).toMatch(/\[layout="inline"\]\[align="stretch"\][^{]*\{[^}]*justify-self:\s*stretch/);
56
- expect(cssText).toMatch(/\[layout="inline"\]\[align="stretch"\][^{]*\{[^}]*width:\s*100%/);
57
- });
58
-
59
- it('host carries the align="stretch" attribute (CSS selector match precondition)', () => {
60
- expect(dl.getAttribute('align')).toBe('stretch');
61
- expect(dl.getAttribute('layout')).toBe('inline');
62
- });
63
-
64
- it('yaml enum includes "stretch" (schema contract)', () => {
65
- const yamlText = readFileSync(resolve(HERE, 'description-list.yaml'), 'utf8');
66
- // Match the align enum block specifically (between `align:` and the next prop).
67
- const alignBlock = yamlText.match(/align:[\s\S]*?(?=\n \w+:)/)?.[0] || '';
68
- expect(alignBlock).toMatch(/enum:[\s\S]*-\s+start/);
69
- expect(alignBlock).toMatch(/enum:[\s\S]*-\s+between/);
70
- expect(alignBlock).toMatch(/enum:[\s\S]*-\s+stretch/);
71
- });
72
- });