fix(a11y): resolve serious accessibility issues on search page (GOO-110)
- Add aria-hidden="true" to all decorative inline SVGs (bookmark, view-mode, funnel, checkmark) - Convert save-search popover to proper dialog: role="dialog", aria-modal, focus trap, Escape key, focus return to trigger - Add aria-pressed on list/map/split view-mode toggle buttons - Add aria-expanded + aria-controls on mobile filter toggle button - Add role="status" + aria-label="Đang tải..." on Suspense fallback Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
127
apps/web/app/[locale]/(admin)/admin/__tests__/admin-kyc.spec.tsx
Normal file
127
apps/web/app/[locale]/(admin)/admin/__tests__/admin-kyc.spec.tsx
Normal file
@@ -0,0 +1,127 @@
|
||||
/* eslint-disable import-x/order */
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
vi.mock('lucide-react', () => {
|
||||
const icon = (name: string) => (props: Record<string, unknown>) => <span data-testid={`icon-${name}`} {...props} />;
|
||||
return {
|
||||
CheckCircle: icon('check'),
|
||||
XCircle: icon('x'),
|
||||
RefreshCw: icon('refresh'),
|
||||
ChevronLeft: icon('chevron-left'),
|
||||
ChevronRight: icon('chevron-right'),
|
||||
ShieldCheck: icon('shield'),
|
||||
X: icon('close'),
|
||||
User: icon('user'),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('next/image', () => ({
|
||||
default: (props: Record<string, unknown>) => <img {...props} />,
|
||||
}));
|
||||
|
||||
vi.mock('@/components/design-system/status-chip', () => ({
|
||||
StatusChip: ({ status }: { status: string }) => <span data-testid="status-chip">{status}</span>,
|
||||
}));
|
||||
|
||||
const mockGetKycQueue = vi.fn();
|
||||
const mockApproveKyc = vi.fn();
|
||||
const mockRejectKyc = vi.fn();
|
||||
|
||||
vi.mock('@/lib/admin-api', () => ({
|
||||
adminApi: {
|
||||
getKycQueue: (...args: unknown[]) => mockGetKycQueue(...args),
|
||||
approveKyc: (...args: unknown[]) => mockApproveKyc(...args),
|
||||
rejectKyc: (...args: unknown[]) => mockRejectKyc(...args),
|
||||
},
|
||||
}));
|
||||
|
||||
import AdminKycPage from '../kyc/page';
|
||||
|
||||
const mockQueueData = {
|
||||
data: [
|
||||
{
|
||||
userId: 'u1',
|
||||
fullName: 'Nguyen Van A',
|
||||
phone: '0912345678',
|
||||
email: 'a@test.com',
|
||||
role: 'AGENT',
|
||||
kycStatus: 'PENDING',
|
||||
kycData: { idType: 'CCCD', idNumber: '012345678901', frontImageUrl: 'https://img.test/front.jpg' },
|
||||
createdAt: '2024-06-15T10:00:00Z',
|
||||
},
|
||||
{
|
||||
userId: 'u2',
|
||||
fullName: 'Tran Thi B',
|
||||
phone: '0987654321',
|
||||
email: null,
|
||||
role: 'USER',
|
||||
kycStatus: 'PENDING',
|
||||
kycData: null,
|
||||
createdAt: '2024-06-16T10:00:00Z',
|
||||
},
|
||||
],
|
||||
total: 2,
|
||||
page: 1,
|
||||
limit: 20,
|
||||
totalPages: 1,
|
||||
};
|
||||
|
||||
describe('AdminKycPage', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockGetKycQueue.mockResolvedValue(mockQueueData);
|
||||
mockApproveKyc.mockResolvedValue({});
|
||||
mockRejectKyc.mockResolvedValue({});
|
||||
});
|
||||
|
||||
it('renders heading and fetches queue', async () => {
|
||||
render(<AdminKycPage />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Duyệt KYC')).toBeInTheDocument();
|
||||
});
|
||||
expect(mockGetKycQueue).toHaveBeenCalledWith(1, 20);
|
||||
});
|
||||
|
||||
it('renders queue items in table', async () => {
|
||||
render(<AdminKycPage />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Nguyen Van A')).toBeInTheDocument();
|
||||
expect(screen.getByText('Tran Thi B')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows empty state when no requests', async () => {
|
||||
mockGetKycQueue.mockResolvedValue({ data: [], total: 0, page: 1, limit: 20, totalPages: 0 });
|
||||
render(<AdminKycPage />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Không có yêu cầu KYC nào đang chờ')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows error state when fetch fails', async () => {
|
||||
mockGetKycQueue.mockRejectedValue(new Error('Network error'));
|
||||
render(<AdminKycPage />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Network error')).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText('Thử lại')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('refreshes queue on refresh button click', async () => {
|
||||
render(<AdminKycPage />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Nguyen Van A')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: /làm mới/i }));
|
||||
|
||||
expect(mockGetKycQueue).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user