import { createJob } from "@shared/testing/factories.js"; import type { Job } from "@shared/types.js"; import { fireEvent, screen, waitFor } from "@testing-library/react"; import type React from "react"; import { beforeEach, describe, expect, it, vi } from "vitest"; import * as api from "../api"; import { _resetTracerReadinessCache } from "../hooks/useTracerReadiness"; import { renderWithQueryClient } from "../test/renderWithQueryClient"; import { JobDetailsEditDrawer } from "./JobDetailsEditDrawer"; const render = (ui: Parameters[0]) => renderWithQueryClient(ui); vi.mock("@/components/ui/sheet", () => ({ Sheet: ({ open, children }: { open: boolean; children: React.ReactNode }) => open ?
{children}
: null, SheetContent: ({ children }: { children: React.ReactNode }) => (
{children}
), SheetHeader: ({ children }: { children: React.ReactNode }) => (
{children}
), SheetTitle: ({ children }: { children: React.ReactNode }) => (

{children}

), SheetDescription: ({ children }: { children: React.ReactNode }) => (

{children}

), })); vi.mock("../api", () => ({ updateJob: vi.fn(), checkSponsor: vi.fn(), rescoreJob: vi.fn(), getTracerReadiness: vi.fn(), })); vi.mock("sonner", () => ({ toast: { success: vi.fn(), error: vi.fn(), }, })); describe("JobDetailsEditDrawer", () => { beforeEach(() => { vi.clearAllMocks(); _resetTracerReadinessCache(); vi.mocked(api.getTracerReadiness).mockResolvedValue({ status: "ready", canEnable: true, publicBaseUrl: "https://my-jobops.example.com", healthUrl: "https://my-jobops.example.com/health", checkedAt: Date.now(), lastSuccessAt: Date.now(), reason: null, }); }); it("saves details and reruns sponsor check when employer changes", async () => { const onJobUpdated = vi.fn().mockResolvedValue(undefined); const onOpenChange = vi.fn(); vi.mocked(api.updateJob).mockResolvedValue({} as Job); vi.mocked(api.checkSponsor).mockResolvedValue({} as Job); render( , ); fireEvent.change(screen.getByLabelText("Employer *"), { target: { value: "NewCo" }, }); fireEvent.click(screen.getByRole("button", { name: /save details/i })); await waitFor(() => expect(api.updateJob).toHaveBeenCalledWith( "job-1", expect.objectContaining({ employer: "NewCo", title: "Backend Engineer", }), ), ); expect(api.checkSponsor).toHaveBeenCalledWith("job-1"); expect(onJobUpdated).toHaveBeenCalledTimes(1); expect(onOpenChange).toHaveBeenCalledWith(false); }); it("validates required fields before saving", async () => { const onJobUpdated = vi.fn().mockResolvedValue(undefined); const onOpenChange = vi.fn(); render( , ); fireEvent.change(screen.getByLabelText("Title *"), { target: { value: " " }, }); fireEvent.click(screen.getByRole("button", { name: /save details/i })); expect(await screen.findByText("Title is required.")).toBeInTheDocument(); expect(api.updateJob).not.toHaveBeenCalled(); expect(onJobUpdated).not.toHaveBeenCalled(); }); it("offers a rescore action after successful save", async () => { const onJobUpdated = vi.fn().mockResolvedValue(undefined); const onOpenChange = vi.fn(); const { toast } = await import("sonner"); vi.mocked(api.updateJob).mockResolvedValue({} as Job); vi.mocked(api.rescoreJob).mockResolvedValue({} as Job); render( , ); fireEvent.change(screen.getByLabelText("Salary"), { target: { value: "GBP 90k" }, }); fireEvent.click(screen.getByRole("button", { name: /save details/i })); await waitFor(() => expect(vi.mocked(toast.success)).toHaveBeenCalledWith( "Job details updated", expect.any(Object), ), ); const successCalls = vi.mocked(toast.success).mock.calls; const [, payload] = successCalls.find((call) => call[0] === "Job details updated") ?? []; expect(payload).toBeTruthy(); (payload as { action?: { onClick?: () => void } }).action?.onClick?.(); await waitFor(() => expect(api.rescoreJob).toHaveBeenCalledWith("job-1")); expect(onJobUpdated).toHaveBeenCalledTimes(2); }); it("persists tracer-links toggle with job updates", async () => { const onJobUpdated = vi.fn().mockResolvedValue(undefined); const onOpenChange = vi.fn(); vi.mocked(api.updateJob).mockResolvedValue({} as Job); render( , ); await waitFor(() => expect(api.getTracerReadiness).toHaveBeenCalled()); const tracerToggle = await screen.findByRole("checkbox", { name: "Enable tracer links for this job", }); await waitFor(() => expect(tracerToggle).toBeEnabled()); fireEvent.click(tracerToggle); fireEvent.click(screen.getByRole("button", { name: /save details/i })); await waitFor(() => expect(api.updateJob).toHaveBeenCalledWith( "job-1", expect.objectContaining({ tracerLinksEnabled: true, }), ), ); }); });