import { act, renderHook, waitFor } from "@testing-library/react"; import { describe, expect, it, vi } from "vitest"; import { useMediaUploadSession } from "@/app/(protected)/workorders/_hooks/use-media-upload-session"; function makeFile(name = "photo.jpg") { return new File(["bytes"], name, { type: "image/jpeg" }); } describe("useMediaUploadSession retry / cancel", () => { it("retry upserts the same localId instead of appending a duplicate", async () => { let rejectFirst!: (error: Error) => void; const uploadMutateAsync = vi .fn() .mockImplementationOnce( () => new Promise((_resolve, reject) => { rejectFirst = reject; }), ) .mockImplementation(() => new Promise(() => undefined)); const { result } = renderHook(() => useMediaUploadSession(undefined, uploadMutateAsync)); const firstController = new AbortController(); const localId = "local-1"; const file = makeFile(); act(() => { result.current.startUploads([ { localId, file, signal: firstController.signal, onProgress: () => undefined, }, ]); }); await waitFor(() => { expect(result.current.uploads).toHaveLength(1); }); act(() => { rejectFirst(new Error("network")); }); await waitFor(() => { expect(result.current.uploads[0]?.uploadStatus).toBe("failed"); }); const retryController = new AbortController(); act(() => { result.current.retryUpload(localId, retryController.signal); }); await waitFor(() => { expect(result.current.uploads).toHaveLength(1); expect(result.current.uploads[0]?.id).toBe(localId); expect(result.current.uploads[0]?.uploadStatus).toBe("uploading"); }); expect(uploadMutateAsync).toHaveBeenCalledTimes(2); expect(uploadMutateAsync.mock.calls[1]?.[0]?.signal).toBe(retryController.signal); }); it("cancel after retry aborts the replacement upload and removes the local row", async () => { let rejectFirst!: (error: Error) => void; const uploadMutateAsync = vi .fn() .mockImplementationOnce( () => new Promise((_resolve, reject) => { rejectFirst = reject; }), ) .mockImplementation( ({ signal }: { signal?: AbortSignal }) => new Promise((_resolve, reject) => { signal?.addEventListener("abort", () => { const error = new Error("aborted"); error.name = "AbortError"; reject(error); }); }), ); const { result } = renderHook(() => useMediaUploadSession(undefined, uploadMutateAsync)); const localId = "local-retry"; const file = makeFile("retry.jpg"); act(() => { result.current.startUploads([ { localId, file, signal: new AbortController().signal, onProgress: () => undefined, }, ]); }); act(() => { rejectFirst(new Error("upload failed")); }); await waitFor(() => { expect(result.current.uploads[0]?.uploadStatus).toBe("failed"); }); const retryController = new AbortController(); act(() => { result.current.retryUpload(localId, retryController.signal); }); await waitFor(() => { expect(result.current.uploads[0]?.uploadStatus).toBe("uploading"); }); act(() => { // Mirrors MediaUploader.cancel: abort the mapped controller, then removeLocal. retryController.abort(); result.current.removeLocal(localId); }); await waitFor(() => { expect(result.current.uploads).toHaveLength(0); }); expect(retryController.signal.aborted).toBe(true); expect(uploadMutateAsync.mock.calls[1]?.[0]?.signal).toBe(retryController.signal); }); });