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; beforeEach(() => jest.clearAllMocks()); test.each([ ['en', 'Preview: resume.pdf', 'Preview: resume.pdf'], ['nb', 'Forhåndsvis: resume.pdf', 'Forhåndsvisning: resume.pdf'], ])('PDF preview identifies the document in %s', async (language, buttonName, frameTitle) => { const previousLanguage = localStorage.getItem('uiLanguage'); const originalCreate = URL.createObjectURL; const originalRevoke = URL.revokeObjectURL; localStorage.setItem('uiLanguage', language); URL.createObjectURL = jest.fn(() => 'blob:attachment-preview'); URL.revokeObjectURL = jest.fn(); mockedApi.get.mockResolvedValueOnce({ data: [{ id: 7, fileName: 'resume.pdf', fileType: 'application/pdf', fileSize: 2048, uploadDate: '2026-11-02T10:00:00Z' }] } as any); mockedApi.get.mockResolvedValueOnce({ data: new Blob(['sample'], { type: 'application/pdf' }) } as any); const view = render(); try { fireEvent.click(await screen.findByRole('button', { name: buttonName })); const frame = await screen.findByTitle(frameTitle); expect(frame.tagName).toBe('IFRAME'); expect(frame).toHaveAttribute('src', 'blob:attachment-preview'); } finally { view.unmount(); URL.createObjectURL = originalCreate; URL.revokeObjectURL = originalRevoke; if (previousLanguage === null) localStorage.removeItem('uiLanguage'); else localStorage.setItem('uiLanguage', previousLanguage); } }); test('attachments empty state renders drag and drop guidance', async () => { mockedApi.get.mockResolvedValueOnce({ data: [] } as any); render( , ); 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( , ); 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(); 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(); });