mirror of
https://github.com/browseros-ai/BrowserOS.git
synced 2026-05-20 12:30:04 +00:00
git-subtree-dir: packages/browseros-agent git-subtree-mainline:8f148d0918git-subtree-split:90bd4be300
238 lines
6.5 KiB
TypeScript
238 lines
6.5 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it } from 'bun:test'
|
|
import { mkdir, rm, writeFile } from 'node:fs/promises'
|
|
import { tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
import {
|
|
detectLineEnding,
|
|
isBinaryPath,
|
|
normalizeToLF,
|
|
restoreLineEndings,
|
|
stripBom,
|
|
truncateHead,
|
|
truncateLine,
|
|
truncateTail,
|
|
walkFiles,
|
|
} from '../../../src/tools/filesystem/utils'
|
|
|
|
describe('truncateHead', () => {
|
|
it('does not truncate small content', () => {
|
|
const result = truncateHead('a\nb\nc')
|
|
expect(result.truncated).toBe(false)
|
|
expect(result.content).toBe('a\nb\nc')
|
|
expect(result.totalLines).toBe(3)
|
|
expect(result.keptLines).toBe(3)
|
|
})
|
|
|
|
it('truncates by line count', () => {
|
|
const content = Array.from({ length: 100 }, (_, i) => `line ${i}`).join(
|
|
'\n',
|
|
)
|
|
const result = truncateHead(content, 10)
|
|
expect(result.truncated).toBe(true)
|
|
expect(result.keptLines).toBe(10)
|
|
expect(result.totalLines).toBe(100)
|
|
})
|
|
|
|
it('truncates by byte size', () => {
|
|
const content = Array.from({ length: 100 }, () => 'x'.repeat(200)).join(
|
|
'\n',
|
|
)
|
|
const result = truncateHead(content, 10000, 1024)
|
|
expect(result.truncated).toBe(true)
|
|
expect(Buffer.byteLength(result.content, 'utf-8')).toBeLessThanOrEqual(
|
|
1024 + 200,
|
|
)
|
|
})
|
|
|
|
it('handles empty content', () => {
|
|
const result = truncateHead('')
|
|
expect(result.truncated).toBe(false)
|
|
expect(result.content).toBe('')
|
|
})
|
|
})
|
|
|
|
describe('truncateTail', () => {
|
|
it('does not truncate small content', () => {
|
|
const result = truncateTail('a\nb\nc')
|
|
expect(result.truncated).toBe(false)
|
|
expect(result.content).toBe('a\nb\nc')
|
|
})
|
|
|
|
it('keeps last N lines', () => {
|
|
const lines = Array.from({ length: 100 }, (_, i) => `line ${i}`)
|
|
const content = lines.join('\n')
|
|
const result = truncateTail(content, 10)
|
|
expect(result.truncated).toBe(true)
|
|
expect(result.keptLines).toBe(10)
|
|
expect(result.content).toContain('line 99')
|
|
expect(result.content).not.toContain('line 0')
|
|
})
|
|
|
|
it('handles empty content', () => {
|
|
const result = truncateTail('')
|
|
expect(result.truncated).toBe(false)
|
|
expect(result.content).toBe('')
|
|
})
|
|
})
|
|
|
|
describe('truncateLine', () => {
|
|
it('does not truncate short lines', () => {
|
|
expect(truncateLine('hello', 500)).toBe('hello')
|
|
})
|
|
|
|
it('truncates long lines', () => {
|
|
const long = 'x'.repeat(600)
|
|
const result = truncateLine(long, 500)
|
|
expect(result.length).toBe(500 + ' [truncated]'.length)
|
|
expect(result).toContain('[truncated]')
|
|
})
|
|
})
|
|
|
|
describe('stripBom', () => {
|
|
it('strips BOM', () => {
|
|
const result = stripBom('\uFEFFhello')
|
|
expect(result.hasBom).toBe(true)
|
|
expect(result.content).toBe('hello')
|
|
})
|
|
|
|
it('handles content without BOM', () => {
|
|
const result = stripBom('hello')
|
|
expect(result.hasBom).toBe(false)
|
|
expect(result.content).toBe('hello')
|
|
})
|
|
})
|
|
|
|
describe('detectLineEnding', () => {
|
|
it('detects CRLF', () => {
|
|
expect(detectLineEnding('a\r\nb')).toBe('\r\n')
|
|
})
|
|
|
|
it('detects CR', () => {
|
|
expect(detectLineEnding('a\rb')).toBe('\r')
|
|
})
|
|
|
|
it('defaults to LF', () => {
|
|
expect(detectLineEnding('a\nb')).toBe('\n')
|
|
expect(detectLineEnding('abc')).toBe('\n')
|
|
})
|
|
})
|
|
|
|
describe('normalizeToLF', () => {
|
|
it('converts CRLF to LF', () => {
|
|
expect(normalizeToLF('a\r\nb')).toBe('a\nb')
|
|
})
|
|
|
|
it('converts CR to LF', () => {
|
|
expect(normalizeToLF('a\rb')).toBe('a\nb')
|
|
})
|
|
|
|
it('preserves LF', () => {
|
|
expect(normalizeToLF('a\nb')).toBe('a\nb')
|
|
})
|
|
})
|
|
|
|
describe('restoreLineEndings', () => {
|
|
it('converts LF to CRLF', () => {
|
|
expect(restoreLineEndings('a\nb', '\r\n')).toBe('a\r\nb')
|
|
})
|
|
|
|
it('leaves LF as-is when target is LF', () => {
|
|
expect(restoreLineEndings('a\nb', '\n')).toBe('a\nb')
|
|
})
|
|
})
|
|
|
|
describe('isBinaryPath', () => {
|
|
it('detects image files', () => {
|
|
expect(isBinaryPath('photo.png')).toBe(true)
|
|
expect(isBinaryPath('image.jpg')).toBe(true)
|
|
})
|
|
|
|
it('detects archives', () => {
|
|
expect(isBinaryPath('archive.zip')).toBe(true)
|
|
expect(isBinaryPath('package.tar.gz')).toBe(true)
|
|
})
|
|
|
|
it('returns false for text files', () => {
|
|
expect(isBinaryPath('code.ts')).toBe(false)
|
|
expect(isBinaryPath('readme.md')).toBe(false)
|
|
expect(isBinaryPath('data.json')).toBe(false)
|
|
})
|
|
|
|
it('returns false for extensionless files', () => {
|
|
expect(isBinaryPath('Makefile')).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('walkFiles', () => {
|
|
let tmpDir: string
|
|
|
|
beforeEach(async () => {
|
|
tmpDir = join(
|
|
tmpdir(),
|
|
`fs-walk-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
)
|
|
await mkdir(tmpDir, { recursive: true })
|
|
})
|
|
|
|
afterEach(async () => {
|
|
await rm(tmpDir, { recursive: true, force: true })
|
|
})
|
|
|
|
it('walks files recursively', async () => {
|
|
await mkdir(join(tmpDir, 'a', 'b'), { recursive: true })
|
|
await writeFile(join(tmpDir, 'root.txt'), '')
|
|
await writeFile(join(tmpDir, 'a', 'mid.txt'), '')
|
|
await writeFile(join(tmpDir, 'a', 'b', 'deep.txt'), '')
|
|
|
|
const files: string[] = []
|
|
for await (const f of walkFiles(tmpDir, tmpDir)) {
|
|
files.push(f)
|
|
}
|
|
expect(files).toContain('root.txt')
|
|
expect(files).toContain(join('a', 'mid.txt'))
|
|
expect(files).toContain(join('a', 'b', 'deep.txt'))
|
|
})
|
|
|
|
it('skips node_modules', async () => {
|
|
await mkdir(join(tmpDir, 'node_modules', 'pkg'), { recursive: true })
|
|
await writeFile(join(tmpDir, 'node_modules', 'pkg', 'index.js'), '')
|
|
await writeFile(join(tmpDir, 'real.ts'), '')
|
|
|
|
const files: string[] = []
|
|
for await (const f of walkFiles(tmpDir, tmpDir)) {
|
|
files.push(f)
|
|
}
|
|
expect(files).toContain('real.ts')
|
|
expect(files.some((f) => f.includes('node_modules'))).toBe(false)
|
|
})
|
|
|
|
it('skips .git', async () => {
|
|
await mkdir(join(tmpDir, '.git', 'objects'), { recursive: true })
|
|
await writeFile(join(tmpDir, '.git', 'HEAD'), '')
|
|
await writeFile(join(tmpDir, 'code.ts'), '')
|
|
|
|
const files: string[] = []
|
|
for await (const f of walkFiles(tmpDir, tmpDir)) {
|
|
files.push(f)
|
|
}
|
|
expect(files).toContain('code.ts')
|
|
expect(files.some((f) => f.includes('.git'))).toBe(false)
|
|
})
|
|
|
|
it('handles empty directories', async () => {
|
|
const files: string[] = []
|
|
for await (const f of walkFiles(tmpDir, tmpDir)) {
|
|
files.push(f)
|
|
}
|
|
expect(files.length).toBe(0)
|
|
})
|
|
|
|
it('handles nonexistent directory gracefully', async () => {
|
|
const files: string[] = []
|
|
for await (const f of walkFiles(join(tmpDir, 'nonexistent'), tmpDir)) {
|
|
files.push(f)
|
|
}
|
|
expect(files.length).toBe(0)
|
|
})
|
|
})
|