Skip to content

Commit

Permalink
Merge pull request #3 from trurl-master/more-elements
Browse files Browse the repository at this point in the history
v1.1
  • Loading branch information
trurl-master authored Jun 17, 2024
2 parents c731954 + 1621101 commit f4b8e39
Show file tree
Hide file tree
Showing 18 changed files with 226 additions and 52 deletions.
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ yarn add --dev accessibility-testing-toolkit

## Usage

Import `accessibility-testing-toolkit` in your project once. The best place is to do it in your test setup file:
Import `accessibility-testing-toolkit/matchers` in your project once. The best place is to do it in your test setup file:

```js
// In your own jest-setup.js
import 'accessibility-testing-toolkit';
import 'accessibility-testing-toolkit/matchers';

// In jest.config.js add
setupFilesAfterEnv: ['<rootDir>/jest-setup.js'];
Expand Down Expand Up @@ -121,8 +121,8 @@ Container nodes in the DOM, such as non-semantic `<div>` and `<span>` elements,
When determining whether elements in the DOM are accessible, certain attributes and CSS properties signal that an element, along with its children, should not be considered visible:
- Elements with the `hidden` attribute or `aria-hidden="true"`.
- Styles that set `display: none` or `visibility: hidden`.
- Elements with the `hidden` attribute or `aria-hidden="true"`
- Styles that set `display: none` or `visibility: hidden`
In testing environments, relying on attribute checks may be necessary since `getComputedStyle` may not reflect styles defined in external stylesheets.
Expand Down Expand Up @@ -164,7 +164,6 @@ Specifically, the toolkit applies the following custom roles:
- `abbr`: Mapped to `Abbr`
- `audio`: Mapped to `Audio`
- `canvas`: Mapped to `Canvas`
- `details`: Mapped to `Details`
- `dd`: Mapped to `DescriptionListDetails`
- `dl`: Mapped to `DescriptionList`
- `dt`: Mapped to `DescriptionListTerm`
Expand Down
2 changes: 1 addition & 1 deletion jest-setup.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
import '@testing-library/jest-dom';
import './src/index';
import './src/matchers';
3 changes: 3 additions & 0 deletions matchers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const matchers = require('./dist/matchers');
module.exports = matchers;
32 changes: 23 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "accessibility-testing-toolkit",
"version": "1.0.3",
"version": "1.1.0",
"author": "Ivan Galiatin",
"license": "MIT",
"description": "A toolkit for testing accessibility",
Expand All @@ -9,18 +9,32 @@
"url": "git+https://github.com/trurl-master/accessibility-testing-toolkit.git"
},
"keywords": [
"testing",
"jsdom",
"jest",
"accessibility",
"accessibility tree",
"aria"
"aria",
"dom",
"jest",
"jsdom",
"testing"
],
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"types": "dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"./matchers": {
"import": "./dist/matchers.mjs",
"require": "./dist/matchers.js",
"types": "./dist/matchers.d.ts"
}
},
"files": [
"/dist",
"/src",
"matchers.js",
"!/src/**/__tests__/*",
"!/src/**/*.test.tsx"
],
Expand Down Expand Up @@ -49,7 +63,8 @@
"module": "dist/esm/index.js",
"tsup": {
"entry": [
"src/index.ts"
"src/index.ts",
"src/matchers.ts"
],
"splitting": false,
"sourcemap": true,
Expand All @@ -58,8 +73,7 @@
"cjs",
"esm"
],
"clean": true,
"legacyOutput": true
"clean": true
},
"dependencies": {
"aria-query": "^5.3.0",
Expand Down
2 changes: 1 addition & 1 deletion src/elements/code.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { render } from '@testing-library/react';
import { byRole } from '..';

describe('', () => {
describe('code', () => {
it('renders correct structure', () => {
const { container } = render(
<p>
Expand Down
2 changes: 1 addition & 1 deletion src/elements/col-colgroup.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { render } from '@testing-library/react';
import { byRole } from '..';

describe('', () => {
describe('col-colgroup', () => {
it('renders correct structure', () => {
const { container } = render(
<table>
Expand Down
32 changes: 32 additions & 0 deletions src/elements/datalist.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { render } from '@testing-library/react';
import { byRole } from '..';

describe('datalist', () => {
it('renders correct structure', () => {
const { container } = render(
<>
<label htmlFor="ice-cream-choice">Choose a flavor:</label>
<input
list="ice-cream-flavors"
id="ice-cream-choice"
name="ice-cream-choice"
/>
<datalist id="ice-cream-flavors">
<option value="Chocolate"></option>
<option value="Coconut"></option>
<option value="Mint"></option>
<option value="Strawberry"></option>
<option value="Vanilla"></option>
</datalist>
</>
);

expect(container).toHaveA11yTree(
byRole('generic', [
byRole('LabelText', ['Choose a flavor:']),
byRole('combobox', { name: 'Choose a flavor:' }),
// datalist has display: none by default
])
);
});
});
27 changes: 27 additions & 0 deletions src/elements/del-ins.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { render } from '@testing-library/react';
import { byRole } from '..';

describe('del-ins', () => {
it('renders correct structure', () => {
const { container } = render(
<blockquote>
There is <del>nothing</del> <ins>no code</ins> either good or bad, but
<del>thinking</del> <ins>running it</ins> makes it so.
</blockquote>
);

expect(container.firstChild).toHaveA11yTree(
byRole('blockquote', [
'There is ',
byRole('deletion', ['nothing']),
' ',
byRole('insertion', ['no code']),
' either good or bad, but',
byRole('deletion', ['thinking']),
' ',
byRole('insertion', ['running it']),
' makes it so.',
])
);
});
});
38 changes: 38 additions & 0 deletions src/elements/details.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { render } from '@testing-library/react';
import { byRole } from '..';

describe('details', () => {
it('closed by default, content is hidden', () => {
const { container } = render(
<details>
<summary>Details</summary>
<p>Something small enough to escape casual notice.</p>
some simple text
</details>
);

expect(container.firstChild).toHaveA11yTree(
byRole('group', { expanded: false }, [
byRole('DisclosureTriangle', ['Details']),
// the details element is not expanded by default
// so the content is not rendered
])
);
});

it('open, content is visible', () => {
const { container } = render(
<details open>
<summary>Details</summary>
Something small enough to escape casual notice.
</details>
);

expect(container.firstChild).toHaveA11yTree(
byRole('group', { expanded: true }, [
byRole('DisclosureTriangle', ['Details']),
'Something small enough to escape casual notice.',
])
);
});
});
37 changes: 37 additions & 0 deletions src/elements/dl-dt-dd.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { render } from '@testing-library/react';
import { byRole } from '..';

describe('dl-dt-dd', () => {
it('renders correct structure', () => {
const { container } = render(
<>
<p>Cryptids of Cornwall:</p>

<dl>
<dt>Beast of Bodmin</dt>
<dd>A large feline inhabiting Bodmin Moor.</dd>

<dt>Morgawr</dt>
<dd>A sea serpent.</dd>

<dt>Owlman</dt>
<dd>A giant owl-like creature.</dd>
</dl>
</>
);

expect(container).toHaveA11yTree(
byRole('generic', [
byRole('paragraph', ['Cryptids of Cornwall:']),
byRole('DescriptionList', [
byRole('term', ['Beast of Bodmin']),
byRole('definition', ['A large feline inhabiting Bodmin Moor.']),
byRole('term', ['Morgawr']),
byRole('definition', ['A sea serpent.']),
byRole('term', ['Owlman']),
byRole('definition', ['A giant owl-like creature.']),
]),
])
);
});
});
3 changes: 1 addition & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
export { MatcherOptions } from './types/matchers.d';
import './matchers';
export * from './helpers/by-role';
export {
AsNonLandmarkRoles,
Expand All @@ -9,6 +7,7 @@ export {
A11yTreeNode,
A11yTreeNodeMatch,
} from './types/types';
export { getPrettyTree } from './pretty-tree/pretty-tree';
export { getAccessibilityTree } from './tree/accessibility-tree';
export { pruneContainerNodes } from './tree/prune-container-nodes';
export { isSubtreeInaccessible } from 'dom-accessibility-api';
Expand Down
2 changes: 2 additions & 0 deletions src/matchers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { getPrettyTree } from './pretty-tree/pretty-tree';
import { MatcherOptions } from './types/matchers';
import { nodeTester } from './testers/node';

export { MatcherOptions } from './types/matchers.d';

expect.extend({
toHaveA11yTree(
received: HTMLElement | A11yTreeNode,
Expand Down
22 changes: 1 addition & 21 deletions src/pretty-tree/pretty-tree.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,9 @@
// import { defaultState } from '../helpers';
import { defaultQueries, defaultState } from '../helpers';
import { StaticText } from '../tree/leafs';
import {
A11yTreeForDiff,
// A11yTreeNodeState,
// A11yTreeNodeStateForDiff,
TextMatcher,
} from '../types/types';
import { A11yTreeForDiff, TextMatcher } from '../types/types';
import { omitDefaultValues } from './omit-default-values';
import { renderProperties } from './render-properties';

// const getStateDetails = (
// state: A11yTreeNodeState | A11yTreeNodeStateForDiff
// ): string => {
// const nonDefaultEntries = Object.entries(state).filter(
// ([key, value]) => value !== defaultState[key as keyof A11yTreeNodeState]
// );

// return nonDefaultEntries.length > 0
// ? `${nonDefaultEntries
// .map(([key, value]) => `${key}: ${value}`)
// .join(', ')}`
// : '';
// };

const renderStringMatcher = (textMatcher: TextMatcher): string => {
if (textMatcher instanceof RegExp || typeof textMatcher === 'number') {
return textMatcher.toString();
Expand Down
24 changes: 13 additions & 11 deletions src/tree/accessibility-tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,21 @@ import { isDefined } from '../type-guards';
import { StaticText } from './leafs';
import { MatcherOptions } from '../types/matchers';
import { getConfig } from '../config';

// if a descendant of an article, aside, main, nav or section element, or an element with role=article, complementary, main, navigation or region
const isNonLandmarkRole = (element: HTMLElement, role: string) =>
['article', 'aside', 'main', 'nav', 'section'].includes(
element.tagName.toLowerCase()
) ||
['aricle', 'complementary', 'main', 'navigation', 'region'].includes(role);

const isList = (role: HTMLElement['role']) => role === 'list';
import { isClosedDetails, isList, isNonLandmarkRole } from './context';
import { isInaccessibleOverride } from './overrides';

const defaultOptions = {
isListSubtree: false,
isClosedDetailsSubtree: false,
isNonLandmarkSubtree: false,
} satisfies MatcherOptions;

export const getAccessibilityTree = (
element: HTMLElement,
{
isListSubtree: userListSubtree = defaultOptions.isListSubtree,
isClosedDetailsSubtree:
userIsClosedDetailsSubtree = defaultOptions.isClosedDetailsSubtree,
isNonLandmarkSubtree:
userNonLandmarkSubtree = defaultOptions.isNonLandmarkSubtree,
isInaccessibleOptions = getConfig().isInaccessibleOptions,
Expand All @@ -52,7 +48,10 @@ export const getAccessibilityTree = (
element: HTMLElement,
context: A11yTreeNodeContext
): A11yTreeNode | null {
if (isInaccessible(element, context.isInaccessibleOptions)) {
if (
isInaccessible(element, context.isInaccessibleOptions) ||
isInaccessibleOverride(element)
) {
return null;
}

Expand Down Expand Up @@ -88,6 +87,8 @@ export const getAccessibilityTree = (
if (child instanceof HTMLElement) {
return assembleTree(child, {
isListSubtree: context.isListSubtree || isList(role),
isClosedDetailsSubtree:
context.isClosedDetailsSubtree || isClosedDetails(element),
isNonLandmarkSubtree:
context.isNonLandmarkSubtree ||
isNonLandmarkRole(element, role),
Expand All @@ -96,7 +97,7 @@ export const getAccessibilityTree = (
}

if (child instanceof Text) {
if (child.textContent === null) {
if (child.textContent === null || isInaccessibleOverride(child)) {
return undefined;
}

Expand All @@ -111,6 +112,7 @@ export const getAccessibilityTree = (

return assembleTree(element, {
isListSubtree: userListSubtree,
isClosedDetailsSubtree: userIsClosedDetailsSubtree,
isNonLandmarkSubtree: userNonLandmarkSubtree,
isInaccessibleOptions,
});
Expand Down
13 changes: 13 additions & 0 deletions src/tree/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { isDetailsElement } from '../type-guards';

// if a descendant of an article, aside, main, nav or section element, or an element with role=article, complementary, main, navigation or region
export const isNonLandmarkRole = (element: HTMLElement, role: string) =>
['article', 'aside', 'main', 'nav', 'section'].includes(
element.tagName.toLowerCase()
) ||
['aricle', 'complementary', 'main', 'navigation', 'region'].includes(role);

export const isList = (role: HTMLElement['role']) => role === 'list';

export const isClosedDetails = (element: HTMLElement) =>
isDetailsElement(element) && !element.open;
Loading

0 comments on commit f4b8e39

Please sign in to comment.