Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
import { BackgroundCheckAttachForm, type AttachFormValues } from './BackgroundCheckAttachForm';

function renderForm(overrides: Partial<Parameters<typeof BackgroundCheckAttachForm>[0]> = {}) {
const values: AttachFormValues = { vendor: 'other', reportDate: '2026-06-01', file: null };
const props = {
values,
onChange: vi.fn(),
onSubmit: vi.fn(),
submitting: false,
canSubmit: true,
...overrides,
};
render(<BackgroundCheckAttachForm {...props} />);
return props;
}

function fileInput() {
return screen.getByLabelText(/background check report or identity document/i);
}

describe('BackgroundCheckAttachForm', () => {
it('accepts a passport photo (the manual identity fallback)', () => {
const onChange = vi.fn();
renderForm({ onChange });

const passport = new File([new Uint8Array([0xff, 0xd8, 0xff])], 'passport.jpg', {
type: 'image/jpeg',
});
fireEvent.change(fileInput(), { target: { files: [passport] } });

expect(screen.queryByText(/only pdf files are accepted/i)).not.toBeInTheDocument();
expect(onChange).toHaveBeenCalledWith(expect.objectContaining({ file: passport }));
});

it('lets the native picker offer images, not just PDFs', () => {
renderForm();

const accept = fileInput().getAttribute('accept') ?? '';
expect(accept).toContain('application/pdf');
expect(accept).toMatch(/image\/png/);
expect(accept).toMatch(/image\/jpeg/);
expect(accept).toMatch(/image\/heic/);
});

it('still accepts a PDF report', () => {
const onChange = vi.fn();
renderForm({ onChange });

const pdf = new File(['%PDF-1.7'], 'report.pdf', { type: 'application/pdf' });
fireEvent.change(fileInput(), { target: { files: [pdf] } });

expect(screen.queryByText(/upload a pdf or image/i)).not.toBeInTheDocument();
expect(onChange).toHaveBeenCalledWith(expect.objectContaining({ file: pdf }));
});

it('rejects unsupported types and oversized files', () => {
const onChange = vi.fn();
renderForm({ onChange });

const exe = new File([new Uint8Array([0x4d, 0x5a])], 'malware.exe', {
type: 'application/x-msdownload',
});
fireEvent.change(fileInput(), { target: { files: [exe] } });
expect(screen.getByText(/upload a pdf or image/i)).toBeInTheDocument();
expect(onChange).not.toHaveBeenCalled();

const huge = new File(['x'], 'huge.png', { type: 'image/png' });
Object.defineProperty(huge, 'size', { value: 26 * 1024 * 1024 });
fireEvent.change(fileInput(), { target: { files: [huge] } });
expect(screen.getByText(/exceeds 25 mb limit/i)).toBeInTheDocument();
expect(onChange).not.toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,21 @@ interface AttachFormProps {

const MAX_FILE_BYTES = 25 * 1024 * 1024;

// Reports are usually PDFs, but the manual identity fallback is a passport
// photo (JPEG/PNG/HEIC). The API accepts these same types — see
// validateFileContent in apps/api/src/utils/file-type-validation.ts.
const ACCEPTED_MIME_TYPES = [
'application/pdf',
'image/png',
'image/jpeg',
'image/webp',
'image/heic',
'image/heif',
];

const FILE_ACCEPT_ATTR =
'application/pdf,image/png,image/jpeg,image/webp,image/heic,image/heif,.pdf,.png,.jpg,.jpeg,.webp,.heic,.heif';

export function BackgroundCheckAttachForm({
values,
onChange,
Expand All @@ -65,8 +80,8 @@ export function BackgroundCheckAttachForm({
setFileError('File exceeds 25 MB limit.');
return;
}
if (file.type && file.type !== 'application/pdf') {
setFileError('Only PDF files are accepted.');
if (file.type && !ACCEPTED_MIME_TYPES.includes(file.type)) {
setFileError('Upload a PDF or image file (PDF, PNG, JPG, HEIC).');
return;
}
setFileError(null);
Expand Down Expand Up @@ -125,10 +140,10 @@ export function BackgroundCheckAttachForm({
<input
ref={inputRef}
type="file"
accept="application/pdf"
accept={FILE_ACCEPT_ATTR}
className="sr-only"
onChange={handleFileChange}
aria-label="Background check PDF"
aria-label="Background check report or identity document"
/>
<div
onDragOver={(event) => {
Expand All @@ -151,7 +166,7 @@ export function BackgroundCheckAttachForm({
<>Selected: {values.file.name}</>
) : (
<>
Drop the PDF here, or{' '}
Drop the file here, or{' '}
<button
type="button"
onClick={handleBrowse}
Expand All @@ -163,7 +178,7 @@ export function BackgroundCheckAttachForm({
)}
</Text>
<Text size="xs" variant="muted">
PDF · up to 25 MB · stored encrypted in your evidence vault
PDF or image (PNG, JPG, HEIC) · up to 25 MB · stored encrypted in your evidence vault
</Text>
{fileError && (
<p className="mt-2 text-xs text-destructive">{fileError}</p>
Expand Down
Loading