import { defineComponent, h } from 'vue' import { flushPromises, mount } from '@vue/test-utils' import { beforeEach, describe, expect, it, vi } from 'vitest' import { ApiResponseError } from '@/shared/api/http' import OutsourcedBusinessOperationsEditor from './OutsourcedBusinessOperationsEditor.vue' import { outsourcedBusinessFixture } from './OutsourcedBusinessFixtures' const calls = vi.hoisted(() => ({ save: vi.fn(), detail: vi.fn(), command: vi.fn(), canEdit: true })) vi.mock('../api/outsourcedBusiness', () => ({ saveOutsourcedBusinessOperations: calls.save, getOutsourcedBusiness: calls.detail, getOutsourcedBusinessCommand: calls.command })) vi.mock('@/core/auth/auth', () => ({ useAuthStore: () => ({ user: { id: 'actor' } }) })) vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: () => calls.canEdit }) })) vi.mock('@/core/unsaved-work/useUnsavedWorkRegistration', () => ({ useUnsavedWorkRegistration: () => ({ captureBaseline: vi.fn() }) })) vi.mock('element-plus', () => ({ ElMessage: { success: vi.fn() }, ElMessageBox: { confirm: vi.fn() } })) const Frame = defineComponent({ props: ['modelValue','busy','saveDisabled','pending'], emits: ['save','update:modelValue'], setup(props,{ slots,emit }) { return () => h('div', [...Object.values(slots).map(slot => slot?.()), h('button',{ 'data-save':'',disabled: props.busy || props.saveDisabled,onClick: () => emit('save') },'save')]) } }) const Control = defineComponent({ props: ['modelValue'], emits: ['update:modelValue'], template: '' }) const Field = defineComponent({ props: ['label'], template: '' }) const global = { stubs: { OutsourcedBusinessOperationsDialog: Frame, ElForm: { template:'
' }, ElFormItem: Field, ElInput: Control, ElSelect: Control, ElOption: true, ElDatePicker: Control, LoadErrorState: { props:['message'],template:'
{{message}}
' }, ApiErrorMeta: true } } beforeEach(() => { vi.clearAllMocks(); sessionStorage.clear(); calls.canEdit = true const current = outsourcedBusinessFixture(); current.row.recordVersion = '1' calls.save.mockResolvedValue(current); calls.detail.mockResolvedValue(current) calls.command.mockRejectedValue(new ApiResponseError('not found','A0404',404)) }) describe('outsourced operations registration', () => { it('sends explicit zero and destination arrival with auxiliary zero, without fuel or mileage fields', async () => { const wrapper = mount(OutsourcedBusinessOperationsEditor,{ global,props:{modelValue:true,detail:outsourcedBusinessFixture()} }); await flushPromises() const currency = wrapper.findAll('label').find(field => field.text() === '币种')!.find('input') const amount = wrapper.findAll('label').find(field => field.text() === '整车承运金额')!.find('input') await currency.setValue('USD'); await amount.setValue('0.00') await wrapper.findAll('input').at(-1)!.setValue('2026-10-20T12:00:00+08:00') await wrapper.find('[data-save]').trigger('click'); await flushPromises() const body = calls.save.mock.calls[0]![1] expect(body).toEqual({ recordVersion:'0',idempotencyKey:expect.any(String),operations:{money:{currency:'USD',amount:'0.00'},arrivals:[{destinationKey:'destination-1',actualArrivedAt:'2026-10-20T12:00:00+08:00'}]},reason:null }) expect(calls.save).toHaveBeenCalledTimes(1); expect(wrapper.emitted('saved')).toHaveLength(1) expect(wrapper.text()).not.toContain('油耗'); expect(wrapper.text()).not.toContain('里程'); wrapper.unmount() }) it('clears money as null and unlocks the form after an unknown save', async () => { const detail = outsourcedBusinessFixture(); detail.row.operations.money = {currency:'CNY',amount:'123.45'} calls.save.mockRejectedValueOnce(new TypeError('network interrupted')) const wrapper = mount(OutsourcedBusinessOperationsEditor,{global,props:{modelValue:true,detail}}); await flushPromises() await wrapper.findAll('label').find(field => field.text() === '币种')!.find('input').setValue('') await wrapper.findAll('label').find(field => field.text() === '整车承运金额')!.find('input').setValue('') await wrapper.find('[data-save]').trigger('click'); await flushPromises() const original = JSON.parse(JSON.stringify(calls.save.mock.calls[0]![1])) expect(original.operations.money).toBeNull(); expect(wrapper.emitted('saved')).toBeUndefined() await wrapper.find('[data-save]').trigger('click'); await flushPromises() expect(calls.command).not.toHaveBeenCalled() expect(calls.save.mock.calls[1]![1]).toEqual({...original,idempotencyKey:expect.any(String)});expect(calls.save.mock.calls[1]![1].idempotencyKey).not.toBe(original.idempotencyKey); expect(wrapper.emitted('saved')).toHaveLength(1); wrapper.unmount() }) it('does not write for an unchanged form or a user without edit permission', async () => { const first = mount(OutsourcedBusinessOperationsEditor,{global,props:{modelValue:true,detail:outsourcedBusinessFixture()}}); await flushPromises() await first.find('[data-save]').trigger('click'); await flushPromises(); expect(calls.save).not.toHaveBeenCalled(); first.unmount() calls.canEdit = false const readonly = mount(OutsourcedBusinessOperationsEditor,{global,props:{modelValue:true,detail:outsourcedBusinessFixture()}}); await flushPromises() expect(readonly.find('[data-save]').attributes('disabled')).toBeDefined(); expect(calls.save).not.toHaveBeenCalled(); readonly.unmount() }) })