Jobber/orchestrator/src/client/components/ReadyPanel.test.tsx
2026-01-25 13:14:59 +00:00

151 lines
3.7 KiB
TypeScript

import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import type React from "react";
import { toast } from "sonner";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { Job } from "../../shared/types";
import * as api from "../api";
import { ReadyPanel } from "./ReadyPanel";
vi.mock("@/components/ui/dropdown-menu", () => {
return {
DropdownMenu: ({ children }: { children: React.ReactNode }) => (
<div>{children}</div>
),
DropdownMenuTrigger: ({ children }: { children: React.ReactNode }) => (
<>{children}</>
),
DropdownMenuContent: ({ children }: { children: React.ReactNode }) => (
<div role="menu">{children}</div>
),
DropdownMenuItem: ({
children,
onSelect,
...props
}: {
children: React.ReactNode;
onSelect?: () => void;
}) => (
<button
type="button"
role="menuitem"
onClick={() => onSelect?.()}
{...props}
>
{children}
</button>
),
DropdownMenuSeparator: () => <hr />,
};
});
vi.mock("../hooks/useProfile", () => ({
useProfile: () => ({ personName: "Test" }),
}));
vi.mock("../hooks/useSettings", () => ({
useSettings: () => ({ showSponsorInfo: false }),
}));
vi.mock("../api", () => ({
rescoreJob: vi.fn(),
getResumeProjectsCatalog: vi.fn().mockResolvedValue([]),
markAsApplied: vi.fn(),
generateJobPdf: vi.fn(),
checkSponsor: vi.fn(),
skipJob: vi.fn(),
updateJob: vi.fn(),
}));
vi.mock("sonner", () => ({
toast: {
success: vi.fn(),
error: vi.fn(),
message: vi.fn(),
},
}));
const createJob = (overrides: Partial<Job> = {}): Job => ({
id: "job-1",
source: "linkedin",
sourceJobId: null,
jobUrlDirect: null,
datePosted: null,
title: "Backend Engineer",
employer: "Acme",
employerUrl: null,
jobUrl: "https://example.com/job",
applicationLink: "https://example.com/apply",
disciplines: null,
deadline: "2025-02-01",
salary: "GBP 50k",
location: "London",
degreeRequired: null,
starting: null,
jobDescription: "Build APIs",
status: "ready",
suitabilityScore: 82,
suitabilityReason: "Strong fit",
tailoredSummary: null,
tailoredHeadline: null,
tailoredSkills: null,
selectedProjectIds: null,
pdfPath: null,
notionPageId: null,
sponsorMatchScore: null,
sponsorMatchNames: null,
jobType: null,
salarySource: null,
salaryInterval: null,
salaryMinAmount: null,
salaryMaxAmount: null,
salaryCurrency: null,
isRemote: null,
jobLevel: null,
jobFunction: null,
listingType: null,
emails: null,
companyIndustry: null,
companyLogo: null,
companyUrlDirect: null,
companyAddresses: null,
companyNumEmployees: null,
companyRevenue: null,
companyDescription: null,
skills: null,
experienceRange: null,
companyRating: null,
companyReviewsCount: null,
vacancyCount: null,
workFromHomeType: null,
discoveredAt: "2025-01-01T00:00:00Z",
processedAt: null,
appliedAt: null,
createdAt: "2025-01-01T00:00:00Z",
updatedAt: "2025-01-02T00:00:00Z",
...overrides,
});
describe("ReadyPanel", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("re-runs the fit assessment from the menu", async () => {
const onJobUpdated = vi.fn().mockResolvedValue(undefined);
const job = createJob();
vi.mocked(api.rescoreJob).mockResolvedValue(job as Job);
render(
<ReadyPanel job={job} onJobUpdated={onJobUpdated} onJobMoved={vi.fn()} />,
);
fireEvent.click(
screen.getByRole("menuitem", { name: /recalculate match/i }),
);
await waitFor(() => expect(api.rescoreJob).toHaveBeenCalledWith("job-1"));
expect(onJobUpdated).toHaveBeenCalled();
expect(toast.success).toHaveBeenCalledWith("Match recalculated");
});
});