'use strict'; /** * Tests for the file-browser helpers in src/file-browser.js. * * Run with: node --test * (or) node --test test/file-browser.test.js * * Pure unit tests except for `listEntries`/`readLines`, which read the real * workspace (ROOT_DIR defaults to the process cwd, i.e. the repo root). */ import test from 'node:test'; import assert from 'node:assert/strict'; import { listEntries, readLines, formatMention, normalizeRange, windowStart, parentOf, baseOf, } from '../src/file-browser.js'; // ─── mention formatting ─────────────────────────────────────────────────────── test('formatMention: whole file', () => { assert.equal(formatMention('a/b.js'), '@a/b.js'); }); test('formatMention: single line collapses start==end', () => { assert.equal(formatMention('a/b.js', 10, 10), '@a/b.js:10'); assert.equal(formatMention('a/b.js', 10, null), '@a/b.js:10'); }); test('formatMention: line range', () => { assert.equal(formatMention('a/b.js', 10, 20), '@a/b.js:10-20'); }); test('formatMention: normalizes backslashes and trailing slashes', () => { assert.equal(formatMention('a\\b.js', 1, 2), '@a/b.js:1-2'); assert.equal(formatMention('src/', 1, 2), '@src:1-2'); }); // ─── range normalization ────────────────────────────────────────────────────── test('normalizeRange: anchor above cursor', () => { assert.deepEqual(normalizeRange(2, 5), { start: 3, end: 6 }); }); test('normalizeRange: anchor below cursor', () => { assert.deepEqual(normalizeRange(5, 2), { start: 3, end: 6 }); }); test('normalizeRange: single line', () => { assert.deepEqual(normalizeRange(4, 4), { start: 5, end: 5 }); }); // ─── windowing ──────────────────────────────────────────────────────────────── test('windowStart: everything fits', () => { assert.equal(windowStart(0, 5, 200), 0); assert.equal(windowStart(4, 5, 200), 0); }); test('windowStart: centers around the cursor', () => { assert.equal(windowStart(100, 1000, 20), 90); }); test('windowStart: clamps at the top', () => { assert.equal(windowStart(0, 1000, 20), 0); assert.equal(windowStart(3, 1000, 20), 0); }); test('windowStart: clamps at the bottom', () => { assert.equal(windowStart(999, 1000, 20), 980); }); // ─── path helpers ───────────────────────────────────────────────────────────── test('parentOf / baseOf', () => { assert.equal(parentOf(''), ''); assert.equal(parentOf('src'), ''); assert.equal(parentOf('src/tools'), 'src'); assert.equal(baseOf('src/tools'), 'tools'); assert.equal(baseOf('README.md'), 'README.md'); }); // ─── listing (real workspace) ───────────────────────────────────────────────── test('listEntries: dirs before files, dot-dirs and gitignored hidden', () => { const entries = listEntries(''); const names = entries.map((e) => e.name); // node_modules is hardcoded-ignored, .git is a dot-dir: both must be absent. assert.ok(!names.includes('node_modules'), 'node_modules hidden'); assert.ok(!names.includes('.git'), '.git hidden'); // A root listing has no synthetic parent entry. assert.ok(!names.includes('..'), 'no parent entry at root'); // src/ is a directory and README.md a file: directory must come first. const srcIdx = names.indexOf('src'); const readmeIdx = names.indexOf('README.md'); assert.ok(srcIdx !== -1, 'src listed'); assert.ok(readmeIdx !== -1, 'README.md listed'); assert.ok(srcIdx < readmeIdx, 'directories sort before files'); const src = entries.find((e) => e.name === 'src'); assert.equal(src.type, 'd'); assert.equal(src.rel, 'src'); const readme = entries.find((e) => e.name === 'README.md'); assert.equal(readme.type, 'f'); assert.equal(readme.rel, 'README.md'); }); test('listEntries: nested listing prepends a parent entry', () => { const entries = listEntries('src'); assert.equal(entries[0].name, '..'); assert.equal(entries[0].type, 'parent'); assert.equal(entries[0].rel, ''); assert.ok(entries.some((e) => e.name === 'fido.js' && e.type === 'f')); }); // ─── reading (real workspace) ───────────────────────────────────────────────── test('readLines: reads a real file into lines', () => { const { lines, binary } = readLines('README.md'); assert.equal(binary, false); assert.ok(Array.isArray(lines)); assert.ok(lines.length > 0); assert.ok(lines[0].includes('FIDO')); }); test('readLines: rejects paths that escape the workspace', () => { const res = readLines('../secrets.txt'); assert.equal(res.error, true); assert.deepEqual(res.lines, []); }); test('readLines: missing file reports error, not a throw', () => { const res = readLines('does/not/exist.js'); assert.equal(res.error, true); assert.deepEqual(res.lines, []); });