Files
shanxia2.0/web-admin/scripts/mobile-quote-form-detail-parity.test.ts

112 lines
3.9 KiB
TypeScript

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<string, any>) {
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<string, any>
}
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<string, any>
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)
})