2026-10-19 17:56:31 +08:00
import ElementPlus from 'element-plus'
import { flushPromises , mount } from '@vue/test-utils'
import { beforeEach , expect , it , vi } from 'vitest'
import DispatchResourceFields from './DispatchResourceFields.vue'
import { resources } from '../dispatchTestFixtures'
2026-10-23 00:55:21 +08:00
import { listDrivers , listVehicles , type DriverDetail , type VehicleDetail } from '@/modules/overseas-master-data'
2026-10-19 17:56:31 +08:00
const can = vi . hoisted (() => vi . fn (() => true ))
vi . mock ( '@/core/access/accessPreview' , () => ({ useAccessPreviewStore : () => ({ can }) }))
vi . mock ( '@/modules/overseas-master-data' , () => ({ listDrivers : vi.fn (), listVehicles : vi.fn (), overseasMasterDataPermissions : { vehicleList : 'vehicle:list' , driverList : 'driver:list' } }))
2026-10-22 23:10:09 +08:00
beforeEach (() => { vi . clearAllMocks (); can . mockReturnValue ( true ); vi . mocked ( listVehicles ). mockResolvedValue ({ records : [], page : 1 , size : 100 , total : 0 , pages : 0 }) })
2026-10-23 00:55:21 +08:00
it ( 'carries the selected master license for preview without carrying another driver’ s trip photos' , async () => {
const candidate : DriverDetail = { id : '22' , driverNo : 'DR22' , details : { name : '司机乙' , phone : '555-0022' , ownership : 'OWNED' , region : 'OVERSEAS' , licenseNumber : 'LICENSE22' , licenseType : null , licenseIssuedOn : null , licenseExpiresOn : '2028-01-01' , licenseZone : 'UTC' , remark : null , profile : null }, active : true , reviewStatus : 'APPROVED' , licenseFileId : '33' , recordVersion : 1 , updatedBy : 'operator' , updatedAt : '2026-10-23T00:00:00Z' , qualification : { canDispatch : true , expiringSoon : false , reason : 'ELIGIBLE' , licenseExpiresOn : '2028-01-01' }, licenseState : 'VALID' , warehouseCode : null , warehouseName : null }
vi . mocked ( listDrivers ). mockResolvedValue ({ records : [ candidate ], page : 1 , size : 100 , total : 1 , pages : 1 , summary : { total : 1 , owned : 1 , dispatched : 0 , expiringSoon : 0 , expired : 0 } })
const value = resources ()
2026-10-29 13:37:22 +08:00
const wrapper = mount ( DispatchResourceFields , { props : { ... value , disabled : false , dispatchNo : 'PC-OWN' }, global : { plugins : [ ElementPlus ] } })
2026-10-23 00:55:21 +08:00
const state = wrapper . vm as unknown as { searchDrivers () : Promise < void >; selectDriver ( id : string ) : void }
2026-10-29 13:37:22 +08:00
await state . searchDrivers (); expect ( listDrivers ). toHaveBeenCalledWith ( expect . objectContaining ({ dispatchNo : 'PC-OWN' })); state . selectDriver ( '22' ); await flushPromises ()
2026-10-23 00:55:21 +08:00
expect ( wrapper . emitted ( 'update:driver' ) ! . at ( - 1 ) ! [ 0 ]). toMatchObject ({ driverId : '22' , evidence : { fileIds : [], masterLicenseFileId : '33' } })
wrapper . unmount ()
})
2026-10-21 23:20:10 +08:00
it . each ([[ '本趟承运公司' , '555-0123' ], [ null , null ]])( 'shows continuous master snapshot fields read-only and uses a dash for missing facts (%s)' , async ( carrierName , phone ) => {
const value = resources (); value . vehicle . mode = 'MASTER' ; value . vehicle . vehicleId = 'vehicle-1' ; value . vehicle . carrierName = carrierName ; value . driver . mode = 'MASTER' ; value . driver . driverId = 'driver-1' ; value . driver . phone = phone
const wrapper = mount ( DispatchResourceFields , { props : { vehicle : value.vehicle , driver : value.driver , disabled : false , continuous : true }, global : { plugins : [ ElementPlus ] } }); await flushPromises ()
const field = ( label : string ) => wrapper . findAllComponents ({ name : 'ElFormItem' }). find ( item => item . props ( 'label' ) === label ) ! . getComponent ({ name : 'ElInput' })
expect ( field ( '拖车公司名称' ). props ( 'modelValue' )). toBe ( carrierName || '—' ); expect ( field ( '拖车公司名称' ). props ( 'readonly' )). toBe ( true )
expect ( field ( '联系电话' ). props ( 'modelValue' )). toBe ( phone || '—' ); expect ( field ( '联系电话' ). props ( 'readonly' )). toBe ( true )
2026-10-22 23:10:09 +08:00
expect ( listVehicles ). toHaveBeenCalled (); expect ( listDrivers ). not . toHaveBeenCalled ()
2026-10-21 23:20:10 +08:00
expect ( wrapper . emitted ( 'update:vehicle' )). toBeUndefined (); expect ( wrapper . emitted ( 'update:driver' )). toBeUndefined (); wrapper . unmount ()
})
it ( 'keeps master company and phone fields out of the existing non-continuous creation form' , async () => {
const value = resources (); value . vehicle . mode = 'MASTER' ; value . driver . mode = 'MASTER'
const wrapper = mount ( DispatchResourceFields , { props : { vehicle : value.vehicle , driver : value.driver , disabled : false }, global : { plugins : [ ElementPlus ] } }); await flushPromises ()
const labels = wrapper . findAllComponents ({ name : 'ElFormItem' }). map ( item => item . props ( 'label' ))
expect ( labels ). not . toContain ( '拖车公司名称' ); expect ( labels ). not . toContain ( '联系电话' ); wrapper . unmount ()
})
it ( 'shows the newly selected authorized vehicle company without copying it into the master command' , async () => {
const candidate : VehicleDetail = { id : 'vehicle-2' , plateNumber : 'CA-200' , model : '厢式货车' , driverOrDispatchCompany : '新承运公司' , phone : null , maxPalletSpaces : 20 , maxLoadKg : 18000 , maxVolumeCbm : 60 , fuelConsumptionPer100Km : 0 , mileageKm : 0 , region : 'OVERSEAS' , ownership : 'OWNED' , status : 'IN_SERVICE' , remark : null , recordVersion : 1 , updatedBy : 'operator' , updatedAt : '2026-10-21T10:00:00Z' }
2026-10-22 23:10:09 +08:00
vi . mocked ( listVehicles ). mockResolvedValue ({ records : [ candidate ], page : 1 , size : 100 , total : 1 , pages : 1 })
2026-10-21 23:20:10 +08:00
const value = resources (); value . vehicle . mode = 'MASTER' ; value . vehicle . vehicleId = 'vehicle-1'
const wrapper = mount ( DispatchResourceFields , { props : { vehicle : value.vehicle , driver : value.driver , disabled : false , continuous : true }, global : { plugins : [ ElementPlus ] } })
const state = wrapper . vm as unknown as { searchVehicles () : Promise < void >; selectVehicle ( id : string ) : void }
await state . searchVehicles (); state . selectVehicle ( 'vehicle-2' )
const selected = wrapper . emitted ( 'update:vehicle' ) ! . at ( - 1 ) ! [ 0 ] as typeof value . vehicle
expect ( selected ). toMatchObject ({ vehicleId : 'vehicle-2' , carrierName : null })
await wrapper . setProps ({ vehicle : selected })
const company = wrapper . findAllComponents ({ name : 'ElFormItem' }). find ( item => item . props ( 'label' ) === '拖车公司名称' ) ! . getComponent ({ name : 'ElInput' })
expect ( company . props ( 'modelValue' )). toBe ( '新承运公司' ); expect ( company . props ( 'readonly' )). toBe ( true ); wrapper . unmount ()
})
2026-10-20 10:11:31 +08:00
it . each ([ false , true ])( 'renders known business labels without internal IDs (continuous=%s)' , async continuous => {
2026-10-19 17:56:31 +08:00
const value = resources (); value . vehicle . mode = 'MASTER' ; value . vehicle . vehicleId = '900719925474099301' ; value . driver . mode = 'MASTER' ; value . driver . driverId = '900719925474099302'
2026-10-20 10:11:31 +08:00
const wrapper = mount ( DispatchResourceFields , { props : { vehicle : value.vehicle , driver : value.driver , disabled : false , continuous }, global : { plugins : [ ElementPlus ] } }); await flushPromises ()
2026-10-19 17:56:31 +08:00
expect ( wrapper . text ()). toContain ( 'CA-100' ); expect ( wrapper . text ()). toContain ( '司机甲' ); expect ( wrapper . text ()). not . toContain ( '90071992547409930' ); wrapper . unmount ()
})
2026-10-20 10:11:31 +08:00
it . each ([ false , true ])( 'resets only the switched resource and keeps choices independent (continuous=%s)' , async continuous => {
2026-10-19 17:56:31 +08:00
const value = resources (); value . vehicle . mode = 'MASTER' ; value . vehicle . vehicleId = 'vehicle-1'
2026-10-20 10:11:31 +08:00
const wrapper = mount ( DispatchResourceFields , { props : { vehicle : value.vehicle , driver : value.driver , disabled : false , continuous }, global : { plugins : [ ElementPlus ] } })
2026-10-19 17:56:31 +08:00
const state = wrapper . vm as unknown as { changeDriver () : void }; state . changeDriver (); await flushPromises ()
expect ( wrapper . emitted ( 'update:driver' ) ! [ 0 ] ! [ 0 ]). toMatchObject ({ mode : 'EXTERNAL' , driverId : null , name : null , evidence : { fileIds : [], licenseNo : null } })
expect ( wrapper . emitted ( 'update:vehicle' )). toBeUndefined (); expect ( value . vehicle . vehicleId ). toBe ( 'vehicle-1' ); wrapper . unmount ()
})
2026-10-20 10:11:31 +08:00
it . each ([ false , true ])( 'requires each directory permission before reading candidates (continuous=%s)' , async continuous => {
2026-10-19 17:56:31 +08:00
can . mockReturnValue ( false ); const value = resources ()
2026-10-20 10:11:31 +08:00
const wrapper = mount ( DispatchResourceFields , { props : { vehicle : value.vehicle , driver : value.driver , disabled : false , continuous }, global : { plugins : [ ElementPlus ] } })
2026-10-19 17:56:31 +08:00
const state = wrapper . vm as unknown as { searchVehicles () : Promise < void >; searchDrivers () : Promise < void > }; await state . searchVehicles (); await state . searchDrivers ()
expect ( listVehicles ). not . toHaveBeenCalled (); expect ( listDrivers ). not . toHaveBeenCalled (); expect ( wrapper . text ()). toContain ( '缺少司机档案读取权限' ); wrapper . unmount ()
})
2026-10-22 23:10:09 +08:00
it ( 'filters real unavailable statuses, sends dispatch context, and lets an invalid selection be cleared' , async () => {
const base : VehicleDetail = { id : 'good' , plateNumber : 'GOOD' , model : '厢式货车' , driverOrDispatchCompany : null , phone : null , maxPalletSpaces : 20 , maxLoadKg : 18000 , maxVolumeCbm : 60 , fuelConsumptionPer100Km : 0 , mileageKm : 0 , region : 'OVERSEAS' , ownership : 'OWNED' , status : 'IN_SERVICE' , remark : null , recordVersion : 1 , updatedBy : 'operator' , updatedAt : '2026-10-21T10:00:00Z' }
vi . mocked ( listVehicles ). mockResolvedValue ({ records : [ base , { ... base , id : 'bad' , plateNumber : 'BAD' , status : 'MAINTENANCE' }], page : 1 , size : 100 , total : 2 , pages : 1 })
const value = resources (); value . vehicle . mode = 'MASTER' ; value . vehicle . vehicleId = 'bad'
const wrapper = mount ( DispatchResourceFields , { props : { vehicle : value.vehicle , driver : value.driver , disabled : false , continuous : true , dispatchNo : 'PC-REAL' }, global : { plugins : [ ElementPlus ] } }); await flushPromises ()
expect ( wrapper . text ()). toContain ( '当前车辆已不可用' )
const state = wrapper . vm as unknown as { searchVehicles () : Promise < void >; selectVehicle ( id : string | null ) : void }
await state . searchVehicles (); await flushPromises ()
expect ( listVehicles ). toHaveBeenLastCalledWith ( expect . objectContaining ({ status : 'IN_SERVICE' , region : 'OVERSEAS' , dispatchNo : 'PC-REAL' }))
const options = wrapper . findAllComponents ({ name : 'ElOption' })
expect ( options . find ( item => item . props ( 'value' ) === 'good' ) ? . props ( 'disabled' )). toBe ( false )
2026-10-29 13:37:22 +08:00
expect ( options . find ( item => item . props ( 'value' ) === 'bad' )). toBeUndefined ()
2026-10-22 23:10:09 +08:00
state . selectVehicle ( 'bad' ); expect ( wrapper . emitted ( 'update:vehicle' )). toBeUndefined ()
state . selectVehicle ( null ); expect ( wrapper . emitted ( 'update:vehicle' ) ! . at ( - 1 ) ! [ 0 ]). toMatchObject ({ vehicleId : null })
wrapper . unmount ()
})