import { fireEvent, render, waitFor } from "@testing-library/react"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { toast } from "react-toastify"; import { CompDocDialog } from "@/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog"; import type { CompDocData, CompDocSaveStatus } from "@/domain/work-orders/types/completion-doc"; import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row"; type SaveHandler = (data: CompDocData, status: CompDocSaveStatus) => void | Promise; vi.mock("@/domain/locations/use-cases/use-location-detail", () => ({ useLocationDetail: () => ({ data: undefined }), })); vi.mock("@/domain/work-orders/use-cases/use-work-order-detail", () => ({ useWorkOrderBoardDetail: () => ({ data: undefined, isLoading: false }), })); // A completion-doc upload that settles successfully. The backend has already set // DocStatus=Yes and bumped the work-order row version inside the same transaction, // so any client follow-up patch would carry a stale version. vi.mock("@/domain/work-orders/use-cases/use-work-order-mutations", () => ({ useUploadCompletionDoc: () => ({ mutate: (_vars: unknown, options?: { onSuccess?: () => void | Promise }) => { void options?.onSuccess?.(); }, isPending: false, }), })); vi.mock("react-toastify", () => ({ toast: Object.assign(vi.fn(), { error: vi.fn(), success: vi.fn() }), })); vi.mock("@/app/(protected)/workorders/_components/comp-doc/comp-doc-draft-toast", () => ({ showCompDocDraftSavedToast: vi.fn(), })); function baseRow(overrides: Partial = {}): WorkOrderTableRow { const defaults: WorkOrderTableRow = { id: 42, woNumber: "WO-42", site: "SEA1", type: "PM", severity: null, dispatcherId: "u1", dispatcherName: "Dispatcher", scheduledOn: "2026-06-01", completedDate: "2026-07-01", targetWeek: "", dayGroup: "", dayLabel: "", location: "Seattle", locationId: "mock-loc-1", pm: "HVAC", serviceNotes: "", extraServices: [], vendorId: "", company: "Vendor Co", tech: "Tech One", techPhone: "", apptStart: "", apptEnd: "", apptTime: "", status: "Scheduled", docStatus: "No", pocName: "Amazon Rep", pocPhone: "", pocNotes: "", additionalContacts: [], dueDate: "", rescheduleCount: 0, carriedOver: 0, originalWeek: "", originalDate: "", isPastDue: false, scheduleWeekOnly: false, mediaCount: 0, title: "PM — SEA1", priority: "Sev 3", primaryDispatchId: null, rowVersion: "v1", dispatchRowVersion: "", flagColor: null, isAddOn: false, upliftSummary: { hasUplift: false, pendingCount: 0 }, }; return { ...defaults, ...overrides }; } describe("CompDocDialog signed PDF upload (SH-380)", () => { beforeEach(() => { vi.mocked(toast.error).mockReset(); }); function uploadSignedPdf(onSave: SaveHandler) { const onDateCompletedChange = vi.fn().mockResolvedValue(undefined); const client = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false } }, }); render( , ); const fileInput = document.querySelector('input[type="file"]'); expect(fileInput).not.toBeNull(); fireEvent.change(fileInput!, { target: { files: [ new File([new Uint8Array([0x25, 0x50, 0x44, 0x46, 0x2d])], "signed.pdf", { type: "application/pdf", }), ], }, }); return { onSave, onDateCompletedChange }; } it("does not re-run the docStatus persist after a successful upload", async () => { // The server already marked the document generated; a client docStatus patch here would // carry the pre-upload row version and 409. It must not run. const onSave: SaveHandler = vi.fn(() => Promise.reject(new Error("The record was modified by another user. Refresh and retry.")), ); uploadSignedPdf(onSave); await waitFor(() => { expect(vi.mocked(toast.error)).not.toHaveBeenCalled(); }); expect(onSave).not.toHaveBeenCalled(); }); it("surfaces no error toast to the user when the signed PDF upload settles", async () => { const onSave: SaveHandler = vi.fn(() => Promise.reject(new Error("conflict"))); const { onDateCompletedChange } = uploadSignedPdf(onSave); await waitFor(() => { expect(vi.mocked(toast.error)).not.toHaveBeenCalled(); }); expect(onDateCompletedChange).not.toHaveBeenCalled(); }); });