Skip to content

Commit

Permalink
feat: sa11y jest to match snapshot wrapper (#487)
Browse files Browse the repository at this point in the history
* feat: wrap toMatchSnapshot api to restore document body element for a11y check

* feat: wrap toMatchSnapshot api to restore document body element for a11y check

* feat: wrap toMatchSnapshot api to restore document body element for a11y check
  • Loading branch information
navateja-alagam authored Jun 18, 2023
1 parent 03c4889 commit 440bbb4
Show file tree
Hide file tree
Showing 6 changed files with 39 additions and 5 deletions.
5 changes: 5 additions & 0 deletions packages/jest/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Accessibility matcher for [Jest](https://jestjs.io)
- [JSON result transformation](#json-result-transformation)
- [Limitations](#limitations)
- [Disabled Checks](#disabled-checks)
- [Jest toMatchSnapshot() API Wrapper](#jest-tomatchsnapshot-api-wrapper)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

Expand Down Expand Up @@ -278,3 +279,7 @@ Automatic checks currently has the following limitations.
### Disabled Checks

- @sa11y/jest automatic checks also disabled rules which were disabled in `toBeAccessible` Jest API ([disabled-checks](https://github.com/salesforce/sa11y/tree/master/packages/jest#disabled-checks))

### Jest toMatchSnapshot() API Wrapper

- When Snapshot testing is done, jest's `toMatchSnapshot()` API is altering the document.body element attributes as needed to generate snapshots thereby the a11y checks which happen later on `document.body` isn't running on actual elements. To solve this, a wrapper has been introduced to restore the document.body DOM before jest's `toMatchSnapshot` is invoked and use the restored version of document.body for a11y checks.
1 change: 1 addition & 0 deletions packages/jest/__tests__/setup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ describe('jest setup', () => {
registerSa11yMatcher();
it('should define matcher on expect object', () => {
expect(expect['toBeAccessible']).toBeDefined();
expect(expect['toMatchSnapshot']).toBeDefined();
});

it.each([extended, base])('should customize %s preset-rule as expected', (config) => {
Expand Down
4 changes: 3 additions & 1 deletion packages/jest/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@
"devDependencies": {
"@jest/globals": "28.1.3",
"@sa11y/common": "5.1.0",
"@sa11y/test-utils": "5.1.0"
"@sa11y/test-utils": "5.1.0",
"expect": "28.1.3",
"jest-snapshot": "28.1.3"
},
"publishConfig": {
"access": "public"
Expand Down
10 changes: 10 additions & 0 deletions packages/jest/src/automatic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ const defaultAutoCheckOpts: AutoCheckOpts = {
filesFilter: [],
};

let originalDocumentBodyHtml: string | null = null;

export const setOriginalDocumentBodyHtml = (bodyHtml: string | null) => {
originalDocumentBodyHtml = bodyHtml ?? null;
};

/**
* Check if current test file needs to be skipped based on any provided filter
*/
Expand Down Expand Up @@ -65,6 +71,9 @@ export async function automaticCheck(opts: AutoCheckOpts = defaultAutoCheckOpts)
}

const violations: AxeResults = [];
if (originalDocumentBodyHtml) {
document.body.innerHTML = originalDocumentBodyHtml;
}
// Create a DOM walker filtering only elements (skipping text, comment nodes etc)
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT);
let currNode = walker.firstChild();
Expand All @@ -81,6 +90,7 @@ export async function automaticCheck(opts: AutoCheckOpts = defaultAutoCheckOpts)
currNode = walker.nextSibling();
}
} finally {
setOriginalDocumentBodyHtml(null);
if (opts.cleanupAfterEach) document.body.innerHTML = ''; // remove non-element nodes
// TODO (spike): Disable stack trace for automatic checks.
// Will this affect all errors globally?
Expand Down
20 changes: 18 additions & 2 deletions packages/jest/src/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,14 @@

import { toBeAccessible } from './matcher';
import { A11yConfig } from '@sa11y/common';
import { AutoCheckOpts, registerSa11yAutomaticChecks } from './automatic';
import { AutoCheckOpts, registerSa11yAutomaticChecks, setOriginalDocumentBodyHtml } from './automatic';
import { expect } from '@jest/globals';
import { toMatchSnapshot, SnapshotState } from 'jest-snapshot';
import type { MatcherFunctionWithState, MatcherState } from 'expect';

interface Context extends MatcherState {
snapshotState: SnapshotState;
}

export const disabledRules = [
// Descendancy checks that would fail at unit/component level, but pass at page level
Expand All @@ -29,6 +35,15 @@ export const disabledRules = [
'video-caption',
];

function wrapperSnapshotMatcher(originalMatcher: MatcherFunctionWithState<Context>) {
return function (...args: Context[]) {
setOriginalDocumentBodyHtml(document?.body?.innerHTML ?? '');
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return originalMatcher.call(expect.getState(), ...args);
};
}

/**
* Options to be passed on to {@link setup}
*/
Expand Down Expand Up @@ -72,8 +87,9 @@ export function setup(opts: Sa11yOpts = defaultSa11yOpts): void {
* Register accessibility helpers toBeAccessible as jest matchers
*/
export function registerSa11yMatcher(): void {
const wrapper = wrapperSnapshotMatcher(toMatchSnapshot);
if (expect !== undefined) {
expect.extend({ toBeAccessible });
expect.extend({ toBeAccessible, toMatchSnapshot: wrapper });
} else {
throw new Error(
"Unable to find Jest's expect function." +
Expand Down
4 changes: 2 additions & 2 deletions yarn.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 440bbb4

Please sign in to comment.