const { test } = require('node:test') const assert = require('node:assert/strict') const fs = require('node:fs') const vm = require('node:vm') const ts = require('typescript') const { parse, compileScript } = require('@vue/compiler-sfc') const { ref, computed } = require('vue') const helpers = import('../utils/documentTitleSections.js') const title = (id, titleLevel = 1) => ({ id, mode: 'title', titleLevel, text: id }) const position = (id, mode = 'free') => ({ id, mode, quantity: 2, price: 10, linkedEntitys: [42] }) const ids = rows => Array.from(rows, row => row.id) test('moving a top-level title carries every nested title and row, preserving data and order', async () => { const { moveTitleSection } = await helpers const rows = [position('intro', 'text'), title('a'), position('article', 'normal'), title('a.1', 2), position('service', 'service'), title('a.1.1', 3), position('note', 'text'), position('break', 'pagebreak'), title('b'), position('last')] const before = JSON.stringify(rows) const moved = moveTitleSection(rows, 'a', 'down') assert.deepEqual(ids(moved), ['intro', 'b', 'last', 'a', 'article', 'a.1', 'service', 'a.1.1', 'note', 'break']) assert.equal(JSON.stringify(rows), before) for (const row of rows) assert.equal(moved.find(item => item.id === row.id), row) assert.deepEqual(moveTitleSection(moved, 'a', 'up'), rows) }) test('subtitles exchange complete sibling sections without leaving their parent', async () => { const { moveTitleSection, getTitleSectionMove } = await helpers const rows = [title('a'), position('parent-intro'), title('a.1', 2), title('a.1.1', 3), position('child'), title('a.2', 2), position('sibling'), title('b'), title('b.1', 2)] assert.deepEqual(ids(moveTitleSection(rows, 'a.2', 'up')), ['a', 'parent-intro', 'a.2', 'sibling', 'a.1', 'a.1.1', 'child', 'b', 'b.1']) assert.deepEqual(ids(moveTitleSection(rows, 'a.1', 'down')), ['a', 'parent-intro', 'a.2', 'sibling', 'a.1', 'a.1.1', 'child', 'b', 'b.1']) for (const [id, direction] of [['a.1', 'up'], ['a.2', 'down'], ['b.1', 'up']]) { assert.equal(getTitleSectionMove(rows, id, direction), null) assert.equal(moveTitleSection(rows, id, direction), rows) } }) test('level-three titles move within their level-two parent', async () => { const { moveTitleSection } = await helpers const rows = [title('a'), title('a.1', 2), title('x', 3), position('x-row'), title('y', 3), position('y-row'), title('a.2', 2)] const moved = moveTitleSection(rows, 'x', 'down') assert.deepEqual(ids(moved), ['a', 'a.1', 'y', 'y-row', 'x', 'x-row', 'a.2']) assert.equal(moveTitleSection(moved, 'x', 'down'), moved) }) test('empty titles, the final section and legacy levels are supported', async () => { const { moveTitleSection } = await helpers const rows = [{ id: 'legacy', mode: 'title' }, title('empty', '1'), title('last', 1), position('end')] assert.deepEqual(ids(moveTitleSection(rows, 'legacy', 'down')), ['empty', 'legacy', 'last', 'end']) assert.deepEqual(ids(moveTitleSection(rows, 'last', 'up')), ['legacy', 'last', 'end', 'empty']) assert.equal(moveTitleSection(rows, 'last', 'down'), rows) }) test('missing intermediate levels still retain their enclosing section', async () => { const { moveTitleSection } = await helpers const rows = [title('a'), title('x', 3), position('x-row'), title('y', 3), title('b')] assert.deepEqual(ids(moveTitleSection(rows, 'x', 'down')), ['a', 'y', 'x', 'x-row', 'b']) assert.equal(moveTitleSection(rows, 'x', 'up'), rows) assert.equal(moveTitleSection(rows, 'y', 'down'), rows) }) test('invalid requests and document boundaries leave rows untouched', async () => { const { moveTitleSection } = await helpers const rows = [position('intro'), title('only'), position('last')] for (const [id, direction] of [['only', 'up'], ['only', 'down'], ['intro', 'down'], ['missing', 'up'], ['only', 'sideways']]) { assert.equal(moveTitleSection(rows, id, direction), rows) } const empty = [] assert.equal(moveTitleSection(empty, 'missing', 'up'), empty) }) async function editorFixture(rows, type = 'invoices') { const { descriptor, errors } = parse(fs.readFileSync(`${__dirname}/../pages/createDocument/edit/[[id]].vue`, 'utf8')) assert.deepEqual(errors, []) compileScript(descriptor, { id: 'document-editor', inlineTemplate: true }) const script = ts.createSourceFile('editor.js', descriptor.scriptSetup.content, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS) const names = new Set(['moveDocumentTitleSection', 'getTitleSectionMoveMenuItems', 'setPosNumbers', 'collapsedTitleRowIds', 'toggleTitleSection', 'hiddenDocumentRowIds', 'isDocumentRowHidden']) const source = script.statements.filter(statement => ts.isVariableStatement(statement) && statement.declarationList.declarations.some(declaration => names.has(declaration.name.getText(script)))) .map(statement => statement.getText(script)).join('\n') const context = vm.createContext({ ...await helpers, ref, computed, itemInfo: ref({ type, rows }) }) vm.runInContext(source, context) return { context, run: code => vm.runInContext(code, context) } } test('the editor menu moves collapsed sections and updates numbering and reactive visibility', async () => { const { run, context } = await editorFixture([title('a'), title('a.1', 2), position('a-row'), title('b'), position('b-row')]) run("setPosNumbers(); toggleTitleSection('a'); toggleTitleSection('a.1')") assert.equal(run("isDocumentRowHidden('a-row')"), true) assert.equal(run("getTitleSectionMoveMenuItems('a')[0].disabled"), true) assert.equal(run("getTitleSectionMoveMenuItems('a')[1].disabled"), false) run("getTitleSectionMoveMenuItems('a')[1].onSelect()") assert.deepEqual(ids(context.itemInfo.value.rows), ['b', 'b-row', 'a', 'a.1', 'a-row']) assert.deepEqual(Array.from(context.itemInfo.value.rows, row => row.pos), ['1', '1.1', '2', '2.1', '2.1.1']) assert.equal(run("isDocumentRowHidden('b-row')"), false) assert.equal(run("isDocumentRowHidden('a-row')"), true) run("toggleTitleSection('a')") assert.equal(run("isDocumentRowHidden('a.1')"), false) assert.equal(run("isDocumentRowHidden('a-row')"), true) run("toggleTitleSection('a.1')") assert.equal(run("isDocumentRowHidden('a-row')"), false) assert.equal(run("getTitleSectionMoveMenuItems('a')[1].disabled"), true) assert.equal(run("getTitleSectionMoveMenuItems('a')[0].disabled"), false) }) test('cancellation documents reject section moves even when called directly', async () => { const { run, context } = await editorFixture([title('a'), position('a-row'), title('b')], 'cancellationInvoices') const before = JSON.stringify(context.itemInfo.value.rows) assert.equal(run("getTitleSectionMoveMenuItems('a').every(item => item.disabled)"), true) run("moveDocumentTitleSection('a', 'down')") assert.equal(JSON.stringify(context.itemInfo.value.rows), before) })