@adia-ai/web-components 0.8.19 → 0.8.20

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 (119) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/components/nav-group/nav-group.a2ui.json +5 -0
  3. package/components/nav-group/nav-group.class.js +14 -6
  4. package/components/nav-group/nav-group.css +5 -2
  5. package/components/nav-group/nav-group.d.ts +2 -0
  6. package/components/nav-group/nav-group.yaml +4 -0
  7. package/dist/theme-provider.min.js +1 -1
  8. package/dist/web-components.min.css +1 -1
  9. package/dist/web-components.min.js +2 -2
  10. package/dist/web-components.sheet.js +1 -1
  11. package/package.json +2 -1
  12. package/components/adia-mark/adia-mark.test.js +0 -69
  13. package/components/adia-wordmark/adia-wordmark.test.js +0 -69
  14. package/components/agent-artifact/agent-artifact.test.js +0 -61
  15. package/components/alert/alert.test.js +0 -364
  16. package/components/color-picker/color-picker.test.js +0 -96
  17. package/components/combobox/combobox.test.js +0 -256
  18. package/components/command/command.test.js +0 -99
  19. package/components/date-range-picker/date-range-picker.test.js +0 -387
  20. package/components/datetime-picker/datetime-picker.test.js +0 -454
  21. package/components/demo-toggle/demo-toggle.test.js +0 -102
  22. package/components/description-list/description-list.test.js +0 -72
  23. package/components/display-field/display-field.test.js +0 -115
  24. package/components/empty-state/empty-state.test.js +0 -77
  25. package/components/field/field.test.js +0 -260
  26. package/components/fields/fields.test.js +0 -88
  27. package/components/inline-message/inline-message.test.js +0 -203
  28. package/components/input/input.test.js +0 -335
  29. package/components/integration-card/integration-card.test.js +0 -318
  30. package/components/link/link.test.js +0 -143
  31. package/components/list/list.test.js +0 -106
  32. package/components/list-window/list-window.test.js +0 -303
  33. package/components/loading-overlay/loading-overlay.test.js +0 -257
  34. package/components/menu/menu.test.js +0 -176
  35. package/components/nav/nav.test.js +0 -428
  36. package/components/nav-group/nav-group.test.js +0 -67
  37. package/components/option-card/option-card.test.js +0 -32
  38. package/components/pagination/pagination.test.js +0 -195
  39. package/components/select/select.test.js +0 -481
  40. package/components/slider/slider.test.js +0 -202
  41. package/components/spinner/spinner.test.js +0 -272
  42. package/components/stat/stat.test.js +0 -108
  43. package/components/step-progress/step-progress.test.js +0 -129
  44. package/components/swatch/swatch.test.js +0 -563
  45. package/components/table/table.test.js +0 -479
  46. package/components/table-toolbar/table-toolbar.test.js +0 -152
  47. package/components/tag/tag.test.js +0 -137
  48. package/components/tags-input/tags-input.test.js +0 -477
  49. package/components/text/text.test.js +0 -202
  50. package/components/theme-provider/theme-provider.test.js +0 -94
  51. package/components/time-picker/time-picker.test.js +0 -287
  52. package/components/timeline/timeline.test.js +0 -31
  53. package/components/toggle-scheme/toggle-scheme.test.js +0 -110
  54. package/components/tree/tree.test.js +0 -312
  55. package/core/element.test.js +0 -389
  56. package/core/icons.test.js +0 -187
  57. package/core/logical-children.test.js +0 -54
  58. package/core/markdown.test.js +0 -47
  59. package/core/responsive.test.js +0 -121
  60. package/core/template.test.js +0 -611
  61. package/traits/active-state/active-state.test.js +0 -28
  62. package/traits/anchor-positioning/anchor-positioning.test.js +0 -122
  63. package/traits/announcer/announcer.test.js +0 -268
  64. package/traits/arrow-grid-nav/arrow-grid-nav.test.js +0 -375
  65. package/traits/attention-pulse/attention-pulse.test.js +0 -26
  66. package/traits/confetti/confetti.test.js +0 -46
  67. package/traits/confetti-burst/confetti-burst.test.js +0 -46
  68. package/traits/count-up/count-up.test.js +0 -54
  69. package/traits/declarative.test.js +0 -138
  70. package/traits/dirty-state/dirty-state.test.js +0 -45
  71. package/traits/drag-ghost/drag-ghost.test.js +0 -19
  72. package/traits/draggable/draggable.test.js +0 -60
  73. package/traits/draggable-list-item/draggable-list-item.test.js +0 -51
  74. package/traits/drop-target/drop-target.test.js +0 -241
  75. package/traits/droppable/droppable.test.js +0 -54
  76. package/traits/droppable-collection/droppable-collection.test.js +0 -99
  77. package/traits/error-shake/error-shake.test.js +0 -114
  78. package/traits/fade-presence/fade-presence.test.js +0 -20
  79. package/traits/focus-restore/focus-restore.test.js +0 -202
  80. package/traits/focus-trap/focus-trap.test.js +0 -42
  81. package/traits/focusable/focusable.test.js +0 -53
  82. package/traits/glow-focus/glow-focus.test.js +0 -31
  83. package/traits/gradient-shift/gradient-shift.test.js +0 -22
  84. package/traits/haptic-feedback/haptic-feedback.test.js +0 -52
  85. package/traits/hotkey/hotkey.test.js +0 -61
  86. package/traits/hoverable/hoverable.test.js +0 -24
  87. package/traits/inertia-drag/inertia-drag.test.js +0 -33
  88. package/traits/input-mask/input-mask.test.js +0 -151
  89. package/traits/intersection-observer/intersection-observer.test.js +0 -38
  90. package/traits/keyboard-nav/keyboard-nav.test.js +0 -41
  91. package/traits/keyboard-reorderable/keyboard-reorderable.test.js +0 -45
  92. package/traits/layout-animation/layout-animation.test.js +0 -114
  93. package/traits/long-press/long-press.test.js +0 -244
  94. package/traits/magnetic-hover/magnetic-hover.test.js +0 -30
  95. package/traits/noise-texture/noise-texture.test.js +0 -20
  96. package/traits/parallax/parallax.test.js +0 -26
  97. package/traits/portal/portal.test.js +0 -30
  98. package/traits/pressable/pressable.test.js +0 -73
  99. package/traits/resettable/resettable.test.js +0 -100
  100. package/traits/resizable/resizable.test.js +0 -39
  101. package/traits/resize-observer/resize-observer.test.js +0 -38
  102. package/traits/ripple/ripple.test.js +0 -32
  103. package/traits/roving-tabindex/roving-tabindex.test.js +0 -28
  104. package/traits/scale-press/scale-press.test.js +0 -39
  105. package/traits/scroll-lock/scroll-lock.test.js +0 -45
  106. package/traits/scroll-progress/scroll-progress.test.js +0 -182
  107. package/traits/shimmer-loading/shimmer-loading.test.js +0 -43
  108. package/traits/smoke.test.js +0 -111
  109. package/traits/snap-to-grid/snap-to-grid.test.js +0 -40
  110. package/traits/sound-feedback/sound-feedback.test.js +0 -26
  111. package/traits/spring-animate/spring-animate.test.js +0 -28
  112. package/traits/success-checkmark/success-checkmark.test.js +0 -120
  113. package/traits/tilt-hover/tilt-hover.test.js +0 -32
  114. package/traits/tossable/tossable.test.js +0 -31
  115. package/traits/traits-host.test.js +0 -73
  116. package/traits/typeahead/typeahead.test.js +0 -38
  117. package/traits/typewriter/typewriter.test.js +0 -47
  118. package/traits/validation/validation.test.js +0 -93
  119. package/traits/view-transition/view-transition.test.js +0 -268
