Skip to content

Commit 19b08da

Browse files
committed
test(accessibility): add ResumeUpload accessibility coverage
1 parent e3369ea commit 19b08da

1 file changed

Lines changed: 122 additions & 0 deletions

File tree

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import { render, screen, fireEvent } from '@testing-library/react';
2+
import { describe, it, expect, vi, beforeEach } from 'vitest';
3+
import ResumeUpload from './ResumeUpload';
4+
import type { ReactNode, HTMLAttributes } from 'react';
5+
import '@testing-library/jest-dom';
6+
7+
vi.mock('framer-motion', () => ({
8+
motion: {
9+
div: ({ children, ...props }: HTMLAttributes<HTMLDivElement> & { children?: ReactNode }) => (
10+
<div {...props}>{children}</div>
11+
),
12+
},
13+
AnimatePresence: ({ children }: { children: ReactNode }) => <>{children}</>,
14+
}));
15+
16+
describe('ResumeUpload - Accessibility compliance', () => {
17+
const onParsed = vi.fn();
18+
const onError = vi.fn();
19+
20+
beforeEach(() => {
21+
vi.clearAllMocks();
22+
});
23+
24+
it('renders the hidden file input with an accessible label and correct accept types', () => {
25+
render(<ResumeUpload onParsed={onParsed} onError={onError} />);
26+
27+
// File input must be queryable via its accessible name (aria-label)
28+
const fileInput = screen.getByLabelText('Upload resume');
29+
expect(fileInput).toBeInTheDocument();
30+
expect(fileInput).toHaveAttribute('type', 'file');
31+
expect(fileInput).toHaveAttribute('accept', '.pdf,.docx,.doc');
32+
});
33+
34+
it('displays descriptive, screen-reader-visible instruction text in the empty state', () => {
35+
render(<ResumeUpload onParsed={onParsed} onError={onError} />);
36+
37+
// Main instruction text should be present and legible to screen readers
38+
expect(screen.getByText('Drop your resume here or click to browse')).toBeInTheDocument();
39+
expect(screen.getByText('PDF or DOCX · Max 5MB')).toBeInTheDocument();
40+
});
41+
42+
it('renders a remove button with an accessible label when a file is selected', async () => {
43+
// Mock successful fetch to allow transitioning to the file-selected state
44+
global.fetch = vi.fn().mockResolvedValue({
45+
ok: true,
46+
json: async () => ({
47+
success: true,
48+
fileName: 'my_resume.pdf',
49+
data: { name: 'John Doe' },
50+
}),
51+
}) as typeof fetch;
52+
53+
render(<ResumeUpload onParsed={onParsed} onError={onError} />);
54+
55+
const fileInput = screen.getByLabelText('Upload resume');
56+
const file = new File(['pdf-content'], 'my_resume.pdf', {
57+
type: 'application/pdf',
58+
});
59+
60+
fireEvent.change(fileInput, {
61+
target: { files: [file] },
62+
});
63+
64+
// The component should display the filename after uploading finishes
65+
const fileNameElement = await screen.findByText('my_resume.pdf');
66+
expect(fileNameElement).toBeInTheDocument();
67+
68+
// Verify the remove button exists and has a descriptive accessible label
69+
const removeButton = screen.getByRole('button', { name: 'Remove file' });
70+
expect(removeButton).toBeInTheDocument();
71+
});
72+
73+
it('clears the file and resets focus/state when the remove button is activated', async () => {
74+
// Mock successful fetch to allow transitioning to the file-selected state
75+
global.fetch = vi.fn().mockResolvedValue({
76+
ok: true,
77+
json: async () => ({
78+
success: true,
79+
fileName: 'my_resume.pdf',
80+
data: { name: 'John Doe' },
81+
}),
82+
}) as typeof fetch;
83+
84+
render(<ResumeUpload onParsed={onParsed} onError={onError} />);
85+
86+
const fileInput = screen.getByLabelText('Upload resume');
87+
const file = new File(['pdf-content'], 'my_resume.pdf', {
88+
type: 'application/pdf',
89+
});
90+
91+
fireEvent.change(fileInput, {
92+
target: { files: [file] },
93+
});
94+
95+
const removeButton = await screen.findByRole('button', { name: 'Remove file' });
96+
fireEvent.click(removeButton);
97+
98+
// After removal, the file name should not be present anymore
99+
expect(screen.queryByText('my_resume.pdf')).not.toBeInTheDocument();
100+
// The screen-reader-visible empty state instructions should be restored
101+
expect(screen.getByText('Drop your resume here or click to browse')).toBeInTheDocument();
102+
});
103+
104+
it('renders a screen-reader-visible message when parsing/uploading is in progress', async () => {
105+
// Keep upload pending
106+
global.fetch = vi.fn().mockImplementation(() => new Promise(() => {}));
107+
108+
render(<ResumeUpload onParsed={onParsed} onError={onError} />);
109+
110+
const fileInput = screen.getByLabelText('Upload resume');
111+
const file = new File(['pdf-content'], 'my_resume.pdf', {
112+
type: 'application/pdf',
113+
});
114+
115+
fireEvent.change(fileInput, {
116+
target: { files: [file] },
117+
});
118+
119+
// Verify the parsing state is announced
120+
expect(screen.getByText('Parsing resume...')).toBeInTheDocument();
121+
});
122+
});

0 commit comments

Comments
 (0)