import { afterEach, beforeEach, expect, it, vi } from 'vitest' import { flushPromises, shallowMount } from '@vue/test-utils' import TripAssignmentPanel from './TripAssignmentPanel.vue' import { listVehicles } from '../api/overseasMasterData' import { listDrivers } from '../api/drivers' import { assignTrip, getTripResourceOccupancy } from '../api/trips' import type { TripDetail } from '../tripTypes' import type { VehicleDetail } from '../types' import type { DriverDetail } from '../driverTypes' vi.mock('element-plus', () => ({ ElMessage: { warning: vi.fn(), success: vi.fn() } })) vi.mock('../api/overseasMasterData', () => ({ listVehicles: vi.fn() })) vi.mock('../api/drivers', () => ({ listDrivers: vi.fn() })) vi.mock('../api/trips', () => ({ assignTrip: vi.fn(), getTripResourceOccupancy: vi.fn() })) const vehicle: VehicleDetail = { id: '9007199254740999', plateNumber: 'TEST-FREE', model: '26ft', maxPalletSpaces: 12, maxLoadKg: 8000, maxVolumeCbm: 45, fuelConsumptionPer100Km: 20, mileageKm: 0, driverOrDispatchCompany: null, phone: null, region: 'OVERSEAS', ownership: 'OWNED', status: 'IN_SERVICE', remark: null, recordVersion: 0, updatedBy: '1', updatedAt: '' } const driver: DriverDetail = { id: '9007199254740998', driverNo: 'DR-TEST', details: { name: '验收司机', phone: '000', ownership: 'OWNED', region: 'OVERSEAS', licenseNumber: null, licenseType: null, licenseIssuedOn: null, licenseExpiresOn: '2030-12-31', licenseZone: 'UTC', remark: null, profile: null }, active: true, reviewStatus: 'APPROVED', licenseFileId: '1', recordVersion: 0, updatedBy: '1', updatedAt: '', qualification: { canDispatch: true, expiringSoon: false, reason: 'ELIGIBLE', licenseExpiresOn: '2030-12-31' }, licenseState: 'VALID', warehouseCode: null, warehouseName: null } const trip: TripDetail = { trip: { tripNo: 'TR-CURRENT', status: 'PLANNED', warehouseId: '1', warehouseCode: 'WH', warehouseName: '仓库', vehicleId: null, vehiclePlate: null, driverId: null, driverName: null, driverPhone: null, maxPalletSpaces: null, maxLoadKg: null, maxVolumeCbm: null, plannedDepartureAt: '2026-10-18T01:00:00Z', expectedDriverEndAt: '2026-10-18T09:00:00Z', departedAt: null, completedAt: null, stops: [], remark: null, recordVersion: 1, updatedAt: '', vehicleRelease: null, driverRelease: null }, driverQualification: null, dispatches: [], loadingReview: { status: 'PENDING', reason: null, reviewerId: null, reviewedAt: null, recordVersion: null }, previousDispatches: [], appointmentHistoryUnavailableReason: null } function mount() { return shallowMount(TripAssignmentPanel, { props: { detail: trip }, global: { stubs: { ElForm: { template: '
' }, ElFormItem: { template: '
' }, ElSelect: { name: 'ElSelect', props: ['modelValue'], emits: ['focus', 'update:modelValue'], template: '
' }, ElOption: { name: 'ElOption', props: ['value', 'label', 'disabled'], template: '{{ label }}' }, ElButton: { template: '' }, } } }) } beforeEach(() => { vi.useFakeTimers(); vi.clearAllMocks() vi.mocked(listVehicles).mockResolvedValue({ records: [vehicle], page: 1, size: 30, total: 1, pages: 1 }) vi.mocked(listDrivers).mockResolvedValue({ records: [driver], page: 1, size: 30, total: 1, pages: 1, summary: { total: 1, owned: 1, dispatched: 0, expiringSoon: 0, expired: 0 } }) }) afterEach(() => vi.useRealTimers()) it('disables resources held by another trip and shows the occupying trip before saving', async () => { vi.mocked(getTripResourceOccupancy).mockImplementation(async (_, kind, ids) => ids.map(resourceId => ({ resourceId, occupiedTripNo: kind === 'VEHICLE' ? 'TR-OTHER' : 'TR-CURRENT', available: kind === 'DRIVER' }))) const wrapper = mount() const selects = wrapper.findAllComponents({ name: 'ElSelect' }) selects.forEach(select => select.vm.$emit('focus')); await vi.advanceTimersByTimeAsync(250); await flushPromises() const options = wrapper.findAllComponents({ name: 'ElOption' }) expect(options[0]!.props()).toMatchObject({ value: vehicle.id, disabled: true }) expect(options[0]!.props('label')).toContain('被车次 TR-OTHER 占用') expect(options[1]!.props('disabled')).toBe(false) expect(options[1]!.props('label')).toContain('当前车次') expect(getTripResourceOccupancy).toHaveBeenCalledTimes(2) selects[0]!.vm.$emit('update:modelValue', vehicle.id); selects[1]!.vm.$emit('update:modelValue', driver.id); await flushPromises() await wrapper.find('button').trigger('click'); expect(assignTrip).not.toHaveBeenCalled() wrapper.unmount() }) it('does not expose directory candidates as free when occupancy lookup fails', async () => { vi.mocked(getTripResourceOccupancy).mockRejectedValue(new Error('查询占用失败')) const wrapper = mount(); wrapper.findComponent({ name: 'ElSelect' }).vm.$emit('focus') await vi.advanceTimersByTimeAsync(250); await flushPromises() expect(wrapper.findAllComponents({ name: 'ElOption' })).toHaveLength(0) wrapper.unmount() })