shoc-frontend-new/src/test/app/(protected)/workorders/use-slide-over-media-state.test.tsx
Codex Review Integration 8774b2e0a6 fix(work-orders): render PM extra media in the gallery (SH-371)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 15:23:24 -03:00

330 lines
9.2 KiB
TypeScript

import { act, renderHook } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { useSlideOverMediaState } from "@/app/(protected)/workorders/_components/detail/use-slide-over-media-state";
import type { WorkOrderBoardDetail } from "@/domain/work-orders/types/work-order-board-detail";
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
function baseInfo(overrides: Partial<WorkOrderTableRow> = {}): WorkOrderTableRow {
return {
id: 100,
woNumber: "100",
site: "SITE",
type: "Emergency",
severity: null,
dispatcherId: "d1",
dispatcherName: "Alex",
scheduledOn: "2026-07-16",
completedDate: "",
targetWeek: "",
dayGroup: "2026-07-16",
dayLabel: "Thu",
location: "Main",
locationId: 5,
pm: "",
serviceNotes: "",
extraServices: [],
vendorId: "v1",
company: "Acme",
tech: "Sam",
techPhone: "",
apptStart: "",
apptEnd: "",
apptTime: "",
status: "Incomplete",
docStatus: "",
pocName: "",
pocPhone: "",
pocNotes: "",
additionalContacts: [],
dueDate: "",
rescheduleCount: 0,
carriedOver: 0,
originalWeek: "",
originalDate: "",
isPastDue: false,
scheduleWeekOnly: false,
mediaCount: 0,
title: "",
priority: "",
primaryDispatchId: null,
rowVersion: "1",
dispatchRowVersion: "",
flagColor: null,
isAddOn: false,
upliftSummary: { hasUplift: false, pendingCount: 0 },
...overrides,
};
}
function detailWithMedia(
media: WorkOrderBoardDetail["media"],
infoOverrides: Partial<WorkOrderTableRow> = {},
): WorkOrderBoardDetail {
return {
info: baseInfo(infoOverrides),
completion: {
docStatus: "",
template: null,
signOffName: "",
signOffAttachment: "",
signOffSignature: "",
dispatchSignoffs: [],
},
comments: [],
audit: [],
media,
};
}
function idleMutation() {
return {
mutate: vi.fn(),
mutateAsync: vi.fn().mockResolvedValue({}),
isPending: false,
} as never;
}
describe("useSlideOverMediaState completion filter", () => {
beforeEach(() => {
sessionStorage.clear();
});
it("keeps Before/After/Completion after refetch mapped purpose (not file category)", () => {
const detail = detailWithMedia([
{
id: "1",
category: "Before",
url: "https://cdn.example.com/before.jpg",
fileName: "before.jpg",
uploadedAt: "2026-07-15T18:00:00.0000000Z",
isLegacy: false,
},
{
id: "2",
category: "After",
url: "https://cdn.example.com/after.jpg",
fileName: "after.jpg",
uploadedAt: "2026-07-15T18:00:00.0000000Z",
isLegacy: false,
},
{
id: "3",
category: "Completion",
url: "https://cdn.example.com/completion.pdf",
fileName: "completion.pdf",
uploadedAt: "2026-07-15T18:00:00.0000000Z",
isLegacy: false,
},
{
id: "4",
category: "Extra",
url: "https://cdn.example.com/extra.pdf",
fileName: "extra.pdf",
uploadedAt: "2026-07-15T18:00:00.0000000Z",
isLegacy: false,
},
{
id: "10",
category: "Extra",
url: "https://cdn.example.com/site.jpg",
fileName: "site.jpg",
uploadedAt: "2026-07-15T18:00:00.0000000Z",
isLegacy: false,
},
]);
const { result } = renderHook(() =>
useSlideOverMediaState({
detailForRow: detail,
uploadMediaMutation: idleMutation(),
deleteMediaMutation: idleMutation(),
categorizeMediaMutation: idleMutation(),
}),
);
expect(result.current.completionMedia.map((item) => item.purpose)).toEqual([
"Before",
"After",
"Completion",
"Extra",
]);
expect(result.current.completionMedia.map((item) => item.id)).toContain("10");
expect(
result.current.completionMedia.every((item) =>
["Photo", "Video", "Report", "Other"].includes(item.category),
),
).toBe(true);
expect(result.current.extrasUploads).toHaveLength(1);
expect(result.current.extrasUploads[0]?.purpose).toBe("Extra");
expect(result.current.extrasUploads[0]?.id).toBe("4");
});
it("keeps Extra photos on Completion Doc until Extra is assigned", () => {
const detail = detailWithMedia([
{
id: "10",
category: "Extra",
url: "https://cdn.example.com/site.jpg",
fileName: "site.jpg",
uploadedAt: "2026-07-15T18:00:00.0000000Z",
isLegacy: false,
},
]);
const { result } = renderHook(() =>
useSlideOverMediaState({
detailForRow: detail,
uploadMediaMutation: idleMutation(),
deleteMediaMutation: idleMutation(),
categorizeMediaMutation: idleMutation(),
}),
);
expect(result.current.completionMedia.map((item) => item.id)).toEqual(["10"]);
expect(result.current.extrasUploads).toHaveLength(0);
});
it("includes Aveta-categorized extra documents in extrasUploads", () => {
const detail = detailWithMedia([
{
id: "4",
category: "Extra",
url: "https://cdn.example.com/extra.pdf",
fileName: "extra.pdf",
uploadedAt: "2026-07-15T18:00:00.0000000Z",
isLegacy: false,
},
{
id: "5",
category: "Aveta",
url: "https://cdn.example.com/aveta.pdf",
fileName: "aveta.pdf",
uploadedAt: "2026-07-15T18:00:00.0000000Z",
isLegacy: false,
},
]);
const { result } = renderHook(() =>
useSlideOverMediaState({
detailForRow: detail,
uploadMediaMutation: idleMutation(),
deleteMediaMutation: idleMutation(),
categorizeMediaMutation: idleMutation(),
}),
);
expect(result.current.extrasUploads).toHaveLength(2);
expect(result.current.extrasUploads.map((item) => item.purpose)).toEqual(["Extra", "Aveta"]);
expect(result.current.extrasUploads[1]?.category).toBe("Aveta");
});
it("keeps an Extra photo in Extra Docs after assigning Extra and remounting", () => {
const detail = detailWithMedia([
{
id: "10",
category: "Extra",
url: "https://cdn.example.com/site.jpg",
fileName: "site.jpg",
uploadedAt: "2026-07-15T18:00:00.0000000Z",
isLegacy: false,
},
]);
const categorizeMutate = vi.fn((_input: unknown, options?: { onSuccess?: () => void }) =>
options?.onSuccess?.(),
);
const categorize = {
mutate: categorizeMutate,
mutateAsync: vi.fn().mockResolvedValue({}),
isPending: false,
} as never;
const { result, unmount } = renderHook(() =>
useSlideOverMediaState({
detailForRow: detail,
uploadMediaMutation: idleMutation(),
deleteMediaMutation: idleMutation(),
categorizeMediaMutation: categorize,
}),
);
expect(result.current.completionMedia).toHaveLength(1);
expect(result.current.extrasUploads).toHaveLength(0);
act(() => {
result.current.categorizeMedia("10", "Extra");
});
expect(result.current.completionMedia).toHaveLength(0);
expect(result.current.extrasUploads.map((item) => item.id)).toEqual(["10"]);
expect(categorizeMutate).toHaveBeenCalledWith(
{
mediaId: "10",
category: "Extra",
workOrderVersion: "1",
},
expect.objectContaining({ onSuccess: expect.any(Function) }),
);
unmount();
const remounted = renderHook(() =>
useSlideOverMediaState({
detailForRow: detail,
uploadMediaMutation: idleMutation(),
deleteMediaMutation: idleMutation(),
categorizeMediaMutation: idleMutation(),
}),
);
expect(remounted.result.current.completionMedia).toHaveLength(0);
expect(remounted.result.current.extrasUploads.map((item) => item.id)).toEqual(["10"]);
});
it("shows Extra photos and videos in Extra Docs on PM work orders", () => {
const detail = detailWithMedia(
[
{
id: "10",
category: "Extra",
url: "https://cdn.example.com/site.jpg",
fileName: "site.jpg",
uploadedAt: "2026-07-15T18:00:00.0000000Z",
isLegacy: false,
},
{
id: "11",
category: "Extra",
url: "https://cdn.example.com/walkthrough.mp4",
fileName: "walkthrough.mp4",
uploadedAt: "2026-07-15T18:00:00.0000000Z",
isLegacy: false,
},
{
id: "4",
category: "Extra",
url: "https://cdn.example.com/extra.pdf",
fileName: "extra.pdf",
uploadedAt: "2026-07-15T18:00:00.0000000Z",
isLegacy: false,
},
],
{ id: 200, type: "PM" },
);
const { result } = renderHook(() =>
useSlideOverMediaState({
detailForRow: detail,
uploadMediaMutation: idleMutation(),
deleteMediaMutation: idleMutation(),
categorizeMediaMutation: idleMutation(),
}),
);
expect(result.current.completionMedia).toHaveLength(0);
expect(result.current.extrasUploads.map((item) => item.id)).toEqual(["10", "11", "4"]);
expect(result.current.extrasUploads.map((item) => item.fileType)).toEqual([
"image",
"video",
"pdf",
]);
});
});