material_manage_system/frontend/src/views/Materials.test.js

212 lines
7.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { flushPromises, mount } from '@vue/test-utils'
import ElementPlus, { ElMessage } from 'element-plus'
import { beforeEach, describe, expect, test, vi } from 'vitest'
import Materials from './Materials.vue'
import { codeRulesApi, materialCategoriesApi, unitsApi } from '../api/masterData'
const materialsStore = vi.hoisted(() => ({
materials: [],
materialOptions: [],
loading: false,
pagination: { currentPage: 1, pageSize: 20, total: 0, totalPages: 0 },
fetchMaterials: vi.fn(),
fetchMaterialOptions: vi.fn(),
createMaterial: vi.fn(),
updateMaterial: vi.fn(),
obsoleteMaterial: vi.fn(),
restoreMaterial: vi.fn(),
getCategoryDisplay: vi.fn(() => '-')
}))
vi.mock('../stores/materials', () => ({ useMaterialsStore: () => materialsStore }))
vi.mock('../stores/auth', () => ({ useAuthStore: () => ({ isAdmin: true }) }))
vi.mock('../api/masterData', () => ({
materialCategoriesApi: { list: vi.fn() },
unitsApi: { list: vi.fn() },
codeRulesApi: { next: vi.fn() }
}))
// 真实的 ElSelect 在 jsdom 下会因自身更新报 "Maximum recursive updates exceeded",
// 这里用替身直接派发 change,模拟用户选中某个分类。
const SelectStub = {
props: ['modelValue', 'placeholder', 'filterable', 'filterMethod', 'noDataText', 'disabled', 'clearable'],
emits: ['update:modelValue', 'change'],
methods: {
pick(value) {
this.$emit('update:modelValue', value)
this.$emit('change', value)
}
},
template: '<div class="select-stub"></div>'
}
const OptionStub = { props: ['label', 'value'], template: '<span />' }
const categories = [
{ id: 1, code: 'A', name: '机械结构类', parent_id: null, status: 'active' },
{ id: 12, code: 'A01', name: '标准件', parent_id: 1, status: 'active' },
{ id: 13, code: 'A02', name: '成品件', parent_id: 1, status: 'active' }
]
function mountMaterials() {
return mount(Materials, {
global: {
plugins: [ElementPlus],
stubs: { teleport: true, ElSelect: SelectStub, ElOption: OptionStub }
}
})
}
function findButton(wrapper, text) {
return wrapper.findAll('button').find(button => button.text().includes(text))
}
// 页面上的物料搜索框也用了 ElSelect,弹窗里的三个依次是大类、小类、单位。
function formSelects(wrapper) {
const selects = wrapper.findAllComponents(SelectStub)
return { major: selects[1], minor: selects[2], unit: selects[3] }
}
function codeInput(wrapper) {
return wrapper.find('.material-code-field input')
}
async function openAddDialog(wrapper) {
await findButton(wrapper, '新增物料').trigger('click')
await flushPromises()
}
async function pickCategories(wrapper, { major = 1, minor = 12 } = {}) {
await formSelects(wrapper).major.vm.pick(major)
await formSelects(wrapper).minor.vm.pick(minor)
await flushPromises()
}
describe('新增物料时的编码联动', () => {
let warnSpy
beforeEach(() => {
materialCategoriesApi.list.mockResolvedValue({ data: categories })
unitsApi.list.mockResolvedValue({ data: [] })
codeRulesApi.next.mockReset()
materialsStore.createMaterial.mockReset()
materialsStore.updateMaterial.mockReset()
warnSpy = vi.spyOn(ElMessage, 'warning').mockImplementation(() => {})
})
test('还没选小类时,编码框提示先选分类', async () => {
codeRulesApi.next.mockResolvedValue({ data: { available: true, code: 'A010201' } })
const wrapper = mountMaterials()
await flushPromises()
await openAddDialog(wrapper)
expect(codeInput(wrapper).attributes('placeholder')).toBe('请先选择大类类型与小类类型')
await pickCategories(wrapper)
expect(codeInput(wrapper).attributes('placeholder')).toBe('')
wrapper.unmount()
})
test('选完小类后自动填入按规则算出的编码', async () => {
codeRulesApi.next.mockResolvedValue({ data: { available: true, code: 'A010201' } })
const wrapper = mountMaterials()
await flushPromises()
await openAddDialog(wrapper)
await pickCategories(wrapper)
expect(codeRulesApi.next).toHaveBeenCalledWith(12)
expect(codeInput(wrapper).element.value).toBe('A010201')
expect(wrapper.find('.material-code-hint').exists()).toBe(false)
wrapper.unmount()
})
test('小类没有规则时清空编码并提示手工填写', async () => {
codeRulesApi.next.mockResolvedValue({ data: { available: false, code: '' } })
const wrapper = mountMaterials()
await flushPromises()
await openAddDialog(wrapper)
await pickCategories(wrapper)
expect(codeInput(wrapper).element.value).toBe('')
expect(wrapper.find('.material-code-hint').text()).toContain('请手工填写')
wrapper.unmount()
})
test('没手改过编码时,换小类会按新分类的规则覆盖', async () => {
codeRulesApi.next
.mockResolvedValueOnce({ data: { available: true, code: 'A010201' } })
.mockResolvedValueOnce({ data: { available: true, code: 'A020202' } })
const wrapper = mountMaterials()
await flushPromises()
await openAddDialog(wrapper)
await pickCategories(wrapper)
expect(codeInput(wrapper).element.value).toBe('A010201')
await formSelects(wrapper).minor.vm.pick(13)
await flushPromises()
expect(codeRulesApi.next).toHaveBeenLastCalledWith(13)
expect(codeInput(wrapper).element.value).toBe('A020202')
wrapper.unmount()
})
test('手改过编码后,换小类保留用户填的值', async () => {
codeRulesApi.next.mockResolvedValue({ data: { available: true, code: 'A010201' } })
const wrapper = mountMaterials()
await flushPromises()
await openAddDialog(wrapper)
await pickCategories(wrapper)
await codeInput(wrapper).setValue('MY-001')
await flushPromises()
await formSelects(wrapper).minor.vm.pick(13)
await flushPromises()
expect(codeInput(wrapper).element.value).toBe('MY-001')
// 手改过就不再白白请求一次预览接口
expect(codeRulesApi.next).toHaveBeenCalledTimes(1)
wrapper.unmount()
})
test('点「重新生成」把编码交回当前分类的规则', async () => {
codeRulesApi.next
.mockResolvedValueOnce({ data: { available: true, code: 'A010201' } })
.mockResolvedValueOnce({ data: { available: true, code: 'A010299' } })
const wrapper = mountMaterials()
await flushPromises()
await openAddDialog(wrapper)
await pickCategories(wrapper)
await codeInput(wrapper).setValue('MY-001')
await flushPromises()
await findButton(wrapper, '重新生成').trigger('click')
await flushPromises()
expect(codeInput(wrapper).element.value).toBe('A010299')
wrapper.unmount()
})
test('小类没有规则且没填编码时,保存被拦下并提示补编码', async () => {
codeRulesApi.next.mockResolvedValue({ data: { available: false, code: '' } })
const wrapper = mountMaterials()
await flushPromises()
await openAddDialog(wrapper)
await pickCategories(wrapper)
await findButton(wrapper, '保存').trigger('click')
await flushPromises()
expect(materialsStore.createMaterial).not.toHaveBeenCalled()
expect(warnSpy).toHaveBeenCalledWith('请填写物料编码')
wrapper.unmount()
})
})