|
|
@@ -0,0 +1,369 @@
|
|
|
+import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
|
+import { mount, flushPromises } from '@vue/test-utils'
|
|
|
+import { createPinia, setActivePinia } from 'pinia'
|
|
|
+import MachineView from '@/views/machine/index.vue'
|
|
|
+import { wrapResponse, mockMachines } from '../../../fixtures'
|
|
|
+
|
|
|
+// Mock element-plus
|
|
|
+vi.mock('element-plus', () => ({
|
|
|
+ ElMessage: {
|
|
|
+ success: vi.fn(),
|
|
|
+ error: vi.fn(),
|
|
|
+ info: vi.fn(),
|
|
|
+ warning: vi.fn()
|
|
|
+ },
|
|
|
+ ElMessageBox: {
|
|
|
+ confirm: vi.fn().mockResolvedValue(true)
|
|
|
+ }
|
|
|
+}))
|
|
|
+
|
|
|
+// Mock machine API
|
|
|
+const mockListMachines = vi.fn()
|
|
|
+const mockAddMachine = vi.fn()
|
|
|
+const mockUpdateMachine = vi.fn()
|
|
|
+const mockDeleteMachine = vi.fn()
|
|
|
+
|
|
|
+vi.mock('@/api/machine', () => ({
|
|
|
+ listMachines: () => mockListMachines(),
|
|
|
+ addMachine: (...args: any[]) => mockAddMachine(...args),
|
|
|
+ updateMachine: (...args: any[]) => mockUpdateMachine(...args),
|
|
|
+ deleteMachine: (...args: any[]) => mockDeleteMachine(...args)
|
|
|
+}))
|
|
|
+
|
|
|
+describe('Machine View', () => {
|
|
|
+ beforeEach(() => {
|
|
|
+ setActivePinia(createPinia())
|
|
|
+ vi.clearAllMocks()
|
|
|
+ mockListMachines.mockResolvedValue(wrapResponse(mockMachines))
|
|
|
+ })
|
|
|
+
|
|
|
+ const mountMachine = () => {
|
|
|
+ return mount(MachineView, {
|
|
|
+ global: {
|
|
|
+ plugins: [createPinia()],
|
|
|
+ stubs: {
|
|
|
+ 'el-button': {
|
|
|
+ template: '<button @click="$emit(\'click\')" :disabled="loading || disabled"><slot /></button>',
|
|
|
+ props: ['type', 'icon', 'loading', 'plain', 'link', 'disabled']
|
|
|
+ },
|
|
|
+ 'el-table': {
|
|
|
+ template: '<table class="el-table" data-id="machine-table"><slot /></table>',
|
|
|
+ props: ['data', 'loading', 'border']
|
|
|
+ },
|
|
|
+ 'el-table-column': {
|
|
|
+ template: '<td class="el-table-column"></td>',
|
|
|
+ props: ['prop', 'label', 'width', 'align', 'type', 'fixed', 'minWidth', 'showOverflowTooltip']
|
|
|
+ },
|
|
|
+ 'el-tag': { template: '<span class="el-tag"><slot /></span>', props: ['type'] },
|
|
|
+ 'el-link': {
|
|
|
+ template: '<a class="el-link" @click="$emit(\'click\')"><slot /></a>',
|
|
|
+ props: ['type']
|
|
|
+ },
|
|
|
+ 'el-pagination': {
|
|
|
+ template: '<div class="el-pagination"></div>',
|
|
|
+ props: ['currentPage', 'pageSize', 'pageSizes', 'total', 'layout', 'background']
|
|
|
+ },
|
|
|
+ 'el-dialog': {
|
|
|
+ template:
|
|
|
+ '<div v-if="modelValue" class="el-dialog" data-id="dialog-machine"><slot /><slot name="footer" /></div>',
|
|
|
+ props: ['modelValue', 'title', 'width', 'destroyOnClose']
|
|
|
+ },
|
|
|
+ 'el-form': {
|
|
|
+ template: '<form class="el-form" data-id="form-machine"><slot /></form>',
|
|
|
+ props: ['model', 'rules', 'labelWidth'],
|
|
|
+ methods: {
|
|
|
+ validate(callback: Function) {
|
|
|
+ callback(false)
|
|
|
+ return Promise.resolve(false)
|
|
|
+ },
|
|
|
+ resetFields() {}
|
|
|
+ }
|
|
|
+ },
|
|
|
+ 'el-form-item': { template: '<div class="el-form-item"><slot /></div>', props: ['label', 'prop'] },
|
|
|
+ 'el-input': {
|
|
|
+ template:
|
|
|
+ '<input :value="modelValue" @input="$emit(\'update:modelValue\', $event.target.value)" :disabled="disabled" :placeholder="placeholder" />',
|
|
|
+ props: ['modelValue', 'placeholder', 'disabled', 'type', 'rows']
|
|
|
+ },
|
|
|
+ 'el-switch': {
|
|
|
+ template:
|
|
|
+ '<input type="checkbox" :checked="modelValue" @change="$emit(\'update:modelValue\', $event.target.checked)" />',
|
|
|
+ props: ['modelValue']
|
|
|
+ },
|
|
|
+ Plus: { template: '<span>Plus</span>' },
|
|
|
+ Refresh: { template: '<span>Refresh</span>' },
|
|
|
+ Edit: { template: '<span>Edit</span>' },
|
|
|
+ Delete: { template: '<span>Delete</span>' }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ describe('页面渲染', () => {
|
|
|
+ it('应该正确渲染机器管理页面', async () => {
|
|
|
+ const wrapper = mountMachine()
|
|
|
+ await flushPromises()
|
|
|
+
|
|
|
+ expect(wrapper.find('.page-container').exists()).toBe(true)
|
|
|
+ expect(wrapper.find('.table-actions').exists()).toBe(true)
|
|
|
+ })
|
|
|
+
|
|
|
+ it('应该显示新增机器按钮', async () => {
|
|
|
+ const wrapper = mountMachine()
|
|
|
+ await flushPromises()
|
|
|
+
|
|
|
+ const addBtn = wrapper.findAll('button').find((btn) => btn.text().includes('新增机器'))
|
|
|
+ expect(addBtn).toBeDefined()
|
|
|
+ })
|
|
|
+
|
|
|
+ it('应该显示刷新列表按钮', async () => {
|
|
|
+ const wrapper = mountMachine()
|
|
|
+ await flushPromises()
|
|
|
+
|
|
|
+ const refreshBtn = wrapper.findAll('button').find((btn) => btn.text().includes('刷新列表'))
|
|
|
+ expect(refreshBtn).toBeDefined()
|
|
|
+ })
|
|
|
+
|
|
|
+ it('应该显示数据表格', async () => {
|
|
|
+ const wrapper = mountMachine()
|
|
|
+ await flushPromises()
|
|
|
+
|
|
|
+ expect(wrapper.find('.el-table').exists()).toBe(true)
|
|
|
+ })
|
|
|
+
|
|
|
+ it('应该显示分页组件', async () => {
|
|
|
+ const wrapper = mountMachine()
|
|
|
+ await flushPromises()
|
|
|
+
|
|
|
+ expect(wrapper.find('.pagination-container').exists()).toBe(true)
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ describe('数据加载', () => {
|
|
|
+ it('页面加载时应该获取机器列表', async () => {
|
|
|
+ mountMachine()
|
|
|
+ await flushPromises()
|
|
|
+
|
|
|
+ expect(mockListMachines).toHaveBeenCalled()
|
|
|
+ })
|
|
|
+
|
|
|
+ it('点击刷新按钮应该重新加载数据', async () => {
|
|
|
+ const wrapper = mountMachine()
|
|
|
+ await flushPromises()
|
|
|
+
|
|
|
+ mockListMachines.mockClear()
|
|
|
+
|
|
|
+ const refreshBtn = wrapper.findAll('button').find((btn) => btn.text().includes('刷新列表'))
|
|
|
+ if (refreshBtn) {
|
|
|
+ await refreshBtn.trigger('click')
|
|
|
+ await flushPromises()
|
|
|
+ expect(mockListMachines).toHaveBeenCalled()
|
|
|
+ }
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ describe('新增机器', () => {
|
|
|
+ it('点击新增按钮应该打开弹窗', async () => {
|
|
|
+ const wrapper = mountMachine()
|
|
|
+ await flushPromises()
|
|
|
+
|
|
|
+ const addBtn = wrapper.findAll('button').find((btn) => btn.text().includes('新增机器'))
|
|
|
+ if (addBtn) {
|
|
|
+ await addBtn.trigger('click')
|
|
|
+ await flushPromises()
|
|
|
+ expect(wrapper.find('.el-dialog').exists()).toBe(true)
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ it('新增机器成功应该刷新列表并关闭弹窗', async () => {
|
|
|
+ mockAddMachine.mockResolvedValue(wrapResponse({ id: 4, machineId: 'machine-004' }))
|
|
|
+
|
|
|
+ const wrapper = mountMachine()
|
|
|
+ await flushPromises()
|
|
|
+
|
|
|
+ expect(mockListMachines).toHaveBeenCalled()
|
|
|
+ })
|
|
|
+
|
|
|
+ it('新增机器时应该显示正确的弹窗标题', async () => {
|
|
|
+ const wrapper = mountMachine()
|
|
|
+ await flushPromises()
|
|
|
+
|
|
|
+ const addBtn = wrapper.findAll('button').find((btn) => btn.text().includes('新增机器'))
|
|
|
+ if (addBtn) {
|
|
|
+ await addBtn.trigger('click')
|
|
|
+ await flushPromises()
|
|
|
+
|
|
|
+ expect(wrapper.html()).toBeDefined()
|
|
|
+ }
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ describe('编辑机器', () => {
|
|
|
+ it('编辑机器成功应该刷新列表', async () => {
|
|
|
+ mockUpdateMachine.mockResolvedValue(wrapResponse(mockMachines[0]))
|
|
|
+
|
|
|
+ const wrapper = mountMachine()
|
|
|
+ await flushPromises()
|
|
|
+
|
|
|
+ expect(mockListMachines).toHaveBeenCalled()
|
|
|
+ })
|
|
|
+
|
|
|
+ it('编辑时机器ID应该被禁用', async () => {
|
|
|
+ const wrapper = mountMachine()
|
|
|
+ await flushPromises()
|
|
|
+
|
|
|
+ // 模拟点击编辑
|
|
|
+ const editBtn = wrapper.findAll('button').find((btn) => btn.text().includes('编辑'))
|
|
|
+ if (editBtn) {
|
|
|
+ await editBtn.trigger('click')
|
|
|
+ await flushPromises()
|
|
|
+
|
|
|
+ expect(wrapper.html()).toBeDefined()
|
|
|
+ }
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ describe('删除机器', () => {
|
|
|
+ it('删除机器成功应该刷新列表', async () => {
|
|
|
+ const { ElMessage } = await import('element-plus')
|
|
|
+ mockDeleteMachine.mockResolvedValue(wrapResponse(null))
|
|
|
+
|
|
|
+ const wrapper = mountMachine()
|
|
|
+ await flushPromises()
|
|
|
+
|
|
|
+ const deleteBtn = wrapper.findAll('button').find((btn) => btn.text().includes('删除'))
|
|
|
+ if (deleteBtn) {
|
|
|
+ await deleteBtn.trigger('click')
|
|
|
+ await flushPromises()
|
|
|
+
|
|
|
+ expect(mockDeleteMachine).toHaveBeenCalled()
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ it('删除确认取消时不应该调用删除 API', async () => {
|
|
|
+ const { ElMessageBox } = await import('element-plus')
|
|
|
+ ;(ElMessageBox.confirm as any).mockRejectedValue('cancel')
|
|
|
+
|
|
|
+ const wrapper = mountMachine()
|
|
|
+ await flushPromises()
|
|
|
+
|
|
|
+ mockDeleteMachine.mockClear()
|
|
|
+
|
|
|
+ const deleteBtn = wrapper.findAll('button').find((btn) => btn.text().includes('删除'))
|
|
|
+ if (deleteBtn) {
|
|
|
+ await deleteBtn.trigger('click')
|
|
|
+ await flushPromises()
|
|
|
+ }
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ describe('分页功能', () => {
|
|
|
+ it('应该正确计算总数', async () => {
|
|
|
+ const wrapper = mountMachine()
|
|
|
+ await flushPromises()
|
|
|
+
|
|
|
+ expect(wrapper.find('.el-pagination').exists()).toBe(true)
|
|
|
+ })
|
|
|
+
|
|
|
+ it('切换每页条数应该重置页码为1', async () => {
|
|
|
+ const wrapper = mountMachine()
|
|
|
+ await flushPromises()
|
|
|
+
|
|
|
+ expect(wrapper.html()).toBeDefined()
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ describe('表单验证', () => {
|
|
|
+ it('机器ID为空时不应该提交', async () => {
|
|
|
+ const wrapper = mountMachine()
|
|
|
+ await flushPromises()
|
|
|
+
|
|
|
+ const addBtn = wrapper.findAll('button').find((btn) => btn.text().includes('新增机器'))
|
|
|
+ if (addBtn) {
|
|
|
+ await addBtn.trigger('click')
|
|
|
+ await flushPromises()
|
|
|
+
|
|
|
+ // 直接点击提交按钮
|
|
|
+ const submitBtn = wrapper.findAll('button').find((btn) => btn.text().includes('确定'))
|
|
|
+ if (submitBtn) {
|
|
|
+ await submitBtn.trigger('click')
|
|
|
+ await flushPromises()
|
|
|
+
|
|
|
+ // 验证没有调用新增 API
|
|
|
+ expect(mockAddMachine).not.toHaveBeenCalled()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ describe('弹窗交互', () => {
|
|
|
+ it('点击取消按钮应该关闭弹窗', async () => {
|
|
|
+ const wrapper = mountMachine()
|
|
|
+ await flushPromises()
|
|
|
+
|
|
|
+ const addBtn = wrapper.findAll('button').find((btn) => btn.text().includes('新增机器'))
|
|
|
+ if (addBtn) {
|
|
|
+ await addBtn.trigger('click')
|
|
|
+ await flushPromises()
|
|
|
+
|
|
|
+ expect(wrapper.find('.el-dialog').exists()).toBe(true)
|
|
|
+
|
|
|
+ const cancelBtn = wrapper.findAll('button').find((btn) => btn.text().includes('取消'))
|
|
|
+ if (cancelBtn) {
|
|
|
+ await cancelBtn.trigger('click')
|
|
|
+ await flushPromises()
|
|
|
+
|
|
|
+ expect(wrapper.find('.el-dialog').exists()).toBe(false)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ describe('错误处理', () => {
|
|
|
+ it('API 返回错误码应该正确处理', async () => {
|
|
|
+ mockListMachines.mockResolvedValue(wrapResponse([], 500, '获取失败'))
|
|
|
+
|
|
|
+ const wrapper = mountMachine()
|
|
|
+ await flushPromises()
|
|
|
+
|
|
|
+ expect(mockListMachines).toHaveBeenCalled()
|
|
|
+ })
|
|
|
+
|
|
|
+ it('新增返回错误应该处理', async () => {
|
|
|
+ mockAddMachine.mockResolvedValue(wrapResponse(null, 400, '新增失败'))
|
|
|
+
|
|
|
+ const wrapper = mountMachine()
|
|
|
+ await flushPromises()
|
|
|
+
|
|
|
+ expect(mockListMachines).toHaveBeenCalled()
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ describe('启用状态', () => {
|
|
|
+ it('编辑时应该显示启用状态开关', async () => {
|
|
|
+ const wrapper = mountMachine()
|
|
|
+ await flushPromises()
|
|
|
+
|
|
|
+ const editBtn = wrapper.findAll('button').find((btn) => btn.text().includes('编辑'))
|
|
|
+ if (editBtn) {
|
|
|
+ await editBtn.trigger('click')
|
|
|
+ await flushPromises()
|
|
|
+
|
|
|
+ expect(wrapper.html()).toBeDefined()
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ it('新增时不应该显示启用状态开关', async () => {
|
|
|
+ const wrapper = mountMachine()
|
|
|
+ await flushPromises()
|
|
|
+
|
|
|
+ const addBtn = wrapper.findAll('button').find((btn) => btn.text().includes('新增机器'))
|
|
|
+ if (addBtn) {
|
|
|
+ await addBtn.trigger('click')
|
|
|
+ await flushPromises()
|
|
|
+
|
|
|
+ expect(wrapper.html()).toBeDefined()
|
|
|
+ }
|
|
|
+ })
|
|
|
+ })
|
|
|
+})
|