import { shallowMount, flushPromises } from '@vue/test-utils' import { defineComponent } from 'vue' import { createPinia } from 'pinia' import { afterEach, beforeEach, expect, it, vi } from 'vitest' import Dialog from './CapacityOfferSettingsDialog.vue' import * as offers from '../api/capacityOffers' import * as trips from '../api/trips' import type { FleetTrip } from '../tripTypes' const state = vi.hoisted(() => ({ plan: vi.fn(), push: vi.fn() })) vi.mock('vue-router', () => ({ useRouter: () => ({ push: state.push }) })) vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: () => true }) })) vi.mock('element-plus', () => ({ ElMessage: { success: vi.fn(), warning: vi.fn() }, ElMessageBox: { confirm: vi.fn() } })) vi.mock('../api/capacityOffers', () => ({ listCapacityOffers: vi.fn(), saveCapacityOffer: vi.fn(), commandCapacityOffer: vi.fn() })) vi.mock('../api/trips', () => ({ getTrip: vi.fn(), getTripLoading: vi.fn(), updateTripPlan: vi.fn(), getTripCapacityPlan: vi.fn(), confirmTripCapacityPlan: vi.fn(), confirmTripLoading: vi.fn() })) const trip = { tripNo: 'TR-1', recordVersion: 5, plannedDepartureAt: '2099-01-01T12:00:00Z', stops: [{ name: '起运仓' }, { name: 'ACY2', destinationKey: 'FBA:ACY2' }] } as FleetTrip const draft = { offerNo: 'FO-1', tripNo: 'TR-1', status: 'DRAFT', recordVersion: 1, tripRecordVersion: 6, actions: { canEdit: true } } const wrappers: ReturnType[] = [] function mount(realPlanner = false) { const wrapper = shallowMount(Dialog, { props: { modelValue: true, trip }, global: { plugins: [createPinia()], stubs: { AppDialog: { name: 'AppDialog', props: ['confirmText'], emits: ['confirm'], template: '
' }, TripLoadingPanel: realPlanner ? false : defineComponent({ name: 'TripLoadingPanel', setup(_, { expose }) { expose({ save: state.plan }); return {} }, template: '
' }), DataTable: { name: 'DataTable', props: ['data', 'columns'], template: '
' }, ElInputNumber: { name: 'ElInputNumber', props: ['modelValue'], emits: ['update:modelValue'], template: '' }, ElForm: { template: '
' }, ElFormItem: { template: '
' }, ElButton: { template: '' }, ElDatePicker: true, ElInput: true, LoadErrorState: true, ApiErrorMeta: true, } } }); wrappers.push(wrapper); return wrapper } beforeEach(() => { vi.clearAllMocks() vi.mocked(trips.getTrip).mockResolvedValue({ trip } as Awaited>) vi.mocked(offers.listCapacityOffers).mockResolvedValue({ records: [], total: 0, page: 1, size: 1, pages: 0 }) state.plan.mockResolvedValue({ tripNo: 'TR-1', recordVersion: 6 }) vi.mocked(offers.saveCapacityOffer).mockResolvedValue({ offer: draft, replayed: false } as Awaited>) vi.mocked(offers.commandCapacityOffer).mockResolvedValue({ offer: { ...draft, status: 'OPEN', recordVersion: 2 }, replayed: false } as Awaited>) }) afterEach(() => wrappers.splice(0).forEach(wrapper => wrapper.unmount())) it('confirms capacity, saves metadata and publishes from one click using returned versions', async () => { const wrapper = mount(); await flushPromises() expect(wrapper.findComponent({ name: 'ElDatePicker' }).attributes('modelvalue')).toBe(trip.plannedDepartureAt) wrapper.findComponent({ name: 'AppDialog' }).vm.$emit('confirm'); await flushPromises() expect(state.plan).toHaveBeenCalledOnce() expect(offers.saveCapacityOffer).toHaveBeenCalledWith(null, expect.objectContaining({ tripRecordVersion: 6, cutoffAt: trip.plannedDepartureAt })) expect(offers.commandCapacityOffer).toHaveBeenCalledWith('FO-1', 'publish', expect.objectContaining({ tripRecordVersion: 6, offerRecordVersion: 1 })) expect(wrapper.text()).toContain('查看需求池发布') }) it('does not publish if capacity validation is incomplete', async () => { state.plan.mockResolvedValue(undefined) const wrapper = mount(); await flushPromises() wrapper.findComponent({ name: 'AppDialog' }).vm.$emit('confirm'); await flushPromises() expect(offers.saveCapacityOffer).not.toHaveBeenCalled(); expect(offers.commandCapacityOffer).not.toHaveBeenCalled() }) it('retries only the uncertain publish command with the original key', async () => { vi.mocked(offers.commandCapacityOffer).mockRejectedValueOnce(new Error('network')) const wrapper = mount(); await flushPromises() wrapper.findComponent({ name: 'AppDialog' }).vm.$emit('confirm'); await flushPromises() const first = vi.mocked(offers.commandCapacityOffer).mock.calls[0]! expect(wrapper.findComponent({ name: 'AppDialog' }).props('confirmText')).toBe('重试原请求') wrapper.findComponent({ name: 'AppDialog' }).vm.$emit('confirm'); await flushPromises() expect(offers.commandCapacityOffer).toHaveBeenNthCalledWith(2, ...first) expect(state.plan).toHaveBeenCalledOnce(); expect(offers.saveCapacityOffer).toHaveBeenCalledOnce() }) it('submits measurements from the embedded panel before saving and publishing', async () => { const basis = { tripNo: 'TR-1', recordVersion: 5, cargo: [{ dispatchNo: 'DP-1', customerId: '1', dispatchItemId: '2', warehousePalletId: '3', palletNo: 'PLT-USLAX01-261010-G0RD3LYHGD8H', packages: 1, destinationKey: 'FBA:ACY2', destinationLabel: 'ACY2' }], manifestHash: 'hash', capacityPlan: null, planValid: false, blockReason: null, availableSegments: [] } vi.mocked(trips.getTripCapacityPlan).mockResolvedValue(basis) vi.mocked(trips.confirmTripCapacityPlan).mockResolvedValue({ ...basis, recordVersion: 6, planValid: true }) const wrapper = mount(true); await flushPromises() expect(wrapper.text()).toContain('PLT-USLAX01-261010-G0RD3LYHGD8H') const columns = wrapper.findComponent({ name: 'DataTable' }).props('columns') as { key: string; singleLine?: boolean; width?: number; minWidth?: number }[] expect(columns.filter(column => ['palletNo', 'spaces', 'weight', 'volume', 'stop'].includes(column.key)).every(column => column.singleLine === false)).toBe(true) expect(columns.reduce((sum, column) => sum + (column.width || column.minWidth || 0), 0)).toBeLessThanOrEqual(1048) const fields = wrapper.findAllComponents({ name: 'ElInputNumber' }) fields[0]!.vm.$emit('update:modelValue', 100); fields[1]!.vm.$emit('update:modelValue', 2); await flushPromises() wrapper.findComponent({ name: 'AppDialog' }).vm.$emit('confirm'); await flushPromises() expect(trips.confirmTripCapacityPlan).toHaveBeenCalledWith('TR-1', expect.objectContaining({ recordVersion: 5, pallets: [{ warehousePalletId: '3', palletSpaces: 1, weightKg: 100, volumeCbm: 2, deliveryIndex: 1 }] })) expect(offers.commandCapacityOffer).toHaveBeenCalledOnce() })