import { afterEach, beforeEach, expect, it, vi } from 'vitest' import { shallowMount, flushPromises, type VueWrapper } from '@vue/test-utils' import PickupDemandPanel from './PickupDemandPanel.vue' import { listPickupCandidates, createPickupTrip, type PickupCandidate } from '../api/pickups' import type { TripDetail } from '../tripTypes' const state = vi.hoisted(() => ({ push: vi.fn(), write: true })) vi.mock('vue-router', () => ({ useRouter: () => ({ push: state.push }), onBeforeRouteLeave: vi.fn() })) vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: () => state.write }) })) vi.mock('element-plus', () => ({ ElMessage: { warning: vi.fn() } })) vi.mock('../api/pickups', () => ({ listPickupCandidates: vi.fn(), createPickupTrip: vi.fn() })) const rows: PickupCandidate[] = [1, 2].map(i => ({ customerId: `C${i}`, transferNo: `WT-${i}`, lastlegOrderNo: `LL-${i}`, warehouseId: '9007199254740999', pickupAddress: `TEST Ontario ${i}`, contactName: '取货验收员', contactPhone: '000', packageCount: 5, expectedArrivalAt: null })) const wrappers: VueWrapper[] = [] function mount() { const wrapper = shallowMount(PickupDemandPanel, { global: { stubs: { DataTable: { name: 'DataTable', props: ['data', 'total'], emits: ['selection-change'], template: '
' }, AppDialog: { name: 'AppDialog', props: ['modelValue'], emits: ['confirm'], template: '
' }, ElButton: { props: ['disabled'], template: '' }, ElForm: { template: '
' }, ElFormItem: { template: '
' }, ElDatePicker: { name: 'ElDatePicker', props: ['modelValue'], emits: ['update:modelValue'], template: '' }, } } }); wrappers.push(wrapper); return wrapper } beforeEach(() => { vi.clearAllMocks(); state.write = true; vi.mocked(listPickupCandidates).mockResolvedValue({ records: rows, total: 42 }) }) afterEach(() => wrappers.splice(0).forEach(wrapper => wrapper.unmount())) it('uses server total and rejects combining different destination warehouses', async () => { const wrapper = mount(); await flushPromises() expect(wrapper.findComponent({ name: 'DataTable' }).props('total')).toBe(42) wrapper.findComponent({ name: 'DataTable' }).vm.$emit('selection-change', [rows[0], { ...rows[1], warehouseId: 'OTHER' }]); await flushPromises() await wrapper.find('button').trigger('click') expect(wrapper.findComponent({ name: 'AppDialog' }).props('modelValue')).toBe(false) expect(createPickupTrip).not.toHaveBeenCalled() }) it('keeps exact source identities and the same create request after an unknown network result', async () => { const wrapper = mount(); await flushPromises() wrapper.findComponent({ name: 'DataTable' }).vm.$emit('selection-change', rows); await flushPromises(); await wrapper.find('button').trigger('click') const dates = wrapper.findAllComponents({ name: 'ElDatePicker' }) dates[0]!.vm.$emit('update:modelValue', '2026-10-17T08:00:00Z'); dates[1]!.vm.$emit('update:modelValue', '2026-10-18T08:00:00Z'); await flushPromises() vi.mocked(createPickupTrip).mockRejectedValueOnce(new Error('connection lost')).mockResolvedValueOnce({ trip: { tripNo: 'TR-TEST' } } as TripDetail) const dialog = wrapper.findComponent({ name: 'AppDialog' }); dialog.vm.$emit('confirm'); await flushPromises() const request = vi.mocked(createPickupTrip).mock.calls[0]![0] expect(request.sources).toEqual(rows.map(({ customerId, transferNo }) => ({ customerId, transferNo }))) dialog.vm.$emit('confirm'); await flushPromises() expect(vi.mocked(createPickupTrip).mock.calls[1]![0]).toBe(request) expect(state.push).toHaveBeenCalledWith({ name: 'overseas-master-data-trip-detail', params: { tripNo: 'TR-TEST' } }) }) it('does not show scheduling to read-only users', async () => { state.write = false; const wrapper = mount(); await flushPromises() expect(wrapper.find('button').exists()).toBe(false) })