/** * vfs.readdir()'s `recursive` option: typed since 7.30 but never read, so it * silently behaved exactly like `recursive: false`. This pins the real, * documented contract: a recursive listing returns every descendant (files * AND directories, all depths) as paths RELATIVE TO THE QUERIED DIRECTORY — * the same convention Node's `fs.readdir(dir, { recursive: true })` uses — * for both the plain string-array form and the `withFileTypes` VFSDirent * form (whose `name` carries that same relative path when recursive). */ import { describe, it, expect, beforeEach, afterEach } from 'vitest' import { Brainy } from '../../src/brainy.js' import type { VFSDirent } from '../../src/vfs/types.js' describe('vfs.readdir() recursive option', () => { let brain: Brainy beforeEach(async () => { brain = new Brainy({ requireSubtype: false, storage: { type: 'memory' }, silent: true }) await brain.init() // Build: // /a/b.txt // /a/sub/c.txt // /a/sub/deeper/d.txt // /a/sub2/ (empty directory) await brain.vfs.writeFile('/a/b.txt', 'B') await brain.vfs.writeFile('/a/sub/c.txt', 'C') await brain.vfs.writeFile('/a/sub/deeper/d.txt', 'D') await brain.vfs.mkdir('/a/sub2', { recursive: true }) }) afterEach(async () => { await brain.close() }) it('non-recursive (default) still returns only direct children, by basename', async () => { const entries = await brain.vfs.readdir('/a') as string[] expect([...entries].sort()).toEqual(['b.txt', 'sub', 'sub2']) }) it('recursive: true returns every descendant as a path relative to the queried directory', async () => { const entries = await brain.vfs.readdir('/a', { recursive: true }) as string[] expect([...entries].sort()).toEqual([ 'b.txt', 'sub', 'sub/c.txt', 'sub/deeper', 'sub/deeper/d.txt', 'sub2' ]) }) it('recursive: true at the root has no leading slash on relative entries', async () => { const entries = await brain.vfs.readdir('/', { recursive: true }) as string[] expect(entries).toContain('a') expect(entries).toContain('a/b.txt') expect(entries).toContain('a/sub/deeper/d.txt') for (const entry of entries) { expect(entry.startsWith('/')).toBe(false) } }) it('recursive + withFileTypes: VFSDirent.name is the relative path, .path stays absolute', async () => { const entries = await brain.vfs.readdir('/a', { recursive: true, withFileTypes: true }) as VFSDirent[] const byName = new Map(entries.map((e) => [e.name, e])) const nested = byName.get('sub/deeper/d.txt') expect(nested).toBeDefined() expect(nested!.path).toBe('/a/sub/deeper/d.txt') expect(nested!.type).toBe('file') const nestedDir = byName.get('sub/deeper') expect(nestedDir).toBeDefined() expect(nestedDir!.path).toBe('/a/sub/deeper') expect(nestedDir!.type).toBe('directory') // Non-recursive VFSDirent behavior is unchanged: name is the basename. const direct = await brain.vfs.readdir('/a', { withFileTypes: true }) as VFSDirent[] const directEntry = direct.find((e) => e.path === '/a/b.txt') expect(directEntry?.name).toBe('b.txt') }) it('recursive + filter composes: only files survive a type filter', async () => { const entries = await brain.vfs.readdir('/a', { recursive: true, filter: { type: 'file' } }) as string[] expect([...entries].sort()).toEqual(['b.txt', 'sub/c.txt', 'sub/deeper/d.txt']) }) })