Spaces:
Sleeping
Sleeping
| // Unit tests for the structure YAML grammar and the pure text mutations | |
| // behind sidebar drag-and-drop and groups. No server needed. | |
| import assert from 'node:assert' | |
| import { parseStructure, moveNodeInYaml, moveNodesInYaml, addGroupToYaml, renameGroupInYaml, dissolveGroupInYaml, validGroupLabel } from '../server/pages.js' | |
| const YAML = [ | |
| '- home', | |
| '- "Getting started":', | |
| ' - intro', | |
| ' - setup', | |
| '- design', | |
| ' - communication', | |
| '- references', | |
| ].join('\n') | |
| // --- parsing --- | |
| { | |
| const { tree, problems } = parseStructure(YAML) | |
| assert.deepStrictEqual(problems, []) | |
| assert.strictEqual(tree.length, 4) | |
| assert.strictEqual(tree[0].slug, 'home') | |
| assert.strictEqual(tree[1].group, 'Getting started') | |
| assert.deepStrictEqual(tree[1].children.map(c => c.slug), ['intro', 'setup']) | |
| assert.strictEqual(tree[2].children[0].slug, 'communication') | |
| } | |
| { | |
| // problem lines are reported, groups can nest, comments are skipped | |
| const { tree, problems } = parseStructure('# comment\n- "A":\n - "B":\n - x\n- ???') | |
| assert.strictEqual(problems.length, 1) | |
| assert.strictEqual(tree[0].group, 'A') | |
| assert.strictEqual(tree[0].children[0].group, 'B') | |
| assert.strictEqual(tree[0].children[0].children[0].slug, 'x') | |
| } | |
| // --- move: reorder at root --- | |
| { | |
| const { raw } = moveNodeInYaml(YAML, 'references', null, 0) | |
| assert.strictEqual(raw.split('\n')[0], '- references') | |
| assert.strictEqual(parseStructure(raw).tree[0].slug, 'references') | |
| } | |
| // --- move: nest a page (subtree travels, reindents) --- | |
| { | |
| const { raw } = moveNodeInYaml(YAML, 'design', ['"Getting started"'], 0) | |
| const t = parseStructure(raw).tree | |
| const g = t.find(n => n.group === 'Getting started') | |
| assert.deepStrictEqual(g.children.map(c => c.slug), ['design', 'intro', 'setup']) | |
| assert.strictEqual(g.children[0].children[0].slug, 'communication') | |
| assert.ok(raw.includes(' - communication')) | |
| } | |
| // --- move: out of a group to root, append --- | |
| { | |
| const { raw } = moveNodeInYaml(YAML, 'intro', null, 99) | |
| const t = parseStructure(raw).tree | |
| assert.strictEqual(t[t.length - 1].slug, 'intro') | |
| const g = t.find(n => n.group === 'Getting started') | |
| assert.deepStrictEqual(g.children.map(c => c.slug), ['setup']) | |
| } | |
| // --- move: index counts after removal (down within same parent) --- | |
| { | |
| const { raw } = moveNodeInYaml(YAML, 'home', null, 2) | |
| assert.deepStrictEqual( | |
| parseStructure(raw).tree.map(n => n.slug || n.group), | |
| ['Getting started', 'design', 'home', 'references'] | |
| ) | |
| } | |
| // --- move: under a page (pages nest pages) --- | |
| { | |
| const { raw } = moveNodeInYaml(YAML, 'references', 'design', 99) | |
| const d = parseStructure(raw).tree.find(n => n.slug === 'design') | |
| assert.deepStrictEqual(d.children.map(c => c.slug), ['communication', 'references']) | |
| } | |
| // --- move: unfiled page gets a line --- | |
| { | |
| const { raw } = moveNodeInYaml(YAML, 'notes', ['"Getting started"'], 1) | |
| const g = parseStructure(raw).tree.find(n => n.group === 'Getting started') | |
| assert.deepStrictEqual(g.children.map(c => c.slug), ['intro', 'notes', 'setup']) | |
| } | |
| // --- move: comments inside a block travel with it --- | |
| { | |
| const withComment = YAML.replace(' - communication', ' # keep\n - communication') | |
| const { raw } = moveNodeInYaml(withComment, 'design', null, 0) | |
| const lines = raw.split('\n') | |
| assert.ok(lines.indexOf(' # keep') === lines.indexOf('- design') + 1) | |
| } | |
| // --- move: rejections --- | |
| assert.ok(moveNodeInYaml(YAML, 'design', 'communication', 0).error, 'nest into own subtree') | |
| assert.ok(moveNodeInYaml(YAML, '_structure', null, 0).error, 'move _structure') | |
| assert.ok(moveNodeInYaml(YAML, 'home', ['"Nope"'], 0).error, 'unknown parent') | |
| assert.ok(moveNodeInYaml(YAML, ['"Nope"'], null, 0).error, 'unknown group node') | |
| assert.ok(moveNodeInYaml(YAML, 'bad slug!', null, 0).error, 'bad slug') | |
| // --- groups: add / rename / dissolve --- | |
| { | |
| const { raw } = addGroupToYaml(YAML, 'References & links', null, 1) | |
| const t = parseStructure(raw).tree | |
| assert.strictEqual(t[1].group, 'References & links') | |
| assert.ok(addGroupToYaml(raw, 'References & links', null, 0).error, 'duplicate sibling label') | |
| } | |
| { | |
| const { raw } = addGroupToYaml(YAML, 'Sub', ['"Getting started"'], 99) | |
| const g = parseStructure(raw).tree.find(n => n.group === 'Getting started') | |
| assert.strictEqual(g.children[2].group, 'Sub') | |
| } | |
| { | |
| const { raw } = renameGroupInYaml(YAML, ['"Getting started"'], 'Start here') | |
| const t = parseStructure(raw).tree | |
| assert.strictEqual(t[1].group, 'Start here') | |
| assert.deepStrictEqual(t[1].children.map(c => c.slug), ['intro', 'setup']) | |
| assert.ok(renameGroupInYaml(YAML, ['"Getting started"'], 'x'.repeat(61)).error, 'label too long') | |
| assert.ok(renameGroupInYaml(YAML, ['"Nope"'], 'ok').error, 'unknown group') | |
| } | |
| { | |
| const { raw } = dissolveGroupInYaml(YAML, ['"Getting started"']) | |
| const t = parseStructure(raw).tree | |
| assert.deepStrictEqual(t.map(n => n.slug), ['home', 'intro', 'setup', 'design', 'references']) | |
| assert.ok(dissolveGroupInYaml(YAML, ['home']).error, 'dissolve a page') | |
| } | |
| // --- multi-node move (sidebar multi-select) --- | |
| { | |
| // two pages from different parents land as consecutive siblings, doc order | |
| const { raw } = moveNodesInYaml(YAML, ['setup', 'references'], 'design', 0) | |
| const d = parseStructure(raw).tree.find(n => n.slug === 'design') | |
| assert.deepStrictEqual(d.children.map(c => c.slug), ['setup', 'references', 'communication']) | |
| } | |
| { | |
| // a group and its own child selected together: the child travels once | |
| const { raw } = moveNodesInYaml(YAML, [['"Getting started"'], 'intro'], 'design', 99) | |
| const t = parseStructure(raw).tree | |
| assert.deepStrictEqual(t.map(n => n.slug || n.group), ['home', 'design', 'references']) | |
| const d = t.find(n => n.slug === 'design') | |
| const g = d.children.find(n => n.group === 'Getting started') | |
| assert.deepStrictEqual(g.children.map(c => c.slug), ['intro', 'setup']) | |
| assert.strictEqual(d.children.filter(n => n.slug === 'intro').length, 0, 'intro not duplicated') | |
| } | |
| { | |
| // index counts after ALL moved nodes leave: home+intro to root index 2 | |
| const { raw } = moveNodesInYaml(YAML, ['home', 'intro'], null, 2) | |
| assert.deepStrictEqual( | |
| parseStructure(raw).tree.map(n => n.slug || n.group), | |
| ['Getting started', 'design', 'home', 'intro', 'references'] | |
| ) | |
| } | |
| { | |
| // mixed tree + unfiled refs; duplicates collapse | |
| const { raw } = moveNodesInYaml(YAML, ['setup', 'notes', 'setup'], null, 0) | |
| const t = parseStructure(raw).tree | |
| assert.deepStrictEqual(t.slice(0, 2).map(n => n.slug), ['setup', 'notes']) | |
| assert.strictEqual(JSON.stringify(t).split('"setup"').length, 2, 'setup appears once') | |
| } | |
| assert.ok(moveNodesInYaml(YAML, [], null, 0).error, 'empty selection') | |
| assert.ok(moveNodesInYaml(YAML, ['home', 'design'], 'communication', 0).error, 'parent inside a moved subtree') | |
| // --- labels --- | |
| assert.strictEqual(validGroupLabel(' Reading list '), 'Reading list') | |
| assert.strictEqual(validGroupLabel('a"b'), null) | |
| assert.strictEqual(validGroupLabel(''), null) | |
| // --- a round of moves keeps the yaml parseable with zero problems --- | |
| { | |
| let raw = YAML | |
| for (const [node, parent, index] of [ | |
| ['setup', null, 0], | |
| ['design', ['"Getting started"'], 0], | |
| ['setup', 'design', 99], | |
| ['home', 'setup', 0], | |
| ]) { | |
| const r = moveNodeInYaml(raw, node, parent, index) | |
| assert.ok(!r.error, `${node}: ${r.error}`) | |
| raw = r.raw | |
| assert.deepStrictEqual(parseStructure(raw).problems, []) | |
| } | |
| const g = parseStructure(raw).tree.find(n => n.group === 'Getting started') | |
| assert.strictEqual(g.children[0].slug, 'design') | |
| assert.strictEqual(g.children[0].children[1].slug, 'setup') | |
| assert.strictEqual(g.children[0].children[1].children[0].slug, 'home') | |
| } | |
| console.log('structure tests passed') | |