Files
jobtrackingapp/job-tracker-ui/src/cv-photo-controls.test.tsx
T

67 lines
3.9 KiB
TypeScript

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(<I18nProvider><CropImageDialog open file={new File(["portrait"], "portrait.png", { type: "image/png" })} onClose={jest.fn()} onSave={save} /></I18nProvider>);
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(<MemoryRouter><I18nProvider><ConfirmProvider><CvCustomizeTab mode="customize" settings={{ ...emptyCvVariantSettings(), includeProfileContact }} themes={[{ ...theme, photoShape }]} update={update} sectionLabel={(_, fallback) => fallback} /></ConfirmProvider></I18nProvider></MemoryRouter>);
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();
});