import { flushPromises, mount } from '@vue/test-utils' import ElementPlus from 'element-plus' import { beforeEach, describe, expect, it, vi } from 'vitest' import AppointmentCancellationDialog from './AppointmentCancellationDialog.vue' import AppointmentCancellationHistory from './AppointmentCancellationHistory.vue' import AppointmentCancellationRecords from './AppointmentCancellationRecords.vue' import { deleteAppointment, listAppointmentCancellations, listAppointmentCancellationRecords } from './api' const access = vi.hoisted(() => ({ canCancel: true })) vi.mock('@/core/access/accessPreview', () => ({useAccessPreviewStore: () => ({can: () => access.canCancel})})) vi.mock('vue-router', () => ({onBeforeRouteLeave: vi.fn(), onBeforeRouteUpdate: vi.fn()})) vi.mock('./api', () => ({deleteAppointment: vi.fn(),listAppointmentCancellations: vi.fn(),listAppointmentCancellationRecords: vi.fn()})) const record = {appointmentNo:'AP-CANCEL',version:1,cancelReason:null,cancelledAt:'2026-10-11T01:00:00',snapshot:{fbaWarehouseCode:'ABE3',appointmentStatus:'CANCELLED',appointmentRemark:'原备注保留',plannedDate:null,plannedAt:null,plannedEndAt:null,confirmationNo:'EXT-1',appointmentAt:'2026-10-15T09:00:00',appointmentEndAt:null,timezone:'America/New_York',selectedCount:1}} function dialog() { return mount(AppointmentCancellationDialog,{props:{appointmentNo:'AP-CANCEL',version:0,selectedCount:1},global:{plugins:[ElementPlus],stubs:{AppDialog:{name:'AppDialog',props:['beforeClose','showConfirm'],template:'
'}}}}) } describe('appointment cancellation',()=>{ beforeEach(()=>{vi.resetAllMocks();access.canCancel=true}) it('requires a reason and retries the same reason and version after an unknown result',async()=>{ vi.mocked(deleteAppointment).mockRejectedValueOnce(new Error('lost')).mockResolvedValueOnce(undefined) const wrapper=dialog();await flushPromises() await wrapper.get('[data-save]').trigger('click');expect(deleteAppointment).not.toHaveBeenCalled() await wrapper.get('textarea').setValue(' 仓库取消时段 ') await wrapper.get('[data-save]').trigger('click');await flushPromises() expect(deleteAppointment).toHaveBeenCalledWith('AP-CANCEL',0,'仓库取消时段') const done=vi.fn();wrapper.getComponent({name:'AppDialog'}).props('beforeClose')(done);expect(done).not.toHaveBeenCalled() expect(wrapper.get('textarea').attributes('disabled')).toBeDefined() await wrapper.get('[data-save]').trigger('click');await flushPromises() expect(deleteAppointment).toHaveBeenNthCalledWith(2,'AP-CANCEL',0,'仓库取消时段') expect(wrapper.emitted('cancelled')).toHaveLength(1);wrapper.unmount() }) it('does not expose or invoke cancellation without its independent permission',async()=>{ access.canCancel=false;const wrapper=dialog();await flushPromises() expect(wrapper.find('[data-save]').exists()).toBe(false) wrapper.getComponent({name:'AppDialog'}).vm.$emit('confirm');await flushPromises() expect(deleteAppointment).not.toHaveBeenCalled();wrapper.unmount() }) it('reads history once per page and keeps missing historical reason explicit',async()=>{ vi.mocked(listAppointmentCancellations).mockResolvedValue({records:[record],total:11}) const wrapper=mount(AppointmentCancellationHistory,{props:{appointmentNo:'AP-CANCEL'},global:{plugins:[ElementPlus]}});await flushPromises() expect(listAppointmentCancellations).toHaveBeenCalledExactlyOnceWith('AP-CANCEL',1,10) expect(wrapper.text()).toContain('历史原因未记录');expect(wrapper.text()).toContain('原备注保留') wrapper.getComponent({name:'DataTable'}).vm.$emit('update:page',2);await flushPromises() expect(listAppointmentCancellations).toHaveBeenLastCalledWith('AP-CANCEL',2,10);wrapper.unmount() }) it('discovers persisted cancellations with one server page and opens by business number',async()=>{ vi.mocked(listAppointmentCancellationRecords).mockResolvedValue({records:[{appointmentNo:'AP-CANCEL',version:1,cancelReason:'实际原因',cancelledAt:record.cancelledAt,fbaWarehouseCode:'ABE3',selectedCount:1}],total:1}) const wrapper=mount(AppointmentCancellationRecords,{global:{plugins:[ElementPlus]}});await flushPromises() expect(listAppointmentCancellationRecords).toHaveBeenCalledExactlyOnceWith(1,10) expect(listAppointmentCancellations).not.toHaveBeenCalled() await wrapper.get('.el-table__body-wrapper .el-table__row').findAll('button').find(b=>b.text()==='查看')!.trigger('click') expect(wrapper.emitted('view')).toEqual([['AP-CANCEL']]);wrapper.unmount() }) })