@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,110 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* toggle-scheme-ui §200 race-fix tests — v0.5.7 (FEEDBACK-10 §1).
|
|
3
|
-
*
|
|
4
|
-
* Verifies the post-connect attribute application is HONORED — not raced over
|
|
5
|
-
* by the synchronous #initState() call in connected(). Per the bug class:
|
|
6
|
-
*
|
|
7
|
-
* 1. template.js scan() strips placeholder attributes pre-insertion
|
|
8
|
-
* 2. replaceChildren() upgrades the element; connectedCallback fires
|
|
9
|
-
* 3. #initState() reads this.scheme (default 'auto')
|
|
10
|
-
* 4. template.js update() then setAttribute('scheme', 'dark')
|
|
11
|
-
* 5. attributeChangedCallback syncs to property — but #initState already ran
|
|
12
|
-
*
|
|
13
|
-
* Pre-§200, step 4 was a no-op for the scheme cascade. Post-§200,
|
|
14
|
-
* attributeChangedCallback re-runs #initState() (until user-touched).
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
18
|
-
import '../../core/element.js';
|
|
19
|
-
import './toggle-scheme.js';
|
|
20
|
-
|
|
21
|
-
const tick = () => new Promise((r) => queueMicrotask(r));
|
|
22
|
-
|
|
23
|
-
function mount(html) {
|
|
24
|
-
const wrap = document.createElement('div');
|
|
25
|
-
wrap.innerHTML = html;
|
|
26
|
-
document.body.appendChild(wrap);
|
|
27
|
-
return wrap.firstElementChild;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
describe('toggle-scheme-ui §200 race fix', () => {
|
|
31
|
-
beforeEach(() => {
|
|
32
|
-
document.body.innerHTML = '';
|
|
33
|
-
document.documentElement.style.removeProperty('color-scheme');
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('honors post-connect scheme attribute application (reactive consumer)', async () => {
|
|
37
|
-
// Simulate template.js's strip-then-restamp pattern: mount without scheme,
|
|
38
|
-
// then set the attribute immediately afterward (mimics post-connect apply).
|
|
39
|
-
const t = mount('<toggle-scheme-ui></toggle-scheme-ui>');
|
|
40
|
-
await tick();
|
|
41
|
-
t.setAttribute('scheme', 'dark');
|
|
42
|
-
await tick();
|
|
43
|
-
expect(t.activeScheme).toBe('dark');
|
|
44
|
-
expect(document.documentElement.style.colorScheme).toBe('dark');
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('honors the initial scheme attribute (no race scenario)', async () => {
|
|
48
|
-
const t = mount('<toggle-scheme-ui scheme="dark"></toggle-scheme-ui>');
|
|
49
|
-
await tick();
|
|
50
|
-
expect(t.activeScheme).toBe('dark');
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('user button press locks #userTouched — subsequent attr changes are ignored', async () => {
|
|
54
|
-
const t = mount('<toggle-scheme-ui scheme="dark"></toggle-scheme-ui>');
|
|
55
|
-
await tick();
|
|
56
|
-
expect(t.activeScheme).toBe('dark');
|
|
57
|
-
|
|
58
|
-
// User press: flips to light. #userTouched flips true.
|
|
59
|
-
const btn = t.querySelector(':scope > button-ui');
|
|
60
|
-
btn.dispatchEvent(new CustomEvent('press', { bubbles: true }));
|
|
61
|
-
await tick();
|
|
62
|
-
expect(t.activeScheme).toBe('light');
|
|
63
|
-
|
|
64
|
-
// Consumer re-renders with scheme="dark" — should NOT clobber user choice.
|
|
65
|
-
t.setAttribute('scheme', 'dark');
|
|
66
|
-
await tick();
|
|
67
|
-
expect(t.activeScheme).toBe('light'); // user choice survives
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('programmatic setScheme also locks #userTouched', async () => {
|
|
71
|
-
const t = mount('<toggle-scheme-ui scheme="auto"></toggle-scheme-ui>');
|
|
72
|
-
await tick();
|
|
73
|
-
|
|
74
|
-
t.setScheme('dark');
|
|
75
|
-
await tick();
|
|
76
|
-
expect(t.activeScheme).toBe('dark');
|
|
77
|
-
|
|
78
|
-
// Consumer reactive write should NOT override.
|
|
79
|
-
t.setAttribute('scheme', 'light');
|
|
80
|
-
await tick();
|
|
81
|
-
expect(t.activeScheme).toBe('dark');
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('toggle() also locks #userTouched', async () => {
|
|
85
|
-
const t = mount('<toggle-scheme-ui scheme="auto"></toggle-scheme-ui>');
|
|
86
|
-
await tick();
|
|
87
|
-
const initial = t.activeScheme; // depends on prefers-color-scheme (likely light in test env)
|
|
88
|
-
|
|
89
|
-
t.toggle();
|
|
90
|
-
await tick();
|
|
91
|
-
expect(t.activeScheme).not.toBe(initial);
|
|
92
|
-
|
|
93
|
-
const afterToggle = t.activeScheme;
|
|
94
|
-
t.setAttribute('scheme', initial === 'dark' ? 'dark' : 'light');
|
|
95
|
-
await tick();
|
|
96
|
-
expect(t.activeScheme).toBe(afterToggle); // user choice survives
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('removing the attribute (back to auto) is honored pre-touch', async () => {
|
|
100
|
-
const t = mount('<toggle-scheme-ui scheme="dark"></toggle-scheme-ui>');
|
|
101
|
-
await tick();
|
|
102
|
-
expect(t.activeScheme).toBe('dark');
|
|
103
|
-
|
|
104
|
-
t.removeAttribute('scheme');
|
|
105
|
-
await tick();
|
|
106
|
-
// After removal, attr is null; #initState falls to auto → resolves
|
|
107
|
-
// prefers-color-scheme. Test env should resolve to light by default.
|
|
108
|
-
expect(['light', 'dark']).toContain(t.activeScheme);
|
|
109
|
-
});
|
|
110
|
-
});
|
|
@@ -1,312 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* <tree-ui> behavioral tests.
|
|
3
|
-
*
|
|
4
|
-
* FB-46 (v0.5.19): tree-select detail now forwards ctrlKey/metaKey/shiftKey
|
|
5
|
-
* from the originating MouseEvent or KeyboardEvent. Programmatic select()
|
|
6
|
-
* calls (no event) surface all three as false.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { describe, it, expect, beforeAll, beforeEach, afterEach } from 'vitest';
|
|
10
|
-
import { html, stamp } from '../../core/template.js';
|
|
11
|
-
|
|
12
|
-
beforeAll(async () => {
|
|
13
|
-
await import('./tree.js');
|
|
14
|
-
await import('../button/button.js');
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
describe('<tree-ui> tree-select forwards modifier keys (FB-46)', () => {
|
|
18
|
-
let host;
|
|
19
|
-
let tree;
|
|
20
|
-
|
|
21
|
-
beforeEach(async () => {
|
|
22
|
-
host = document.createElement('div');
|
|
23
|
-
document.body.appendChild(host);
|
|
24
|
-
|
|
25
|
-
tree = document.createElement('tree-ui');
|
|
26
|
-
const a = document.createElement('tree-item-ui');
|
|
27
|
-
a.setAttribute('text', 'Alpha');
|
|
28
|
-
a.setAttribute('value', 'a');
|
|
29
|
-
const b = document.createElement('tree-item-ui');
|
|
30
|
-
b.setAttribute('text', 'Beta');
|
|
31
|
-
b.setAttribute('value', 'b');
|
|
32
|
-
tree.appendChild(a);
|
|
33
|
-
tree.appendChild(b);
|
|
34
|
-
host.appendChild(tree);
|
|
35
|
-
|
|
36
|
-
// Let connectedCallback + #stamp() settle.
|
|
37
|
-
await new Promise((r) => setTimeout(r, 30));
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
afterEach(() => {
|
|
41
|
-
host.remove();
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('programmatic select() emits ctrlKey/metaKey/shiftKey = false', () => {
|
|
45
|
-
let received = null;
|
|
46
|
-
tree.addEventListener('tree-select', (e) => { received = e.detail; });
|
|
47
|
-
|
|
48
|
-
const alpha = tree.querySelector('tree-item-ui[value="a"]');
|
|
49
|
-
tree.select(alpha);
|
|
50
|
-
|
|
51
|
-
expect(received).not.toBeNull();
|
|
52
|
-
expect(received.item).toBe(alpha);
|
|
53
|
-
expect(received.text).toBe('Alpha');
|
|
54
|
-
expect(received.value).toBe('a');
|
|
55
|
-
expect(received.ctrlKey).toBe(false);
|
|
56
|
-
expect(received.metaKey).toBe(false);
|
|
57
|
-
expect(received.shiftKey).toBe(false);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('select(item, event) forwards Mouse/KeyboardEvent modifier flags into detail', () => {
|
|
61
|
-
let received = null;
|
|
62
|
-
tree.addEventListener('tree-select', (e) => { received = e.detail; });
|
|
63
|
-
|
|
64
|
-
const alpha = tree.querySelector('tree-item-ui[value="a"]');
|
|
65
|
-
// Synthesize an event with all three modifiers held.
|
|
66
|
-
const fakeEvent = new MouseEvent('click', {
|
|
67
|
-
ctrlKey: true,
|
|
68
|
-
metaKey: true,
|
|
69
|
-
shiftKey: true,
|
|
70
|
-
});
|
|
71
|
-
tree.select(alpha, fakeEvent);
|
|
72
|
-
|
|
73
|
-
expect(received.ctrlKey).toBe(true);
|
|
74
|
-
expect(received.metaKey).toBe(true);
|
|
75
|
-
expect(received.shiftKey).toBe(true);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('detail shape stays backward-compatible (existing fields unchanged)', () => {
|
|
79
|
-
let received = null;
|
|
80
|
-
tree.addEventListener('tree-select', (e) => { received = e.detail; });
|
|
81
|
-
|
|
82
|
-
const beta = tree.querySelector('tree-item-ui[value="b"]');
|
|
83
|
-
tree.select(beta);
|
|
84
|
-
|
|
85
|
-
// Existing consumers destructure { item, text, value } — those keys
|
|
86
|
-
// MUST remain present + carry the expected values.
|
|
87
|
-
const { item, text, value } = received;
|
|
88
|
-
expect(item).toBe(beta);
|
|
89
|
-
expect(text).toBe('Beta');
|
|
90
|
-
expect(value).toBe('b');
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it('partial modifier state passes through (single key held)', () => {
|
|
94
|
-
let received = null;
|
|
95
|
-
tree.addEventListener('tree-select', (e) => { received = e.detail; });
|
|
96
|
-
|
|
97
|
-
const alpha = tree.querySelector('tree-item-ui[value="a"]');
|
|
98
|
-
const onlyMeta = new MouseEvent('click', { metaKey: true });
|
|
99
|
-
tree.select(alpha, onlyMeta);
|
|
100
|
-
|
|
101
|
-
expect(received.ctrlKey).toBe(false);
|
|
102
|
-
expect(received.metaKey).toBe(true);
|
|
103
|
-
expect(received.shiftKey).toBe(false);
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
describe('<tree-item-ui> caret slot + actions adoption (caret convention + FB-89)', () => {
|
|
108
|
-
let host;
|
|
109
|
-
const settle = () => new Promise((r) => setTimeout(r, 30));
|
|
110
|
-
|
|
111
|
-
beforeEach(() => {
|
|
112
|
-
host = document.createElement('div');
|
|
113
|
-
document.body.appendChild(host);
|
|
114
|
-
});
|
|
115
|
-
afterEach(() => host.remove());
|
|
116
|
-
|
|
117
|
-
it('auto-stamps a slot="caret" icon (renamed from slot="chevron")', async () => {
|
|
118
|
-
const item = document.createElement('tree-item-ui');
|
|
119
|
-
item.setAttribute('text', 'Colors');
|
|
120
|
-
host.appendChild(item);
|
|
121
|
-
await settle();
|
|
122
|
-
const row = item.querySelector(':scope > [slot="row"]');
|
|
123
|
-
expect(row.querySelector('[slot="caret"]')).toBeTruthy();
|
|
124
|
-
expect(row.querySelector('[slot="chevron"]')).toBeNull();
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it('adopts a declarative slot="actions" child into the stamped row (FB-89)', async () => {
|
|
128
|
-
const item = document.createElement('tree-item-ui');
|
|
129
|
-
item.setAttribute('text', 'Colors');
|
|
130
|
-
const btn = document.createElement('button-ui');
|
|
131
|
-
btn.setAttribute('slot', 'actions');
|
|
132
|
-
btn.setAttribute('icon', 'plus');
|
|
133
|
-
item.appendChild(btn);
|
|
134
|
-
host.appendChild(item);
|
|
135
|
-
await settle();
|
|
136
|
-
const row = item.querySelector(':scope > [slot="row"]');
|
|
137
|
-
// The action button is now INSIDE the row, not a sibling left below it.
|
|
138
|
-
expect(btn.parentElement).toBe(row);
|
|
139
|
-
expect(item.querySelector(':scope > [slot="actions"]')).toBeNull();
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it('adopts a declarative slot="caret" child instead of stamping a default', async () => {
|
|
143
|
-
const item = document.createElement('tree-item-ui');
|
|
144
|
-
item.setAttribute('text', 'Colors');
|
|
145
|
-
const customCaret = document.createElement('icon-ui');
|
|
146
|
-
customCaret.setAttribute('slot', 'caret');
|
|
147
|
-
customCaret.setAttribute('name', 'folder');
|
|
148
|
-
item.appendChild(customCaret);
|
|
149
|
-
host.appendChild(item);
|
|
150
|
-
await settle();
|
|
151
|
-
const row = item.querySelector(':scope > [slot="row"]');
|
|
152
|
-
const carets = row.querySelectorAll('[slot="caret"]');
|
|
153
|
-
expect(carets.length).toBe(1); // no double-stamp
|
|
154
|
-
expect(carets[0]).toBe(customCaret); // consumer's caret used
|
|
155
|
-
expect(carets[0].getAttribute('name')).toBe('folder');
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
describe('<tree-item-ui> ARIA tree containment (FEEDBACK-91)', () => {
|
|
160
|
-
let host;
|
|
161
|
-
const settle = () => new Promise((r) => setTimeout(r, 30));
|
|
162
|
-
|
|
163
|
-
beforeEach(() => {
|
|
164
|
-
host = document.createElement('div');
|
|
165
|
-
document.body.appendChild(host);
|
|
166
|
-
});
|
|
167
|
-
afterEach(() => host.remove());
|
|
168
|
-
|
|
169
|
-
// Mirrors axe-core's `aria-required-parent` check for `treeitem`: walking up
|
|
170
|
-
// from the treeitem, the first ancestor carrying a *non-generic* role must be
|
|
171
|
-
// `group` or `tree`. Roleless / presentation / display:contents wrapper spans
|
|
172
|
-
// are transparent pass-throughs (exactly the consumer's `.map()` case).
|
|
173
|
-
function requiredParentSatisfied(treeitem, root) {
|
|
174
|
-
let p = treeitem.parentElement;
|
|
175
|
-
while (p && p !== root.parentElement) {
|
|
176
|
-
const role = p.getAttribute('role');
|
|
177
|
-
if (role === 'group' || role === 'tree') return true;
|
|
178
|
-
if (role && role !== 'presentation' && role !== 'none') return false;
|
|
179
|
-
p = p.parentElement;
|
|
180
|
-
}
|
|
181
|
-
return false;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function buildStaticTree() {
|
|
185
|
-
const tree = document.createElement('tree-ui');
|
|
186
|
-
const parent = document.createElement('tree-item-ui');
|
|
187
|
-
parent.setAttribute('text', 'Colors');
|
|
188
|
-
parent.setAttribute('open', '');
|
|
189
|
-
for (const t of ['Neutral', 'Brand']) {
|
|
190
|
-
const c = document.createElement('tree-item-ui');
|
|
191
|
-
c.setAttribute('text', t);
|
|
192
|
-
parent.appendChild(c);
|
|
193
|
-
}
|
|
194
|
-
tree.appendChild(parent);
|
|
195
|
-
host.appendChild(tree);
|
|
196
|
-
return tree;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
it('roles the host as group and the [slot="row"] as treeitem', async () => {
|
|
200
|
-
const tree = buildStaticTree();
|
|
201
|
-
await settle();
|
|
202
|
-
expect(tree.getAttribute('role')).toBe('tree');
|
|
203
|
-
for (const item of tree.querySelectorAll('tree-item-ui')) {
|
|
204
|
-
expect(item.getAttribute('role')).toBe('group');
|
|
205
|
-
const row = item.querySelector(':scope > [slot="row"]');
|
|
206
|
-
expect(row.getAttribute('role')).toBe('treeitem');
|
|
207
|
-
expect(row.getAttribute('tabindex')).toBe('0');
|
|
208
|
-
}
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
it('every treeitem row has a group/tree required parent (static markup)', async () => {
|
|
212
|
-
const tree = buildStaticTree();
|
|
213
|
-
await settle();
|
|
214
|
-
const rows = tree.querySelectorAll('[role="treeitem"]');
|
|
215
|
-
expect(rows.length).toBe(3); // parent + 2 children — was the axe-critical case
|
|
216
|
-
for (const row of rows) expect(requiredParentSatisfied(row, tree)).toBe(true);
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
it('nested treeitems satisfy required-parent when rendered via the template engine .map() (consumer repro)', async () => {
|
|
220
|
-
const ITEMS = [{ id: 'neutral', text: 'Neutral' }, { id: 'brand', text: 'Brand' }];
|
|
221
|
-
stamp(html`
|
|
222
|
-
<tree-ui>
|
|
223
|
-
<tree-item-ui text="Colors" open>
|
|
224
|
-
${ITEMS.map((i) => html`<tree-item-ui .text=${i.text} .value=${i.id}></tree-item-ui>`)}
|
|
225
|
-
</tree-item-ui>
|
|
226
|
-
</tree-ui>
|
|
227
|
-
`, host);
|
|
228
|
-
await settle();
|
|
229
|
-
|
|
230
|
-
const tree = host.querySelector('tree-ui');
|
|
231
|
-
const rows = tree.querySelectorAll('[role="treeitem"]');
|
|
232
|
-
expect(rows.length).toBe(3);
|
|
233
|
-
for (const row of rows) expect(requiredParentSatisfied(row, tree)).toBe(true);
|
|
234
|
-
|
|
235
|
-
// The nested items sit BEHIND the template engine's display:contents wrapper
|
|
236
|
-
// span — proving the fix works without re-homing engine-owned nodes.
|
|
237
|
-
const child = tree.querySelector('tree-item-ui[value="neutral"]');
|
|
238
|
-
expect(child.parentElement.style.display).toBe('contents');
|
|
239
|
-
expect(child.getAttribute('role')).toBe('group');
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
it('aria-expanded lives on the row (treeitem), not the host group', async () => {
|
|
243
|
-
const tree = buildStaticTree();
|
|
244
|
-
await settle();
|
|
245
|
-
const parent = tree.querySelector('tree-item-ui');
|
|
246
|
-
const parentRow = parent.querySelector(':scope > [slot="row"]');
|
|
247
|
-
expect(parentRow.getAttribute('aria-expanded')).toBe('true');
|
|
248
|
-
expect(parent.hasAttribute('aria-expanded')).toBe(false); // not stranded on the host
|
|
249
|
-
|
|
250
|
-
const leaf = tree.querySelectorAll('tree-item-ui')[1]; // a child leaf
|
|
251
|
-
const leafRow = leaf.querySelector(':scope > [slot="row"]');
|
|
252
|
-
expect(leafRow.hasAttribute('aria-expanded')).toBe(false); // leaves carry none
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
it('aria-selected reflects onto the selected row only', async () => {
|
|
256
|
-
const tree = buildStaticTree();
|
|
257
|
-
await settle();
|
|
258
|
-
const [parent, neutral] = tree.querySelectorAll('tree-item-ui');
|
|
259
|
-
tree.select(neutral);
|
|
260
|
-
await settle();
|
|
261
|
-
expect(neutral.querySelector(':scope > [slot="row"]').getAttribute('aria-selected')).toBe('true');
|
|
262
|
-
expect(parent.querySelector(':scope > [slot="row"]').hasAttribute('aria-selected')).toBe(false);
|
|
263
|
-
});
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
describe('<tree-item-ui> adopts interpolated [slot="actions"] into the row (FEEDBACK-96)', () => {
|
|
267
|
-
let host;
|
|
268
|
-
const settle = () => new Promise((r) => setTimeout(r, 40));
|
|
269
|
-
beforeEach(() => { host = document.createElement('div'); document.body.appendChild(host); });
|
|
270
|
-
afterEach(() => host.remove());
|
|
271
|
-
|
|
272
|
-
it('moves a `.map()`-rendered actions button into the treeitem row (not a stray child of the group)', async () => {
|
|
273
|
-
const PALS = [{ id: 'n', name: 'Neutral' }, { id: 'b', name: 'Brand' }];
|
|
274
|
-
stamp(html`
|
|
275
|
-
<tree-ui>
|
|
276
|
-
<tree-item-ui text="Colors" value="colors" open>
|
|
277
|
-
${html`<button-ui slot="actions" icon="plus" title="Add"></button-ui>`}
|
|
278
|
-
${PALS.map((p) => html`<tree-item-ui text="${p.name}" value="${p.id}"></tree-item-ui>`)}
|
|
279
|
-
</tree-item-ui>
|
|
280
|
-
</tree-ui>
|
|
281
|
-
`, host);
|
|
282
|
-
await settle();
|
|
283
|
-
|
|
284
|
-
const colors = host.querySelector('tree-item-ui[value="colors"]');
|
|
285
|
-
const row = colors.querySelector(':scope > [slot="row"]');
|
|
286
|
-
const btn = colors.querySelector('button-ui[slot="actions"]');
|
|
287
|
-
expect(btn).not.toBeNull();
|
|
288
|
-
// The fix: the interpolated button (wrapped in a display:contents span) is
|
|
289
|
-
// adopted INTO the row. Pre-fix, #stamp's `:scope >` query missed it (the
|
|
290
|
-
// button arrives after #stamp, inside a wrapper span) and it stayed a bare
|
|
291
|
-
// child of the group/tree → axe aria-required-children.
|
|
292
|
-
expect(btn.parentElement).toBe(row);
|
|
293
|
-
// Auto-stamped empty placeholder dropped — exactly one actions slot in the row.
|
|
294
|
-
expect(row.querySelectorAll(':scope > [slot="actions"]').length).toBe(1);
|
|
295
|
-
// Nested items NOT mis-adopted as actions (the tree-item-ui boundary holds).
|
|
296
|
-
expect(host.querySelectorAll('tree-item-ui').length).toBe(3);
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
it('is idempotent — no double-adopt across settles', async () => {
|
|
300
|
-
stamp(html`
|
|
301
|
-
<tree-ui>
|
|
302
|
-
<tree-item-ui text="Colors" value="colors" open>
|
|
303
|
-
${html`<button-ui slot="actions" icon="plus"></button-ui>`}
|
|
304
|
-
</tree-item-ui>
|
|
305
|
-
</tree-ui>
|
|
306
|
-
`, host);
|
|
307
|
-
await settle();
|
|
308
|
-
await settle();
|
|
309
|
-
const row = host.querySelector('tree-item-ui[value="colors"] > [slot="row"]');
|
|
310
|
-
expect(row.querySelectorAll(':scope > [slot="actions"]').length).toBe(1);
|
|
311
|
-
});
|
|
312
|
-
});
|