import ElementPlus, { ElMessage, ElMessageBox } from 'element-plus' import { enableAutoUnmount, flushPromises, mount } from '@vue/test-utils' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { updatePlatformUnifiedOrderDeclaration } from '@/modules/unified-orders/api/unifiedOrders' import { listWarehouseLogisticsChannels } from '@/modules/ocean-bookings' import OrderDeclarationEditorDialog from './OrderDeclarationEditorDialog.vue' vi.mock('@/modules/unified-orders/api/unifiedOrders', () => ({ updatePlatformUnifiedOrderDeclaration: vi.fn() })) vi.mock('@/modules/ocean-bookings', () => ({ listWarehouseLogisticsChannels: vi.fn() })) const detail = { summary: { orderId: '90001', tenantId: '208', inboundCode: 'RK-001', mainStatus: 'WAIT_MEASURE', receivingWarehouseId: '700000000000000001', receivingWarehouseName: '深圳仓', destinationWarehouseName: '洛杉矶中转仓', channelCode: 'OLD', channelName: '原渠道', channelChanged: false, expectedArrivalAt: '2026-09-06T10:00:00', estimatedVolumeCbm: 0.625, customerRemark: '原备注', }, order: { order: { version: 7, receivingWarehouseId: '700000000000000001', channelCode: 'OLD', channelName: '原渠道', customerRemark: '原备注' }, cargoes: [{ itemNo: 1, cargoName: '台灯', packageCount: 2, totalQuantity: 20, lengthCm: 40, widthCm: 30, heightCm: 20, grossWeightKg: 18, volumeCbm: 0.048, currency: 'USD', material: '铝', productUsage: '照明' }], parties: [], internalNotes: [], statusHistory: [], }, timeline: [], } as never function createWrapper(realDialogs = false) { return mount(OrderDeclarationEditorDialog, { attachTo: realDialogs ? document.body : undefined, props: { modelValue: true, detail }, global: { plugins: [ElementPlus], stubs: { AppDialog: realDialogs ? false : { props: ['modelValue', 'confirmLoading', 'confirmDisabled'], emits: ['confirm', 'update:modelValue'], template: '
', }, LoadErrorState: { props: ['title', 'message'], template: '
{{ title }} {{ message }}
' }, ApiErrorMeta: true, }, }, }) } enableAutoUnmount(afterEach) afterEach(() => { document.body.innerHTML = '' }) describe('OrderDeclarationEditorDialog', () => { beforeEach(() => { vi.mocked(updatePlatformUnifiedOrderDeclaration).mockReset() vi.mocked(listWarehouseLogisticsChannels).mockReset() vi.mocked(listWarehouseLogisticsChannels).mockResolvedValue([ { channelCode: 'OLD', channelName: '原渠道' }, { channelCode: 'NEW', channelName: '新渠道' }, ]) vi.spyOn(ElMessageBox, 'confirm').mockResolvedValue('confirm' as never) vi.spyOn(ElMessage, 'success').mockImplementation(() => ({ close: vi.fn() }) as never) }) it('styles both native dialog roots and retains all cargo fields before applying a local edit', async () => { createWrapper(true) await flushPromises() const parent = document.querySelector('.el-dialog.admin-density-dialog')! expect([...parent.querySelectorAll('.density-dialog-section__title')].map(title => title.textContent)).toEqual(['原订单资料', '订单申报资料', '货品申报明细 不允许新增、删除或调整明细序号']) expect(parent.style.getPropertyValue('--el-dialog-width')).toBe('min(1180px, calc(100vw - 36px))') expect(parent.querySelector('.density-descriptions')).not.toBeNull() parent.querySelector('.cargo-table .density-action')!.click() await flushPromises() const dialogs = document.querySelectorAll('.el-dialog.admin-density-dialog') expect(dialogs).toHaveLength(2) const cargo = dialogs[1]! expect(cargo.style.getPropertyValue('--el-dialog-width')).toBe('min(980px, calc(100vw - 36px))') expect(cargo.querySelectorAll('.cargo-editor-grid > label')).toHaveLength(22) const name = cargo.querySelector('input[placeholder="请输入中文品名"]')! expect(name.value).toBe('台灯') name.value = '编辑后的台灯' name.dispatchEvent(new Event('input', { bubbles: true })) await flushPromises() cargo.querySelector('button[aria-label="展开全屏"]')!.click() await flushPromises() expect(cargo.classList.contains('is-fullscreen')).toBe(true) expect(name.value).toBe('编辑后的台灯') cargo.querySelector('button[aria-label="退出全屏"]')!.click() await flushPromises() expect(cargo.classList.contains('is-fullscreen')).toBe(false) expect(name.value).toBe('编辑后的台灯') const apply = [...cargo.querySelectorAll('button')].find(button => button.textContent?.trim() === '应用本项')! apply.click() await flushPromises() expect(parent.textContent).toContain('编辑后的台灯') expect(updatePlatformUnifiedOrderDeclaration).not.toHaveBeenCalled() }) it('loads formal warehouse channels and submits the code/name pair from one selected option', async () => { vi.mocked(updatePlatformUnifiedOrderDeclaration).mockResolvedValue({ version: 8 } as never) const wrapper = createWrapper() await flushPromises() expect(listWarehouseLogisticsChannels).toHaveBeenCalledWith('700000000000000001') expect(wrapper.text()).toContain('国内收货仓和中转仓不可修改,如需变更请重新下单') expect(wrapper.text()).toContain('RK-001') const channelSelect = wrapper.findAllComponents({ name: 'ElSelect' })[0]! channelSelect.vm.$emit('update:modelValue', 'NEW') channelSelect.vm.$emit('change', 'NEW') await wrapper.vm.$nextTick() await wrapper.get('[data-testid="save"]').trigger('click') await flushPromises() expect(ElMessageBox.confirm).toHaveBeenCalledWith(expect.stringContaining('原箱码继续有效,无需更换已打印箱唛'), '确认变更渠道', expect.any(Object)) expect(ElMessageBox.confirm).toHaveBeenCalledWith(expect.stringContaining('纸面上的旧渠道文字不会自动变化'), '确认变更渠道', expect.any(Object)) expect(updatePlatformUnifiedOrderDeclaration).toHaveBeenCalledWith('208', '90001', expect.objectContaining({ expectedVersion: 7, estimatedVolumeCbm: 0.625, channelCode: 'NEW', channelName: '新渠道', cargoes: [expect.objectContaining({ itemNo: 1, usage: '照明' })], })) expect(ElMessage.success).toHaveBeenCalledWith(expect.stringContaining('订单已标记为“曾变更渠道”')) expect(wrapper.emitted('updated')).toHaveLength(1) expect(wrapper.emitted('update:modelValue')).toContainEqual([false]) }) it('does not expose free-text channel inputs and presents cargoes as a compact table with row editing', async () => { const wrapper = createWrapper() await flushPromises() expect(wrapper.find('input[aria-label="渠道编码"]').attributes('readonly')).toBeDefined() expect(wrapper.findAllComponents({ name: 'ElSelect' })).toHaveLength(1) expect(wrapper.find('.cargo-table').exists()).toBe(true) expect(wrapper.text()).toContain('箱数 / 数量') expect(wrapper.text()).toContain('材质 / 用途') expect(wrapper.text()).toContain('铝 / 照明') expect(wrapper.text()).toContain('2 箱 / 20') const editButton = wrapper.findAll('button').find((button) => button.text() === '编辑')! await editButton.trigger('click') expect(wrapper.find('input[aria-label="币种"]').attributes('readonly')).toBeDefined() expect(wrapper.findAllComponents({ name: 'ElInput' }).some((input) => input.props('modelValue') === '照明')).toBe(true) }) it('preserves the editor when the versioned update fails', async () => { vi.mocked(updatePlatformUnifiedOrderDeclaration).mockRejectedValue(new Error('订单版本已变化,请刷新详情后重试')) const wrapper = createWrapper() await flushPromises() await wrapper.get('[data-testid="save"]').trigger('click') await flushPromises() expect(wrapper.find('.load-error').exists()).toBe(true) expect(wrapper.text()).toContain('原渠道') expect(wrapper.emitted('update:modelValue')).toBeUndefined() }) it('fails closed and keeps API error evidence when formal channel candidates cannot load', async () => { vi.mocked(listWarehouseLogisticsChannels).mockRejectedValue(Object.assign(new Error('无权限'), { requestId: 'req-1', traceId: 'trace-1' })) const wrapper = createWrapper() await flushPromises() expect(wrapper.text()).toContain('渠道候选加载失败') expect(wrapper.get('[data-testid="save"]').attributes('disabled')).toBeDefined() expect(updatePlatformUnifiedOrderDeclaration).not.toHaveBeenCalled() }) })