shoc-frontend-new/src/test/app/(protected)/workorders/slide-over-signed-pdf-upload.test.tsx
Alexandre Brandizzi 8d50c6f75e fix(work-orders): stop the stale-version docStatus patch after a signed PDF upload (SH-380)
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.
2026-09-18 16:59:24 -03:00

145 lines
4.4 KiB
TypeScript

import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { toast } from "react-toastify";
import { WorkOrderSlideOver } from "@/app/(protected)/workorders/_components/detail/work-order-slide-over";
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
vi.mock("@/providers/auth-context", async (importOriginal) => {
const actual = await importOriginal<typeof import("@/providers/auth-context")>();
return { ...actual, useAuthContext: () => ({ user: { id: "u1" } }) };
});
vi.mock("@/domain/work-orders/use-cases/use-work-order-detail", () => ({
useWorkOrderBoardDetail: () => ({
data: undefined,
isLoading: false,
isFetching: false,
error: null,
refetch: vi.fn(),
}),
}));
// A completion-doc upload that settles successfully. The mock forwards any onSuccess the
// caller passes; the fix means the slide-over passes none (no follow-up docStatus patch).
vi.mock("@/domain/work-orders/use-cases/use-work-order-mutations", () => ({
useAddWorkOrderComment: () => ({ mutate: vi.fn(), isPending: false }),
useUpdateWorkOrderComment: () => ({ mutate: vi.fn(), isPending: false }),
useUploadWorkOrderMedia: () => ({ mutate: vi.fn(), mutateAsync: vi.fn(), isPending: false }),
useUpdateWorkOrderMediaCategory: () => ({ mutate: vi.fn(), isPending: false }),
useDeleteWorkOrderMedia: () => ({ mutate: vi.fn(), isPending: false }),
useUploadCompletionDoc: () => ({
mutate: (_vars: unknown, options?: { onSuccess?: () => void | Promise<void> }) => {
void options?.onSuccess?.();
},
isPending: false,
}),
}));
vi.mock("react-toastify", () => ({ toast: { error: vi.fn(), success: vi.fn() } }));
function baseRow(overrides: Partial<WorkOrderTableRow> = {}): WorkOrderTableRow {
return {
id: 42,
woNumber: "WO-42",
site: "SEA1",
type: "PM",
severity: null,
dispatcherId: "u1",
dispatcherName: "Dispatcher",
scheduledOn: "2026-07-20",
completedDate: "",
targetWeek: "",
dayGroup: "07/20",
dayLabel: "Mon",
location: "Seattle",
locationId: "loc-1",
pm: "HVAC",
serviceNotes: "",
extraServices: [],
company: "Vendor Co",
tech: "Tech",
techPhone: "",
vendorId: "v1",
apptStart: "",
apptEnd: "",
apptTime: "",
status: "Scheduled",
docStatus: "No",
pocName: "",
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 },
...overrides,
};
}
describe("WorkOrderSlideOver signed PDF upload (SH-380)", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("does not issue a docStatus patch or surface an error after a successful upload", async () => {
const row = baseRow();
// A follow-up docStatus patch would carry the pre-upload version and reject; assert none runs.
const onSave = vi.fn(
(
_id: string | number,
_patch: Partial<WorkOrderTableRow>,
options?: { onSuccess?: () => void; onError?: (error: Error) => void },
) => {
options?.onError?.(new Error("The record was modified by another user."));
},
);
render(
<WorkOrderSlideOver
row={row}
editMode={false}
users={[{ id: "u1", name: "Dispatcher" }]}
sites={[{ id: "loc-1", name: "Seattle" }]}
vendors={[]}
pmTypes={["HVAC"]}
allOrders={[row]}
onSave={onSave}
onOpenCompletionDoc={vi.fn()}
onCancelWO={vi.fn()}
onClose={vi.fn()}
/>,
);
fireEvent.click(screen.getByRole("tab", { name: /completion doc/i }));
const pdfInput = document.querySelector('input[accept="application/pdf,.pdf"]');
expect(pdfInput).not.toBeNull();
fireEvent.change(pdfInput!, {
target: {
files: [
new File([new Uint8Array([0x25, 0x50, 0x44, 0x46, 0x2d])], "signed.pdf", {
type: "application/pdf",
}),
],
},
});
await waitFor(() => {
expect(vi.mocked(toast.error)).not.toHaveBeenCalled();
});
expect(onSave).not.toHaveBeenCalled();
});
});