index.spec.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  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', 'height'],
  47. methods: {
  48. clearSelection() {}
  49. }
  50. },
  51. 'el-table-column': {
  52. template: '<td class="el-table-column"></td>',
  53. props: ['prop', 'label', 'width', 'align', 'type', 'fixed', 'minWidth', 'showOverflowTooltip', 'sortable']
  54. },
  55. 'el-tag': { template: '<span class="el-tag"><slot /></span>', props: ['type'] },
  56. 'el-link': {
  57. template: '<a class="el-link" @click="$emit(\'click\')"><slot /></a>',
  58. props: ['type']
  59. },
  60. 'el-pagination': {
  61. template: '<div class="el-pagination"></div>',
  62. props: ['currentPage', 'pageSize', 'pageSizes', 'total', 'layout', 'background']
  63. },
  64. 'el-dialog': {
  65. template:
  66. '<div v-if="modelValue" class="el-dialog" data-id="dialog-machine"><slot /><slot name="footer" /></div>',
  67. props: ['modelValue', 'title', 'width', 'destroyOnClose']
  68. },
  69. 'el-form': {
  70. template: '<form class="el-form" data-id="form-machine"><slot /></form>',
  71. props: ['model', 'rules', 'labelWidth', 'inline'],
  72. methods: {
  73. validate(callback: Function) {
  74. callback(false)
  75. return Promise.resolve(false)
  76. },
  77. resetFields() {}
  78. }
  79. },
  80. 'el-form-item': { template: '<div class="el-form-item"><slot /></div>', props: ['label', 'prop'] },
  81. 'el-input': {
  82. template:
  83. '<input :value="modelValue" @input="$emit(\'update:modelValue\', $event.target.value)" :disabled="disabled" :placeholder="placeholder" />',
  84. props: ['modelValue', 'placeholder', 'disabled', 'type', 'rows', 'clearable']
  85. },
  86. 'el-select': {
  87. template:
  88. '<select :value="modelValue" @change="$emit(\'update:modelValue\', $event.target.value)"><slot /></select>',
  89. props: ['modelValue', 'placeholder', 'clearable']
  90. },
  91. 'el-option': {
  92. template: '<option :value="value">{{ label }}</option>',
  93. props: ['label', 'value']
  94. },
  95. 'el-date-picker': {
  96. template:
  97. '<input type="text" :value="modelValue" @input="$emit(\'update:modelValue\', $event.target.value)" />',
  98. props: [
  99. 'modelValue',
  100. 'type',
  101. 'rangeSeparator',
  102. 'startPlaceholder',
  103. 'endPlaceholder',
  104. 'valueFormat',
  105. 'clearable'
  106. ]
  107. },
  108. 'el-switch': {
  109. template:
  110. '<input type="checkbox" :checked="modelValue" @change="$emit(\'update:modelValue\', $event.target.checked)" />',
  111. props: ['modelValue']
  112. },
  113. Plus: { template: '<span>Plus</span>' },
  114. Refresh: { template: '<span>Refresh</span>' },
  115. Search: { template: '<span>Search</span>' },
  116. Edit: { template: '<span>Edit</span>' },
  117. Delete: { template: '<span>Delete</span>' }
  118. }
  119. }
  120. })
  121. }
  122. describe('页面渲染', () => {
  123. it('应该正确渲染机器管理页面', async () => {
  124. const wrapper = mountMachine()
  125. await flushPromises()
  126. expect(wrapper.find('.page-container').exists()).toBe(true)
  127. expect(wrapper.find('.search-form').exists()).toBe(true)
  128. })
  129. it('应该显示新增按钮', async () => {
  130. const wrapper = mountMachine()
  131. await flushPromises()
  132. const addBtn = wrapper.findAll('button').find((btn) => btn.text().includes('新增'))
  133. expect(addBtn).toBeDefined()
  134. })
  135. it('应该显示查询按钮', async () => {
  136. const wrapper = mountMachine()
  137. await flushPromises()
  138. const searchBtn = wrapper.findAll('button').find((btn) => btn.text().includes('查询'))
  139. expect(searchBtn).toBeDefined()
  140. })
  141. it('应该显示数据表格', async () => {
  142. const wrapper = mountMachine()
  143. await flushPromises()
  144. expect(wrapper.find('.el-table').exists()).toBe(true)
  145. })
  146. it('应该显示分页组件', async () => {
  147. const wrapper = mountMachine()
  148. await flushPromises()
  149. expect(wrapper.find('.pagination-container').exists()).toBe(true)
  150. })
  151. })
  152. describe('数据加载', () => {
  153. it('页面加载时应该获取机器列表', async () => {
  154. mountMachine()
  155. await flushPromises()
  156. expect(mockListMachines).toHaveBeenCalled()
  157. })
  158. it('应该显示查询和重置按钮', async () => {
  159. const wrapper = mountMachine()
  160. await flushPromises()
  161. const searchBtn = wrapper.findAll('button').find((btn) => btn.text().includes('查询'))
  162. const resetBtn = wrapper.findAll('button').find((btn) => btn.text().includes('重置'))
  163. expect(searchBtn).toBeDefined()
  164. expect(resetBtn).toBeDefined()
  165. })
  166. })
  167. describe('新增', () => {
  168. it('点击新增按钮应该打开弹窗', async () => {
  169. const wrapper = mountMachine()
  170. await flushPromises()
  171. const addBtn = wrapper.findAll('button').find((btn) => btn.text().includes('新增'))
  172. if (addBtn) {
  173. await addBtn.trigger('click')
  174. await flushPromises()
  175. expect(wrapper.find('.el-dialog').exists()).toBe(true)
  176. }
  177. })
  178. it('新增成功应该重置并关闭弹窗', async () => {
  179. mockAddMachine.mockResolvedValue(wrapResponse({ id: 4, machineId: 'machine-004' }))
  180. const wrapper = mountMachine()
  181. await flushPromises()
  182. expect(mockListMachines).toHaveBeenCalled()
  183. })
  184. it('新增时应该显示正确的弹窗标题', async () => {
  185. const wrapper = mountMachine()
  186. await flushPromises()
  187. const addBtn = wrapper.findAll('button').find((btn) => btn.text().includes('新增'))
  188. if (addBtn) {
  189. await addBtn.trigger('click')
  190. await flushPromises()
  191. expect(wrapper.html()).toBeDefined()
  192. }
  193. })
  194. })
  195. describe('编辑机器', () => {
  196. it('编辑机器成功应该重置', async () => {
  197. mockUpdateMachine.mockResolvedValue(wrapResponse(mockMachines[0]))
  198. const wrapper = mountMachine()
  199. await flushPromises()
  200. expect(mockListMachines).toHaveBeenCalled()
  201. })
  202. it('编辑时机器ID应该被禁用', async () => {
  203. const wrapper = mountMachine()
  204. await flushPromises()
  205. // 模拟点击编辑
  206. const editBtn = wrapper.findAll('button').find((btn) => btn.text().includes('编辑'))
  207. if (editBtn) {
  208. await editBtn.trigger('click')
  209. await flushPromises()
  210. expect(wrapper.html()).toBeDefined()
  211. }
  212. })
  213. })
  214. describe('删除机器', () => {
  215. it('删除机器成功应该重置', async () => {
  216. const { ElMessage } = await import('element-plus')
  217. mockDeleteMachine.mockResolvedValue(wrapResponse(null))
  218. const wrapper = mountMachine()
  219. await flushPromises()
  220. const deleteBtn = wrapper.findAll('button').find((btn) => btn.text().includes('删除'))
  221. if (deleteBtn) {
  222. await deleteBtn.trigger('click')
  223. await flushPromises()
  224. expect(mockDeleteMachine).toHaveBeenCalled()
  225. }
  226. })
  227. it('删除确认取消时不应该调用删除 API', async () => {
  228. const { ElMessageBox } = await import('element-plus')
  229. ;(ElMessageBox.confirm as any).mockRejectedValue('cancel')
  230. const wrapper = mountMachine()
  231. await flushPromises()
  232. mockDeleteMachine.mockClear()
  233. const deleteBtn = wrapper.findAll('button').find((btn) => btn.text().includes('删除'))
  234. if (deleteBtn) {
  235. await deleteBtn.trigger('click')
  236. await flushPromises()
  237. }
  238. })
  239. })
  240. describe('分页功能', () => {
  241. it('应该正确计算总数', async () => {
  242. const wrapper = mountMachine()
  243. await flushPromises()
  244. expect(wrapper.find('.el-pagination').exists()).toBe(true)
  245. })
  246. it('切换每页条数应该重置页码为1', async () => {
  247. const wrapper = mountMachine()
  248. await flushPromises()
  249. expect(wrapper.html()).toBeDefined()
  250. })
  251. })
  252. describe('表单验证', () => {
  253. it('机器ID为空时不应该提交', async () => {
  254. const wrapper = mountMachine()
  255. await flushPromises()
  256. const addBtn = wrapper.findAll('button').find((btn) => btn.text().includes('新增'))
  257. if (addBtn) {
  258. await addBtn.trigger('click')
  259. await flushPromises()
  260. // 直接点击提交按钮
  261. const submitBtn = wrapper.findAll('button').find((btn) => btn.text().includes('确定'))
  262. if (submitBtn) {
  263. await submitBtn.trigger('click')
  264. await flushPromises()
  265. // 验证没有调用新增 API
  266. expect(mockAddMachine).not.toHaveBeenCalled()
  267. }
  268. }
  269. })
  270. })
  271. describe('弹窗交互', () => {
  272. it('点击取消按钮应该关闭弹窗', async () => {
  273. const wrapper = mountMachine()
  274. await flushPromises()
  275. const addBtn = wrapper.findAll('button').find((btn) => btn.text().includes('新增'))
  276. if (addBtn) {
  277. await addBtn.trigger('click')
  278. await flushPromises()
  279. expect(wrapper.find('.el-dialog').exists()).toBe(true)
  280. const cancelBtn = wrapper.findAll('button').find((btn) => btn.text().includes('取消'))
  281. if (cancelBtn) {
  282. await cancelBtn.trigger('click')
  283. await flushPromises()
  284. expect(wrapper.find('.el-dialog').exists()).toBe(false)
  285. }
  286. }
  287. })
  288. })
  289. describe('错误处理', () => {
  290. it('API 返回错误码应该正确处理', async () => {
  291. mockListMachines.mockResolvedValue(wrapResponse([], 500, '获取失败'))
  292. const wrapper = mountMachine()
  293. await flushPromises()
  294. expect(mockListMachines).toHaveBeenCalled()
  295. })
  296. it('新增返回错误应该处理', async () => {
  297. mockAddMachine.mockResolvedValue(wrapResponse(null, 400, '新增失败'))
  298. const wrapper = mountMachine()
  299. await flushPromises()
  300. expect(mockListMachines).toHaveBeenCalled()
  301. })
  302. })
  303. describe('启用状态', () => {
  304. it('编辑时应该显示启用状态开关', async () => {
  305. const wrapper = mountMachine()
  306. await flushPromises()
  307. const editBtn = wrapper.findAll('button').find((btn) => btn.text().includes('编辑'))
  308. if (editBtn) {
  309. await editBtn.trigger('click')
  310. await flushPromises()
  311. expect(wrapper.html()).toBeDefined()
  312. }
  313. })
  314. it('新增时不应该显示启用状态开关', async () => {
  315. const wrapper = mountMachine()
  316. await flushPromises()
  317. const addBtn = wrapper.findAll('button').find((btn) => btn.text().includes('新增'))
  318. if (addBtn) {
  319. await addBtn.trigger('click')
  320. await flushPromises()
  321. expect(wrapper.html()).toBeDefined()
  322. }
  323. })
  324. })
  325. })