78 lines
6.8 KiB
TypeScript
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()
|
|
})
|