Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/ariperkkio/extend-to-be-announced

Custom Jest/Vitest matcher for validating ARIA Live Regions
https://github.com/ariperkkio/extend-to-be-announced

a11y accessibility aria-live jest vitest

Last synced: 3 months ago
JSON representation

Custom Jest/Vitest matcher for validating ARIA Live Regions

Awesome Lists containing this project

README

        

# extend-to-be-announced

[![version](https://img.shields.io/npm/v/extend-to-be-announced)](https://www.npmjs.com/package/extend-to-be-announced)

[Motivation](#Motivation) | [Installation](#installation) | [Usage](#usage) | [Support](#support)

> Utility for asserting [ARIA live regions](https://www.w3.org/TR/wai-aria-1.2/#dfn-live-region).

`extend-to-be-announced` is a matcher extender for [Jest](https://jestjs.io/) and [Vitest](https://vitest.dev/). It makes validating ARIA live regions extremely easy. Internally it is utilizing [`aria-live-capture`](https://github.com/AriPerkkio/aria-live-capture) for announcement detection.

For Storybook integration see [`storybook-addon-aria-live`](https://github.com/AriPerkkio/storybook-addon-aria-live).

```js
test('live region is announced', () => {
const region = document.createElement('div');
region.setAttribute('role', 'status');

document.body.appendChild(region);
region.textContent = 'Loading';

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

## Motivation

Validating ARIA live regions with [`@testing-library`](https://testing-library.com/) and [`jest-dom`](https://github.com/testing-library/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 - not the initial content.

```js
render(

Loading...
);

const liveRegion = screen.getByRole('status');

// Loading is not be announced by assistive technologies ❌
// Content of live region has not updated. This is it's initial text content.
expect(liveRegion).toHaveTextContent('Loading...');
```

Instead developers should check that messages are rendered into existing ARIA Live regions.

```js
const { rerender } = render(

);

// Live region should be present
const liveRegion = screen.getByRole('status');

// Live region should initially be empty
expect(liveRegion).toBeEmptyDOMElement();

// Update content of the live region
rerender(

Loading...
);

// Loading is announced by assistive technologies ✅
expect(liveRegion).toHaveTextContent('Loading...');
```

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

```js
const { rerender } = render(

);

rerender(

Loading...
);

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

## Installation

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

```bash
npm install --save-dev extend-to-be-announced
```

## Usage

### Test setup

There are out-of-the-box setups for Vitest and Jest.

#### Vitest

Import registration entrypoint in your [test setup](https://vitest.dev/config/#setupfiles).

```js
import 'extend-to-be-announced/vitest';
```

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

```js
import { register } from 'extend-to-be-announced/vitest/register';

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

#### Jest

Import registration entrypoint in your [test setup](https://jestjs.io/docs/en/configuration.html#setupfilesafterenv-array).

```js
import 'extend-to-be-announced/jest';
```

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

```js
import { register } from 'extend-to-be-announced/jest/register';

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

Note that you'll need to add ESM dependencies of this package to your Jest config's `transformIgnorePatterns`. For example with `pnpm`:

```js
transformIgnorePatterns: ['/node_modules/.pnpm/(?!(aria-live-capture)@)'],
```

### Typescript

This package utilizes [Typescript's `exports` support](https://www.typescriptlang.org/docs/handbook/esm-node.html#packagejson-exports-imports-and-self-referencing) for type declarations. You'll need to set `"moduleResolution": "node16"` or `"moduleResolution": "nodenext"` in your `tsconfig.json` in order to have typings picked properly. For legacy setups where certain fields of `tsconfig.json` cannot be modified, such as `create-react-app`, there is a work-around entrypoint for `jest`.

```json
{
"compilerOptions": {
"moduleResolution": "node16" // Or nodenext
}
}
```

### Assertions

#### toBeAnnounced

Assert whether given message was announced by assistive technologies.
Accepts string or regexp as matcher value.

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

Politeness setting of announcement can be optionally asserted.

```js
expect('Loading...').toBeAnnounced('polite');
expect('Error occured...').toBeAnnounced('assertive');
```

##### Examples

```html
Render#1 |


Render#2 |
Loading

PASS ✅ | expect('Loading').toBeAnnounced('polite');
```

```html
Render#1 |

Error

PASS ✅ | expect('Error').toBeAnnounced('assertive');
```

```html
Render#1 |


Render#2 |
Error

PASS ✅ | expect('Error').toBeAnnounced();
```

```html
Render#1 |

Loading

FAIL ❌ | expect('Loading').toBeAnnounced();
```

```html
Render#1 |


Render#2 |
Loading

FAIL ❌ | expect('Loading').toBeAnnounced();
```

With `register({ includeShadowDom: true })`:

```html
Render#1 |


| #shadow-root
|

|

|
Render#2 |

| #shadow-root
|
Loading

|

|
PASS ✅ | expect('Loading').toBeAnnounced()
```

### Utilities

#### getAnnouncements

Get all announcements as `Map`.

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

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

#### clearAnnouncements

Clear all captured announcements.

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

## Support

| Feature | Status |
| :-------------: | :----: |
| `role` | ✅ |
| `aria-live` | ✅ |
| `aria-atomic` | ❌ 👷 |
| `aria-busy` | ❌ |
| `aria-relevant` | ❌ |