feat(cv): qualify templates and reliable local import processing
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import "@testing-library/jest-dom";
|
||||
import { act, fireEvent, render, screen, waitFor, within } from "@testing-library/react";
|
||||
import { CoverLetterDocumentFields } from "./components/CoverLetterDocument";
|
||||
import { CoverLetterDocumentFields, CoverLetterDocumentPreview } from "./components/CoverLetterDocument";
|
||||
import { ApplicationCoverLetterSection } from "./components/ApplicationAssets";
|
||||
import { CoverLetterDocumentSettings } from "./applicationWorkspace";
|
||||
import { api } from "./api";
|
||||
@@ -116,11 +116,93 @@ test("design copy resolves null overrides through actual theme defaults and effe
|
||||
await screen.findByRole("region", { name: "Design snapshot" });
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
fireEvent.click(screen.getByRole("button", { name: "Use this copy" }));
|
||||
expect(onChange).toHaveBeenCalledWith({ ...settings, accentColor: "#112233", bodyFont: "Georgia, 'Times New Roman', serif", baseFontSizePt: 11, lineHeight: 1.5, pageMarginMm: 13.12, sourceCvId: 3 });
|
||||
expect(onChange).toHaveBeenCalledWith({ ...settings, designVersion: 2, accentColor: "#112233", bodyFont: "Georgia, 'Times New Roman', serif", headingFont: "Georgia, 'Times New Roman', serif", baseFontSizePt: 11, lineHeight: 1.5, pageMarginMm: 13.12, sourceCvId: 3 });
|
||||
expect(mockedApi.get).not.toHaveBeenCalledWith("/career/profile");
|
||||
expect(mockedApi.put).not.toHaveBeenCalled(); expect(mockedApi.post).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("letter template chooser uses nine actual matching themes and preserves destination letter details", async () => {
|
||||
const originalGet = mockedApi.get.getMockImplementation()!;
|
||||
const themeRows = ["Modern", "Minimal", "Executive", "Technical", "ATS Classic", "Nordic", "Elegant", "Creative", "Code"].map(name => ({
|
||||
id: name.toLowerCase().replace(/ /g, "-"), name, description: `${name} letter design`, available: name !== "Executive", requiresPro: name === "Executive",
|
||||
accent: "#3157d5", bodyFont: "Arial, Helvetica, sans-serif", headingFont: "Georgia, 'Times New Roman', serif", baseFontSizePt: 10, lineHeight: 1.42, pageMarginMm: 18,
|
||||
}));
|
||||
mockedApi.get.mockImplementation(((url: string) => url === "/cv/themes" ? Promise.resolve({ data: themeRows }) : originalGet(url)) as any);
|
||||
const onChange = jest.fn();
|
||||
render(wrap(<CoverLetterDocumentFields settings={{ ...settings, pageSize: "letter" }} disabled={false} onChange={onChange} />));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Choose letter template" }));
|
||||
const select = await screen.findByRole("combobox", { name: "Letter template" });
|
||||
await waitFor(() => expect(select).not.toHaveAttribute("aria-disabled", "true"));
|
||||
fireEvent.mouseDown(select);
|
||||
expect(await screen.findAllByRole("option")).toHaveLength(9);
|
||||
expect(screen.getByRole("option", { name: /Executive/ })).toHaveAttribute("aria-disabled", "true");
|
||||
fireEvent.click(screen.getByRole("option", { name: "Nordic" }));
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
fireEvent.click(screen.getByRole("button", { name: "Use this template" }));
|
||||
expect(onChange).toHaveBeenCalledWith({ ...settings, pageSize: "letter", designVersion: 2, themeId: "nordic", accentColor: "#3157d5", bodyFont: "Arial, Helvetica, sans-serif", headingFont: "Georgia, 'Times New Roman', serif", baseFontSizePt: 10, lineHeight: 1.42, pageMarginMm: 18 });
|
||||
expect(mockedApi.put).not.toHaveBeenCalled();
|
||||
expect(mockedApi.post).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("letter template load failures retry and cancel never changes the local letter", async () => {
|
||||
const originalGet = mockedApi.get.getMockImplementation()!;
|
||||
let attempts = 0;
|
||||
mockedApi.get.mockImplementation(((url: string) => url === "/cv/themes" && ++attempts === 1 ? Promise.reject(new Error("Synthetic theme failure")) : originalGet(url)) as any);
|
||||
const onChange = jest.fn();
|
||||
render(wrap(<CoverLetterDocumentFields settings={settings} disabled={false} onChange={onChange} />));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Choose letter template" }));
|
||||
expect(await screen.findByRole("alert")).toHaveTextContent("Could not load letter templates.");
|
||||
expect(screen.getByRole("button", { name: "Use this template" })).toBeDisabled();
|
||||
fireEvent.click(screen.getByRole("button", { name: "Retry" }));
|
||||
await waitFor(() => expect(screen.getByRole("button", { name: "Use this template" })).toBeEnabled());
|
||||
fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("applying a letter template updates the existing letter preview using the current body without saving", async () => {
|
||||
mockedApi.post.mockResolvedValue({ data: { html: '<main class="page">Actual letter preview</main>' } } as any);
|
||||
function Draft() {
|
||||
const [draft, setDraft] = React.useState(settings);
|
||||
return <><CoverLetterDocumentFields settings={draft} disabled={false} onChange={setDraft} />
|
||||
<CoverLetterDocumentPreview jobId={7} text="Current authored letter body" settings={draft} active /></>;
|
||||
}
|
||||
render(wrap(<Draft />));
|
||||
await waitFor(() => expect(mockedApi.post).toHaveBeenCalledTimes(1));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Choose letter template" }));
|
||||
await waitFor(() => expect(screen.getByRole("button", { name: "Use this template" })).toBeEnabled());
|
||||
fireEvent.click(screen.getByRole("button", { name: "Use this template" }));
|
||||
await waitFor(() => expect(mockedApi.post).toHaveBeenLastCalledWith('/jobapplications/7/cover-letter/preview', {
|
||||
text: 'Current authored letter body', documentSettings: expect.objectContaining({ designVersion: 2, headingFont: "Georgia, 'Times New Roman', serif", recipient: settings.recipient, language: settings.language }),
|
||||
}));
|
||||
expect(mockedApi.put).not.toHaveBeenCalled();
|
||||
expect(mockedApi.get).not.toHaveBeenCalledWith('/cv/themes/modern/preview');
|
||||
});
|
||||
|
||||
test("disabled letter drafting cannot open the template chooser", () => {
|
||||
const onChange = jest.fn();
|
||||
render(wrap(<CoverLetterDocumentFields settings={settings} disabled onChange={onChange} />));
|
||||
const chooser = screen.getByRole('button', { name: 'Choose letter template' });
|
||||
expect(chooser).toBeDisabled();
|
||||
fireEvent.click(chooser);
|
||||
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
expect(mockedApi.get).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("matching a customized CV copies heading density and paper but preserves letter language", async () => {
|
||||
const originalGet = mockedApi.get.getMockImplementation()!;
|
||||
mockedApi.get.mockImplementation(((url: string) => url === "/cv/variants/3" ? Promise.resolve({ data: { ...variant, settings: {
|
||||
...variant.settings, headingFont: "Georgia, 'Times New Roman', serif", bodyFont: "Arial, Helvetica, sans-serif", accentColor: "#abcdef", pageMarginMm: 20, density: "roomy", pageSize: "letter",
|
||||
} } }) : originalGet(url)) as any);
|
||||
const onChange = jest.fn();
|
||||
render(wrap(<CoverLetterDocumentFields settings={settings} disabled={false} onChange={onChange} />));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Match CV design" }));
|
||||
await selectSource();
|
||||
await screen.findByRole("region", { name: "Design snapshot" });
|
||||
fireEvent.click(screen.getByRole("button", { name: "Use this copy" }));
|
||||
expect(onChange).toHaveBeenCalledWith({ ...settings, pageSize: "letter", designVersion: 2, headingFont: "Georgia, 'Times New Roman', serif", bodyFont: "Arial, Helvetica, sans-serif", accentColor: "#abcdef", baseFontSizePt: 11, lineHeight: 1.5, pageMarginMm: 23.6, sourceCvId: 3 });
|
||||
});
|
||||
|
||||
test("closing a copy dialog ignores a late selected CV read", async () => {
|
||||
const selected = deferred<any>();
|
||||
const originalGet = mockedApi.get.getMockImplementation()!;
|
||||
|
||||
Reference in New Issue
Block a user