extend-to-be-announced

Utility for asserting Aria Live Regions

Usage no npm install needed!

<script type="module">
  import extendToBeAnnounced from 'https://cdn.skypack.dev/extend-to-be-announced';
</script>

README

extend-to-be-announced

Utility for asserting ARIA live regions.

Validating ARIA live regions with @testing-library and jest-dom requires developers to consider implementation details. Current solutions are prone to false positives.

In test below it is not clearly visible that Loading... is not actually announced. Assistive technologies are only expected to announce updates of ARIA live regions with polite as politeness setting.

render(<div role="status">Loading...</div>);

// Loading should be announced ❌
const statusContainer = screen.getByRole('status');
expect(statusContainer).toHaveTextContent('Loading...');
// Not detected by assistive technologies since content of
// live container was not updated

Instead developers should check that messages are rendered into existing Aria Live Containers.

const { rerender } = render(<div role="status"></div>);

// Status container should be present
const statusContainer = screen.getByRole('status');

// Status container should initially be empty
expect(statusContainer).toBeEmptyDOMElement();

// Update content of live region
rerender(<div role="status">Loading...</div>);

// Loading should be announced ✅
expect(statusContainer).toHaveTextContent('Loading...');

toBeAnnounced can be used to hide such implementation detail from tests.

const { rerender } = render(<div role="status"></div>);
rerender(<div role="status">Loading...</div>);

expect('Loading...').toBeAnnounced('polite');

Installation

extend-to-be-announced should be included in development dependencies.

yarn add --dev extend-to-be-announced

Usage

Test setup

Import registration entrypoint in your test setup.

import 'extend-to-be-announced/register';

For setting up registration options use register(options) method instead.

import { register } from 'extend-to-be-announced';

register({
    /** Indicates whether live regions inside `ShadowRoot`s should be tracked. Defaults to false. */
    includeShadowDom: true,
});

Assertions

toBeAnnounced

Assert whether given message was announced by ARIA live region. Accepts string or regexp as matcher value.

expect('Loading...').toBeAnnounced();
expect(/loading/i).toBeAnnounced();
expect('Error occured...').toBeAnnounced();
expect(/error occured/i).toBeAnnounced();

Politeness setting of announcement can be optionally asserted.

expect('Loading...').toBeAnnounced('polite');
expect('Error occured...').toBeAnnounced('assertive');
Examples
Render#1 | <div role="status"></div>
Render#2 | <div role="status">Loading</div>
PASS ✅  | expect('Loading').toBeAnnounced('polite');
Render#1 | <div role="alert">Error</div>
PASS ✅  | expect('Error').toBeAnnounced('assertive');
Render#1 | <div></div>
Render#2 | <div role="alert">Error</div>
PASS ✅  | expect('Error').toBeAnnounced();
Render#1 | <div role="status">Loading</div>
FAIL ❌  | expect('Loading').toBeAnnounced();
Render#1 | <div></div>
Render#2 | <div role="status">Loading</div>
FAIL ❌  | expect('Loading').toBeAnnounced();

With register({ includeShadowDom: true }):

Render#1 | <div role="status">
         |     #shadow-root
         |     <div></div>
         | </div>
         |
Render#2 | <div role="status">
         |     #shadow-root
         |     <div>Loading</div>
         | </div>
         |
PASS ✅  | expect('Loading').toBeAnnounced()

Utilities

getAnnouncements

Get all announcements as Map<string, PolitenessSetting>.

import { getAnnouncements } from 'extend-to-be-announced';
getAnnouncements();

> Map {
>   "Status message" => "polite",
>   "Alert message" => "assertive",
> }

clearAnnouncements

Clear all captured announcements.

import { clearAnnouncements } from 'extend-to-be-announced';
clearAnnouncements();

Support

Feature Status
role
aria-live
aria-atomic ❌ 👷
aria-busy
aria-relevant