fix: repair tests and formatting that Gitea CI never checked
All checks were successful
CI / skip-ci-check (pull_request) Successful in 24s
CI / docker-ci (pull_request) Successful in 30s
CI / secret-scan (pull_request) Successful in 37s
CI / unit-tests (pull_request) Successful in 5m24s

- 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:
ilia 2026-07-26 15:54:12 -04:00
parent 285b094bce
commit a16fa88a2d
11 changed files with 78 additions and 36 deletions

View File

@ -6,7 +6,10 @@ import type { Job } from "@shared/types.js";
import type React from "react";
import { useEffect, useRef, useState } from "react";
import { toast } from "sonner";
import { toastMoveToReadyUndo, toastWithUndo } from "@/client/lib/jobActionUndo";
import {
toastMoveToReadyUndo,
toastWithUndo,
} from "@/client/lib/jobActionUndo";
import { trackProductEvent } from "@/lib/analytics";
import { JobDetailsEditDrawer } from "../JobDetailsEditDrawer";
import { DecideMode } from "./DecideMode";

View File

@ -14,10 +14,7 @@ vi.mock("@client/api", () => ({
import * as api from "@client/api";
import { toast } from "sonner";
import {
restoresFromSkipResults,
toastWithUndo,
} from "./jobActionUndo";
import { restoresFromSkipResults, toastWithUndo } from "./jobActionUndo";
describe("restoresFromSkipResults", () => {
it("includes primary and also-skipped restores", () => {
@ -49,7 +46,7 @@ describe("toastWithUndo", () => {
expect(toast.message).toHaveBeenCalled();
const call = vi.mocked(toast.message).mock.calls[0];
const opts = call?.[1] as {
action?: { onClick: () => void };
action?: { label?: string; onClick: () => void };
};
expect(opts?.action?.label ?? opts?.action).toBeTruthy();
opts?.action?.onClick();

View File

@ -1,5 +1,9 @@
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";
const DEFAULT_UNDO_MS = 8_000;

View File

@ -27,7 +27,6 @@ import {
import React from "react";
import { useNavigate, useParams } from "react-router-dom";
import { toast } from "sonner";
import { toastAppliedUndo, toastWithUndo } from "@/client/lib/jobActionUndo";
import { invalidateJobData } from "@/client/hooks/queries/invalidate";
import {
useCheckSponsorMutation,
@ -38,6 +37,7 @@ import {
useUpdateJobMutation,
} from "@/client/hooks/queries/useJobMutations";
import { useQueryErrorToast } from "@/client/hooks/useQueryErrorToast";
import { toastAppliedUndo, toastWithUndo } from "@/client/lib/jobActionUndo";
import { queryKeys } from "@/client/lib/queryKeys";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";

View File

@ -34,23 +34,25 @@ vi.mock("../api", () => ({
}),
getProfile: vi.fn().mockResolvedValue({ personName: "Test User" }),
skipJob: vi.fn().mockResolvedValue({}),
runJobAction: vi.fn().mockImplementation(async (input: { action: string; jobIds: string[] }) => {
const jobId = input.jobIds[0] ?? "job-1";
return {
action: input.action,
requested: 1,
succeeded: 1,
failed: 0,
results: [
{
jobId,
ok: true,
job: { id: jobId, status: "skipped" },
undo: { previousStatus: "ready", alsoSkipped: [] },
},
],
};
}),
runJobAction: vi
.fn()
.mockImplementation(async (input: { action: string; jobIds: string[] }) => {
const jobId = input.jobIds[0] ?? "job-1";
return {
action: input.action,
requested: 1,
succeeded: 1,
failed: 0,
results: [
{
jobId,
ok: true,
job: { id: jobId, status: "skipped" },
undo: { previousStatus: "ready", alsoSkipped: [] },
},
],
};
}),
restoreJobStatuses: vi.fn().mockResolvedValue({ restored: 1, jobs: [] }),
markAsApplied: vi.fn().mockResolvedValue({}),
processJob: vi.fn().mockResolvedValue({}),

View File

@ -136,7 +136,7 @@ vi.mock("@client/api", () => ({
processJob: vi.fn(),
generateJobPdf: vi.fn(),
markAsApplied: vi.fn(),
skipJob: vi.fn(),
runJobAction: vi.fn(),
getProfile: vi.fn().mockResolvedValue({}),
}));
@ -346,7 +346,16 @@ describe("JobDetailPanel", () => {
it("skips a job from the menu", async () => {
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({
activeTab: "all",
@ -362,7 +371,12 @@ describe("JobDetailPanel", () => {
const skipItem = await screen.findByRole("menuitem", { name: /skip job/i });
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();
});

View File

@ -33,8 +33,8 @@ import {
import type React from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { toast } from "sonner";
import { toastAppliedUndo, toastWithUndo } from "@/client/lib/jobActionUndo";
import { JobDescriptionMarkdown } from "@/client/components/JobDescriptionMarkdown";
import { toastAppliedUndo, toastWithUndo } from "@/client/lib/jobActionUndo";
import { getRenderableJobDescription } from "@/client/lib/jobDescription";
import { Button } from "@/components/ui/button";
import {
@ -324,7 +324,12 @@ export const JobDetailPanel: React.FC<JobDetailPanelProps> = ({
});
toastWithUndo({
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",
onRestored: onJobUpdated,
successMessage: "Reverted to Applied",

View File

@ -7,7 +7,10 @@ import {
} from "@shared/types.js";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { toast } from "sonner";
import { restoresFromSkipResults, toastWithUndo } from "@/client/lib/jobActionUndo";
import {
restoresFromSkipResults,
toastWithUndo,
} from "@/client/lib/jobActionUndo";
import { trackProductEvent } from "@/lib/analytics";
import type { FilterTab } from "./constants";
import { JobActionProgressToast } from "./JobActionProgressToast";

View File

@ -9,7 +9,11 @@ import { SHORTCUTS } from "@client/lib/shortcut-map";
import type { JobAction, JobListItem } from "@shared/types.js";
import { useCallback, useRef } from "react";
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 type { FilterTab } from "./constants";
import { tabs } from "./constants";

View File

@ -1004,9 +1004,12 @@ jobsRouter.post("/restore-status", async (req: Request, res: Response) => {
try {
const parsed = restoreStatusSchema.safeParse(req.body);
if (!parsed.success) {
return fail(res, badRequest("Invalid restore payload", {
issues: parsed.error.issues,
}));
return fail(
res,
badRequest("Invalid restore payload", {
issues: parsed.error.issues,
}),
);
}
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
*/

View File

@ -23,6 +23,14 @@ vi.mock("@server/extractors/registry", () => ({
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 = {
topN: 10,
minSuitabilityScore: 50,