Files
MetonaSqlark/tests/helpers/opfs-mock.ts
T
thzxx 334067d89e
CI / test (18.x) (push) Successful in 10m10s
CI / test (20.x) (push) Successful in 10m10s
CI / test (22.x) (push) Successful in 10m6s
CI / e2e (push) Successful in 9m51s
CI / test (24.x) (push) Successful in 10m28s
release: v0.5.1 — 存储后端生产级硬化(CRC-32/全库加密/WAL分片/页面化存储/多标签页锁/e2e)+ 深度审查修复(假实现接线/死代码清理)
2026-08-10 12:07:00 +08:00

89 lines
3.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 测试共享 — 真实语义 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<string, MockOpfsFile>): {
files: Map<string, MockOpfsFile>;
dir: {
getFileHandle: (name: string, opts?: { create?: boolean }) => Promise<{
getFile: () => Promise<{ size: number; arrayBuffer: () => Promise<ArrayBuffer> }>;
createWritable: (wOpts?: { keepExistingData?: boolean }) => Promise<{
write: (arg: ArrayBuffer | { type: string; position: number; data: ArrayBuffer }) => Promise<void>;
close: () => Promise<void>;
}>;
}>;
entries: () => AsyncGenerator<[string]>;
removeEntry: (name: string) => Promise<void>;
};
} {
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<void>;
close: () => Promise<void>;
} = {
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 };
}