import React from "react"; import "@testing-library/jest-dom"; import { act, fireEvent, render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import Attachments from "./components/Attachments"; import ImportExportJobs from "./components/ImportExportJobs"; import AddJobModal from "./components/AddJobModal"; import { ConfirmProvider } from "./confirm"; import { PromptProvider } from "./prompt"; import { I18nProvider } from "./i18n/I18nProvider"; import { ToastProvider } from "./toast"; import { api } from "./api"; // These tests exercise file selection; the unrelated date-picker adapter is ESM. jest.mock("@mui/x-date-pickers/DatePicker", () => ({ DatePicker: ({ label }: { label: string }) =>
{label}
})); const mockedApi = api as jest.Mocked; function renderPicker(component: React.ReactNode) { return render({component}); } async function expectNativeInputActivation(button: HTMLElement, input: HTMLInputElement) { const activated = jest.fn(); input.addEventListener("click", activated); act(() => button.focus()); await act(async () => { await userEvent.keyboard("{Enter}"); }); expect(activated).toHaveBeenCalledTimes(1); await act(async () => { await userEvent.keyboard(" "); }); expect(activated).toHaveBeenCalledTimes(2); await act(async () => { userEvent.click(button); }); expect(activated).toHaveBeenCalledTimes(3); expect(button).toHaveFocus(); } beforeEach(() => { window.localStorage.clear(); jest.clearAllMocks(); }); test("attachment Upload activates the same native input from Enter, Space and pointer", async () => { mockedApi.get.mockResolvedValue({ data: [] }); const { container } = renderPicker(); await screen.findByText(/No attachments yet\./); await expectNativeInputActivation(screen.getByRole("button", { name: "Upload" }), container.querySelector('input[type="file"]')!); expect(mockedApi.post).not.toHaveBeenCalled(); }); test.each([ ["Import JSON", "application/json"], ["Convert CSV to Import JSON", ".csv,text/csv"], ])("%s activates its own native input from Enter, Space and pointer", async (name, accept) => { const { container } = renderPicker(); await expectNativeInputActivation(screen.getByRole("button", { name }), container.querySelector(`input[accept="${accept}"]`)!); expect(mockedApi.post).not.toHaveBeenCalled(); }); test.each([0, 1, 2, 3])("Add Job attachment bucket %s retains keyboard selection and distinct pending files", async (bucket) => { mockedApi.post.mockImplementation((url) => Promise.resolve({ data: url === "/jobimport/preview" ? { success: true, title: "Supported role", company: "Fixture company", description: "Known description" } : {} })); renderPicker( {}} onCreated={() => {}} />); await screen.findByDisplayValue("Supported role"); fireEvent.click(screen.getByRole("button", { name: "Continue" })); for (let step = 0; step < bucket; step += 1) fireEvent.click(screen.getByRole("button", { name: "Skip and continue" })); const input = document.querySelector('input[type="file"]')!; expect(input.multiple).toBe(true); expect(input.accept).toContain(".pdf,.doc,.docx,.txt,.md"); await expectNativeInputActivation(screen.getByRole("button", { name: "Choose files" }), input); fireEvent.change(input, { target: { files: [new File(["fixture"], `bucket-${bucket}.txt`, { type: "text/plain" })] } }); expect(screen.getByText(`bucket-${bucket}.txt`)).toBeInTheDocument(); expect(mockedApi.post).not.toHaveBeenCalledWith("/attachments", expect.anything(), expect.anything()); }); test("attachment selection retains files and job ownership, disables activation while uploading and clears selection after completion", async () => { mockedApi.get.mockResolvedValue({ data: [] }); let finishUpload!: (value: { data: object }) => void; mockedApi.post.mockImplementationOnce(() => new Promise((resolve) => { finishUpload = resolve; })); const { container } = renderPicker(); await screen.findByText(/No attachments yet\./); const input = container.querySelector('input[type="file"]')!; const files = [new File(["Known CV"], "cv.pdf", { type: "application/pdf" }), new File(["Søknad"], "søknad.txt", { type: "text/plain" })]; fireEvent.change(input, { target: { files } }); const uploading = await screen.findByRole("button", { name: "Uploading..." }); expect(uploading).toBeDisabled(); const body = mockedApi.post.mock.calls[0][1] as FormData; expect(body.get("jobId")).toBe("42"); expect(body.getAll("files")).toEqual(files); const activated = jest.fn(); input.addEventListener("click", activated); act(() => uploading.click()); expect(activated).not.toHaveBeenCalled(); finishUpload({ data: {} }); await waitFor(() => expect(screen.getByRole("button", { name: "Upload" })).toBeEnabled()); expect(input.value).toBe(""); });