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') test('loading evidence is mounted only after the current picking task is complete', () => { assert.match(page, /v-if="loadingStageReady" class="loading-evidence-section"/) 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\)/) assert.doesNotMatch(page, /class="loading-evidence-section" v-if="currentBox\.inboundId"/) }) test('loading evidence is not requested before picking completion', () => { assert.match(page, /if \(this\.loadingStageReady\) await this\.loadLoadingEvidence\(\)/) const loader = page.match(/async loadLoadingEvidence\(\) \{[\s\S]*?\n\t\t\t\},/)?.[0] || '' assert.match(loader, /if \(!this\.loadingStageReady/) 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/) }) test('AWAITING_REVIEW alone cannot mount or request evidence without a server loading version', () => { 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\(\)/) }) 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/) assert.match(page, /this\.currentBox\.inboundId = firstItem\.inboundId/) assert.doesNotMatch(page, /firstItem\.inboundId \|\| this\.currentBox\.inboundId/) assert.match(page, /this\.evidenceIdentity = this\.captureEvidenceIdentity\(\)/) }) test('evidence mutations defensively reject calls before picking completion', () => { assert.match(page, /requireEvidenceStage\(\)[\s\S]*?if \(!this\.pickingCompleted\)[\s\S]*?请先提交拣货完成[\s\S]*?if \(!this\.loadingStageReady\)/) for (const method of ['chooseEvidence', 'queueEvidenceUpload', 'retryEvidence', 'saveEvidence', 'saveSealNo', 'voidEvidencePhoto', 'completeLoadingTask']) { const body = page.match(new RegExp(`(?:async )?${method}\\([^)]*\\) \\{[\\s\\S]*?\\n\\t\\t\\t\\},`))?.[0] || '' assert.match(body, /if \(!this\.requireEvidenceStage\(\)\) return/, `${method} should guard the evidence stage`) } }) 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\(\)/) assert.match(page, /!this\.isEvidenceContextActive\(contextId, identity\) \|\| queue !== this\.evidenceQueue/) const loader = page.match(/async loadLoadingEvidence\(\) \{[\s\S]*?\n\t\t\t\},/)?.[0] || '' assert.equal((loader.match(/isEvidenceContextActive\(contextId, identity\)/g) || []).length, 2) }) 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/) 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 =/) }) 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`) 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`) } const complete = page.match(/async completeLoadingTask\([^)]*\) \{[\s\S]*?\n\t\t\t\},/)?.[0] || '' assert.match(complete, /finally \{[\s\S]*?isEvidenceContextActive\(contextId, identity\)[\s\S]*?this\.submitting = false/) }) test('completed server tasks restore evidence stage without being claimed again', () => { assert.match(page, /const remoteTask = normalizePickingTask\(task\)/) assert.match(page, /if \(!isPickingCompleteTask\(remoteTask\)\) \{[\s\S]*?claimContainerPickingTask/) assert.match(page, /if \(this\.loadingStageReady\) await this\.loadLoadingEvidence\(\)/) })