import React from "react"; import "@testing-library/jest-dom"; import { act, fireEvent, render, screen, waitFor } from "@testing-library/react"; import CvTemplatePreviewDialog from "./components/cv/CvTemplatePreviewDialog"; import { cvBuilderApi, CvRender, CvTheme } from "./cvBuilder"; import { I18nProvider } from "./i18n/I18nProvider"; const modern: CvTheme = { id: "modern", name: "Modern", category: "Professional", description: "A clear document", layout: "single", accent: "#3157d5", photoShape: "none", supportsIcons: false, atsFriendly: true, requiresPro: false, available: true, swatches: [] }; const code = { ...modern, id: "code", name: "Code" }; const sample = (id: string): CvRender => ({ themeId: id, html: `
${id} sample
`, suggestedFileName: "sample.pdf" }); function deferred() { let resolve!: (value: T) => void; const promise = new Promise(yes => { resolve = yes; }); return { promise, resolve }; } beforeEach(() => { localStorage.clear(); global.ResizeObserver = class { observe() {} unobserve() {} disconnect() {} }; jest.spyOn(cvBuilderApi, "themePreview").mockResolvedValue(sample("modern")); }); afterEach(() => jest.restoreAllMocks()); test("candidate loads a labelled sample without applying; Cancel preserves the draft", async () => { const onApply = jest.fn(); const onClose = jest.fn(); render(); expect(await screen.findByTitle("Template sample preview")).toHaveAttribute("srcdoc", expect.stringContaining("modern sample")); expect(screen.getByText(/Sample content/)).toBeVisible(); expect(onApply).not.toHaveBeenCalled(); fireEvent.click(screen.getByRole("button", { name: "Cancel" })); expect(onClose).toHaveBeenCalledTimes(1); expect(onApply).not.toHaveBeenCalled(); }); test("sample scales down to a narrow preview host", async () => { const width = jest.spyOn(HTMLElement.prototype, "clientWidth", "get").mockReturnValue(280); render(); const frame = await screen.findByTitle("Template sample preview"); expect(Number((frame as HTMLIFrameElement).style.transform.match(/scale\(([^)]+)\)/)?.[1])).toBeLessThan(0.4); width.mockRestore(); }); test("a late candidate response cannot replace the selected template", async () => { const held = deferred(); jest.mocked(cvBuilderApi.themePreview).mockImplementation(id => id === "modern" ? held.promise : Promise.resolve(sample("code"))); const view = render(); view.rerender(); await waitFor(() => expect(screen.getByTitle("Template sample preview")).toHaveAttribute("srcdoc", expect.stringContaining("code sample"))); await act(async () => held.resolve(sample("modern"))); expect(screen.getByTitle("Template sample preview")).toHaveAttribute("srcdoc", expect.stringContaining("code sample")); }); test("failed preview has an explicit retry and cannot apply until it succeeds", async () => { jest.mocked(cvBuilderApi.themePreview).mockRejectedValueOnce(new Error("synthetic failure")); const onApply = jest.fn(); render(); expect(await screen.findByRole("alert")).toHaveTextContent("Could not preview this design"); expect(screen.getByRole("button", { name: "Apply template" })).toBeDisabled(); fireEvent.click(screen.getByRole("button", { name: "Retry preview" })); await waitFor(() => expect(screen.getByRole("button", { name: "Apply template" })).toBeEnabled()); fireEvent.click(screen.getByRole("button", { name: "Apply template" })); expect(onApply).toHaveBeenCalledWith(modern); }); test("locked candidates cannot request or apply a preview", async () => { render(); expect(screen.getByRole("button", { name: "Apply template" })).toBeDisabled(); expect(cvBuilderApi.themePreview).not.toHaveBeenCalled(); }); test("Bokmål sample labelling and actions are localised", async () => { localStorage.setItem("uiLanguage", "nb"); render(); expect(await screen.findByTitle("Eksempelforhåndsvisning av mal")).toBeInTheDocument(); expect(screen.getByRole("button", { name: "Bruk mal" })).toBeEnabled(); });