import { test } from 'brittle'
import { mkdirSync, writeFileSync, rmSync, existsSync } from 'fs'
import { join, dirname, resolve } from 'path'
import { fileURLToPath } from 'url'
import { buildIgnoreMatcher } from '../lib/ignore.js'
const HERE = dirname(fileURLToPath(import.meta.url))
const FIXTURE = resolve(HERE, 'fixtures-ignore')
function setupFixture () {
if (existsSync(FIXTURE)) rmSync(FIXTURE, { recursive: true, force: true })
mkdirSync(FIXTURE, { recursive: true })
return FIXTURE
}
function mk (root, relPath, content = '') {
const full = join(root, relPath)
mkdirSync(dirname(full), { recursive: true })
writeFileSync(full, content)
return full
}
test('ignore: built-in defaults skip node_modules/.git/vendor/etc', (t) => {
const root = setupFixture()
const ignore = buildIgnoreMatcher(root)
t.ok(ignore(join(root, 'node_modules')), 'node_modules hidden')
t.ok(ignore(join(root, 'node_modules/foo/index.js')), 'file under node_modules hidden')
t.ok(ignore(join(root, '.git/HEAD')), '.git contents hidden')
t.ok(ignore(join(root, 'vendor')), 'vendor hidden')
t.ok(ignore(join(root, 'build/output.js')), 'build hidden')
t.absent(ignore(join(root, 'src/index.js')), 'src/ not hidden')
rmSync(root, { recursive: true, force: true })
})
test('ignore: .codetreeignore file at rootDir', (t) => {
const root = setupFixture()
mk(root, '.codetreeignore', '# comment line\nsecrets\nclients/*.key\nbackup/\n')
const ignore = buildIgnoreMatcher(root)
t.ok(ignore(join(root, 'secrets')), 'pattern "secrets" hides dir')
t.ok(ignore(join(root, 'foo/secrets/bar.txt')), 'name pattern matches at any depth')
t.ok(ignore(join(root, 'clients/acme.key')), 'glob pattern works')
t.ok(ignore(join(root, 'backup')), 'trailing-slash dir pattern')
t.absent(ignore(join(root, 'public/index.html')), 'unrelated path not hidden')
t.absent(ignore(join(root, 'clients/readme.md')), 'non-matching glob not hidden')
rmSync(root, { recursive: true, force: true })
})
test('ignore: .codetree-private marker hides whole subtree', (t) => {
const root = setupFixture()
mk(root, 'public/foo.js')
mk(root, 'private-work/.codetree-private')
mk(root, 'private-work/src/secret.js')
mk(root, 'private-work/docs/plan.md')
const ignore = buildIgnoreMatcher(root)
t.absent(ignore(join(root, 'public/foo.js')), 'public path visible')
t.ok(ignore(join(root, 'private-work')), 'marked dir itself hidden')
t.ok(ignore(join(root, 'private-work/src/secret.js')), 'file deep under marker hidden')
t.ok(ignore(join(root, 'private-work/docs/plan.md')), 'another file deep under marker hidden')
rmSync(root, { recursive: true, force: true })
})
test('ignore: --ignore flag patterns', (t) => {
const root = setupFixture()
const ignore = buildIgnoreMatcher(root, ['my-custom', 'tmp-*'])
t.ok(ignore(join(root, 'my-custom')), 'extra pattern applied')
t.ok(ignore(join(root, 'some/path/my-custom')), 'extra pattern matches at any depth')
t.ok(ignore(join(root, 'tmp-scratch')), 'glob in extra pattern works')
t.absent(ignore(join(root, 'normal')), 'unrelated not hidden')
rmSync(root, { recursive: true, force: true })
})
test('ignore: .codetreeignore + --ignore stack', (t) => {
const root = setupFixture()
mk(root, '.codetreeignore', 'from-file\n')
const ignore = buildIgnoreMatcher(root, ['from-flag'])
t.ok(ignore(join(root, 'from-file')), 'file pattern applied')
t.ok(ignore(join(root, 'from-flag')), 'flag pattern applied')
t.ok(ignore(join(root, 'node_modules')), 'built-in still applied')
rmSync(root, { recursive: true, force: true })
})
test('ignore: anchored / pattern matches only at root', (t) => {
const root = setupFixture()
mk(root, '.codetreeignore', '/top-only\n')
const ignore = buildIgnoreMatcher(root)
t.ok(ignore(join(root, 'top-only')), 'anchored matches at root')
t.ok(ignore(join(root, 'top-only/child.js')), 'contents under anchored also hidden')
t.absent(ignore(join(root, 'nested/top-only')), 'anchored does NOT match deeper')
rmSync(root, { recursive: true, force: true })
})
test('ignore: comments and blank lines in .codetreeignore', (t) => {
const root = setupFixture()
mk(root, '.codetreeignore', '# this is a comment\n\n \nlegit\n')
const ignore = buildIgnoreMatcher(root)
t.ok(ignore(join(root, 'legit')), 'valid pattern applied')
t.absent(ignore(join(root, '# this is a comment')), 'comments not treated as patterns')
t.absent(ignore(join(root, 'unrelated')), 'unrelated not hidden')
rmSync(root, { recursive: true, force: true })
})