index.spec.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. import { describe, it, expect, vi, beforeEach } from 'vitest'
  2. import { mount, flushPromises } from '@vue/test-utils'
  3. import { createPinia, setActivePinia } from 'pinia'
  4. import MachineView from '@/views/machine/index.vue'
  5. import { wrapResponse, mockMachines } from '../../../fixtures'
  6. // Mock element-plus
  7. vi.mock('element-plus', () => ({
  8. ElMessage: {
  9. success: vi.fn(),
  10. error: vi.fn(),
  11. info: vi.fn(),
  12. warning: vi.fn()
  13. },
  14. ElMessageBox: {
  15. confirm: vi.fn().mockResolvedValue(true)
  16. }
  17. }))
  18. // Mock machine API
  19. const mockListMachines = vi.fn()
  20. const mockAddMachine = vi.fn()
  21. const mockUpdateMachine = vi.fn()
  22. const mockDeleteMachine = vi.fn()
  23. vi.mock('@/api/machine', () => ({
  24. listMachines: () => mockListMachines(),
  25. addMachine: (...args: any[]) => mockAddMachine(...args),
  26. updateMachine: (...args: any[]) => mockUpdateMachine(...args),
  27. deleteMachine: (...args: any[]) => mockDeleteMachine(...args)
  28. }))
  29. describe('Machine View', () => {
  30. beforeEach(() => {
  31. setActivePinia(createPinia())
  32. vi.clearAllMocks()
  33. mockListMachines.mockResolvedValue(wrapResponse(mockMachines))
  34. })
  35. const mountMachine = () => {
  36. return mount(MachineView, {
  37. global: {
  38. plugins: [createPinia()],
  39. stubs: {
  40. 'el-button': {
  41. template: '<button @click="$emit(\'click\')" :disabled="loading || disabled"><slot /></button>',
  42. props: ['type', 'icon', 'loading', 'plain', 'link', 'disabled']
  43. },
  44. 'el-table': {
  45. template: '<table class="el-table" data-id="machine-table"><slot /></table>',
  46. props: ['data', 'loading', 'border']
  47. },
  48. 'el-table-column': {
  49. template: '<td class="el-table-column"></td>',
  50. props: ['prop', 'label', 'width', 'align', 'type', 'fixed', 'minWidth', 'showOverflowTooltip']
  51. },
  52. 'el-tag': { template: '<span class="el-tag"><slot /></span>', props: ['type'] },
  53. 'el-link': {
  54. template: '<a class="el-link" @click="$emit(\'click\')"><slot /></a>',
  55. props: ['type']
  56. },
  57. 'el-pagination': {
  58. template: '<div class="el-pagination"></div>',
  59. props: ['currentPage', 'pageSize', 'pageSizes', 'total', 'layout', 'background']
  60. },
  61. 'el-dialog': {
  62. template:
  63. '<div v-if="modelValue" class="el-dialog" data-id="dialog-machine"><slot /><slot name="footer" /></div>',
  64. props: ['modelValue', 'title', 'width', 'destroyOnClose']
  65. },
  66. 'el-form': {
  67. template: '<form class="el-form" data-id="form-machine"><slot /></form>',
  68. props: ['model', 'rules', 'labelWidth'],
  69. methods: {
  70. validate(callback: Function) {
  71. callback(false)
  72. return Promise.resolve(false)
  73. },
  74. resetFields() {}
  75. }
  76. },
  77. 'el-form-item': { template: '<div class="el-form-item"><slot /></div>', props: ['label', 'prop'] },
  78. 'el-input': {
  79. template:
  80. '<input :value="modelValue" @input="$emit(\'update:modelValue\', $event.target.value)" :disabled="disabled" :placeholder="placeholder" />',
  81. props: ['modelValue', 'placeholder', 'disabled', 'type', 'rows']
  82. },
  83. 'el-switch': {
  84. template:
  85. '<input type="checkbox" :checked="modelValue" @change="$emit(\'update:modelValue\', $event.target.checked)" />',
  86. props: ['modelValue']
  87. },
  88. Plus: { template: '<span>Plus</span>' },
  89. Refresh: { template: '<span>Refresh</span>' },
  90. Edit: { template: '<span>Edit</span>' },
  91. Delete: { template: '<span>Delete</span>' }
  92. }
  93. }
  94. })
  95. }
  96. describe('页面渲染', () => {
  97. it('应该正确渲染机器管理页面', async () => {
  98. const wrapper = mountMachine()
  99. await flushPromises()
  100. expect(wrapper.find('.page-container').exists()).toBe(true)
  101. expect(wrapper.find('.table-actions').exists()).toBe(true)
  102. })
  103. it('应该显示新增机器按钮', async () => {
  104. const wrapper = mountMachine()
  105. await flushPromises()
  106. const addBtn = wrapper.findAll('button').find((btn) => btn.text().includes('新增机器'))
  107. expect(addBtn).toBeDefined()
  108. })
  109. it('应该显示刷新列表按钮', async () => {
  110. const wrapper = mountMachine()
  111. await flushPromises()
  112. const refreshBtn = wrapper.findAll('button').find((btn) => btn.text().includes('刷新列表'))
  113. expect(refreshBtn).toBeDefined()
  114. })
  115. it('应该显示数据表格', async () => {
  116. const wrapper = mountMachine()
  117. await flushPromises()
  118. expect(wrapper.find('.el-table').exists()).toBe(true)
  119. })
  120. it('应该显示分页组件', async () => {
  121. const wrapper = mountMachine()
  122. await flushPromises()
  123. expect(wrapper.find('.pagination-container').exists()).toBe(true)
  124. })
  125. })
  126. describe('数据加载', () => {
  127. it('页面加载时应该获取机器列表', async () => {
  128. mountMachine()
  129. await flushPromises()
  130. expect(mockListMachines).toHaveBeenCalled()
  131. })
  132. it('点击刷新按钮应该重新加载数据', async () => {
  133. const wrapper = mountMachine()
  134. await flushPromises()
  135. mockListMachines.mockClear()
  136. const refreshBtn = wrapper.findAll('button').find((btn) => btn.text().includes('刷新列表'))
  137. if (refreshBtn) {
  138. await refreshBtn.trigger('click')
  139. await flushPromises()
  140. expect(mockListMachines).toHaveBeenCalled()
  141. }
  142. })
  143. })
  144. describe('新增机器', () => {
  145. it('点击新增按钮应该打开弹窗', async () => {
  146. const wrapper = mountMachine()
  147. await flushPromises()
  148. const addBtn = wrapper.findAll('button').find((btn) => btn.text().includes('新增机器'))
  149. if (addBtn) {
  150. await addBtn.trigger('click')
  151. await flushPromises()
  152. expect(wrapper.find('.el-dialog').exists()).toBe(true)
  153. }
  154. })
  155. it('新增机器成功应该刷新列表并关闭弹窗', async () => {
  156. mockAddMachine.mockResolvedValue(wrapResponse({ id: 4, machineId: 'machine-004' }))
  157. const wrapper = mountMachine()
  158. await flushPromises()
  159. expect(mockListMachines).toHaveBeenCalled()
  160. })
  161. it('新增机器时应该显示正确的弹窗标题', async () => {
  162. const wrapper = mountMachine()
  163. await flushPromises()
  164. const addBtn = wrapper.findAll('button').find((btn) => btn.text().includes('新增机器'))
  165. if (addBtn) {
  166. await addBtn.trigger('click')
  167. await flushPromises()
  168. expect(wrapper.html()).toBeDefined()
  169. }
  170. })
  171. })
  172. describe('编辑机器', () => {
  173. it('编辑机器成功应该刷新列表', async () => {
  174. mockUpdateMachine.mockResolvedValue(wrapResponse(mockMachines[0]))
  175. const wrapper = mountMachine()
  176. await flushPromises()
  177. expect(mockListMachines).toHaveBeenCalled()
  178. })
  179. it('编辑时机器ID应该被禁用', async () => {
  180. const wrapper = mountMachine()
  181. await flushPromises()
  182. // 模拟点击编辑
  183. const editBtn = wrapper.findAll('button').find((btn) => btn.text().includes('编辑'))
  184. if (editBtn) {
  185. await editBtn.trigger('click')
  186. await flushPromises()
  187. expect(wrapper.html()).toBeDefined()
  188. }
  189. })
  190. })
  191. describe('删除机器', () => {
  192. it('删除机器成功应该刷新列表', async () => {
  193. const { ElMessage } = await import('element-plus')
  194. mockDeleteMachine.mockResolvedValue(wrapResponse(null))
  195. const wrapper = mountMachine()
  196. await flushPromises()
  197. const deleteBtn = wrapper.findAll('button').find((btn) => btn.text().includes('删除'))
  198. if (deleteBtn) {
  199. await deleteBtn.trigger('click')
  200. await flushPromises()
  201. expect(mockDeleteMachine).toHaveBeenCalled()
  202. }
  203. })
  204. it('删除确认取消时不应该调用删除 API', async () => {
  205. const { ElMessageBox } = await import('element-plus')
  206. ;(ElMessageBox.confirm as any).mockRejectedValue('cancel')
  207. const wrapper = mountMachine()
  208. await flushPromises()
  209. mockDeleteMachine.mockClear()
  210. const deleteBtn = wrapper.findAll('button').find((btn) => btn.text().includes('删除'))
  211. if (deleteBtn) {
  212. await deleteBtn.trigger('click')
  213. await flushPromises()
  214. }
  215. })
  216. })
  217. describe('分页功能', () => {
  218. it('应该正确计算总数', async () => {
  219. const wrapper = mountMachine()
  220. await flushPromises()
  221. expect(wrapper.find('.el-pagination').exists()).toBe(true)
  222. })
  223. it('切换每页条数应该重置页码为1', async () => {
  224. const wrapper = mountMachine()
  225. await flushPromises()
  226. expect(wrapper.html()).toBeDefined()
  227. })
  228. })
  229. describe('表单验证', () => {
  230. it('机器ID为空时不应该提交', async () => {
  231. const wrapper = mountMachine()
  232. await flushPromises()
  233. const addBtn = wrapper.findAll('button').find((btn) => btn.text().includes('新增机器'))
  234. if (addBtn) {
  235. await addBtn.trigger('click')
  236. await flushPromises()
  237. // 直接点击提交按钮
  238. const submitBtn = wrapper.findAll('button').find((btn) => btn.text().includes('确定'))
  239. if (submitBtn) {
  240. await submitBtn.trigger('click')
  241. await flushPromises()
  242. // 验证没有调用新增 API
  243. expect(mockAddMachine).not.toHaveBeenCalled()
  244. }
  245. }
  246. })
  247. })
  248. describe('弹窗交互', () => {
  249. it('点击取消按钮应该关闭弹窗', async () => {
  250. const wrapper = mountMachine()
  251. await flushPromises()
  252. const addBtn = wrapper.findAll('button').find((btn) => btn.text().includes('新增机器'))
  253. if (addBtn) {
  254. await addBtn.trigger('click')
  255. await flushPromises()
  256. expect(wrapper.find('.el-dialog').exists()).toBe(true)
  257. const cancelBtn = wrapper.findAll('button').find((btn) => btn.text().includes('取消'))
  258. if (cancelBtn) {
  259. await cancelBtn.trigger('click')
  260. await flushPromises()
  261. expect(wrapper.find('.el-dialog').exists()).toBe(false)
  262. }
  263. }
  264. })
  265. })
  266. describe('错误处理', () => {
  267. it('API 返回错误码应该正确处理', async () => {
  268. mockListMachines.mockResolvedValue(wrapResponse([], 500, '获取失败'))
  269. const wrapper = mountMachine()
  270. await flushPromises()
  271. expect(mockListMachines).toHaveBeenCalled()
  272. })
  273. it('新增返回错误应该处理', async () => {
  274. mockAddMachine.mockResolvedValue(wrapResponse(null, 400, '新增失败'))
  275. const wrapper = mountMachine()
  276. await flushPromises()
  277. expect(mockListMachines).toHaveBeenCalled()
  278. })
  279. })
  280. describe('启用状态', () => {
  281. it('编辑时应该显示启用状态开关', async () => {
  282. const wrapper = mountMachine()
  283. await flushPromises()
  284. const editBtn = wrapper.findAll('button').find((btn) => btn.text().includes('编辑'))
  285. if (editBtn) {
  286. await editBtn.trigger('click')
  287. await flushPromises()
  288. expect(wrapper.html()).toBeDefined()
  289. }
  290. })
  291. it('新增时不应该显示启用状态开关', async () => {
  292. const wrapper = mountMachine()
  293. await flushPromises()
  294. const addBtn = wrapper.findAll('button').find((btn) => btn.text().includes('新增机器'))
  295. if (addBtn) {
  296. await addBtn.trigger('click')
  297. await flushPromises()
  298. expect(wrapper.html()).toBeDefined()
  299. }
  300. })
  301. })
  302. })