Files
shanxia2.0/web-admin/scripts/mobile-quote-customer-maintenance.test.ts
T

172 lines
6.4 KiB
TypeScript

import assert from 'node:assert/strict'
import test from 'node:test'
import { readFileSync } from 'node:fs'
import { parse, compileScript, compileTemplate } from '@vue/compiler-sfc'
import { ref, nextTick } from 'vue'
import ts from 'typescript'
const file = new URL('../src/views/crm/quote/components/QuoteFormPanel.vue', import.meta.url)
function harness(fetchContacts: (input: any) => Promise<any> = async () => []) {
const events: any[] = []
const b: Record<string, any> = {
form: ref({
customerId: 'C1',
customerName: '客户一',
contactId: 'P1',
contactName: '联系人一',
products: [{ productId: 'product' }],
freight: 12
}),
isPageVariant: ref(true),
route: { fullPath: '/m/crm/quote/new' },
customerShortName: ref('简称'),
confirmedCustomerId: ref('C1'),
contactOptions: ref([]),
contactOptionsLoading: ref(false),
contactOptionsError: ref(''),
contactEmptyPrompted: ref(false),
opportunityDisplayName: ref(''),
mobileContactFormCustomerId: ref(''),
mobileContactKeyword: ref(''),
contactSelectorVisible: ref(false),
mobileCustomerEditor: ref(null),
mobileInvoiceCustomer: ref(null),
customerSelectorVisible: ref(true),
fetchContactsByCustomerId: fetchContacts,
extractContactList: (res: any) => res?.data ?? res,
ensureContactOption: () => {},
ensureCustomerOption: (row: any) => events.push(['option', row]),
updateQuoteTitleAndNo: () => events.push(['title']),
loadMobileCustomers: async () => events.push(['reload']),
basicLabels: ref({ contactName: '客户联系人' }),
showFailToast: (message: string) => events.push(['error', message]),
ElMessage: { warning: () => {} },
nextTick,
contactFormRef: ref({ open: () => events.push(['web-contact']) }),
$modal: { msgWarning: () => {} },
handleContactConfirm: (row: any) => {
b.form.value.contactId = row.id
b.form.value.contactName = row.contactName
}
}
const names = [
'contactOptionsRequest',
'loadContactOptions',
'openMobileContactForm',
'openContactFormDialog',
'handleCustomerConfirm',
'openMobileContact',
'handleMobileContactSaved',
'handleMobileCustomerSaved',
'openMobileCustomerEditor',
'openMobileInvoice'
]
const ast = ts.createSourceFile(
'panel.ts',
parse(readFileSync(file, 'utf8')).descriptor.scriptSetup!.content,
ts.ScriptTarget.Latest,
true
)
const nodes = ast.statements.filter((n) =>
ts.isFunctionDeclaration(n)
? names.includes(n.name?.text || '')
: ts.isVariableStatement(n) &&
names.includes(n.declarationList.declarations[0].name.getText(ast))
)
assert.equal(nodes.length, names.length, 'maintenance handlers must exist')
const api = new Function(
...Object.keys(b),
ts.transpile(nodes.map((n) => n.getText(ast)).join('\n')) + `;return {${names.join(',')}}`
)(...Object.values(b))
return { ...b, ...api, events }
}
test('customer chooser offers create, edit and invoice actions without selecting the row', () => {
const t = parse(readFileSync(file, 'utf8')).descriptor.template!.content
for (const action of [
'@click="openMobileCustomerEditor()"',
'@click.stop="openMobileCustomerEditor(item)"',
'@click.stop="openMobileInvoice(item)"'
])
assert.ok(t.includes(action), action)
})
test('empty contacts open mobile creation; existing primary contact is selected', async () => {
for (const contacts of [
[],
[
{ id: 'P2', contactName: '普通' },
{ id: 'P3', contactName: '主要', isPrimary: 1 }
]
]) {
const h = harness(async () => contacts)
await h.handleCustomerConfirm({ id: 'C2', customerName: '客户二', shortName: '二' })
assert.equal(h.mobileContactFormCustomerId.value, contacts.length ? '' : 'C2')
assert.equal(h.form.value.contactId, contacts.length ? 'P3' : undefined)
assert.equal(h.form.value.freight, 12)
}
})
test('contact load failure permits retry and never opens create', async () => {
let fail = true
const h = harness(async () => {
if (fail) throw new Error('网络失败')
return []
})
await h.handleCustomerConfirm({ id: 'C2', customerName: '客户二' })
assert.equal(h.mobileContactFormCustomerId.value, '')
assert.ok(h.contactOptionsError.value)
fail = false
await h.openMobileContact()
assert.equal(h.mobileContactFormCustomerId.value, 'C2')
})
for (const change of ['customer', 'route'])
test(`late contact responses after ${change} change are discarded`, async () => {
let resolve!: (value: any) => void
const h = harness(
() =>
new Promise((r) => {
resolve = r
})
)
const pending = h.handleCustomerConfirm({ id: 'C2', customerName: '客户二' })
if (change === 'customer') h.form.value.customerId = 'C3'
else h.route.fullPath = '/m/crm/quote'
resolve([])
await pending
assert.equal(h.mobileContactFormCustomerId.value, '')
})
test('customer editing refreshes labels but preserves quotation and contact', async () => {
const h = harness()
await h.handleMobileCustomerSaved({ id: 'C1', customerName: '新名称', shortName: '新简称' })
assert.equal(h.form.value.customerName, '新名称')
assert.equal(h.customerShortName.value, '新简称')
assert.equal(h.form.value.contactId, 'P1')
assert.equal(h.form.value.products[0].productId, 'product')
assert.equal(h.form.value.freight, 12)
assert.ok(h.events.some((e: any) => e[0] === 'reload'))
})
test('created contact fills current customer; stale customer events are ignored', async () => {
const h = harness()
h.mobileContactFormCustomerId.value = 'C1'
await h.handleMobileContactSaved({ id: 'P2', customerId: 'C1', contactName: '新增联系人' })
assert.equal(h.form.value.contactId, 'P2')
await h.handleMobileContactSaved({ id: 'P3', customerId: 'OTHER', contactName: '错误联系人' })
assert.equal(h.form.value.contactId, 'P2')
})
test('affected Vue templates compile', () => {
for (const path of [
'views/crm/quote/components/QuoteFormPanel.vue',
'views/m/crm/customer/form.vue',
'views/m/crm/customer/ContactPanel.vue'
]) {
const d = parse(readFileSync(new URL('../src/' + path, import.meta.url), 'utf8')).descriptor
const s = compileScript(d, { id: path })
assert.deepEqual(
compileTemplate({
source: d.template!.content,
filename: path,
id: path,
compilerOptions: { bindingMetadata: s.bindings }
}).errors,
[]
)
}
})