Lands Gosalyn's round 2 candidate,f37a65857on gosalyn/c2-01b-r2 (base fefd54e13; commits3647a5a93,88daaac50,f37a65857), approved by Webby at merge scope (webby-0072, REVIEW-r2efd6c1f02on webby/reviews, #1733 comment 30005, rev 2787). Nine paths, manifest 312c16fc4f65. No 231 path changed betweenfefd54e13and this commit's parent. Patch-id 59b98b1b3e00, the same as fefd54e13..f37a65857. Evidence: Gosalyn's singles on my slot, 61/0 on r2 (gosalyn-0099; run 1 60/1 was a test order defect). Gate on my slot, 10:05:31-10:07:52Z on62ddc96ccplus the nine paths: library 45/0, and every scripts/test-*.sh green (auth 15, conductor 17, config 31, discord 78, extension-package 18, foundation 44, land-work 86, queue 156 and 28, release 14, task 98, all 0 failed). Webby's N4 (README:88 "A link to ... an attachment is R4" next to the embed paragraph) is doc-only and stays as restored (sage-1917).
186 lines
12 KiB
JavaScript
186 lines
12 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { writeFileSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import { readLibrary, parseFrontMatter, LibraryError, MAX_BLOB } from '../src/index.mjs';
|
|
import { BASE, git, mapping, repo } from './helpers.mjs';
|
|
|
|
const codes = (result, path) => result.problems.filter(p => p.path === path).map(p => p.code);
|
|
const file = (result, path) => result.files.find(f => f.path === path);
|
|
|
|
test('the fixture reads clean: every resolver step, appears_in and relationships wikilinks indexed', async t => {
|
|
const r = repo(t);
|
|
const result = await readLibrary({ repo: r.repo, commit: r.commit, mapping: mapping() });
|
|
assert.equal(result.commit, r.commit);
|
|
assert.deepEqual(result.files.map(f => [f.path, f.scope]), [
|
|
['Book One/Chapters/Chapter 01.md', 'canon'], ['Book One/Characters/Mara Vell.md', 'canon'],
|
|
['Book One/Characters/Tomas.md', 'canon'], ['Book One/Places/Harbor Town.md', 'canon'],
|
|
['Book One/Playground/What If.md', 'playground'], ['Second World/Chapters/01.md', 'canon']]);
|
|
const chapter = file(result, 'Book One/Chapters/Chapter 01.md');
|
|
assert.deepEqual(chapter.links.map(l => [l.path, l.step, l.line]), [
|
|
['Book One/Characters/Mara Vell.md', 'shortest', 2], ['Book One/Places/Harbor Town.md', 'root', 2],
|
|
['Book One/Places/Harbor Town.md', 'relative', 3], ['Book One/Characters/Tomas.md', 'vault-prefix', 3]]);
|
|
const mara = file(result, 'Book One/Characters/Mara Vell.md');
|
|
assert.deepEqual(mara.frontmatter, { type: 'character', name: 'Mara Vell', appears_in: '[[Book One/Chapters/Chapter 01]]',
|
|
relationships: ['[[Tomas]]', 'The Ferryman'] });
|
|
assert.deepEqual(mara.links.map(l => [l.field, l.path, l.line]), [
|
|
['appears_in', 'Book One/Chapters/Chapter 01.md', 4], ['relationships', 'Book One/Characters/Tomas.md', 6]]);
|
|
assert.deepEqual(file(result, 'Book One/Characters/Tomas.md').links.map(l => [l.field, l.path]),
|
|
[['appears_in', 'Book One/Chapters/Chapter 01.md']]);
|
|
assert.equal(mara.bodyLine, 9);
|
|
assert.equal(mara.lines[8][0], 9);
|
|
assert.equal(mara.type, 'character');
|
|
// Only the unmapped paths are problems: the top-level note and both .canvas files.
|
|
assert.deepEqual(result.problems.map(p => [p.code, p.path]), [
|
|
['R2', 'Book One/Board.canvas'], ['R2', 'Book One/Playground/Sketch.canvas'], ['R2', 'Inbox Note.md']]);
|
|
assert.ok(Object.isFrozen(result.files[0].links));
|
|
});
|
|
|
|
test('R1: executables, symlinks, gitlinks, blobs over 1 MiB and non-UTF-8 text are not indexed', async t => {
|
|
const r = repo(t, { ...BASE,
|
|
'Book One/Places/Run.md': { text: 'x\n', mode: 0o755 },
|
|
'Book One/Places/Link.md': { link: 'Harbor Town.md' },
|
|
'Book One/Places/Big.md': 'x'.repeat(MAX_BLOB + 1),
|
|
'Book One/Places/Edge.md': 'y'.repeat(MAX_BLOB),
|
|
'Book One/Places/Latin.md': { bytes: Buffer.from([0x63, 0x61, 0x66, 0xe9, 0x0a]) },
|
|
'Book One/Places/Nul.md': 'a\0b\n' });
|
|
git(r.repo, 'update-index', '--add', '--cacheinfo', `160000,${r.commit},Book One/Places/Sub.md`);
|
|
git(r.repo, 'commit', '-qm', 'gitlink');
|
|
const result = await readLibrary({ repo: r.repo, commit: git(r.repo, 'rev-parse', 'HEAD'), mapping: mapping() });
|
|
for (const name of ['Run', 'Link', 'Big', 'Latin', 'Nul', 'Sub']) {
|
|
assert.deepEqual(codes(result, `Book One/Places/${name}.md`), ['R1'], name);
|
|
assert.equal(file(result, `Book One/Places/${name}.md`), undefined, name);
|
|
}
|
|
assert.ok(file(result, 'Book One/Places/Edge.md'), 'exactly 1 MiB is read');
|
|
});
|
|
|
|
test('R2: unmapped paths are reported and not indexed', async t => {
|
|
const r = repo(t);
|
|
const result = await readLibrary({ repo: r.repo, commit: r.commit, mapping: mapping() });
|
|
for (const path of ['Inbox Note.md', 'Book One/Board.canvas', 'Book One/Playground/Sketch.canvas']) {
|
|
assert.deepEqual(codes(result, path), ['R2']);
|
|
assert.equal(file(result, path), undefined);
|
|
}
|
|
});
|
|
|
|
test('R3: front matter not fully parsed stays raw, with the line where parsing stopped', async t => {
|
|
const r = repo(t, { ...BASE,
|
|
'Book One/Places/Nested.md': ['---', 'type: place', 'coords:', ' lat: 1', '---', 'Body [[Tomas]]', ''].join('\n'),
|
|
'Book One/Places/Block.md': ['---', 'summary: |', ' text', '---', ''].join('\n'),
|
|
'Book One/Places/Open.md': ['---', 'type: place', 'never closed', ''].join('\n') });
|
|
const result = await readLibrary({ repo: r.repo, commit: r.commit, mapping: mapping() });
|
|
const nested = file(result, 'Book One/Places/Nested.md');
|
|
assert.deepEqual(result.problems.filter(p => p.path === 'Book One/Places/Nested.md'), [{ code: 'R3', path: 'Book One/Places/Nested.md', line: 4, message: 'front matter not fully parsed' }]);
|
|
assert.equal(nested.frontmatter, null);
|
|
assert.equal(nested.frontmatterRaw, 'type: place\ncoords:\n lat: 1');
|
|
assert.deepEqual(nested.links.map(l => l.path), ['Book One/Characters/Tomas.md'], 'still indexed by body');
|
|
assert.equal(result.problems.find(p => p.path === 'Book One/Places/Block.md').line, 2);
|
|
assert.equal(result.problems.find(p => p.path === 'Book One/Places/Open.md').line, 1);
|
|
assert.equal(file(result, 'Book One/Places/Open.md').bodyLine, 1);
|
|
});
|
|
|
|
test('front matter forms the reader accepts and refuses', () => {
|
|
const parse = text => parseFrontMatter(text.split('\n'));
|
|
assert.deepEqual(parse('---\na: plain words\nb: "quoted: yes"\nc: \'it\'\'s\'\nd: [x, "y z", 3]\ne:\n- one\n- "[[Two]]"\nf: [[Wiki]]\ng: true\nh:\n---').frontmatter,
|
|
{ a: 'plain words', b: 'quoted: yes', c: "it's", d: ['x', 'y z', 3], e: ['one', '[[Two]]'], f: '[[Wiki]]', g: true, h: null });
|
|
for (const bad of ['a: {x: 1}', 'a: &anchor x', 'a: [x, [y]]', 'a: x: y', 'a: 1\na: 2', ' a: 1', 'a: "open', '- loose'])
|
|
assert.notEqual(parse(`---\n${bad}\n---`).stop, null, bad);
|
|
assert.deepEqual(parse('no front matter'), { frontmatter: null, raw: null, bodyLine: 1, stop: null });
|
|
});
|
|
|
|
test('R4: a link to no file, to more than one, or into another repo is reported and not followed', async t => {
|
|
const r = repo(t, { ...BASE,
|
|
'Book One/Research/Notes.md': 'a\n', 'Book One/Places/Notes.md': 'b\n',
|
|
'Book One/Chapters/Links.md': ['[[Nobody]]', '[[Notes]]', '[[bean-brain/Journal/Today]]', '[[Tomas]]', ''].join('\n') });
|
|
const result = await readLibrary({ repo: r.repo, commit: r.commit, mapping: mapping() });
|
|
assert.deepEqual(result.problems.filter(p => p.path === 'Book One/Chapters/Links.md').map(p => [p.code, p.line]), [['R4', 1], ['R4', 2], ['R4', 3]]);
|
|
assert.deepEqual(file(result, 'Book One/Chapters/Links.md').links.map(l => l.path), ['Book One/Characters/Tomas.md']);
|
|
});
|
|
|
|
test('a block list link carries its item line, a scalar or flow link its key line, and problems follow', async t => {
|
|
const r = repo(t, { ...BASE, 'Book One/Characters/Ana.md': ['---', 'appears_in: ["[[Nobody]]"]', 'relationships:', ' - "[[Tomas]]"',
|
|
' - Plain Name', ' - "[[Ghost]]"', ' - "[[What If]]"', '---', ''].join('\n') });
|
|
const result = await readLibrary({ repo: r.repo, commit: r.commit, mapping: mapping() });
|
|
assert.deepEqual(file(result, 'Book One/Characters/Ana.md').links.map(l => [l.field, l.path, l.line]), [
|
|
['relationships', 'Book One/Characters/Tomas.md', 4], ['relationships', 'Book One/Playground/What If.md', 7]]);
|
|
assert.deepEqual(result.problems.filter(p => p.path === 'Book One/Characters/Ana.md').map(p => [p.code, p.line]),
|
|
[['R4', 2], ['R4', 6], ['R5', 7]]);
|
|
});
|
|
|
|
// sage-1900: only an unresolved body embed with an extension other than .md is an attachment. A plain
|
|
// link to one and a front matter value stay R4 when they resolve to nothing.
|
|
test('an unresolved attachment embed gives no problem; a link or front matter value to one is R4; a note embed resolves', async t => {
|
|
const r = repo(t, { ...BASE, 'Book One/Places/note.md': 'n\n', 'Book One/Places/Shore.md': ['---',
|
|
'relationships:', ' - "![[portrait.JPG]]"', '---', '![[cover.png]] [[Board.canvas]] ![[map.pdf|Map]]',
|
|
'![[missing-note]] ![[note.md]] ![[Harbor Town]]', ''].join('\n') });
|
|
const result = await readLibrary({ repo: r.repo, commit: r.commit, mapping: mapping() });
|
|
assert.deepEqual(result.problems.filter(p => p.path === 'Book One/Places/Shore.md').map(p => [p.code, p.line]),
|
|
[['R4', 3], ['R4', 5], ['R4', 6]]);
|
|
assert.deepEqual(file(result, 'Book One/Places/Shore.md').links.map(l => [l.path, l.embed]),
|
|
[['Book One/Places/note.md', true], ['Book One/Places/Harbor Town.md', true]]);
|
|
});
|
|
|
|
// webby-0070 B1: a note whose name looks like it has an extension resolves before the attachment test.
|
|
test('an indexed Dr.Quinn.md resolves as a body link, a block list link and an embed', async t => {
|
|
const r = repo(t, { ...BASE, 'Book One/Characters/Dr.Quinn.md': 'q\n', 'Book One/Characters/Ana.md': ['---',
|
|
'relationships:', ' - "[[Dr.Quinn]]"', '---', '[[Dr.Quinn]] ![[Dr.Quinn]]', ''].join('\n') });
|
|
const result = await readLibrary({ repo: r.repo, commit: r.commit, mapping: mapping() });
|
|
assert.deepEqual(file(result, 'Book One/Characters/Ana.md').links.map(l => [l.field ?? null, l.path, l.line, l.embed]), [
|
|
[null, 'Book One/Characters/Dr.Quinn.md', 5, false], [null, 'Book One/Characters/Dr.Quinn.md', 5, true],
|
|
['relationships', 'Book One/Characters/Dr.Quinn.md', 3, false]]);
|
|
assert.deepEqual(result.problems.filter(p => p.path === 'Book One/Characters/Ana.md'), []);
|
|
});
|
|
|
|
test('R5: a canon file that refers into the playground is reported, and the link still goes by path', async t => {
|
|
const r = repo(t, { ...BASE, 'Book One/Places/Dock.md': 'See [[What If]].\n' });
|
|
const result = await readLibrary({ repo: r.repo, commit: r.commit, mapping: mapping() });
|
|
assert.deepEqual(codes(result, 'Book One/Places/Dock.md'), ['R5']);
|
|
assert.deepEqual(file(result, 'Book One/Places/Dock.md').links.map(l => l.path), ['Book One/Playground/What If.md']);
|
|
// The playground file links to canon: no problem, R5 is only about canon.
|
|
assert.deepEqual(codes(result, 'Book One/Playground/What If.md'), []);
|
|
});
|
|
|
|
test('R6: a canon file that refers into another universe is reported; a hybrid may refer into its sources', async t => {
|
|
const r = repo(t, { ...BASE, 'Book One/Places/Far.md': 'Like [[Second World/Chapters/01]].\n',
|
|
'Crossover/Chapters/A.md': 'From [[Mara Vell]] and [[Second World/Chapters/01]].\n' });
|
|
const hybrid = mapping(e => { e.universes.cross = { kind: 'hybrid', name: 'Cross', sources: ['harbor', 'second'], books: ['Crossover'] }; return e; });
|
|
const result = await readLibrary({ repo: r.repo, commit: r.commit, mapping: hybrid });
|
|
assert.deepEqual(codes(result, 'Book One/Places/Far.md'), ['R6']);
|
|
assert.deepEqual(file(result, 'Book One/Places/Far.md').links.map(l => l.path), ['Second World/Chapters/01.md']);
|
|
assert.deepEqual(codes(result, 'Crossover/Chapters/A.md'), []);
|
|
});
|
|
|
|
test('R7: two paths that differ only in case are both left out', async t => {
|
|
const r = repo(t, BASE);
|
|
writeFileSync(join(r.root, 'twin'), 'twin\n');
|
|
const blob = git(r.repo, 'hash-object', '-w', join(r.root, 'twin'));
|
|
git(r.repo, 'update-index', '--add', '--cacheinfo', `100644,${blob},Book One/Characters/tomas.md`);
|
|
git(r.repo, 'commit', '-qm', 'case twin');
|
|
const result = await readLibrary({ repo: r.repo, commit: git(r.repo, 'rev-parse', 'HEAD'), mapping: mapping() });
|
|
for (const path of ['Book One/Characters/Tomas.md', 'Book One/Characters/tomas.md']) {
|
|
assert.deepEqual(codes(result, path), ['R7'], path);
|
|
assert.equal(file(result, path), undefined);
|
|
}
|
|
});
|
|
|
|
test('the reader reads the commit, never the working tree, and refuses a bad commit id', async t => {
|
|
const r = repo(t);
|
|
writeFileSync(join(r.repo, 'Book One/Places/Harbor Town.md'), 'unsaved [[Nobody]]\n');
|
|
writeFileSync(join(r.repo, 'Book One/Places/Untracked.md'), 'x\n');
|
|
const result = await readLibrary({ repo: r.repo, commit: r.commit, mapping: mapping() });
|
|
assert.equal(file(result, 'Book One/Places/Harbor Town.md').lines[0][1], '---');
|
|
assert.equal(file(result, 'Book One/Places/Untracked.md'), undefined);
|
|
for (const commit of ['HEAD', r.commit.slice(0, 12), '0'.repeat(40), git(r.repo, 'rev-parse', 'HEAD^{tree}')])
|
|
await assert.rejects(readLibrary({ repo: r.repo, commit, mapping: mapping() }), e => e instanceof LibraryError, commit);
|
|
await assert.rejects(readLibrary({ repo: r.repo, commit: r.commit, mapping: {} }), /library-mapping-invalid/);
|
|
});
|
|
|
|
test('a bare mirror reads the same as the clone', async t => {
|
|
const r = repo(t);
|
|
const bare = join(r.root, 'mirror.git');
|
|
git(r.root, 'clone', '-q', '--bare', r.repo, bare);
|
|
const a = await readLibrary({ repo: r.repo, commit: r.commit, mapping: mapping() });
|
|
const b = await readLibrary({ repo: bare, commit: r.commit, mapping: mapping() });
|
|
assert.deepEqual(b, a);
|
|
});
|