Files
pda-uniapp/tests/picking-demo-readiness.test.mjs
T

76 lines
5.1 KiB
JavaScript

import test from 'node:test'
import assert from 'node:assert/strict'
import { readFile } from 'node:fs/promises'
const read = (path) => readFile(new URL(path, import.meta.url), 'utf8')
const [page, tasks, api, http, completion, scan] = await Promise.all([
read('../pages/picking/picking.vue'),
read('../pages/tasks/tasks.vue'),
read('../api/pda-loading.js'),
read('../api/http.js'),
read('../utils/loading-completion-state.js'),
read('../utils/scan-broadcast.js')
])
test('demo claim supports the PDA broadcast and normalized manual input without duplicate picks', () => {
assert.match(page, /remoteTask\.workflowStatus === 'UNASSIGNED'[\s\S]*?claimContainerPickingTask\(this\.remoteTaskId, this\.assignmentVersion, operationId\)/)
assert.match(page, /this\.remotePick\.workflowStatus !== 'IN_PROGRESS'[\s\S]*?任务领取尚未完成/)
assert.match(scan, /android\.intent\.action\.SCANRESULT/)
assert.match(scan, /SCAN_BROADCAST_EXTRA = 'value'/)
assert.match(scan, /getStringExtra\(SCAN_BROADCAST_EXTRA\)/)
assert.match(page, /handleHardwareScan\(value\)[\s\S]*?processScannedValue\(value, 'broadcast'\)/)
assert.match(page, /handleManualSubmit\(value\)[\s\S]*?trim\(\)\.toUpperCase\(\)[\s\S]*?processScannedValue\(normalizedValue, 'manual'\)/)
assert.match(page, /result\.type === 'processed'[\s\S]*?重复扫描[\s\S]*?return/)
})
test('demo batch freezes inventory and submits PICK, SKIP and PARTIAL under one stable key', () => {
const submit = page.match(/async performSubmit\(incompleteReason = ''\) \{[\s\S]*?\n\t\t\t\},/)?.[0] || ''
for (const action of ['PICK', 'SKIP', 'PARTIAL']) assert.match(submit, new RegExp(`type: '${action}'`))
assert.match(submit, /const submitId = this\.submitId \|\| stableOperationId\('submit'/)
assert.match(submit, /this\.submitId = submitId/)
assert.match(submit, /const inventorySnapshot = await this\.refreshInventorySnapshot\(\)[\s\S]*?submitContainerPickingBatch/)
assert.match(api, /'Idempotency-Key': stableIdempotencyKey\(submitOperationId\)/)
assert.match(api, /inventorySnapshotToken, items/)
assert.match(page, /value >= this\.partialTargetItem\.qty[\s\S]*?部分拣货必须少于/)
assert.match(page, /value > available[\s\S]*?超过库存\/库位可用量/)
assert.match(page, /available < Number\(item\.qty \|\| 0\)[\s\S]*?库存\/库位可用量不足/)
})
test('demo session and network failures never turn into local success', () => {
assert.match(http, /requireOnline\(\)/)
assert.match(http, /let refreshPromise = null/)
assert.match(http, /session\.token && session\.token !== accessToken \? session\.token : await refreshAccessToken\(\)/)
assert.match(http, /if \(code !== '200'\)[\s\S]*?error\.requestId[\s\S]*?error\.traceId[\s\S]*?throw error/)
const submit = page.match(/async performSubmit\(incompleteReason = ''\) \{[\s\S]*?\n\t\t\t\},/)?.[0] || ''
const failedReturn = submit.indexOf("this.showToast('拣货完成未提交,请查看错误提示', 'error')")
const localFinalize = submit.indexOf('const result = submitStates')
assert.ok(failedReturn >= 0 && localFinalize > failedReturn, 'backend failure must return before local completion')
assert.match(submit, /网络失败,拣货完成未提交,请重试/)
})
test('demo evidence requires all five server-backed types and keeps seal upload explicit', () => {
for (const type of ['PHOTO_STEP_1', 'PHOTO_STEP_2', 'PHOTO_STEP_3', 'PHOTO_STEP_4', 'SEAL']) {
assert.match(api, new RegExp(`'${type}'`))
}
assert.match(page, /this\.evidenceSlots\.every\(\(slot\) => this\.validEvidenceEntries\(slot\.type\)\.length > 0\)/)
assert.match(page, /请填写封签号,保存后上传/)
assert.match(page, /sealSaving \? '上传中…' : '保存并上传'/)
assert.match(page, /await this\.queueEvidenceUpload\('SEAL', current\.localPath, String\(this\.sealNo\)\.trim\(\)\)/)
assert.match(api, /if \(code !== '200'\)[\s\S]*?throw error/)
})
test('demo completion reuses its command, restores processing, and refreshes the list on return', () => {
assert.match(page, /restoreLoadingCompletionAttempt\(this\.draftIdentity\(\)\)/)
assert.match(page, /const operationId = savedAttempt\?\.operationId \|\| stableOperationId\('complete'/)
assert.match(page, /saveLoadingCompletionAttempt\(this\.draftIdentity\(\), completionAttempt\)[\s\S]*?completeContainerPickingTask/)
assert.match(completion, /PROCESSING_STATUSES = new Set\(\['COMPLETING', 'PROCESSING'\]\)/)
assert.match(completion, /statuses\.some\(\(status\) => PROCESSING_STATUSES\.has\(status\)\)/)
assert.match(page, /phase === COMPLETION_PHASE\.PROCESSING[\s\S]*?后台处理中[\s\S]*?navigateBackOr\('\/pages\/tasks\/tasks'\)/)
assert.match(page, /shouldReconcileCompletion\(error\)[\s\S]*?提交结果待确认[\s\S]*?navigateBackOr\('\/pages\/tasks\/tasks'\)/)
assert.match(tasks, /onShow\(\)[\s\S]*?this\.loadTasks\(\)/)
assert.match(tasks, /status === 'PROCESSING'[\s\S]*?完成处理中/)
assert.match(tasks, /isTaskReady\(task\)[\s\S]*?\['UNASSIGNED', 'IN_PROGRESS', 'AWAITING_REVIEW', 'COMPLETION_FAILED'\]/)
assert.match(tasks, /isTaskVisible\(task\)[\s\S]*?\['UNASSIGNED', 'IN_PROGRESS', 'AWAITING_REVIEW', 'PROCESSING', 'COMPLETION_FAILED'\]/)
assert.doesNotMatch(tasks, /isTaskVisible\(task\)[\s\S]*?'COMPLETED'/)
})