@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.
- package/CHANGELOG.md +26 -0
- package/components/card/card.a2ui.json +6 -3
- package/components/card/card.css +15 -1
- package/components/card/card.yaml +5 -3
- package/components/nav-group/nav-group.a2ui.json +5 -0
- package/components/nav-group/nav-group.class.js +14 -6
- package/components/nav-group/nav-group.css +5 -2
- package/components/nav-group/nav-group.d.ts +2 -0
- package/components/nav-group/nav-group.yaml +4 -0
- package/components/select/select.a2ui.json +3 -2
- package/components/select/select.class.js +14 -1
- package/components/select/select.css +13 -1
- package/components/select/select.yaml +5 -4
- package/components/table/table.class.js +7 -3
- package/components/table/table.css +7 -5
- package/dist/theme-provider.min.js +1 -1
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +19 -19
- package/dist/web-components.sheet.js +1 -1
- package/package.json +2 -1
- package/components/adia-mark/adia-mark.test.js +0 -69
- package/components/adia-wordmark/adia-wordmark.test.js +0 -69
- package/components/agent-artifact/agent-artifact.test.js +0 -61
- package/components/alert/alert.test.js +0 -364
- package/components/color-picker/color-picker.test.js +0 -96
- package/components/combobox/combobox.test.js +0 -256
- package/components/command/command.test.js +0 -99
- package/components/date-range-picker/date-range-picker.test.js +0 -387
- package/components/datetime-picker/datetime-picker.test.js +0 -454
- package/components/demo-toggle/demo-toggle.test.js +0 -102
- package/components/description-list/description-list.test.js +0 -72
- package/components/display-field/display-field.test.js +0 -115
- package/components/empty-state/empty-state.test.js +0 -77
- package/components/field/field.test.js +0 -260
- package/components/fields/fields.test.js +0 -88
- package/components/inline-message/inline-message.test.js +0 -203
- package/components/input/input.test.js +0 -335
- package/components/integration-card/integration-card.test.js +0 -318
- package/components/link/link.test.js +0 -143
- package/components/list/list.test.js +0 -106
- package/components/list-window/list-window.test.js +0 -303
- package/components/loading-overlay/loading-overlay.test.js +0 -257
- package/components/menu/menu.test.js +0 -176
- package/components/nav/nav.test.js +0 -428
- package/components/nav-group/nav-group.test.js +0 -67
- package/components/option-card/option-card.test.js +0 -32
- package/components/pagination/pagination.test.js +0 -195
- package/components/select/select.test.js +0 -481
- package/components/slider/slider.test.js +0 -202
- package/components/spinner/spinner.test.js +0 -272
- package/components/stat/stat.test.js +0 -108
- package/components/step-progress/step-progress.test.js +0 -129
- package/components/swatch/swatch.test.js +0 -563
- package/components/table/table.test.js +0 -479
- package/components/table-toolbar/table-toolbar.test.js +0 -152
- package/components/tag/tag.test.js +0 -137
- package/components/tags-input/tags-input.test.js +0 -477
- package/components/text/text.test.js +0 -202
- package/components/theme-provider/theme-provider.test.js +0 -94
- package/components/time-picker/time-picker.test.js +0 -287
- package/components/timeline/timeline.test.js +0 -31
- package/components/toggle-scheme/toggle-scheme.test.js +0 -110
- package/components/tree/tree.test.js +0 -312
- package/core/element.test.js +0 -389
- package/core/icons.test.js +0 -187
- package/core/logical-children.test.js +0 -54
- package/core/markdown.test.js +0 -47
- package/core/responsive.test.js +0 -121
- package/core/template.test.js +0 -611
- package/traits/active-state/active-state.test.js +0 -28
- package/traits/anchor-positioning/anchor-positioning.test.js +0 -122
- package/traits/announcer/announcer.test.js +0 -268
- package/traits/arrow-grid-nav/arrow-grid-nav.test.js +0 -375
- package/traits/attention-pulse/attention-pulse.test.js +0 -26
- package/traits/confetti/confetti.test.js +0 -46
- package/traits/confetti-burst/confetti-burst.test.js +0 -46
- package/traits/count-up/count-up.test.js +0 -54
- package/traits/declarative.test.js +0 -138
- package/traits/dirty-state/dirty-state.test.js +0 -45
- package/traits/drag-ghost/drag-ghost.test.js +0 -19
- package/traits/draggable/draggable.test.js +0 -60
- package/traits/draggable-list-item/draggable-list-item.test.js +0 -51
- package/traits/drop-target/drop-target.test.js +0 -241
- package/traits/droppable/droppable.test.js +0 -54
- package/traits/droppable-collection/droppable-collection.test.js +0 -99
- package/traits/error-shake/error-shake.test.js +0 -114
- package/traits/fade-presence/fade-presence.test.js +0 -20
- package/traits/focus-restore/focus-restore.test.js +0 -202
- package/traits/focus-trap/focus-trap.test.js +0 -42
- package/traits/focusable/focusable.test.js +0 -53
- package/traits/glow-focus/glow-focus.test.js +0 -31
- package/traits/gradient-shift/gradient-shift.test.js +0 -22
- package/traits/haptic-feedback/haptic-feedback.test.js +0 -52
- package/traits/hotkey/hotkey.test.js +0 -61
- package/traits/hoverable/hoverable.test.js +0 -24
- package/traits/inertia-drag/inertia-drag.test.js +0 -33
- package/traits/input-mask/input-mask.test.js +0 -151
- package/traits/intersection-observer/intersection-observer.test.js +0 -38
- package/traits/keyboard-nav/keyboard-nav.test.js +0 -41
- package/traits/keyboard-reorderable/keyboard-reorderable.test.js +0 -45
- package/traits/layout-animation/layout-animation.test.js +0 -114
- package/traits/long-press/long-press.test.js +0 -244
- package/traits/magnetic-hover/magnetic-hover.test.js +0 -30
- package/traits/noise-texture/noise-texture.test.js +0 -20
- package/traits/parallax/parallax.test.js +0 -26
- package/traits/portal/portal.test.js +0 -30
- package/traits/pressable/pressable.test.js +0 -73
- package/traits/resettable/resettable.test.js +0 -100
- package/traits/resizable/resizable.test.js +0 -39
- package/traits/resize-observer/resize-observer.test.js +0 -38
- package/traits/ripple/ripple.test.js +0 -32
- package/traits/roving-tabindex/roving-tabindex.test.js +0 -28
- package/traits/scale-press/scale-press.test.js +0 -39
- package/traits/scroll-lock/scroll-lock.test.js +0 -45
- package/traits/scroll-progress/scroll-progress.test.js +0 -182
- package/traits/shimmer-loading/shimmer-loading.test.js +0 -43
- package/traits/smoke.test.js +0 -111
- package/traits/snap-to-grid/snap-to-grid.test.js +0 -40
- package/traits/sound-feedback/sound-feedback.test.js +0 -26
- package/traits/spring-animate/spring-animate.test.js +0 -28
- package/traits/success-checkmark/success-checkmark.test.js +0 -120
- package/traits/tilt-hover/tilt-hover.test.js +0 -32
- package/traits/tossable/tossable.test.js +0 -31
- package/traits/traits-host.test.js +0 -73
- package/traits/typeahead/typeahead.test.js +0 -38
- package/traits/typewriter/typewriter.test.js +0 -47
- package/traits/validation/validation.test.js +0 -93
- package/traits/view-transition/view-transition.test.js +0 -268
|
@@ -1,479 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* table-ui — focused unit tests for the v0.6.18 loading=skeleton-rows
|
|
3
|
-
* behavior change (FB-12 P2 resolution).
|
|
4
|
-
*
|
|
5
|
-
* Pre-v0.6.18: `loading=true` rendered a `<progress-ui>` spinner overlay
|
|
6
|
-
* inside the body via `[data-loading]`. The yaml description always said
|
|
7
|
-
* "Shows a loading overlay AND skeleton rows" but the impl only did the
|
|
8
|
-
* overlay — yaml-vs-impl drift since the initial table cut.
|
|
9
|
-
*
|
|
10
|
-
* v0.6.18 changes the loading branch to render N ghost skeleton rows
|
|
11
|
-
* (N = paginate if set, else 5) inside the [data-body] rowgroup. Header +
|
|
12
|
-
* columns stay intact so layout is preserved. Sets aria-busy="true" on the
|
|
13
|
-
* host. Old `[data-loading]` overlay element is removed if it lingers.
|
|
14
|
-
*
|
|
15
|
-
* Note: table-ui needs columns + data set imperatively (via the .columns /
|
|
16
|
-
* .data properties on the element) — declarative <col-def> children also
|
|
17
|
-
* work but require the col-def element to be registered first. Tests use
|
|
18
|
-
* the imperative path.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
22
|
-
import '../../core/element.js';
|
|
23
|
-
import './table.js';
|
|
24
|
-
import '../skeleton/skeleton.js';
|
|
25
|
-
|
|
26
|
-
const tick = () => new Promise((r) => queueMicrotask(r));
|
|
27
|
-
const raf = () => new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(r)));
|
|
28
|
-
|
|
29
|
-
function mount(html) {
|
|
30
|
-
const wrap = document.createElement('div');
|
|
31
|
-
wrap.innerHTML = html;
|
|
32
|
-
document.body.appendChild(wrap);
|
|
33
|
-
return wrap.firstElementChild;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const COLS = [
|
|
37
|
-
{ key: 'id', label: 'ID' },
|
|
38
|
-
{ key: 'name', label: 'Name' },
|
|
39
|
-
{ key: 'email', label: 'Email' },
|
|
40
|
-
];
|
|
41
|
-
const ROWS = [
|
|
42
|
-
{ id: 1, name: 'Alice', email: 'alice@acme.com' },
|
|
43
|
-
{ id: 2, name: 'Bob', email: 'bob@acme.com' },
|
|
44
|
-
];
|
|
45
|
-
|
|
46
|
-
describe('table-ui — v0.6.18 loading=skeleton-rows (FB-12 P2)', () => {
|
|
47
|
-
beforeEach(() => { document.body.innerHTML = ''; });
|
|
48
|
-
|
|
49
|
-
it('renders skeleton rows in [data-body] when [loading] is set', async () => {
|
|
50
|
-
const el = mount('<table-ui></table-ui>');
|
|
51
|
-
el.columns = COLS;
|
|
52
|
-
el.data = ROWS;
|
|
53
|
-
await tick();
|
|
54
|
-
el.setAttribute('loading', '');
|
|
55
|
-
await tick();
|
|
56
|
-
const body = el.querySelector(':scope > [data-body]');
|
|
57
|
-
expect(body).not.toBeNull();
|
|
58
|
-
const skRows = body.querySelectorAll('[data-skeleton-row]');
|
|
59
|
-
expect(skRows.length).toBeGreaterThan(0);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('sets aria-busy="true" on the host when [loading]', async () => {
|
|
63
|
-
const el = mount('<table-ui></table-ui>');
|
|
64
|
-
el.columns = COLS;
|
|
65
|
-
el.data = ROWS;
|
|
66
|
-
await tick();
|
|
67
|
-
el.setAttribute('loading', '');
|
|
68
|
-
await tick();
|
|
69
|
-
expect(el.getAttribute('aria-busy')).toBe('true');
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('replaces real body rows with skeleton rows when [loading]', async () => {
|
|
73
|
-
const el = mount('<table-ui></table-ui>');
|
|
74
|
-
el.columns = COLS;
|
|
75
|
-
el.data = ROWS;
|
|
76
|
-
// table-ui uses requestAnimationFrame in #requestRender(), not microtasks.
|
|
77
|
-
// queueMicrotask-based tick() won't drain RAF callbacks — must await raf().
|
|
78
|
-
// Loop up to 5 RAF cycles in case the initial mount needs multiple renders
|
|
79
|
-
// to settle (columns set → render → data set → render).
|
|
80
|
-
for (let i = 0; i < 5; i++) {
|
|
81
|
-
await raf();
|
|
82
|
-
const body = el.querySelector(':scope > [data-body]');
|
|
83
|
-
if (body && body.children.length >= 2) break;
|
|
84
|
-
}
|
|
85
|
-
const body = el.querySelector(':scope > [data-body]');
|
|
86
|
-
expect(body).not.toBeNull();
|
|
87
|
-
// Real rows present (2)
|
|
88
|
-
const realRows = body.querySelectorAll(':scope > [role="row"]:not([data-skeleton-row])');
|
|
89
|
-
expect(realRows.length).toBe(2);
|
|
90
|
-
el.setAttribute('loading', '');
|
|
91
|
-
await raf();
|
|
92
|
-
// Real rows gone, skeleton rows present
|
|
93
|
-
expect(body.querySelectorAll(':scope > [role="row"]:not([data-skeleton-row])').length).toBe(0);
|
|
94
|
-
expect(body.querySelectorAll(':scope > [data-skeleton-row]').length).toBeGreaterThan(0);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it('each skeleton row has cell count matching column count', async () => {
|
|
98
|
-
const el = mount('<table-ui></table-ui>');
|
|
99
|
-
el.columns = COLS; // 3 columns
|
|
100
|
-
el.data = ROWS;
|
|
101
|
-
await tick();
|
|
102
|
-
el.setAttribute('loading', '');
|
|
103
|
-
await tick();
|
|
104
|
-
const skRow = el.querySelector('[data-skeleton-row]');
|
|
105
|
-
expect(skRow.children.length).toBe(3);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it('each skeleton cell contains a <skeleton-ui> shimmer element', async () => {
|
|
109
|
-
const el = mount('<table-ui></table-ui>');
|
|
110
|
-
el.columns = COLS;
|
|
111
|
-
el.data = ROWS;
|
|
112
|
-
await tick();
|
|
113
|
-
el.setAttribute('loading', '');
|
|
114
|
-
await tick();
|
|
115
|
-
const skCells = el.querySelectorAll('[data-skeleton-row] > [role="gridcell"]');
|
|
116
|
-
expect(skCells.length).toBeGreaterThan(0);
|
|
117
|
-
for (const cell of skCells) {
|
|
118
|
-
const sk = cell.querySelector('skeleton-ui');
|
|
119
|
-
expect(sk).not.toBeNull();
|
|
120
|
-
expect(sk.tagName.toLowerCase()).toBe('skeleton-ui');
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it('does NOT render a [data-loading] overlay element (old behavior removed)', async () => {
|
|
125
|
-
const el = mount('<table-ui></table-ui>');
|
|
126
|
-
el.columns = COLS;
|
|
127
|
-
el.data = ROWS;
|
|
128
|
-
await tick();
|
|
129
|
-
el.setAttribute('loading', '');
|
|
130
|
-
await tick();
|
|
131
|
-
// Old impl created <div data-loading> with <progress-ui> child.
|
|
132
|
-
// v0.6.18 does NOT — skeleton rows ARE the loading affordance.
|
|
133
|
-
expect(el.querySelector(':scope > [data-loading]')).toBeNull();
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it('restores real rows + clears aria-busy when loading toggles off', async () => {
|
|
137
|
-
const el = mount('<table-ui></table-ui>');
|
|
138
|
-
el.columns = COLS;
|
|
139
|
-
el.data = ROWS;
|
|
140
|
-
await tick();
|
|
141
|
-
el.setAttribute('loading', '');
|
|
142
|
-
await tick();
|
|
143
|
-
el.removeAttribute('loading');
|
|
144
|
-
await tick();
|
|
145
|
-
expect(el.getAttribute('aria-busy')).toBeNull();
|
|
146
|
-
expect(el.querySelectorAll('[data-body] > [data-skeleton-row]').length).toBe(0);
|
|
147
|
-
expect(el.querySelectorAll('[data-body] > [role="row"]').length).toBe(2);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it('skeleton row count tracks paginate when set, capped at 8', async () => {
|
|
151
|
-
const el = mount('<table-ui paginate="3"></table-ui>');
|
|
152
|
-
el.columns = COLS;
|
|
153
|
-
el.data = ROWS;
|
|
154
|
-
await tick();
|
|
155
|
-
el.setAttribute('loading', '');
|
|
156
|
-
await tick();
|
|
157
|
-
expect(el.querySelectorAll('[data-body] > [data-skeleton-row]').length).toBe(3);
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it('preserves header row when [loading]', async () => {
|
|
161
|
-
const el = mount('<table-ui></table-ui>');
|
|
162
|
-
el.columns = COLS;
|
|
163
|
-
el.data = ROWS;
|
|
164
|
-
await tick();
|
|
165
|
-
const beforeHeader = el.querySelector(':scope > [data-header]');
|
|
166
|
-
expect(beforeHeader).not.toBeNull();
|
|
167
|
-
el.setAttribute('loading', '');
|
|
168
|
-
await tick();
|
|
169
|
-
const afterHeader = el.querySelector(':scope > [data-header]');
|
|
170
|
-
expect(afterHeader).not.toBeNull();
|
|
171
|
-
// Header cells unchanged in count
|
|
172
|
-
expect(afterHeader.children.length).toBe(3);
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Body-cell wrap default + opt-in paths (PATCH-class behavior change).
|
|
178
|
-
*
|
|
179
|
-
* Default: cells truncate single-line with ellipsis (the CSS rule sets
|
|
180
|
-
* white-space: nowrap; overflow: hidden; text-overflow: ellipsis). Three
|
|
181
|
-
* opt-in surfaces lift the truncation:
|
|
182
|
-
* - host [wrap] attribute (reflected from the `wrap` prop)
|
|
183
|
-
* - col.wrap=true in columns[] → renderer sets [data-wrap] on each
|
|
184
|
-
* body cell in that column
|
|
185
|
-
* - [data-wrap] directly on a hand-authored <td>
|
|
186
|
-
*
|
|
187
|
-
* Tests lock the attribute contract; CSS-rule application is covered by
|
|
188
|
-
* the source-grep stylistic invariants of table.css.
|
|
189
|
-
*/
|
|
190
|
-
describe('table-ui — wrap default + opt-in', () => {
|
|
191
|
-
beforeEach(() => { document.body.innerHTML = ''; });
|
|
192
|
-
|
|
193
|
-
it('host [wrap] attribute reflects from the wrap property', async () => {
|
|
194
|
-
const el = mount('<table-ui></table-ui>');
|
|
195
|
-
el.columns = COLS;
|
|
196
|
-
el.data = ROWS;
|
|
197
|
-
await raf();
|
|
198
|
-
expect(el.hasAttribute('wrap')).toBe(false);
|
|
199
|
-
el.wrap = true;
|
|
200
|
-
await raf();
|
|
201
|
-
expect(el.hasAttribute('wrap')).toBe(true);
|
|
202
|
-
el.wrap = false;
|
|
203
|
-
await raf();
|
|
204
|
-
expect(el.hasAttribute('wrap')).toBe(false);
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
it('col.wrap=true marks body cells in that column with [data-wrap]', async () => {
|
|
208
|
-
const el = mount('<table-ui></table-ui>');
|
|
209
|
-
el.columns = [
|
|
210
|
-
{ key: 'id', label: 'ID' },
|
|
211
|
-
{ key: 'name', label: 'Name', wrap: true },
|
|
212
|
-
{ key: 'email', label: 'Email' },
|
|
213
|
-
];
|
|
214
|
-
el.data = ROWS;
|
|
215
|
-
await raf();
|
|
216
|
-
const bodyRows = el.querySelectorAll(':scope > [data-body] > [role="row"]');
|
|
217
|
-
expect(bodyRows.length).toBe(2);
|
|
218
|
-
for (const row of bodyRows) {
|
|
219
|
-
const cells = row.querySelectorAll('[role="gridcell"]');
|
|
220
|
-
expect(cells.length).toBe(3);
|
|
221
|
-
expect(cells[0].hasAttribute('data-wrap')).toBe(false);
|
|
222
|
-
expect(cells[1].hasAttribute('data-wrap')).toBe(true);
|
|
223
|
-
expect(cells[2].hasAttribute('data-wrap')).toBe(false);
|
|
224
|
-
}
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
it('omitting col.wrap leaves all body cells without [data-wrap]', async () => {
|
|
228
|
-
const el = mount('<table-ui></table-ui>');
|
|
229
|
-
el.columns = COLS;
|
|
230
|
-
el.data = ROWS;
|
|
231
|
-
await raf();
|
|
232
|
-
const cells = el.querySelectorAll(':scope > [data-body] [role="gridcell"]');
|
|
233
|
-
expect(cells.length).toBeGreaterThan(0);
|
|
234
|
-
for (const cell of cells) {
|
|
235
|
-
expect(cell.hasAttribute('data-wrap')).toBe(false);
|
|
236
|
-
}
|
|
237
|
-
});
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
describe('table-ui — FB-47 keyboard: Enter activates focused body cell', () => {
|
|
241
|
-
beforeEach(() => { document.body.innerHTML = ''; });
|
|
242
|
-
|
|
243
|
-
it('Enter on a focused body cell fires cell-click with the row data', async () => {
|
|
244
|
-
const el = mount('<table-ui></table-ui>');
|
|
245
|
-
el.columns = COLS;
|
|
246
|
-
el.data = ROWS;
|
|
247
|
-
await raf();
|
|
248
|
-
|
|
249
|
-
// ArrowDown from null initialises #focusedCell to {row:0,col:0} then
|
|
250
|
-
// increments to {row:1,col:0} and calls #updateFocus → [data-focused] on
|
|
251
|
-
// the second body row cell.
|
|
252
|
-
el.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }));
|
|
253
|
-
await tick();
|
|
254
|
-
|
|
255
|
-
let fired = null;
|
|
256
|
-
el.addEventListener('cell-click', (e) => { fired = e.detail; });
|
|
257
|
-
|
|
258
|
-
el.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
|
|
259
|
-
await tick();
|
|
260
|
-
|
|
261
|
-
expect(fired).not.toBeNull();
|
|
262
|
-
expect(fired.row).toMatchObject({ id: 2, name: 'Bob' }); // index 1 = second row
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
it('Enter on a focused header cell does NOT fire cell-click (sort path only)', async () => {
|
|
266
|
-
const el = mount('<table-ui></table-ui>');
|
|
267
|
-
el.columns = COLS;
|
|
268
|
-
el.data = ROWS;
|
|
269
|
-
await raf();
|
|
270
|
-
|
|
271
|
-
// ArrowUp from null initialises to {row:0,col:0} then decrements to
|
|
272
|
-
// {row:-1,col:0} → header row.
|
|
273
|
-
el.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp', bubbles: true }));
|
|
274
|
-
await tick();
|
|
275
|
-
|
|
276
|
-
let cellClickFired = false;
|
|
277
|
-
el.addEventListener('cell-click', () => { cellClickFired = true; });
|
|
278
|
-
|
|
279
|
-
el.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
|
|
280
|
-
await tick();
|
|
281
|
-
|
|
282
|
-
// Header Enter triggers sort (or no-op if not sortable), never cell-click.
|
|
283
|
-
expect(cellClickFired).toBe(false);
|
|
284
|
-
});
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* Raw mode: visual-only passthrough — consumer owns the body shape.
|
|
289
|
-
*
|
|
290
|
-
* Pre-v0.6.33 (FB-53 §2), `<table-ui raw>` only applied the CSS chrome
|
|
291
|
-
* reset (background/border/radius transparent) but the data lifecycle
|
|
292
|
-
* still ran. With `.data === []` (the natural state when wrapping a
|
|
293
|
-
* consumer-authored native <table>), `#renderOverlays` injected a
|
|
294
|
-
* [data-empty] "No data" placeholder above the consumer's content.
|
|
295
|
-
*
|
|
296
|
-
* v0.6.33 short-circuits `render()` at the top when `this.raw` is set:
|
|
297
|
-
* no header injection, no body reconciliation, no overlays, no
|
|
298
|
-
* aggregation, no pagination.
|
|
299
|
-
*/
|
|
300
|
-
describe('table-ui — raw mode passthrough (FB-53 §2)', () => {
|
|
301
|
-
beforeEach(() => { document.body.innerHTML = ''; });
|
|
302
|
-
|
|
303
|
-
it('raw mode does NOT inject [data-empty] overlay when .data is empty', async () => {
|
|
304
|
-
const el = mount('<table-ui raw><table><tr><td>foo</td></tr></table></table-ui>');
|
|
305
|
-
// No .data set → empty by default. Without the raw early-return,
|
|
306
|
-
// #renderOverlays would create the [data-empty] placeholder.
|
|
307
|
-
await raf();
|
|
308
|
-
expect(el.querySelector(':scope > [data-empty]')).toBeNull();
|
|
309
|
-
// Consumer's native <table> survives untouched.
|
|
310
|
-
expect(el.querySelector(':scope > table')).not.toBeNull();
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
it('raw mode does NOT inject [data-header] or [data-body] rowgroups', async () => {
|
|
314
|
-
const el = mount('<table-ui raw><table><tr><td>x</td></tr></table></table-ui>');
|
|
315
|
-
await raf();
|
|
316
|
-
expect(el.querySelector(':scope > [data-header]')).toBeNull();
|
|
317
|
-
expect(el.querySelector(':scope > [data-body]')).toBeNull();
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
it('raw mode preserves consumer markup even when .columns + .data are set', async () => {
|
|
321
|
-
const el = mount('<table-ui raw><table id="consumer"><tr><td>consumer-owned</td></tr></table></table-ui>');
|
|
322
|
-
el.columns = COLS;
|
|
323
|
-
el.data = ROWS;
|
|
324
|
-
await raf();
|
|
325
|
-
// The consumer's native <table> is still the only table-shaped child.
|
|
326
|
-
expect(el.querySelector('#consumer')).not.toBeNull();
|
|
327
|
-
expect(el.querySelectorAll(':scope > [role="rowgroup"]').length).toBe(0);
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
it('non-raw mode (default) still injects header + body rowgroups', async () => {
|
|
331
|
-
const el = mount('<table-ui></table-ui>');
|
|
332
|
-
el.columns = COLS;
|
|
333
|
-
el.data = ROWS;
|
|
334
|
-
await raf();
|
|
335
|
-
expect(el.querySelector(':scope > [data-header]')).not.toBeNull();
|
|
336
|
-
expect(el.querySelector(':scope > [data-body]')).not.toBeNull();
|
|
337
|
-
});
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
// ── F6 (upstream): per-row expand gate via `rowExpandable` ───────────────────
|
|
341
|
-
describe('table-ui — F6 per-row expand suppression (rowExpandable)', () => {
|
|
342
|
-
beforeEach(() => { document.body.innerHTML = ''; });
|
|
343
|
-
|
|
344
|
-
const bodyRows = (el) =>
|
|
345
|
-
el.querySelectorAll(':scope > [data-body] > [role="row"]:not([data-detail-row])');
|
|
346
|
-
|
|
347
|
-
it('default (no predicate): every row carries an expand caret', async () => {
|
|
348
|
-
const el = mount('<table-ui expandable></table-ui>');
|
|
349
|
-
el.columns = COLS;
|
|
350
|
-
el.data = ROWS;
|
|
351
|
-
await raf();
|
|
352
|
-
const rows = bodyRows(el);
|
|
353
|
-
expect(rows.length).toBe(2);
|
|
354
|
-
for (const row of rows) {
|
|
355
|
-
expect(row.querySelector('[data-expand-toggle]')).not.toBeNull();
|
|
356
|
-
}
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
it('predicate suppresses the caret on gated rows but keeps the reserved cell', async () => {
|
|
360
|
-
const el = mount('<table-ui expandable></table-ui>');
|
|
361
|
-
el.columns = COLS;
|
|
362
|
-
el.data = ROWS;
|
|
363
|
-
el.rowExpandable = (row) => row.id === 2; // only Bob expandable
|
|
364
|
-
await raf();
|
|
365
|
-
const rows = bodyRows(el);
|
|
366
|
-
|
|
367
|
-
// Alice (id 1) — reserved cell present, NO caret button.
|
|
368
|
-
expect(rows[0].querySelector(':scope > [data-expand-col]')).not.toBeNull();
|
|
369
|
-
expect(rows[0].querySelector('[data-expand-toggle]')).toBeNull();
|
|
370
|
-
|
|
371
|
-
// Bob (id 2) — caret button present.
|
|
372
|
-
expect(rows[1].querySelector('[data-expand-toggle]')).not.toBeNull();
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
it('toggleExpand is a no-op on a gated row (no detail row renders)', async () => {
|
|
376
|
-
const el = mount('<table-ui expandable></table-ui>');
|
|
377
|
-
el.columns = COLS;
|
|
378
|
-
el.data = ROWS;
|
|
379
|
-
el.rowExpandable = (row) => row.id === 2;
|
|
380
|
-
await raf();
|
|
381
|
-
|
|
382
|
-
el.toggleExpand(0); // Alice — gated out
|
|
383
|
-
await raf();
|
|
384
|
-
expect(el.querySelector('[data-detail-row][data-for="0"]')).toBeNull();
|
|
385
|
-
expect(el.expanded).not.toContain(0);
|
|
386
|
-
|
|
387
|
-
el.toggleExpand(1); // Bob — allowed
|
|
388
|
-
await raf();
|
|
389
|
-
expect(el.expanded).toContain(1);
|
|
390
|
-
});
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
describe('table-ui — "markdown" cell type (TKT-0008)', () => {
|
|
394
|
-
it('renders inline `code`/**bold**/*italic*/[link](href) as real elements, not literal syntax', async () => {
|
|
395
|
-
const el = mount('<table-ui></table-ui>');
|
|
396
|
-
el.columns = [{ key: 'desc', label: 'Description', type: 'markdown' }];
|
|
397
|
-
el.data = [{ desc: 'Semantic status: `new` | `updated` | **beta** or *deprecated*, see [docs](/site/components/badge).' }];
|
|
398
|
-
await raf();
|
|
399
|
-
|
|
400
|
-
const cell = el.querySelector('[role="gridcell"][data-key="desc"]');
|
|
401
|
-
expect([...cell.querySelectorAll('code')].map((c) => c.textContent)).toEqual(['new', 'updated']);
|
|
402
|
-
expect(cell.querySelector('strong').textContent).toBe('beta');
|
|
403
|
-
expect(cell.querySelector('em').textContent).toBe('deprecated');
|
|
404
|
-
expect(cell.querySelector('a').getAttribute('href')).toBe('/site/components/badge');
|
|
405
|
-
expect(cell.textContent).not.toMatch(/`|\*\*|\[docs\]/); // no literal markdown syntax leaked through
|
|
406
|
-
});
|
|
407
|
-
|
|
408
|
-
it('sets [title] to the plain-text (tag-stripped) version, for the truncation tooltip', async () => {
|
|
409
|
-
const el = mount('<table-ui></table-ui>');
|
|
410
|
-
el.columns = [{ key: 'prop', label: 'Prop', type: 'markdown' }];
|
|
411
|
-
el.data = [{ prop: '`variant`' }];
|
|
412
|
-
await raf();
|
|
413
|
-
|
|
414
|
-
const cell = el.querySelector('[role="gridcell"][data-key="prop"]');
|
|
415
|
-
expect(cell.title).toBe('variant');
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
it('a plain value with no markdown syntax renders as ordinary text (no stray elements)', async () => {
|
|
419
|
-
const el = mount('<table-ui></table-ui>');
|
|
420
|
-
el.columns = [{ key: 'desc', label: 'Description', type: 'markdown' }];
|
|
421
|
-
el.data = [{ desc: 'Plain sentence, no special characters.' }];
|
|
422
|
-
await raf();
|
|
423
|
-
|
|
424
|
-
const cell = el.querySelector('[role="gridcell"][data-key="desc"]');
|
|
425
|
-
expect(cell.querySelector('code, strong, em, a')).toBeNull();
|
|
426
|
-
expect(cell.textContent).toBe('Plain sentence, no special characters.');
|
|
427
|
-
});
|
|
428
|
-
});
|
|
429
|
-
|
|
430
|
-
// gh#288 — declarative `data="[…]"` attribute, mirroring chart-ui's existing
|
|
431
|
-
// pattern. Row data previously had no declarative form at all — `columns`
|
|
432
|
-
// already had `<col-def>` children; `data` had nothing, so a static-HTML or
|
|
433
|
-
// SSR-authored table always rendered its data empty.
|
|
434
|
-
describe('table-ui — declarative data="[…]" attribute (gh#288)', () => {
|
|
435
|
-
beforeEach(() => { document.body.innerHTML = ''; });
|
|
436
|
-
|
|
437
|
-
it('hydrates .data from a JSON-array data attribute at connect', async () => {
|
|
438
|
-
const el = mount(
|
|
439
|
-
`<table-ui data='${JSON.stringify(ROWS)}'></table-ui>`,
|
|
440
|
-
);
|
|
441
|
-
el.columns = COLS;
|
|
442
|
-
await raf();
|
|
443
|
-
expect(el.data).toEqual(ROWS);
|
|
444
|
-
expect(el.querySelectorAll('[role="gridcell"][data-key="name"]').length).toBe(2);
|
|
445
|
-
});
|
|
446
|
-
|
|
447
|
-
it('a malformed data attribute is ignored silently, not thrown', async () => {
|
|
448
|
-
expect(() => mount(`<table-ui data='not json'></table-ui>`)).not.toThrow();
|
|
449
|
-
const el = document.querySelector('table-ui');
|
|
450
|
-
expect(el.data).toEqual([]);
|
|
451
|
-
});
|
|
452
|
-
|
|
453
|
-
it('a non-array JSON value in the attribute is ignored, not coerced', async () => {
|
|
454
|
-
const el = mount(`<table-ui data='{"not":"an array"}'></table-ui>`);
|
|
455
|
-
await raf();
|
|
456
|
-
expect(el.data).toEqual([]);
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
it('a programmatic .data set before connect wins over the attribute', () => {
|
|
460
|
-
const el = document.createElement('table-ui');
|
|
461
|
-
el.setAttribute('data', JSON.stringify(ROWS));
|
|
462
|
-
el.data = [{ id: 99, name: 'Override', email: 'override@acme.com' }];
|
|
463
|
-
document.body.appendChild(el);
|
|
464
|
-
expect(el.data).toEqual([{ id: 99, name: 'Override', email: 'override@acme.com' }]);
|
|
465
|
-
});
|
|
466
|
-
|
|
467
|
-
it('works together with declarative <col-def> children — fully static-HTML table', async () => {
|
|
468
|
-
const el = mount(`
|
|
469
|
-
<table-ui data='${JSON.stringify(ROWS)}'>
|
|
470
|
-
<col-def key="id" label="ID"></col-def>
|
|
471
|
-
<col-def key="name" label="Name"></col-def>
|
|
472
|
-
</table-ui>
|
|
473
|
-
`);
|
|
474
|
-
await raf();
|
|
475
|
-
expect(el.data).toEqual(ROWS);
|
|
476
|
-
expect(el.columns.length).toBe(2);
|
|
477
|
-
expect(el.querySelectorAll('[role="gridcell"][data-key="name"]').length).toBe(2);
|
|
478
|
-
});
|
|
479
|
-
});
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* table-toolbar-ui tests
|
|
3
|
-
*
|
|
4
|
-
* Pinning the §381 (v0.6.10 slice F) one-shot dev-mode warning for
|
|
5
|
-
* unknown opt-out attributes. Triggered by claims-ui FB-04: consumers
|
|
6
|
-
* set `searchable` / `exportable` / `sortable` thinking they toggle
|
|
7
|
-
* visibility; canonical attrs are `no-search`/`no-filter`/`no-sort`/
|
|
8
|
-
* `no-columns`. Previous behavior: silent no-op.
|
|
9
|
-
*
|
|
10
|
-
* Contract:
|
|
11
|
-
* 1. Unknown attrs matching /^(search|export|sort|filter|column)/i
|
|
12
|
-
* → console.warn fires once per element with the attr names listed
|
|
13
|
-
* 2. Canonical attrs (no-search/no-filter/no-sort/no-columns) → no warn
|
|
14
|
-
* 3. Unrelated attrs (id, class, role, aria-*, data-*) → no warn
|
|
15
|
-
* 4. Multiple unknown attrs in one declaration → single warn (one warn
|
|
16
|
-
* per element, all unknown attrs in the message)
|
|
17
|
-
* 5. Repeated reconnects of the same element → still one warn total
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
21
|
-
import '../../core/element.js';
|
|
22
|
-
import './table-toolbar.js';
|
|
23
|
-
|
|
24
|
-
const tick = () => new Promise((r) => queueMicrotask(r));
|
|
25
|
-
|
|
26
|
-
function mount(html) {
|
|
27
|
-
const wrap = document.createElement('div');
|
|
28
|
-
wrap.innerHTML = html;
|
|
29
|
-
document.body.appendChild(wrap);
|
|
30
|
-
return wrap.firstElementChild;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
describe('table-toolbar-ui — unknown opt-out attribute warning (§381 / FB-04)', () => {
|
|
34
|
-
beforeEach(() => { document.body.innerHTML = ''; });
|
|
35
|
-
|
|
36
|
-
it('warns when an unknown `searchable` attribute is set', async () => {
|
|
37
|
-
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
38
|
-
mount('<table-toolbar-ui searchable></table-toolbar-ui>');
|
|
39
|
-
await tick();
|
|
40
|
-
expect(warn).toHaveBeenCalledTimes(1);
|
|
41
|
-
const msg = warn.mock.calls[0][0];
|
|
42
|
-
expect(msg).toContain('[table-toolbar-ui]');
|
|
43
|
-
expect(msg).toContain('"searchable"');
|
|
44
|
-
expect(msg).toContain('no-search');
|
|
45
|
-
warn.mockRestore();
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('warns when an unknown `exportable` attribute is set', async () => {
|
|
49
|
-
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
50
|
-
mount('<table-toolbar-ui exportable></table-toolbar-ui>');
|
|
51
|
-
await tick();
|
|
52
|
-
expect(warn).toHaveBeenCalledTimes(1);
|
|
53
|
-
const msg = warn.mock.calls[0][0];
|
|
54
|
-
expect(msg).toContain('"exportable"');
|
|
55
|
-
warn.mockRestore();
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('warns when an unknown `sortable` attribute is set', async () => {
|
|
59
|
-
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
60
|
-
mount('<table-toolbar-ui sortable></table-toolbar-ui>');
|
|
61
|
-
await tick();
|
|
62
|
-
expect(warn).toHaveBeenCalledTimes(1);
|
|
63
|
-
expect(warn.mock.calls[0][0]).toContain('"sortable"');
|
|
64
|
-
warn.mockRestore();
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('warns when an unknown `filterable` attribute is set', async () => {
|
|
68
|
-
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
69
|
-
mount('<table-toolbar-ui filterable></table-toolbar-ui>');
|
|
70
|
-
await tick();
|
|
71
|
-
expect(warn).toHaveBeenCalledTimes(1);
|
|
72
|
-
expect(warn.mock.calls[0][0]).toContain('"filterable"');
|
|
73
|
-
warn.mockRestore();
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('warns when an unknown `columns-visible` attribute is set', async () => {
|
|
77
|
-
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
78
|
-
mount('<table-toolbar-ui columns-visible></table-toolbar-ui>');
|
|
79
|
-
await tick();
|
|
80
|
-
expect(warn).toHaveBeenCalledTimes(1);
|
|
81
|
-
expect(warn.mock.calls[0][0]).toContain('"columns-visible"');
|
|
82
|
-
warn.mockRestore();
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('does NOT warn on canonical no-search', async () => {
|
|
86
|
-
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
87
|
-
mount('<table-toolbar-ui no-search></table-toolbar-ui>');
|
|
88
|
-
await tick();
|
|
89
|
-
expect(warn).not.toHaveBeenCalled();
|
|
90
|
-
warn.mockRestore();
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it('does NOT warn on canonical no-filter / no-sort / no-columns', async () => {
|
|
94
|
-
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
95
|
-
mount('<table-toolbar-ui no-filter no-sort no-columns></table-toolbar-ui>');
|
|
96
|
-
await tick();
|
|
97
|
-
expect(warn).not.toHaveBeenCalled();
|
|
98
|
-
warn.mockRestore();
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('does NOT warn on unrelated attributes (id, class, role, aria-*, data-*)', async () => {
|
|
102
|
-
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
103
|
-
mount('<table-toolbar-ui id="x" class="y" role="toolbar" aria-label="z" data-test="ok"></table-toolbar-ui>');
|
|
104
|
-
await tick();
|
|
105
|
-
expect(warn).not.toHaveBeenCalled();
|
|
106
|
-
warn.mockRestore();
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('does NOT warn on canonical attrs even when placeholder is also set', async () => {
|
|
110
|
-
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
111
|
-
mount('<table-toolbar-ui no-search no-sort placeholder="Find..."></table-toolbar-ui>');
|
|
112
|
-
await tick();
|
|
113
|
-
expect(warn).not.toHaveBeenCalled();
|
|
114
|
-
warn.mockRestore();
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it('emits a single warn that names all unknown opt-out attrs', async () => {
|
|
118
|
-
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
119
|
-
mount('<table-toolbar-ui searchable exportable sortable></table-toolbar-ui>');
|
|
120
|
-
await tick();
|
|
121
|
-
expect(warn).toHaveBeenCalledTimes(1);
|
|
122
|
-
const msg = warn.mock.calls[0][0];
|
|
123
|
-
expect(msg).toContain('"searchable"');
|
|
124
|
-
expect(msg).toContain('"exportable"');
|
|
125
|
-
expect(msg).toContain('"sortable"');
|
|
126
|
-
warn.mockRestore();
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it('warns at most once per element across reconnects', async () => {
|
|
130
|
-
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
131
|
-
const el = mount('<table-toolbar-ui searchable></table-toolbar-ui>');
|
|
132
|
-
await tick();
|
|
133
|
-
expect(warn).toHaveBeenCalledTimes(1);
|
|
134
|
-
// Disconnect + reconnect.
|
|
135
|
-
el.remove();
|
|
136
|
-
document.body.appendChild(el);
|
|
137
|
-
await tick();
|
|
138
|
-
expect(warn).toHaveBeenCalledTimes(1); // unchanged
|
|
139
|
-
warn.mockRestore();
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it('mixes canonical + unknown — names only the unknown in the warning', async () => {
|
|
143
|
-
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
144
|
-
mount('<table-toolbar-ui no-search searchable></table-toolbar-ui>');
|
|
145
|
-
await tick();
|
|
146
|
-
expect(warn).toHaveBeenCalledTimes(1);
|
|
147
|
-
const msg = warn.mock.calls[0][0];
|
|
148
|
-
expect(msg).toContain('"searchable"');
|
|
149
|
-
expect(msg).not.toContain('"no-search"');
|
|
150
|
-
warn.mockRestore();
|
|
151
|
-
});
|
|
152
|
-
});
|