import React from "react"; import "@testing-library/jest-dom"; import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import { MemoryRouter } from "react-router-dom"; import CropImageDialog from "./components/CropImageDialog"; import CvCustomizeTab from "./components/cv/CvCustomizeTab"; import { emptyCvVariantSettings, type CvTheme } from "./cvBuilder"; import { ConfirmProvider } from "./confirm"; import { I18nProvider } from "./i18n/I18nProvider"; beforeEach(() => { localStorage.clear(); URL.createObjectURL = jest.fn(() => "blob:portrait"); URL.revokeObjectURL = jest.fn(); }); afterEach(() => jest.restoreAllMocks()); test("keyboard crop positions are bounded and feed the same exported square as dragging", async () => { const drawImage = jest.fn(); jest.spyOn(HTMLCanvasElement.prototype, "getContext").mockReturnValue({ drawImage } as any); jest.spyOn(HTMLCanvasElement.prototype, "toBlob").mockImplementation(callback => callback(new Blob(["portrait"], { type: "image/png" }))); const save = jest.fn(); render(); const image = screen.getByRole("img", { name: "Crop preview" }); Object.defineProperties(image, { naturalWidth: { value: 400 }, naturalHeight: { value: 200 } }); fireEvent.load(image); const horizontal = screen.getByRole("slider", { name: "Horizontal position" }); const vertical = screen.getByRole("slider", { name: "Vertical position" }); expect(vertical).toBeDisabled(); fireEvent.keyDown(horizontal, { key: "Home" }); expect(horizontal).toHaveAttribute("aria-valuenow", "-280"); fireEvent.keyDown(horizontal, { key: "ArrowLeft" }); expect(horizontal).toHaveAttribute("aria-valuenow", "-280"); fireEvent.change(screen.getByRole("slider", { name: "Zoom" }), { target: { value: "2" } }); fireEvent.keyDown(vertical, { key: "Home" }); expect(vertical).toHaveAttribute("aria-valuenow", "-280"); fireEvent.click(screen.getByRole("button", { name: "Save image" })); await waitFor(() => expect(save).toHaveBeenCalledTimes(1)); expect(drawImage).toHaveBeenLastCalledWith(image, 100, 100, 100, 100, 0, 0, 512, 512); }); const theme: CvTheme = { id: "modern", name: "Modern", category: "Professional", description: "", layout: "single", accent: "#3157d5", photoShape: "circle", supportsIcons: true, atsFriendly: true, requiresPro: false, available: true, swatches: [] }; function mountPhoto(photoShape: CvTheme["photoShape"], includeProfileContact = true) { const update = jest.fn(); render( fallback} />); return update; } test("capable CV designs link directly to existing portrait management without changing the CV", () => { const update = mountPhoto("circle"); expect(screen.getByRole("link", { name: "Manage profile photo" })).toHaveAttribute("href", "/career?section=profile"); expect(screen.getByRole("switch", { name: "Show profile photo" })).toBeInTheDocument(); expect(update).not.toHaveBeenCalled(); }); test("photo-free designs do not offer portrait controls", () => { mountPhoto("none"); fireEvent.click(screen.getByRole("button", { name: "Advanced document settings" })); expect(screen.queryByRole("switch", { name: "Show profile photo" })).not.toBeInTheDocument(); expect(screen.queryByRole("link", { name: "Manage profile photo" })).not.toBeInTheDocument(); }); test("explicitly excluded profile contact explains why the portrait is omitted", () => { mountPhoto("circle", false); expect(screen.getByText("Include Career Profile contact details in Content to show its photo. A missing photo leaves the document unchanged.")).toBeInTheDocument(); });