2026-10-21 16:12:24 +08:00
import { mount , flushPromises } from '@vue/test-utils'
import ElementPlus from 'element-plus'
import { beforeEach , expect , it , vi } from 'vitest'
import TransportExecutionPanel from './TransportExecutionPanel.vue'
import { commandTransport , getTransport , uploadTransport , type TransportDetail } from '../api/transportTasks'
vi . mock ( 'vue-router' , () => ({ onBeforeRouteLeave : vi.fn () }))
vi . mock ( '@/core/access/accessPreview' , () => ({ useAccessPreviewStore : () => ({ can : () => true }) }))
vi . mock ( '@/modules/overseas-master-data' , () => ({ listVehicles : vi.fn (), listDrivers : vi.fn (), pickupStatusLabels : { ASSIGNED : '待取货' , PICKED : '已取货' , ARRIVED : '已到仓' } }))
vi . mock ( '../api/transportTasks' , () => ({ getTransport : vi.fn (), assignTransport : vi.fn (), transportOccupancy : vi.fn (), cancelTransport : vi.fn (), releaseTransport : vi.fn (), commandTransport : vi.fn (), uploadTransport : vi.fn (), downloadTransportEvidence : vi.fn (), transportLabels : { PICKUP : '到仓取货' , LOCAL_TRUCK : '本土卡车' } }))
const facts = ( arriving = false ) : TransportDetail => ({ dispatchNo : 'PC1' , taskType : 'PICKUP' , execution : { trip : { status : arriving ? 'IN_TRANSIT' : 'PLANNED' , recordVersion : 4 , warehouseName : '洛杉矶仓' , vehicleId : '1' , vehiclePlate : 'CA-1' , driverId : '2' , driverName : '司机甲' , driverPhone : '555' } }, pickups : [{ customerId : 'C1' , transferNo : 'WT1' , packageCount : 5 , actualPackages : arriving ? 5 : null , status : arriving ? 'PICKED' : 'ASSIGNED' , evidence : [] }], completionEvidence : [] }) as unknown as TransportDetail
const options = { global : { plugins : [ ElementPlus ] } }
beforeEach (() => {
vi . clearAllMocks (); vi . stubGlobal ( 'ResizeObserver' , class { observe() { } unobserve() { } disconnect() { } })
vi . mocked ( getTransport ). mockResolvedValue ( facts ())
vi . mocked ( uploadTransport ). mockResolvedValue ({ fileId : '88' , fileName : '现场.jpg' , contentType : 'image/jpeg' , sizeBytes : 10 })
})
it . each ([ false , true ])( 'confirms the %s photo stage with its own evidence and current transport version' , async arriving => {
vi . mocked ( getTransport ). mockResolvedValue ( facts ( arriving ))
const wrapper = mount ( TransportExecutionPanel , { ... options , props : { dispatchNo : 'PC1' , pickupApproved : true } }); await flushPromises ()
expect ( wrapper . text ()). toContain ( arriving ? '派送中' : '装货中' )
expect ( wrapper . findAll ( 'button' ). some ( button => button . text () === '确认发车' )). toBe ( false )
2026-10-21 17:28:04 +08:00
await wrapper . findAll ( 'button' ). find ( button => button . text () === ( arriving ? '上传到仓照片' : '上传装货照片' )) ! . trigger ( 'click' ); await flushPromises ()
if ( ! arriving ) {
const quantity = wrapper . get ( 'input[role="spinbutton"]' )
expect (( quantity . element as HTMLInputElement ). value ). toBe ( '' )
await quantity . setValue ( '3' ); await quantity . trigger ( 'change' ); await flushPromises ()
}
2026-10-21 16:12:24 +08:00
const input = wrapper . get ( 'input[type="file"]' )
Object . defineProperty ( input . element , 'files' , { value : [ new File ([ 'photo' ], '现场.jpg' , { type : 'image/jpeg' })], configurable : true })
await input . trigger ( 'change' ); await flushPromises ()
await wrapper . findAll ( 'button' ). find ( button => button . text () === ( arriving ? '确认到仓' : '确认取货' )) ! . trigger ( 'click' ); await flushPromises ()
2026-10-21 17:28:04 +08:00
expect ( commandTransport ). toHaveBeenCalledWith ( 'PC1' , `pickup/C1/WT1/ ${ arriving ? 'arrive' : 'picked' } ` , expect . objectContaining ({ recordVersion : 4 , evidenceFileIds : [ '88' ], ...( arriving ? { reason : '上传到仓照片,确认取货派送完成' } : { actualPackages : 3 }) }))
if ( arriving ) expect ( commandTransport . mock . calls [ 0 ] ! [ 2 ]). not . toHaveProperty ( 'actualPackages' )
2026-10-21 16:12:24 +08:00
expect ( wrapper . emitted ( 'saved' )). toHaveLength ( 1 ); wrapper . unmount ()
})
it ( 'keeps photo confirmation unavailable until both reviews are approved' , async () => {
const wrapper = mount ( TransportExecutionPanel , { ... options , props : { dispatchNo : 'PC1' , pickupApproved : false } }); await flushPromises ()
2026-10-21 17:28:04 +08:00
expect ( wrapper . findAll ( 'button' ). some ( button => button . text () === '上传装货照片' )). toBe ( false )
2026-10-21 16:12:24 +08:00
expect ( commandTransport ). not . toHaveBeenCalled (); wrapper . unmount ()
})
2026-10-21 17:28:04 +08:00
it ( 'requires an explicitly entered pickup quantity even after a photo has uploaded' , async () => {
const wrapper = mount ( TransportExecutionPanel , { ... options , props : { dispatchNo : 'PC1' , pickupApproved : true } }); await flushPromises ()
await wrapper . findAll ( 'button' ). find ( button => button . text () === '上传装货照片' ) ! . trigger ( 'click' ); await flushPromises ()
const input = wrapper . get ( 'input[type="file"]' )
Object . defineProperty ( input . element , 'files' , { value : [ new File ([ 'photo' ], '现场.jpg' , { type : 'image/jpeg' })], configurable : true })
await input . trigger ( 'change' ); await flushPromises ()
const confirm = wrapper . findAll ( 'button' ). find ( button => button . text () === '确认取货' ) !
expect ( confirm . attributes ( 'disabled' )). toBeDefined ()
await confirm . trigger ( 'click' )
expect ( commandTransport ). not . toHaveBeenCalled ()
wrapper . unmount ()
})
2026-10-21 16:12:24 +08:00
it . each ([ false , true ])( 'renders read-only transport facts without resource editing or execution controls (%s)' , async arriving => {
const value = facts ( arriving )
value . execution . trip . vehicleId = null
value . execution . trip . driverId = null
vi . mocked ( getTransport ). mockResolvedValue ( value )
const wrapper = mount ( TransportExecutionPanel , { ... options , props : { dispatchNo : 'PC1' , pickupApproved : true , readonly : true } }); await flushPromises ()
expect ( wrapper . text ()). toContain ( 'WT1' )
expect ( wrapper . text ()). toContain ( '洛杉矶仓' )
expect ( wrapper . findAll ( 'input, select, textarea' )). toHaveLength ( 0 )
expect ( wrapper . findAll ( 'button' )). toHaveLength ( 0 )
expect ( wrapper . findAll ( 'th' ). map ( column => column . text ())). not . toContain ( '操作' )
expect ( commandTransport ). not . toHaveBeenCalled ()
wrapper . unmount ()
})
2026-10-21 17:28:04 +08:00
it ( 'keeps pickup addresses and contacts on their own transfer rows' , async () => {
const value = facts ()
value . pickups [ 0 ] = { ... value . pickups [ 0 ] ! , pickupAddress : '100 Test Pickup Avenue' , contactName : '取货联系人甲' , contactPhone : '0000000001' }
value . pickups . push ({ ... value . pickups [ 0 ] ! , transferNo : 'WT2' , customerId : 'C2' , pickupAddress : '200 Test Pickup Road' , contactName : '取货联系人乙' , contactPhone : '0000000002' })
vi . mocked ( getTransport ). mockResolvedValue ( value )
const wrapper = mount ( TransportExecutionPanel , { ... options , props : { dispatchNo : 'PC1' , readonly : true } }); await flushPromises ()
const rows = wrapper . get ( '[aria-label="取货明细"]' ). findAll ( 'tbody tr' )
expect ( rows [ 0 ] ! . text ()). toContain ( '100 Test Pickup Avenue' )
expect ( rows [ 0 ] ! . text ()). toContain ( '取货联系人甲' )
expect ( rows [ 0 ] ! . text ()). toContain ( '0000000001' )
expect ( rows [ 0 ] ! . text ()). not . toContain ( '200 Test Pickup Road' )
expect ( rows [ 1 ] ! . text ()). toContain ( '200 Test Pickup Road' )
expect ( rows [ 1 ] ! . text ()). toContain ( '取货联系人乙' )
expect ( rows [ 1 ] ! . text ()). toContain ( '0000000002' )
expect ( wrapper . findAll ( 'button' )). toHaveLength ( 0 )
wrapper . unmount ()
})