Files
OpenFlare/frontend/tests/a11y.test.tsx
T
ryan 7f8e257d33 前端真实 a11y 审计:新增 axe-core(devDep)+ tests/a11y.test.tsx,对登录页与注册页渲染完整表单后运行 axe 结构性规则(label/button-name/heading-order/landmark/aria),两页均零违规。摸清并处理了渲染依赖(UserProvider 会话检查、publicConfigQuery 门控、configBool 字符串语义)。vitest 116→118 全绿。
Result: {"status":"keep","total_issues":8,"golint_canonicalheader":0,"golint_errname":0,"golint_errorlint":1,"golint_exhaustive":0,"golint_forcetypeassert":0,"golint_gosec":0,"golint_intrange":0,"golint_modernize":3,"golint_nilnil":3,"golint_perfsprint":0,"golint_prealloc":0,"golint_recvcheck":1,"golint_usestdlibvars":0,"golint_wastedassign":0,"golint_total":8,"golint_test_testifylint":0,"golint_test_thelper":0,"golint_test_usetesting":0,"golint_test_total":0,"golint_vetx_total":0,"eslint_problems":0,"eslint_errors":0,"eslint_warnings":0,"tsc_errors":0,"vitest_failed":0,"vitest_total":118,"measure_s":71}
2026-08-16 20:49:55 +08:00

104 lines
2.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { render, screen } from '@testing-library/react';
import { NextIntlClientProvider } from 'next-intl';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import axe from 'axe-core';
import { LoginPage } from '@/components/auth/login-page';
import { RegisterPage } from '@/components/auth/register-page';
import { UserProvider } from '@/contexts/user-context';
import zhCN from '@/messages/zh-CN.json';
vi.mock('next/navigation', () => ({
useRouter: () => ({ replace: vi.fn(), push: vi.fn() }),
useSearchParams: () => new URLSearchParams(''),
}));
const { getUserInfoMock, getPublicConfigMock, getAuthSourcesMock } =
vi.hoisted(() => ({
getUserInfoMock: vi.fn(),
getPublicConfigMock: vi.fn(),
getAuthSourcesMock: vi.fn(),
}));
vi.mock('@/lib/services/auth', async (importOriginal) => {
const actual = await importOriginal<typeof import('@/lib/services/auth')>();
return {
...actual,
AuthService: {
...actual.AuthService,
getUserInfo: getUserInfoMock,
getAuthSources: getAuthSourcesMock,
},
};
});
vi.mock('@/lib/services/config', async (importOriginal) => {
const actual = await importOriginal<typeof import('@/lib/services/config')>();
return {
...actual,
ConfigService: {
...actual.ConfigService,
getPublicConfig: getPublicConfigMock,
},
};
});
async function runAxe(container: HTMLElement) {
const results = await axe.run(container, {
rules: {
// jsdom 无布局引擎,颜色对比度规则无法评估
'color-contrast': { enabled: false },
},
});
return results.violations;
}
function renderWithProviders(page: React.ReactNode) {
return render(
<NextIntlClientProvider
locale='zh-CN'
messages={zhCN}
timeZone='Asia/Shanghai'
>
<QueryClientProvider
client={
new QueryClient({
defaultOptions: { queries: { retry: false } },
})
}
>
<UserProvider>{page}</UserProvider>
</QueryClientProvider>
</NextIntlClientProvider>,
);
}
describe('a11y(axe-core 结构性规则)', () => {
beforeEach(() => {
getUserInfoMock.mockReset();
getUserInfoMock.mockResolvedValue(null);
getPublicConfigMock.mockReset();
getPublicConfigMock.mockResolvedValue({
registration_enabled: 'true',
password_register_enabled: 'true',
});
getAuthSourcesMock.mockReset();
getAuthSourcesMock.mockResolvedValue([]);
});
it('登录页表单无 axe 违规', async () => {
renderWithProviders(<LoginPage />);
await screen.findByRole('button', { name: /登录|submit|登录/ });
expect(await runAxe(document.body)).toEqual([]);
});
it('注册页表单无 axe 违规', async () => {
renderWithProviders(<RegisterPage />);
await screen.findByRole('button', { name: /创建账号|submit/ });
expect(await runAxe(document.body)).toEqual([]);
});
});