import { configureAxe } from 'jest-axe' import { isNotNil } from '@monorail/sharedHelpers/typeGuards' import { A11yElement, Meta, Story } from '@monorail/helpers/storybook' import { renderStory } from '@monorail/testHelpers/render' const axe = configureAxe({ rules: { // Ignore page-level rules 'landmark-one-main': { enabled: false }, region: { enabled: false }, 'page-has-heading-one': { enabled: false }, }, }) function shouldCheckA11y(item: Story) { return !item.parameters?.a11y?.disable } //#region Type Guards function isStory(item: Story | Meta): item is Story { return typeof item === 'function' } function isA11yStoryEntry( entry: [string, Story | Meta], ): entry is [string, Story] { return isStory(entry[1]) && shouldCheckA11y(entry[1]) } /** * Valid if it exists, and is not an auto-added Storybook element * * i.e. It is not dependent on Storybook's Preview implementation, so it'll work in JSDom (Jest) */ function isValidTargetElement(targetElement?: string): targetElement is string { return ( isNotNil(targetElement) && targetElement !== A11yElement.Root && targetElement !== A11yElement.Component ) } //#endregion /** * Generates Jest a11y tests for all stories in a module * * Note: `color-contrast` rule is NOT checked, and it is not likely to be fixed soon. * - https://github.com/dequelabs/axe-core/issues/2610 * - https://github.com/jsdom/jsdom#unimplemented-parts-of-the-web-platform * - Verify it is/isn't checked with a `console.log(results)` below */ export async function generateA11yStoryTests( storyModule: Record, ) { const storiesTargetElement = storyModule.default.parameters?.a11y?.element Object.entries(storyModule) .filter(isA11yStoryEntry) .forEach(([storyName, story]) => { it(`${storyName} story is accessible`, async () => { const targetElement = story.parameters?.a11y?.element ?? storiesTargetElement const { baseElement, container } = renderStory(story) const results = isValidTargetElement(targetElement) ? await axe(baseElement.querySelector(targetElement) ?? baseElement) : await axe(container) expect(results).toHaveNoViolations() }) }) }