test-water-package-delete.mjs 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. import assert from 'node:assert/strict'
  2. import { readFile } from 'node:fs/promises'
  3. import { compileScript, compileTemplate, parse } from '@vue/compiler-sfc'
  4. import * as vue from 'vue'
  5. import ts from 'typescript'
  6. // Run the real component setup with controlled confirmation and API dependencies.
  7. const filename = 'WaterWorkspace.vue'
  8. const source = await readFile(
  9. new URL('../src/views/water/modules/WaterWorkspace.vue', import.meta.url),
  10. 'utf8'
  11. )
  12. const { descriptor, errors } = parse(source, { filename })
  13. assert.deepEqual(errors, [])
  14. const script = compileScript(descriptor, { id: 'package-delete-test' })
  15. const template = compileTemplate({
  16. source: descriptor.template.content,
  17. filename,
  18. id: 'package-delete-test',
  19. compilerOptions: { bindingMetadata: script.bindings }
  20. })
  21. assert.deepEqual(template.errors, [])
  22. const saves = []
  23. const reads = []
  24. const messages = []
  25. const confirmations = []
  26. let confirm = async () => {}
  27. let save = async () => ({})
  28. let listing = async () => ({ data: [{ id: 2, name: '保留套餐' }], total: 1 })
  29. const user = vue.reactive({ info: { id: 1, buttons: [] } })
  30. const dependencies = {
  31. vue: { ...vue, onMounted: () => {} },
  32. 'vue-router': { useRoute: () => ({ query: {} }), useRouter: () => ({ push: () => {} }) },
  33. 'element-plus': {
  34. ElMessage: { success: (message) => messages.push(message), warning: () => {} },
  35. ElMessageBox: {
  36. confirm: (...args) => {
  37. confirmations.push(args)
  38. return confirm()
  39. }
  40. }
  41. },
  42. xlsx: {},
  43. '@/store/modules/user': { useUserStore: () => user },
  44. '@/api/water': {
  45. saveWater: async (...args) => {
  46. saves.push(args)
  47. return save()
  48. },
  49. getWater: async (...args) => {
  50. reads.push(args)
  51. return listing()
  52. },
  53. waterOptions: async () => []
  54. }
  55. }
  56. function evaluate(code) {
  57. const { outputText } = ts.transpileModule(code, {
  58. compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.CommonJS }
  59. })
  60. const exports = {}
  61. const require = (name) => {
  62. if (name in dependencies) return dependencies[name]
  63. if (name.endsWith('.vue') || name.endsWith('.scss')) return {}
  64. throw new Error(`Unexpected dependency: ${name}`)
  65. }
  66. new Function('require', 'exports', outputText)(require, exports)
  67. return exports
  68. }
  69. dependencies['./catalog'] = evaluate(
  70. await readFile(new URL('../src/views/water/modules/catalog.ts', import.meta.url), 'utf8')
  71. )
  72. const component = evaluate(script.content).default
  73. const state = component.setup({ module: 'packages' }, { expose: () => {} })
  74. const row = { id: '1', name: '待删除套餐' }
  75. confirm = async () => {
  76. throw new Error('cancel')
  77. }
  78. await state.removePackage(row)
  79. assert.equal(saves.length, 0)
  80. assert.equal(reads.length, 0)
  81. assert.equal(state.removingPackageId.value, undefined)
  82. assert.match(confirmations[0][0], /待删除套餐.*ID:1/)
  83. assert.equal(confirmations[0][2].cancelButtonText, '取消')
  84. let resolveConfirmation
  85. confirm = () => new Promise((resolve) => (resolveConfirmation = resolve))
  86. state.preview.value = row
  87. const deleting = state.removePackage(row)
  88. assert.equal(saves.length, 0, 'no deletion before confirmation')
  89. const confirmationCount = confirmations.length
  90. await state.removePackage(row)
  91. assert.equal(confirmations.length, confirmationCount, 'repeated click does not open another dialog')
  92. resolveConfirmation()
  93. await deleting
  94. assert.deepEqual(saves, [['packages', { id: 1, action: 'delete' }]])
  95. assert.equal(reads.length, 1)
  96. assert.equal(state.preview.value, undefined)
  97. assert.equal(messages.at(-1), '删除成功')
  98. assert.equal(state.removingPackageId.value, undefined)
  99. confirm = async () => {}
  100. state.page.value = 2
  101. listing = async () =>
  102. reads.at(-1)[1].page === 2 ? { data: [], total: 20 } : { data: [{ id: 2 }], total: 20 }
  103. await state.removePackage({ id: 21, name: '第二页最后一条' })
  104. assert.deepEqual(
  105. reads.slice(-2).map(([, query]) => query.page),
  106. [2, 1]
  107. )
  108. assert.equal(state.page.value, 1)
  109. assert.equal(state.rows.value[0].id, 2)
  110. const successCount = messages.length
  111. const readCount = reads.length
  112. save = async () => {
  113. throw new Error('API rejected')
  114. }
  115. await state.removePackage(row)
  116. assert.equal(reads.length, readCount, 'failed deletion does not refresh or remove a row')
  117. assert.equal(messages.length, successCount, 'failed deletion shows no success message')
  118. assert.equal(state.removingPackageId.value, undefined)
  119. user.info.id = 2
  120. const saveCount = saves.length
  121. const dialogs = confirmations.length
  122. await state.removePackage(row)
  123. assert.equal(confirmations.length, dialogs, 'read-only operator cannot initiate deletion')
  124. assert.equal(saves.length, saveCount)
  125. console.log(
  126. 'PASS: component compilation, confirmation, cancellation, duplicate click, ID deletion, pagination, API failure and write permission.'
  127. )