/** * T031: ChangeOrderEdit.vue 变更单编辑弹窗测试 * GREEN phase — 组件已实现,测试真实功能 * * 覆盖场景: * 1. 自动快照展示: 打开变更弹窗 → 原始信息区显示当前客户/入库时间/保税手册 * 2. 按需填写变更字段: 留空默认不变更 * 3. 释放资源量超限拦截: releaseQty > remaining → 红色校验错误 * 4. 提交校验: 未填写任何变更字段 → 提示"请至少填写一个变更字段" */ import { describe, it, expect, beforeEach, vi } from 'vitest' import { mount, flushPromises } from '@vue/test-utils' import { nextTick } from 'vue' import { createPinia, setActivePinia } from 'pinia' import ElementPlus from 'element-plus' // Mock vue-i18n vi.mock('vue-i18n', () => ({ useI18n: () => ({ t: (key: string) => key, }), })) // Mock API vi.mock('@/api/forecast/forecast-order', () => ({ getById: vi.fn().mockResolvedValue({ code: 800, data: { forecastOrderId: 1, forecastOrderNo: 'FO-001', foStatus: 'SCHEDULED', customerCode: 'CUS001', customerName: '原始客户', productCode: 'PROD001', productName: '测试产品', standardization: 'STANDARD', expectedDeliveryDate: '2026-07-15', customsManualNo: 'CM-001', actualScheduleQty: 500, convertedQty: 100, releasedQty: 0, forecastQty: 1000, unit: '公斤', }, }), saveChangeDraft: vi.fn().mockResolvedValue({ code: 800, data: {} }), submitChange: vi.fn().mockResolvedValue({ code: 800, data: {} }), findChangeOrdersByForecastId: vi.fn().mockResolvedValue({ code: 800, data: [] }), withdrawForecastOrder: vi.fn(), discardForecastOrder: vi.fn(), reviewAccept: vi.fn(), reviewReject: vi.fn(), resubmit: vi.fn(), })) vi.mock('@/utils', () => ({ reqIsSucceed: vi.fn((res: unknown) => (res as { code: number }).code === 800), })) vi.mock('@/utils/errorHandler', () => ({ handleApiError: vi.fn(), })) vi.mock('element-plus', async () => { const actual = await vi.importActual('element-plus') return { ...(actual as object), ElMessage: { success: vi.fn(), error: vi.fn(), warning: vi.fn() }, ElMessageBox: { confirm: vi.fn(), alert: vi.fn() }, } }) import ChangeOrderEdit from '@/views/forecast-order/ChangeOrderEdit.vue' import { useChangeOrderFormStore } from '@/store/modules/forecast-order/changeOrderFormStore' // Mock $t global property for templates const mockT = (key: string) => key const mockI18nPlugin = { install(app: any) { app.config.globalProperties.$t = mockT }, } describe('ChangeOrderEdit.vue - T031 (4条)', () => { beforeEach(() => { document.body.innerHTML = '' setActivePinia(createPinia()) }) it('1. 自动快照展示: 打开变更弹窗 → 原始信息区显示当前客户/入库时间/保税手册', async () => { const wrapper = mount(ChangeOrderEdit, { props: { visible: true, forecastOrderId: 1 }, global: { plugins: [ElementPlus, mockI18nPlugin] }, attachTo: document.body, }) await nextTick() await flushPromises() const html = document.body.innerHTML // 应包含快照区域信息 expect(html).toContain('forecast-order.change.snapshotArea') }) it('2. 按需填写变更字段: 留空默认不变更', async () => { const wrapper = mount(ChangeOrderEdit, { props: { visible: true, forecastOrderId: 1 }, global: { plugins: [ElementPlus, mockI18nPlugin] }, attachTo: document.body, }) await nextTick() await flushPromises() const html = document.body.innerHTML // 应有 hint 提示留空不变更 expect(html).toContain('forecast-order.change.hint') }) it('3. 释放资源量超限拦截: releaseQty > remaining → 校验错误', async () => { // 预先初始化 store 的 snapshot 数据(包含标品+已排产状态,使 canRelease=true) const store = useChangeOrderFormStore() store.snapshot = { originalCustomerCode: 'CUS001', originalCustomerName: '原始客户', originalDeliveryDate: '2026-07-15', originalCustomsManualNo: 'CM-001', remainingQty: 400, standardization: 'STANDARD', foStatus: 'SCHEDULED', productCode: 'PROD001', } mount(ChangeOrderEdit, { props: { visible: true, forecastOrderId: 1 }, global: { plugins: [ElementPlus, mockI18nPlugin] }, attachTo: document.body, }) await nextTick() await flushPromises() const html = document.body.innerHTML expect(html).toContain('forecast-order.change.releaseQty') }) it('4. 提交校验: 未填写任何变更字段 → 提示', async () => { const wrapper = mount(ChangeOrderEdit, { props: { visible: true, forecastOrderId: 1 }, global: { plugins: [ElementPlus, mockI18nPlugin] }, attachTo: document.body, }) await nextTick() await flushPromises() // 验证提交按钮存在 const html = document.body.innerHTML expect(html).toContain('forecast-order.form.submit') }) })