import { expect, describe, test, beforeEach, afterEach } from 'bun:test' import { mkdirSync, writeFileSync, rmSync, existsSync } from 'fs' import { join, resolve } from 'path' import { fs } from '../fs' const TEST_DIR = resolve('./tmp/shrimp-fs-test') const CWD = process.cwd() beforeEach(() => { if (existsSync(TEST_DIR)) { rmSync(TEST_DIR, { recursive: true }) } mkdirSync(TEST_DIR, { recursive: true }) }) afterEach(() => { process.chdir(CWD) if (existsSync(TEST_DIR)) { rmSync(TEST_DIR, { recursive: true }) } }) describe('fs - directory operations', () => { test('fs.ls lists directory contents', () => { writeFileSync(join(TEST_DIR, 'file1.txt'), 'content1') writeFileSync(join(TEST_DIR, 'file2.txt'), 'content2') const result = fs.ls(TEST_DIR) expect(result).toContain('file1.txt') expect(result).toContain('file2.txt') }) test('fs.mkdir creates directory', () => { const newDir = join(TEST_DIR, 'newdir') fs.mkdir(newDir) expect(existsSync(newDir)).toBe(true) }) test('fs.rmdir removes empty directory', () => { const dir = join(TEST_DIR, 'toremove') mkdirSync(dir) fs.rmdir(dir) expect(existsSync(dir)).toBe(false) }) test('fs.pwd returns current working directory', () => { const result = fs.pwd() expect(typeof result).toBe('string') expect(result.length).toBeGreaterThan(0) }) test('fs.cd changes current working directory', () => { const originalCwd = process.cwd() fs.cd(TEST_DIR) expect(process.cwd()).toBe(TEST_DIR) process.chdir(originalCwd) // restore }) }) describe('fs - reading', () => { test('fs.read reads file contents as string', () => { const file = join(TEST_DIR, 'test.txt') writeFileSync(file, 'hello world') const result = fs.read(file) expect(result).toBe('hello world') }) test('fs.cat is alias for fs.read', () => { const file = join(TEST_DIR, 'test.txt') writeFileSync(file, 'hello world') const result = fs.cat(file) expect(result).toBe('hello world') }) test('fs.read-bytes reads file as buffer', () => { const file = join(TEST_DIR, 'test.bin') writeFileSync(file, Buffer.from([1, 2, 3, 4])) const result = fs['read-bytes'](file) expect(result).toBeInstanceOf(Array) expect(result).toEqual([1, 2, 3, 4]) }) }) describe('fs - writing', () => { test('fs.write writes string to file', async () => { const file = join(TEST_DIR, 'output.txt') fs.write(file, 'test content') const content = Bun.file(file).text() expect(await content).toBe('test content') }) test('fs.append appends to existing file', async () => { const file = join(TEST_DIR, 'append.txt') writeFileSync(file, 'first') fs.append(file, ' second') const content = await Bun.file(file).text() expect(content).toBe('first second') }) }) describe('fs - file operations', () => { test('fs.rm removes file', () => { const file = join(TEST_DIR, 'remove.txt') writeFileSync(file, 'content') fs.rm(file) expect(existsSync(file)).toBe(false) }) test('fs.delete is alias for fs.rm', () => { const file = join(TEST_DIR, 'delete.txt') writeFileSync(file, 'content') fs.delete(file) expect(existsSync(file)).toBe(false) }) test('fs.copy copies file', async () => { const src = join(TEST_DIR, 'source.txt') const dest = join(TEST_DIR, 'dest.txt') writeFileSync(src, 'content') fs.copy(src, dest) expect(await Bun.file(dest).text()).toBe('content') }) test('fs.cp is alias for fs.copy', async () => { const src = join(TEST_DIR, 'source2.txt') const dest = join(TEST_DIR, 'dest2.txt') writeFileSync(src, 'content') fs.cp(src, dest) expect(await Bun.file(dest).text()).toBe('content') }) test('fs.move moves file', async () => { const src = join(TEST_DIR, 'source.txt') const dest = join(TEST_DIR, 'moved.txt') writeFileSync(src, 'content') fs.move(src, dest) expect(existsSync(src)).toBe(false) expect(await Bun.file(dest).text()).toBe('content') }) test('fs.mv is alias for fs.move', async () => { const src = join(TEST_DIR, 'source2.txt') const dest = join(TEST_DIR, 'moved2.txt') writeFileSync(src, 'content') fs.mv(src, dest) expect(existsSync(src)).toBe(false) expect(await Bun.file(dest).text()).toBe('content') }) }) describe('fs - path operations', () => { test('fs.basename extracts filename from path', () => { expect(fs.basename('/path/to/file.txt')).toBe('file.txt') expect(fs.basename('/path/to/dir/')).toBe('dir') }) test('fs.dirname extracts directory from path', () => { expect(fs.dirname('/path/to/file.txt')).toBe('/path/to') expect(fs.dirname('/path/to/dir/')).toBe('/path/to') }) test('fs.extname extracts file extension', () => { expect(fs.extname('file.txt')).toBe('.txt') expect(fs.extname('file.tar.gz')).toBe('.gz') expect(fs.extname('noext')).toBe('') }) test('fs.join joins path segments', () => { expect(fs.join('path', 'to', 'file.txt')).toBe('path/to/file.txt') expect(fs.join('/absolute', 'path')).toBe('/absolute/path') }) test('fs.resolve resolves to absolute path', () => { const result = fs.resolve('relative', 'path') expect(result.startsWith('/')).toBe(true) expect(result).toContain('relative') }) }) describe('fs - file info', () => { test('fs.stat returns file stats', () => { const file = join(TEST_DIR, 'stat.txt') writeFileSync(file, 'content') const stats = fs.stat(file) expect(stats).toHaveProperty('size') expect(stats).toHaveProperty('mtime') expect(stats.size).toBe(7) // 'content' is 7 bytes }) test('fs.exists? checks if path exists', () => { const file = join(TEST_DIR, 'exists.txt') expect(fs['exists?'](file)).toBe(false) writeFileSync(file, 'content') expect(fs['exists?'](file)).toBe(true) }) test('fs.file? checks if path is a file', () => { const file = join(TEST_DIR, 'isfile.txt') writeFileSync(file, 'content') expect(fs['file?'](file)).toBe(true) expect(fs['file?'](TEST_DIR)).toBe(false) }) test('fs.dir? checks if path is a directory', () => { const dir = join(TEST_DIR, 'isdir') mkdirSync(dir) expect(fs['dir?'](dir)).toBe(true) expect(fs['dir?'](join(TEST_DIR, 'isfile.txt'))).toBe(false) }) test('fs.symlink? checks if path is a symbolic link', () => { const file = join(TEST_DIR, 'target.txt') const link = join(TEST_DIR, 'link.txt') writeFileSync(file, 'content') fs.symlink(file, link) expect(fs['symlink?'](link)).toBe(true) expect(fs['symlink?'](file)).toBe(false) }) test('fs.exec? checks if file is executable', () => { const file = join(TEST_DIR, 'script.sh') writeFileSync(file, '#!/bin/bash\necho hello') fs.chmod(file, 0o755) expect(fs['exec?'](file)).toBe(true) fs.chmod(file, 0o644) expect(fs['exec?'](file)).toBe(false) }) test('fs.size returns file size in bytes', () => { const file = join(TEST_DIR, 'sizeme.txt') writeFileSync(file, 'content') expect(fs.size(file)).toBe(7) // 'content' is 7 bytes }) }) describe('fs - permissions', () => { test('fs.chmod changes file permissions with octal number', () => { const file = join(TEST_DIR, 'perms.txt') writeFileSync(file, 'content') fs.chmod(file, 0o755) expect(fs['exec?'](file)).toBe(true) fs.chmod(file, 0o644) expect(fs['exec?'](file)).toBe(false) }) test('fs.chmod changes file permissions with string', () => { const file = join(TEST_DIR, 'perms2.txt') writeFileSync(file, 'content') fs.chmod(file, '755') expect(fs['exec?'](file)).toBe(true) fs.chmod(file, '644') expect(fs['exec?'](file)).toBe(false) }) }) describe('fs - symlinks', () => { test('fs.symlink creates symbolic link', () => { const target = join(TEST_DIR, 'target.txt') const link = join(TEST_DIR, 'link.txt') writeFileSync(target, 'content') fs.symlink(target, link) expect(fs['symlink?'](link)).toBe(true) expect(fs.read(link)).toBe('content') }) test('fs.readlink reads symbolic link target', () => { const target = join(TEST_DIR, 'target.txt') const link = join(TEST_DIR, 'link.txt') writeFileSync(target, 'content') fs.symlink(target, link) expect(fs.readlink(link)).toBe(target) }) }) describe('fs - other', () => { test('fs.glob matches file patterns', () => { writeFileSync(join(TEST_DIR, 'file1.txt'), '') writeFileSync(join(TEST_DIR, 'file2.txt'), '') writeFileSync(join(TEST_DIR, 'file3.md'), '') const result = fs.glob(join(TEST_DIR, '*.txt')) expect(result).toHaveLength(2) expect(result).toContain(join(TEST_DIR, 'file1.txt')) expect(result).toContain(join(TEST_DIR, 'file2.txt')) }) test('fs.watch calls callback on file change', async () => { const file = join(TEST_DIR, 'watch.txt') writeFileSync(file, 'initial') let called = false const watcher = fs.watch(file, () => { called = true }) // Trigger change await new Promise(resolve => setTimeout(resolve, 100)) writeFileSync(file, 'updated') // Wait for watcher await new Promise(resolve => setTimeout(resolve, 500)) expect(called).toBe(true) watcher.close?.() }) })