import { beforeEach, describe, expect, test } from "bun:test" import { useInputStore } from "./input-store" class MockFileReader { result: string | ArrayBuffer | null = null onload: ((this: FileReader, event: ProgressEvent) => unknown) | null = null onerror: ((this: FileReader, event: ProgressEvent) => unknown) | null = null onabort: ((this: FileReader, event: ProgressEvent) => unknown) | null = null error: DOMException | null = null readAsDataURL() { pendingReaders.push(this) } } const pendingReaders: MockFileReader[] = [] const originalFileReader = globalThis.FileReader const restoreFileReader = () => { pendingReaders.length = 0 globalThis.FileReader = originalFileReader } const testWithMockFileReader = (name: string, fn: () => Promise) => { test(name, async () => { try { await fn() } finally { restoreFileReader() } }) } const resolveReader = (reader: MockFileReader, result: string) => { reader.result = result reader.onload?.call(reader as unknown as FileReader, {} as ProgressEvent) } const rejectReader = (reader: MockFileReader) => { reader.error = new DOMException("read failed", "NotReadableError") reader.onerror?.call(reader as unknown as FileReader, {} as ProgressEvent) } describe("input-store attachments", () => { beforeEach(() => { pendingReaders.length = 0 globalThis.FileReader = MockFileReader as unknown as typeof FileReader useInputStore.setState({ pendingInputText: null, pendingInputMode: "replace", pendingSyntheticParts: null, activeEditorFile: null, }) useInputStore.getState().setAttachedFiles([]) }) testWithMockFileReader("does not attach a local file that finishes reading after attachments are cleared", async () => { const addPromise = useInputStore.getState().addAttachedFile(new File(["hello"], "hello.txt", { type: "text/plain" })) expect(pendingReaders).toHaveLength(1) useInputStore.getState().clearAttachedFiles() resolveReader(pendingReaders[0], "data:text/plain;base64,aGVsbG8=") await addPromise expect(useInputStore.getState().attachedFiles).toEqual([]) }) testWithMockFileReader("does not attach a local file after attached files are replaced", async () => { const addPromise = useInputStore.getState().addAttachedFile(new File(["hello"], "hello.txt", { type: "text/plain" })) expect(pendingReaders).toHaveLength(1) useInputStore.getState().setAttachedFiles([]) resolveReader(pendingReaders[0], "data:text/plain;base64,aGVsbG8=") await addPromise expect(useInputStore.getState().attachedFiles).toEqual([]) }) testWithMockFileReader("does not attach a local file after attached files are restored", async () => { const addPromise = useInputStore.getState().addAttachedFile(new File(["hello"], "hello.txt", { type: "text/plain" })) expect(pendingReaders).toHaveLength(1) const restored = new File(["restored"], "restored.txt", { type: "text/plain" }) useInputStore.getState().setAttachedFiles([{ id: "restored", file: restored, dataUrl: "data:text/plain;base64,cmVzdG9yZWQ=", mimeType: "text/plain", filename: "restored.txt", size: restored.size, source: "local", }]) resolveReader(pendingReaders[0], "data:text/plain;base64,aGVsbG8=") await addPromise expect(useInputStore.getState().attachedFiles.map((file) => file.filename)).toEqual(["restored.txt"]) }) testWithMockFileReader("does not attach a VS Code selection that finishes reading after attachments are cleared", async () => { const addPromise = useInputStore.getState().addVSCodeSelectionAttachment( "/workspace/hello.txt", new File(["hello"], "hello.txt", { type: "text/plain" }) ) expect(pendingReaders).toHaveLength(1) useInputStore.getState().clearAttachedFiles() resolveReader(pendingReaders[0], "data:text/plain;base64,aGVsbG8=") await addPromise expect(useInputStore.getState().attachedFiles).toEqual([]) }) test("does not leave local file reads pending after a reader error", async () => { const addPromise = useInputStore.getState().addAttachedFile(new File(["hello"], "hello.txt", { type: "text/plain" })) expect(pendingReaders).toHaveLength(1) rejectReader(pendingReaders[0]) await addPromise expect(useInputStore.getState().attachedFiles).toEqual([]) }) test("cleans up pending VS Code selection keys after a reader error", async () => { const file = new File(["hello"], "hello.txt", { type: "text/plain" }) const firstAdd = useInputStore.getState().addVSCodeSelectionAttachment("/workspace/hello.txt", file) expect(pendingReaders).toHaveLength(1) rejectReader(pendingReaders[0]) await firstAdd const secondAdd = useInputStore.getState().addVSCodeSelectionAttachment("/workspace/hello.txt", file) expect(pendingReaders).toHaveLength(2) resolveReader(pendingReaders[1], "data:text/plain;base64,aGVsbG8=") await secondAdd expect(useInputStore.getState().attachedFiles.map((attached) => attached.filename)).toEqual(["hello.txt"]) }) })