@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,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
- });