Files
openchamber/packages/ui/src/sync/input-store.test.ts
T

139 lines
5.1 KiB
TypeScript

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<FileReader>) => unknown) | null = null
onerror: ((this: FileReader, event: ProgressEvent<FileReader>) => unknown) | null = null
onabort: ((this: FileReader, event: ProgressEvent<FileReader>) => 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<void>) => {
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<FileReader>)
}
const rejectReader = (reader: MockFileReader) => {
reader.error = new DOMException("read failed", "NotReadableError")
reader.onerror?.call(reader as unknown as FileReader, {} as ProgressEvent<FileReader>)
}
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"])
})
})