import { fireEvent, screen, waitFor } from "@testing-library/react"; import { afterEach, describe, expect, it, vi } from "vitest"; import { VendorPortalDocuments } from "@/app/v/_components/vendor-portal-documents"; import { vendorPortalApi } from "@/domain/vendor-portal/api/vendor-portal-api"; import type { VendorPortalDocument } from "@/domain/vendor-portal/types/vendor-portal"; import { probeVideoDurationSeconds } from "@/lib/probe-video-duration"; import { renderWithProviders } from "@/test/test-utils"; vi.mock("@/lib/probe-video-duration", () => ({ probeVideoDurationSeconds: vi.fn() })); const quarantinedDocument: VendorPortalDocument = { id: 12, originalFileName: "completion.pdf", contentType: "application/pdf", sizeBytes: 1024, scanStatus: "Pending", reviewStatus: "Processing", version: 1, canDownload: false, }; afterEach(() => { vi.restoreAllMocks(); }); describe("VendorPortalDocuments", () => { it("rejects unsupported files before starting an upload", async () => { const upload = vi.spyOn(vendorPortalApi, "uploadDocument"); renderWithProviders( , { withAuth: false }, ); fireEvent.change(screen.getByLabelText("Choose completion document"), { target: { files: [new File(["text"], "notes.txt", { type: "text/plain" })] }, }); expect(await screen.findByRole("alert")).toHaveTextContent( "Only PDF, JPG, PNG, HEIC, MP4, and MOV files are allowed.", ); expect(upload).not.toHaveBeenCalled(); }); it("keeps pending documents quarantined", () => { renderWithProviders( , { withAuth: false }, ); expect(screen.getByRole("button", { name: "Quarantined" })).toBeDisabled(); expect(screen.getByText("Pending")).toBeInTheDocument(); expect(screen.getByText("Processing")).toBeInTheDocument(); }); it.each([ ["animation.gif", "image/gif"], ["clip.webm", "video/webm"], ])("rejects %s (%s), which the server does not accept", async (name, type) => { const upload = vi.spyOn(vendorPortalApi, "uploadDocument"); renderWithProviders( , { withAuth: false }, ); fireEvent.change(screen.getByLabelText("Choose completion document"), { target: { files: [new File(["x"], name, { type })] }, }); expect(await screen.findByRole("alert")).toHaveTextContent( "Only PDF, JPG, PNG, HEIC, MP4, and MOV files are allowed.", ); expect(upload).not.toHaveBeenCalled(); }); describe("SH-116 media contract", () => { const MB = 1_000_000; const renderDocuments = () => renderWithProviders( , { withAuth: false }, ); const choose = (file: File) => fireEvent.change(screen.getByLabelText("Choose completion document"), { target: { files: [file] }, }); it("uploads a ~60 MB phone MOV whose browser type is empty", async () => { vi.mocked(probeVideoDurationSeconds).mockResolvedValue(null); const upload = vi .spyOn(vendorPortalApi, "uploadDocument") .mockResolvedValue(quarantinedDocument); renderDocuments(); const video = { name: "IMG_0001.MOV", type: "", size: 60 * MB } as File; choose(video); await waitFor(() => expect(upload).toHaveBeenCalledOnce()); expect(upload.mock.calls[0][0].file).toBe(video); expect(screen.queryByRole("alert")).not.toBeInTheDocument(); }); it.each([ [ { name: "clip.mp4", type: "video/mp4", size: 101 * MB }, "Videos must be 100 MB or smaller.", ], [ { name: "IMG_1.jpg", type: "image/jpeg", size: 11 * MB }, "Photos must be 10 MB or smaller.", ], ])("rejects %o with a generic message", async (file, message) => { vi.mocked(probeVideoDurationSeconds).mockResolvedValue(30); const upload = vi.spyOn(vendorPortalApi, "uploadDocument"); renderDocuments(); choose(file as File); expect(await screen.findByRole("alert")).toHaveTextContent(message); expect(upload).not.toHaveBeenCalled(); }); }); it("reports upload progress and refreshes the dispatch after success", async () => { const onChanged = vi.fn().mockResolvedValue(undefined); let finishUpload: (() => void) | undefined; vi.spyOn(vendorPortalApi, "uploadDocument").mockImplementation(async ({ onProgress }) => { onProgress(65); await new Promise((resolve) => { finishUpload = resolve; }); return quarantinedDocument; }); renderWithProviders( , { withAuth: false }, ); fireEvent.change(screen.getByLabelText("Choose completion document"), { target: { files: [ new File(["%PDF-1.4"], "completion.pdf", { type: "application/pdf", }), ], }, }); expect(await screen.findByText("Uploading… 65%")).toBeInTheDocument(); finishUpload?.(); await waitFor(() => expect(onChanged).toHaveBeenCalledOnce()); }); });