2024-04-08 14:04:08 +08:00
|
|
|
import React from 'react';
|
2022-04-15 23:33:10 +08:00
|
|
|
import { render } from '@testing-library/react';
|
2024-11-01 11:30:11 +08:00
|
|
|
import { globSync } from 'glob';
|
2023-06-07 11:54:50 +08:00
|
|
|
import { axe } from 'jest-axe';
|
2021-12-02 20:25:31 +08:00
|
|
|
|
2024-11-01 11:30:11 +08:00
|
|
|
class AxeQueueManager {
|
|
|
|
private queue: Promise<any> = Promise.resolve();
|
|
|
|
private isProcessing = false;
|
|
|
|
|
|
|
|
async enqueue<T>(task: () => Promise<T>): Promise<T> {
|
|
|
|
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<typeof axe>): Promise<ReturnType<typeof axe>> => {
|
|
|
|
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 },
|
|
|
|
}),
|
|
|
|
{},
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2023-06-07 21:59:21 +08:00
|
|
|
// eslint-disable-next-line jest/no-export
|
2024-11-01 11:30:11 +08:00
|
|
|
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();
|
|
|
|
});
|
2021-12-02 20:25:31 +08:00
|
|
|
describe(`accessibility`, () => {
|
|
|
|
it(`component does not have any violations`, async () => {
|
2023-06-07 21:59:21 +08:00
|
|
|
jest.useRealTimers();
|
2022-04-15 23:33:10 +08:00
|
|
|
const { container } = render(<Component />);
|
2024-11-01 11:30:11 +08:00
|
|
|
|
|
|
|
const rules = convertRulesToAxeFormat(disabledRules || []);
|
|
|
|
|
|
|
|
const results = await runAxe(container, { rules });
|
2021-12-02 20:25:31 +08:00
|
|
|
expect(results).toHaveNoViolations();
|
2024-11-01 11:30:11 +08:00
|
|
|
}, 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);
|
|
|
|
});
|
2021-12-02 20:25:31 +08:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|