import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test"; const mocks = vi.hoisted(() => ({ directories: new Set(), deleted: vi.fn(), download: vi.fn(), copy: vi.fn(), share: vi.fn(), shareFromSource: vi.fn(), available: vi.fn(), uuid: vi.fn(), })); vi.mock("expo-file-system", () => { class Directory { readonly uri: string; constructor(...parts: Array) { this.uri = parts.map((part) => (typeof part === "string" ? part : part.uri)).join("/"); } get name(): string { return this.uri.split("/").at(-1)!; } get exists(): boolean { return mocks.directories.has(this.uri); } create(): void { mocks.directories.add(this.uri); } list(): Directory[] { const prefix = `${this.uri}/`; return [...mocks.directories] .filter((uri) => uri.startsWith(prefix) && !uri.slice(prefix.length).includes("/")) .map((uri) => new Directory(uri)); } delete(): void { mocks.deleted(this.uri); mocks.directories.delete(this.uri); } } class File { static downloadFileAsync = mocks.download; readonly uri: string; constructor(source: Directory | string, name?: string) { this.uri = typeof source === "string" ? source : `${source.uri}/${encodeURIComponent(name!)}`; } async copy(destination: File): Promise { await mocks.copy(this.uri, destination.uri); } } return { Directory, File, Paths: { cache: "file:///cache" } }; }); vi.mock("expo-sharing", () => ({ isAvailableAsync: mocks.available, shareAsync: mocks.share, })); vi.mock("./uuid", () => ({ uuidv4: mocks.uuid })); vi.mock("./shareFileFromSource", () => ({ shareFileFromSource: mocks.shareFromSource })); import { downloadAndShareAttachment, downloadAttachmentForPreview, shareLocalAttachment, } from "./attachmentDownload"; import { isForegroundHandoffActive } from "./foreground-handoff"; const NOW = 1_787_990_400_000; const DAY_MS = 24 * 60 * 60_000; const CACHE = "file:///cache/t3-attachment-downloads"; const input = { url: "https://chosen-environment.example/api/assets/signed-token/report.pdf", attachment: { name: "report.pdf", mimeType: "application/pdf" }, }; beforeEach(() => { mocks.directories.clear(); mocks.deleted.mockReset(); mocks.download.mockReset(); mocks.copy.mockReset(); mocks.share.mockReset(); mocks.shareFromSource.mockReset(); mocks.available.mockReset(); mocks.uuid.mockReset(); mocks.download.mockImplementation(async (_url: string, file: { uri: string }) => file); mocks.copy.mockResolvedValue(undefined); mocks.share.mockResolvedValue(undefined); mocks.shareFromSource.mockResolvedValue(undefined); mocks.available.mockResolvedValue(true); let sequence = 0; mocks.uuid.mockImplementation( () => `00000000-0000-4000-8000-${String(++sequence).padStart(12, "0")}`, ); vi.spyOn(Date, "now").mockReturnValue(NOW); }); afterEach(() => { vi.restoreAllMocks(); expect(isForegroundHandoffActive()).toBe(false); }); describe("downloadAndShareAttachment", () => { it("downloads the chosen environment's signed URL and shares the local file", async () => { const controller = new AbortController(); await downloadAndShareAttachment({ ...input, signal: controller.signal }); expect(mocks.download).toHaveBeenCalledWith( input.url, expect.objectContaining({ uri: expect.stringMatching(/\/report\.pdf$/) }), { signal: controller.signal }, ); expect(mocks.share).toHaveBeenCalledWith( expect.stringMatching(/^file:\/\/\/cache\/.+\/report\.pdf$/), { mimeType: "application/pdf", dialogTitle: "report.pdf", }, ); expect(mocks.deleted).not.toHaveBeenCalled(); }); it("shares videos even when the server serves their bytes inline", async () => { await downloadAndShareAttachment({ url: "https://relay-environment.example/api/assets/signed-video/clip.mp4", attachment: { name: "clip.mp4", mimeType: 'video/mp4; codecs="avc1"' }, signal: new AbortController().signal, }); expect(mocks.share).toHaveBeenCalledWith(expect.stringMatching(/\/clip\.mp4$/), { mimeType: "video/mp4", dialogTitle: "clip.mp4", }); }); it.each([ ["../../résumé.pdf", "résumé.pdf"], ["C:\\folder\\clip.mp4", "clip.mp4"], ["a?query#part%2F.txt", "a?query#part%2F.txt"], ["Report #5 - 100%.pdf", "Report #5 - 100%.pdf"], ["..", "attachment"], [" ", "attachment"], ["\ud800file\u0000.txt", "_file_.txt"], [".env", ".env"], ])("uses a safe basename for %j", async (name, expected) => { await downloadAndShareAttachment({ ...input, attachment: { ...input.attachment, name }, signal: new AbortController().signal, }); const file = mocks.download.mock.calls[0]![1] as { uri: string }; expect(decodeURIComponent(file.uri.split("/").at(-1)!)).toBe(expected); }); it("preserves ordinary long filenames that fit within the filesystem limit", async () => { const name = "Project quarterly report with detailed implementation and delivery notes for August 2026.pdf"; await downloadAndShareAttachment({ ...input, attachment: { ...input.attachment, name }, signal: new AbortController().signal, }); const file = mocks.download.mock.calls[0]![1] as { uri: string }; expect(decodeURIComponent(file.uri.split("/").at(-1)!)).toBe(name); }); it("bounds the UTF-8 filename length while preserving its extension", async () => { await downloadAndShareAttachment({ ...input, attachment: { name: `${"🙂".repeat(80)}.mp4`, mimeType: "video/mp4" }, signal: new AbortController().signal, }); const file = mocks.download.mock.calls[0]![1] as { uri: string }; const name = decodeURIComponent(file.uri.split("/").at(-1)!); expect(name.endsWith(".mp4")).toBe(true); expect(new TextEncoder().encode(name).length).toBeLessThanOrEqual(255); }); it("reports unavailable sharing before downloading or creating files", async () => { mocks.available.mockResolvedValue(false); await expect( downloadAndShareAttachment({ ...input, signal: new AbortController().signal }), ).rejects.toThrow("Saving and sharing files is unavailable on this device."); expect(mocks.download).not.toHaveBeenCalled(); expect(mocks.directories.size).toBe(0); }); it("cleans an interrupted download only after the native request settles", async () => { const started = Promise.withResolvers(); const download = Promise.withResolvers<{ uri: string }>(); mocks.download.mockImplementation(() => { started.resolve(); return download.promise; }); const controller = new AbortController(); const task = downloadAndShareAttachment({ ...input, signal: controller.signal }); await started.promise; controller.abort(); expect(mocks.deleted).not.toHaveBeenCalled(); download.reject(new Error("Canceled native request")); await task; expect(mocks.deleted).toHaveBeenCalledTimes(1); expect(mocks.share).not.toHaveBeenCalled(); }); it("does not open a late download after cancellation", async () => { const started = Promise.withResolvers<{ uri: string }>(); const download = Promise.withResolvers<{ uri: string }>(); mocks.download.mockImplementation((_url: string, file: { uri: string }) => { started.resolve(file); return download.promise; }); const controller = new AbortController(); const task = downloadAndShareAttachment({ ...input, signal: controller.signal }); const file = await started.promise; controller.abort(); download.resolve(file); await task; expect(mocks.share).not.toHaveBeenCalled(); expect(mocks.deleted).toHaveBeenCalledTimes(1); }); it("retains an export when its row unmounts during the native handoff", async () => { const opened = Promise.withResolvers(); const share = Promise.withResolvers(); mocks.share.mockImplementation(() => { expect(isForegroundHandoffActive()).toBe(true); opened.resolve(); return share.promise; }); const controller = new AbortController(); const task = downloadAndShareAttachment({ ...input, signal: controller.signal }); await opened.promise; controller.abort(); share.resolve(); await task; expect(mocks.deleted).not.toHaveBeenCalled(); }); it("cleans failed exports and releases the foreground handoff", async () => { mocks.share.mockRejectedValue(new Error("No activity can open this file")); await expect( downloadAndShareAttachment({ ...input, signal: new AbortController().signal }), ).rejects.toThrow("Could not open the share sheet. Try again."); expect(mocks.deleted).toHaveBeenCalledTimes(1); }); it("removes expired exports while leaving recent and unrelated cache entries alone", async () => { const old = `${CACHE}/${NOW - DAY_MS - 1}-00000000-0000-4000-8000-000000000010`; const recent = `${CACHE}/${NOW - DAY_MS + 1}-00000000-0000-4000-8000-000000000011`; const unrelated = `${CACHE}/unrelated`; mocks.directories.add(old).add(recent).add(unrelated); await downloadAndShareAttachment({ ...input, signal: new AbortController().signal }); expect(mocks.deleted.mock.calls).toEqual([[old]]); expect(mocks.directories.has(recent)).toBe(true); expect(mocks.directories.has(unrelated)).toBe(true); }); it("does not prune an active export even if it passes the cache expiry", async () => { const opened = Promise.withResolvers(); const share = Promise.withResolvers(); mocks.share.mockImplementationOnce(() => { opened.resolve(); return share.promise; }); const first = downloadAndShareAttachment({ ...input, signal: new AbortController().signal }); await opened.promise; vi.mocked(Date.now).mockReturnValue(NOW + DAY_MS + 1); await downloadAndShareAttachment({ ...input, signal: new AbortController().signal }); expect(mocks.deleted).not.toHaveBeenCalled(); share.resolve(); await first; }); }); describe("attachment preview files", () => { it("does not start a native request after cancellation during setup", async () => { const controller = new AbortController(); const loading = downloadAttachmentForPreview({ ...input, signal: controller.signal }); controller.abort(); await expect(loading).resolves.toBeNull(); expect(mocks.download).not.toHaveBeenCalled(); expect(mocks.share).not.toHaveBeenCalled(); }); it("downloads for playback without requiring a share sheet and removes the file on close", async () => { mocks.available.mockResolvedValue(false); const file = await downloadAttachmentForPreview({ ...input, signal: new AbortController().signal, }); expect(file?.uri.endsWith("/report.pdf")).toBe(true); expect(mocks.available).not.toHaveBeenCalled(); expect(mocks.deleted).not.toHaveBeenCalled(); file?.dispose(); file?.dispose(); expect(mocks.deleted).toHaveBeenCalledTimes(1); }); it.each([undefined, "share-button"])( "keeps a shared preview after its owner closes (source: %s)", async (sourceIdentifier) => { const opened = Promise.withResolvers(); const sharing = Promise.withResolvers(); const nativeShare = sourceIdentifier ? mocks.shareFromSource : mocks.share; nativeShare.mockImplementationOnce(() => { opened.resolve(); return sharing.promise; }); const file = await downloadAttachmentForPreview({ ...input, signal: new AbortController().signal, }); const share = file!.share(new AbortController().signal, sourceIdentifier); await opened.promise; file!.dispose(); expect(mocks.deleted).not.toHaveBeenCalled(); expect(isForegroundHandoffActive()).toBe(true); sharing.resolve(); await share; expect(isForegroundHandoffActive()).toBe(false); expect(mocks.deleted).not.toHaveBeenCalled(); expect(mocks.download).toHaveBeenCalledTimes(1); expect(mocks.copy).not.toHaveBeenCalled(); }, ); it.each([undefined, "share-button"])( "does not share a disposed preview after availability checking (source: %s)", async (sourceIdentifier) => { const checking = Promise.withResolvers(); const available = Promise.withResolvers(); mocks.available.mockImplementation(() => { checking.resolve(); return available.promise; }); const file = await downloadAttachmentForPreview({ ...input, signal: new AbortController().signal, }); const share = file!.share(new AbortController().signal, sourceIdentifier); await checking.promise; file!.dispose(); available.resolve(true); await share; expect(mocks.share).not.toHaveBeenCalled(); expect(mocks.shareFromSource).not.toHaveBeenCalled(); expect(mocks.deleted).toHaveBeenCalledTimes(1); }, ); it("copies a local original before sharing without downloading or deleting the source", async () => { const uri = "file:///documents/draft/report.pdf"; await shareLocalAttachment({ uri, attachment: input.attachment, signal: new AbortController().signal, }); expect(mocks.copy).toHaveBeenCalledWith( uri, expect.stringMatching(/^file:\/\/\/cache\/.+\/report\.pdf$/), ); expect(mocks.share).toHaveBeenCalledWith(mocks.copy.mock.calls[0]![1], expect.any(Object)); expect(mocks.download).not.toHaveBeenCalled(); expect(mocks.deleted).not.toHaveBeenCalled(); }); it("waits for a local copy to finish before cleaning up a canceled share", async () => { const copying = Promise.withResolvers(); const copied = Promise.withResolvers(); mocks.copy.mockImplementation(() => { copying.resolve(); return copied.promise; }); const controller = new AbortController(); const task = shareLocalAttachment({ uri: "file:///documents/draft/report.pdf", attachment: input.attachment, signal: controller.signal, }); await copying.promise; controller.abort(); expect(mocks.deleted).not.toHaveBeenCalled(); copied.resolve(); await task; expect(mocks.share).not.toHaveBeenCalled(); expect(mocks.deleted).toHaveBeenCalledTimes(1); }); });