299 lines
21 KiB
TypeScript
299 lines
21 KiB
TypeScript
import React from "react";
|
|
import "@testing-library/jest-dom";
|
|
import { act, fireEvent, render, screen, waitFor, within } from "@testing-library/react";
|
|
import { CoverLetterDocumentFields, CoverLetterDocumentPreview } from "./components/CoverLetterDocument";
|
|
import { ApplicationCoverLetterSection } from "./components/ApplicationAssets";
|
|
import { CoverLetterDocumentSettings } from "./applicationWorkspace";
|
|
import { api } from "./api";
|
|
import { I18nProvider } from "./i18n/I18nProvider";
|
|
import { ConfirmProvider } from "./confirm";
|
|
|
|
const mockedApi = api as jest.Mocked<typeof api>;
|
|
const settings: CoverLetterDocumentSettings = {
|
|
schemaVersion: 1, senderName: "Saved sender", senderTitle: "Saved title", senderEmail: "saved@example.test", senderPhone: "123", senderLocation: "Oslo",
|
|
recipient: "Preserve recipient", subject: "Preserve subject", date: "2026-10-25", themeId: "modern", accentColor: "#2563eb", bodyFont: "Arial, Helvetica, sans-serif",
|
|
baseFontSizePt: 10, lineHeight: 1.42, pageMarginMm: 16, pageSize: "a4", language: "nb-NO",
|
|
};
|
|
const letter = { revision: 7, text: "Preserve letter body", currentVersion: 2, aiSuggestionCount: 0, documentSettings: settings,
|
|
versions: [{ version: 1, source: "manual", aiAction: null, length: 20, createdAtUtc: "2026-10-25T10:00:00Z", isCurrent: false }] };
|
|
const variant = { id: 3, name: "Chosen CV", settings: { themeId: "modern", headline: null, accentColor: null, bodyFont: null, baseFontSizePt: null, lineHeight: null, pageMarginMm: null, pageSize: null, density: "compact", language: "en" } };
|
|
const wrap = (ui: React.ReactNode) => <I18nProvider><ConfirmProvider>{ui}</ConfirmProvider></I18nProvider>;
|
|
function deferred<T>() {
|
|
let resolve!: (value: T) => void;
|
|
const promise = new Promise<T>(yes => { resolve = yes; });
|
|
return { promise, resolve };
|
|
}
|
|
beforeEach(() => {
|
|
localStorage.removeItem("uiLanguage");
|
|
jest.clearAllMocks(); mockedApi.get.mockReset(); mockedApi.post.mockReset(); mockedApi.put.mockReset();
|
|
mockedApi.get.mockImplementation((url: string) => Promise.resolve({ data:
|
|
url === "/cv/variants" ? [{ id: 3, name: "Chosen CV", themeId: "modern" }] :
|
|
url === "/cv/variants/3" ? variant :
|
|
url === "/cv/themes" ? [{ id: "modern", available: true, accent: "#112233", bodyFont: "Georgia, 'Times New Roman', serif", baseFontSizePt: 11, lineHeight: 1.5, pageMarginMm: 16 }] :
|
|
url === "/career/profile" ? { profile: { contact: { fullName: "Canonical name", headline: "Canonical title", email: "canonical@example.test", phone: "+47 555", location: "Bergen", website: "https://private.test", dateOfBirth: "private" } } } :
|
|
url.endsWith("/cover-letter") ? letter : { availableVariants: [] },
|
|
} as any));
|
|
});
|
|
afterEach(() => jest.restoreAllMocks());
|
|
|
|
async function selectSource() {
|
|
const select = await screen.findByRole("combobox", { name: "Source CV" });
|
|
await waitFor(() => expect(select).not.toHaveAttribute("aria-disabled", "true"));
|
|
fireEvent.mouseDown(select);
|
|
fireEvent.click(await screen.findByRole("option", { name: "Chosen CV" }));
|
|
}
|
|
|
|
test("contact copy is a reviewed canonical snapshot; cancel is inert and apply preserves recipient and design", async () => {
|
|
const onChange = jest.fn();
|
|
render(wrap(<CoverLetterDocumentFields settings={settings} disabled={false} onChange={onChange} />));
|
|
fireEvent.click(screen.getByRole("button", { name: "Copy contact from CV" }));
|
|
await selectSource();
|
|
const snapshot = await screen.findByRole("region", { name: "Contact snapshot" });
|
|
expect(snapshot).toHaveTextContent("Canonical name");
|
|
expect(snapshot).toHaveTextContent("Canonical title");
|
|
expect(snapshot).not.toHaveTextContent("private");
|
|
for (const name of [/Sender name/, /Professional title/, /Email/, /Phone/]) expect(within(snapshot).getByRole("checkbox", { name })).toBeChecked();
|
|
expect(within(snapshot).getByRole("checkbox", { name: /Location/ })).not.toBeChecked();
|
|
expect(onChange).not.toHaveBeenCalled();
|
|
fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
|
|
await waitFor(() => expect(screen.queryByRole("dialog")).not.toBeInTheDocument());
|
|
expect(onChange).not.toHaveBeenCalled();
|
|
fireEvent.click(screen.getByRole("button", { name: "Copy contact from CV" }));
|
|
await selectSource();
|
|
await screen.findByRole("region", { name: "Contact snapshot" });
|
|
fireEvent.click(screen.getByRole("button", { name: "Use this copy" }));
|
|
expect(onChange).toHaveBeenCalledWith({ ...settings, senderName: "Canonical name", senderTitle: "Canonical title", senderEmail: "canonical@example.test", senderPhone: "+47 555", sourceCvId: 3 });
|
|
expect(mockedApi.get).not.toHaveBeenCalledWith("/auth/me");
|
|
expect(mockedApi.put).not.toHaveBeenCalled(); expect(mockedApi.post).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test("contact location copies only after selection and unselected contact values preserve destination fields", async () => {
|
|
const onChange = jest.fn();
|
|
render(wrap(<CoverLetterDocumentFields settings={settings} disabled={false} onChange={onChange} />));
|
|
fireEvent.click(screen.getByRole("button", { name: "Copy contact from CV" }));
|
|
await selectSource();
|
|
const snapshot = within(await screen.findByRole("region", { name: "Contact snapshot" }));
|
|
for (const name of [/Sender name/, /Professional title/, /Email/, /Phone/]) fireEvent.click(snapshot.getByRole("checkbox", { name }));
|
|
fireEvent.click(snapshot.getByRole("checkbox", { name: /Location/ }));
|
|
fireEvent.click(screen.getByRole("button", { name: "Use this copy" }));
|
|
expect(onChange).toHaveBeenCalledWith({ ...settings, senderLocation: "Bergen", sourceCvId: 3 });
|
|
expect(mockedApi.put).not.toHaveBeenCalled(); expect(mockedApi.post).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test("blank source contacts and choosing no fields never clear destination contacts", async () => {
|
|
const originalGet = mockedApi.get.getMockImplementation()!;
|
|
mockedApi.get.mockImplementation(((url: string) => url === "/career/profile" ? Promise.resolve({ data: { profile: { contact: { fullName: "Canonical name", email: "", phone: " " } } } }) : originalGet(url)) as any);
|
|
const onChange = jest.fn();
|
|
render(wrap(<CoverLetterDocumentFields settings={settings} disabled={false} onChange={onChange} />));
|
|
fireEvent.click(screen.getByRole("button", { name: "Copy contact from CV" }));
|
|
await selectSource();
|
|
const snapshot = within(await screen.findByRole("region", { name: "Contact snapshot" }));
|
|
expect(snapshot.getByRole("checkbox", { name: /Email/ })).toBeDisabled();
|
|
expect(snapshot.getByRole("checkbox", { name: /Phone/ })).toBeDisabled();
|
|
fireEvent.click(snapshot.getByRole("checkbox", { name: /Sender name/ }));
|
|
fireEvent.click(screen.getByRole("button", { name: "Use this copy" }));
|
|
expect(onChange).toHaveBeenCalledWith(settings);
|
|
});
|
|
|
|
test("letter language is an explicit document draft and saves independently of interface and CV language", async () => {
|
|
mockedApi.put.mockResolvedValue({ data: { ...letter, revision: 8, documentSettings: { ...settings, language: "en" } } } as any);
|
|
render(wrap(<ApplicationCoverLetterSection jobId={7} />));
|
|
const language = await screen.findByRole("combobox", { name: "Letter language" });
|
|
expect(language).toHaveTextContent(/Norsk bokmål/i);
|
|
fireEvent.mouseDown(language);
|
|
fireEvent.click(await screen.findByRole("option", { name: "English" }));
|
|
expect(mockedApi.put).not.toHaveBeenCalled();
|
|
fireEvent.click(screen.getByRole("button", { name: "Save" }));
|
|
await waitFor(() => expect(mockedApi.put).toHaveBeenCalledWith("/jobapplications/7/cover-letter", expect.objectContaining({ documentSettings: { ...settings, language: "en" }, expectedVersion: 7 })));
|
|
expect(screen.getByRole("textbox", { name: "Cover letter" })).toHaveValue(letter.text);
|
|
});
|
|
|
|
test("design copy resolves null overrides through actual theme defaults and effective CV density", async () => {
|
|
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" });
|
|
expect(onChange).not.toHaveBeenCalled();
|
|
fireEvent.click(screen.getByRole("button", { name: "Use this copy" }));
|
|
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()!;
|
|
mockedApi.get.mockImplementation(((url: string) => url === "/cv/variants/3" ? selected.promise : originalGet(url)) as any);
|
|
const onChange = jest.fn();
|
|
render(wrap(<CoverLetterDocumentFields settings={settings} disabled={false} onChange={onChange} />));
|
|
fireEvent.click(screen.getByRole("button", { name: "Copy contact from CV" }));
|
|
await selectSource();
|
|
fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
|
|
await act(async () => { selected.resolve({ data: variant }); await selected.promise; });
|
|
expect(onChange).not.toHaveBeenCalled(); expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
|
expect(mockedApi.get).not.toHaveBeenCalledWith("/career/profile");
|
|
});
|
|
|
|
test("failed copy reads are retryable and cannot apply or save a partial snapshot", async () => {
|
|
const originalGet = mockedApi.get.getMockImplementation()!;
|
|
let reads = 0;
|
|
mockedApi.get.mockImplementation(((url: string) => url === "/cv/variants/3" && ++reads === 1 ? Promise.reject({ response: { status: 503 } }) : originalGet(url)) as any);
|
|
const onChange = jest.fn();
|
|
render(wrap(<CoverLetterDocumentFields settings={settings} disabled={false} onChange={onChange} />));
|
|
fireEvent.click(screen.getByRole("button", { name: "Copy contact from CV" }));
|
|
await selectSource();
|
|
await screen.findByText("Could not load the CV snapshot. Try again.");
|
|
expect(screen.getByRole("button", { name: "Use this copy" })).toBeDisabled();
|
|
fireEvent.click(screen.getByRole("button", { name: "Retry" }));
|
|
await screen.findByRole("region", { name: "Contact snapshot" });
|
|
expect(screen.getByRole("button", { name: "Use this copy" })).toBeEnabled();
|
|
expect(onChange).not.toHaveBeenCalled(); expect(mockedApi.put).not.toHaveBeenCalled(); expect(mockedApi.post).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test("restoring a historical letter restores saved details with text after explicit dirty confirmation", async () => {
|
|
mockedApi.post.mockResolvedValue({ data: { ...letter, revision: 8, text: "Historical body", documentSettings: { ...settings, subject: "Historical subject", senderName: "Historical sender", language: "en" } } } as any);
|
|
render(wrap(<ApplicationCoverLetterSection jobId={7} />));
|
|
fireEvent.change(await screen.findByRole("textbox", { name: "Subject" }), { target: { value: "Local unsaved subject" } });
|
|
expect(screen.queryByRole("region", { name: "Letter text preview" })).not.toBeInTheDocument();
|
|
fireEvent.click(screen.getByRole("button", { name: "Restore version 1" }));
|
|
const dialog = await screen.findByRole("dialog");
|
|
fireEvent.click(within(dialog).getByRole("button", { name: "Discard and restore" }));
|
|
await waitFor(() => expect(screen.getByRole("textbox", { name: "Subject" })).toHaveValue("Historical subject"));
|
|
expect(screen.getByRole("textbox", { name: "Sender name" })).toHaveValue("Historical sender");
|
|
expect(screen.getByRole("combobox", { name: "Letter language" })).toHaveTextContent("English");
|
|
expect(screen.getByRole("textbox", { name: "Cover letter" })).toHaveValue("Historical body");
|
|
expect(mockedApi.post).toHaveBeenCalledWith("/jobapplications/7/cover-letter/versions/1/restore", { expectedVersion: 7 });
|
|
expect(mockedApi.put).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test("successful export waits for the committed local snapshot and uses its returned revision once", async () => {
|
|
const write = deferred<any>(); const pdf = deferred<any>();
|
|
mockedApi.put.mockReturnValueOnce(write.promise); mockedApi.post.mockReturnValueOnce(pdf.promise);
|
|
const create = jest.fn(() => "blob:letter-test"); const revoke = jest.fn();
|
|
const oldCreate = Object.getOwnPropertyDescriptor(URL, "createObjectURL"); const oldRevoke = Object.getOwnPropertyDescriptor(URL, "revokeObjectURL");
|
|
Object.defineProperty(URL, "createObjectURL", { configurable: true, value: create }); Object.defineProperty(URL, "revokeObjectURL", { configurable: true, value: revoke });
|
|
jest.spyOn(HTMLAnchorElement.prototype, "click").mockImplementation(() => {});
|
|
try {
|
|
render(wrap(<ApplicationCoverLetterSection jobId={7} />));
|
|
fireEvent.change(await screen.findByRole("textbox", { name: "Subject" }), { target: { value: "Durable subject" } });
|
|
const button = screen.getByRole("button", { name: "Export letter PDF" });
|
|
act(() => { fireEvent.click(button); fireEvent.click(button); });
|
|
expect(mockedApi.put).toHaveBeenCalledTimes(1); expect(mockedApi.post).not.toHaveBeenCalled();
|
|
await act(async () => { write.resolve({ data: { ...letter, revision: 9, documentSettings: { ...settings, subject: "Durable subject" } } }); await write.promise; });
|
|
await waitFor(() => expect(mockedApi.post).toHaveBeenCalledWith("/jobapplications/7/cover-letter/export-pdf?expectedVersion=9", undefined, { responseType: "blob" }));
|
|
expect(screen.getByRole("textbox", { name: "Subject" })).toBeDisabled();
|
|
expect(create).not.toHaveBeenCalled();
|
|
const blob = new Blob(["pdf"], { type: "application/pdf" });
|
|
await act(async () => { pdf.resolve({ data: blob }); await pdf.promise; });
|
|
expect(create).toHaveBeenCalledWith(blob); expect(revoke).toHaveBeenCalledWith("blob:letter-test");
|
|
expect(mockedApi.post).toHaveBeenCalledTimes(1);
|
|
} finally {
|
|
if (oldCreate) Object.defineProperty(URL, "createObjectURL", oldCreate); else Reflect.deleteProperty(URL, "createObjectURL");
|
|
if (oldRevoke) Object.defineProperty(URL, "revokeObjectURL", oldRevoke); else Reflect.deleteProperty(URL, "revokeObjectURL");
|
|
}
|
|
});
|
|
|
|
test("a late letter PDF from a previous keyed job never downloads or replaces the current draft", async () => {
|
|
const pdf = deferred<any>();
|
|
mockedApi.post.mockReturnValueOnce(pdf.promise);
|
|
const create = jest.fn();
|
|
const oldCreate = Object.getOwnPropertyDescriptor(URL, "createObjectURL");
|
|
Object.defineProperty(URL, "createObjectURL", { configurable: true, value: create });
|
|
try {
|
|
const mounted = render(wrap(<ApplicationCoverLetterSection key={7} jobId={7} />));
|
|
fireEvent.click(await screen.findByRole("button", { name: "Export letter PDF" }));
|
|
await waitFor(() => expect(mockedApi.post).toHaveBeenCalledTimes(1));
|
|
mounted.rerender(wrap(<ApplicationCoverLetterSection key={8} jobId={8} />));
|
|
fireEvent.change(await screen.findByRole("textbox", { name: "Subject" }), { target: { value: "Job B private draft" } });
|
|
await act(async () => { pdf.resolve({ data: new Blob(["Old job A PDF"]) }); await pdf.promise; });
|
|
expect(create).not.toHaveBeenCalled();
|
|
expect(screen.getByRole("textbox", { name: "Subject" })).toHaveValue("Job B private draft");
|
|
expect(mockedApi.put).not.toHaveBeenCalled();
|
|
} finally {
|
|
if (oldCreate) Object.defineProperty(URL, "createObjectURL", oldCreate); else Reflect.deleteProperty(URL, "createObjectURL");
|
|
}
|
|
});
|