Skip to content

Commit 2e1d6ab

Browse files
authored
feat: add support for dynamic insert, await for messages (#86)
* feat: add support for dynamic insert, await for messages * chore: add changesets * chore: address eslint violations
1 parent 8da3de4 commit 2e1d6ab

File tree

12 files changed

+406
-207
lines changed

12 files changed

+406
-207
lines changed

.changeset/healthy-queens-learn.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@primer/live-region-element": minor
3+
---
4+
5+
Add support for waiting for announcement completion by returning a promise from announce, announceFromElement

.changeset/poor-dingos-jog.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@primer/live-region-element": minor
3+
---
4+
5+
Add support for dynamically inserting live regions while still announcing to screen readers

.eslintrc.cjs

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const config = {
2929
rules: {
3030
'no-unused-vars': 'off',
3131
'import/no-unresolved': 'off',
32+
'eslint-comments/no-use': 'off',
3233
},
3334
overrides: [
3435
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any, github/no-then */
2+
3+
export class Deferred<T = void> implements Promise<T> {
4+
[Symbol.toStringTag] = 'Deferred'
5+
6+
#promise: Promise<T>
7+
#resolve!: (value: T | PromiseLike<T>) => void
8+
#reject!: (reason?: any) => void
9+
10+
constructor() {
11+
this.#promise = new Promise<T>((resolve, reject) => {
12+
this.#resolve = resolve
13+
this.#reject = reject
14+
})
15+
}
16+
17+
then<TResult1 = T, TResult2 = never>(
18+
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null | undefined,
19+
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined,
20+
): Promise<TResult1 | TResult2> {
21+
return Promise.prototype.then.apply(this.#promise, [onfulfilled, onrejected]) as Promise<TResult1 | TResult2>
22+
}
23+
24+
catch<TResult = never>(
25+
onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null | undefined,
26+
): Promise<T | TResult> {
27+
return Promise.prototype.catch.apply(this.#promise, [onrejected])
28+
}
29+
30+
finally(onfinally?: (() => void) | null | undefined): Promise<T> {
31+
return Promise.prototype.finally.apply(this.#promise, [onfinally])
32+
}
33+
34+
resolve(value: T) {
35+
this.#resolve(value)
36+
}
37+
38+
reject(reason?: any) {
39+
this.#reject(reason)
40+
}
41+
42+
getPromise(): Promise<T> {
43+
return this.#promise
44+
}
45+
}

packages/live-region-element/src/__tests__/live-region-element.test.ts

+57-1
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
1-
import {afterEach, beforeEach, describe, test, expect} from 'vitest'
1+
import {afterEach, beforeEach, describe, test, expect, vi} from 'vitest'
22
import {LiveRegionElement, compareMessages} from '../live-region-element'
33
import {Ordering} from '../order'
4+
import {Deferred} from '../Deferred'
45
import '../define'
56

67
describe('live-region-element', () => {
78
let liveRegion: LiveRegionElement
89

910
beforeEach(() => {
11+
vi.useFakeTimers()
1012
liveRegion = document.createElement('live-region') as LiveRegionElement
1113
document.body.appendChild(liveRegion)
1214
})
1315

1416
afterEach(() => {
1517
document.body.removeChild(liveRegion)
18+
vi.restoreAllMocks()
1619
})
1720

1821
test('announce() message', () => {
@@ -33,6 +36,51 @@ describe('live-region-element', () => {
3336
expect(liveRegion.getMessage('assertive')).toBe('test')
3437
})
3538

39+
test('announce() with delayMs', () => {
40+
liveRegion.announce('test', {
41+
delayMs: 1000,
42+
})
43+
expect(liveRegion.getMessage('polite')).toBe('')
44+
vi.runAllTimers()
45+
expect(liveRegion.getMessage('polite')).toBe('test')
46+
})
47+
48+
test('cancel announce() with delayMs', () => {
49+
const {cancel} = liveRegion.announce('test', {
50+
delayMs: 1000,
51+
})
52+
expect(liveRegion.getMessage('polite')).toBe('')
53+
54+
cancel()
55+
vi.runAllTimers()
56+
57+
expect(liveRegion.getMessage('polite')).toBe('')
58+
})
59+
60+
test('await announce()', async () => {
61+
const announcement = liveRegion.announce('test', {
62+
delayMs: 1000,
63+
})
64+
expect(liveRegion.getMessage('polite')).toBe('')
65+
66+
vi.runAllTimers()
67+
await announcement
68+
expect(liveRegion.getMessage('polite')).toBe('test')
69+
})
70+
71+
test('cancel await announce()', async () => {
72+
const announcement = liveRegion.announce('test', {
73+
delayMs: 1000,
74+
})
75+
expect(liveRegion.getMessage('polite')).toBe('')
76+
77+
vi.advanceTimersByTime(500)
78+
announcement.cancel()
79+
vi.runAllTimers()
80+
await announcement
81+
expect(liveRegion.getMessage('polite')).toBe('')
82+
})
83+
3684
test('announceFromElement()', () => {
3785
const element = document.createElement('div')
3886
element.textContent = 'test'
@@ -59,11 +107,13 @@ describe('live-region-element', () => {
59107
contents: 'test',
60108
politeness: 'polite',
61109
scheduled: now,
110+
deferred: new Deferred(),
62111
},
63112
{
64113
contents: 'test',
65114
politeness: 'polite',
66115
scheduled: now,
116+
deferred: new Deferred(),
67117
},
68118
),
69119
).toBe(Ordering.Equal)
@@ -77,11 +127,13 @@ describe('live-region-element', () => {
77127
contents: 'test',
78128
politeness: 'polite',
79129
scheduled: now,
130+
deferred: new Deferred(),
80131
},
81132
{
82133
contents: 'test',
83134
politeness: 'polite',
84135
scheduled: now + 1000,
136+
deferred: new Deferred(),
85137
},
86138
),
87139
).toBe(Ordering.Less)
@@ -95,11 +147,13 @@ describe('live-region-element', () => {
95147
contents: 'test',
96148
politeness: 'polite',
97149
scheduled: now + 1000,
150+
deferred: new Deferred(),
98151
},
99152
{
100153
contents: 'test',
101154
politeness: 'polite',
102155
scheduled: now,
156+
deferred: new Deferred(),
103157
},
104158
),
105159
).toBe(Ordering.Greater)
@@ -113,11 +167,13 @@ describe('live-region-element', () => {
113167
contents: 'test',
114168
politeness: 'assertive',
115169
scheduled: now,
170+
deferred: new Deferred(),
116171
},
117172
{
118173
contents: 'test',
119174
politeness: 'polite',
120175
scheduled: now,
176+
deferred: new Deferred(),
121177
},
122178
),
123179
).toBe(Ordering.Less)

packages/live-region-element/src/__tests__/query.test.ts

-99
This file was deleted.

0 commit comments

Comments
 (0)