const assert = require('node:assert/strict') const fs = require('node:fs') const path = require('node:path') const test = require('node:test') const ts = require('typescript') const vue = require('vue') const { parse, compileScript, compileTemplate } = require('@vue/compiler-sfc') const handlers = [ 'isDualAuditPassed', 'canQuickTrackRow', 'canShipRow', 'openQuickTrack', 'submitQuickTrack', 'openShip', 'openEdit', 'submitEdit', 'resetEditOrderDialog' ] const stateNames = [ 'quickTrackVisible', 'quickTrackSaving', 'quickTrackRowId', 'quickTrackForm', 'shipVisible', 'shipRowId', 'shipForm', 'editVisible', 'editDialogLoading', 'editSaving', 'editOrderStep', 'editOrderPrescription', 'editFormRef', 'editForm', 'editDepositMin' ] for (const page of ['order_list.vue', 'order_list_h5.vue']) { const filename = path.join(__dirname, '../src/views/consumer/prescription', page) const { descriptor, errors } = parse(fs.readFileSync(filename, 'utf8'), { filename }) assert.deepEqual(errors, []) const script = compileScript(descriptor, { id: page }) const ast = ts.createSourceFile(filename + '.ts', descriptor.scriptSetup.content, ts.ScriptTarget.Latest, true) // Execute the actual order handlers with only network and unrelated UI dependencies stubbed. const declarations = new Map() for (const node of ast.statements) { if (ts.isFunctionDeclaration(node) && node.name) declarations.set(node.name.text, node.getText(ast)) if (ts.isVariableStatement(node)) { for (const declaration of node.declarationList.declarations) { declarations.set(declaration.name.getText(ast), `const ${declaration.getText(ast)}`) } } } const selected = [...handlers, ...stateNames] for (const name of selected) assert.ok(declarations.has(name), `${page}: missing ${name}`) const compiled = ts.transpileModule(selected.map(name => declarations.get(name)).join('\n'), { compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.CommonJS } }).outputText function instance(detail = {}, saveTracking = async () => {}) { const warnings = [] const errors = [] const edits = [] const tracks = [] const globals = { ref: vue.ref, reactive: vue.reactive, nextTick: vue.nextTick, feedback: { msgWarning: message => warnings.push(message), msgError: message => errors.push(message), msgSuccess() {} }, prescriptionOrderDetail: async () => ({ data: detail }), prescriptionOrderEdit: async payload => edits.push(payload), prescriptionOrderDdcode: async payload => { tracks.push(payload); await saveTracking(payload) }, canEditRow: () => true, parseServicePackageValues: () => [], loadEditPaidOrders: async () => {}, resolveShipModeForRow: row => row.ship_mode || 'gancao', editGancaoLogisticsOnlyMode: vue.ref(false), editGancaoDisplayNo: vue.ref(''), detailDrawerRef: vue.ref(null), detailVisible: vue.ref(false), getLists() {} } const state = new Function(...Object.keys(globals), `${compiled}\nreturn { ${selected.join(', ')} }`)(...Object.values(globals)) state.editFormRef.value = { validate: async () => {}, clearValidate() {} } return { state, warnings, errors, edits, tracks } } test(`${page}: only two approved audits unlock tracking, including legacy string statuses`, () => { const { state } = instance() for (const rx of [undefined, null, 0, 1, 2, '0', '1', '2']) { for (const pay of [undefined, null, 0, 1, 2, '0', '1', '2']) { const row = { prescription_audit_status: rx, payment_slip_audit_status: pay, fulfillment_status: 2 } const allowed = [1, '1'].includes(rx) && [1, '1'].includes(pay) assert.equal(state.canQuickTrackRow(row), allowed, `tracking: ${rx}/${pay}`) assert.equal(state.canShipRow(row), allowed, `shipping: ${rx}/${pay}`) } } for (const status of [1, 3, 4, 5, 6, 9]) { assert.equal(state.canShipRow({ prescription_audit_status: 1, payment_slip_audit_status: 1, fulfillment_status: status }), false) } }) test(`${page}: direct handler calls cannot open tracking or shipping before both approvals`, () => { for (const [rx, pay] of [[0, 0], [1, 0], [0, 1], [2, 1], [1, 2]]) { const { state, warnings } = instance() const row = { id: 42, prescription_audit_status: rx, payment_slip_audit_status: pay, fulfillment_status: 2 } state.openQuickTrack(row) state.openShip(row) assert.equal(state.quickTrackVisible.value, false) assert.equal(state.shipVisible.value, false) assert.equal(state.quickTrackRowId.value, 0) assert.equal(state.shipRowId.value, 0) assert.equal(warnings.length, 2) } }) test(`${page}: approved orders can fill or replace the number and preserve the carrier`, async () => { for (const oldNumber of ['', 'SF-OLD']) { const { state, tracks } = instance() const row = { id: 42, prescription_audit_status: 1, payment_slip_audit_status: 1, fulfillment_status: 2, express_company: 'sf', tracking_number: oldNumber } state.openQuickTrack(row) assert.equal(state.quickTrackVisible.value, true) assert.equal(state.quickTrackForm.tracking_number, oldNumber) state.quickTrackForm.tracking_number = ' SF-NEW ' await state.submitQuickTrack() assert.deepEqual(tracks, [{ id: 42, express_company: 'sf', tracking_number: 'SF-NEW' }]) assert.equal(state.quickTrackVisible.value, false) state.openShip(row) assert.equal(state.shipVisible.value, true) } }) test(`${page}: a server rejection after audit revocation retains the dialog and entered number`, async () => { const { state } = instance({}, async () => { throw new Error('audit revoked') }) state.openQuickTrack({ id: 42, prescription_audit_status: 1, payment_slip_audit_status: 1 }) state.quickTrackForm.tracking_number = 'SF-NEW' await state.submitQuickTrack() assert.equal(state.quickTrackVisible.value, true) assert.equal(state.quickTrackSaving.value, false) assert.equal(state.quickTrackForm.tracking_number, 'SF-NEW') }) test(`${page}: editing preserves the existing number while saving other fields before approval`, async () => { const { state, edits, errors } = instance({ id: 42, prescription_audit_status: 1, payment_slip_audit_status: 0, tracking_number: 'SF-EXISTING' }) await state.openEdit({ id: 42, prescription_audit_status: 1, payment_slip_audit_status: 1 }) assert.deepEqual(errors, []) assert.equal(state.editForm.tracking_number, 'SF-EXISTING') state.editForm.tracking_number = 'FORGED' state.editForm.recipient_name = '修改后的收货人' state.editOrderStep.value = 2 await state.submitEdit() assert.equal(edits.length, 1) assert.equal(edits[0].recipient_name, '修改后的收货人') assert.equal(Object.hasOwn(edits[0], 'tracking_number'), false) }) test(`${page}: ordinary editing cannot change tracking while resetting an approved payment audit`, async () => { const { state, edits, errors } = instance({ id: 42, prescription_audit_status: '1', payment_slip_audit_status: '1' }) await state.openEdit({ id: 42 }) assert.deepEqual(errors, []) state.editForm.tracking_number = 'SF-NEW' state.editOrderStep.value = 2 await state.submitEdit() assert.equal(edits.length, 1) assert.equal(Object.hasOwn(edits[0], 'tracking_number'), false) }) test(`${page}: template compiles with tracking controls bound to audit restrictions`, () => { const template = compileTemplate({ source: descriptor.template.content, filename, id: page, compilerOptions: { bindingMetadata: script.bindings } }) assert.deepEqual(template.errors, []) const inputs = [...descriptor.template.content.matchAll(/]*v-model="editForm\.tracking_number"[^>]*>/g)] assert.equal(inputs.length, page === 'order_list.vue' ? 2 : 1) for (const [input] of inputs) assert.match(input, /\sdisabled(?:\s|\/?>)/) assert.match(descriptor.template.content, /v-if="canQuickTrackRow\(row\)"/) }) }