import { beforeEach, expect, it, vi } from 'vitest' import { shallowMount, flushPromises } from '@vue/test-utils' import TripPickupPanel from './TripPickupPanel.vue' import { commandPickupTask, commandPickupTrip, getPickupTasks, type PickupTask } from '../api/pickups' import { getTrip } from '../api/trips' import type { TripDetail } from '../tripTypes' vi.mock('vue-router', () => ({ onBeforeRouteLeave: vi.fn() })) vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: () => true }) })) vi.mock('element-plus', () => ({ ElMessage: { warning: vi.fn() }, ElMessageBox: { confirm: vi.fn().mockResolvedValue(true) } })) vi.mock('../api/trips', () => ({ getTrip: vi.fn() })) vi.mock('../api/pickups', () => ({ getPickupTasks: vi.fn(), commandPickupTask: vi.fn(), commandPickupTrip: vi.fn(), uploadPickupEvidence: vi.fn(), downloadPickupEvidence: vi.fn(), pickupStatusLabels: {} })) const row: PickupTask = { customerId: 'C1', transferNo: 'WT-1', status: 'PICKED', evidence: [], lastlegOrderNo: 'LL-1', warehouseId: 'W1', pickupAddress: 'TEST Pickup Ave', contactName: '验收联系人', contactPhone: '0000000000', packageCount: 3, tripNo: 'TR-1', vehiclePlate: 'TEST-V1', driverName: '验收司机', driverPhone: '0000000000', plannedDepartureAt: null, actualPackages: 3, pickedAt: null, arrivedAt: null, remark: null } const detail = (status: string) => ({ trip: { tripNo: 'TR-1', status, recordVersion: 2, vehicleId: 'V1', driverId: 'D1' } }) as TripDetail function mount(status: string) { return shallowMount(TripPickupPanel, { props: { detail: detail(status) }, global: { stubs: { DataTable: { name: 'DataTable', props: ['data'], template: '
' }, ElButton: { props: ['disabled'], template: '' }, LoadErrorState: { name: 'LoadErrorState', emits: ['retry'], template: '' }, } } }) } beforeEach(() => { vi.clearAllMocks(); vi.mocked(getPickupTasks).mockResolvedValue([row]) }) it('unlocks parent actions and submits a fresh departure command after failure', async () => { vi.mocked(commandPickupTrip).mockRejectedValueOnce(new Error('connection lost')).mockResolvedValueOnce(detail('IN_TRANSIT')) const wrapper = mount('PLANNED'); await flushPromises() await wrapper.findAll('button').find(button => button.text() === '确认取货发车')!.trigger('click'); await flushPromises() expect(wrapper.emitted('locked')?.at(-1)).toEqual([false]) const body = vi.mocked(commandPickupTrip).mock.calls[0]![2] await wrapper.findAll('button').find(button => button.text() === '确认取货发车')!.trigger('click'); await flushPromises() expect(vi.mocked(commandPickupTrip).mock.calls[1]![2]).toEqual({ ...body, idempotencyKey: expect.any(String) }); expect(vi.mocked(commandPickupTrip).mock.calls[1]![2].idempotencyKey).not.toBe(body.idempotencyKey) expect(wrapper.emitted('locked')?.at(-1)).toEqual([false]) wrapper.unmount() }) it('retries only reading after arrival committed but the trip refresh failed', async () => { vi.mocked(commandPickupTask).mockResolvedValue([{ ...row, status: 'ARRIVED' }]) vi.mocked(getTrip).mockRejectedValueOnce(new Error('refresh lost')).mockResolvedValueOnce(detail('IN_TRANSIT')) const wrapper = mount('IN_TRANSIT'); await flushPromises() await wrapper.findAll('button').find(button => button.text() === '确认送仓')!.trigger('click'); await flushPromises() expect(commandPickupTask).toHaveBeenCalledTimes(1) wrapper.findComponent({ name: 'LoadErrorState' }).vm.$emit('retry'); await flushPromises() expect(commandPickupTask).toHaveBeenCalledTimes(1) expect(getTrip).toHaveBeenCalledTimes(2) expect(wrapper.emitted('saved')).toHaveLength(1) wrapper.unmount() })