import assert from 'node:assert/strict' import { readFile } from 'node:fs/promises' import { compileScript, compileTemplate, parse } from '@vue/compiler-sfc' import * as vue from 'vue' import ts from 'typescript' // Run the real component setup with controlled confirmation and API dependencies. const filename = 'WaterWorkspace.vue' const source = await readFile( new URL('../src/views/water/modules/WaterWorkspace.vue', import.meta.url), 'utf8' ) const { descriptor, errors } = parse(source, { filename }) assert.deepEqual(errors, []) const script = compileScript(descriptor, { id: 'package-delete-test' }) const template = compileTemplate({ source: descriptor.template.content, filename, id: 'package-delete-test', compilerOptions: { bindingMetadata: script.bindings } }) assert.deepEqual(template.errors, []) const saves = [] const reads = [] const messages = [] const confirmations = [] let confirm = async () => {} let save = async () => ({}) let listing = async () => ({ data: [{ id: 2, name: '保留套餐' }], total: 1 }) const user = vue.reactive({ info: { id: 1, buttons: [] } }) const dependencies = { vue: { ...vue, onMounted: () => {} }, 'vue-router': { useRoute: () => ({ query: {} }), useRouter: () => ({ push: () => {} }) }, 'element-plus': { ElMessage: { success: (message) => messages.push(message), warning: () => {} }, ElMessageBox: { confirm: (...args) => { confirmations.push(args) return confirm() } } }, xlsx: {}, '@/store/modules/user': { useUserStore: () => user }, '@/api/water': { saveWater: async (...args) => { saves.push(args) return save() }, getWater: async (...args) => { reads.push(args) return listing() }, waterOptions: async () => [] } } function evaluate(code) { const { outputText } = ts.transpileModule(code, { compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.CommonJS } }) const exports = {} const require = (name) => { if (name in dependencies) return dependencies[name] if (name.endsWith('.vue') || name.endsWith('.scss')) return {} throw new Error(`Unexpected dependency: ${name}`) } new Function('require', 'exports', outputText)(require, exports) return exports } dependencies['./catalog'] = evaluate( await readFile(new URL('../src/views/water/modules/catalog.ts', import.meta.url), 'utf8') ) const component = evaluate(script.content).default const state = component.setup({ module: 'packages' }, { expose: () => {} }) const row = { id: '1', name: '待删除套餐' } confirm = async () => { throw new Error('cancel') } await state.removePackage(row) assert.equal(saves.length, 0) assert.equal(reads.length, 0) assert.equal(state.removingPackageId.value, undefined) assert.match(confirmations[0][0], /待删除套餐.*ID:1/) assert.equal(confirmations[0][2].cancelButtonText, '取消') let resolveConfirmation confirm = () => new Promise((resolve) => (resolveConfirmation = resolve)) state.preview.value = row const deleting = state.removePackage(row) assert.equal(saves.length, 0, 'no deletion before confirmation') const confirmationCount = confirmations.length await state.removePackage(row) assert.equal(confirmations.length, confirmationCount, 'repeated click does not open another dialog') resolveConfirmation() await deleting assert.deepEqual(saves, [['packages', { id: 1, action: 'delete' }]]) assert.equal(reads.length, 1) assert.equal(state.preview.value, undefined) assert.equal(messages.at(-1), '删除成功') assert.equal(state.removingPackageId.value, undefined) confirm = async () => {} state.page.value = 2 listing = async () => reads.at(-1)[1].page === 2 ? { data: [], total: 20 } : { data: [{ id: 2 }], total: 20 } await state.removePackage({ id: 21, name: '第二页最后一条' }) assert.deepEqual( reads.slice(-2).map(([, query]) => query.page), [2, 1] ) assert.equal(state.page.value, 1) assert.equal(state.rows.value[0].id, 2) const successCount = messages.length const readCount = reads.length save = async () => { throw new Error('API rejected') } await state.removePackage(row) assert.equal(reads.length, readCount, 'failed deletion does not refresh or remove a row') assert.equal(messages.length, successCount, 'failed deletion shows no success message') assert.equal(state.removingPackageId.value, undefined) user.info.id = 2 const saveCount = saves.length const dialogs = confirmations.length await state.removePackage(row) assert.equal(confirmations.length, dialogs, 'read-only operator cannot initiate deletion') assert.equal(saves.length, saveCount) console.log( 'PASS: component compilation, confirmation, cancellation, duplicate click, ID deletion, pagination, API failure and write permission.' )