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' , () => {
assert . match ( page , /v-if="activeStage === 'evidence'" class="loading-evidence-section"/ )
2026-08-14 20:58:38 +08:00
assert . match ( page , /isPickingCompleteTask\(task\)[\s\S]*?AWAITING_REVIEW[\s\S]*?COMPLETED/ )
assert . match ( page , /pickingCompleted\(\)[\s\S]*?completedBoxes\[this\.currentBoxIndex\][\s\S]*?isPickingCompleteTask\(this\.remotePick\)/ )
2026-08-15 12:12:40 +08:00
assert . match ( page , /照片可先暂存/ )
2026-08-14 20:58:38 +08:00
})
test ( 'loading evidence is not requested before picking completion' , () => {
2026-08-15 09:57:53 +08:00
assert . match ( page , /if \(this\.loadingStageReady\) 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-15 09:57:53 +08:00
assert . match ( loader , /if \(!this\.loadingStageReady/ )
2026-08-14 20:58:38 +08:00
assert . match ( loader , /await getEvidence\(/ )
assert . match ( page , /this\.completedBoxes = \{[\s\S]*?this\.loadLoadingEvidence\(\)/ )
})
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-15 12:12:40 +08:00
test ( 'AWAITING_REVIEW alone cannot request or mutate server evidence without a server loading version' , () => {
2026-08-15 09:57:53 +08:00
assert . match ( page , /loadingStageReady\(\)[\s\S]*?this\.pickingCompleted[\s\S]*?this\.loadingVersion !== null[\s\S]*?Number\.isFinite/ )
assert . match ( page , /拣货已完成,装柜状态尚未同步,请刷新重试/ )
assert . match ( page , /if \(!this\.loadingStageReady \|\| !this\.evidenceIdentity/ )
assert . doesNotMatch ( page , /if \(this\.pickingCompleted\) await this\.loadLoadingEvidence\(\)/ )
})
2026-08-15 11:51:18 +08:00
test ( 'picking and evidence are separate operator-selected stages' , () => {
assert . match ( page , /class="stage-switch" role="tablist"/ )
assert . match ( page , /@tap="selectStage\('picking'\)"/ )
assert . match ( page , /@tap="selectStage\('evidence'\)"/ )
2026-08-15 12:12:40 +08:00
assert . match ( page , /selectStage\(stage\) \{[\s\S]*?this\.activeStage = stage/ )
assert . match ( page , /activeStage === 'picking'/ )
assert . doesNotMatch ( page , /activeStage === 'picking' \|\| !loadingStageReady/ )
assert . match ( page , /requireEvidenceCapture\(\)[\s\S]*?evidenceIdentity\?\.inboundId/ )
assert . match ( page , /if \(this\.loadingStageReady\) this\.queueEvidenceUpload/ )
assert . match ( page , /照片已暂存,装柜状态同步后自动上传/ )
assert . match ( page , /本地暂存,待自动上传/ )
2026-08-15 11:51:18 +08:00
})
2026-08-15 09:57:53 +08:00
test ( 'evidence identity is bound to task, inbound, loading plan and container booking' , () => {
assert . match ( page , /captureEvidenceIdentity\(\)[\s\S]*?taskId:[\s\S]*?inboundId:[\s\S]*?loadingPlanId:[\s\S]*?containerBookingId:/ )
assert . match ( page , /isSameEvidenceIdentity\(left, right\)[\s\S]*?taskId[\s\S]*?inboundId[\s\S]*?loadingPlanId[\s\S]*?containerBookingId/ )
2026-08-15 17:04:00 +08:00
assert . match ( page , /this\.currentBox\.inboundId = task\.inboundId/ )
assert . match ( page , /String\(detailTaskId \|\| ''\) !== String\(requestedTaskId\)/ )
assert . match ( page , /!task\.inboundId \|\| !task\.loadingPlanId \|\| !task\.containerBookingId/ )
assert . match ( page , /evidenceItem[\s\S]*?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-15 12:12:40 +08:00
test ( 'server evidence mutations remain closed before the loading stage is ready' , () => {
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 ] || ''
assert . match ( queue , /if \(!this\.loadingStageReady\) return/ )
for ( const method of [ 'retryEvidence' , 'saveEvidence' , 'voidEvidencePhoto' , 'completeLoadingTask' ]) {
2026-08-14 20:58:38 +08:00
const body = page . match ( new RegExp ( `(?:async )? ${ method } \\([^)]*\\) \\{[\\s\\S]*?\\n\\t\\t\\t\\},` )) ? .[ 0 ] || ''
2026-08-15 11:36:56 +08:00
assert . match ( body , /if \(!this\.requireEvidenceStage\(\)\)(?: return| \{)/ , ` ${ method } should guard the evidence stage` )
2026-08-14 20:58:38 +08:00
}
})
2026-08-15 12:12:40 +08:00
test ( 'refresh preserves local photos for the same evidence identity and flushes them after loading readiness' , () => {
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\)"/ )
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-15 12:33:33 +08:00
assert . match ( page , /item\.previewUrl \|\| getEvidencePreviewUrl\(identity\.inboundId, item\.id, this\.currentBox\.tenantId\)/ )
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\.loadingVersion = 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 [
[ 'saveEvidence' , 'upsertEvidence' ],
[ 'voidEvidencePhoto' , 'voidEvidence' ],
[ 'completeLoadingTask' , 'completeLoading' ]
]) {
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-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-15 09:57:53 +08:00
assert . match ( page , /if \(this\.loadingStageReady\) await this\.loadLoadingEvidence\(\)/ )
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' , () => {
assert . match ( page , /pickingOperationReady\(\)[\s\S]*?workflowStatus === 'IN_PROGRESS'[\s\S]*?remoteTaskId[\s\S]*?inboundId[\s\S]*?loadingPlanId[\s\S]*?containerBookingId/ )
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` )
}
})