@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,122 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { anchorPositioning } from './anchor-positioning.js';
|
|
3
|
-
import { mountHost, connectTrait, spyEvent, resetDOM } from '../test-helpers.js';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Note on test environment: happy-dom does not implement
|
|
7
|
-
* `CSS.supports('anchor-name', '--x')` or the Popover API surface that
|
|
8
|
-
* actually moves an element into the top layer. Tests therefore land on
|
|
9
|
-
* the fallback path and assert the public contract (attrs, event,
|
|
10
|
-
* connect/disconnect symmetry) — not specific viewport coordinates.
|
|
11
|
-
* Real layout is exercised in the live browser demo.
|
|
12
|
-
*/
|
|
13
|
-
describe('anchor-positioning', () => {
|
|
14
|
-
beforeEach(resetDOM);
|
|
15
|
-
|
|
16
|
-
it('connect with valid anchor sets placed + actual + mode attributes', () => {
|
|
17
|
-
const anchor = document.createElement('div');
|
|
18
|
-
anchor.id = 'anchor-x';
|
|
19
|
-
document.body.appendChild(anchor);
|
|
20
|
-
const host = mountHost('div', {
|
|
21
|
-
'data-anchor': '#anchor-x',
|
|
22
|
-
'data-anchor-placement': 'bottom',
|
|
23
|
-
});
|
|
24
|
-
connectTrait(anchorPositioning, host);
|
|
25
|
-
expect(host.hasAttribute('data-anchor-positioning-placed')).toBe(true);
|
|
26
|
-
expect(host.getAttribute('data-anchor-placement-actual')).toBeTruthy();
|
|
27
|
-
expect(host.getAttribute('data-anchor-mode')).toMatch(/^(native|fallback)$/);
|
|
28
|
-
// Both paths set position:fixed — native because that's what the
|
|
29
|
-
// CSS Anchor Positioning helpers expect, fallback because it's the
|
|
30
|
-
// measured-coords positioning context.
|
|
31
|
-
expect(host.style.position).toBe('fixed');
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('dispatches anchor-placed event with the actual placement + mode in detail', () => {
|
|
35
|
-
const anchor = document.createElement('div');
|
|
36
|
-
anchor.id = 'anchor-y';
|
|
37
|
-
document.body.appendChild(anchor);
|
|
38
|
-
const host = mountHost('div', { 'data-anchor': '#anchor-y' });
|
|
39
|
-
const spy = spyEvent(host, 'anchor-placed');
|
|
40
|
-
connectTrait(anchorPositioning, host);
|
|
41
|
-
expect(spy.count).toBeGreaterThanOrEqual(1);
|
|
42
|
-
expect(spy.last.actual).toBeTruthy();
|
|
43
|
-
expect(spy.last.mode).toMatch(/^(native|fallback)$/);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('resolves anchor by selector OR by bare id', () => {
|
|
47
|
-
const anchor = document.createElement('div');
|
|
48
|
-
anchor.id = 'bare-id-anchor';
|
|
49
|
-
document.body.appendChild(anchor);
|
|
50
|
-
const host = mountHost('div', { 'data-anchor': 'bare-id-anchor' });
|
|
51
|
-
connectTrait(anchorPositioning, host);
|
|
52
|
-
expect(host.hasAttribute('data-anchor-positioning-placed')).toBe(true);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('missing anchor: does not throw, does not place', () => {
|
|
56
|
-
const host = mountHost('div', { 'data-anchor': '#nope' });
|
|
57
|
-
expect(() => connectTrait(anchorPositioning, host)).not.toThrow();
|
|
58
|
-
expect(host.hasAttribute('data-anchor-positioning-placed')).toBe(false);
|
|
59
|
-
expect(host.hasAttribute('data-anchor-mode')).toBe(false);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('missing data-anchor attribute: does not throw, does not place', () => {
|
|
63
|
-
const host = mountHost('div');
|
|
64
|
-
expect(() => connectTrait(anchorPositioning, host)).not.toThrow();
|
|
65
|
-
expect(host.hasAttribute('data-anchor-positioning-placed')).toBe(false);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('disconnect clears all three managed attributes and stops listening', () => {
|
|
69
|
-
const anchor = document.createElement('div');
|
|
70
|
-
anchor.id = 'anchor-z';
|
|
71
|
-
document.body.appendChild(anchor);
|
|
72
|
-
const host = mountHost('div', { 'data-anchor': '#anchor-z' });
|
|
73
|
-
const inst = connectTrait(anchorPositioning, host);
|
|
74
|
-
inst.disconnect(host);
|
|
75
|
-
expect(host.hasAttribute('data-anchor-positioning-placed')).toBe(false);
|
|
76
|
-
expect(host.hasAttribute('data-anchor-placement-actual')).toBe(false);
|
|
77
|
-
expect(host.hasAttribute('data-anchor-mode')).toBe(false);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('connect → disconnect → connect again does not throw', () => {
|
|
81
|
-
const anchor = document.createElement('div');
|
|
82
|
-
anchor.id = 'anchor-recycle';
|
|
83
|
-
document.body.appendChild(anchor);
|
|
84
|
-
const host = mountHost('div', { 'data-anchor': '#anchor-recycle' });
|
|
85
|
-
const inst1 = connectTrait(anchorPositioning, host);
|
|
86
|
-
inst1.disconnect(host);
|
|
87
|
-
expect(() => {
|
|
88
|
-
const inst2 = connectTrait(anchorPositioning, host);
|
|
89
|
-
inst2.disconnect(host);
|
|
90
|
-
}).not.toThrow();
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it('honors explicit placements (top / left / right)', () => {
|
|
94
|
-
for (const placement of ['top', 'left', 'right', 'bottom-start', 'top-end']) {
|
|
95
|
-
const anchor = document.createElement('div');
|
|
96
|
-
anchor.id = `anchor-${placement}`;
|
|
97
|
-
document.body.appendChild(anchor);
|
|
98
|
-
const host = mountHost('div', {
|
|
99
|
-
'data-anchor': `#anchor-${placement}`,
|
|
100
|
-
'data-anchor-placement': placement,
|
|
101
|
-
});
|
|
102
|
-
const inst = connectTrait(anchorPositioning, host);
|
|
103
|
-
expect(host.hasAttribute('data-anchor-positioning-placed')).toBe(true);
|
|
104
|
-
expect(host.getAttribute('data-anchor-placement-actual')).toBeTruthy();
|
|
105
|
-
inst.disconnect(host);
|
|
106
|
-
anchor.remove();
|
|
107
|
-
host.remove();
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it('honors data-anchor-gap (numeric, defaults to 0)', () => {
|
|
112
|
-
const anchor = document.createElement('div');
|
|
113
|
-
anchor.id = 'anchor-gap';
|
|
114
|
-
document.body.appendChild(anchor);
|
|
115
|
-
const host = mountHost('div', {
|
|
116
|
-
'data-anchor': '#anchor-gap',
|
|
117
|
-
'data-anchor-gap': '12',
|
|
118
|
-
});
|
|
119
|
-
expect(() => connectTrait(anchorPositioning, host)).not.toThrow();
|
|
120
|
-
expect(host.hasAttribute('data-anchor-positioning-placed')).toBe(true);
|
|
121
|
-
});
|
|
122
|
-
});
|
|
@@ -1,268 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { announcer } from './announcer.js';
|
|
3
|
-
import { _resetRegions, getRegion } from './announcer-stage.js';
|
|
4
|
-
import { mountHost, connectTrait, spyEvent, resetDOM, tick, wait } from '../test-helpers.js';
|
|
5
|
-
|
|
6
|
-
// happy-dom doesn't speak — we assert region textContent instead.
|
|
7
|
-
async function settleAnnounce() {
|
|
8
|
-
// The stage's blank-then-write uses queueMicrotask. Two ticks lets
|
|
9
|
-
// both the blank + the message land before we read.
|
|
10
|
-
await tick();
|
|
11
|
-
await tick();
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
describe('announcer', () => {
|
|
15
|
-
beforeEach(() => {
|
|
16
|
-
resetDOM();
|
|
17
|
-
_resetRegions();
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it('schema: declares announcement-made event + four config attrs', () => {
|
|
21
|
-
expect(announcer.schema.name).toBe('announcer');
|
|
22
|
-
expect(announcer.schema.category).toBe('audio-haptics-sensory');
|
|
23
|
-
expect(announcer.schema.events).toContain('announcement-made');
|
|
24
|
-
expect(announcer.schema.config).toEqual([
|
|
25
|
-
'data-announce-on',
|
|
26
|
-
'data-announce-message',
|
|
27
|
-
'data-announce-priority',
|
|
28
|
-
'data-announce-throttle',
|
|
29
|
-
]);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it('flips data-announcer-active on connect and cleans up on disconnect', () => {
|
|
33
|
-
const host = mountHost('div', { 'data-announce-on': 'click' });
|
|
34
|
-
const inst = connectTrait(announcer, host);
|
|
35
|
-
expect(host.hasAttribute('data-announcer-active')).toBe(true);
|
|
36
|
-
inst.disconnect(host);
|
|
37
|
-
expect(host.hasAttribute('data-announcer-active')).toBe(false);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('with no data-announce-on: trait connects + disconnects without listening', () => {
|
|
41
|
-
const host = mountHost();
|
|
42
|
-
const inst = connectTrait(announcer, host);
|
|
43
|
-
expect(host.hasAttribute('data-announcer-active')).toBe(true);
|
|
44
|
-
// Firing some random event should not crash and not announce anything.
|
|
45
|
-
host.dispatchEvent(new CustomEvent('foo', { detail: 'bar' }));
|
|
46
|
-
expect(document.getElementById('adia-live-polite')).toBeNull();
|
|
47
|
-
inst.disconnect(host);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('writes data-announce-message into the polite region by default', async () => {
|
|
51
|
-
const host = mountHost('div', {
|
|
52
|
-
'data-announce-on': 'submit-success',
|
|
53
|
-
'data-announce-message': 'Form submitted successfully.',
|
|
54
|
-
'data-announce-throttle': '0',
|
|
55
|
-
});
|
|
56
|
-
connectTrait(announcer, host);
|
|
57
|
-
host.dispatchEvent(new CustomEvent('submit-success', { bubbles: true }));
|
|
58
|
-
await settleAnnounce();
|
|
59
|
-
const region = document.getElementById('adia-live-polite');
|
|
60
|
-
expect(region).not.toBeNull();
|
|
61
|
-
expect(region.getAttribute('aria-live')).toBe('polite');
|
|
62
|
-
expect(region.textContent).toBe('Form submitted successfully.');
|
|
63
|
-
// Assertive region should NOT exist when only polite messages have fired.
|
|
64
|
-
expect(document.getElementById('adia-live-assertive')).toBeNull();
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('routes data-announce-priority="assertive" to the assertive region', async () => {
|
|
68
|
-
const host = mountHost('div', {
|
|
69
|
-
'data-announce-on': 'invalid',
|
|
70
|
-
'data-announce-message': 'Email is required.',
|
|
71
|
-
'data-announce-priority': 'assertive',
|
|
72
|
-
'data-announce-throttle': '0',
|
|
73
|
-
});
|
|
74
|
-
connectTrait(announcer, host);
|
|
75
|
-
host.dispatchEvent(new CustomEvent('invalid', { bubbles: true }));
|
|
76
|
-
await settleAnnounce();
|
|
77
|
-
const assertiveRegion = document.getElementById('adia-live-assertive');
|
|
78
|
-
expect(assertiveRegion).not.toBeNull();
|
|
79
|
-
expect(assertiveRegion.getAttribute('aria-live')).toBe('assertive');
|
|
80
|
-
expect(assertiveRegion.getAttribute('role')).toBe('alert');
|
|
81
|
-
expect(assertiveRegion.textContent).toBe('Email is required.');
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('substitutes {detail} placeholder from a scalar event detail', async () => {
|
|
85
|
-
const host = mountHost('div', {
|
|
86
|
-
'data-announce-on': 'count-up-done',
|
|
87
|
-
'data-announce-message': 'Counted to {detail}.',
|
|
88
|
-
'data-announce-throttle': '0',
|
|
89
|
-
});
|
|
90
|
-
connectTrait(announcer, host);
|
|
91
|
-
host.dispatchEvent(new CustomEvent('count-up-done', { detail: 18931, bubbles: true }));
|
|
92
|
-
await settleAnnounce();
|
|
93
|
-
const region = document.getElementById('adia-live-polite');
|
|
94
|
-
expect(region.textContent).toBe('Counted to 18931.');
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it('substitutes {detail} placeholder from object detail.message', async () => {
|
|
98
|
-
const host = mountHost('div', {
|
|
99
|
-
'data-announce-on': 'validated',
|
|
100
|
-
'data-announce-message': 'Validation: {detail}.',
|
|
101
|
-
'data-announce-throttle': '0',
|
|
102
|
-
});
|
|
103
|
-
connectTrait(announcer, host);
|
|
104
|
-
host.dispatchEvent(new CustomEvent('validated', {
|
|
105
|
-
detail: { valid: false, message: 'Invalid email address' },
|
|
106
|
-
bubbles: true,
|
|
107
|
-
}));
|
|
108
|
-
await settleAnnounce();
|
|
109
|
-
const region = document.getElementById('adia-live-polite');
|
|
110
|
-
expect(region.textContent).toBe('Validation: Invalid email address.');
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it('falls back to event.detail.message when no template is set', async () => {
|
|
114
|
-
const host = mountHost('div', {
|
|
115
|
-
'data-announce-on': 'validated',
|
|
116
|
-
'data-announce-throttle': '0',
|
|
117
|
-
});
|
|
118
|
-
connectTrait(announcer, host);
|
|
119
|
-
host.dispatchEvent(new CustomEvent('validated', {
|
|
120
|
-
detail: { message: 'Required field is empty' },
|
|
121
|
-
bubbles: true,
|
|
122
|
-
}));
|
|
123
|
-
await settleAnnounce();
|
|
124
|
-
const region = document.getElementById('adia-live-polite');
|
|
125
|
-
expect(region.textContent).toBe('Required field is empty');
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it('falls back to data-validation-message when no detail and no template', async () => {
|
|
129
|
-
const host = mountHost('div', {
|
|
130
|
-
'data-announce-on': 'validated',
|
|
131
|
-
'data-validation-message': 'This field is required',
|
|
132
|
-
'data-announce-throttle': '0',
|
|
133
|
-
});
|
|
134
|
-
connectTrait(announcer, host);
|
|
135
|
-
host.dispatchEvent(new CustomEvent('validated', { bubbles: true }));
|
|
136
|
-
await settleAnnounce();
|
|
137
|
-
const region = document.getElementById('adia-live-polite');
|
|
138
|
-
expect(region.textContent).toBe('This field is required');
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it('skips announce when there is no resolvable message', async () => {
|
|
142
|
-
const host = mountHost('div', {
|
|
143
|
-
'data-announce-on': 'noop',
|
|
144
|
-
'data-announce-throttle': '0',
|
|
145
|
-
});
|
|
146
|
-
connectTrait(announcer, host);
|
|
147
|
-
const spy = spyEvent(host, 'announcement-made');
|
|
148
|
-
host.dispatchEvent(new CustomEvent('noop', { bubbles: true }));
|
|
149
|
-
await settleAnnounce();
|
|
150
|
-
expect(spy.count).toBe(0);
|
|
151
|
-
// No region created either — nothing to write into.
|
|
152
|
-
const region = document.getElementById('adia-live-polite');
|
|
153
|
-
expect(region == null || region.textContent === '').toBe(true);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
it('throttle: suppresses a second announce inside the window', async () => {
|
|
157
|
-
const host = mountHost('div', {
|
|
158
|
-
'data-announce-on': 'tick',
|
|
159
|
-
'data-announce-message': 'Tick at {detail}',
|
|
160
|
-
'data-announce-throttle': '1000',
|
|
161
|
-
});
|
|
162
|
-
connectTrait(announcer, host);
|
|
163
|
-
const spy = spyEvent(host, 'announcement-made');
|
|
164
|
-
host.dispatchEvent(new CustomEvent('tick', { detail: 1, bubbles: true }));
|
|
165
|
-
host.dispatchEvent(new CustomEvent('tick', { detail: 2, bubbles: true }));
|
|
166
|
-
host.dispatchEvent(new CustomEvent('tick', { detail: 3, bubbles: true }));
|
|
167
|
-
await settleAnnounce();
|
|
168
|
-
expect(spy.count).toBe(1);
|
|
169
|
-
expect(spy.last.message).toBe('Tick at 1');
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
it('throttle=0 lets every event through', async () => {
|
|
173
|
-
const host = mountHost('div', {
|
|
174
|
-
'data-announce-on': 'tick',
|
|
175
|
-
'data-announce-message': 'Tick at {detail}',
|
|
176
|
-
'data-announce-throttle': '0',
|
|
177
|
-
});
|
|
178
|
-
connectTrait(announcer, host);
|
|
179
|
-
const spy = spyEvent(host, 'announcement-made');
|
|
180
|
-
host.dispatchEvent(new CustomEvent('tick', { detail: 1 }));
|
|
181
|
-
host.dispatchEvent(new CustomEvent('tick', { detail: 2 }));
|
|
182
|
-
await settleAnnounce();
|
|
183
|
-
expect(spy.count).toBe(2);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
it('dispatches announcement-made with { message, priority } detail', async () => {
|
|
187
|
-
const host = mountHost('div', {
|
|
188
|
-
'data-announce-on': 'go',
|
|
189
|
-
'data-announce-message': 'Hello',
|
|
190
|
-
'data-announce-priority': 'assertive',
|
|
191
|
-
'data-announce-throttle': '0',
|
|
192
|
-
});
|
|
193
|
-
connectTrait(announcer, host);
|
|
194
|
-
const spy = spyEvent(host, 'announcement-made');
|
|
195
|
-
host.dispatchEvent(new CustomEvent('go', { bubbles: true }));
|
|
196
|
-
await settleAnnounce();
|
|
197
|
-
expect(spy.count).toBe(1);
|
|
198
|
-
expect(spy.last.message).toBe('Hello');
|
|
199
|
-
expect(spy.last.priority).toBe('assertive');
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it('singleton regions: two announcer instances share one polite region', async () => {
|
|
203
|
-
const a = mountHost('div', {
|
|
204
|
-
'data-announce-on': 'go',
|
|
205
|
-
'data-announce-message': 'A says hi',
|
|
206
|
-
'data-announce-throttle': '0',
|
|
207
|
-
});
|
|
208
|
-
const b = mountHost('div', {
|
|
209
|
-
'data-announce-on': 'go',
|
|
210
|
-
'data-announce-message': 'B says hi',
|
|
211
|
-
'data-announce-throttle': '0',
|
|
212
|
-
});
|
|
213
|
-
connectTrait(announcer, a);
|
|
214
|
-
connectTrait(announcer, b);
|
|
215
|
-
a.dispatchEvent(new CustomEvent('go'));
|
|
216
|
-
await settleAnnounce();
|
|
217
|
-
b.dispatchEvent(new CustomEvent('go'));
|
|
218
|
-
await settleAnnounce();
|
|
219
|
-
// Only one polite region in the document.
|
|
220
|
-
const regions = document.querySelectorAll('#adia-live-polite');
|
|
221
|
-
expect(regions.length).toBe(1);
|
|
222
|
-
expect(regions[0].textContent).toBe('B says hi');
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
it('regions are created with sr-only clipping (visually hidden, AT-visible)', async () => {
|
|
226
|
-
const host = mountHost('div', {
|
|
227
|
-
'data-announce-on': 'go',
|
|
228
|
-
'data-announce-message': 'hi',
|
|
229
|
-
'data-announce-throttle': '0',
|
|
230
|
-
});
|
|
231
|
-
connectTrait(announcer, host);
|
|
232
|
-
host.dispatchEvent(new CustomEvent('go'));
|
|
233
|
-
await settleAnnounce();
|
|
234
|
-
const region = document.getElementById('adia-live-polite');
|
|
235
|
-
expect(region.classList.contains('adia-sr-only')).toBe(true);
|
|
236
|
-
expect(region.getAttribute('aria-atomic')).toBe('true');
|
|
237
|
-
// The role is 'status' for polite, 'alert' for assertive.
|
|
238
|
-
expect(region.getAttribute('role')).toBe('status');
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
it('disconnect leaves the singleton regions in place for other consumers', () => {
|
|
242
|
-
const host = mountHost('div', { 'data-announce-on': 'go' });
|
|
243
|
-
// Pre-create the region by hand so we know it's there before connect.
|
|
244
|
-
getRegion('polite');
|
|
245
|
-
const inst = connectTrait(announcer, host);
|
|
246
|
-
inst.disconnect(host);
|
|
247
|
-
expect(document.getElementById('adia-live-polite')).not.toBeNull();
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
it('reconnect after disconnect re-listens cleanly', async () => {
|
|
251
|
-
const host = mountHost('div', {
|
|
252
|
-
'data-announce-on': 'go',
|
|
253
|
-
'data-announce-message': 'reconnected',
|
|
254
|
-
'data-announce-throttle': '0',
|
|
255
|
-
});
|
|
256
|
-
const inst1 = connectTrait(announcer, host);
|
|
257
|
-
inst1.disconnect(host);
|
|
258
|
-
host.dispatchEvent(new CustomEvent('go'));
|
|
259
|
-
await settleAnnounce();
|
|
260
|
-
expect(document.getElementById('adia-live-polite')?.textContent || '').toBe('');
|
|
261
|
-
|
|
262
|
-
const inst2 = connectTrait(announcer, host);
|
|
263
|
-
host.dispatchEvent(new CustomEvent('go'));
|
|
264
|
-
await settleAnnounce();
|
|
265
|
-
expect(document.getElementById('adia-live-polite').textContent).toBe('reconnected');
|
|
266
|
-
inst2.disconnect(host);
|
|
267
|
-
});
|
|
268
|
-
});
|