修复:完善基础资料页异步与提交保护
This commit is contained in:
@@ -564,6 +564,8 @@ test('六个基础资料页面逐页满足 API、权限、分页和安全删除
|
||||
await t.test(contract.relativePath, async () => {
|
||||
const source = await readFile(path.join(frontendRoot, contract.relativePath), 'utf8')
|
||||
const permissionPrefix = `master:${resource}`
|
||||
const suffix = resource[0].toUpperCase() + resource.slice(1)
|
||||
const idKey = `${resource}Id`
|
||||
const importedModules = [...source.matchAll(/from\s*['"]@\/api\/master\/([a-z]+)['"]/g)]
|
||||
.map((match) => match[1])
|
||||
|
||||
@@ -608,17 +610,62 @@ test('六个基础资料页面逐页满足 API、权限、分页和安全删除
|
||||
assert.doesNotMatch(source, />\s*(?:导入|导出)\s*<\/el-button>/)
|
||||
assert.doesNotMatch(source, /\bhandle(?:Import|Export)\b|proxy\.download\(/)
|
||||
|
||||
assert.match(source, /<el-table-column\b[^>]*type="selection"[^>]*:selectable="isRowSelectable"/)
|
||||
assert.match(source, /function isRowSelectable\(row\)\s*{\s*return row\.canRemove === true;?\s*}/)
|
||||
assert.match(source, /ids\.value\s*=\s*selection\s*\.filter\(\(item\) => item\.canRemove === true\)/)
|
||||
assert.match(source, /let listGeneration = 0;/)
|
||||
assert.match(source, /function isCurrentListRequest\(listContext\)\s*{\s*return listContext\.generation === listGeneration;?\s*}/)
|
||||
const listSource = source.slice(source.indexOf('function getList()'), source.indexOf('function reset()'))
|
||||
const staleListGuard = 'if \\(!isCurrentListRequest\\(listContext\\)\\) \\{\\s*return;\\s*\\}'
|
||||
assert.match(listSource, /const listContext = \{\s*generation: \+\+listGeneration,\s*query: \{ \.\.\.queryParams\.value \}\s*};/)
|
||||
assert.match(listSource, new RegExp(`list${suffix}\\(listContext\\.query\\)`))
|
||||
assert.match(listSource, new RegExp(`\\.then\\(\\(response\\) => \\{\\s*${staleListGuard}`))
|
||||
assert.match(listSource, new RegExp(`\\.catch\\(\\(\\) => \\{\\s*${staleListGuard}`))
|
||||
assert.match(listSource, new RegExp(`\\.finally\\(\\(\\) => \\{\\s*${staleListGuard}`))
|
||||
|
||||
assert.match(source, /let detailGeneration = 0;/)
|
||||
assert.match(source, /function isCurrentDetailRequest\(detailContext\)\s*{\s*return detailContext\.generation === detailGeneration;?\s*}/)
|
||||
assert.match(source, /function invalidateDetailRequest\(\)\s*{\s*detailGeneration \+= 1;\s*detailLoading\.value = false;\s*}/)
|
||||
const detailSource = source.slice(source.indexOf('function handleUpdate('), source.indexOf('function afterSubmit('))
|
||||
const staleDetailGuard = 'if \\(!isCurrentDetailRequest\\(detailContext\\)\\) \\{\\s*return;\\s*\\}'
|
||||
assert.match(detailSource, new RegExp(`const detailContext = \\{\\s*generation: \\+\\+detailGeneration,\\s*resourceId: ${idKey}\\s*};`))
|
||||
assert.match(detailSource, new RegExp(`get${suffix}\\(detailContext\\.resourceId\\)`))
|
||||
assert.match(detailSource, new RegExp(`\\.then\\(\\(response\\) => \\{\\s*${staleDetailGuard}`))
|
||||
assert.match(detailSource, new RegExp(`\\.catch\\(\\(\\) => \\{\\s*${staleDetailGuard}`))
|
||||
assert.match(detailSource, new RegExp(`\\.finally\\(\\(\\) => \\{\\s*${staleDetailGuard}`))
|
||||
assert.match(source, /function handleAdd\(\)\s*{\s*invalidateDetailRequest\(\)/)
|
||||
assert.match(source, /function cancel\(\)\s*{\s*if \(submitLoading\.value\) return;\s*invalidateDetailRequest\(\)/)
|
||||
|
||||
const dialogTag = source.match(/<el-dialog\b[^>]*>/)?.[0] || ''
|
||||
assert.match(dialogTag, /:close-on-click-modal="!submitLoading"/)
|
||||
assert.match(dialogTag, /:close-on-press-escape="!submitLoading"/)
|
||||
assert.match(dialogTag, /:show-close="!submitLoading"/)
|
||||
assert.match(dialogTag, /:before-close="handleDialogBeforeClose"/)
|
||||
assert.match(source, /function handleDialogBeforeClose\(done\)\s*{\s*if \(submitLoading\.value\) return;[\s\S]*?invalidateDetailRequest\(\);[\s\S]*?done\(\);\s*}/)
|
||||
|
||||
const selectionColumn = source.match(/<el-table-column\b[^>]*type="selection"[^>]*\/>/)?.[0] || ''
|
||||
assert.ok(selectionColumn, `${resource} 缺少选择列`)
|
||||
assert.doesNotMatch(selectionColumn, /:selectable=/)
|
||||
assert.doesNotMatch(source, /function isRowSelectable\(/)
|
||||
assert.match(source, /const selectedIds = ref\(\[\]\)/)
|
||||
assert.match(source, /const removableIds = ref\(\[\]\)/)
|
||||
const selectionSource = source.slice(source.indexOf('function handleSelectionChange('), source.indexOf('function handleAdd('))
|
||||
assert.match(selectionSource, new RegExp(`selectedIds\\.value = selection\\.map\\(\\(item\\) => item\\.${idKey}\\)`))
|
||||
assert.match(selectionSource, new RegExp(`removableIds\\.value = selection\\s*\\.filter\\(\\(item\\) => item\\.canRemove === true\\)\\s*\\.map\\(\\(item\\) => item\\.${idKey}\\)`))
|
||||
assert.match(selectionSource, /single\.value = selectedIds\.value\.length !== 1/)
|
||||
assert.match(selectionSource, /multiple\.value = removableIds\.value\.length === 0/)
|
||||
assert.match(detailSource, new RegExp(`const ${idKey} = row\\.${idKey} \\|\\| selectedIds\\.value\\[0\\]`))
|
||||
assert.match(source, new RegExp(`const targetIds = row\\?\\.${idKey} \\? \\(row\\.canRemove === true \\? \\[row\\.${idKey}\\] : \\[\\]\\) : removableIds\\.value`))
|
||||
const rowDeleteButtons = extractButtonBlocks(source, 'handleDelete')
|
||||
.filter((button) => button.includes('handleDelete(scope.row)'))
|
||||
assert.ok(rowDeleteButtons.length > 0, `${resource} 缺少行删除按钮`)
|
||||
for (const button of rowDeleteButtons) {
|
||||
assert.match(button, /v-if="scope\.row\.canRemove === true"/)
|
||||
}
|
||||
const rowUpdateButtons = extractButtonBlocks(source, 'handleUpdate')
|
||||
.filter((button) => button.includes('handleUpdate(scope.row)'))
|
||||
assert.ok(rowUpdateButtons.length > 0, `${resource} 缺少行修改按钮`)
|
||||
for (const button of rowUpdateButtons) {
|
||||
assert.match(button, /:disabled="detailLoading"/)
|
||||
}
|
||||
|
||||
const suffix = resource[0].toUpperCase() + resource.slice(1)
|
||||
assert.deepEqual(extractCallObjectKeys(source, `add${suffix}`), contract.addPayloadKeys)
|
||||
assert.deepEqual(extractCallObjectKeys(source, `update${suffix}`), contract.editPayloadKeys)
|
||||
|
||||
@@ -659,10 +706,12 @@ test('六个基础资料页面逐页满足 API、权限、分页和安全删除
|
||||
assert.match(printHandler, /['"]load['"][\s\S]*?printWindow\.print\(\)/)
|
||||
assert.match(printHandler, /let closeMonitorId;/)
|
||||
assert.match(printHandler, /let cleanupFinished = false;/)
|
||||
assert.match(printHandler, /const cleanupPrint = \(\) => \{\s*if \(cleanupFinished\) return;\s*cleanupFinished = true;/)
|
||||
assert.doesNotMatch(printHandler, /if \(cleanupFinished\) return;/)
|
||||
assert.match(printHandler, /const cleanupPrint = \(\) => \{[\s\S]*?window\.clearInterval\(closeMonitorId\)[\s\S]*?URL\.revokeObjectURL\(objectUrl\)[\s\S]*?if \(!cleanupFinished\) \{\s*cleanupFinished = true;\s*setPrinting\(row\.palletId, false\);\s*}/)
|
||||
assert.match(printHandler, /window\.clearInterval\(closeMonitorId\)/)
|
||||
assert.equal((printHandler.match(/URL\.revokeObjectURL\(objectUrl\)/g) || []).length, 1)
|
||||
assert.equal((printHandler.match(/setPrinting\(row\.palletId, false\)/g) || []).length, 1)
|
||||
assert.match(printHandler, /objectUrl = URL\.createObjectURL\([^;]+\);\s*if \(cleanupFinished \|\| printWindow\.closed\) \{\s*cleanupPrint\(\);\s*return;\s*}/)
|
||||
assert.match(printHandler, /closeMonitorId = window\.setInterval\(\(\) => \{\s*if \(printWindow\.closed\) cleanupPrint\(\);\s*}, \d+\)/)
|
||||
assert.ok(
|
||||
printHandler.indexOf('window.setInterval(') < printHandler.indexOf('getPalletQrcode('),
|
||||
@@ -713,7 +762,16 @@ test('业务用户分配组件使用固定映射并允许空数组完整替换',
|
||||
assert.match(source, /nextTick\([\s\S]*?toggleRowSelection\(row, selectedUserIds\.value\.includes\(row\.userId\)\)/)
|
||||
assert.match(source, /@row-click="handleRowClick"/)
|
||||
assert.match(source, /const userIds = \[\.\.\.new Set\(selectedUserIds\.value\.filter\(\(userId\) => Number\.isInteger\(userId\) && userId > 0\)\)\]/)
|
||||
assert.match(source, /assignUsers\(resourceId\.value, \{ userIds }\)/)
|
||||
assert.match(source, /let submitGeneration = 0;/)
|
||||
assert.match(source, /function isCurrentSubmit\(submitContext\)\s*{[\s\S]*?submitContext\.generation === submitGeneration[\s\S]*?submitContext\.resourceType === resourceType\.value[\s\S]*?submitContext\.resourceId === resourceId\.value[\s\S]*?}/)
|
||||
const submitSource = source.slice(source.indexOf('function submitAssignment()'), source.indexOf('defineExpose('))
|
||||
const staleSubmitGuard = 'if \\(!isCurrentSubmit\\(submitContext\\)\\) \\{\\s*return;\\s*\\}'
|
||||
assert.match(submitSource, /const submitContext = \{\s*generation: \+\+submitGeneration,\s*resourceType: resourceType\.value,\s*resourceId: resourceId\.value\s*};/)
|
||||
assert.match(submitSource, /resourceApiMap\[submitContext\.resourceType\]\.assignUsers\(submitContext\.resourceId, \{ userIds }\)/)
|
||||
assert.match(submitSource, new RegExp(`\\.then\\(\\(response\\) => \\{\\s*${staleSubmitGuard}`))
|
||||
assert.match(submitSource, new RegExp(`\\.catch\\(\\(\\) => \\{\\s*${staleSubmitGuard}`))
|
||||
assert.match(submitSource, new RegExp(`\\.finally\\(\\(\\) => \\{\\s*${staleSubmitGuard}`))
|
||||
assert.match(resetDialogSource, /submitGeneration \+= 1;/)
|
||||
assert.doesNotMatch(source, /请选择要分配的用户|userIds\.length\s*===?\s*0/)
|
||||
assert.match(source, /:loading="submitLoading"/)
|
||||
assert.match(source, /const loadFailed = ref\(false\)/)
|
||||
@@ -726,6 +784,13 @@ test('业务用户分配组件使用固定映射并允许空数组完整替换',
|
||||
assert.deepEqual(submitPermissions, ['master:site:assign', 'master:warehouse:assign'])
|
||||
assert.match(source, /proxy\.\$modal\.msgSuccess\(response\.msg \|\| ['"]分配成功['"]\)/)
|
||||
assert.match(source, /visible\.value = false[\s\S]*?emit\(['"]ok['"]\)/)
|
||||
const dialogTag = source.match(/<el-dialog\b[^>]*>/)?.[0] || ''
|
||||
assert.match(dialogTag, /:close-on-click-modal="!submitLoading"/)
|
||||
assert.match(dialogTag, /:close-on-press-escape="!submitLoading"/)
|
||||
assert.match(dialogTag, /:show-close="!submitLoading"/)
|
||||
assert.match(dialogTag, /:before-close="handleDialogBeforeClose"/)
|
||||
assert.match(source, /function handleDialogBeforeClose\(done\)\s*{\s*if \(submitLoading\.value\) return;[\s\S]*?resetDialogState\(\);[\s\S]*?done\(\);\s*}/)
|
||||
assert.match(source, /function closeDialog\(\)\s*{\s*if \(submitLoading\.value\) return;/)
|
||||
assert.match(source, /<el-alert\b[^>]*v-if="loadError"/)
|
||||
assert.match(source, /width="min\(720px, 92vw\)"/)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user