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()
})
})