Files
jobtrackingapp/job-tracker-ui/src/attachments.test.tsx
T
2026-10-07 01:58:31 +02:00

105 lines
3.9 KiB
TypeScript

import React from 'react';
import '@testing-library/jest-dom';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { ConfirmProvider } from './confirm';
import { PromptProvider } from './prompt';
import Attachments from './components/Attachments';
import { ToastProvider } from './toast';
import { I18nProvider } from './i18n/I18nProvider';
import { api } from './api';
jest.mock('./api', () => ({
getApiErrorMessage: (_error: unknown, fallback: string) => fallback,
api: {
get: jest.fn(),
post: jest.fn(),
patch: jest.fn(),
delete: jest.fn(),
},
}));
const mockedApi = api as jest.Mocked<typeof api>;
beforeEach(() => jest.clearAllMocks());
test('attachments empty state renders drag and drop guidance', async () => {
mockedApi.get.mockResolvedValueOnce({ data: [] } as any);
render(
<ToastProvider>
<I18nProvider>
<ConfirmProvider>
<PromptProvider>
<Attachments jobId={42} />
</PromptProvider>
</ConfirmProvider>
</I18nProvider>
</ToastProvider>,
);
expect(await screen.findByText(/drag and drop files here/i)).toBeInTheDocument();
expect(await screen.findByText(/no attachments yet/i)).toBeInTheDocument();
expect(screen.getByText(/max 10 mb each/i)).toBeInTheDocument();
});
test('attachments metadata controls update purpose and ai usage', async () => {
mockedApi.get.mockResolvedValueOnce({
data: [
{
id: 7,
fileName: 'resume.pdf',
uploadDate: new Date().toISOString(),
fileType: 'application/pdf',
fileSize: 2048,
purpose: 'resume',
useForAi: true,
},
],
} as any);
mockedApi.patch.mockResolvedValue({ data: {} } as any);
render(
<ToastProvider>
<I18nProvider>
<ConfirmProvider>
<PromptProvider>
<Attachments jobId={42} />
</PromptProvider>
</ConfirmProvider>
</I18nProvider>
</ToastProvider>,
);
expect(await screen.findByDisplayValue(/resume/i)).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Preview: resume.pdf' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Download: resume.pdf' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Rename: resume.pdf' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Delete: resume.pdf' })).toBeInTheDocument();
expect(screen.getAllByRole('columnheader')).toHaveLength(8);
expect(screen.getByRole('columnheader', { name: 'Uploaded' })).toBeInTheDocument();
fireEvent.mouseDown(screen.getByRole('combobox', { name: /purpose/i }));
fireEvent.click(await screen.findByRole('option', { name: /portfolio/i }));
await waitFor(() => {
expect(mockedApi.patch).toHaveBeenCalledWith('/attachments/7', expect.objectContaining({ purpose: 'portfolio', useForAi: true }));
});
fireEvent.click(screen.getByRole('switch', { name: 'Use for AI: resume.pdf' }));
await waitFor(() => {
expect(mockedApi.patch).toHaveBeenCalledWith('/attachments/7', expect.objectContaining({ purpose: 'portfolio', useForAi: false }));
});
});
test('attachment failures remain explicit and retry restores the real empty state', async () => {
mockedApi.get.mockRejectedValueOnce({ response: { status: 503 } }).mockResolvedValueOnce({ data: [] } as any);
render(<ToastProvider><I18nProvider><ConfirmProvider><PromptProvider><Attachments jobId={42} /></PromptProvider></ConfirmProvider></I18nProvider></ToastProvider>);
expect(await screen.findByRole('alert')).toHaveTextContent("Could not load this application's attachments.");
expect(screen.queryByText(/no attachments yet/i)).not.toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: 'Retry' }));
expect(await screen.findByText(/no attachments yet/i)).toBeInTheDocument();
expect(mockedApi.get).toHaveBeenCalledTimes(2);
expect(mockedApi.post).not.toHaveBeenCalled();
});