Files
admin-web/src/modules/overseas-master-data/components/CapacityOfferSettingsDialog.test.ts
T

78 lines
6.8 KiB
TypeScript

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<typeof shallowMount>[] = []
function mount(realPlanner = false) {
const wrapper = shallowMount(Dialog, { props: { modelValue: true, trip }, global: { plugins: [createPinia()], stubs: {
AppDialog: { name: 'AppDialog', props: ['confirmText'], emits: ['confirm'], template: '<div><slot/><button @click="$emit(\'confirm\')">{{confirmText}}</button></div>' },
TripLoadingPanel: realPlanner ? false : defineComponent({ name: 'TripLoadingPanel', setup(_, { expose }) { expose({ save: state.plan }); return {} }, template: '<div/>' }),
DataTable: { name: 'DataTable', props: ['data', 'columns'], template: '<div><div v-for="row in data" :key="row.warehousePalletId"><slot name="palletNo" :row="row"/><slot name="weight" :row="row"/><slot name="volume" :row="row"/></div></div>' },
ElInputNumber: { name: 'ElInputNumber', props: ['modelValue'], emits: ['update:modelValue'], template: '<input/>' },
ElForm: { template: '<div><slot/></div>' }, ElFormItem: { template: '<div><slot/></div>' },
ElButton: { template: '<button><slot/></button>' }, ElDatePicker: true, ElInput: true, LoadErrorState: true, ApiErrorMeta: true,
} } }); wrappers.push(wrapper); return wrapper
}
beforeEach(() => {
vi.clearAllMocks()
vi.mocked(trips.getTrip).mockResolvedValue({ trip } as Awaited<ReturnType<typeof trips.getTrip>>)
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<ReturnType<typeof offers.saveCapacityOffer>>)
vi.mocked(offers.commandCapacityOffer).mockResolvedValue({ offer: { ...draft, status: 'OPEN', recordVersion: 2 }, replayed: false } as Awaited<ReturnType<typeof offers.commandCapacityOffer>>)
})
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()
})