Skip to content

Commit

Permalink
feat: support asserting by politeness level
Browse files Browse the repository at this point in the history
  • Loading branch information
AriPerkkio committed Feb 27, 2021
1 parent 1b7d109 commit 552e542
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 12 deletions.
4 changes: 3 additions & 1 deletion index.d.ts
@@ -1,7 +1,9 @@
declare global {
namespace jest {
interface Matchers<R> {
toBeAnnounced(): Promise<jest.CustomMatcherResult>;
toBeAnnounced(
politenessSetting?: 'polite' | 'assertive'
): Promise<jest.CustomMatcherResult>;
}
}
}
Expand Down
28 changes: 22 additions & 6 deletions src/to-be-announced.ts
Expand Up @@ -3,12 +3,15 @@ import {
isElement,
isInDOM,
LIVE_REGION_QUERY,
PolitenessSetting,
resolvePolitenessSetting,
} from './utils';
import { interceptMethod, interceptSetter, Restore } from './interceptors';

// TODO include politeness setting
const announcements = new Map<string, boolean>();
// TODO Capture incorrectly used aria-live="polite"

// Map of announcements to their politeness settings
const announcements = new Map<string, Exclude<PolitenessSetting, 'off'>>();

// Map of live regions to previous textContent
const liveRegions = new Map<Node, string | null>();
Expand All @@ -30,7 +33,7 @@ function updateAnnouncements(node: Node) {
const newText = node.textContent || '';

if (previousText !== newText) {
announcements.set(newText, true);
announcements.set(newText, politenessSetting);
liveRegions.set(node, newText);
}
}
Expand All @@ -52,7 +55,7 @@ function updateLiveRegions() {

// Content of assertive live regions is announced on initial mount
if (politenessSetting === 'assertive' && liveRegion.textContent) {
announcements.set(liveRegion.textContent, true);
announcements.set(liveRegion.textContent, politenessSetting);
}
}
}
Expand All @@ -76,6 +79,7 @@ function onAppendChild(newChild: Node) {
updateAnnouncements(newChild);
}

// TODO Add options: { "warnIncorrectlyUsedPoliteContainer": boolean (defaults to false) }
export function register(): void {
const cleanups: Restore[] = [];

Expand All @@ -100,18 +104,30 @@ export function register(): void {

export function toBeAnnounced(
this: jest.MatcherContext,
text: string
text: string,
politenessSetting?: Exclude<PolitenessSetting, 'off'>
): jest.CustomMatcherResult {
const textMissing = text == null || text === '';
const isAnnounced = announcements.has(text);

// Optionally asserted by politeness setting
const politenessSettingMatch =
politenessSetting == null ||
(isAnnounced && announcements.get(text) === politenessSetting);

// TODO Refactor to use multiple returns instead of a complex single return
return {
pass: !textMissing && isAnnounced,
pass: !textMissing && isAnnounced && politenessSettingMatch,
message: () => {
if (textMissing) {
return `toBeAnnounced was given falsy or empty string: (${text})`;
}

if (!politenessSettingMatch && isAnnounced) {
const actual = announcements.get(text);
return `${text} was announced with politeness setting "${actual}" when "${politenessSetting}" was expected`;
}

const allAnnouncements: string[] = [];
for (const [announcement] of announcements.entries()) {
allAnnouncements.push(announcement);
Expand Down
2 changes: 1 addition & 1 deletion src/utils.ts
@@ -1,4 +1,4 @@
type PolitenessSetting = 'polite' | 'assertive' | 'off';
export type PolitenessSetting = 'polite' | 'assertive' | 'off';

export const LIVE_REGION_QUERY = [
'[role="status"]',
Expand Down
13 changes: 13 additions & 0 deletions test/errors.test.ts
Expand Up @@ -32,6 +32,19 @@ describe('Errors', () => {
);
});

test('should throw when asserting with incorrect polite setting', () => {
const container = document.createElement('div');
container.setAttribute('role', 'status');
appendToRoot(container);
container.textContent = 'Hello world';

expect(() =>
expect('Hello world').toBeAnnounced('assertive')
).toThrowErrorMatchingInlineSnapshot(
`"Hello world was announced with politeness setting \\"polite\\" when \\"assertive\\" was expected"`
);
});

test('should throw when given empty string', () => {
expect(() =>
expect('').toBeAnnounced()
Expand Down
8 changes: 4 additions & 4 deletions test/to-be-announced.react.test.tsx
Expand Up @@ -85,10 +85,10 @@ function toggleContent() {
expect('Message #2').not.toBeAnnounced();

toggleContent();
expect('Message #1').toBeAnnounced();
expect('Message #1').toBeAnnounced('polite');

toggleContent();
expect('Message #2').toBeAnnounced();
expect('Message #2').toBeAnnounced('polite');
});
});
});
Expand Down Expand Up @@ -144,10 +144,10 @@ function toggleContent() {
);

screen.getByText('Message #2');
expect('Message #2').toBeAnnounced();
expect('Message #2').toBeAnnounced('assertive');

toggleContent();
expect('Message #1').toBeAnnounced();
expect('Message #1').toBeAnnounced('assertive');
});
});
});
Expand Down

0 comments on commit 552e542

Please sign in to comment.