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