Files
pda-overseas-uniapp/tests/outbound-lifecycle.test.mjs
T

84 lines
5.3 KiB
JavaScript

import test from 'node:test'
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
const source = readFileSync(new URL('../src/pages/outbound/outbound.vue', import.meta.url), 'utf8').match(/<script>([\s\S]*?)<\/script>/)[1]
const deferred = () => { let resolve; const promise = new Promise(done => { resolve = done }); return { promise, resolve } }
function page(overrides = {}) {
const calls = []
const deps = { PortalHeader: {}, ScanTargetInput: {}, session: { currentUser: {} }, fulfillment: { outbound: { packages: 1, currentTaskNo: 'OLD-TASK' } },
updateFulfillmentDraft() {}, fulfillmentIdempotencyKey: () => ({ value: 'key', signature: 'signature' }), releaseFulfillmentKey() {},
playErrorSound() {}, playSuccessSound() {}, pickOutboundPallet: async () => calls.push('pick'), completeOutboundTask: async () => calls.push('complete'),
getOutboundTask: async () => ({ taskNo: 'TASK', status: 'COMPLETED', items: [] }), normalizeCargoDiscardTask: value => value,
...overrides }
const definition = new Function(...Object.keys(deps), source.replace(/^\s*import .*$/gm, '').replace('export default', 'return'))(...Object.values(deps))
const vm = definition.data()
for (const [name, fn] of Object.entries(definition.methods)) vm[name] = fn.bind(vm)
for (const [name, fn] of Object.entries(definition.computed)) Object.defineProperty(vm, name, { get: () => fn.call(vm) })
return { vm, calls }
}
test('completed and cancelled tasks cannot submit pallet picking or task completion', async () => {
for (const status of ['COMPLETED', 'CANCELLED']) {
const { vm, calls } = page()
vm.current = { taskNo: 'TASK', status, items: [{ palletNo: 'PALLET', plannedPackages: 1, pickedPackages: 1, positions: [] }] }
await vm.handleScan('PALLET'); await vm.submitPalletPick('PALLET'); await vm.completeTask()
assert.deepEqual(calls, [])
}
})
test('pending and in-progress tasks still submit exactly once while busy', async () => {
for (const status of ['PENDING', 'IN_PROGRESS']) {
const pending = deferred(); let submits = 0
const { vm } = page({ pickOutboundPallet: async () => { submits++; await pending.promise } })
vm.current = { taskNo: 'TASK', status, items: [{ palletNo: 'PALLET', plannedPackages: 1, pickedPackages: 0, positions: [] }] }
const first = vm.handleScan('PALLET'); const second = vm.handleScan('PALLET')
pending.resolve(); await Promise.all([first, second]); assert.equal(submits, 1)
}
})
test('failed new pallet lookup clears the previous selectable pallet and preserves retry code', async () => {
const { vm } = page({ resolveCargoDiscardPallet: async () => { throw new Error('offline') } })
vm.discardCurrent = { discardNo: 'DISCARD' }; vm.discardTask = { palletNo: 'OLD', status: 'PENDING', skus: [] }
await vm.resolveDiscardPallet('NEW')
assert.equal(vm.discardTask, null); assert.equal(vm.canConfirmDiscard, false)
assert.equal(vm.discardPalletCode, 'NEW'); assert.equal(vm.error.message, 'offline'); assert.equal(vm.discardLoading, false)
})
test('discard pallet scans do not overlap and cannot leave the previous pallet ready to submit', async () => {
const pending = deferred(); let resolves = 0
const { vm } = page({ resolveCargoDiscardPallet: async () => { resolves++; return pending.promise } })
vm.discardCurrent = { discardNo: 'DISCARD' }; vm.discardTask = { palletNo: 'OLD', status: 'PENDING', skus: [{ itemId: 'sku', plannedPackages: 1 }] }; vm.discardActuals = { sku: 1 }
const first = vm.resolveDiscardPallet('NEW')
const second = vm.resolveDiscardPallet('SECOND')
const canConfirmDuringLoad = vm.canConfirmDiscard
pending.resolve({ discardNo: 'DISCARD', palletNo: 'NEW', status: 'PENDING', skus: [] }); await Promise.all([first, second])
assert.equal(resolves, 1); assert.equal(canConfirmDuringLoad, false)
assert.equal(vm.discardTask.palletNo, 'NEW')
})
test('discard confirmation is guarded against double submission', async () => {
const pending = deferred(); let submits = 0
const { vm } = page({ confirmCargoDiscard: async () => { submits++; await pending.promise }, listCargoDiscardTasks: async () => [] })
vm.discardCurrent = { discardNo: 'DISCARD' }; vm.discardTask = { palletNo: 'PALLET', status: 'PENDING', skus: [{ itemId: 'sku', plannedPackages: 1 }] }; vm.discardActuals = { sku: 1 }
const first = vm.confirmDiscard(); const second = vm.confirmDiscard()
pending.resolve(); await Promise.all([first, second]); assert.equal(submits, 1)
})
test('discard retry stays in the discard workflow instead of restoring an old outbound task', async () => {
const { vm } = page(); const calls = []
vm.discardCurrent = { discardNo: 'DISCARD' }; vm.discardPalletCode = 'PALLET'
vm.resolveDiscardPallet = async code => calls.push(['discard', code]); vm.openTask = async code => calls.push(['outbound', code])
await vm.retry(); assert.deepEqual(calls, [['discard', 'PALLET']])
})
test('closing or switching discard order ignores the previous resolve response', async () => {
const pending = deferred()
const { vm } = page({ resolveCargoDiscardPallet: () => pending.promise })
vm.discardCurrent = { discardNo: 'FIRST' }
const first = vm.resolveDiscardPallet('PALLET')
vm.openDiscardOrder('SECOND')
pending.resolve({ discardNo: 'FIRST', palletNo: 'PALLET', skus: [] }); await first
assert.equal(vm.discardTask, null); assert.equal(vm.error, null)
})