Files
stack/packages/library/tests/survey.test.mjs

112 lines
5.6 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { spawnSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';
import { surveyMain } from '../src/index.mjs';
import { BASE, FIXTURES, MARKER, repo } from './helpers.mjs';
const ROOT = fileURLToPath(new URL('../../..', import.meta.url));
async function run(args) {
let stdout = '', stderr = '';
const code = await surveyMain(args, { stdout: { write: s => { stdout += s; } }, stderr: { write: s => { stderr += s; } } });
return { code, stdout, stderr };
}
// Strings from the fixture that are text, values, file names or link targets: none may be printed.
const NEVER = [MARKER, 'Grey eyes', 'The Ferryman', 'Mara Vell', 'Tomas', 'Harbor Town', 'What If', 'Inbox Note', 'Chapter 01',
'Sketch', 'Board', 'Not A Link', 'the harbor', 'Docks', 'character'];
test('the survey prints folder names, counts, key sets and link-form counts, and never text', async t => {
const r = repo(t, { ...BASE, 'Book One/Places/Odd.md': ['---', `"${MARKER} key": 1`, '---', ''].join('\n') });
const out = await run(['survey', r.repo]);
assert.equal(out.code, 0, out.stderr);
assert.equal(out.stdout, [
`commit ${r.commit}`,
'entries 10',
'folders (direct children: md canvas base other)',
' (root) 1 0 0 0',
' "Book One" 0 1 0 0',
' "Book One/Chapters" 1 0 0 0',
' "Book One/Characters" 2 0 0 0',
' "Book One/Places" 2 0 0 0',
' "Book One/Playground" 1 1 0 0',
' "Second World/Chapters" 1 0 0 0',
'markdown 8 files, 8 read, 3 without front matter, 0 with front matter not fully parsed',
'front matter key sets',
' 1 [(other key)]',
' 1 [appears_in, name, relationships, type]',
' 1 [appears_in, name, type]',
' 1 [name]',
' 1 [type]',
'body links plain 3, label 1, heading 1, embed 0, self 0, markdown 1; attachment 0',
'field appears_in wikilink 2, name 0',
'field relationships wikilink 1, name 1',
''].join('\n'));
for (const s of NEVER) assert.ok(!out.stdout.includes(s), s);
});
test('folder names print DEL, C1 and bidi controls, U+061C among them, as escapes', async t => {
// One name per escaped range, with each range's first and last code point.
const names = ['A\u007fB', 'C\u0080D\u009fE', 'F\u200eG\u200fH', 'I\u202aJ\u202eK', 'L\u2066M\u2069N', 'O\u061cP'];
const r = repo(t, Object.fromEntries(names.map(n => [`${n}/a.md`, `${MARKER}\n`])));
const out = await run(['survey', r.repo]);
assert.equal(out.code, 0, out.stderr);
assert.deepEqual(out.stdout.split('\n').slice(3, 9), [
' "A\\u007fB" 1 0 0 0', ' "C\\u0080D\\u009fE" 1 0 0 0', ' "F\\u200eG\\u200fH" 1 0 0 0',
' "I\\u202aJ\\u202eK" 1 0 0 0', ' "L\\u2066M\\u2069N" 1 0 0 0', ' "O\\u061cP" 1 0 0 0']);
assert.doesNotMatch(out.stdout, /[\u007f-\u009f\u061c\u200e\u200f\u202a-\u202e\u2066-\u2069]/);
});
test('an embed of an attachment counts as an embed and as an attachment; a plain link to one does not', async t => {
const r = repo(t, { 'Book One/Chapters/A.md': ['![[cover.png]] ![[missing-note]] [[map.PDF]] [[Chapter 1.5]]', ''].join('\n') });
const out = await run(['survey', r.repo]);
assert.match(out.stdout, /^body links plain 2, label 0, heading 0, embed 2, self 0, markdown 0; attachment 1$/m);
});
test('with --mapping the survey adds scope, resolver step and problem counts', async t => {
const r = repo(t);
const out = await run(['survey', r.repo, '--mapping', join(FIXTURES, 'mapping.json')]);
assert.equal(out.code, 0, out.stderr);
const tail = out.stdout.trimEnd().split('\n').slice(-3);
assert.deepEqual(tail, [
'scopes canon 5, playground 1, supporting 0, unmapped 3; indexed 6',
'resolved root 2, vault-prefix 1, relative 2, shortest 3, unresolved 0',
'problems R1 0, R2 3, R3 0, R4 0, R5 0, R6 0, R7 0, R8 0']);
for (const s of NEVER) assert.ok(!out.stdout.includes(s), s);
});
test('C2-01d: with --mapping the survey counts supporting files', async t => {
const r = repo(t);
const entry = JSON.parse(readFileSync(join(FIXTURES, 'mapping.json'), 'utf8'));
entry.universes.harbor.supporting = ['Book One/Places/'];
const file = join(r.root, 'supporting.json');
writeFileSync(file, JSON.stringify(entry));
const out = await run(['survey', r.repo, '--mapping', file]);
assert.equal(out.code, 0, out.stderr);
assert.match(out.stdout, /^scopes canon 4, playground 1, supporting 1, unmapped 3; indexed 6$/m);
});
test('survey exit codes: usage 4, bad mapping 3, unreadable repo 2', async t => {
const r = repo(t);
for (const args of [[], ['scan', r.repo], ['survey'], ['survey', r.repo, '--mapping'], ['survey', r.repo, '--other'], ['survey', r.repo, r.repo]])
assert.equal((await run(args)).code, 4, args.join(' '));
const bad = join(r.root, 'bad.json');
writeFileSync(bad, JSON.stringify({ kind: 'library' }));
const refused = await run(['survey', r.repo, '--mapping', bad]);
assert.equal(refused.code, 3);
assert.match(refused.stderr, /library-mapping-invalid/);
writeFileSync(bad, 'not json');
assert.equal((await run(['survey', r.repo, '--mapping', bad])).code, 3);
assert.equal((await run(['survey', join(r.root, 'missing')])).code, 2);
assert.equal((await run(['survey', r.root])).code, 2, 'a directory that is not a repo');
});
test('scripts/mosaic library survey reaches the survey', t => {
const r = repo(t);
const out = spawnSync(join(ROOT, 'scripts/mosaic'), ['library', 'survey', r.repo], { encoding: 'utf8', timeout: 30000 });
assert.equal(out.status, 0, out.stderr);
assert.match(out.stdout, new RegExp(`^commit ${r.commit}\n`));
assert.ok(!out.stdout.includes(MARKER));
});