feat(approval): add task workflow and waiver decisions
This commit is contained in:
127
web/tests/approval-task-queue-state.test.mjs
Normal file
127
web/tests/approval-task-queue-state.test.mjs
Normal file
@@ -0,0 +1,127 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
|
||||
import {
|
||||
createApprovalTaskSelectionState,
|
||||
reconcileApprovalTaskSelectionAfterBatch,
|
||||
reconcileApprovalTaskSelectionWithPage,
|
||||
resolveApprovalTaskSelectionMeta,
|
||||
resolveCurrentPageApprovalSelection,
|
||||
syncApprovalTaskSelectionFilters,
|
||||
toggleApprovalTaskSelection,
|
||||
toggleCurrentPageApprovalSelection
|
||||
} from '../src/views/scripts/approvalTaskQueueState.js'
|
||||
|
||||
function task(id, overrides = {}) {
|
||||
return {
|
||||
id,
|
||||
status: 'pending',
|
||||
version: 1,
|
||||
canAct: true,
|
||||
availableActions: ['approve'],
|
||||
batchEligible: true,
|
||||
...overrides
|
||||
}
|
||||
}
|
||||
|
||||
test('任务可选性优先使用服务端权限和批量阻断原因', () => {
|
||||
assert.deepEqual(resolveApprovalTaskSelectionMeta(task('task-001')), {
|
||||
taskId: 'task-001',
|
||||
selectable: true,
|
||||
reason: ''
|
||||
})
|
||||
assert.equal(
|
||||
resolveApprovalTaskSelectionMeta(task('task-002', {
|
||||
canAct: false,
|
||||
readOnlyReason: '该任务已委托给李审批'
|
||||
})).reason,
|
||||
'该任务已委托给李审批'
|
||||
)
|
||||
assert.equal(
|
||||
resolveApprovalTaskSelectionMeta(task('task-003', {
|
||||
batchEligible: false,
|
||||
batchBlockReasons: ['存在开放高风险', '审批意见必须单独填写']
|
||||
})).reason,
|
||||
'存在开放高风险;审批意见必须单独填写'
|
||||
)
|
||||
assert.equal(
|
||||
resolveApprovalTaskSelectionMeta(task('task-004', {
|
||||
availableActions: ['delegate']
|
||||
})).selectable,
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
test('当前页全选和半选只处理服务端允许批量审批的任务', () => {
|
||||
const rows = [
|
||||
task('task-001'),
|
||||
task('task-002'),
|
||||
task('task-003', { batchEligible: false, batchBlockReasons: ['需单独核对'] })
|
||||
]
|
||||
let selected = toggleApprovalTaskSelection([], rows[0], true)
|
||||
let pageState = resolveCurrentPageApprovalSelection(rows, selected)
|
||||
assert.deepEqual(selected, ['task-001'])
|
||||
assert.equal(pageState.indeterminate, true)
|
||||
assert.equal(pageState.allSelected, false)
|
||||
|
||||
selected = toggleCurrentPageApprovalSelection(selected, rows, true)
|
||||
pageState = resolveCurrentPageApprovalSelection(rows, selected)
|
||||
assert.deepEqual(selected, ['task-001', 'task-002'])
|
||||
assert.equal(pageState.allSelected, true)
|
||||
assert.equal(pageState.eligibleCount, 2)
|
||||
|
||||
selected = toggleCurrentPageApprovalSelection(selected, rows, false)
|
||||
assert.deepEqual(selected, [])
|
||||
})
|
||||
|
||||
test('刷新后任务失去权限时从当前页选择中移除', () => {
|
||||
const selected = ['off-page-task', 'task-001', 'task-002']
|
||||
const rows = [
|
||||
task('task-001'),
|
||||
task('task-002', { canAct: false, readOnlyReason: '任务已转交' })
|
||||
]
|
||||
assert.deepEqual(
|
||||
reconcileApprovalTaskSelectionWithPage(selected, rows),
|
||||
['off-page-task', 'task-001']
|
||||
)
|
||||
})
|
||||
|
||||
test('筛选变化清空选择但翻页和每页条数变化不清空', () => {
|
||||
let state = createApprovalTaskSelectionState({
|
||||
selectedTaskIds: ['task-001', 'task-002'],
|
||||
filters: { status: 'pending', riskLevel: 'high', page: 1, pageSize: 20 }
|
||||
})
|
||||
state = syncApprovalTaskSelectionFilters(state, {
|
||||
status: 'pending',
|
||||
riskLevel: 'high',
|
||||
page: 2,
|
||||
pageSize: 50
|
||||
})
|
||||
assert.deepEqual(state.selectedTaskIds, ['task-001', 'task-002'])
|
||||
|
||||
state = syncApprovalTaskSelectionFilters(state, {
|
||||
status: 'pending',
|
||||
riskLevel: 'medium',
|
||||
page: 1,
|
||||
pageSize: 20
|
||||
})
|
||||
assert.deepEqual(state.selectedTaskIds, [])
|
||||
})
|
||||
|
||||
test('批量部分成功只移除成功和幂等重放项,失败项继续保留', () => {
|
||||
const selected = ['task-001', 'task-002', 'task-003', 'task-004', 'task-005', 'task-unreported']
|
||||
const result = {
|
||||
status: 'partial',
|
||||
items: [
|
||||
{ taskId: 'task-001', status: 'succeeded' },
|
||||
{ taskId: 'task-002', status: 'replayed' },
|
||||
{ taskId: 'task-003', status: 'blocked' },
|
||||
{ taskId: 'task-004', status: 'conflict' },
|
||||
{ taskId: 'task-005', status: 'forbidden' }
|
||||
]
|
||||
}
|
||||
assert.deepEqual(
|
||||
reconcileApprovalTaskSelectionAfterBatch(selected, result),
|
||||
['task-003', 'task-004', 'task-005', 'task-unreported']
|
||||
)
|
||||
})
|
||||
Reference in New Issue
Block a user