Jobber/orchestrator/src/client/components/TailoringEditor.test.tsx
Shaheer Sarfaraz 0e6f0bcf88
Inputs getting unset fix (#91)
* codex 5.3 one shot

* prevent cursor changing position
2026-02-05 19:43:10 +00:00

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",
);
});
});