import { mount, flushPromises } from '@vue/test-utils' import ElementPlus from 'element-plus' import { afterEach, beforeEach, expect, it, vi } from 'vitest' import TransportExecutionPanel from './TransportExecutionPanel.vue' import DispatchEvidenceFiles from './DispatchEvidenceFiles.vue' import { assignTransport, getTransport, type TransportDetail } from '../api/transportTasks' import { getDispatchEvidence, getDispatchEvidenceContent } from '../api/dispatches' import { getDriver, getResourcePhoto, downloadResourcePhoto, downloadDriverLicense } from '@/modules/overseas-master-data' 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(), getDriver: vi.fn(), getResourcePhoto: vi.fn(), downloadResourcePhoto: vi.fn(), downloadDriverLicense: vi.fn(), pickupStatusLabels: { ASSIGNED: '待取货' } })) 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: '到仓取货' } })) vi.mock('../api/dispatches', () => ({ getDispatchEvidence: vi.fn(), getDispatchEvidenceContent: vi.fn(), uploadDispatchEvidence: vi.fn() })) const facts = (): TransportDetail => ({ dispatchNo: 'PC1', taskType: 'PICKUP', dispatchRecordVersion: 9, execution: { trip: { status: 'PLANNED', recordVersion: 4, vehicleId: '1', vehiclePlate: 'CA-1', driverId: '2', driverName: '司机甲' } }, pickups: [{ customerId: 'C1', transferNo: 'WT1', packageCount: 5, status: 'ASSIGNED', evidence: [] }], completionEvidence: [] }) as unknown as TransportDetail const photo = (fileId: string) => ({ fileId, fileName: `${fileId}.jpg`, contentType: 'image/jpeg', sizeBytes: 5 }) const blob = () => new Blob(['photo'], { type: 'image/jpeg' }) const mountPanel = () => mount(TransportExecutionPanel, { props: { dispatchNo: 'PC1', editResources: true }, global: { plugins: [ElementPlus] } }) const saveButton = (wrapper: ReturnType) => wrapper.findAll('button').find(button => button.text() === '保存车辆司机')! beforeEach(() => { vi.resetAllMocks() vi.stubGlobal('ResizeObserver', class { observe() {} unobserve() {} disconnect() {} }) vi.spyOn(URL, 'createObjectURL').mockReturnValue('blob:resource-photo') vi.spyOn(URL, 'revokeObjectURL').mockImplementation(() => {}) vi.mocked(getTransport).mockImplementation(async () => facts()) vi.mocked(getDriver).mockResolvedValue({ id: '2', licenseFileId: '33' } as Awaited>) vi.mocked(getResourcePhoto).mockImplementation(async kind => ({ recordVersion: '1', photo: photo(kind === 'VEHICLE' ? '91' : '92') })) vi.mocked(downloadResourcePhoto).mockImplementation(async () => blob()) vi.mocked(downloadDriverLicense).mockImplementation(async () => blob()) vi.mocked(getDispatchEvidence).mockImplementation(async (_no, _kind, id) => photo(id)) vi.mocked(getDispatchEvidenceContent).mockImplementation(async () => blob()) }) afterEach(() => { vi.restoreAllMocks(); vi.unstubAllGlobals() }) it('keeps automatically loaded photos available after refreshing the open form', async () => { const wrapper = mountPanel() try { await flushPromises() expect(saveButton(wrapper).attributes('disabled')).toBeUndefined() expect(getResourcePhoto).toHaveBeenCalledTimes(2) await wrapper.get('[aria-label="刷新运输任务"]').trigger('click') await flushPromises() expect(wrapper.findAll('.evidence-photo-preview')).toHaveLength(3) expect(saveButton(wrapper).attributes('disabled')).toBeUndefined() vi.mocked(assignTransport).mockResolvedValue(facts()) await saveButton(wrapper).trigger('click') await flushPromises() expect(assignTransport).toHaveBeenCalledWith('PC1', expect.objectContaining({ reviewPhotos: { vehicleFileIds: [], driverPhotoFileIds: [], licenseFileIds: [] } })) } finally { wrapper.unmount() } }) it('resubmits saved snapshots when reopening maintenance even if archive photos change', async () => { const saved = facts() saved.execution.trip.recordVersion = 5 saved.dispatchRecordVersion = 10 saved.resources = { vehicle: { vehicleId: '1', evidenceFileIds: ['101'] }, driver: { driverId: '2', evidence: { photoFileIds: ['102'], fileIds: ['33'], masterLicenseFileId: '33' } } } as TransportDetail['resources'] vi.mocked(assignTransport).mockResolvedValue(saved) const wrapper = mountPanel() try { await flushPromises() await saveButton(wrapper).trigger('click') await flushPromises() expect(assignTransport).toHaveBeenCalledTimes(1) vi.mocked(getResourcePhoto).mockResolvedValue({ recordVersion: '2', photo: photo('999') }) await wrapper.findAll('button').find(button => button.text() === '维护车辆信息')!.trigger('click') await flushPromises() const reopened = wrapper.findAllComponents(DispatchEvidenceFiles) expect(reopened[0]!.props('modelValue')).toEqual(['101']) expect(reopened[1]!.props('modelValue')).toEqual(['102']) expect(getResourcePhoto).toHaveBeenCalledTimes(2) expect(getDispatchEvidence).toHaveBeenCalledWith('PC1', 'VEHICLE', '101') expect(saveButton(wrapper).attributes('disabled')).toBeUndefined() await saveButton(wrapper).trigger('click') await flushPromises() expect(assignTransport).toHaveBeenLastCalledWith('PC1', expect.objectContaining({ recordVersion: 5, dispatchRecordVersion: 10, reviewPhotos: { vehicleFileIds: ['101'], driverPhotoFileIds: ['102'], licenseFileIds: [] } })) } finally { wrapper.unmount() } }) it.each([0, 1])('requires a new photo when resource selector %s changes to an archive without a photo', async index => { const wrapper = mountPanel() try { await flushPromises() expect(saveButton(wrapper).attributes('disabled')).toBeUndefined() vi.mocked(getResourcePhoto).mockResolvedValue({ recordVersion: '0', photo: null }) wrapper.findAllComponents({ name: 'ElSelect' })[index]!.vm.$emit('update:modelValue', '4') await flushPromises() expect(saveButton(wrapper).attributes('disabled')).toBeDefined() expect(wrapper.findAllComponents(DispatchEvidenceFiles)[index]!.props('modelValue')).toEqual([]) expect(getResourcePhoto).toHaveBeenLastCalledWith(index === 0 ? 'VEHICLE' : 'DRIVER', '4') await saveButton(wrapper).trigger('click') expect(assignTransport).not.toHaveBeenCalled() } finally { wrapper.unmount() } })