import React from 'react'; import { render } from '@testing-library/react'; import { globSync } from 'glob'; import { axe } from 'jest-axe'; class AxeQueueManager { private queue: Promise = Promise.resolve(); private isProcessing = false; async enqueue(task: () => Promise): Promise { const currentQueue = this.queue; const newTask = async () => { try { await currentQueue; this.isProcessing = true; return await task(); } finally { this.isProcessing = false; } }; this.queue = this.queue.then(newTask, newTask); return this.queue; } isRunning(): boolean { return this.isProcessing; } } const axeQueueManager = new AxeQueueManager(); const runAxe = async (...args: Parameters): Promise> => { return axeQueueManager.enqueue(async () => { try { return await axe(...args); } catch (error) { console.error('Axe test failed:', error); throw error; } }); }; type Rules = { [key: string]: { enabled: boolean; }; }; const convertRulesToAxeFormat = (rules: string[]): Rules => { return rules.reduce( (acc, rule) => ({ ...acc, [rule]: { enabled: false }, }), {}, ); }; // eslint-disable-next-line jest/no-export export function accessibilityTest(Component: React.ComponentType, disabledRules?: string[]) { beforeAll(() => { // Fake ResizeObserver global.ResizeObserver = jest.fn(() => { return { observe() {}, unobserve() {}, disconnect() {}, }; }) as jest.Mock; // fake fetch global.fetch = jest.fn(() => { return { then() { return this; }, catch() { return this; }, finally() { return this; }, }; }) as jest.Mock; }); beforeEach(() => { // Reset all mocks if (global.fetch) { (global.fetch as jest.Mock).mockClear(); } }); afterEach(() => { // Clear all mocks jest.clearAllMocks(); }); describe(`accessibility`, () => { it(`component does not have any violations`, async () => { jest.useRealTimers(); const { container } = render(); const rules = convertRulesToAxeFormat(disabledRules || []); const results = await runAxe(container, { rules }); expect(results).toHaveNoViolations(); }, 30000); }); } type Options = { skip?: boolean | string[]; disabledRules?: string[]; }; // eslint-disable-next-line jest/no-export export default function accessibilityDemoTest(component: string, options: Options = {}) { // If skip is true, return immediately without executing any tests if (options.skip === true) { describe.skip(`${component} demo a11y`, () => { it('skipped', () => {}); }); return; } describe(`${component} demo a11y`, () => { const files = globSync(`./components/${component}/demo/*.tsx`).filter( (file) => !file.includes('_semantic') && !file.includes('debug') && !file.includes('component-token'), ); files.forEach((file) => { const shouldSkip = Array.isArray(options.skip) && options.skip.some((c) => file.endsWith(c)); const testMethod = shouldSkip ? describe.skip : describe; testMethod(`Test ${file} accessibility`, () => { const Demo = require(`../../${file}`).default; accessibilityTest(Demo, options.disabledRules); }); }); }); }