Jobber/orchestrator/src/client/pages/TrackingInboxPage.test.tsx
Shaheer Sarfaraz f8b5dc2f42
In progress flow (#163)
* initial commit

* move from applied to in-progress

* KANBAN BOARD!

* backfill jobs

* backfill rejected jobs

* drag events 😋

* fix backfill bug

* UI improvements

* remove applied

* gold near offer

* team match meeting swim lane

* formatting

* Add tests for InProgressBoardPage and enhance job stage handling
2026-02-15 00:45:45 +00:00

247 lines
6.4 KiB
TypeScript

import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { MemoryRouter } from "react-router-dom";
import { beforeEach, describe, expect, it, vi } from "vitest";
import * as api from "../api";
import { TrackingInboxPage } from "./TrackingInboxPage";
vi.mock("../api", () => ({
postApplicationProviderStatus: vi.fn(),
getPostApplicationInbox: vi.fn(),
getPostApplicationRuns: vi.fn(),
getJobs: vi.fn(),
approvePostApplicationInboxItem: vi.fn(),
denyPostApplicationInboxItem: vi.fn(),
getPostApplicationRunMessages: vi.fn(),
postApplicationGmailOauthStart: vi.fn(),
postApplicationGmailOauthExchange: vi.fn(),
postApplicationProviderSync: vi.fn(),
postApplicationProviderDisconnect: vi.fn(),
}));
function makeInboxItem() {
return {
message: {
id: "msg-1",
provider: "gmail" as const,
accountKey: "default",
integrationId: null,
syncRunId: null,
externalMessageId: "ext-1",
externalThreadId: null,
fromAddress: "jobs@example.com",
fromDomain: "example.com",
senderName: "Recruiting",
subject: "Interview invite",
receivedAt: Date.now(),
snippet: "Let's schedule",
classificationLabel: "interview",
classificationConfidence: 0.95,
classificationPayload: null,
relevanceLlmScore: 95,
relevanceDecision: "relevant" as const,
matchedJobId: "job-2",
matchConfidence: 95,
stageTarget: "technical_interview" as const,
messageType: "interview" as const,
stageEventPayload: null,
processingStatus: "pending_user" as const,
decidedAt: null,
decidedBy: null,
errorCode: null,
errorMessage: null,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
},
matchedJob: {
id: "job-2",
title: "Software Engineer",
employer: "Example",
},
};
}
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(api.postApplicationProviderStatus).mockResolvedValue({
provider: "gmail",
action: "status",
accountKey: "default",
status: {
provider: "gmail",
accountKey: "default",
connected: true,
integration: {
id: "int-1",
provider: "gmail",
accountKey: "default",
displayName: null,
status: "connected",
credentials: null,
lastConnectedAt: null,
lastSyncedAt: null,
lastError: null,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
},
},
});
vi.mocked(api.getPostApplicationInbox).mockResolvedValue({
items: [makeInboxItem()],
total: 1,
});
vi.mocked(api.getPostApplicationRuns).mockResolvedValue({
runs: [],
total: 0,
});
vi.mocked(api.getJobs).mockResolvedValue({
jobs: [
{
id: "job-1",
source: "manual",
title: "Software Engineer I",
employer: "Example",
jobUrl: "https://example.com/job-1",
applicationLink: null,
datePosted: null,
deadline: null,
salary: null,
location: null,
status: "applied",
suitabilityScore: null,
sponsorMatchScore: null,
jobType: null,
jobFunction: null,
salaryMinAmount: null,
salaryMaxAmount: null,
salaryCurrency: null,
discoveredAt: new Date().toISOString(),
appliedAt: null,
updatedAt: new Date().toISOString(),
},
{
id: "job-2",
source: "manual",
title: "Software Engineer II",
employer: "Example",
jobUrl: "https://example.com/job-2",
applicationLink: null,
datePosted: null,
deadline: null,
salary: null,
location: null,
status: "applied",
suitabilityScore: null,
sponsorMatchScore: null,
jobType: null,
jobFunction: null,
salaryMinAmount: null,
salaryMaxAmount: null,
salaryCurrency: null,
discoveredAt: new Date().toISOString(),
appliedAt: null,
updatedAt: new Date().toISOString(),
},
],
total: 2,
byStatus: {
discovered: 0,
processing: 0,
ready: 0,
applied: 2,
skipped: 0,
expired: 0,
},
revision: "r1",
} as Awaited<ReturnType<typeof api.getJobs>>);
vi.mocked(api.approvePostApplicationInboxItem).mockResolvedValue({
message: makeInboxItem().message,
stageEventId: "evt-1",
});
vi.mocked(api.denyPostApplicationInboxItem).mockResolvedValue({
message: {
...makeInboxItem().message,
processingStatus: "ignored",
matchedJobId: null,
},
});
vi.mocked(api.getPostApplicationRunMessages).mockResolvedValue({
run: {
id: "run-1",
provider: "gmail",
accountKey: "default",
integrationId: null,
status: "completed",
startedAt: Date.now(),
completedAt: Date.now(),
messagesDiscovered: 1,
messagesRelevant: 1,
messagesClassified: 1,
messagesMatched: 1,
messagesApproved: 0,
messagesDenied: 0,
messagesErrored: 0,
errorCode: null,
errorMessage: null,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
},
items: [makeInboxItem()],
total: 1,
});
});
describe("TrackingInboxPage", () => {
it("renders pending messages", async () => {
render(
<MemoryRouter>
<TrackingInboxPage />
</MemoryRouter>,
);
await waitFor(() => {
expect(screen.getByText("Interview invite")).toBeInTheDocument();
});
});
it("submits approve action", async () => {
render(
<MemoryRouter>
<TrackingInboxPage />
</MemoryRouter>,
);
await waitFor(() => {
expect(screen.getByText("Interview invite")).toBeInTheDocument();
});
fireEvent.click(
screen.getByRole("button", { name: "Confirm email-job match" }),
);
await waitFor(() => {
expect(api.approvePostApplicationInboxItem).toHaveBeenCalled();
});
expect(api.approvePostApplicationInboxItem).toHaveBeenCalledWith(
expect.objectContaining({
jobId: "job-2",
}),
);
});
it("loads dropdown jobs excluding discovered status", async () => {
render(
<MemoryRouter>
<TrackingInboxPage />
</MemoryRouter>,
);
await waitFor(() => {
expect(api.getJobs).toHaveBeenCalledWith({
statuses: ["applied", "in_progress"],
view: "list",
});
});
});
});