52 lines
4.1 KiB
TypeScript
52 lines
4.1 KiB
TypeScript
import { flushPromises, mount } from '@vue/test-utils'
|
|||
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||
|
|
import AppointmentFollowupDialog from './AppointmentFollowupDialog.vue'
|
||
|
|
import { getFollowups, saveFollowup, type PlanningSource } from './planningApi'
|
||
|
|
|
||
|
|
vi.mock('./planningApi', () => ({ getFollowups: vi.fn(), saveFollowup: vi.fn() }))
|
||
|
|
vi.mock('element-plus', () => ({ ElMessage: { warning: vi.fn(), success: vi.fn() } }))
|
||
|
|
const history = { history: { records: [], page: 1, size: 10, total: 0, pages: 0 }, versions: { DRAYAGE_CONTACT: 0, TERMINAL_APPOINTMENT: 0, WAREHOUSE_RECEIVING: 0, FBA_RECEIVING: 0 } }
|
||
|
|
const source: PlanningSource = { sourceKey: 'ORDER:1', customerId: 'A', canFollowup: true, blockReasons: [], orderId: '1', inboundCode: 'IN-1', bookingNo: null, containerNo: null, originWarehouseId: 'US-1', originWarehouseName: '美国仓', destinationKey: 'FBA:ONT8', destinationWarehouseCode: 'ONT8', packages: null, sourceVersion: null }
|
||
|
|
function render(canWrite = true) {
|
||
|
|
return mount(AppointmentFollowupDialog, { props: { source, coverage: { status: 'UNASSIGNED', appointments: [], missingFields: [], changeReviewRequired: false }, canWrite }, global: { stubs: {
|
||
|
|
AppDialog: { props: ['confirmDisabled'], template: '<div><slot/><button data-save :disabled="confirmDisabled" @click="$emit(\'confirm\')">保存</button></div>' },
|
||
|
|
ElForm: { template: '<form><slot/></form>' }, ElFormItem: { template: '<label><slot/></label>' },
|
||
|
|
ElInput: { props: ['modelValue', 'placeholder'], template: '<input :value="modelValue" :placeholder="placeholder" @input="$emit(\'update:modelValue\', $event.target.value)" />' },
|
||
|
|
ElSelect: true, ElOption: true, ElDatePicker: true,
|
||
|
|
DataTable: true, LoadErrorState: true, ApiErrorMeta: true,
|
||
|
|
} } })
|
||
|
|
}
|
||
|
|
describe('appointment followup recovery', () => {
|
||
|
|
beforeEach(() => {
|
||
|
|
vi.resetAllMocks(); vi.mocked(getFollowups).mockResolvedValue(history)
|
||
|
|
vi.mocked(saveFollowup).mockResolvedValue({ stage: 'DRAYAGE_CONTACT', version: 1, contactedParty: null, result: '已联系', nextAction: null, followupDate: null, operatorName: '运营', createdAt: '2026-10-09T12:00:00Z' })
|
||
|
|
})
|
||
|
|
it('saves contact facts without requiring appointment, inbound plan or driver fields', async () => {
|
||
|
|
const wrapper = render(); await flushPromises()
|
||
|
|
await wrapper.get('input[placeholder="记录对方反馈,不会自动确认预约"]').setValue('已联系,等待回复')
|
||
|
|
await wrapper.get('[data-save]').trigger('click'); await flushPromises()
|
||
|
|
expect(saveFollowup).toHaveBeenCalledWith('ORDER:1', 'A', expect.objectContaining({ stage: 'DRAYAGE_CONTACT', expectedVersion: 0, result: '已联系,等待回复', followupDate: null }))
|
||
|
|
const body = vi.mocked(saveFollowup).mock.calls[0]![2]
|
||
|
|
expect(body).not.toHaveProperty('appointmentNo'); expect(body).not.toHaveProperty('driverId'); expect(body).not.toHaveProperty('operatorId')
|
||
|
|
expect(getFollowups).toHaveBeenCalledTimes(2)
|
||
|
|
})
|
||
|
|
it('retries an unknown outcome with the exact same body and persisted command key', async () => {
|
||
|
|
vi.mocked(saveFollowup).mockRejectedValueOnce(new Error('network'))
|
||
|
|
const wrapper = render(); await flushPromises()
|
||
|
|
await wrapper.get('input[placeholder="记录对方反馈,不会自动确认预约"]').setValue('等待码头开放')
|
||
|
|
await wrapper.get('[data-save]').trigger('click'); await flushPromises()
|
||
|
|
const first = vi.mocked(saveFollowup).mock.calls[0]![2]
|
||
|
|
await wrapper.get('[data-save]').trigger('click'); await flushPromises()
|
||
|
|
expect(vi.mocked(saveFollowup).mock.calls[1]![2]).toEqual(first)
|
||
|
|
expect(first.idempotencyKey).toBeTruthy()
|
||
|
|
})
|
||
|
|
it('does not allow a write without write permission or a successfully loaded version', async () => {
|
||
|
|
const readonly = render(false); await flushPromises()
|
||
|
|
expect(readonly.get('[data-save]').attributes('disabled')).toBeDefined()
|
||
|
|
vi.mocked(getFollowups).mockRejectedValueOnce(new Error('unavailable'))
|
||
|
|
const failed = render(); await flushPromises()
|
||
|
|
expect(failed.get('[data-save]').attributes('disabled')).toBeDefined()
|
||
|
|
expect(saveFollowup).not.toHaveBeenCalled()
|
||
|
|
})
|
||
|
|
})
|