import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import test from 'node:test' import { parse } from '@vue/compiler-sfc' import ts from 'typescript' import * as navigation from '../src/utils/mp-webview' function handler(file: string, name: string, bindings: Record) { const source = parse(readFileSync(new URL(`../src/${file}`, import.meta.url), 'utf8')).descriptor .scriptSetup!.content const ast = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true) const fn = ast.statements.find( (node) => ts.isFunctionDeclaration(node) && node.name?.text === name ) assert.ok(fn, `${name} must exist`) return new Function(...Object.keys(bindings), ts.transpile(fn.getText(ast)) + `;return ${name}`)( ...Object.values(bindings) ) } test('mobile quote validates freight, deposit and validity bounds before either save path', () => { const form = { value: { quoteType: 'sales', supplierId: 's', customerId: 'c', contactId: 'p', ownerUserId: 'u', quoteDate: '2026-10-24', validityDays: 30, freight: 0, barrelDeposit: 0 } as Record } const validate = handler('views/crm/quote/components/QuoteFormPanel.vue', 'pageMissingMessage', { form, isPageVariant: { value: true }, quoteMeta: { blockingMessage: () => '' }, basicLabels: { value: {} } }) assert.equal(validate(), '') for (const field of ['freight', 'barrelDeposit']) { for (const invalid of [-1, '-0.01', 'invalid', Infinity]) { form.value[field] = invalid assert.notEqual(validate(), '', `${field}=${invalid} must fail`) } for (const valid of [0, '0', 1.25, '', null]) { form.value[field] = valid assert.equal(validate(), '') } form.value[field] = 0 } for (const invalid of [0, 366, Infinity, 'invalid']) { form.value.validityDays = invalid assert.notEqual(validate(), '', `validity=${invalid} must fail`) } for (const valid of [1, 365]) { form.value.validityDays = valid assert.equal(validate(), '') } }) test('quote customer navigation preserves approval and contract origins using fixed mobile routes', () => { const nav = navigation as Record assert.equal(typeof nav.mpQuoteCustomerLocation, 'function') assert.equal(typeof nav.mpRelatedQuoteReturnLocation, 'function') for (const origin of [ {}, { from: 'approve' }, { from: 'contract', returnContractId: 'K-1', originCustomerId: 'C-0' } ]) { const location = nav.mpQuoteCustomerLocation({ id: 'Q-1', customerId: 'C-1' }, origin) assert.equal(location.path, '/m/crm/customer/C-1') assert.deepEqual(nav.mpRelatedQuoteReturnLocation(location.query), { path: '/m/crm/quote/Q-1', query: origin }) } assert.equal(nav.mpQuoteCustomerLocation({ id: 'Q', customerId: '../bad' }, {}), null) assert.equal(nav.mpRelatedQuoteReturnLocation({ from: 'quote', returnQuoteId: '//evil' }), null) }) test('quote customer click is guarded and customer back uses the quote return destination', () => { const targets: any[] = [] let ready = false const goCustomer = handler('views/m/crm/quote/detail.vue', 'goCustomer', { navigationReady: () => ready, quote: { value: { id: 'Q', customerId: 'C' } }, route: { query: { from: 'approve' } }, router: { push: (v: any) => targets.push(v) }, mpQuoteCustomerLocation: (navigation as any).mpQuoteCustomerLocation }) goCustomer() assert.equal(targets.length, 0) ready = true goCustomer() assert.equal(targets[0].path, '/m/crm/customer/C') const destination = { path: '/m/crm/quote/Q', query: { from: 'approve' } } const back = handler('views/m/crm/customer/detail.vue', 'back', { relatedContractReturn: { value: null }, relatedQuoteReturn: { value: destination }, router: { replace: (v: any) => targets.push(v) }, CUSTOMER_PATH: '/m/crm/customer' }) back() assert.deepEqual(targets[1], destination) })