2026-08-14 20:58:38 +08:00
import test from 'node:test'
import assert from 'node:assert/strict'
import { readFile } from 'node:fs/promises'
const page = await readFile ( new URL ( '../pages/picking/picking.vue' , import . meta . url ), 'utf8' )
2026-08-15 12:12:40 +08:00
test ( 'picking and photo workspaces are independently selectable' , () => {
2026-08-27 13:50:44 +08:00
assert . match ( page , /v-if="activeStage === 'evidence'" class="loading-evidence-section"/ )
assert . match ( page , /selectStage\(stage\) \{[\s\S]*?this\.activeStage = stage/ )
assert . doesNotMatch ( page , /:disabled="!loadingStageReady"/ )
2026-08-14 20:58:38 +08:00
assert . match ( page , /isPickingCompleteTask\(task\)[\s\S]*?AWAITING_REVIEW[\s\S]*?COMPLETED/ )
2026-08-17 15:55:17 +08:00
assert . match ( page , /pickingCompleted\(\)[\s\S]*?this\.remoteTaskId \|\| this\.currentBox\.taskId[\s\S]*?if \(!this\.remotePick\) return false[\s\S]*?hasServerPending[\s\S]*?isPickingCompleteTask\(this\.remotePick\) && !hasServerPending/ )
2026-08-27 13:50:44 +08:00
assert . doesNotMatch ( page , /任务增量更新后|evidence-stage-hint/ )
2026-08-14 20:58:38 +08:00
})
2026-08-17 15:55:17 +08:00
test ( 'a server task never inherits a completed marker from the previously opened local cabinet' , () => {
assert . match ( page , /pickingCompleted\(\) \{[\s\S]*?this\.remoteTaskId \|\| this\.currentBox\.taskId[\s\S]*?if \(!this\.remotePick\) return false[\s\S]*?hasServerPending/ )
assert . match ( page , /if \(options\.taskId\) \{[\s\S]*?this\.completedBoxes = \{\}[\s\S]*?this\.boxes = \[/ )
2026-08-17 16:17:25 +08:00
assert . match ( page , /:disabled="submitting \|\| \(!pickingCompleted && !summary\.canSubmit\)"/ )
assert . match ( page , /submitButtonText\(\)[\s\S]*?return this\.pickingCompleted \? '继续装柜取证'/ )
assert . match ( page , /submitStatusHint\(\)[\s\S]*?if \(this\.pickingCompleted\) return '拣货已完成,请继续装柜取证'/ )
const submit = page . match ( /async submitPicking\(\) \{[\s\S]*?\n\t\t\t\},/ ) ? .[ 0 ] || ''
2026-08-17 15:55:17 +08:00
assert . doesNotMatch ( submit , /completedBoxes/ )
2026-08-27 14:30:26 +08:00
assert . match ( page , /v-else-if="pickingOperationReady" class="pick-item-actions processed-actions"/ )
assert . doesNotMatch ( page , /v-else-if="!(?:pickingCompleted|completedBoxes\[currentBoxIndex\])"/ )
})
test ( 'picking completion leads to current cabinet evidence instead of an all-complete summary' , () => {
assert . match ( page , /装柜照片已拍 \{\{ evidenceCapturedTypeCount \}\}\/\{\{ evidenceSlots\.length \}\} 类/ )
assert . match ( page , /evidenceNextButtonText\(\)[\s\S]*?'查看装柜取证'[\s\S]*?'去拍照'/ )
assert . match ( page , /continueToEvidence\(\)[\s\S]*?this\.activeStage = 'evidence'[\s\S]*?this\.scrollToTop\(\)/ )
assert . doesNotMatch ( page , /查看全部完成|allCompletePopup|nextBox\(\)/ )
2026-08-17 15:55:17 +08:00
})
2026-08-31 18:08:43 +08:00
test ( 'loading evidence is requested by task id as soon as the task is claimed' , () => {
2026-08-28 16:53:58 +08:00
assert . match ( page , /this\.evidenceIdentity = this\.captureEvidenceIdentity\(\)[\s\S]*?await this\.loadLoadingEvidence\(\)/ )
2026-08-14 20:58:38 +08:00
const loader = page . match ( /async loadLoadingEvidence\(\) \{[\s\S]*?\n\t\t\t\},/ ) ? .[ 0 ] || ''
2026-08-31 18:08:43 +08:00
assert . match ( loader , /if \(!this\.evidenceIdentity\?\.taskId/ )
2026-08-28 16:53:58 +08:00
assert . doesNotMatch ( loader , /if \(!this\.loadingStageReady/ )
2026-08-14 20:58:38 +08:00
assert . match ( loader , /await getEvidence\(/ )
})
test ( 'operators receive actionable refresh guidance without internal version wording' , () => {
assert . match ( page , /状态尚未同步,请刷新重试/ )
assert . doesNotMatch ( page , /缺少拣货版本号|提供装柜版本号/ )
const complete = page . match ( /async completeLoadingTask\(\) \{[\s\S]*?\n\t\t\t\},/ ) ? .[ 0 ] || ''
assert . match ( complete , /if \(!this\.requireEvidenceStage\(\)\) return/ )
2026-08-17 09:41:16 +08:00
assert . match ( complete , /completeContainerPickingTask\(this\.remoteTaskId/ )
assert . match ( complete , /expectedTaskVersion/ )
assert . doesNotMatch ( complete , /completeLoading\(inboundId/ )
2026-08-14 20:58:38 +08:00
})
2026-08-31 18:08:43 +08:00
test ( 'AWAITING_REVIEW uses the canonical task version for final completion' , () => {
assert . match ( page , /loadingStageReady\(\)[\s\S]*?this\.pickingCompleted[\s\S]*?this\.evidenceIdentity\.taskId[\s\S]*?this\.evidenceIdentity\.warehouseId[\s\S]*?this\.pickingVersion !== null[\s\S]*?Number\.isFinite/ )
2026-08-15 09:57:53 +08:00
assert . match ( page , /拣货已完成,装柜状态尚未同步,请刷新重试/ )
2026-08-28 16:53:58 +08:00
assert . match ( page , /async completeLoadingTask\([^)]*\) \{[\s\S]*?requireEvidenceStage/ )
assert . match ( page , /requireEvidenceMutation\(\)[\s\S]*?requireEvidenceCapture/ )
2026-08-15 09:57:53 +08:00
})
2026-08-27 13:50:44 +08:00
test ( 'dynamic pending items do not block switching between picking and evidence' , () => {
2026-08-15 11:51:18 +08:00
assert . match ( page , /class="stage-switch" role="tablist"/ )
assert . match ( page , /@tap="selectStage\('picking'\)"/ )
assert . match ( page , /@tap="selectStage\('evidence'\)"/ )
2026-08-27 13:50:44 +08:00
assert . doesNotMatch ( page , /stage === 'evidence' && !this\.loadingStageReady/ )
assert . doesNotMatch ( page , /请先完成新增待拣明细/ )
2026-08-15 12:12:40 +08:00
assert . match ( page , /activeStage === 'picking'/ )
2026-08-17 15:41:50 +08:00
assert . match ( page , /:disabled="submitting \|\| !loadingStageReady \|\| pickingVersion === null/ )
})
test ( 'incremental refresh merges new pending items without overwriting submitted server results' , () => {
const restore = page . match ( /restoreRemoteDraft\(\) \{[\s\S]*?\n\t\t\t\},/ ) ? .[ 0 ] || ''
assert . match ( restore , /const serverStates = \{ \.\.\.this\.itemStates \}/ )
assert . match ( restore , /serverStates\[id\] !== ITEM_STATUS\.PENDING/ )
assert . match ( restore , /status === ITEM_STATUS\.PARTIAL[\s\S]*?partial >= Number\(item\?\.qty/ )
assert . match ( restore , /this\.remotePick && !isPickingCompleteTask\(this\.remotePick\)[\s\S]*?delete completedBoxes\[this\.currentBoxIndex\]/ )
assert . match ( page , /const previousTaskVersion = this\.pickingVersion/ )
assert . match ( page , /const previousItemIds = new Set/ )
assert . match ( page , /previousEvidenceState[\s\S]*?isSameEvidenceIdentity\(previousEvidenceState\.identity, this\.evidenceIdentity\)[\s\S]*?this\.evidenceFiles = \{ \.\.\.this\.evidenceFiles, \.\.\.previousEvidenceState\.files \}/ )
2026-08-27 13:50:44 +08:00
assert . match ( page , /if \(!this\.pickingCompleted\)[\s\S]*?delete completedBoxes\[this\.currentBoxIndex\]/ )
assert . doesNotMatch ( page , /delete completedBoxes\[this\.currentBoxIndex\][\s\S]{0,160}?this\.activeStage = 'picking'/ )
2026-08-17 15:41:50 +08:00
assert . match ( page , /refresh\.incremental[\s\S]*?addedItemIds/ )
})
test ( 'incremental batch submits only current server-pending items and refreshes stale inventory conflicts' , () => {
const submit = page . match ( /async performSubmit\([^)]*\) \{[\s\S]*?\n\t\t\t\},/ ) ? .[ 0 ] || ''
assert . match ( submit , /item\.itemStatus && !\['PENDING', 'PICKING'\]\.includes\(item\.itemStatus\)\) return null/ )
assert . match ( submit , /const versionConflict = this\.isTaskConflict\(error\)/ )
assert . match ( submit , /await this\.refreshAfterTaskConflict\(conflictMessage\)/ )
assert . match ( submit , /const inventorySnapshot = await this\.refreshInventorySnapshot\(\)/ )
assert . match ( page , /refreshAfterTaskConflict\(message\)[\s\S]*?this\.submitId = ''[\s\S]*?await this\.syncRemoteTask\(\)[\s\S]*?await this\.refreshInventorySnapshot\(\)/ )
assert . match ( page , /refreshInventorySnapshot\(\)[\s\S]*?getPickingInventorySnapshot\(this\.remoteTaskId, warehouseId\)[\s\S]*?String\(snapshot\.version\) !== String\(this\.pickingVersion\)[\s\S]*?error\.code = 'A0409'/ )
assert . match ( page , /this\.pickingVersion = this\.remotePick\.pickingVersion \?\? this\.remotePick\.version/ )
})
2026-08-17 16:17:25 +08:00
test ( 'completed detail overrides stale local state and an empty batch refreshes into evidence instead of submitting' , () => {
assert . match ( page , /if \(item\.itemStatus === 'PICKED'\) serverStates\[item\.id\] = ITEM_STATUS\.PICKED/ )
assert . match ( page , /if \(!this\.pickingCompleted\) this\.restoreRemoteDraft\(\)/ )
2026-08-28 16:53:58 +08:00
assert . match ( page , /await this\.loadLoadingEvidence\(\)[\s\S]*?if \(this\.loadingStageReady\) this\.activeStage = 'evidence'/ )
2026-08-17 16:17:25 +08:00
const submit = page . match ( /async submitPicking\(\) \{[\s\S]*?\n\t\t\t\},/ ) ? .[ 0 ] || ''
assert . match ( submit , /if \(this\.pickingCompleted\)[\s\S]*?await this\.syncRemoteTask\(\)[\s\S]*?this\.activeStage = 'evidence'[\s\S]*?return/ )
const perform = page . match ( /async performSubmit\([^)]*\) \{[\s\S]*?\n\t\t\t\},/ ) ? .[ 0 ] || ''
assert . match ( perform , /if \(!actions\.length\) \{[\s\S]*?submit\.empty-refresh[\s\S]*?await this\.syncRemoteTask\(\)[\s\S]*?return/ )
assert . ok ( perform . indexOf ( 'if (!actions.length)' ) < perform . indexOf ( 'submitContainerPickingBatch(' ))
})
2026-08-31 13:43:18 +08:00
test ( 'successful batch submission automatically refreshes canonical item status before completion gates' , () => {
const perform = page . match ( /async performSubmit\([^)]*\) \{[\s\S]*?\n\t\t\t\},/ ) ? .[ 0 ] || ''
const submitIndex = perform . indexOf ( 'await submitContainerPickingBatch(' )
const refreshIndex = perform . indexOf ( 'await this.syncRemoteTask()' , submitIndex )
const gateIndex = perform . indexOf ( 'if (!this.pickingCompleted)' , refreshIndex )
assert . ok ( submitIndex >= 0 && refreshIndex > submitIndex , 'canonical detail should refresh after the accepted batch' )
assert . ok ( gateIndex > refreshIndex , 'completion should be evaluated only after the automatic refresh' )
assert . match ( perform , /拣货已提交,正在刷新最新状态/ )
assert . match ( perform , /拣货已提交,但最新任务状态尚未同步,请点击刷新任务/ )
assert . match ( perform , /if \(!this\.pickingCompleted\)[\s\S]*?return/ )
})
2026-08-17 15:41:50 +08:00
test ( 'final completion preserves backend gate errors and refreshes after an atomic A0409' , () => {
const complete = page . match ( /async completeLoadingTask\(\) \{[\s\S]*?\n\t\t\t\},/ ) ? .[ 0 ] || ''
2026-08-30 17:36:57 +08:00
assert . match ( complete , /this\.isTaskConflict\(error\)[\s\S]*?await this\.refreshAfterTaskConflict\(sanitizeUserMessage\(error\.message, '订舱、装柜关联或证据状态已变化,请刷新后重试'\)\)/ )
2026-08-17 15:55:17 +08:00
assert . doesNotMatch ( complete , /拖车|trucking/i )
2026-08-15 11:51:18 +08:00
})
2026-08-28 16:53:58 +08:00
test ( 'seal photo upload waits for an explicit seal-number save without waiting for picking completion' , () => {
2026-08-31 18:08:43 +08:00
const chooser = page . match ( /chooseEvidence\(slot\) \{[\s\S]*?\n\t\t\t\},/ ) ? .[ 0 ] || ''
2026-09-01 11:38:45 +08:00
assert . match ( chooser , /slot\.type === 'SEAL'[\s\S]*?照片已拍摄,请输入封签号并保存上传[\s\S]*?return/ )
2026-08-31 18:08:43 +08:00
assert . match ( page , /async saveSealNo\(\)[\s\S]*?await this\.queueEvidenceUpload\('SEAL', current\.localPath, String\(this\.sealNo\)\.trim\(\)\)/ )
2026-08-28 16:53:58 +08:00
const saver = page . match ( /async saveSealNo\(\) \{[\s\S]*?\n\t\t\t\},/ ) ? .[ 0 ] || ''
assert . doesNotMatch ( saver , /if \(!this\.loadingStageReady\)/ )
2026-08-31 18:08:43 +08:00
assert . match ( page , /sealReadyToSave\(\)[\s\S]*?hasPendingLocalEvidence\('SEAL'\)/ )
2026-09-01 11:38:45 +08:00
assert . match ( page , /slot\.type === 'SEAL' \? '请填写封签号,保存后上传' : '照片待自动上传'/ )
assert . match ( page , /带锁码照上传成功,封签号已保存/ )
2026-08-17 19:52:01 +08:00
})
2026-08-31 18:08:43 +08:00
test ( 'cabinet evidence identity is task-scoped and never selects an inbound owner' , () => {
const capture = page . match ( /captureEvidenceIdentity\(\) \{[\s\S]*?\n\t\t\t\},/ ) ? .[ 0 ] || ''
const same = page . match ( /isSameEvidenceIdentity\(left, right\) \{[\s\S]*?\n\t\t\t\},/ ) ? .[ 0 ] || ''
assert . match ( capture , /taskId: this\.remoteTaskId \|\| this\.currentBox\.taskId/ )
assert . match ( capture , /warehouseId: this\.currentBox\.warehouseId/ )
assert . doesNotMatch ( capture , /inboundId|loadingPlanId|containerBookingId/ )
assert . match ( same , /left\?\.taskId[\s\S]*?right\?\.taskId/ )
assert . doesNotMatch ( same , /inboundId|loadingPlanId|containerBookingId/ )
2026-08-15 17:04:00 +08:00
assert . match ( page , /String\(detailTaskId \|\| ''\) !== String\(requestedTaskId\)/ )
2026-08-31 18:08:43 +08:00
assert . match ( page , /!task\.loadingPlanId \|\| !task\.containerBookingId/ )
assert . doesNotMatch ( page , /const evidenceItem|String\(task\.inboundId\)/ )
2026-08-15 10:03:05 +08:00
assert . match ( page , /this\.evidenceIdentity = this\.captureEvidenceIdentity\(\)/ )
2026-08-15 09:57:53 +08:00
})
2026-08-17 13:24:58 +08:00
test ( 'merged or non-active cabinet details are rejected before claim and clear stale local state' , () => {
const sync = page . match ( /async syncRemoteTask\(\) \{[\s\S]*?\n\t\t\t\},/ ) ? .[ 0 ] || ''
assert . match ( sync , /if \(this\.isMergedContainerRecord\(task\)\)[\s\S]*?this\.clearMergedTask\(task, previousIdentity\)[\s\S]*?return[\s\S]*?task\.recordStatus !== 'ACTIVE'[\s\S]*?claimContainerPickingTask/ )
const clear = page . match ( /clearMergedTask\(task, previousIdentity\) \{[\s\S]*?\n\t\t\t\},/ ) ? .[ 0 ] || ''
assert . match ( clear , /taskId: previousIdentity\?\.taskId \?\? task\?\.taskId/ )
assert . match ( clear , /loadingPlanId: previousIdentity\?\.loadingPlanId \?\? task\?\.loadingPlanId/ )
assert . match ( clear , /containerBookingId: previousIdentity\?\.containerBookingId \?\? task\?\.containerBookingId/ )
assert . match ( clear , /clearPickingDraft\(staleIdentity\)/ )
assert . match ( clear , /this\.evidenceDrafts = Object\.fromEntries/ )
assert . match ( clear , /this\.evidencePreviewCache = Object\.fromEntries/ )
2026-08-31 18:08:43 +08:00
for ( const reset of [ 'remoteLoading = false' , 'submitting = false' , 'remoteTaskId = null' , 'scanHistory = []' , 'currentBox.containerBookingId = null' , 'currentBox.currentContainerNo = null' , 'resetEvidenceState()' ]) assert . match ( clear , new RegExp ( reset . replace ( /[.()[\]]/g , '\\$&' )))
2026-08-17 13:24:58 +08:00
})
2026-08-28 16:53:58 +08:00
test ( 'photos upload, bind, reload and void before picking completion' , () => {
2026-08-15 09:57:53 +08:00
assert . match ( page , /requireEvidenceStage\(\)[\s\S]*?if \(!this\.pickingCompleted\)[\s\S]*?请先提交拣货完成[\s\S]*?if \(!this\.loadingStageReady\)/ )
2026-08-15 12:12:40 +08:00
const queue = page . match ( /async queueEvidenceUpload\([^)]*\) \{[\s\S]*?\n\t\t\t\},/ ) ? .[ 0 ] || ''
2026-08-31 18:08:43 +08:00
assert . match ( queue , /await queue\.flush\(\)[\s\S]*?this\.applyEvidenceResponse\(latest\.result, type, path, identity\)/ )
2026-08-28 16:53:58 +08:00
assert . doesNotMatch ( queue , /if \(!this\.loadingStageReady\)/ )
2026-08-27 13:50:44 +08:00
assert . match ( page , /chooseEvidence\(slot\)[\s\S]*?this\.queueEvidenceUpload\(slot\.type, path\)/ )
const retry = page . match ( /async retryEvidence\([^)]*\) \{[\s\S]*?\n\t\t\t\},/ ) ? .[ 0 ] || ''
assert . match ( retry , /if \(!this\.requireEvidenceCapture\(\)\) return/ )
2026-08-31 18:08:43 +08:00
assert . match ( retry , /await queue\.flush\(\)[\s\S]*?this\.applyEvidenceResponse\(latest\.result, type, item\.localPath, identity\)/ )
2026-08-28 16:53:58 +08:00
assert . doesNotMatch ( retry , /if \(!this\.loadingStageReady\)/ )
2026-08-31 18:08:43 +08:00
assert . match ( page , /async loadLoadingEvidence\(\) \{[\s\S]*?!this\.evidenceIdentity\?\.taskId[\s\S]*?!this\.evidenceIdentity\?\.warehouseId[\s\S]*?getEvidence\(identity\.taskId, identity\.warehouseId\)/ )
2026-08-28 16:53:58 +08:00
assert . match ( page , /await this\.loadLoadingEvidence\(\)[\s\S]*?if \(this\.loadingStageReady\) this\.activeStage = 'evidence'/ )
assert . match ( page , /async voidEvidencePhoto\([^)]*\) \{[\s\S]*?if \(!this\.requireEvidenceMutation\(\)\) return/ )
assert . match ( page , /async completeLoadingTask\([^)]*\) \{[\s\S]*?if \(!this\.requireEvidenceStage\(\)\) return/ )
assert . doesNotMatch ( page , /照片已上传,拣货完成后自动保存/ )
2026-08-14 20:58:38 +08:00
})
2026-08-28 16:53:58 +08:00
test ( 'refresh preserves local photos and flushes them as soon as evidence identity is ready' , () => {
2026-08-15 12:12:40 +08:00
assert . match ( page , /this\.rememberEvidenceDraft\(\)/ )
assert . match ( page , /this\.restoreEvidenceDraft\(\)/ )
assert . match ( page , /async flushPendingEvidence\(\)[\s\S]*?queueEvidenceUpload/ )
assert . match ( page , /const localFiles = Object\.fromEntries/ )
})
test ( 'evidence photos render thumbnails and support full-screen preview' , () => {
assert . match ( page , /class="evidence-thumbnail" :src="evidencePreviewSource\(photo\)"/ )
2026-08-27 13:50:44 +08:00
assert . match ( page , /class="work-content" :class="\{ 'evidence-active': activeStage === 'evidence' \}"/ )
2026-08-17 17:49:08 +08:00
assert . match ( page , /class="evidence-photo-grid"/ )
assert . match ( page , /class="evidence-count"/ )
2026-08-15 12:12:40 +08:00
assert . match ( page , /@tap="previewEvidencePhoto\(photo, slot\.type\)"/ )
2026-08-15 12:33:33 +08:00
assert . match ( page , /evidencePreviewSource\(photo\)[\s\S]*?localPath[\s\S]*?fileUrl/ )
2026-08-15 12:12:40 +08:00
assert . match ( page , /previewEvidencePhoto\(photo, type\)[\s\S]*?uni\.previewImage\([\s\S]*?current[\s\S]*?urls/ )
assert . match ( page , /cacheEvidencePreview\(fileId, localPath/ )
assert . match ( page , /withCachedEvidencePreview\(photo[\s\S]*?evidencePreviewCache/ )
2026-08-31 18:08:43 +08:00
assert . match ( page , /item\.previewUrl \|\| getEvidencePreviewUrl\(identity\.taskId, item\.id, identity\.warehouseId\)/ )
2026-08-15 12:33:33 +08:00
assert . match ( page , /['"]list\.loaded['"]/ )
assert . match ( page , /loadEvidencePreviews\(contextId, identity\)[\s\S]*?downloadEvidencePreview\(item\.previewUrl\)/ )
assert . match ( page , /['"]preview\.downloaded['"]/ )
2026-08-15 12:12:40 +08:00
assert . match ( page , /已上传并保存/ )
})
2026-08-14 20:58:38 +08:00
test ( 'switching cabinets invalidates old sync and evidence upload state' , () => {
const selector = page . match ( /selectBox\(index\) \{[\s\S]*?\n\t\t\t\},/ ) ? .[ 0 ] || ''
assert . match ( selector , /this\.taskRequestId \+= 1/ )
assert . match ( selector , /this\.pickingVersion = null/ )
assert . match ( selector , /this\.submitting = false/ )
assert . match ( selector , /this\.resetEvidenceState\(\)/ )
2026-08-15 10:03:05 +08:00
assert . match ( page , /!this\.isEvidenceContextActive\(contextId, identity\) \|\| queue !== this\.evidenceQueue/ )
2026-08-14 20:58:38 +08:00
const loader = page . match ( /async loadLoadingEvidence\(\) \{[\s\S]*?\n\t\t\t\},/ ) ? .[ 0 ] || ''
2026-08-15 12:33:33 +08:00
assert . ok (( loader . match ( /isEvidenceContextActive\(contextId, identity\)/g ) || []). length >= 2 )
2026-08-14 20:58:38 +08:00
})
test ( 'camera callbacks cannot append evidence after switching cabinets' , () => {
const chooser = page . match ( /chooseEvidence\(slot\) \{[\s\S]*?\n\t\t\t\},/ ) ? .[ 0 ] || ''
assert . match ( chooser , /const contextId = this\.evidenceContextId/ )
2026-08-15 09:57:53 +08:00
assert . match ( chooser , /const identity = \{ \.\.\.this\.evidenceIdentity \}/ )
assert . equal (( chooser . match ( /isEvidenceContextActive\(contextId, identity\)/g ) || []). length , 2 )
assert . match ( chooser , /isEvidenceContextActive\(contextId, identity\)[\s\S]*?this\.evidenceFiles =/ )
2026-08-14 20:58:38 +08:00
})
2026-08-15 11:36:56 +08:00
test ( 'returning from the camera does not refresh and invalidate the active evidence context' , () => {
assert . match ( page , /cameraPicking: false/ )
assert . match ( page , /onShow\(\) \{[\s\S]*?if \(this\.cameraPicking\)[\s\S]*?choose\.resume[\s\S]*?return[\s\S]*?this\.syncRemoteTask\(\)/ )
const chooser = page . match ( /chooseEvidence\(slot\) \{[\s\S]*?\n\t\t\t\},/ ) ? .[ 0 ] || ''
assert . match ( chooser , /this\.cameraPicking = true/ )
assert . match ( chooser , /success: \(result\) => \{[\s\S]*?this\.cameraPicking = false/ )
assert . match ( chooser , /fail: \(error\) => \{[\s\S]*?this\.cameraPicking = false/ )
})
2026-08-15 17:04:00 +08:00
test ( 'returning from an image preview does not refresh the picking task' , () => {
assert . match ( page , /evidencePreviewOpening: false, evidencePreviewReturning: false/ )
assert . match ( page , /onShow\(\) \{[\s\S]*?if \(this\.evidencePreviewReturning\)[\s\S]*?preview\.resume[\s\S]*?return[\s\S]*?this\.syncRemoteTask\(\)/ )
assert . match ( page , /onHide\(\) \{[\s\S]*?if \(this\.evidencePreviewOpening\)[\s\S]*?this\.evidencePreviewReturning = true/ )
const preview = page . match ( /previewEvidencePhoto\(photo, type\) \{[\s\S]*?\n\t\t\t\},/ ) ? .[ 0 ] || ''
assert . match ( preview , /this\.evidencePreviewOpening = true/ )
assert . match ( preview , /uni\.previewImage\(\{ current, urls, fail:/ )
})
2026-08-14 20:58:38 +08:00
test ( 'evidence save, void and loading completion ignore stale cabinet responses' , () => {
for ( const [ method , awaitedCall ] of [
[ 'voidEvidencePhoto' , 'voidEvidence' ],
2026-08-17 09:41:16 +08:00
[ 'completeLoadingTask' , 'completeContainerPickingTask' ]
2026-08-14 20:58:38 +08:00
]) {
const body = page . match ( new RegExp ( `async ${ method } \\([^)]*\\) \\{[\\s\\S]*?\\n\\t\\t\\t\\},` )) ? .[ 0 ] || ''
assert . match ( body , /const contextId = this\.evidenceContextId/ , ` ${ method } should capture the evidence context` )
2026-08-15 09:57:53 +08:00
assert . match ( body , /const identity = \{ \.\.\.this\.evidenceIdentity \}/ , ` ${ method } should capture stable evidence identity` )
assert . match ( body , new RegExp ( `await ${ awaitedCall } \\([\\s\\S]*?if \\(!this\\.isEvidenceContextActive\\(contextId, identity\\)\\) return` ), ` ${ method } should guard the success writeback` )
assert . ok (( body . match ( /isEvidenceContextActive\(contextId, identity\)/g ) || []). length >= 2 , ` ${ method } should guard success and error writebacks` )
2026-08-14 20:58:38 +08:00
}
const complete = page . match ( /async completeLoadingTask\([^)]*\) \{[\s\S]*?\n\t\t\t\},/ ) ? .[ 0 ] || ''
2026-08-15 09:57:53 +08:00
assert . match ( complete , /finally \{[\s\S]*?isEvidenceContextActive\(contextId, identity\)[\s\S]*?this\.submitting = false/ )
2026-08-14 20:58:38 +08:00
})
test ( 'completed server tasks restore evidence stage without being claimed again' , () => {
assert . match ( page , /const remoteTask = normalizePickingTask\(task\)/ )
2026-08-17 16:17:25 +08:00
assert . match ( page , /remoteTask\.workflowStatus !== 'AWAITING_REVIEW' && this\.assignmentVersion === null/ )
2026-08-15 17:04:00 +08:00
assert . match ( page , /if \(remoteTask\.workflowStatus === 'UNASSIGNED'\) \{[\s\S]*?claimContainerPickingTask/ )
assert . match ( page , /else if \(!isPickingCompleteTask\(remoteTask\)\)/ )
2026-08-28 16:53:58 +08:00
assert . match ( page , /await this\.loadLoadingEvidence\(\)[\s\S]*?if \(this\.loadingStageReady\) this\.activeStage = 'evidence'/ )
2026-08-14 20:58:38 +08:00
})
2026-08-15 17:04:00 +08:00
test ( 'picking mutations stay closed until the canonical task has been claimed' , () => {
2026-08-31 18:08:43 +08:00
assert . match ( page , /pickingOperationReady\(\)[\s\S]*?workflowStatus === 'IN_PROGRESS'[\s\S]*?remoteTaskId[\s\S]*?loadingPlanId[\s\S]*?containerBookingId/ )
2026-08-15 17:04:00 +08:00
assert . match ( page , /requirePickingOperation\(\)[\s\S]*?任务尚未领取完成/ )
for ( const method of [ 'processScannedValue' , 'pickItem' , 'openSkip' , 'openPartial' , 'submitPicking' ]) {
const body = page . match ( new RegExp ( ` ${ method } \\([^)]*\\) \\{[\\s\\S]*?\\n\\t\\t\\t\\},` )) ? .[ 0 ] || ''
assert . match ( body , /if \(!this\.requirePickingOperation\(\)\) return/ , ` ${ method } should wait for claim` )
}
})
2026-08-24 20:47:13 +08:00
test ( 'completion shows a Chinese server failure with a request correlation id' , () => {
assert . match ( page , /remoteFailureMessage\(error, fallback = '操作失败'\)/ )
2026-08-30 17:36:57 +08:00
assert . match ( page , /formatApiError\(error, fallback\)/ )
2026-08-31 17:10:46 +08:00
assert . match ( page , /import \{ containerReference, formatApiError, sanitizeUserMessage \}/ )
2026-08-24 20:47:13 +08:00
assert . match ( page , /requestIdFromServer: error\.requestId/ )
assert . match ( page , /this\.remoteFailureMessage\(error, '装柜完成失败'\)/ )
})