Jobber/orchestrator/src/client/pages/InProgressBoardPage.test.tsx
Shaheer Sarfaraz 3640abef2d
Migration to tanstack query (#199)
* 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>
2026-02-19 23:04:47 +00:00

168 lines
4.1 KiB
TypeScript

import type { JobListItem, StageEvent } from "@shared/types";
import { fireEvent, screen, waitFor } from "@testing-library/react";
import { MemoryRouter } from "react-router-dom";
import { toast } from "sonner";
import { beforeEach, describe, expect, it, vi } from "vitest";
import * as api from "../api";
import { renderWithQueryClient } from "../test/renderWithQueryClient";
import { InProgressBoardPage } from "./InProgressBoardPage";
const render = (ui: Parameters<typeof renderWithQueryClient>[0]) =>
renderWithQueryClient(ui);
vi.mock("../api", () => ({
getJobs: vi.fn(),
getJobStageEvents: vi.fn(),
transitionJobStage: vi.fn(),
}));
vi.mock("sonner", () => ({
toast: {
success: vi.fn(),
error: vi.fn(),
},
}));
const makeJob = (overrides: Partial<JobListItem>): JobListItem => ({
id: "job-1",
source: "manual",
title: "Backend Engineer",
employer: "Acme",
jobUrl: "https://example.com/jobs/1",
applicationLink: null,
datePosted: null,
deadline: null,
salary: null,
location: null,
status: "in_progress",
outcome: null,
closedAt: null,
suitabilityScore: null,
sponsorMatchScore: null,
jobType: null,
jobFunction: null,
salaryMinAmount: null,
salaryMaxAmount: null,
salaryCurrency: null,
discoveredAt: "2026-01-01T00:00:00.000Z",
appliedAt: null,
updatedAt: "2026-01-01T00:00:00.000Z",
...overrides,
});
const makeEvent = (overrides: Partial<StageEvent>): StageEvent => ({
id: "evt-1",
applicationId: "job-1",
title: "Recruiter Screen",
groupId: null,
fromStage: "applied",
toStage: "recruiter_screen",
occurredAt: 1_700_000_000,
metadata: null,
outcome: null,
...overrides,
});
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(api.getJobs).mockResolvedValue({
jobs: [makeJob({})],
total: 1,
byStatus: {
discovered: 0,
processing: 0,
ready: 0,
applied: 0,
in_progress: 1,
skipped: 0,
expired: 0,
},
revision: "r1",
} as Awaited<ReturnType<typeof api.getJobs>>);
vi.mocked(api.getJobStageEvents).mockResolvedValue([makeEvent({})]);
vi.mocked(api.transitionJobStage).mockResolvedValue(
makeEvent({ toStage: "offer", title: "Offer" }),
);
});
describe("InProgressBoardPage", () => {
it("loads in-progress jobs and renders cards", async () => {
render(
<MemoryRouter>
<InProgressBoardPage />
</MemoryRouter>,
);
await waitFor(() => {
expect(api.getJobs).toHaveBeenCalledWith({
statuses: ["in_progress"],
view: "list",
});
});
expect(await screen.findByText("Backend Engineer")).toBeInTheDocument();
});
it("shows cards even when no stage events are present", async () => {
vi.mocked(api.getJobStageEvents).mockResolvedValue([]);
render(
<MemoryRouter>
<InProgressBoardPage />
</MemoryRouter>,
);
expect(await screen.findByText("Backend Engineer")).toBeInTheDocument();
});
it("transitions a job stage when dropped into another lane", async () => {
render(
<MemoryRouter>
<InProgressBoardPage />
</MemoryRouter>,
);
const card = await screen.findByRole("link", { name: /Backend Engineer/i });
const offerHeader = await screen.findByText("Offer");
const offerLane = offerHeader.closest("section");
if (!offerLane) {
throw new Error("Offer lane section not found");
}
fireEvent.dragStart(card, {
dataTransfer: {
effectAllowed: "move",
},
});
fireEvent.dragOver(offerLane);
fireEvent.drop(offerLane);
await waitFor(() => {
expect(api.transitionJobStage).toHaveBeenCalledWith("job-1", {
toStage: "offer",
metadata: {
actor: "user",
eventType: "status_update",
eventLabel: "Moved to Offer",
},
});
});
});
it("surfaces load errors", async () => {
vi.mocked(api.getJobs).mockRejectedValue(new Error("Failed to load board"));
render(
<MemoryRouter>
<InProgressBoardPage />
</MemoryRouter>,
);
await waitFor(() => {
expect(toast.error).toHaveBeenCalledWith("Failed to load board");
});
});
});