| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226 |
- import assert from 'node:assert/strict'
- import test from 'node:test'
- import { readFileSync } from 'node:fs'
- import { parse } from '@vue/compiler-sfc'
- import ts from 'typescript'
- import { ref } from 'vue'
- function evaluate(file: string, names: string[], b: Record<string, any>) {
- const ast = ts.createSourceFile(
- 'x.ts',
- parse(readFileSync(new URL(file, import.meta.url), 'utf8')).descriptor.scriptSetup!.content,
- ts.ScriptTarget.Latest,
- true
- )
- const text = names
- .map((name) => {
- const n = ast.statements.find((n) =>
- ts.isFunctionDeclaration(n)
- ? n.name?.text === name
- : ts.isVariableStatement(n) &&
- n.declarationList.declarations.some((d) => d.name.getText(ast) === name)
- )
- assert.ok(n, name)
- return n.getText(ast)
- })
- .join('\n')
- return new Function(
- ...Object.keys(b),
- ts.transpile(text, { target: ts.ScriptTarget.ESNext }) + ';return {' + names.join(',') + '}'
- )(...Object.values(b))
- }
- const file = '../src/components/business/pl-plan-form-dialog/index.vue'
- function harness(overrides: any = {}) {
- const writes: string[] = [],
- events: any[] = []
- const yes = () => true,
- noop = () => {},
- b: any = {
- ref,
- isPageVariant: ref(true),
- pageReady: ref(true),
- mobileMetadata: { ready: yes },
- mirrorPlanProductsFromLoad: noop,
- buildMobileProductLinks: noop,
- loadForm: ref({ products: [] }),
- deliveryForms: ref([]),
- formData: { products: [], vehicleId: 'v1' },
- pageLoadGeneration: 1,
- isPageSubmitCurrent: yes,
- isPageLoadCurrent: yes,
- submitLoading: ref(false),
- mobileSavedPlanId: ref(''),
- mobileCreateUncertain: ref(false),
- props: {},
- isEdit: ref(false),
- basicFormRef: ref({ validate: async () => true }),
- validateBusinessRules: yes,
- prefetchStopContractProducts: async () => {},
- validateSingleContractPerStop: yes,
- validateContractPackageSpecs: yes,
- validateStopContractsRequired: yes,
- getArrangePrerequisiteGaps: () => [],
- validateStopProductsForArrange: yes,
- buildPlanCommonPayload: () => ({}),
- planApi: {
- createFull: async () => {
- writes.push('create')
- return { id: 'p1' }
- },
- update: async () => {
- writes.push('update')
- }
- },
- pickBxData: (x: any) => x,
- showPlanSaveWarnings: noop,
- ElMessage: { warning: noop, error: noop, success: noop },
- innerVisible: ref(true),
- emit: (...a: any[]) => events.push(a),
- performArrangeAfterSave: async () => {
- writes.push('arrange')
- throw Error('get failed')
- },
- ...overrides
- }
- return { ...evaluate(file, ['handleSubmit'], b), b, writes, events }
- }
- test('create success then GET failure retains identity and emits saved result', async () => {
- const h = harness()
- await h.handleSubmit('saveAndArrange')
- assert.equal(h.b.mobileSavedPlanId.value, 'p1')
- assert.deepEqual(h.events.at(-1), ['success', { planId: 'p1', arranged: false }])
- })
- test('double click creates once', async () => {
- const h = harness()
- await Promise.all([h.handleSubmit('saveAndArrange'), h.handleSubmit('saveAndArrange')])
- assert.equal(h.writes.filter((x) => x === 'create').length, 1)
- })
- for (const reason of ['certificate', 'overflow', 'cancel', 'arrange500', 'get'])
- test('actual arrange handler preserves saved ID after ' + reason, async () => {
- const h = harness()
- Object.assign(h.b, {
- loadedPlanSnapshot: ref(null),
- selectedDriverId: ref(''),
- selectedEscortId: ref(''),
- freightCalc: {},
- getUnitLabel: (x: any) => x,
- enrichPlanProductsHazardFromCrm: async (x: any) => x,
- buildMissingHazardCertReadyMessage: () => (reason === 'certificate' ? '缺少证件' : ''),
- createArrangePresenter: () => async () => {},
- ElMessageBox: { alert: async () => {} },
- planHasAnyDibangSelected: () => false,
- loadArrangeHistoricalFlags: async () => ({}),
- confirmBeforePlanArrange: async () => {
- if (reason === 'cancel') throw 'cancel'
- },
- buildArrangePayloadFromPlanForm: () => ({}),
- submitPlanArrange: async () => {
- throw {
- response: {
- data: {
- detail: { code: reason === 'overflow' ? 'CONTRACT_QTY_OVERFLOW' : 'SERVER_ERROR' }
- }
- }
- }
- }
- })
- h.b.planApi.get = async () => {
- if (reason === 'get') throw Error('GET failed')
- return { id: 'p1', products: [], transportStatus: 'arranged' }
- }
- h.b.performArrangeAfterSave = evaluate(
- file,
- ['performArrangeAfterSave'],
- h.b
- ).performArrangeAfterSave
- const handler = evaluate(file, ['handleSubmit'], h.b).handleSubmit
- await handler('saveAndArrange')
- assert.equal(h.b.mobileSavedPlanId.value, 'p1')
- await handler('saveAndArrange')
- assert.equal(h.writes.filter((x) => x === 'create').length, 1)
- assert.deepEqual(h.events.at(-1), ['success', { planId: 'p1', arranged: false }])
- })
- test('uncertain create locks retry', async () => {
- let count = 0
- const h = harness({
- planApi: {
- createFull: async () => {
- count++
- throw Error('network')
- }
- }
- })
- await h.handleSubmit('saveAndArrange')
- await h.handleSubmit('saveAndArrange')
- assert.equal(count, 1)
- assert.equal(h.b.mobileCreateUncertain.value, true)
- })
- test('adjustment only updates', async () => {
- const h = harness({ props: { planId: 'existing' }, isEdit: ref(true) })
- await h.handleSubmit('rearrange')
- assert.deepEqual(h.writes, ['update', 'arrange'])
- })
- test('quick create selects returned vehicle without resetting stop choices or unsaved fields', async () => {
- const formData = { vehicleId: 'old', remark: 'unsaved' },
- calls: any[] = []
- const b = {
- isPageVariant: ref(true),
- formData,
- pageLoadGeneration: 1,
- isPageSubmitCurrent: () => true,
- ensureSelectedVehicleOption: async () => {},
- handleVehicleChange: async (...args: any[]) => calls.push(args),
- handleVehicleLibrarySelect: async () => {},
- ElMessage: { warning: () => {} }
- }
- const done = evaluate(file, ['handlePasteRecognizeDone'], b).handlePasteRecognizeDone
- await done({ vehicleId: 'new' })
- assert.equal(formData.vehicleId, 'new')
- assert.equal(formData.remark, 'unsaved')
- assert.equal(calls[0][1].syncStopDibang, false)
- })
- test('explicit validation rejection unlocks correction retry', async () => {
- let count = 0
- const h = harness({ planApi: { createFull: async () => { count++; throw { response: { status: 422 } } } } })
- await h.handleSubmit('saveAndArrange').catch(() => {})
- assert.equal(h.b.mobileCreateUncertain.value, false)
- await h.handleSubmit('saveAndArrange').catch(() => {})
- assert.equal(count, 2)
- })
- test('new plan session clears saved identity and uncertain result', async () => {
- const b = { isPageVariant: ref(true), mobileSavedPlanId: ref('old'), mobileCreateUncertain: ref(true), pageLoadGeneration: 1, pageReady: ref(true), arrangeSheetsRef: ref(null),
- props: { visible: false }, handleOpened: async () => {} }
- const run = evaluate(file, ['handleMobilePlanSessionChange'], b).handleMobilePlanSessionChange
- await run()
- assert.equal(b.mobileSavedPlanId.value, '')
- assert.equal(b.mobileCreateUncertain.value, false)
- assert.equal(b.pageReady.value, false)
- })
- function arrangeFunctions(bindings: Record<string, any>) {
- const source = readFileSync(new URL('../src/composables/usePlPlanArrangeSubmit.ts', import.meta.url), 'utf8')
- const ast = ts.createSourceFile('arrange.ts', source, ts.ScriptTarget.Latest, true)
- const text = ast.statements.filter(n => ts.isFunctionDeclaration(n) && ['confirmBeforePlanArrange', 'submitPlanArrange'].includes(n.name!.text)).map(n => n.getText(ast).replace(/^export /, '')).join('\n')
- return new Function(...Object.keys(bindings), ts.transpile(text, { target: ts.ScriptTarget.ESNext }) + ';return {confirmBeforePlanArrange,submitPlanArrange}')(...Object.values(bindings))
- }
- test('arrange presenter replaces HTML dialogs while PC keeps original calls and payload', async () => {
- const calls:any[] = [], payload = { vehicleId:'v', driverId:undefined, escortId:undefined }
- const b = { planRequiresSiteLink:()=>false, ElMessageBox:{confirm:async(...args:any[])=>calls.push(['pc',...args])}, transportApi:{arrange:async(id:string,body:any)=>{calls.push(['api',id,body]);return {contractQtyReminder:{items:[{remainingQty:2}]}}}}, pickBxData:(x:any)=>x, showContractQtyReminderIfAny:async()=>calls.push(['pc-reminder']), showContractQtyViolationIfMatch:async()=>calls.push(['pc-violation']), extractContractQtyViolationDetail:(e:any)=>e.response?.data?.detail?.code==='CONTRACT_QTY_OVERFLOW'?e.response.data.detail:null }
- const f=arrangeFunctions(b), presenter=async(request:any)=>{calls.push(['mobile',request])}
- await f.confirmBeforePlanArrange({isRearrange:true,plan:{},presenter})
- assert.equal(calls[0][0],'mobile');assert.equal(calls[0][1].confirmText,'继续调整')
- await f.submitPlanArrange('p',payload,presenter)
- assert.equal(calls[1][2],payload);assert.equal(calls[2][1].items[0].remainingQty,2)
- await f.confirmBeforePlanArrange({isRearrange:true,plan:{}})
- assert.deepEqual(calls[3],['pc','司机链接将更新,旧链接将失效。是否继续?','调整运输确认',{type:'warning',confirmButtonText:'继续调整',cancelButtonText:'取消'}])
- const failure={response:{data:{detail:{code:'CONTRACT_QTY_OVERFLOW',message:'超量',items:[{remainingQty:-1}]}}}}
- b.transportApi.arrange=async()=>{throw failure}
- await assert.rejects(f.submitPlanArrange('p',payload,presenter),e=>e===failure)
- assert.equal(calls.at(-1)[1].items[0].remainingQty,-1)
- })
- test('generation-bound presenter ignores old arrange response and session change settles old sheet',async()=>{
- let generation=1;const presented:any[]=[],closed:any[]=[]
- const b={isPageSubmitCurrent:(value:number)=>value===generation,arrangeSheetsRef:ref({present:async(value:any)=>presented.push(value),close:()=>closed.push(true)}),isPageVariant:ref(true),mobileSavedPlanId:ref('old'),mobileCreateUncertain:ref(true),pageLoadGeneration:1,pageReady:ref(true),props:{visible:false},handleOpened:async()=>{}}
- const f=evaluate(file,['createArrangePresenter','handleMobilePlanSessionChange'],b)
- const old=f.createArrangePresenter(1);await f.handleMobilePlanSessionChange();generation=2
- await old({title:'old response'});assert.equal(presented.length,0);assert.equal(closed.length,1)
- await f.createArrangePresenter(2)({title:'new response'});assert.equal(presented[0].title,'new response')
- })
|