mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-07 11:39:00 +00:00
The completion-doc upload endpoint sets DocStatus=Yes and bumps the work-order row version in one transaction. Both signed-PDF upload paths then re-ran a "docStatus=Yes" work-order patch using the pre-upload row version (the comp-doc dialog re-ran the whole Generate flow; the slide-over posted docStatus directly). That patch always 409'd — and in the dialog it also demanded a Date Work Completed — so a successful upload surfaced an error to the user. Both paths now stop after the upload settles: the mutation's onSuccess refreshes the board and the UI reflects the generated state locally, with no second docStatus patch.
153 lines
4.8 KiB
TypeScript
153 lines
4.8 KiB
TypeScript
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<void>;
|
|
|
|
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> }) => {
|
|
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> = {}): 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(
|
|
<QueryClientProvider client={client}>
|
|
<CompDocDialog
|
|
row={baseRow()}
|
|
open
|
|
onOpenChange={vi.fn()}
|
|
onSave={onSave}
|
|
onDateCompletedChange={onDateCompletedChange}
|
|
/>
|
|
</QueryClientProvider>,
|
|
);
|
|
|
|
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();
|
|
});
|
|
});
|