import { beforeEach, describe, expect, it, vi } from "vite-plus/test";import { PROVIDER_SEND_TURN_MAX_IMAGE_BYTES } from "@t3tools/contracts";import type { ImagePickerAsset } from "expo-image-picker"; const mocks = vi.hoisted(() => ({ documentUri: "file:///documents", pickFile: vi.fn(), pickMedia: vi.fn(), copy: vi.fn(), delete: vi.fn(), open: vi.fn(), size: vi.fn(), readBase64: vi.fn(),})); vi.mock("expo-file-system", () => { class Directory { readonly uri: string; constructor(root: string | { readonly uri: string }, name: string) { this.uri = `${typeof root === "string" ? root : root.uri}/${name}`; } create(): void {} } class File { readonly uri: string; constructor(source: string | Directory, name?: string) { this.uri = source instanceof Directory ? `${source.uri}/${name}` : source; } get exists(): boolean { return true; } get size(): number | null { return mocks.size(this.uri) ?? null; } get name(): string { return this.uri.split("/").at(-1) ?? ""; } get type(): string { return "video/quicktime"; } create(): void {} open(mode: string) { return mocks.open(this.uri, mode); } async copy(destination: File): Promise<void> { mocks.copy(this.uri, destination.uri); } async base64(): Promise<string> { return mocks.readBase64(this.uri); } delete(): void { mocks.delete(this.uri); } } return { Directory, File, FileMode: { ReadOnly: "r", WriteOnly: "w" }, Paths: { get document() { return { uri: mocks.documentUri }; }, }, };}); vi.mock("expo-image-picker", () => ({ launchImageLibraryAsync: mocks.pickMedia }));vi.mock("expo-document-picker", () => ({ getDocumentAsync: mocks.pickFile }));vi.mock("./uuid", () => ({ uuidv4: () => "attachment-id" })); import { persistComposerAttachmentFile, pickComposerFiles, pickComposerImages, pickComposerMedia, removePersistedComposerAttachmentFile,} from "./composerImages";import { isForegroundHandoffActive } from "./foreground-handoff";import { retainComposerAttachmentFile } from "./composerAttachmentFiles"; describe("composer file attachments", () => { beforeEach(() => { mocks.documentUri = "file:///documents"; mocks.pickFile.mockReset(); mocks.pickMedia.mockReset(); mocks.copy.mockReset(); mocks.delete.mockReset(); mocks.open.mockReset(); mocks.size.mockReset(); mocks.readBase64.mockReset(); mocks.size.mockImplementation((uri: string) => (uri.startsWith("content:") ? null : 42)); }); describe("photo library image conversion", () => { const jpeg = "/9j/2Q=="; const photo: ImagePickerAsset = { uri: "file:///picker/photo.heic", type: "image", fileName: "photo.HEIC", mimeType: "image/heic", fileSize: 20 * 1024 * 1024, base64: jpeg, width: 1, height: 1, }; it.each(["image/heic", "image/heif", undefined])( "attaches the native JPEG conversion with matching metadata when the source MIME is %s", async (mimeType) => { mocks.pickMedia.mockResolvedValue({ canceled: false, assets: [{ ...photo, mimeType }], }); const result = await pickComposerImages({ existingCount: 0 }); expect(result).toEqual({ images: [ { id: "attachment-id", type: "image", name: "photo.jpg", mimeType: "image/jpeg", sizeBytes: 4, dataUrl: `data:image/jpeg;base64,${jpeg}`, previewUri: `data:image/jpeg;base64,${jpeg}`, }, ], error: null, }); }, ); it.each([ { extension: "png", mimeType: "image/png", base64: "iVBORw0KGgo=" }, { extension: "gif", mimeType: "image/gif", base64: "R0lGODlh" }, { extension: "webp", mimeType: "image/webp", base64: "UklGRgQAAABXRUJQ" }, ])("preserves original $extension bytes instead of the picker's JPEG", async (original) => { const name = `photo.${original.extension}`; mocks.pickMedia.mockResolvedValue({ canceled: false, assets: [{ ...photo, fileName: name, mimeType: original.mimeType }], }); mocks.readBase64.mockResolvedValue(original.base64); const result = await pickComposerImages({ existingCount: 0 }); expect(result.error).toBeNull(); expect(result.images).toEqual([ expect.objectContaining({ name, mimeType: original.mimeType, dataUrl: `data:${original.mimeType};base64,${original.base64}`, sizeBytes: Buffer.from(original.base64, "base64").byteLength, }), ]); }); it("checks the converted JPEG size even when the HEIC source was smaller", async () => { const oversized = jpeg.slice(0, 4) + "A".repeat(Math.ceil(PROVIDER_SEND_TURN_MAX_IMAGE_BYTES / 3) * 4); mocks.pickMedia.mockResolvedValue({ canceled: false, assets: [{ ...photo, fileSize: 42, base64: oversized }], }); await expect(pickComposerImages({ existingCount: 0 })).resolves.toEqual({ images: [], error: "'photo.HEIC' exceeds the 10 MB attachment limit.", }); }); it("does not relabel unconverted HEIC bytes as JPEG", async () => { mocks.pickMedia.mockResolvedValue({ canceled: false, assets: [{ ...photo, base64: "AAAAGGZ0eXBoZWlj" }], }); const result = await pickComposerImages({ existingCount: 0 }); expect(result.images).toEqual([]); expect(result.error).toContain("not a supported image type"); }); it("retains a converted photo when another original cannot be read", async () => { mocks.pickMedia.mockResolvedValue({ canceled: false, assets: [{ ...photo, fileName: "missing.gif", mimeType: "image/gif" }, photo], }); mocks.readBase64.mockRejectedValue(new Error("missing file")); const result = await pickComposerImages({ existingCount: 0 }); expect(result.images).toEqual([expect.objectContaining({ name: "photo.jpg" })]); expect(result.error).toBe("Failed to read 'missing.gif'."); }); }); describe("photo library videos", () => { const image: ImagePickerAsset = { uri: "file:///picker/photo.png", type: "image", fileName: "photo.png", mimeType: "image/png", fileSize: 3, base64: "YWJj", width: 1, height: 1, }; const video: ImagePickerAsset = { uri: "file:///picker/clip.mov", type: "video", fileName: "clip.mov", mimeType: "video/quicktime", fileSize: 20 * 1024 * 1024, base64: null, width: 1920, height: 1080, }; it("retains mixed photos and videos, keeping video bytes in durable file storage", async () => { mocks.pickMedia.mockResolvedValue({ canceled: false, assets: [image, video] }); mocks.size.mockReturnValue(video.fileSize); const result = await pickComposerMedia({ existingCount: 0, maxVideoBytes: 50 * 1024 * 1024 }); expect(mocks.pickMedia).toHaveBeenCalledWith( expect.objectContaining({ mediaTypes: ["images", "videos"], shouldDownloadFromNetwork: true, }), ); expect(result).toEqual({ attachments: [ expect.objectContaining({ type: "image", dataUrl: "data:image/png;base64,YWJj" }), { id: "attachment-id", type: "file", name: "clip.mov", mimeType: "video/quicktime", sizeBytes: video.fileSize, fileUri: "file:///documents/t3-composer-attachments/attachment-id-clip.mov", }, ], error: null, }); expect(mocks.copy).toHaveBeenCalledWith( video.uri, "file:///documents/t3-composer-attachments/attachment-id-clip.mov", ); expect(mocks.delete).not.toHaveBeenCalled(); }); it("keeps image-only destinations on the image picker path", async () => { mocks.pickMedia.mockResolvedValue({ canceled: false, assets: [image] }); const result = await pickComposerImages({ existingCount: 0 }); expect(mocks.pickMedia).toHaveBeenCalledWith( expect.objectContaining({ mediaTypes: ["images"] }), ); expect(result.images).toEqual([ expect.objectContaining({ type: "image", name: "photo.png" }), ]); expect(result.error).toBeNull(); expect(mocks.copy).not.toHaveBeenCalled(); }); it("does not persist videos when the destination lacks file support", async () => { mocks.pickMedia.mockResolvedValue({ canceled: false, assets: [video, image] }); const result = await pickComposerMedia({ existingCount: 0 }); expect(result.attachments).toEqual([expect.objectContaining({ type: "image" })]); expect(result.error).toBe("Video attachments are unavailable here."); expect(mocks.copy).not.toHaveBeenCalled(); }); it("uses local video metadata when the picker omits its name, MIME type, or size", async () => { mocks.pickMedia.mockResolvedValue({ canceled: false, assets: [{ ...video, fileName: null, mimeType: undefined, fileSize: undefined }], }); const result = await pickComposerMedia({ existingCount: 0, maxVideoBytes: 1024 }); expect(result.error).toBeNull(); expect(result.attachments).toEqual([ expect.objectContaining({ type: "file", name: "clip.mov", mimeType: "video/quicktime", sizeBytes: 42, }), ]); }); it.each([ { reason: "picker size exceeds the server limit", reported: 2 * 1024 * 1024, stored: 42, limit: 1024 * 1024, error: "'clip.mov' exceeds the 1 MB attachment limit.", }, { reason: "actual size exceeds the server limit", reported: 42, stored: 2 * 1024 * 1024, limit: 1024 * 1024, error: "'clip.mov' exceeds the 1 MB attachment limit.", }, { reason: "stored copy is empty", reported: 42, stored: 0, limit: 1024 * 1024, error: "'clip.mov' is empty or could not be read.", }, { reason: "server advertises more than the contract limit", reported: 51 * 1024 * 1024, stored: 42, limit: 80 * 1024 * 1024, error: "'clip.mov' exceeds the 50 MB attachment limit.", }, ])( "rejects a video when $reason while retaining the selected photo", async ({ reported, stored, limit, error }) => { mocks.pickMedia.mockResolvedValue({ canceled: false, assets: [{ ...video, fileSize: reported }, image], }); mocks.size.mockReturnValue(stored); const result = await pickComposerMedia({ existingCount: 0, maxVideoBytes: limit }); expect(result).toEqual({ attachments: [expect.objectContaining({ type: "image" })], error, }); if (stored === 0) { expect(mocks.delete).toHaveBeenCalledWith( "file:///documents/t3-composer-attachments/attachment-id-clip.mov", ); } }, ); it("applies the remaining attachment slots to photos and videos together", async () => { mocks.pickMedia.mockResolvedValue({ canceled: false, assets: [image, video] }); const result = await pickComposerMedia({ existingCount: 7, maxVideoBytes: 50 * 1024 * 1024 }); expect(result.attachments).toEqual([expect.objectContaining({ type: "image" })]); expect(result.error).toBe("You can attach up to 8 attachments per message."); expect(mocks.pickMedia).toHaveBeenCalledWith(expect.objectContaining({ selectionLimit: 1 })); expect(mocks.copy).not.toHaveBeenCalled(); }); it("reports a native video retrieval error and ends the foreground handoff", async () => { mocks.pickMedia.mockRejectedValue(new Error("Could not download video from iCloud.")); await expect(pickComposerMedia({ existingCount: 0, maxVideoBytes: 1024 })).resolves.toEqual({ attachments: [], error: "Could not download video from iCloud.", }); expect(isForegroundHandoffActive()).toBe(false); }); }); it("copies picked files into app-owned storage without loading their contents", async () => { mocks.pickFile.mockResolvedValue({ canceled: false, assets: [ { uri: "file:///downloads/report.pdf", name: "report.pdf", mimeType: "application/pdf", size: 42, }, ], }); await expect(pickComposerFiles({ existingCount: 0 })).resolves.toEqual({ files: [ { id: "attachment-id", type: "file", name: "report.pdf", mimeType: "application/pdf", sizeBytes: 42, fileUri: "file:///documents/t3-composer-attachments/attachment-id-report.pdf", }, ], error: null, }); expect(mocks.copy).toHaveBeenCalledWith( "file:///downloads/report.pdf", "file:///documents/t3-composer-attachments/attachment-id-report.pdf", ); }); it("preserves Android picker metadata instead of using the content URI document id", async () => { const uri = "content://com.android.providers.media.documents/document/video%3A18"; mocks.pickFile.mockResolvedValue({ canceled: false, assets: [ { uri, name: "preview-h264.mp4", mimeType: "video/mp4", size: 620_992, lastModified: 0, }, ], }); mocks.size.mockReturnValue(620_992); await expect(pickComposerFiles({ existingCount: 0 })).resolves.toEqual({ files: [ { id: "attachment-id", type: "file", name: "preview-h264.mp4", mimeType: "video/mp4", sizeBytes: 620_992, fileUri: "file:///documents/t3-composer-attachments/attachment-id-preview-h264.mp4", }, ], error: null, }); expect(mocks.pickFile).toHaveBeenCalledWith({ multiple: true, copyToCacheDirectory: true }); expect(mocks.copy).toHaveBeenCalledWith( uri, "file:///documents/t3-composer-attachments/attachment-id-preview-h264.mp4", ); expect(mocks.delete).not.toHaveBeenCalled(); }); it("persists provider selections that require a readable cache copy", async () => { const providerUri = "content://cloud-provider/documents/clip"; const cachedUri = "file:///cache/DocumentPicker/clip.mp4"; mocks.pickFile.mockImplementation(async (options) => ({ canceled: false, assets: [ { uri: options.copyToCacheDirectory ? cachedUri : providerUri, name: "Cloud recording.mp4", mimeType: "video/mp4", size: 42, lastModified: 0, }, ], })); mocks.copy.mockImplementation((uri: string) => { if (uri === providerUri) throw new Error("The provider URI is not directly readable."); }); const result = await pickComposerFiles({ existingCount: 0 }); expect(result.error).toBeNull(); expect(result.files).toEqual([ expect.objectContaining({ name: "Cloud recording.mp4", fileUri: "file:///documents/t3-composer-attachments/attachment-id-Cloud recording.mp4", }), ]); expect(mocks.copy).toHaveBeenCalledWith(cachedUri, result.files[0]!.fileUri); }); it("ends the foreground handoff when the picker is canceled without copying files", async () => { mocks.pickFile.mockImplementation(async () => { expect(isForegroundHandoffActive()).toBe(true); return { canceled: true, assets: null }; }); await expect(pickComposerFiles({ existingCount: 0 })).resolves.toEqual({ files: [], error: null, }); expect(isForegroundHandoffActive()).toBe(false); expect(mocks.copy).not.toHaveBeenCalled(); expect(mocks.open).not.toHaveBeenCalled(); });Lines 1–500 of 812. Load more
import { beforeEach, describe, expect, it, vi } from "vite-plus/test"; import { PROVIDER_SEND_TURN_MAX_IMAGE_BYTES } from "@t3tools/contracts"; import type { ImagePickerAsset } from "expo-image-picker"; const mocks = vi.hoisted(() => ({ documentUri: "file:///documents", pickFile: vi.fn(), pickMedia: vi.fn(), copy: vi.fn(), delete: vi.fn(), open: vi.fn(), size: vi.fn(), readBase64: vi.fn(), })); vi.mock("expo-file-system", () => { class Directory { readonly uri: string; constructor(root: string | { readonly uri: string }, name: string) { this.uri = `${typeof root === "string" ? root : root.uri}/${name}`; } create(): void {} } class File { readonly uri: string; constructor(source: string | Directory, name?: string) { this.uri = source instanceof Directory ? `${source.uri}/${name}` : source; } get exists(): boolean { return true; } get size(): number | null { return mocks.size(this.uri) ?? null; } get name(): string { return this.uri.split("/").at(-1) ?? ""; } get type(): string { return "video/quicktime"; } create(): void {} open(mode: string) { return mocks.open(this.uri, mode); } async copy(destination: File): Promise<void> { mocks.copy(this.uri, destination.uri); } async base64(): Promise<string> { return mocks.readBase64(this.uri); } delete(): void { mocks.delete(this.uri); } } return { Directory, File, FileMode: { ReadOnly: "r", WriteOnly: "w" }, Paths: { get document() { return { uri: mocks.documentUri }; }, }, }; }); vi.mock("expo-image-picker", () => ({ launchImageLibraryAsync: mocks.pickMedia })); vi.mock("expo-document-picker", () => ({ getDocumentAsync: mocks.pickFile })); vi.mock("./uuid", () => ({ uuidv4: () => "attachment-id" })); import { persistComposerAttachmentFile, pickComposerFiles, pickComposerImages, pickComposerMedia, removePersistedComposerAttachmentFile, } from "./composerImages"; import { isForegroundHandoffActive } from "./foreground-handoff"; import { retainComposerAttachmentFile } from "./composerAttachmentFiles"; describe("composer file attachments", () => { beforeEach(() => { mocks.documentUri = "file:///documents"; mocks.pickFile.mockReset(); mocks.pickMedia.mockReset(); mocks.copy.mockReset(); mocks.delete.mockReset(); mocks.open.mockReset(); mocks.size.mockReset(); mocks.readBase64.mockReset(); mocks.size.mockImplementation((uri: string) => (uri.startsWith("content:") ? null : 42)); }); describe("photo library image conversion", () => { const jpeg = "/9j/2Q=="; const photo: ImagePickerAsset = { uri: "file:///picker/photo.heic", type: "image", fileName: "photo.HEIC", mimeType: "image/heic", fileSize: 20 * 1024 * 1024, base64: jpeg, width: 1, height: 1, }; it.each(["image/heic", "image/heif", undefined])( "attaches the native JPEG conversion with matching metadata when the source MIME is %s", async (mimeType) => { mocks.pickMedia.mockResolvedValue({ canceled: false, assets: [{ ...photo, mimeType }], }); const result = await pickComposerImages({ existingCount: 0 }); expect(result).toEqual({ images: [ { id: "attachment-id", type: "image", name: "photo.jpg", mimeType: "image/jpeg", sizeBytes: 4, dataUrl: `data:image/jpeg;base64,${jpeg}`, previewUri: `data:image/jpeg;base64,${jpeg}`, }, ], error: null, }); }, ); it.each([ { extension: "png", mimeType: "image/png", base64: "iVBORw0KGgo=" }, { extension: "gif", mimeType: "image/gif", base64: "R0lGODlh" }, { extension: "webp", mimeType: "image/webp", base64: "UklGRgQAAABXRUJQ" }, ])("preserves original $extension bytes instead of the picker's JPEG", async (original) => { const name = `photo.${original.extension}`; mocks.pickMedia.mockResolvedValue({ canceled: false, assets: [{ ...photo, fileName: name, mimeType: original.mimeType }], }); mocks.readBase64.mockResolvedValue(original.base64); const result = await pickComposerImages({ existingCount: 0 }); expect(result.error).toBeNull(); expect(result.images).toEqual([ expect.objectContaining({ name, mimeType: original.mimeType, dataUrl: `data:${original.mimeType};base64,${original.base64}`, sizeBytes: Buffer.from(original.base64, "base64").byteLength, }), ]); }); it("checks the converted JPEG size even when the HEIC source was smaller", async () => { const oversized = jpeg.slice(0, 4) + "A".repeat(Math.ceil(PROVIDER_SEND_TURN_MAX_IMAGE_BYTES / 3) * 4); mocks.pickMedia.mockResolvedValue({ canceled: false, assets: [{ ...photo, fileSize: 42, base64: oversized }], }); await expect(pickComposerImages({ existingCount: 0 })).resolves.toEqual({ images: [], error: "'photo.HEIC' exceeds the 10 MB attachment limit.", }); }); it("does not relabel unconverted HEIC bytes as JPEG", async () => { mocks.pickMedia.mockResolvedValue({ canceled: false, assets: [{ ...photo, base64: "AAAAGGZ0eXBoZWlj" }], }); const result = await pickComposerImages({ existingCount: 0 }); expect(result.images).toEqual([]); expect(result.error).toContain("not a supported image type"); }); it("retains a converted photo when another original cannot be read", async () => { mocks.pickMedia.mockResolvedValue({ canceled: false, assets: [{ ...photo, fileName: "missing.gif", mimeType: "image/gif" }, photo], }); mocks.readBase64.mockRejectedValue(new Error("missing file")); const result = await pickComposerImages({ existingCount: 0 }); expect(result.images).toEqual([expect.objectContaining({ name: "photo.jpg" })]); expect(result.error).toBe("Failed to read 'missing.gif'."); }); }); describe("photo library videos", () => { const image: ImagePickerAsset = { uri: "file:///picker/photo.png", type: "image", fileName: "photo.png", mimeType: "image/png", fileSize: 3, base64: "YWJj", width: 1, height: 1, }; const video: ImagePickerAsset = { uri: "file:///picker/clip.mov", type: "video", fileName: "clip.mov", mimeType: "video/quicktime", fileSize: 20 * 1024 * 1024, base64: null, width: 1920, height: 1080, }; it("retains mixed photos and videos, keeping video bytes in durable file storage", async () => { mocks.pickMedia.mockResolvedValue({ canceled: false, assets: [image, video] }); mocks.size.mockReturnValue(video.fileSize); const result = await pickComposerMedia({ existingCount: 0, maxVideoBytes: 50 * 1024 * 1024 }); expect(mocks.pickMedia).toHaveBeenCalledWith( expect.objectContaining({ mediaTypes: ["images", "videos"], shouldDownloadFromNetwork: true, }), ); expect(result).toEqual({ attachments: [ expect.objectContaining({ type: "image", dataUrl: "data:image/png;base64,YWJj" }), { id: "attachment-id", type: "file", name: "clip.mov", mimeType: "video/quicktime", sizeBytes: video.fileSize, fileUri: "file:///documents/t3-composer-attachments/attachment-id-clip.mov", }, ], error: null, }); expect(mocks.copy).toHaveBeenCalledWith( video.uri, "file:///documents/t3-composer-attachments/attachment-id-clip.mov", ); expect(mocks.delete).not.toHaveBeenCalled(); }); it("keeps image-only destinations on the image picker path", async () => { mocks.pickMedia.mockResolvedValue({ canceled: false, assets: [image] }); const result = await pickComposerImages({ existingCount: 0 }); expect(mocks.pickMedia).toHaveBeenCalledWith( expect.objectContaining({ mediaTypes: ["images"] }), ); expect(result.images).toEqual([ expect.objectContaining({ type: "image", name: "photo.png" }), ]); expect(result.error).toBeNull(); expect(mocks.copy).not.toHaveBeenCalled(); }); it("does not persist videos when the destination lacks file support", async () => { mocks.pickMedia.mockResolvedValue({ canceled: false, assets: [video, image] }); const result = await pickComposerMedia({ existingCount: 0 }); expect(result.attachments).toEqual([expect.objectContaining({ type: "image" })]); expect(result.error).toBe("Video attachments are unavailable here."); expect(mocks.copy).not.toHaveBeenCalled(); }); it("uses local video metadata when the picker omits its name, MIME type, or size", async () => { mocks.pickMedia.mockResolvedValue({ canceled: false, assets: [{ ...video, fileName: null, mimeType: undefined, fileSize: undefined }], }); const result = await pickComposerMedia({ existingCount: 0, maxVideoBytes: 1024 }); expect(result.error).toBeNull(); expect(result.attachments).toEqual([ expect.objectContaining({ type: "file", name: "clip.mov", mimeType: "video/quicktime", sizeBytes: 42, }), ]); }); it.each([ { reason: "picker size exceeds the server limit", reported: 2 * 1024 * 1024, stored: 42, limit: 1024 * 1024, error: "'clip.mov' exceeds the 1 MB attachment limit.", }, { reason: "actual size exceeds the server limit", reported: 42, stored: 2 * 1024 * 1024, limit: 1024 * 1024, error: "'clip.mov' exceeds the 1 MB attachment limit.", }, { reason: "stored copy is empty", reported: 42, stored: 0, limit: 1024 * 1024, error: "'clip.mov' is empty or could not be read.", }, { reason: "server advertises more than the contract limit", reported: 51 * 1024 * 1024, stored: 42, limit: 80 * 1024 * 1024, error: "'clip.mov' exceeds the 50 MB attachment limit.", }, ])( "rejects a video when $reason while retaining the selected photo", async ({ reported, stored, limit, error }) => { mocks.pickMedia.mockResolvedValue({ canceled: false, assets: [{ ...video, fileSize: reported }, image], }); mocks.size.mockReturnValue(stored); const result = await pickComposerMedia({ existingCount: 0, maxVideoBytes: limit }); expect(result).toEqual({ attachments: [expect.objectContaining({ type: "image" })], error, }); if (stored === 0) { expect(mocks.delete).toHaveBeenCalledWith( "file:///documents/t3-composer-attachments/attachment-id-clip.mov", ); } }, ); it("applies the remaining attachment slots to photos and videos together", async () => { mocks.pickMedia.mockResolvedValue({ canceled: false, assets: [image, video] }); const result = await pickComposerMedia({ existingCount: 7, maxVideoBytes: 50 * 1024 * 1024 }); expect(result.attachments).toEqual([expect.objectContaining({ type: "image" })]); expect(result.error).toBe("You can attach up to 8 attachments per message."); expect(mocks.pickMedia).toHaveBeenCalledWith(expect.objectContaining({ selectionLimit: 1 })); expect(mocks.copy).not.toHaveBeenCalled(); }); it("reports a native video retrieval error and ends the foreground handoff", async () => { mocks.pickMedia.mockRejectedValue(new Error("Could not download video from iCloud.")); await expect(pickComposerMedia({ existingCount: 0, maxVideoBytes: 1024 })).resolves.toEqual({ attachments: [], error: "Could not download video from iCloud.", }); expect(isForegroundHandoffActive()).toBe(false); }); }); it("copies picked files into app-owned storage without loading their contents", async () => { mocks.pickFile.mockResolvedValue({ canceled: false, assets: [ { uri: "file:///downloads/report.pdf", name: "report.pdf", mimeType: "application/pdf", size: 42, }, ], }); await expect(pickComposerFiles({ existingCount: 0 })).resolves.toEqual({ files: [ { id: "attachment-id", type: "file", name: "report.pdf", mimeType: "application/pdf", sizeBytes: 42, fileUri: "file:///documents/t3-composer-attachments/attachment-id-report.pdf", }, ], error: null, }); expect(mocks.copy).toHaveBeenCalledWith( "file:///downloads/report.pdf", "file:///documents/t3-composer-attachments/attachment-id-report.pdf", ); }); it("preserves Android picker metadata instead of using the content URI document id", async () => { const uri = "content://com.android.providers.media.documents/document/video%3A18"; mocks.pickFile.mockResolvedValue({ canceled: false, assets: [ { uri, name: "preview-h264.mp4", mimeType: "video/mp4", size: 620_992, lastModified: 0, }, ], }); mocks.size.mockReturnValue(620_992); await expect(pickComposerFiles({ existingCount: 0 })).resolves.toEqual({ files: [ { id: "attachment-id", type: "file", name: "preview-h264.mp4", mimeType: "video/mp4", sizeBytes: 620_992, fileUri: "file:///documents/t3-composer-attachments/attachment-id-preview-h264.mp4", }, ], error: null, }); expect(mocks.pickFile).toHaveBeenCalledWith({ multiple: true, copyToCacheDirectory: true }); expect(mocks.copy).toHaveBeenCalledWith( uri, "file:///documents/t3-composer-attachments/attachment-id-preview-h264.mp4", ); expect(mocks.delete).not.toHaveBeenCalled(); }); it("persists provider selections that require a readable cache copy", async () => { const providerUri = "content://cloud-provider/documents/clip"; const cachedUri = "file:///cache/DocumentPicker/clip.mp4"; mocks.pickFile.mockImplementation(async (options) => ({ canceled: false, assets: [ { uri: options.copyToCacheDirectory ? cachedUri : providerUri, name: "Cloud recording.mp4", mimeType: "video/mp4", size: 42, lastModified: 0, }, ], })); mocks.copy.mockImplementation((uri: string) => { if (uri === providerUri) throw new Error("The provider URI is not directly readable."); }); const result = await pickComposerFiles({ existingCount: 0 }); expect(result.error).toBeNull(); expect(result.files).toEqual([ expect.objectContaining({ name: "Cloud recording.mp4", fileUri: "file:///documents/t3-composer-attachments/attachment-id-Cloud recording.mp4", }), ]); expect(mocks.copy).toHaveBeenCalledWith(cachedUri, result.files[0]!.fileUri); }); it("ends the foreground handoff when the picker is canceled without copying files", async () => { mocks.pickFile.mockImplementation(async () => { expect(isForegroundHandoffActive()).toBe(true); return { canceled: true, assets: null }; }); await expect(pickComposerFiles({ existingCount: 0 })).resolves.toEqual({ files: [], error: null, }); expect(isForegroundHandoffActive()).toBe(false); expect(mocks.copy).not.toHaveBeenCalled(); expect(mocks.open).not.toHaveBeenCalled(); });