import { flushPromises, mount } from '@vue/test-utils' import { beforeEach, expect, it, vi } from 'vitest' import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router' import InboundOrdersView from './InboundOrdersView.vue' import { getFbaAppointment, updateFbaAppointment, listInboundOrders } from '../api/inboundOrders' const routing = vi.hoisted(() => ({ push: vi.fn() })) vi.mock('vue-router', () => ({ useRouter: () => routing, onBeforeRouteLeave: vi.fn(), onBeforeRouteUpdate: vi.fn() })) const access=vi.hoisted(()=>({canWrite:true})) vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({can: (permission:string) => !permission.endsWith(':fba-appointment') || access.canWrite}) })) vi.mock('../api/inboundOrders', () => ({ listInboundOrders: vi.fn().mockResolvedValue({records:[{lineId:'1',planNo:'IP-1',customerId:'A',appointmentNo:'AP-1',fbaWarehouseCode:'ONT8'}],total:1}), getFbaAppointment: vi.fn(), updateFbaAppointment: vi.fn(), createFbaAppointment: vi.fn(), updateInboundPlan: vi.fn(), })) vi.mock('element-plus', () => ({ ElMessage: {warning:vi.fn(),success:vi.fn()} })) const render=()=>mount(InboundOrdersView,{global:{directives:{loading:{}},stubs:{ AdminPage:{template:'
'},ListPageLayout:{template:'
'}, DataTable:{props:['data'],template:'
'}, AppDialog:{name:'AppDialog',props:['modelValue','beforeClose','showConfirm'],template:'
'}, ElButton:{template:''}, ElAlert:{props:['title'],template:'

{{ title }}

'}, FbaAppointmentFields:true,SmartDispatchDialog:true,QueryToolbar:true,OperationErrorToast:true, }}}) beforeEach(()=>{vi.clearAllMocks();access.canWrite=true;vi.mocked(getFbaAppointment).mockResolvedValue({id:'1',appointmentNo:'AP-1',version:2,originWarehouseId:'100',fbaWarehouseCode:'ONT8',appointmentStatus:'PENDING',selectedCount:1,items:[]})}) it('opens linked appointment details for list-only users and blocks saving',async()=>{ access.canWrite=false const wrapper=render();await flushPromises() expect(wrapper.findAll('button').some(b=>b.text()==='预约')).toBe(false) await wrapper.findAll('button').find(b=>b.text()==='查看预约')!.trigger('click');await flushPromises() expect(getFbaAppointment).toHaveBeenCalledWith('AP-1') expect(wrapper.findComponent({name:'FbaAppointmentFields'}).props('disabled')).toBe(true) expect(wrapper.find('[data-save]').exists()).toBe(false) wrapper.findComponent({name:'AppDialog'}).vm.$emit('confirm');await flushPromises() expect(updateFbaAppointment).not.toHaveBeenCalled();wrapper.unmount() }) it('keeps the inbound appointment retry across close, reopen and route attempts', async () => { vi.mocked(updateFbaAppointment).mockRejectedValueOnce(new Error('connection lost')).mockResolvedValueOnce({} as never) const wrapper=render() await flushPromises() const open=()=>wrapper.findAll('button').find(b=>b.text()==='预约')!.trigger('click') await open();await flushPromises();await wrapper.get('[data-save]').trigger('click');await flushPromises() const original=vi.mocked(updateFbaAppointment).mock.calls[0]![1] await wrapper.get('[data-close]').trigger('click') const dialog=wrapper.findAllComponents({name:'AppDialog'})[0]! const done=vi.fn();dialog.props('beforeClose')(done);expect(done).not.toHaveBeenCalled() expect(wrapper.find('[data-save]').exists()).toBe(true) await open();await flushPromises();expect(getFbaAppointment).toHaveBeenCalledTimes(1) for(const hook of [onBeforeRouteLeave,onBeforeRouteUpdate]) expect(await (vi.mocked(hook).mock.calls[0]![0] as () => Promise)()).toBe(false) await wrapper.get('[data-save]').trigger('click');await flushPromises() expect(updateFbaAppointment).toHaveBeenNthCalledWith(2,'AP-1',original) expect(wrapper.find('[data-save]').exists()).toBe(false) expect(await (vi.mocked(onBeforeRouteLeave).mock.calls[0]![0] as () => Promise)()).toBe(true) wrapper.unmount() }) it('renders MULTIPLE as multiple appointments and opens the exact source/customer instead of choosing an arbitrary appointment', async () => { vi.mocked(listInboundOrders).mockResolvedValueOnce({records:[{lineId:'1',planId:'plan-1',planNo:'IP-1',orderId:'9007199254740993',customerId:'customer-a',appointmentNo:null,appointmentStatus:'MULTIPLE',fbaWarehouseCode:'ONT8'}],total:1,page:1,size:10,pages:1}) const wrapper = render(); await flushPromises() const button = wrapper.findAll('button').find(button => button.text() === '管理多批预约')! expect(button.exists()).toBe(true) expect(wrapper.findAll('button').some(button => button.text() === '查看预约' || button.text() === '预约')).toBe(false) await button.trigger('click'); await flushPromises() expect(routing.push).toHaveBeenCalledWith({path:'/appointment-calendar',query:{orderId:'9007199254740993',customerId:'customer-a',destinationKey:'FBA:ONT8'}}) expect(getFbaAppointment).not.toHaveBeenCalled(); wrapper.unmount() }) it('keeps replaced appointment snapshots read-only in the inbound dialog', async () => { vi.mocked(getFbaAppointment).mockResolvedValueOnce({id:'1',appointmentNo:'AP-1',batchKey:'FBA_APPOINTMENT:1',replacementBatchKeys:['FBA_APPOINTMENT:2'],replacementAppointmentNos:['AP-NEW'],version:3,originWarehouseId:'100',fbaWarehouseCode:'ONT8',appointmentStatus:'PENDING',selectedCount:1,items:[]}) const wrapper=render(); await flushPromises() await wrapper.findAll('button').find(button=>button.text()==='预约')!.trigger('click'); await flushPromises() expect(wrapper.text()).toContain('替代预约:AP-NEW') expect(wrapper.find('[data-save]').exists()).toBe(false) wrapper.findComponent({name:'AppDialog'}).vm.$emit('confirm'); await flushPromises() expect(updateFbaAppointment).not.toHaveBeenCalled(); wrapper.unmount() })