* commit at some point in the middle, WIP * formatting * ci passing * comments * handle no jobid case * better error handling * comments * Update orchestrator/src/client/hooks/queries/useJobMutations.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update orchestrator/src/client/hooks/queries/useSettingsMutation.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * better types * formatter * tracking inbox page * in progress page * tracer links page * invalidate harder * ensure tracer links docs show --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
190 lines
5.7 KiB
TypeScript
190 lines
5.7 KiB
TypeScript
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<typeof renderWithQueryClient>[0]) =>
|
|
renderWithQueryClient(ui);
|
|
|
|
vi.mock("@/components/ui/sheet", () => ({
|
|
Sheet: ({ open, children }: { open: boolean; children: React.ReactNode }) =>
|
|
open ? <div>{children}</div> : null,
|
|
SheetContent: ({ children }: { children: React.ReactNode }) => (
|
|
<div>{children}</div>
|
|
),
|
|
SheetHeader: ({ children }: { children: React.ReactNode }) => (
|
|
<div>{children}</div>
|
|
),
|
|
SheetTitle: ({ children }: { children: React.ReactNode }) => (
|
|
<h2>{children}</h2>
|
|
),
|
|
SheetDescription: ({ children }: { children: React.ReactNode }) => (
|
|
<p>{children}</p>
|
|
),
|
|
}));
|
|
|
|
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(
|
|
<JobDetailsEditDrawer
|
|
open
|
|
onOpenChange={onOpenChange}
|
|
job={createJob()}
|
|
onJobUpdated={onJobUpdated}
|
|
/>,
|
|
);
|
|
|
|
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(
|
|
<JobDetailsEditDrawer
|
|
open
|
|
onOpenChange={onOpenChange}
|
|
job={createJob()}
|
|
onJobUpdated={onJobUpdated}
|
|
/>,
|
|
);
|
|
|
|
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(
|
|
<JobDetailsEditDrawer
|
|
open
|
|
onOpenChange={onOpenChange}
|
|
job={createJob()}
|
|
onJobUpdated={onJobUpdated}
|
|
/>,
|
|
);
|
|
|
|
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(
|
|
<JobDetailsEditDrawer
|
|
open
|
|
onOpenChange={onOpenChange}
|
|
job={createJob({ tracerLinksEnabled: false })}
|
|
onJobUpdated={onJobUpdated}
|
|
/>,
|
|
);
|
|
|
|
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,
|
|
}),
|
|
),
|
|
);
|
|
});
|
|
});
|