127 lines
3.3 KiB
TypeScript
127 lines
3.3 KiB
TypeScript
import type { Job } from "@shared/types.js";
|
|
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import * as api from "../api";
|
|
import { TailoringEditor } from "./TailoringEditor";
|
|
|
|
vi.mock("../api", () => ({
|
|
getResumeProjectsCatalog: vi.fn().mockResolvedValue([]),
|
|
updateJob: vi.fn().mockResolvedValue({}),
|
|
summarizeJob: vi.fn(),
|
|
generateJobPdf: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("sonner", () => ({
|
|
toast: {
|
|
success: vi.fn(),
|
|
error: vi.fn(),
|
|
},
|
|
}));
|
|
|
|
const createJob = (overrides: Partial<Job> = {}): Job =>
|
|
({
|
|
id: "job-1",
|
|
tailoredSummary: "Saved summary",
|
|
jobDescription: "Saved description",
|
|
selectedProjectIds: "p1",
|
|
...overrides,
|
|
}) as Job;
|
|
|
|
describe("TailoringEditor", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it("does not rehydrate local edits from same-job prop updates", async () => {
|
|
const { rerender } = render(
|
|
<TailoringEditor job={createJob()} onUpdate={vi.fn()} />,
|
|
);
|
|
await waitFor(() =>
|
|
expect(api.getResumeProjectsCatalog).toHaveBeenCalled(),
|
|
);
|
|
|
|
fireEvent.change(screen.getByLabelText("Tailored Summary"), {
|
|
target: { value: "Local draft" },
|
|
});
|
|
|
|
rerender(
|
|
<TailoringEditor
|
|
job={createJob({ tailoredSummary: "Older server value" })}
|
|
onUpdate={vi.fn()}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.getByLabelText("Tailored Summary")).toHaveValue("Local draft");
|
|
});
|
|
|
|
it("resets local state when job id changes", async () => {
|
|
const { rerender } = render(
|
|
<TailoringEditor job={createJob()} onUpdate={vi.fn()} />,
|
|
);
|
|
await waitFor(() =>
|
|
expect(api.getResumeProjectsCatalog).toHaveBeenCalled(),
|
|
);
|
|
|
|
fireEvent.change(screen.getByLabelText("Tailored Summary"), {
|
|
target: { value: "Local draft" },
|
|
});
|
|
|
|
rerender(
|
|
<TailoringEditor
|
|
job={createJob({
|
|
id: "job-2",
|
|
tailoredSummary: "New job summary",
|
|
jobDescription: "New job description",
|
|
selectedProjectIds: "",
|
|
})}
|
|
onUpdate={vi.fn()}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.getByLabelText("Tailored Summary")).toHaveValue("New job summary");
|
|
});
|
|
|
|
it("emits dirty state changes", async () => {
|
|
const onDirtyChange = vi.fn();
|
|
render(
|
|
<TailoringEditor
|
|
job={createJob()}
|
|
onUpdate={vi.fn()}
|
|
onDirtyChange={onDirtyChange}
|
|
/>,
|
|
);
|
|
await waitFor(() =>
|
|
expect(api.getResumeProjectsCatalog).toHaveBeenCalled(),
|
|
);
|
|
|
|
fireEvent.change(screen.getByLabelText("Tailored Summary"), {
|
|
target: { value: "Local draft" },
|
|
});
|
|
|
|
expect(onDirtyChange).toHaveBeenCalledWith(true);
|
|
});
|
|
|
|
it("does not sync same-job props while summary field is focused", async () => {
|
|
const { rerender } = render(
|
|
<TailoringEditor job={createJob()} onUpdate={vi.fn()} />,
|
|
);
|
|
await waitFor(() =>
|
|
expect(api.getResumeProjectsCatalog).toHaveBeenCalled(),
|
|
);
|
|
|
|
const summary = screen.getByLabelText("Tailored Summary");
|
|
fireEvent.focus(summary);
|
|
|
|
rerender(
|
|
<TailoringEditor
|
|
job={createJob({ tailoredSummary: "Incoming from poll" })}
|
|
onUpdate={vi.fn()}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.getByLabelText("Tailored Summary")).toHaveValue(
|
|
"Saved summary",
|
|
);
|
|
});
|
|
});
|