147 lines
5.4 KiB
JavaScript
147 lines
5.4 KiB
JavaScript
'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, []);
|
|
});
|