From 5ff91de0ef195348a806bbf0051acb5a8c921166 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 1 Aug 2025 21:27:12 +0000 Subject: [PATCH 1/2] Initial plan From 5dce66895e5b33a85acd25fbc3660371f67f58d2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 1 Aug 2025 21:40:45 +0000 Subject: [PATCH 2/2] docs: implement comprehensive documentation website Co-authored-by: streamich <9773803+streamich@users.noreply.github.com> --- README.md | 4 + docs/browser-usage.md | 425 ++++++++++++++++++++++++++++++++++++++++ docs/getting-started.md | 140 +++++++++++++ docs/index.md | 149 ++++++++++++++ docs/testing-usage.md | 265 +++++++++++++++++++++++++ tsconfig.json | 18 +- 6 files changed, 1000 insertions(+), 1 deletion(-) create mode 100644 docs/browser-usage.md create mode 100644 docs/getting-started.md create mode 100644 docs/index.md create mode 100644 docs/testing-usage.md diff --git a/README.md b/README.md index d245c5f53..30d7ea579 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,10 @@ npm i memfs ## Resources - Documentation + - **[Complete Documentation](./docs/index.md)** - Comprehensive guides and examples + - **[Getting Started](./docs/getting-started.md)** - Quick start guide with examples + - **[Testing Usage](./docs/testing-usage.md)** - Using memfs in your tests + - **[Browser Usage](./docs/browser-usage.md)** - Running memfs in browsers - [In-memory Node.js `fs` API](./docs/node/index.md) - [In-memory browser File System (Access) API](./docs/fsa/fsa.md) - [`fs` to File System (Access) API adapter](./docs/fsa/fs-to-fsa.md) diff --git a/docs/browser-usage.md b/docs/browser-usage.md new file mode 100644 index 000000000..677a617f3 --- /dev/null +++ b/docs/browser-usage.md @@ -0,0 +1,425 @@ +# Browser Usage Guide + +memfs provides excellent browser support, offering both Node.js-style `fs` API and modern File System Access (FSA) API implementations that work entirely in memory. + +## Installation for Browser + +### Using a Bundler (Recommended) + +With webpack, Vite, Rollup, or similar: + +```bash +npm install memfs +``` + +```js +import { fs, vol } from 'memfs'; +// or +import { fsa } from 'memfs/lib/fsa'; +``` + +### Using CDN + +```html + + + + + + +``` + +## Node.js fs API in Browser + +### Basic File Operations + +```js +import { fs, vol } from 'memfs'; + +// Create files and directories +fs.mkdirSync('/app'); +fs.writeFileSync( + '/app/config.json', + JSON.stringify({ + name: 'My Browser App', + version: '1.0.0', + }), +); + +// Read files +const config = JSON.parse(fs.readFileSync('/app/config.json', 'utf8')); +console.log(config.name); // "My Browser App" + +// List directory contents +const files = fs.readdirSync('/app'); +console.log(files); // ['config.json'] +``` + +### Working with Binary Data + +```js +import { fs } from 'memfs'; + +// Write binary data +const imageData = new Uint8Array([137, 80, 78, 71]); // PNG header +fs.writeFileSync('/images/logo.png', imageData); + +// Read binary data +const buffer = fs.readFileSync('/images/logo.png'); +console.log(buffer); // Uint8Array([137, 80, 78, 71]) +``` + +### Async Operations + +```js +import { fs } from 'memfs'; + +// Using callbacks +fs.writeFile('/data.txt', 'Hello, browser!', err => { + if (err) throw err; + + fs.readFile('/data.txt', 'utf8', (err, data) => { + if (err) throw err; + console.log(data); // "Hello, browser!" + }); +}); + +// Using promises +async function browserFileOps() { + await fs.promises.writeFile('/async-data.txt', 'Async content'); + const content = await fs.promises.readFile('/async-data.txt', 'utf8'); + console.log(content); // "Async content" +} +``` + +## File System Access (FSA) API + +The FSA API provides a modern, Promise-based interface that's compatible with the browser's File System Access API. + +### Basic FSA Usage + +```js +import { fsa } from 'memfs/lib/fsa'; + +// Create a new filesystem +const { dir, core } = fsa({ mode: 'readwrite' }); + +async function fsaExample() { + // Create a directory + const documentsDir = await dir.getDirectoryHandle('documents', { create: true }); + + // Create a file + const file = await documentsDir.getFileHandle('readme.txt', { create: true }); + + // Write to the file + const writable = await file.createWritable(); + await writable.write('Welcome to my browser app!'); + await writable.close(); + + // Read the file + const fileObject = await file.getFile(); + const content = await fileObject.text(); + console.log(content); // "Welcome to my browser app!" + + // Export to JSON for inspection + console.log(core.toJSON()); // { '/documents/readme.txt': 'Welcome to my browser app!' } +} + +fsaExample(); +``` + +### Advanced FSA Operations + +```js +import { fsa } from 'memfs/lib/fsa'; + +const { dir, core } = fsa({ mode: 'readwrite' }); + +async function advancedFSA() { + // Create nested directory structure + const projectDir = await dir.getDirectoryHandle('my-project', { create: true }); + const srcDir = await projectDir.getDirectoryHandle('src', { create: true }); + + // Create multiple files + const files = [ + { name: 'index.js', content: 'console.log("main");' }, + { name: 'utils.js', content: 'export const helper = () => {};' }, + { name: 'config.json', content: JSON.stringify({ env: 'production' }) }, + ]; + + for (const { name, content } of files) { + const file = await srcDir.getFileHandle(name, { create: true }); + const writable = await file.createWritable(); + await writable.write(content); + await writable.close(); + } + + // List directory contents + for await (const [name, handle] of srcDir.entries()) { + console.log(name, handle.kind); // "index.js file", "utils.js file", etc. + } + + // Remove a file + await srcDir.removeEntry('config.json'); + + // Export final structure + console.log(core.toJSON()); +} +``` + +## Browser-Specific Use Cases + +### Single Page Applications (SPA) + +```js +import { vol } from 'memfs'; + +class BrowserAppStorage { + constructor() { + this.vol = vol; + this.initializeStorage(); + } + + initializeStorage() { + // Create application directory structure + this.vol.fromJSON({ + '/app/data/users.json': '[]', + '/app/data/settings.json': JSON.stringify({ theme: 'light' }), + '/app/cache/': null, // empty directory + '/app/temp/': null, + }); + } + + saveUserData(users) { + this.vol.writeFileSync('/app/data/users.json', JSON.stringify(users)); + } + + getUserData() { + return JSON.parse(this.vol.readFileSync('/app/data/users.json', 'utf8')); + } + + updateSettings(settings) { + const current = JSON.parse(this.vol.readFileSync('/app/data/settings.json', 'utf8')); + const updated = { ...current, ...settings }; + this.vol.writeFileSync('/app/data/settings.json', JSON.stringify(updated)); + } + + clearCache() { + // Remove all files in cache directory + const cacheFiles = this.vol.readdirSync('/app/cache'); + cacheFiles.forEach(file => { + this.vol.unlinkSync(`/app/cache/${file}`); + }); + } + + exportData() { + return this.vol.toJSON(); + } +} + +const storage = new BrowserAppStorage(); +``` + +### File Upload Simulation + +```js +import { fs } from 'memfs'; + +class FileUploadSimulator { + constructor() { + fs.mkdirSync('/uploads', { recursive: true }); + } + + async simulateUpload(file) { + // Convert File/Blob to buffer + const arrayBuffer = await file.arrayBuffer(); + const buffer = new Uint8Array(arrayBuffer); + + // Save to memfs + const uploadPath = `/uploads/${file.name}`; + fs.writeFileSync(uploadPath, buffer); + + return { + path: uploadPath, + size: buffer.length, + type: file.type, + lastModified: file.lastModified, + }; + } + + getUploadedFiles() { + const files = fs.readdirSync('/uploads'); + return files.map(filename => { + const path = `/uploads/${filename}`; + const stats = fs.statSync(path); + return { + name: filename, + path, + size: stats.size, + uploaded: stats.mtime, + }; + }); + } + + downloadFile(filename) { + const buffer = fs.readFileSync(`/uploads/${filename}`); + const blob = new Blob([buffer]); + + // Create download link + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = filename; + a.click(); + URL.revokeObjectURL(url); + } +} + +// Usage +const uploader = new FileUploadSimulator(); + +// Handle file input +document.getElementById('fileInput').addEventListener('change', async e => { + for (const file of e.target.files) { + const result = await uploader.simulateUpload(file); + console.log('Uploaded:', result); + } +}); +``` + +### Progressive Web App (PWA) Storage + +```js +import { vol } from 'memfs'; + +class PWAStorage { + constructor() { + this.loadFromLocalStorage(); + } + + loadFromLocalStorage() { + const stored = localStorage.getItem('pwa-memfs'); + if (stored) { + vol.fromJSON(JSON.parse(stored)); + } else { + this.initializeDefaults(); + } + } + + initializeDefaults() { + vol.fromJSON({ + '/pwa/manifest.json': JSON.stringify({ + name: 'My PWA', + version: '1.0.0', + offline: true, + }), + '/pwa/data/': null, + '/pwa/cache/': null, + }); + this.saveToLocalStorage(); + } + + saveToLocalStorage() { + const data = vol.toJSON(); + localStorage.setItem('pwa-memfs', JSON.stringify(data)); + } + + cacheResource(url, content) { + const filename = url.replace(/[^a-zA-Z0-9]/g, '_'); + vol.writeFileSync(`/pwa/cache/${filename}`, content); + this.saveToLocalStorage(); + } + + getCachedResource(url) { + const filename = url.replace(/[^a-zA-Z0-9]/g, '_'); + try { + return vol.readFileSync(`/pwa/cache/${filename}`, 'utf8'); + } catch { + return null; + } + } +} +``` + +## Integration with Bundlers + +### Webpack Configuration + +```js +// webpack.config.js +module.exports = { + resolve: { + fallback: { + fs: false, + path: require.resolve('path-browserify'), + stream: require.resolve('stream-browserify'), + util: require.resolve('util/'), + buffer: require.resolve('buffer/'), + }, + }, + plugins: [ + new webpack.ProvidePlugin({ + Buffer: ['buffer', 'Buffer'], + process: 'process/browser', + }), + ], +}; +``` + +### Vite Configuration + +```js +// vite.config.js +import { defineConfig } from 'vite'; + +export default defineConfig({ + define: { + global: 'globalThis', + }, + resolve: { + alias: { + stream: 'stream-browserify', + util: 'util', + }, + }, +}); +``` + +## Browser Compatibility + +- **Modern browsers**: Full support for both fs API and FSA API +- **IE11+**: Basic fs API support with polyfills +- **Mobile browsers**: Full support +- **Node.js compatibility**: Code written for memfs works in both browser and Node.js + +## Performance Considerations + +- **Memory usage**: Files are stored in memory, so consider total size +- **Persistence**: Data is lost on page refresh unless saved to localStorage/IndexedDB +- **Streaming**: Use createReadStream/createWriteStream for large files +- **Chunking**: Break large operations into smaller chunks to avoid blocking + +## Security Considerations + +- **Same-origin policy**: memfs data is isolated to your application +- **No file system access**: Cannot access real file system (that's the point!) +- **Memory limits**: Browser memory limits apply to stored data + +## Examples and Demos + +Check out the [demo folder](../demo) for complete browser examples: + +- [Git in browser with FSA](../demo/git-fsa/) +- [File system sync tests](../demo/fsa-to-node-sync-tests/) +- [CRUD and CAS operations](../demo/crud-and-cas/) + +## See Also + +- [Getting Started Guide](./getting-started.md) +- [Testing Usage Guide](./testing-usage.md) +- [File System Access API Reference](./fsa/fsa.md) +- [API Documentation](https://streamich.github.io/memfs/) diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 000000000..64b597022 --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,140 @@ +# Getting Started with memfs + +memfs is a complete in-memory file system implementation that provides Node.js `fs` API compatibility and browser File System Access (FSA) API support. + +## Installation + +```bash +npm install memfs +``` + +## Quick Start + +### Basic Usage + +```js +import { fs } from 'memfs'; + +// Write a file +fs.writeFileSync('/hello.txt', 'World!'); + +// Read a file +const content = fs.readFileSync('/hello.txt', 'utf8'); +console.log(content); // World! +``` + +### Creating a File System from JSON + +You can initialize a file system from a JSON object: + +```js +import { fs, vol } from 'memfs'; + +const files = { + './README.md': '# My Project', + './src/index.js': 'console.log("Hello, world!");', + './package.json': JSON.stringify({ name: 'my-project', version: '1.0.0' }), +}; + +vol.fromJSON(files, '/app'); + +// Now you can access the files +console.log(fs.readFileSync('/app/README.md', 'utf8')); // # My Project +console.log(fs.readFileSync('/app/src/index.js', 'utf8')); // console.log("Hello, world!"); +``` + +### Creating Multiple Volumes + +Create separate, isolated file systems: + +```js +import { Volume } from 'memfs'; + +// Create first volume +const vol1 = Volume.fromJSON({ '/config.json': '{"env": "dev"}' }); + +// Create second volume +const vol2 = Volume.fromJSON({ '/config.json': '{"env": "prod"}' }); + +// They are completely separate +console.log(vol1.readFileSync('/config.json', 'utf8')); // {"env": "dev"} +console.log(vol2.readFileSync('/config.json', 'utf8')); // {"env": "prod"} +``` + +## Common Use Cases + +### Testing + +Perfect for unit tests where you need a predictable file system: + +```js +import { vol } from 'memfs'; + +beforeEach(() => { + vol.reset(); // Clear the file system + vol.fromJSON({ + '/project/package.json': JSON.stringify({ name: 'test-project' }), + '/project/src/index.js': 'module.exports = "hello";', + }); +}); + +test('should read package.json', () => { + const pkg = JSON.parse(vol.readFileSync('/project/package.json', 'utf8')); + expect(pkg.name).toBe('test-project'); +}); +``` + +### Browser Environment + +Use with bundlers like webpack or Vite: + +```js +import { fs } from 'memfs'; + +// Create some files in memory +fs.writeFileSync('/data.txt', 'Browser data'); +fs.mkdirSync('/uploads'); + +// Read directory contents +const files = fs.readdirSync('/'); +console.log(files); // ['data.txt', 'uploads'] +``` + +### File System Access API (Browser) + +For modern browsers with File System Access API support: + +```js +import { fsa } from 'memfs/lib/fsa'; + +// Create a new FSA-compatible filesystem +const { dir, core } = fsa({ mode: 'readwrite' }); + +// Create folders and files using FSA API +const folder = await dir.getDirectoryHandle('documents', { create: true }); +const file = await folder.getFileHandle('note.txt', { create: true }); + +// Write to file +const writable = await file.createWritable(); +await writable.write('My note content'); +await writable.close(); + +// Export to JSON +console.log(core.toJSON()); // { '/documents/note.txt': 'My note content' } +``` + +## Next Steps + +- [Testing Usage Guide](./testing-usage.md) - Learn how to use memfs in your tests +- [Browser Usage Guide](./browser-usage.md) - Use memfs in browser environments +- [Node.js fs API Reference](./node/index.md) - Complete Node.js fs API implementation +- [File System Access API Reference](./fsa/fsa.md) - Browser FSA API implementation +- [API Documentation](https://streamich.github.io/memfs/) - Complete TypeDoc reference + +## Examples + +Explore the [demo folder](../demo) for comprehensive examples including: + +- Git integration in the browser +- File system synchronization +- Advanced usage patterns diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 000000000..22e00e91b --- /dev/null +++ b/docs/index.md @@ -0,0 +1,149 @@ +# memfs Documentation + +> In-memory file system for Node.js and browsers + +memfs is a complete in-memory file system implementation that provides Node.js `fs` API compatibility and browser File System Access (FSA) API support. Perfect for testing, browser applications, and any scenario where you need a fast, predictable file system that doesn't touch the disk. + +## Quick Start + +```bash +npm install memfs +``` + +```js +import { fs } from 'memfs'; + +fs.writeFileSync('/hello.txt', 'World!'); +fs.readFileSync('/hello.txt', 'utf8'); // World! +``` + +## Documentation + +### ๐Ÿš€ [Getting Started](./getting-started.md) + +Learn the basics of memfs with practical examples covering file operations, JSON initialization, and common patterns. + +### ๐Ÿงช [Testing Usage](./testing-usage.md) + +Comprehensive guide for using memfs in unit tests, integration tests, and mocking file system operations. + +### ๐ŸŒ [Browser Usage](./browser-usage.md) + +Everything you need to know about using memfs in browsers, including FSA API, bundler configuration, and PWA integration. + +### ๐Ÿ“š API References + +#### Node.js fs API + +- **[Node.js fs API Implementation](./node/index.md)** - Complete guide to the Node.js-compatible fs API +- **[Usage Examples](./node/usage.md)** - Practical examples and patterns +- **[API Reference](./node/reference.md)** - Detailed API documentation + +#### File System Access API + +- **[FSA API Guide](./fsa/fsa.md)** - Browser File System Access API implementation +- **[fs to FSA Adapter](./fsa/fs-to-fsa.md)** - Bridge Node.js fs API to browser FSA API +- **[FSA to fs Adapter](./fsa/fsa-to-fs.md)** - Use FSA API with Node.js fs interface + +#### Experimental APIs + +- **[CRUD File System](./crudfs/index.md)** - High-level CRUD operations `experimental` +- **[CAS File System](./casfs/index.md)** - Content Addressable Storage `experimental` + +### ๐Ÿ› ๏ธ Utilities + +- **[Directory Snapshots](./snapshot/index.md)** - Serialize and restore file system state +- **[Directory Tree Printing](./print/index.md)** - Pretty-print directory structures + +### ๐Ÿ”— [Complete API Documentation](https://streamich.github.io/memfs/) + +TypeDoc-generated API reference with complete type information and examples. + +## Features + +- โœ… **Complete Node.js fs API** - All major fs methods implemented +- โœ… **Browser compatible** - Works in all modern browsers +- โœ… **File System Access API** - Modern browser file system interface +- โœ… **TypeScript support** - Full type definitions included +- โœ… **Zero dependencies** - Lightweight and self-contained +- โœ… **Testing friendly** - Perfect for unit tests and mocking +- โœ… **JSON serialization** - Export/import entire file systems +- โœ… **Stream support** - ReadStream and WriteStream implementations +- โœ… **Symlinks & hardlinks** - Full link support like real file systems +- โœ… **Async/sync APIs** - Both callback and promise-based operations + +## Use Cases + +### Testing + +```js +import { vol } from 'memfs'; + +beforeEach(() => { + vol.reset(); + vol.fromJSON({ + '/project/package.json': JSON.stringify({ name: 'test' }), + '/project/src/index.js': 'console.log("test");', + }); +}); +``` + +### Browser Applications + +```js +import { fsa } from 'memfs/lib/fsa'; + +const { dir } = fsa({ mode: 'readwrite' }); +const file = await dir.getFileHandle('data.txt', { create: true }); +``` + +### Mocking File Systems + +```js +import { patchFs } from 'fs-monkey'; +import { vol } from 'memfs'; + +// Replace Node.js fs with memfs +patchFs(vol); +``` + +## Examples + +Explore comprehensive examples in the [demo directory](../demo/): + +- **[Git in Browser (FSA)](../demo/git-fsa/README.md)** - Git operations with File System Access API +- **[Git in Browser (OPFS)](../demo/git-opfs/README.md)** - Git with Origin Private File System +- **[fs to FSA Bridge](../demo/fsa-to-node-zipfile/README.md)** - Create real files from memory +- **[Sync File Operations](../demo/fsa-to-node-sync-tests/README.md)** - Synchronous browser file operations +- **[CRUD & CAS Demo](../demo/crud-and-cas/README.md)** - Advanced file system patterns + +## Performance + +memfs is designed for speed and efficiency: + +- Files stored directly in memory for instant access +- No I/O blocking or disk latency +- Optimized for both small files and large binary data +- Streaming support for memory-efficient large file operations + +## Ecosystem + +memfs works great with other streamich file system libraries: + +- **[unionfs](https://github.com/streamich/unionfs)** - Union multiple file systems +- **[fs-monkey](https://github.com/streamich/fs-monkey)** - Monkey-patch Node.js fs +- **[linkfs](https://github.com/streamich/linkfs)** - Redirect file system paths +- **[spyfs](https://github.com/streamich/spyfs)** - Spy on file system operations + +## Contributing + +memfs is open source! Check out the [GitHub repository](https://github.com/streamich/memfs) to: + +- Report bugs or request features +- Submit pull requests +- Read the source code +- See test coverage at [streamich.github.io/memfs/coverage/](https://streamich.github.io/memfs/coverage/) + +## License + +Apache 2.0 diff --git a/docs/testing-usage.md b/docs/testing-usage.md new file mode 100644 index 000000000..270cadd02 --- /dev/null +++ b/docs/testing-usage.md @@ -0,0 +1,265 @@ +# Testing with memfs + +memfs is particularly useful for testing because it provides a completely isolated, predictable file system that doesn't interact with your actual disk. + +## Basic Testing Setup + +### Jest Example + +```js +import { vol } from 'memfs'; + +describe('File operations', () => { + beforeEach(() => { + // Reset the file system before each test + vol.reset(); + }); + + test('should create and read files', () => { + vol.writeFileSync('/test.txt', 'test content'); + const content = vol.readFileSync('/test.txt', 'utf8'); + expect(content).toBe('test content'); + }); + + test('should handle JSON files', () => { + const data = { name: 'test', version: '1.0.0' }; + vol.writeFileSync('/package.json', JSON.stringify(data)); + + const content = JSON.parse(vol.readFileSync('/package.json', 'utf8')); + expect(content.name).toBe('test'); + expect(content.version).toBe('1.0.0'); + }); +}); +``` + +### Mocha Example + +```js +import { vol } from 'memfs'; +import { expect } from 'chai'; + +describe('File system tests', () => { + beforeEach(() => { + vol.reset(); + }); + + it('should support directory operations', () => { + vol.mkdirSync('/projects'); + vol.mkdirSync('/projects/myapp'); + vol.writeFileSync('/projects/myapp/index.js', 'console.log("Hello");'); + + const dirs = vol.readdirSync('/projects'); + expect(dirs).to.include('myapp'); + + const files = vol.readdirSync('/projects/myapp'); + expect(files).to.include('index.js'); + }); +}); +``` + +## Testing File System Interactions + +### Mocking Node.js fs Module + +Use with libraries like `fs-monkey` to completely replace Node.js fs: + +```js +import { vol } from 'memfs'; +import { patchFs } from 'fs-monkey'; + +describe('Code that uses fs', () => { + let restoreFs; + + beforeEach(() => { + vol.reset(); + vol.fromJSON({ + '/app/config.json': JSON.stringify({ database: 'sqlite' }), + '/app/data/users.json': JSON.stringify([{ id: 1, name: 'John' }]), + }); + + // Replace Node.js fs with memfs + restoreFs = patchFs(vol); + }); + + afterEach(() => { + // Restore original fs + restoreFs(); + }); + + test('application reads config correctly', () => { + // Your application code that uses fs will now use memfs + const config = require('./my-app').loadConfig('/app/config.json'); + expect(config.database).toBe('sqlite'); + }); +}); +``` + +### Testing Async Operations + +```js +import { vol } from 'memfs'; +import { promisify } from 'util'; + +describe('Async file operations', () => { + beforeEach(() => { + vol.reset(); + }); + + test('should handle promises', async () => { + const writeFile = promisify(vol.writeFile.bind(vol)); + const readFile = promisify(vol.readFile.bind(vol)); + + await writeFile('/async-test.txt', 'async content'); + const content = await readFile('/async-test.txt', 'utf8'); + expect(content).toBe('async content'); + }); + + test('should handle fs promises API', async () => { + await vol.promises.writeFile('/promise-test.txt', 'promise content'); + const content = await vol.promises.readFile('/promise-test.txt', 'utf8'); + expect(content).toBe('promise content'); + }); +}); +``` + +## Advanced Testing Patterns + +### Setup Complex File Structures + +```js +import { vol } from 'memfs'; + +function setupProjectStructure() { + vol.fromJSON({ + '/project/package.json': JSON.stringify({ + name: 'test-project', + version: '1.0.0', + scripts: { + test: 'jest', + build: 'webpack', + }, + }), + '/project/src/index.js': ` + const config = require('./config'); + module.exports = { config }; + `, + '/project/src/config.js': 'module.exports = { env: "test" };', + '/project/tests/index.test.js': 'test("placeholder", () => {});', + '/project/node_modules/some-module/index.js': 'module.exports = "mock module";', + }); +} + +describe('Project structure tests', () => { + beforeEach(() => { + vol.reset(); + setupProjectStructure(); + }); + + test('should have correct project structure', () => { + expect(vol.existsSync('/project/package.json')).toBe(true); + expect(vol.existsSync('/project/src/index.js')).toBe(true); + expect(vol.existsSync('/project/tests/index.test.js')).toBe(true); + }); +}); +``` + +### Testing Error Conditions + +```js +import { vol } from 'memfs'; + +describe('Error handling', () => { + beforeEach(() => { + vol.reset(); + }); + + test('should throw ENOENT for missing files', () => { + expect(() => { + vol.readFileSync('/nonexistent.txt'); + }).toThrow(/ENOENT/); + }); + + test('should throw EEXIST when creating existing files', () => { + vol.writeFileSync('/existing.txt', 'content'); + + expect(() => { + vol.writeFileSync('/existing.txt', 'new content', { flag: 'wx' }); + }).toThrow(/EEXIST/); + }); + + test('should handle permission-like errors', () => { + vol.mkdirSync('/readonly'); + + // Simulate readonly behavior by catching and re-throwing + expect(() => { + vol.writeFileSync('/readonly/test.txt', 'content'); + }).not.toThrow(); // memfs doesn't enforce permissions, but your app might + }); +}); +``` + +### Integration with Testing Frameworks + +#### Testing with Snapshots + +```js +import { vol } from 'memfs'; + +describe('File system snapshots', () => { + test('should match file system structure', () => { + vol.fromJSON({ + '/app/index.js': 'console.log("app");', + '/app/lib/utils.js': 'module.exports = {};', + '/app/test/index.test.js': 'test("", () => {});', + }); + + const snapshot = vol.toJSON(); + expect(snapshot).toMatchSnapshot(); + }); +}); +``` + +#### Parameterized Tests + +```js +import { vol } from 'memfs'; + +describe.each([ + ['text file', '/test.txt', 'text content'], + ['json file', '/test.json', JSON.stringify({ test: true })], + ['empty file', '/empty.txt', ''], +])('File operations with %s', (fileType, path, content) => { + beforeEach(() => { + vol.reset(); + }); + + test(`should handle ${fileType}`, () => { + vol.writeFileSync(path, content); + const result = vol.readFileSync(path, 'utf8'); + expect(result).toBe(content); + }); +}); +``` + +## Testing Best Practices + +1. **Always reset between tests**: Use `vol.reset()` in `beforeEach` to ensure test isolation +2. **Use descriptive file paths**: Makes debugging easier when tests fail +3. **Test both sync and async operations**: Ensure your code works with both patterns +4. **Mock external dependencies**: Use memfs with fs-monkey to isolate file system interactions +5. **Test error conditions**: Verify your code handles missing files, permission errors, etc. +6. **Use JSON setup for complex structures**: `vol.fromJSON()` is great for setting up complex file trees + +## Common Gotchas + +- **Path separators**: memfs uses `/` even on Windows in the in-memory representation +- **Encoding**: Always specify encoding when reading text files: `vol.readFileSync(path, 'utf8')` +- **Cleanup**: Remember to call `vol.reset()` between tests to avoid interference +- **Absolute paths**: memfs works with absolute paths; relative paths are resolved from the current working directory + +## See Also + +- [Getting Started Guide](./getting-started.md) +- [Browser Usage Guide](./browser-usage.md) +- [Node.js fs API Reference](./node/index.md) +- [API Documentation](https://streamich.github.io/memfs/) diff --git a/tsconfig.json b/tsconfig.json index 00dda587a..ba1816d46 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -27,6 +27,22 @@ "src/node-to-crud/index.ts", "src/node-to-fsa/index.ts" ], - "out": "typedocs" + "out": "typedocs", + "name": "memfs", + "includeVersion": true, + "readme": "docs/index.md", + "categorizeByGroup": true, + "sort": ["source-order"], + "excludePrivate": true, + "excludeProtected": true, + "excludeExternals": true, + "hideGenerator": true, + "searchInComments": true, + "navigationLinks": { + "Getting Started": "https://github.com/streamich/memfs/blob/master/docs/getting-started.md", + "Testing Guide": "https://github.com/streamich/memfs/blob/master/docs/testing-usage.md", + "Browser Guide": "https://github.com/streamich/memfs/blob/master/docs/browser-usage.md", + "GitHub": "https://github.com/streamich/memfs" + } } }