fix: repair tests and formatting that Gitea CI never checked
- mock the job-listing probe in discover-jobs tests (they hit the real network via example.com fixtures) - update JobDetailPanel skip test to the runJobAction API introduced by the undo feature - fix jobActionUndo test action type and apply Biome formatting that drifted while Gitea CI had no lint lane
This commit is contained in:
parent
285b094bce
commit
a16fa88a2d
@ -6,7 +6,10 @@ import type { Job } from "@shared/types.js";
|
|||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { toastMoveToReadyUndo, toastWithUndo } from "@/client/lib/jobActionUndo";
|
import {
|
||||||
|
toastMoveToReadyUndo,
|
||||||
|
toastWithUndo,
|
||||||
|
} from "@/client/lib/jobActionUndo";
|
||||||
import { trackProductEvent } from "@/lib/analytics";
|
import { trackProductEvent } from "@/lib/analytics";
|
||||||
import { JobDetailsEditDrawer } from "../JobDetailsEditDrawer";
|
import { JobDetailsEditDrawer } from "../JobDetailsEditDrawer";
|
||||||
import { DecideMode } from "./DecideMode";
|
import { DecideMode } from "./DecideMode";
|
||||||
|
|||||||
@ -14,10 +14,7 @@ vi.mock("@client/api", () => ({
|
|||||||
|
|
||||||
import * as api from "@client/api";
|
import * as api from "@client/api";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import {
|
import { restoresFromSkipResults, toastWithUndo } from "./jobActionUndo";
|
||||||
restoresFromSkipResults,
|
|
||||||
toastWithUndo,
|
|
||||||
} from "./jobActionUndo";
|
|
||||||
|
|
||||||
describe("restoresFromSkipResults", () => {
|
describe("restoresFromSkipResults", () => {
|
||||||
it("includes primary and also-skipped restores", () => {
|
it("includes primary and also-skipped restores", () => {
|
||||||
@ -49,7 +46,7 @@ describe("toastWithUndo", () => {
|
|||||||
expect(toast.message).toHaveBeenCalled();
|
expect(toast.message).toHaveBeenCalled();
|
||||||
const call = vi.mocked(toast.message).mock.calls[0];
|
const call = vi.mocked(toast.message).mock.calls[0];
|
||||||
const opts = call?.[1] as {
|
const opts = call?.[1] as {
|
||||||
action?: { onClick: () => void };
|
action?: { label?: string; onClick: () => void };
|
||||||
};
|
};
|
||||||
expect(opts?.action?.label ?? opts?.action).toBeTruthy();
|
expect(opts?.action?.label ?? opts?.action).toBeTruthy();
|
||||||
opts?.action?.onClick();
|
opts?.action?.onClick();
|
||||||
|
|||||||
@ -1,5 +1,9 @@
|
|||||||
import * as api from "@client/api";
|
import * as api from "@client/api";
|
||||||
import type { JobActionResult, JobStatus, JobStatusRestore } from "@shared/types";
|
import type {
|
||||||
|
JobActionResult,
|
||||||
|
JobStatus,
|
||||||
|
JobStatusRestore,
|
||||||
|
} from "@shared/types";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
const DEFAULT_UNDO_MS = 8_000;
|
const DEFAULT_UNDO_MS = 8_000;
|
||||||
|
|||||||
@ -27,7 +27,6 @@ import {
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { toastAppliedUndo, toastWithUndo } from "@/client/lib/jobActionUndo";
|
|
||||||
import { invalidateJobData } from "@/client/hooks/queries/invalidate";
|
import { invalidateJobData } from "@/client/hooks/queries/invalidate";
|
||||||
import {
|
import {
|
||||||
useCheckSponsorMutation,
|
useCheckSponsorMutation,
|
||||||
@ -38,6 +37,7 @@ import {
|
|||||||
useUpdateJobMutation,
|
useUpdateJobMutation,
|
||||||
} from "@/client/hooks/queries/useJobMutations";
|
} from "@/client/hooks/queries/useJobMutations";
|
||||||
import { useQueryErrorToast } from "@/client/hooks/useQueryErrorToast";
|
import { useQueryErrorToast } from "@/client/hooks/useQueryErrorToast";
|
||||||
|
import { toastAppliedUndo, toastWithUndo } from "@/client/lib/jobActionUndo";
|
||||||
import { queryKeys } from "@/client/lib/queryKeys";
|
import { queryKeys } from "@/client/lib/queryKeys";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
|||||||
@ -34,23 +34,25 @@ vi.mock("../api", () => ({
|
|||||||
}),
|
}),
|
||||||
getProfile: vi.fn().mockResolvedValue({ personName: "Test User" }),
|
getProfile: vi.fn().mockResolvedValue({ personName: "Test User" }),
|
||||||
skipJob: vi.fn().mockResolvedValue({}),
|
skipJob: vi.fn().mockResolvedValue({}),
|
||||||
runJobAction: vi.fn().mockImplementation(async (input: { action: string; jobIds: string[] }) => {
|
runJobAction: vi
|
||||||
const jobId = input.jobIds[0] ?? "job-1";
|
.fn()
|
||||||
return {
|
.mockImplementation(async (input: { action: string; jobIds: string[] }) => {
|
||||||
action: input.action,
|
const jobId = input.jobIds[0] ?? "job-1";
|
||||||
requested: 1,
|
return {
|
||||||
succeeded: 1,
|
action: input.action,
|
||||||
failed: 0,
|
requested: 1,
|
||||||
results: [
|
succeeded: 1,
|
||||||
{
|
failed: 0,
|
||||||
jobId,
|
results: [
|
||||||
ok: true,
|
{
|
||||||
job: { id: jobId, status: "skipped" },
|
jobId,
|
||||||
undo: { previousStatus: "ready", alsoSkipped: [] },
|
ok: true,
|
||||||
},
|
job: { id: jobId, status: "skipped" },
|
||||||
],
|
undo: { previousStatus: "ready", alsoSkipped: [] },
|
||||||
};
|
},
|
||||||
}),
|
],
|
||||||
|
};
|
||||||
|
}),
|
||||||
restoreJobStatuses: vi.fn().mockResolvedValue({ restored: 1, jobs: [] }),
|
restoreJobStatuses: vi.fn().mockResolvedValue({ restored: 1, jobs: [] }),
|
||||||
markAsApplied: vi.fn().mockResolvedValue({}),
|
markAsApplied: vi.fn().mockResolvedValue({}),
|
||||||
processJob: vi.fn().mockResolvedValue({}),
|
processJob: vi.fn().mockResolvedValue({}),
|
||||||
|
|||||||
@ -136,7 +136,7 @@ vi.mock("@client/api", () => ({
|
|||||||
processJob: vi.fn(),
|
processJob: vi.fn(),
|
||||||
generateJobPdf: vi.fn(),
|
generateJobPdf: vi.fn(),
|
||||||
markAsApplied: vi.fn(),
|
markAsApplied: vi.fn(),
|
||||||
skipJob: vi.fn(),
|
runJobAction: vi.fn(),
|
||||||
getProfile: vi.fn().mockResolvedValue({}),
|
getProfile: vi.fn().mockResolvedValue({}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -346,7 +346,16 @@ describe("JobDetailPanel", () => {
|
|||||||
|
|
||||||
it("skips a job from the menu", async () => {
|
it("skips a job from the menu", async () => {
|
||||||
const onJobUpdated = vi.fn().mockResolvedValue(undefined);
|
const onJobUpdated = vi.fn().mockResolvedValue(undefined);
|
||||||
vi.mocked(api.skipJob).mockResolvedValue(undefined as any);
|
vi.mocked(api.runJobAction).mockResolvedValue({
|
||||||
|
results: [
|
||||||
|
{
|
||||||
|
jobId: "job-1",
|
||||||
|
ok: true,
|
||||||
|
job: { id: "job-1", status: "skipped" },
|
||||||
|
undo: { previousStatus: "ready" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as any);
|
||||||
|
|
||||||
await renderJobDetailPanel({
|
await renderJobDetailPanel({
|
||||||
activeTab: "all",
|
activeTab: "all",
|
||||||
@ -362,7 +371,12 @@ describe("JobDetailPanel", () => {
|
|||||||
const skipItem = await screen.findByRole("menuitem", { name: /skip job/i });
|
const skipItem = await screen.findByRole("menuitem", { name: /skip job/i });
|
||||||
fireEvent.click(skipItem);
|
fireEvent.click(skipItem);
|
||||||
|
|
||||||
await waitFor(() => expect(api.skipJob).toHaveBeenCalledWith("job-1"));
|
await waitFor(() =>
|
||||||
|
expect(api.runJobAction).toHaveBeenCalledWith({
|
||||||
|
action: "skip",
|
||||||
|
jobIds: ["job-1"],
|
||||||
|
}),
|
||||||
|
);
|
||||||
expect(onJobUpdated).toHaveBeenCalled();
|
expect(onJobUpdated).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -33,8 +33,8 @@ import {
|
|||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { toastAppliedUndo, toastWithUndo } from "@/client/lib/jobActionUndo";
|
|
||||||
import { JobDescriptionMarkdown } from "@/client/components/JobDescriptionMarkdown";
|
import { JobDescriptionMarkdown } from "@/client/components/JobDescriptionMarkdown";
|
||||||
|
import { toastAppliedUndo, toastWithUndo } from "@/client/lib/jobActionUndo";
|
||||||
import { getRenderableJobDescription } from "@/client/lib/jobDescription";
|
import { getRenderableJobDescription } from "@/client/lib/jobDescription";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@ -324,7 +324,12 @@ export const JobDetailPanel: React.FC<JobDetailPanelProps> = ({
|
|||||||
});
|
});
|
||||||
toastWithUndo({
|
toastWithUndo({
|
||||||
title: "Moved to in progress",
|
title: "Moved to in progress",
|
||||||
restores: [{ jobId: selectedJob.id, status: fromStatus === "applied" ? "applied" : "applied" }],
|
restores: [
|
||||||
|
{
|
||||||
|
jobId: selectedJob.id,
|
||||||
|
status: fromStatus === "applied" ? "applied" : "applied",
|
||||||
|
},
|
||||||
|
],
|
||||||
variant: "success",
|
variant: "success",
|
||||||
onRestored: onJobUpdated,
|
onRestored: onJobUpdated,
|
||||||
successMessage: "Reverted to Applied",
|
successMessage: "Reverted to Applied",
|
||||||
|
|||||||
@ -7,7 +7,10 @@ import {
|
|||||||
} from "@shared/types.js";
|
} from "@shared/types.js";
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { restoresFromSkipResults, toastWithUndo } from "@/client/lib/jobActionUndo";
|
import {
|
||||||
|
restoresFromSkipResults,
|
||||||
|
toastWithUndo,
|
||||||
|
} from "@/client/lib/jobActionUndo";
|
||||||
import { trackProductEvent } from "@/lib/analytics";
|
import { trackProductEvent } from "@/lib/analytics";
|
||||||
import type { FilterTab } from "./constants";
|
import type { FilterTab } from "./constants";
|
||||||
import { JobActionProgressToast } from "./JobActionProgressToast";
|
import { JobActionProgressToast } from "./JobActionProgressToast";
|
||||||
|
|||||||
@ -9,7 +9,11 @@ import { SHORTCUTS } from "@client/lib/shortcut-map";
|
|||||||
import type { JobAction, JobListItem } from "@shared/types.js";
|
import type { JobAction, JobListItem } from "@shared/types.js";
|
||||||
import { useCallback, useRef } from "react";
|
import { useCallback, useRef } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { toastAppliedUndo, toastMoveToReadyUndo, toastWithUndo } from "@/client/lib/jobActionUndo";
|
import {
|
||||||
|
toastAppliedUndo,
|
||||||
|
toastMoveToReadyUndo,
|
||||||
|
toastWithUndo,
|
||||||
|
} from "@/client/lib/jobActionUndo";
|
||||||
import { safeFilenamePart } from "@/lib/utils";
|
import { safeFilenamePart } from "@/lib/utils";
|
||||||
import type { FilterTab } from "./constants";
|
import type { FilterTab } from "./constants";
|
||||||
import { tabs } from "./constants";
|
import { tabs } from "./constants";
|
||||||
|
|||||||
@ -1004,9 +1004,12 @@ jobsRouter.post("/restore-status", async (req: Request, res: Response) => {
|
|||||||
try {
|
try {
|
||||||
const parsed = restoreStatusSchema.safeParse(req.body);
|
const parsed = restoreStatusSchema.safeParse(req.body);
|
||||||
if (!parsed.success) {
|
if (!parsed.success) {
|
||||||
return fail(res, badRequest("Invalid restore payload", {
|
return fail(
|
||||||
issues: parsed.error.issues,
|
res,
|
||||||
}));
|
badRequest("Invalid restore payload", {
|
||||||
|
issues: parsed.error.issues,
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const restored: Job[] = [];
|
const restored: Job[] = [];
|
||||||
@ -1470,7 +1473,6 @@ jobsRouter.post("/:id/generate-pdf", async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /api/jobs/:id/apply - Mark a job as applied
|
* POST /api/jobs/:id/apply - Mark a job as applied
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -23,6 +23,14 @@ vi.mock("@server/extractors/registry", () => ({
|
|||||||
getExtractorRegistry: vi.fn(),
|
getExtractorRegistry: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// Keep tests hermetic: the real probe fetches listing URLs over the network.
|
||||||
|
vi.mock("@shared/job-listing-probe.js", () => ({
|
||||||
|
filterExpiredJobListings: vi.fn(async <T>(jobs: readonly T[]) => ({
|
||||||
|
kept: [...jobs],
|
||||||
|
dropped: 0,
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
const baseConfig: PipelineConfig = {
|
const baseConfig: PipelineConfig = {
|
||||||
topN: 10,
|
topN: 10,
|
||||||
minSuitabilityScore: 50,
|
minSuitabilityScore: 50,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user