/** * 测试共享 — 真实语义 OPFS mock * * 模拟 OPFS 的完整语义: * - createWritable({ keepExistingData }) + position 追加写 * - 文件内容以 ArrayBuffer 存储(跨 backend 实例共享于同一 Map) * - entries() 迭代、removeEntry 删除 * * 用法: * const files = installOPFSMock(new Map()); * const backend = new OPFSBackend(); * await backend.open('any'); */ export interface MockOpfsFile { content: ArrayBuffer; } export function installOPFSMock(files: Map): { files: Map; dir: { getFileHandle: (name: string, opts?: { create?: boolean }) => Promise<{ getFile: () => Promise<{ size: number; arrayBuffer: () => Promise }>; createWritable: (wOpts?: { keepExistingData?: boolean }) => Promise<{ write: (arg: ArrayBuffer | { type: string; position: number; data: ArrayBuffer }) => Promise; close: () => Promise; }>; }>; entries: () => AsyncGenerator<[string]>; removeEntry: (name: string) => Promise; }; } { const getFileHandle = async (name: string, opts?: { create?: boolean }) => { if (!files.has(name)) { if (!opts?.create) throw new Error(`NotFoundError: ${name}`); files.set(name, { content: new ArrayBuffer(0) }); } const entry = files.get(name)!; return { getFile: async () => ({ size: entry.content.byteLength, arrayBuffer: async () => entry.content }), createWritable: async (wOpts?: { keepExistingData?: boolean }) => { const w: { write: (arg: ArrayBuffer | { type: string; position: number; data: ArrayBuffer }) => Promise; close: () => Promise; } = { write: async (arg) => { const keepExisting = wOpts?.keepExistingData ?? false; const chunk = (arg as { type?: string }).type === 'write' ? { data: (arg as { data: ArrayBuffer }).data, position: (arg as { position: number }).position } : { data: arg as ArrayBuffer, position: keepExisting ? entry.content.byteLength : 0 }; const merged = new Uint8Array(chunk.position + chunk.data.byteLength); if (keepExisting || chunk.position > 0) { merged.set(new Uint8Array(entry.content), 0); } merged.set(new Uint8Array(chunk.data), chunk.position); entry.content = merged.buffer; }, close: async () => { /* no-op */ }, }; return w; }, }; }; const dir = { getFileHandle, entries: async function* () { for (const [name] of files) yield [name]; }, removeEntry: async (name: string) => { files.delete(name); }, }; Object.defineProperty(globalThis, 'navigator', { value: { storage: { getDirectory: async () => ({ getDirectoryHandle: async (_name: string, _opts?: unknown) => dir, }), }, }, configurable: true, writable: true, }); return { files, dir }; }