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 documentTypesForCreation = Object.fromEntries([ 'invoices', 'advanceInvoices', 'serialInvoices', 'quotes', 'costEstimates', 'deliveryNotes', 'packingSlips', 'confirmationOrders', 'cancellationInvoices', ].map(key => [key, { labelSingle: key }])) function fixture(filename, names, globals = {}) { const { descriptor } = parse(fs.readFileSync(`${__dirname}/../${filename}`, 'utf8')) compileScript(descriptor, { id: filename, inlineTemplate: true }) const script = ts.createSourceFile(filename, descriptor.scriptSetup.content, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS) const source = script.statements.filter(statement => ts.isVariableStatement(statement) && statement.declarationList.declarations.some(declaration => names.includes(declaration.name.getText(script)))) .map(statement => statement.getText(script)).join('\n') const navigations = [] const notices = [] const context = vm.createContext({ ref: value => ({ value }), computed: getter => ({ get value() { return getter() } }), dataStore: { documentTypesForCreation }, URLSearchParams, router: { push: url => navigations.push(url), replace: url => navigations.push(url) }, modal: { close() {} }, toast: { add: notice => notices.push(notice) }, ...globals, }) vm.runInContext(source, context) return { context, navigations, notices, run: code => vm.runInContext(code, context) } } test('copying any document excludes advance invoices and requires an allowed target', () => { for (const type of Object.keys(documentTypesForCreation)) { const { run, navigations } = fixture('components/copyCreatedDocumentModal.vue', [ 'documentTypeToUse', 'documentTypeItems', 'selectedDocumentType', 'optionsToImport', 'startImport', ], { props: { id: '12', type } }) assert.equal(run("documentTypeItems.value.some(item => item.key === 'advanceInvoices')"), false) assert.equal(run('documentTypeToUse.value'), type === 'advanceInvoices' ? null : type) run("documentTypeToUse.value = 'advanceInvoices'; startImport()") assert.equal(navigations.length, 0) run("documentTypeToUse.value = 'invoices'; startImport()") const query = new URL(navigations[0], 'http://localhost').searchParams assert.equal(query.get('type'), 'invoices') assert.equal(query.get('createddocument'), '12') } }) test('+ Dokument excludes advance invoices even when opened inside a project', () => { for (const queryStringData of ['', 'project=7&customer=8']) { const { run, navigations } = fixture('components/createDocumentModal.vue', ['documentTypes', 'createDocument'], { props: { queryStringData }, }) assert.equal(run("documentTypes.value.some(item => item.key === 'advanceInvoices')"), false) run("createDocument('advanceInvoices')") assert.equal(navigations.length, 0) run("createDocument('quotes')") assert.equal(new URL(navigations[0], 'http://localhost').searchParams.get('type'), 'quotes') } }) test('advance invoice templates cannot create outgoing documents', () => { const { run, navigations } = fixture('components/createDocumentFromTemplateModal.vue', [ 'templates', 'selectedType', 'documentTypes', 'visibleTemplates', 'selectTemplate', ], { props: { queryStringData: 'project=7' } }) run("templates.value = [{ id: 1, documentType: 'advanceInvoices' }, { id: 2, documentType: 'quotes' }]") assert.equal(run("documentTypes.value.some(item => item.key === 'advanceInvoices')"), false) assert.equal(run('visibleTemplates.value.length'), 1) assert.equal(run('visibleTemplates.value[0].id'), 2) run('selectTemplate(templates.value[0])') assert.equal(navigations.length, 0) run('selectTemplate(templates.value[1])') assert.equal(new URL(navigations[0], 'http://localhost').searchParams.get('templateId'), '2') }) function editorFixture(query = {}, id) { return fixture('pages/createDocument/edit/[[id]].vue', [ 'originalDocumentType', 'canUseAdvanceInvoice', 'documentTypeItems', 'normalizeEntityId', 'validateAdvanceInvoiceCreation', 'saveDocument', ], { route: { params: { id }, query }, isTemplateMode: { value: false }, itemInfo: { value: { type: 'advanceInvoices', project: 7 } }, }) } test('only the project action opens a new advance invoice with its project and customer', () => { const { run, navigations, context } = fixture('components/EntityShowSubCreatedDocuments.vue', ['createAdvanceInvoice'], { props: { topLevelType: 'projects', item: { id: 7 }, queryStringData: 'customer=8&project=99' }, }) run('createAdvanceInvoice()') const query = Object.fromEntries(new URL(navigations[0], 'http://localhost').searchParams) assert.equal(query.project, '7') assert.equal(query.customer, '8') const editor = editorFixture(query) assert.equal(editor.run('validateAdvanceInvoiceCreation()'), true) assert.equal(editor.run("documentTypeItems.value.some(item => item.type === 'advanceInvoices')"), true) context.props.topLevelType = 'customers' run('createAdvanceInvoice()') assert.equal(navigations.length, 1) }) test('editor rejects alternate creation routes and saving a switched document type', async () => { const projectQuery = { type: 'advanceInvoices', loadMode: 'advanceInvoice', project: '7' } for (const query of [ {}, { type: 'advanceInvoices' }, { type: 'advanceInvoices', project: '7' }, { ...projectQuery, project: '' }, { ...projectQuery, project: 'invalid' }, { ...projectQuery, createddocument: '12' }, { ...projectQuery, optionsToImport: '{}' }, { ...projectQuery, linkedDocuments: '[12]' }, { ...projectQuery, templateId: '5' }, ]) { const { run, notices, navigations } = editorFixture(query) assert.equal(run("documentTypeItems.value.some(item => item.type === 'advanceInvoices')"), false) assert.equal(run('validateAdvanceInvoiceCreation()'), false) await run("saveDocument('Entwurf')") assert.equal(notices.length, 2) assert.equal(navigations.length, 0) } }) test('new advance invoices require a project; existing advance invoices remain editable', () => { const fresh = editorFixture({ type: 'advanceInvoices', loadMode: 'advanceInvoice', project: '7' }) fresh.run('itemInfo.value.project = null') assert.equal(fresh.run('validateAdvanceInvoiceCreation()'), false) const existing = editorFixture({}, '12') existing.run("originalDocumentType.value = 'advanceInvoices'; itemInfo.value.project = null") assert.equal(existing.run('validateAdvanceInvoiceCreation()'), true) assert.equal(existing.run("documentTypeItems.value.some(item => item.type === 'advanceInvoices')"), true) existing.run("originalDocumentType.value = 'invoices'") assert.equal(existing.run('validateAdvanceInvoiceCreation()'), false) existing.run("itemInfo.value.type = 'invoices'") assert.equal(existing.run('validateAdvanceInvoiceCreation()'), true) })