mirror of
https://github.com/ant-design/ant-design.git
synced 2024-12-19 03:54:28 +08:00
08a9325182
* test: add accessibility test utilities * refactor(tests): replace polling with Promise chain for axe test queue management * test: add a11y test for components demo * feat: add a11y test utils * test: add a11y test for components demo * test: add a11y test for components demo * chore: remove unnecessary code * feat: add polyfill for test environment * test: add a11y test for components demo * test: add a11y test for components demo * chore: adjust code style
146 lines
3.4 KiB
TypeScript
146 lines
3.4 KiB
TypeScript
import React from 'react';
|
|
import { render } from '@testing-library/react';
|
|
import { globSync } from 'glob';
|
|
import { axe } from 'jest-axe';
|
|
|
|
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 },
|
|
}),
|
|
{},
|
|
);
|
|
};
|
|
|
|
// 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(<Component />);
|
|
|
|
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);
|
|
});
|
|
});
|
|
});
|
|
}
|