/** * OPFS (Origin Private File System) Mock for Testing * * This module provides a comprehensive mock implementation of the OPFS API * for testing OPFS-based storage in a Node.js environment. */ import { vi } from 'vitest' // In-memory storage to simulate file system const mockFileSystem: Map> = new Map() // Mock file data interface MockFileData { content: string type: string } /** * Create a mock FileSystemFileHandle */ export function createMockFileHandle(fileName: string, content: string = '{}') { return { kind: 'file', name: fileName, getFile: vi.fn().mockResolvedValue({ text: vi.fn().mockResolvedValue(content), arrayBuffer: vi.fn().mockResolvedValue(new TextEncoder().encode(content).buffer), size: content.length }), createWritable: vi.fn().mockImplementation(() => { const writable = { write: vi.fn().mockImplementation((data: string | ArrayBuffer) => { // Store the data in our mock file system const path = mockFileSystem.get('currentPath') || '/' const dirMap = mockFileSystem.get(path) || new Map() let content: string if (typeof data === 'string') { content = data } else if (data instanceof ArrayBuffer) { content = new TextDecoder().decode(data) } else if (data && typeof data === 'object' && 'type' in data && data.type === 'write') { // Handle FileSystemWriteChunkType const chunk = data as { type: 'write', position?: number, data: string | ArrayBuffer } if (typeof chunk.data === 'string') { content = chunk.data } else { content = new TextDecoder().decode(chunk.data) } } else { content = JSON.stringify(data) } dirMap.set(fileName, { content, type: 'file' }) mockFileSystem.set(path, dirMap) return Promise.resolve() }), close: vi.fn().mockResolvedValue(undefined) } return Promise.resolve(writable) }) } } /** * Create a mock FileSystemDirectoryHandle */ export function createMockDirectoryHandle(dirName: string, entries: Map = new Map()) { const dirPath = mockFileSystem.get('currentPath') || '/' const fullPath = dirPath === '/' ? `/${dirName}` : `${dirPath}/${dirName}` // Store the directory in our mock file system mockFileSystem.set(fullPath, entries) return { kind: 'directory', name: dirName, getDirectoryHandle: vi.fn().mockImplementation((name: string, options: { create?: boolean } = {}) => { mockFileSystem.set('currentPath', fullPath) const dirEntries = mockFileSystem.get(fullPath) || new Map() const entry = dirEntries.get(name) if (entry && entry.type === 'directory') { return Promise.resolve(createMockDirectoryHandle(name, entry.content)) } if (!entry && options.create) { const newDir = new Map() dirEntries.set(name, { content: newDir, type: 'directory' }) mockFileSystem.set(fullPath, dirEntries) return Promise.resolve(createMockDirectoryHandle(name, newDir)) } return Promise.reject(new Error(`Directory not found: ${name}`)) }), getFileHandle: vi.fn().mockImplementation((name: string, options: { create?: boolean } = {}) => { mockFileSystem.set('currentPath', fullPath) const dirEntries = mockFileSystem.get(fullPath) || new Map() const entry = dirEntries.get(name) if (entry && entry.type === 'file') { return Promise.resolve(createMockFileHandle(name, entry.content)) } if (!entry && options.create) { return Promise.resolve(createMockFileHandle(name)) } return Promise.reject(new Error(`File not found: ${name}`)) }), removeEntry: vi.fn().mockImplementation((name: string, options: { recursive?: boolean } = {}) => { const dirEntries = mockFileSystem.get(fullPath) || new Map() if (!dirEntries.has(name)) { return Promise.reject(new Error(`Entry not found: ${name}`)) } const entry = dirEntries.get(name) if (entry.type === 'directory' && !options.recursive) { const subDirPath = fullPath === '/' ? `/${name}` : `${fullPath}/${name}` const subDirEntries = mockFileSystem.get(subDirPath) || new Map() if (subDirEntries.size > 0) { return Promise.reject(new Error(`Directory not empty: ${name}`)) } } dirEntries.delete(name) if (entry.type === 'directory') { const subDirPath = fullPath === '/' ? `/${name}` : `${fullPath}/${name}` mockFileSystem.delete(subDirPath) } return Promise.resolve() }), entries: vi.fn().mockImplementation(function* () { const dirEntries = mockFileSystem.get(fullPath) || new Map() for (const [name, entry] of dirEntries.entries()) { if (entry.type === 'file') { yield [name, createMockFileHandle(name, entry.content)] } else { yield [name, createMockDirectoryHandle(name, entry.content)] } } }) } } /** * Setup OPFS mock environment */ export function setupOPFSMock() { // Clear the mock file system mockFileSystem.clear() mockFileSystem.set('/', new Map()) mockFileSystem.set('currentPath', '/') // Create root directory handle const rootDirectoryHandle = createMockDirectoryHandle('root') // Mock navigator.storage if it doesn't exist if (typeof global.navigator === 'undefined') { // @ts-expect-error - Mocking global global.navigator = {} } // Define storage if it doesn't exist if (typeof global.navigator.storage === 'undefined') { Object.defineProperty(global.navigator, 'storage', { value: {}, writable: true, configurable: true }) } // Mock storage methods global.navigator.storage.getDirectory = vi.fn().mockResolvedValue(rootDirectoryHandle) global.navigator.storage.persisted = vi.fn().mockResolvedValue(true) global.navigator.storage.persist = vi.fn().mockResolvedValue(true) global.navigator.storage.estimate = vi.fn().mockImplementation(() => { // Calculate total size of all files in the mock file system let totalSize = 0 for (const [path, entries] of mockFileSystem.entries()) { if (path === 'currentPath') continue for (const [_, entry] of entries.entries()) { if (entry.type === 'file') { totalSize += entry.content.length } } } return Promise.resolve({ usage: totalSize, quota: 10 * 1024 * 1024 }) // 10MB quota }) return { rootDirectoryHandle, mockFileSystem, reset: () => { mockFileSystem.clear() mockFileSystem.set('/', new Map()) mockFileSystem.set('currentPath', '/') } } } /** * Cleanup OPFS mock environment */ export function cleanupOPFSMock() { // Reset mocks vi.restoreAllMocks() // Clear the mock file system mockFileSystem.clear() }