import assert from 'node:assert/strict' import test from 'node:test' import { readFileSync } from 'node:fs' import ts from 'typescript' import * as vue from 'vue' import * as pinia from 'pinia' import { requireOrderProgress } from '../utils/orderProgress.ts' function compile(path, dependencies) { const source = readFileSync(new URL(path, import.meta.url), 'utf8') const code = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 } }).outputText const exports = {} new Function('require', 'exports', code)(id => { assert.ok(id in dependencies, id); return dependencies[id] }, exports) return exports } function harness(t) { const context = pinia.createPinia(); pinia.setActivePinia(context) t.after(() => pinia.disposePinia(context)) const calls = [] const behavior = { failWrite: false, failRead: false, version: 9, status: 'DRAFT' } const api = compile('../api/firstLeg.ts', { '@/api/http': { apiRequest: async config => { calls.push(structuredClone(config)) if (config.method === 'GET') { if (behavior.failRead) throw new Error('读取失败') return { order: { id: 'one', orderNo: 'FL-ONE', version: behavior.version, status: behavior.status, receivingWarehouseId: 'warehouse', receivingWarehouseAddress: behavior.receivingWarehouseAddress, destinationCountryCode: 'US' }, cargoes: [] } } if (behavior.failWrite || (behavior.failSubmit && config.url.endsWith('/submit'))) throw new Error('响应丢失') if (config.url.endsWith('/submit') && behavior.failSubmitRead) behavior.failRead = true if (config.method === 'POST' && config.url === '/merchant/v1/orders/first-leg') return 'one' } } }) const module = compile('./firstLegOrders.ts', { vue, pinia, '@/modules/first-leg/api/firstLeg': api, '@/modules/first-leg/api/unifiedOrders': { getMerchantUnifiedOrder: async () => ({ progress: { orderId: 'one', stage: 'none', partial: false }, summary: { orderId: 'one', mainStatus: null }, order: { order: { id: 'one', status: behavior.status, version: behavior.version, receivingWarehouseId: 'warehouse', receivingWarehouseAddress: behavior.receivingWarehouseAddress } }, timeline: [] }), }, '@/modules/first-leg/utils/firstLegOrder': { canClientEditFirstLegOrder: status => status === 'DRAFT', getFirstLegStatusLabel: value => value }, '@/modules/first-leg/utils/warehousePagination': {}, '@/modules/first-leg/utils/orderProgress': { requireOrderProgress }, }) const store = module.useFirstLegOrdersStore() store.warehouses = [{ id: 'warehouse', name: '国内仓', warehouseType: 'DOMESTIC' }] const draft = { receivingWarehouseId: 'warehouse', destinationType: 'PRIVATE', destinationCountryCode: 'US', items: [], customerRemark: '原草稿' } return { store, api, calls, behavior, draft } } function displayedWarehouseAddress(order) { const view = readFileSync(new URL('../views/FirstLegOrdersView.vue', import.meta.url), 'utf8') const expressions = [...view.matchAll(/\{\{ (detailOrder\.receivingWarehouseSnapshot\.(?:province|city|address)) \}\}/g)] assert.equal(expressions.length, 3) return expressions.map(([, expression]) => new Function('detailOrder', `return ${expression}`)(order)).join('') } test('complete address is identical for cold unified, warm unified, raw detail and remark-only save', async t => { const h = harness(t) const fullAddress = 'CN 浙江省 义乌市 仓库路 8 号 322000' h.behavior.receivingWarehouseAddress = fullAddress h.store.warehouses = [] const cold = await h.store.loadDetail('one') assert.equal(displayedWarehouseAddress(cold), fullAddress) h.store.warehouses = [{ id: 'warehouse', name: '国内仓', warehouseType: 'DOMESTIC', province: '浙江省', city: '义乌市', address: '主档已更新的地址' }] h.store.orders = [] const warm = await h.store.loadDetail('one') const raw = await h.store.loadDraftDetail('one') for (const detail of [warm, raw]) { assert.equal(displayedWarehouseAddress(detail), fullAddress) assert.equal(detail.receivingWarehouseSnapshot.province, '') assert.equal(detail.receivingWarehouseSnapshot.city, '') } const session = h.store.beginDraftEdit(raw) h.draft.customerRemark = '仅修改备注' const saved = await h.store.saveDraft(h.draft, '客户', 'one', session) assert.equal(displayedWarehouseAddress(saved), fullAddress) const write = h.calls.find(call => call.method === 'PUT') assert.equal(write.data.draft.customerRemark, '仅修改备注') assert.equal(Object.hasOwn(write.data.draft, 'receivingWarehouseAddress'), false) assert.equal(h.behavior.receivingWarehouseAddress, fullAddress) assert.equal(h.store.warehouses[0].province, '浙江省') assert.equal(h.store.warehouses[0].address, '主档已更新的地址') }) test('structured master address stays structured when no order address snapshot exists', async t => { const h = harness(t) h.store.warehouses = [{ id: 'warehouse', name: '国内仓', province: '浙江省', city: '义乌市', address: '仓库路 8 号', warehouseType: 'DOMESTIC' }] const detail = await h.store.loadDetail('one') assert.equal(detail.receivingWarehouseSnapshot.province, '浙江省') assert.equal(detail.receivingWarehouseSnapshot.city, '义乌市') assert.equal(detail.receivingWarehouseSnapshot.address, '仓库路 8 号') assert.equal(displayedWarehouseAddress(detail), '浙江省义乌市仓库路 8 号') }) test('raw and unified cancelled detail remain CANCELLED even when unified mainStatus is null', async t => { const h = harness(t) h.behavior.status = 'CANCELLED' const raw = await h.store.loadDraftDetail('one') assert.equal(raw.status, 'CANCELLED') const unified = await h.store.loadDetail('one') assert.equal(unified.status, 'CANCELLED') h.behavior.status = 'APPROVED' const unknown = await h.store.loadDetail('one') assert.notEqual(unknown.status, 'CANCELLED', 'unknown summary must not be reclassified as cancellation') }) test('fresh direct draft detail preserves raw DRAFT with null unified summary and opens an edit session', async t => { const h = harness(t) assert.equal(h.store.orders.length, 0) const direct = await h.store.loadDetail('one') assert.equal(direct.status, 'DRAFT') assert.equal(h.store.beginDraftEdit(direct).expectedVersion, 9) h.store.orders = [] const refreshed = await h.store.loadDetail('one') assert.equal(refreshed.status, 'DRAFT') h.store.orders = [] h.behavior.status = 'APPROVED' const unknown = await h.store.loadDetail('one') assert.notEqual(unknown.status, 'DRAFT') assert.notEqual(unknown.status, 'CANCELLED') assert.throws(() => h.store.beginDraftEdit(unknown), /重新打开/) }) test('cancelled detail summary has no pending overseas synchronization entries', () => { const source = readFileSync(new URL('../views/FirstLegOrdersView.vue', import.meta.url), 'utf8') const script = source.match(/