From 433150dd0fb691805df164f9a804d19d82ba8e1c Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Tue, 1 Sep 2026 12:01:27 +0800 Subject: [PATCH] test(pda): cover picking demo workflow --- tests/picking-demo-readiness.test.mjs | 75 +++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 tests/picking-demo-readiness.test.mjs diff --git a/tests/picking-demo-readiness.test.mjs b/tests/picking-demo-readiness.test.mjs new file mode 100644 index 0000000..62d4c25 --- /dev/null +++ b/tests/picking-demo-readiness.test.mjs @@ -0,0 +1,75 @@ +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'/) +})