import { flushPromises, mount } from '@vue/test-utils' import ElementPlus, { ElMessage } from 'element-plus' import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest' import MasterDataSettings from './MasterDataSettings.vue' import { codeRulesApi, materialCategoriesApi, unitsApi } from '../../api/masterData' vi.mock('../../api/masterData', () => ({ materialCategoriesApi: { list: vi.fn(), create: vi.fn(), update: vi.fn() }, unitsApi: { list: vi.fn(), create: vi.fn(), update: vi.fn() }, codeRulesApi: { list: vi.fn(), create: vi.fn(), update: vi.fn() } })) const STANDARD_PART_DESCRIPTION = '1.核心定义:国家/行业通用标准化零件,型号固定,无需绘图、无需任何二次加工。 2.包含说明:设备通用紧固、锁紧、防松类标准配件,采用全行业通用规格,无非标改造。 3.现场举例:内六角螺钉、沉头螺钉、组合螺钉。 4.边界区分:国标通用紧固配件统一归此类;非标定制紧固结构件归入 A04。' // 真实的 ElSelect 在 jsdom 下会因状态双向绑定反复触发自身更新而报 // "Maximum recursive updates exceeded"。这里只关心表单校验,用轻量替身绕开。 const SelectStub = { props: ['modelValue', 'placeholder'], emits: ['update:modelValue', 'change'], template: '
' } const OptionStub = { props: ['label', 'value'], template: '{{ label }}' } function mountSettings() { return mount(MasterDataSettings, { global: { plugins: [ElementPlus], stubs: { teleport: true, ElSelect: SelectStub, ElOption: OptionStub } } }) } function detailButtons(wrapper) { return wrapper.findAll('button').filter(button => button.text() === '描述详情') } describe('物料分类描述详情', () => { beforeEach(() => { materialCategoriesApi.list.mockResolvedValue({ data: [ { id: 1, code: 'A', name: '机械结构类', description: '纯机械传动件纳入本类管理。', parent_id: null, status: 'active' }, { id: 2, code: 'A01', name: '标准件', description: STANDARD_PART_DESCRIPTION, parent_id: 1, status: 'active' }, { id: 3, code: 'A02', name: '成品件', description: '', parent_id: 1, status: 'active' } ] }) unitsApi.list.mockResolvedValue({ data: [] }) codeRulesApi.list.mockResolvedValue({ data: [] }) }) test('每一行都提供描述详情按钮,描述正文不直接铺在表格里', async () => { const wrapper = mountSettings() await flushPromises() expect(detailButtons(wrapper)).toHaveLength(3) expect(wrapper.text()).not.toContain('国家/行业通用标准化零件') wrapper.unmount() }) test('点击描述详情后弹窗展示该行的完整描述', async () => { const wrapper = mountSettings() await flushPromises() await detailButtons(wrapper)[1].trigger('click') await flushPromises() expect(wrapper.text()).toContain('A01 标准件') expect(wrapper.text()).toContain(STANDARD_PART_DESCRIPTION) expect(wrapper.text()).not.toContain('纯机械传动件纳入本类管理。') wrapper.unmount() }) test('描述为空时弹窗显示占位文案', async () => { const wrapper = mountSettings() await flushPromises() await detailButtons(wrapper)[2].trigger('click') await flushPromises() expect(wrapper.text()).toContain('A02 成品件') expect(wrapper.text()).toContain('暂无描述') wrapper.unmount() }) }) describe('基础配置表单必填校验', () => { let warnSpy // 表单项没有 placeholder,按 label 文本定位输入框。 function inputByLabel(wrapper, label) { return wrapper.findAll('.el-form-item') .find(node => node.find('.el-form-item__label').text() === label) .find('input') } function findButton(wrapper, text) { return wrapper.findAll('button').find(button => button.text().includes(text)) } async function openCreateDialog(wrapper) { await findButton(wrapper, '新增').trigger('click') await flushPromises() } async function clickSave(wrapper) { await findButton(wrapper, '保存').trigger('click') await flushPromises() } async function switchTab(wrapper, label) { await wrapper.findAll('.el-tabs__item').find(node => node.text() === label).trigger('click') await flushPromises() } beforeEach(() => { warnSpy = vi.spyOn(ElMessage, 'warning').mockImplementation(() => {}) materialCategoriesApi.list.mockResolvedValue({ data: [] }) materialCategoriesApi.create.mockReset().mockResolvedValue({ data: {} }) materialCategoriesApi.update.mockReset().mockResolvedValue({ data: {} }) unitsApi.list.mockResolvedValue({ data: [] }) unitsApi.create.mockReset().mockResolvedValue({ data: {} }) codeRulesApi.list.mockResolvedValue({ data: [] }) codeRulesApi.create.mockReset().mockResolvedValue({ data: {} }) }) afterEach(() => { vi.restoreAllMocks() }) test('物料分类 tab:编码留空被拦下', async () => { const wrapper = mountSettings() await flushPromises() await openCreateDialog(wrapper) await clickSave(wrapper) expect(materialCategoriesApi.create).not.toHaveBeenCalled() expect(warnSpy).toHaveBeenCalledWith('请填写编码') wrapper.unmount() }) test('物料分类 tab:只填了编码时提示名称', async () => { const wrapper = mountSettings() await flushPromises() await openCreateDialog(wrapper) await inputByLabel(wrapper, '编码').setValue('A03') await clickSave(wrapper) expect(materialCategoriesApi.create).not.toHaveBeenCalled() expect(warnSpy).toHaveBeenCalledWith('请填写名称') wrapper.unmount() }) test('编码规则 tab:分类未选时提示物料分类', async () => { const wrapper = mountSettings() await flushPromises() await switchTab(wrapper, '编码规则') await openCreateDialog(wrapper) await clickSave(wrapper) expect(codeRulesApi.create).not.toHaveBeenCalled() expect(warnSpy).toHaveBeenCalledWith('请选择物料分类') wrapper.unmount() }) test('编码规则 tab 不校验编码与名称', async () => { const wrapper = mountSettings() await flushPromises() await switchTab(wrapper, '编码规则') await openCreateDialog(wrapper) // 分类和前缀都为空,应当只提示分类,不应冒出"请填写编码" await clickSave(wrapper) expect(warnSpy).not.toHaveBeenCalledWith('请填写编码') expect(warnSpy).not.toHaveBeenCalledWith('请填写名称') wrapper.unmount() }) })