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:
77
apps/web/components/reports/__tests__/report-card.spec.tsx
Normal file
77
apps/web/components/reports/__tests__/report-card.spec.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import * as React from 'react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { ReportCard } from '../report-card';
|
||||
|
||||
vi.mock('@/i18n/navigation', () => ({
|
||||
Link: ({
|
||||
children,
|
||||
href,
|
||||
...rest
|
||||
}: React.PropsWithChildren<{ href: string } & Record<string, unknown>>) => (
|
||||
<a href={href} {...rest}>
|
||||
{children}
|
||||
</a>
|
||||
),
|
||||
}));
|
||||
|
||||
const baseReport = {
|
||||
id: 'r1',
|
||||
type: 'RESIDENTIAL_MARKET' as const,
|
||||
title: 'Báo cáo thị trường Q1',
|
||||
params: {},
|
||||
content: null,
|
||||
pdfUrl: null,
|
||||
status: 'READY' as const,
|
||||
errorMsg: null,
|
||||
createdAt: '2026-04-01T08:30:00.000Z',
|
||||
updatedAt: '2026-04-01T08:30:00.000Z',
|
||||
};
|
||||
|
||||
describe('ReportCard', () => {
|
||||
it('renders title and type/status badges', () => {
|
||||
render(<ReportCard report={baseReport} />);
|
||||
expect(screen.getByText('Báo cáo thị trường Q1')).toBeInTheDocument();
|
||||
expect(screen.getByText('Nhà ở')).toBeInTheDocument();
|
||||
expect(screen.getByText('Hoàn thành')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('links to report detail for READY report (both detail icon link and bottom "Xem báo cáo" link)', () => {
|
||||
const { container } = render(<ReportCard report={baseReport} />);
|
||||
const links = container.querySelectorAll('a[href="/dashboard/reports/r1"]');
|
||||
expect(links.length).toBeGreaterThanOrEqual(1);
|
||||
expect(screen.getByText('Xem báo cáo')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not render "Xem báo cáo" link for non-READY reports', () => {
|
||||
render(
|
||||
<ReportCard report={{ ...baseReport, status: 'GENERATING' }} />,
|
||||
);
|
||||
expect(screen.queryByText('Xem báo cáo')).toBeNull();
|
||||
});
|
||||
|
||||
it('renders error message for FAILED report with errorMsg', () => {
|
||||
render(
|
||||
<ReportCard
|
||||
report={{
|
||||
...baseReport,
|
||||
status: 'FAILED',
|
||||
errorMsg: 'Thiếu dữ liệu',
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByText('Thiếu dữ liệu')).toBeInTheDocument();
|
||||
expect(screen.getByText('Lỗi')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('invokes onDelete with report id when delete button clicked', () => {
|
||||
const onDelete = vi.fn();
|
||||
render(<ReportCard report={baseReport} onDelete={onDelete} />);
|
||||
const trashButton = screen
|
||||
.getAllByRole('button')
|
||||
.find((b) => b.className.includes('text-destructive'));
|
||||
expect(trashButton).toBeDefined();
|
||||
fireEvent.click(trashButton!);
|
||||
expect(onDelete).toHaveBeenCalledWith('r1');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user