@@ -1,375 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import { arrowGridNav } from './arrow-grid-nav.js';
3
- import { mountHost, connectTrait, spyEvent, resetDOM } from '../test-helpers.js';
4
-
5
- /**
6
- * Build a row-flat grid with `total` cells laid out in `cols` columns.
7
- * Returns the host element pre-populated with <button> children.
8
- */
9
- function flatGrid(cols, total) {
10
- const host = mountHost('div', { 'data-grid-columns': String(cols) });
11
- for (let i = 0; i < total; i++) {
12
- const btn = document.createElement('button');
13
- btn.textContent = `cell-${i}`;
14
- btn.setAttribute('data-cell-index', String(i));
15
- host.appendChild(btn);
16
- }
17
- return host;
18
- }
19
-
20
- /**
21
- * Build a row-nested grid: each direct child is a row, each row's children
22
- * are cells. `rowSpec` is an array of column counts per row.
23
- */
24
- function nestedGrid(rowSpec) {
25
- const host = mountHost('div', { 'data-grid-mode': 'row-nested' });
26
- rowSpec.forEach((cols, rowIdx) => {
27
- const row = document.createElement('div');
28
- for (let c = 0; c < cols; c++) {
29
- const btn = document.createElement('button');
30
- btn.textContent = `r${rowIdx}c${c}`;
31
- row.appendChild(btn);
32
- }
33
- host.appendChild(row);
34
- });
35
- return host;
36
- }
37
-
38
- function key(host, k, modifiers = {}) {
39
- host.dispatchEvent(new KeyboardEvent('keydown', {
40
- key: k,
41
- bubbles: true,
42
- ...modifiers,
43
- }));
44
- }
45
-
46
- describe('arrow-grid-nav', () => {
47
- beforeEach(resetDOM);
48
-
49
- it('paints active row + col on connect at (0, 0)', () => {
50
- const host = flatGrid(7, 42);
51
- connectTrait(arrowGridNav, host);
52
- expect(host.getAttribute('data-grid-active-row')).toBe('0');
53
- expect(host.getAttribute('data-grid-active-col')).toBe('0');
54
- const first = host.children[0];
55
- expect(first.hasAttribute('data-grid-active')).toBe(true);
56
- expect(first.getAttribute('tabindex')).toBe('0');
57
- });
58
-
59
- it('sets host tabindex to 0 when not pre-set', () => {
60
- const host = flatGrid(7, 42);
61
- connectTrait(arrowGridNav, host);
62
- expect(host.getAttribute('tabindex')).toBe('0');
63
- });
64
-
65
- it('preserves a pre-set host tabindex', () => {
66
- const host = flatGrid(7, 42);
67
- host.setAttribute('tabindex', '5');
68
- connectTrait(arrowGridNav, host);
69
- expect(host.getAttribute('tabindex')).toBe('5');
70
- });
71
-
72
- it('non-active cells receive tabindex="-1"', () => {
73
- const host = flatGrid(7, 42);
74
- connectTrait(arrowGridNav, host);
75
- expect(host.children[1].getAttribute('tabindex')).toBe('-1');
76
- expect(host.children[10].getAttribute('tabindex')).toBe('-1');
77
- });
78
-
79
- it('ArrowRight increments col within the row', () => {
80
- const host = flatGrid(7, 42);
81
- connectTrait(arrowGridNav, host);
82
- key(host, 'ArrowRight');
83
- expect(host.getAttribute('data-grid-active-col')).toBe('1');
84
- expect(host.getAttribute('data-grid-active-row')).toBe('0');
85
- expect(host.children[1].hasAttribute('data-grid-active')).toBe(true);
86
- expect(host.children[0].hasAttribute('data-grid-active')).toBe(false);
87
- });
88
-
89
- it('ArrowLeft decrements col', () => {
90
- const host = flatGrid(7, 42);
91
- connectTrait(arrowGridNav, host);
92
- key(host, 'ArrowRight');
93
- key(host, 'ArrowRight');
94
- key(host, 'ArrowLeft');
95
- expect(host.getAttribute('data-grid-active-col')).toBe('1');
96
- });
97
-
98
- it('ArrowDown increments row, keeps col', () => {
99
- const host = flatGrid(7, 42);
100
- connectTrait(arrowGridNav, host);
101
- key(host, 'ArrowRight');
102
- key(host, 'ArrowRight');
103
- key(host, 'ArrowRight');
104
- key(host, 'ArrowDown');
105
- expect(host.getAttribute('data-grid-active-row')).toBe('1');
106
- expect(host.getAttribute('data-grid-active-col')).toBe('3');
107
- // index 3 + 7 = 10
108
- expect(host.children[10].hasAttribute('data-grid-active')).toBe(true);
109
- });
110
-
111
- it('ArrowUp decrements row', () => {
112
- const host = flatGrid(7, 42);
113
- connectTrait(arrowGridNav, host);
114
- key(host, 'ArrowDown');
115
- key(host, 'ArrowDown');
116
- key(host, 'ArrowUp');
117
- expect(host.getAttribute('data-grid-active-row')).toBe('1');
118
- });
119
-
120
- it('Home moves to col 0 within current row', () => {
121
- const host = flatGrid(7, 42);
122
- connectTrait(arrowGridNav, host);
123
- key(host, 'ArrowDown');
124
- key(host, 'ArrowRight');
125
- key(host, 'ArrowRight');
126
- expect(host.getAttribute('data-grid-active-col')).toBe('2');
127
- key(host, 'Home');
128
- expect(host.getAttribute('data-grid-active-row')).toBe('1');
129
- expect(host.getAttribute('data-grid-active-col')).toBe('0');
130
- });
131
-
132
- it('Ctrl+Home moves to (0, 0)', () => {
133
- const host = flatGrid(7, 42);
134
- connectTrait(arrowGridNav, host);
135
- key(host, 'ArrowDown');
136
- key(host, 'ArrowDown');
137
- key(host, 'ArrowRight');
138
- key(host, 'Home', { ctrlKey: true });
139
- expect(host.getAttribute('data-grid-active-row')).toBe('0');
140
- expect(host.getAttribute('data-grid-active-col')).toBe('0');
141
- });
142
-
143
- it('End moves to last col in current row', () => {
144
- const host = flatGrid(7, 42);
145
- connectTrait(arrowGridNav, host);
146
- key(host, 'End');
147
- expect(host.getAttribute('data-grid-active-col')).toBe('6');
148
- expect(host.getAttribute('data-grid-active-row')).toBe('0');
149
- });
150
-
151
- it('Ctrl+End moves to (lastRow, lastCol)', () => {
152
- const host = flatGrid(7, 42); // 6 rows × 7 cols
153
- connectTrait(arrowGridNav, host);
154
- key(host, 'End', { ctrlKey: true });
155
- expect(host.getAttribute('data-grid-active-row')).toBe('5');
156
- expect(host.getAttribute('data-grid-active-col')).toBe('6');
157
- });
158
-
159
- it('Meta+End is treated as Ctrl+End on Mac', () => {
160
- const host = flatGrid(7, 42);
161
- connectTrait(arrowGridNav, host);
162
- key(host, 'End', { metaKey: true });
163
- expect(host.getAttribute('data-grid-active-row')).toBe('5');
164
- expect(host.getAttribute('data-grid-active-col')).toBe('6');
165
- });
166
-
167
- it('Enter dispatches grid-activate with row, col, element', () => {
168
- const host = flatGrid(7, 42);
169
- connectTrait(arrowGridNav, host);
170
- const spy = spyEvent(host, 'grid-activate');
171
- key(host, 'ArrowRight');
172
- key(host, 'ArrowDown');
173
- key(host, 'Enter');
174
- expect(spy.count).toBe(1);
175
- expect(spy.last).toMatchObject({ row: 1, col: 1 });
176
- expect(spy.last.element).toBe(host.children[8]); // row 1 col 1 = index 8
177
- });
178
-
179
- it('Space dispatches grid-activate', () => {
180
- const host = flatGrid(7, 42);
181
- connectTrait(arrowGridNav, host);
182
- const spy = spyEvent(host, 'grid-activate');
183
- key(host, ' ');
184
- expect(spy.count).toBe(1);
185
- expect(spy.last).toMatchObject({ row: 0, col: 0 });
186
- });
187
-
188
- it('emits grid-edge "left" at column 0', () => {
189
- const host = flatGrid(7, 42);
190
- connectTrait(arrowGridNav, host);
191
- const spy = spyEvent(host, 'grid-edge');
192
- key(host, 'ArrowLeft');
193
- expect(spy.count).toBe(1);
194
- expect(spy.last.edge).toBe('left');
195
- });
196
-
197
- it('emits grid-edge "right" at last column', () => {
198
- const host = flatGrid(7, 42);
199
- connectTrait(arrowGridNav, host);
200
- key(host, 'End');
201
- const spy = spyEvent(host, 'grid-edge');
202
- key(host, 'ArrowRight');
203
- expect(spy.count).toBe(1);
204
- expect(spy.last.edge).toBe('right');
205
- });
206
-
207
- it('emits grid-edge "top" at row 0', () => {
208
- const host = flatGrid(7, 42);
209
- connectTrait(arrowGridNav, host);
210
- const spy = spyEvent(host, 'grid-edge');
211
- key(host, 'ArrowUp');
212
- expect(spy.count).toBe(1);
213
- expect(spy.last.edge).toBe('top');
214
- });
215
-
216
- it('emits grid-edge "bottom" at last row', () => {
217
- const host = flatGrid(7, 42);
218
- connectTrait(arrowGridNav, host);
219
- key(host, 'End', { ctrlKey: true });
220
- const spy = spyEvent(host, 'grid-edge');
221
- key(host, 'ArrowDown');
222
- expect(spy.count).toBe(1);
223
- expect(spy.last.edge).toBe('bottom');
224
- });
225
-
226
- it('grid-cell-change fires when active changes', () => {
227
- const host = flatGrid(7, 42);
228
- connectTrait(arrowGridNav, host);
229
- const spy = spyEvent(host, 'grid-cell-change');
230
- key(host, 'ArrowRight');
231
- key(host, 'ArrowRight');
232
- expect(spy.count).toBe(2);
233
- expect(spy.last).toMatchObject({ row: 0, col: 2 });
234
- });
235
-
236
- it('grid-cell-change does NOT fire when no movement happens at edge', () => {
237
- const host = flatGrid(7, 42);
238
- connectTrait(arrowGridNav, host);
239
- const spy = spyEvent(host, 'grid-cell-change');
240
- key(host, 'ArrowLeft'); // already at col 0
241
- expect(spy.count).toBe(0);
242
- });
243
-
244
- it('row-nested mode infers rows from container children', () => {
245
- const host = nestedGrid([3, 3, 3]); // 3x3 grid
246
- connectTrait(arrowGridNav, host);
247
- expect(host.getAttribute('data-grid-active-row')).toBe('0');
248
- expect(host.getAttribute('data-grid-active-col')).toBe('0');
249
- key(host, 'ArrowDown');
250
- expect(host.getAttribute('data-grid-active-row')).toBe('1');
251
- const row1 = host.children[1];
252
- expect(row1.children[0].hasAttribute('data-grid-active')).toBe(true);
253
- });
254
-
255
- it('row-nested mode handles ragged rows by clamping col on row change', () => {
256
- const host = nestedGrid([5, 2, 4]); // row 0 has 5 cols, row 1 has 2, row 2 has 4
257
- connectTrait(arrowGridNav, host);
258
- key(host, 'End'); // col 4 in row 0
259
- expect(host.getAttribute('data-grid-active-col')).toBe('4');
260
- key(host, 'ArrowDown'); // row 1 only has 2 cols → clamp to 1
261
- expect(host.getAttribute('data-grid-active-row')).toBe('1');
262
- expect(host.getAttribute('data-grid-active-col')).toBe('1');
263
- });
264
-
265
- it('disabled cells are skipped in row-flat mode', () => {
266
- const host = flatGrid(7, 42);
267
- host.children[1].setAttribute('disabled', '');
268
- connectTrait(arrowGridNav, host);
269
- key(host, 'ArrowRight');
270
- // Disabled child #1 is filtered out → row-flat splits the remaining
271
- // 41 cells; index 1 in the post-filter list is original cell #2.
272
- expect(host.children[2].hasAttribute('data-grid-active')).toBe(true);
273
- });
274
-
275
- it('does not steal arrow keys from <input> within a cell', () => {
276
- const host = mountHost('div', { 'data-grid-columns': '3' });
277
- for (let i = 0; i < 3; i++) {
278
- const cell = document.createElement('div');
279
- const inp = document.createElement('input');
280
- cell.appendChild(inp);
281
- host.appendChild(cell);
282
- }
283
- connectTrait(arrowGridNav, host);
284
- const inp = host.querySelector('input');
285
- inp.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true }));
286
- // No movement — col remains 0.
287
- expect(host.getAttribute('data-grid-active-col')).toBe('0');
288
- });
289
-
290
- it('does not respond to non-handled keys', () => {
291
- const host = flatGrid(7, 42);
292
- connectTrait(arrowGridNav, host);
293
- const spy = spyEvent(host, 'grid-cell-change');
294
- key(host, 'a');
295
- key(host, 'Tab');
296
- key(host, 'Escape');
297
- expect(spy.count).toBe(0);
298
- });
299
-
300
- it('MutationObserver re-paints when children are added', async () => {
301
- const host = flatGrid(7, 7); // 1 row of 7
302
- connectTrait(arrowGridNav, host);
303
- key(host, 'End'); // col 6
304
- expect(host.getAttribute('data-grid-active-col')).toBe('6');
305
- // Add 7 more children → row 1 exists.
306
- for (let i = 0; i < 7; i++) {
307
- const btn = document.createElement('button');
308
- btn.textContent = `new-${i}`;
309
- host.appendChild(btn);
310
- }
311
- await new Promise((r) => setTimeout(r, 10));
312
- // Active should still be at (0, 6).
313
- expect(host.getAttribute('data-grid-active-row')).toBe('0');
314
- // Now arrow-down should reach the new row.
315
- key(host, 'ArrowDown');
316
- expect(host.getAttribute('data-grid-active-row')).toBe('1');
317
- });
318
-
319
- it('disconnect removes all managed attrs from host and active cell', () => {
320
- const host = flatGrid(7, 42);
321
- const inst = connectTrait(arrowGridNav, host);
322
- key(host, 'ArrowRight');
323
- inst.disconnect(host);
324
- expect(host.hasAttribute('data-arrow-grid-nav-active')).toBe(false);
325
- expect(host.hasAttribute('data-grid-active-row')).toBe(false);
326
- expect(host.hasAttribute('data-grid-active-col')).toBe(false);
327
- for (const child of host.children) {
328
- expect(child.hasAttribute('data-grid-active')).toBe(false);
329
- }
330
- });
331
-
332
- it('disconnect removes the keydown listener', () => {
333
- const host = flatGrid(7, 42);
334
- const inst = connectTrait(arrowGridNav, host);
335
- inst.disconnect(host);
336
- const spy = spyEvent(host, 'grid-cell-change');
337
- key(host, 'ArrowRight');
338
- expect(spy.count).toBe(0);
339
- });
340
-
341
- it('reconnect after disconnect works', () => {
342
- const host = flatGrid(7, 42);
343
- const inst1 = connectTrait(arrowGridNav, host);
344
- inst1.disconnect(host);
345
- const inst2 = connectTrait(arrowGridNav, host);
346
- expect(host.getAttribute('data-grid-active-row')).toBe('0');
347
- expect(host.children[0].hasAttribute('data-grid-active')).toBe(true);
348
- inst2.disconnect(host);
349
- });
350
-
351
- it('default mode is row-flat when data-grid-mode missing', () => {
352
- const host = flatGrid(4, 12);
353
- connectTrait(arrowGridNav, host);
354
- key(host, 'ArrowDown');
355
- expect(host.getAttribute('data-grid-active-row')).toBe('1');
356
- expect(host.children[4].hasAttribute('data-grid-active')).toBe(true);
357
- });
358
-
359
- it('missing data-grid-columns falls back to 1 (single column)', () => {
360
- const host = mountHost('div');
361
- for (let i = 0; i < 4; i++) {
362
- const btn = document.createElement('button');
363
- host.appendChild(btn);
364
- }
365
- connectTrait(arrowGridNav, host);
366
- key(host, 'ArrowDown');
367
- expect(host.getAttribute('data-grid-active-row')).toBe('1');
368
- expect(host.getAttribute('data-grid-active-col')).toBe('0');
369
- });
370
-
371
- it('empty grid does not throw', () => {
372
- const host = mountHost('div', { 'data-grid-columns': '7' });
373
- expect(() => connectTrait(arrowGridNav, host)).not.toThrow();
374
- });
375
- });
@@ -1,26 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import { attentionPulse } from './attention-pulse.js';
3
- import { mountHost, connectTrait, resetDOM } from '../test-helpers.js';
4
-
5
- describe('attention-pulse', () => {
6
- beforeEach(resetDOM);
7
-
8
- it('connect sets data-attention-pulse-active', () => {
9
- const host = mountHost();
10
- connectTrait(attentionPulse, host);
11
- expect(host.hasAttribute('data-attention-pulse-active')).toBe(true);
12
- });
13
-
14
- it('disconnect clears active + animation', () => {
15
- const host = mountHost();
16
- const inst = connectTrait(attentionPulse, host);
17
- inst.disconnect(host);
18
- expect(host.hasAttribute('data-attention-pulse-active')).toBe(false);
19
- expect(host.style.animation).toBe('');
20
- });
21
-
22
- it('respects data-pulse-interval (default 2000ms)', () => {
23
- const host = mountHost('div', { 'data-pulse-interval': '500' });
24
- expect(() => connectTrait(attentionPulse, host)).not.toThrow();
25
- });
26
- });
@@ -1,46 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import { confetti } from './confetti.js';
3
- import { _resetStage } from './confetti-stage.js';
4
- import { mountHost, connectTrait, resetDOM } from '../test-helpers.js';
5
-
6
- describe('confetti', () => {
7
- beforeEach(() => {
8
- resetDOM();
9
- _resetStage();
10
- });
11
-
12
- it('connect sets data-confetti-active without throwing', () => {
13
- const host = mountHost();
14
- connectTrait(confetti, host);
15
- expect(host.hasAttribute('data-confetti-active')).toBe(true);
16
- });
17
-
18
- it('disconnect clears active', () => {
19
- const host = mountHost();
20
- const inst = connectTrait(confetti, host);
21
- inst.disconnect(host);
22
- expect(host.hasAttribute('data-confetti-active')).toBe(false);
23
- });
24
-
25
- it('no canvas: graceful degradation in happy-dom', () => {
26
- // Old contract: this trait used a host-scoped <canvas>. New
27
- // contract: it pushes <span> particles into the body-level
28
- // popover stage (no canvas anywhere). The test name is kept for
29
- // continuity — the assertion is the same: connect must not throw
30
- // in environments without real layout (rect = 0,0,0,0 in
31
- // happy-dom; viewportCenterOf returns null and the trait skips
32
- // particle spawning gracefully).
33
- const host = mountHost();
34
- expect(() => connectTrait(confetti, host)).not.toThrow();
35
- });
36
-
37
- it('does not append particles inside the host (stage is body-level)', () => {
38
- const host = mountHost();
39
- const inst = connectTrait(confetti, host);
40
- // The old implementation appended a <canvas> child to the host.
41
- // The new implementation must leave the host's children alone —
42
- // every particle belongs to the body-level popover stage.
43
- expect(host.children.length).toBe(0);
44
- inst.disconnect(host);
45
- });
46
- });
@@ -1,46 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import { confettiBurst } from './confetti-burst.js';
3
- import { _resetStage } from '../confetti/confetti-stage.js';
4
- import { mountHost, connectTrait, spyEvent, resetDOM, wait } from '../test-helpers.js';
5
-
6
- describe('confetti-burst', () => {
7
- beforeEach(() => {
8
- resetDOM();
9
- _resetStage();
10
- });
11
-
12
- it('sets data-confetti-burst-active on connect', () => {
13
- const host = mountHost();
14
- connectTrait(confettiBurst, host);
15
- expect(host.hasAttribute('data-confetti-burst-active')).toBe(true);
16
- });
17
-
18
- it('fires confetti-burst-done even without real layout (happy-dom)', async () => {
19
- const host = mountHost();
20
- const spy = spyEvent(host, 'confetti-burst-done');
21
- connectTrait(confettiBurst, host);
22
- // happy-dom returns rect = 0,0,0,0 for unattached layout. The
23
- // trait's graceful-degradation path queues `done` on next tick.
24
- await wait(50);
25
- expect(spy.count).toBeGreaterThanOrEqual(1);
26
- });
27
-
28
- it('disconnect removes the active attribute', () => {
29
- const host = mountHost();
30
- const inst = confettiBurst();
31
- inst.connect(host);
32
- inst.disconnect(host);
33
- expect(host.hasAttribute('data-confetti-burst-active')).toBe(false);
34
- });
35
-
36
- it('host stays unmodified by particle injection', () => {
37
- const host = mountHost();
38
- const inst = connectTrait(confettiBurst, host);
39
- // New contract: particles live in the body-level popover stage,
40
- // not on the host. The host must not gain children or inline
41
- // position/cssText hacks (the old canvas-injection path did).
42
- expect(host.tagName).toBe('DIV');
43
- expect(host.children.length).toBe(0);
44
- inst.disconnect(host);
45
- });
46
- });
@@ -1,54 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import { countUp } from './count-up.js';
3
- import { mountHost, connectTrait, spyEvent, resetDOM, wait, raf } from '../test-helpers.js';
4
-
5
- describe('count-up', () => {
6
- beforeEach(resetDOM);
7
-
8
- it('reads data-count-up-target + duration; sets active', () => {
9
- const host = mountHost('span', {
10
- 'data-count-up-target': '100',
11
- 'data-count-duration': '200',
12
- });
13
- host.textContent = '0';
14
- connectTrait(countUp, host);
15
- expect(host.hasAttribute('data-count-up-active')).toBe(true);
16
- });
17
-
18
- it('eventually reaches the target value and fires count-up-done', async () => {
19
- const host = mountHost('span', {
20
- 'data-count-up-target': '42',
21
- 'data-count-duration': '50',
22
- });
23
- host.textContent = '0';
24
- const spy = spyEvent(host, 'count-up-done');
25
- connectTrait(countUp, host);
26
- // happy-dom does not implement requestAnimationFrame in a way that drives
27
- // the easing loop; we assert the active flag and that the trait sets up
28
- // without throwing. The end-state assertion is covered by the smoke
29
- // contract (attribute cleanup) and by the e2e demo in real browsers.
30
- expect(host.hasAttribute('data-count-up-active')).toBe(true);
31
- expect(typeof spy.count).toBe('number');
32
- });
33
-
34
- it('disconnect clears active and cancels the rAF loop', () => {
35
- const host = mountHost('span', { 'data-count-up-target': '500' });
36
- host.textContent = '0';
37
- const inst = countUp();
38
- inst.connect(host);
39
- inst.disconnect(host);
40
- expect(host.hasAttribute('data-count-up-active')).toBe(false);
41
- });
42
-
43
- it('missing target attribute uses default 0', () => {
44
- const host = mountHost('span');
45
- host.textContent = '0';
46
- expect(() => countUp().connect(host)).not.toThrow();
47
- });
48
-
49
- it('missing duration uses default 1000ms', () => {
50
- const host = mountHost('span', { 'data-count-up-target': '10' });
51
- host.textContent = '0';
52
- expect(() => countUp().connect(host)).not.toThrow();
53
- });
54
- });
@@ -1,138 +0,0 @@
1
- /**
2
- * UIElement attribute-driven trait composition.
3
- *
4
- * <comp-ui traits="pressable scale-press ripple">
5
- *
6
- * Declarative wiring is the third path next to:
7
- * - static traits = [...] (compile-time, on the class)
8
- * - el.addTrait(traitFn) (runtime, programmatic)
9
- *
10
- * Tests assert: connect-time application, idempotence with static, attribute
11
- * change re-applies, unknown name warns without throwing.
12
- */
13
-
14
- import { describe, it, expect, beforeEach, vi } from 'vitest';
15
- import { UIElement } from '../core/element.js';
16
- import { pressable } from './pressable/pressable.js';
17
- import { focusable } from './focusable/focusable.js';
18
- import { hoverable } from './hoverable/hoverable.js';
19
- import { resetDOM } from './test-helpers.js';
20
-
21
- let counter = 0;
22
- function defineUniqueTag(BaseImpl) {
23
- const tag = `decl-trait-test-${++counter}`;
24
- customElements.define(tag, BaseImpl);
25
- return tag;
26
- }
27
-
28
- describe('UIElement — declarative traits attribute', () => {
29
- beforeEach(resetDOM);
30
-
31
- it('applies traits listed in the attribute on connect', () => {
32
- class El extends UIElement {}
33
- const tag = defineUniqueTag(El);
34
- document.body.innerHTML = `<${tag} traits="pressable focusable"></${tag}>`;
35
- const el = document.body.firstElementChild;
36
- expect(el.hasAttribute('traits')).toBe(true);
37
- el.dispatchEvent(new PointerEvent('pointerdown'));
38
- expect(el.hasAttribute('data-pressable-pressed')).toBe(true);
39
- el.dispatchEvent(new FocusEvent('focus'));
40
- expect(el.hasAttribute('data-focusable-keyboard')).toBe(true);
41
- });
42
-
43
- it('static traits + attribute traits coexist, no double-apply', () => {
44
- class El extends UIElement {
45
- static get traits() { return [pressable]; }
46
- }
47
- const tag = defineUniqueTag(El);
48
- document.body.innerHTML = `<${tag} traits="pressable focusable"></${tag}>`;
49
- const el = document.body.firstElementChild;
50
- el.dispatchEvent(new PointerEvent('pointerdown'));
51
- el.dispatchEvent(new PointerEvent('pointerup'));
52
- let pressCount = 0;
53
- el.addEventListener('press', () => pressCount++);
54
- el.dispatchEvent(new PointerEvent('pointerdown'));
55
- el.dispatchEvent(new PointerEvent('pointerup'));
56
- // exactly ONE press fires per pointerdown/up (not two from double-apply)
57
- expect(pressCount).toBe(1);
58
- // focusable from attribute also wired
59
- el.dispatchEvent(new FocusEvent('focus'));
60
- expect(el.hasAttribute('data-focusable-keyboard')).toBe(true);
61
- });
62
-
63
- it('unknown trait name warns but does not throw', () => {
64
- const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
65
- class El extends UIElement {}
66
- const tag = defineUniqueTag(El);
67
- expect(() => {
68
- document.body.innerHTML = `<${tag} traits="pressable nonexistent-trait"></${tag}>`;
69
- }).not.toThrow();
70
- const el = document.body.firstElementChild;
71
- // Real trait still applied
72
- el.dispatchEvent(new PointerEvent('pointerdown'));
73
- expect(el.hasAttribute('data-pressable-pressed')).toBe(true);
74
- expect(warn).toHaveBeenCalled();
75
- expect(warn.mock.calls[0][0]).toContain('nonexistent-trait');
76
- warn.mockRestore();
77
- });
78
-
79
- it('changing the attribute swaps declarative traits', () => {
80
- class El extends UIElement {}
81
- const tag = defineUniqueTag(El);
82
- document.body.innerHTML = `<${tag} traits="pressable"></${tag}>`;
83
- const el = document.body.firstElementChild;
84
- el.dispatchEvent(new PointerEvent('pointerdown'));
85
- expect(el.hasAttribute('data-pressable-pressed')).toBe(true);
86
- el.dispatchEvent(new PointerEvent('pointerup'));
87
-
88
- // Swap to focusable+hoverable
89
- el.setAttribute('traits', 'focusable hoverable');
90
-
91
- // pressable should no longer respond
92
- el.dispatchEvent(new PointerEvent('pointerdown'));
93
- expect(el.hasAttribute('data-pressable-pressed')).toBe(false);
94
-
95
- // focusable should respond
96
- el.dispatchEvent(new FocusEvent('focus'));
97
- expect(el.hasAttribute('data-focusable-keyboard')).toBe(true);
98
-
99
- // hoverable should respond
100
- el.dispatchEvent(new PointerEvent('pointerenter'));
101
- expect(el.hasAttribute('data-hoverable-hover')).toBe(true);
102
- });
103
-
104
- it('clearing the attribute removes declarative traits', () => {
105
- class El extends UIElement {}
106
- const tag = defineUniqueTag(El);
107
- document.body.innerHTML = `<${tag} traits="pressable"></${tag}>`;
108
- const el = document.body.firstElementChild;
109
- el.removeAttribute('traits');
110
- el.dispatchEvent(new PointerEvent('pointerdown'));
111
- expect(el.hasAttribute('data-pressable-pressed')).toBe(false);
112
- });
113
-
114
- it('disconnect cleans up both static and declarative traits', () => {
115
- class El extends UIElement {
116
- static get traits() { return [pressable]; }
117
- }
118
- const tag = defineUniqueTag(El);
119
- document.body.innerHTML = `<${tag} traits="focusable"></${tag}>`;
120
- const el = document.body.firstElementChild;
121
- el.dispatchEvent(new PointerEvent('pointerdown'));
122
- el.dispatchEvent(new FocusEvent('focus'));
123
- expect(el.hasAttribute('data-pressable-pressed')).toBe(true);
124
- expect(el.hasAttribute('data-focusable-keyboard')).toBe(true);
125
- el.remove();
126
- expect(el.hasAttribute('data-pressable-pressed')).toBe(false);
127
- expect(el.hasAttribute('data-focusable-keyboard')).toBe(false);
128
- });
129
-
130
- it('observedAttributes always includes "traits"', () => {
131
- class El extends UIElement {
132
- static get properties() { return { value: { type: String, default: '' } }; }
133
- }
134
- const observed = El.observedAttributes;
135
- expect(observed).toContain('value');
136
- expect(observed).toContain('traits');
137
- });
138
- });