mobile-pl-paste.test.ts 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. import assert from 'node:assert/strict'
  2. import test from 'node:test'
  3. import { readFileSync } from 'node:fs'
  4. import { parse } from '@vue/compiler-sfc'
  5. import ts from 'typescript'
  6. import { ref } from 'vue'
  7. function evaluate(file: string, names: string[], b: Record<string, any>) {
  8. const ast = ts.createSourceFile(
  9. 'x.ts',
  10. parse(readFileSync(new URL(file, import.meta.url), 'utf8')).descriptor.scriptSetup!.content,
  11. ts.ScriptTarget.Latest,
  12. true
  13. )
  14. const text = names
  15. .map((name) => {
  16. const n = ast.statements.find((n) =>
  17. ts.isFunctionDeclaration(n)
  18. ? n.name?.text === name
  19. : ts.isVariableStatement(n) &&
  20. n.declarationList.declarations.some((d) => d.name.getText(ast) === name)
  21. )
  22. assert.ok(n, name)
  23. return n.getText(ast)
  24. })
  25. .join('\n')
  26. return new Function(
  27. ...Object.keys(b),
  28. ts.transpile(text, { target: ts.ScriptTarget.ESNext }) + ';return {' + names.join(',') + '}'
  29. )(...Object.values(b))
  30. }
  31. const file = '../src/components/business/pl-paste-recognize-dialog/index.vue'
  32. function harness(overrides: any = {}) {
  33. const calls: any[] = [],
  34. events: any[] = []
  35. const b: any = {
  36. ref,
  37. pasteSession: ref(0),
  38. mobileCarriers: ref([]),
  39. mobileCarrierLoading: ref(false),
  40. pasteDisposed: ref(false),
  41. props: { mode: 'vehicle-bundle', visible: true },
  42. isPageVariant: ref(true),
  43. isMobileVariant: ref(true),
  44. parsed: ref({}),
  45. rawText: ref('车牌:皖QA1023 皖QA102挂'),
  46. vehicleForm: { licensePlate: '皖QA1023', trailerNo: '皖QA102挂', loadCapacity: 3 },
  47. driverForm: { personnelName: '', phoneNo: '', idCardNo: '' },
  48. escortForm: { personnelName: '', phoneNo: '', idCardNo: '' },
  49. carrierOptions: ref([]),
  50. carrierId: ref(''),
  51. conflictRows: ref([]),
  52. showVehicleFields: ref(true),
  53. submitLoading: ref(false),
  54. parseLoading: ref(false),
  55. conflictFingerprint: ref(''),
  56. currentConflictFingerprint: () => '',
  57. ensureCarriers: async () => {},
  58. detectConflicts: async () => {},
  59. pasteRecognizeApi: {
  60. commit: async (p: any) => {
  61. calls.push(p)
  62. return { vehicleId: 'v1' }
  63. }
  64. },
  65. pickBxData: (x: any) => x,
  66. ElMessage: { warning: () => {}, info: () => {}, error: () => {}, success: () => {} },
  67. emit: (...a: any[]) => events.push(a),
  68. innerVisible: ref(true),
  69. ...overrides
  70. }
  71. Object.assign(b, evaluate(file, ['isPasteSessionCurrent', 'invalidatePasteSession'], b))
  72. return { ...evaluate(file, ['handleSubmit', 'handleCancel'], b), b, calls, events }
  73. }
  74. test('optional empty personnel produces null people and correct vehicle ID', async () => {
  75. const h = harness()
  76. await h.handleSubmit()
  77. assert.equal(h.calls[0].driver, null)
  78. assert.equal(h.calls[0].escort, null)
  79. assert.deepEqual(h.events[0], ['done', { vehicleId: 'v1' }])
  80. })
  81. test('quick create double click commits once', async () => {
  82. const h = harness()
  83. await Promise.all([h.handleSubmit(), h.handleSubmit()])
  84. assert.equal(h.calls.length, 1)
  85. })
  86. test('cancel does not commit; failed generation retains inputs', async () => {
  87. const h = harness({
  88. pasteRecognizeApi: {
  89. commit: async () => {
  90. throw Error('failed')
  91. }
  92. }
  93. })
  94. h.handleCancel()
  95. assert.equal(h.calls.length, 0)
  96. await h.handleSubmit()
  97. assert.equal(h.b.vehicleForm.licensePlate, '皖QA1023')
  98. assert.equal(
  99. h.events.some((e: any) => e[0] === 'done'),
  100. false
  101. )
  102. })
  103. for (const action of ['reuse', 'overwrite', 'cancel'])
  104. test('conflict ' + action + ' keeps payload choice', async () => {
  105. const row = { key: 'vehicle', existingId: 'old', action }
  106. const h = harness({ conflictRows: ref([row]) })
  107. await h.handleSubmit()
  108. if (action === 'cancel') assert.equal(h.calls.length, 0)
  109. else assert.deepEqual(h.calls[0].conflicts, [row])
  110. })
  111. test('edited identity triggers fresh conflict lookup before commit', async () => {
  112. const h = harness({
  113. conflictFingerprint: ref('old'),
  114. currentConflictFingerprint: () => 'new',
  115. conflictRows: ref([{ key: 'vehicle', existingId: 'old', action: 'reuse' }])
  116. })
  117. h.b.detectConflicts = async () => {
  118. h.b.conflictRows.value = [{ key: 'vehicle', existingId: 'new', action: 'reuse' }]
  119. h.b.conflictFingerprint.value = 'new'
  120. return true
  121. }
  122. const run = evaluate(file, ['handleSubmit'], h.b).handleSubmit
  123. await run()
  124. assert.equal(h.calls.length, 0)
  125. await run()
  126. assert.equal(h.calls[0].conflicts[0].existingId, 'new')
  127. })
  128. test('actual lookup ignores stale identity response', async () => {
  129. let release: any
  130. const pending = new Promise((r) => (release = r))
  131. const h = harness({
  132. currentConflictFingerprint: () => h.b.vehicleForm.licensePlate,
  133. vehicleApi: { list: () => pending },
  134. personnelApi: { list: async () => ({ list: [] }) },
  135. pickBxPageResult: (x: any) => x
  136. })
  137. const lookup = evaluate(file, ['detectConflicts'], h.b).detectConflicts
  138. const running = lookup()
  139. h.b.vehicleForm.licensePlate = 'changed'
  140. release({ list: [{ id: 'old', licensePlate: '皖QA1023' }] })
  141. assert.equal(await running, false)
  142. assert.equal(h.b.conflictRows.value.length, 0)
  143. })
  144. test('old same-identity lookup cannot overwrite a reopened session conflict', async () => {
  145. let release:any
  146. const h=harness({ createVehicle:ref(true), PL_PASTE_SAMPLE_TEXT:'', vehicleApi:{list:()=>new Promise(r=>release=r)}, personnelApi:{list:async()=>({list:[]})}, pickBxPageResult:(x:any)=>x })
  147. const f=evaluate(file,['detectConflicts','handleClosed'],h.b)
  148. const old=f.detectConflicts();f.handleClosed()
  149. h.b.vehicleForm.licensePlate='皖QA1023';h.b.conflictRows.value=[{key:'vehicle',existingId:'new-session-id',action:'reuse'}]
  150. release({list:[{id:'old-session-id',licensePlate:'皖QA1023'}]})
  151. await old;assert.equal(h.b.conflictRows.value[0].existingId,'new-session-id')
  152. })
  153. for (const reject of [false,true]) test('closed-session commit '+(reject?'failure':'success')+' neither emits nor resets new loading',async()=>{
  154. let settle:any
  155. const h=harness({createVehicle:ref(true), PL_PASTE_SAMPLE_TEXT:'',pasteRecognizeApi:{commit:()=>new Promise((yes,no)=>settle=reject?no:yes)}})
  156. const f=evaluate(file,['handleSubmit','handleClosed'],h.b)
  157. const running=f.handleSubmit();await new Promise(r=>setTimeout(r,0));f.handleClosed();h.b.submitLoading.value=true
  158. settle(reject?Error('old failure'):{vehicleId:'old-vehicle'});await running
  159. assert.equal(h.events.length,0);assert.equal(h.b.submitLoading.value,true)
  160. })
  161. test('old parse failure does not clear reopened loading or display error',async()=>{
  162. let reject:any;const errors:any[]=[]
  163. const h=harness({createVehicle:ref(true),PL_PASTE_SAMPLE_TEXT:'',parsePlCustomerVehicleText:()=>({vehicle:{licensePlate:'皖QA1023'},warnings:[]}),detectConflicts:()=>new Promise((_,no)=>reject=no),ElMessage:{warning:()=>{},error:(value:any)=>errors.push(value)}})
  164. const f=evaluate(file,['handleParse','handleClosed'],h.b);const old=f.handleParse();f.handleClosed();h.b.parseLoading.value=true;reject(Error('old'));await old
  165. assert.equal(h.b.parseLoading.value,true);assert.deepEqual(errors,[])
  166. })
  167. test('old carrier search cannot replace new session options or clear its loading',async()=>{
  168. let release:any;const h=harness({createVehicle:ref(true),PL_PASTE_SAMPLE_TEXT:'',mobileCarrierRequest:0,carrierApi:{list:()=>new Promise(r=>release=r)},pickBxList:(value:any)=>value})
  169. const f=evaluate(file,['searchMobileCarriers','handleClosed'],h.b);const old=f.searchMobileCarriers('');f.handleClosed();h.b.mobileCarriers.value=[{id:'new'}];h.b.mobileCarrierLoading.value=true;release([{id:'old'}]);await old
  170. assert.equal(h.b.mobileCarriers.value[0].id,'new');assert.equal(h.b.mobileCarrierLoading.value,true)
  171. })