Files
pda-overseas-uniapp/tests/relabel-pallet-work.test.mjs
T

199 lines
11 KiB
JavaScript
Raw Normal View History

2026-09-17 13:36:08 +08:00
import test from 'node:test'
import assert from 'node:assert/strict'
import { readFile } from 'node:fs/promises'
const source = await readFile(new URL('../src/components/RelabelPalletWork.vue', import.meta.url), 'utf8')
const template = source.split('</template>')[0]
2026-09-17 13:36:08 +08:00
const script = source.split('<script>')[1].split('</script>')[0].replace(/^import .*$/gm, '').replace('export default', 'return')
const component = new Function('ScanTargetInput', script)({})
function context() {
const value = { ...component.data(), $emit() {}, task: { items: [
{ cartonMark: 'A', relabelledPackages: 1, destinationFbaWarehouseCode: 'ONT8' },
{ cartonMark: 'B', relabelledPackages: 1, destinationFbaWarehouseCode: 'JFK8' },
{ cartonMark: 'C', relabelledPackages: 0, destinationFbaWarehouseCode: 'ONT8' },
{ cartonMark: 'OTHER', relabelledPackages: 1, destinationFbaWarehouseCode: 'ONT8' }
] } }
for (const [key, method] of Object.entries(component.methods)) value[key] = method.bind(value)
for (const [key, method] of Object.entries(component.computed)) Object.defineProperty(value, key, { get: method.bind(value) })
2026-10-10 13:47:56 +08:00
value.confirmBindingChoice = async () => true
value.selectSource({ positionId: '1', palletId: '20', palletCode: 'SOURCE', movablePackages: 2, cartonMarks: ['A', 'B', 'C'] })
2026-09-17 13:36:08 +08:00
return value
}
test('quantity batch only includes completed boxes from same inbound and destination', () => {
const value = context()
value.quantity = '1'; value.fillBatch(); assert.deepEqual(value.marks, ['A'])
value.quantity = '2'; value.fillBatch(); assert.match(value.error, /不超过/)
value.quantity = '1.5'; value.fillBatch(); assert.match(value.error, /正整数/)
value.selectedDestination = 'JFK8'; value.quantity = '1'; value.fillBatch(); assert.deepEqual(value.marks, ['B'])
})
test('submission uses pallet identity instead of scanned presentation text', () => {
2026-09-17 13:36:08 +08:00
const value = context(); value.quantity = '1'; value.fillBatch()
value.source = { palletId: '20', palletCode: 'SOURCE' }; value.sourceCode = ' source\n'
value.target = { palletId: '30', palletCode: 'TARGET' }; value.targetCode = 'target-alias'
2026-09-17 13:36:08 +08:00
assert.equal(value.readyToBind, true)
value.toggleMark('A'); assert.equal(value.readyToBind, false)
value.toggleMark('A'); value.source = { palletId: '21', palletCode: 'OTHER' }; assert.equal(value.readyToBind, false)
value.source = { palletId: '20', palletCode: 'SOURCE' }; value.target = { palletId: '20', palletCode: 'TARGET-ALIAS' }
2026-09-17 13:36:08 +08:00
assert.equal(value.readyToBind, false)
})
test('pallet comparison tolerates mixed API versions and canonical code formatting', () => {
const value = context()
assert.equal(value.samePallet(
{ palletCode: ' plt-us-lax-01-1 ' },
{ palletCode: 'PLT-US-LAX-01-1' }
), true)
assert.equal(value.samePallet(
{ palletId: '20', palletCode: 'SOURCE' },
{ palletId: '21', palletCode: 'OTHER' }
), false)
assert.equal(value.samePallet(
{ palletId: '20', palletCode: 'SOURCE' },
{ palletId: '21', palletCode: ' source ' }
), true)
assert.equal(value.samePallet(
{ palletId: 2100203890682015700, palletCode: 'PLT-US-LAX-01-20260916-2100203890682015746' },
{ palletId: '2100203890682015746', palletCode: ' plt-us-lax-01-20260916-2100203890682015746 ' }
), true)
})
2026-09-17 13:36:08 +08:00
test('quantity disassembly has no extra carton scanner', () => {
assert.doesNotMatch(source, /relabel-move-carton/)
assert.match(source, /actualSource:/)
2026-10-16 18:26:06 +08:00
assert.match(source, /来源已确认:\{\{ source.palletCode \}\}<\/text>/)
2026-10-10 13:47:56 +08:00
assert.match(source, /this\.message = `托盘 \$\{this\.target\.palletCode\} 已绑定`/)
2026-09-17 13:36:08 +08:00
})
test('source pallet list is concise and hides rows with no movable packages', () => {
const value = context()
value.sourceRows = [
{ palletCode: 'PALLET-1', inboundCode: 'IN-1', stockPackages: 12, movablePackages: 2 },
{ palletCode: 'PALLET-2', inboundCode: 'IN-2', stockPackages: 10, movablePackages: 0 }
]
assert.deepEqual(value.operableSourceRows.map(row => row.palletCode), ['PALLET-1'])
assert.match(source, />托盘</)
assert.match(source, />刷新</)
2026-10-10 13:47:56 +08:00
assert.match(source, /class="source-caption">托盘号<\/text><text class="source-code">\{\{ row\.palletCode \}\}/)
assert.doesNotMatch(template, /入仓码|row\.inboundCode/)
assert.match(source, /可拆\{\{ isReturnBulk \? '件数' : '箱数' \}\}:\{\{ row\.movablePackages \}\}/)
assert.doesNotMatch(template, /刷新来源托盘/)
assert.doesNotMatch(template, /按原入仓单拆托/)
assert.doesNotMatch(template, /确认后直接提交本批拆托合托/)
})
2026-09-17 13:36:08 +08:00
test('latest current-round binding is used for rebind source', () => {
const value = context()
value.task.history = ['OLD', 'NEW'].map(pallet => ({ action: 'PALLET_BIND', detail: JSON.stringify({ round: 'INITIAL', cartonMarks: ['A'], targetPallets: [pallet] }) }))
assert.deepEqual(value.currentBindings, [{ mark: 'A', pallet: 'NEW' }])
value.task.reviewedAt = 'NEW-ROUND'
assert.deepEqual(value.currentBindings, [{ mark: 'A', pallet: 'NEW' }])
value.task.history.push({ action: 'REWORK_ARCHIVE', detail: '{}' })
assert.deepEqual(value.currentBindings, [])
})
test('rescan same bound pallet does not call stock movement', async () => {
const value = context(); value.rebindSelection = { mark: 'A', pallet: 'TARGET' }; value.targetCode = 'target'
let message = ''
value.showBindingMessage = (title, content) => { message = content }
value.run = async () => assert.fail('same pallet must not move stock')
await value.submitRebind()
2026-10-16 18:26:06 +08:00
assert.match(message, /已绑定此托盘/)
2026-09-17 13:36:08 +08:00
})
test('target confirmation submits binding directly, with no second submit button', async () => {
const value = context(); value.quantity = '1'; value.fillBatch()
value.source = { palletId: '20', palletCode: 'SOURCE' }; value.sourceCode = 'SOURCE'; value.targetCode = 'TARGET'
2026-09-17 13:36:08 +08:00
let binds = 0
value.resolve = async kind => { assert.equal(kind, 'target'); value.target = { palletId: '30', palletCode: 'TARGET' } }
2026-09-17 13:36:08 +08:00
value.bind = async () => { binds++ }
await value.confirmTargetAndBind()
assert.equal(binds, 1)
2026-10-10 13:47:56 +08:00
assert.match(source, /@submit="confirmTargetAndBind"/)
2026-10-12 15:56:16 +08:00
assert.match(source, /@tap="confirmTargetAndBind"/)
2026-09-17 13:36:08 +08:00
assert.doesNotMatch(source, /@tap="bind"/)
})
2026-10-12 15:56:16 +08:00
test('mixed carton scan alone explains missing SKU and does not bind; completing SKU allows binding', async () => {
const value = context()
value.task.items = [{ cartonMark: 'A', operationType: 'CARTON_AND_SKU', expectedPackages: 1, relabelledPackages: 0, cartonProcessedPackages: 1, skuProcessedPackages: 0, destinationFbaWarehouseCode: 'ONT8' }]
value.source = { palletCode: 'SOURCE' }; value.targetCode = 'TARGET'; value.quantity = '1'
let message = ''; let binds = 0
value.showBindingMessage = (title, content) => { message = content }
value.resolve = async () => { value.target = { palletCode: 'TARGET' } }
value.bind = async () => { binds++ }
await value.confirmTargetAndBind()
assert.match(message, /SKU 贴标登记/)
assert.equal(binds, 0)
assert.equal(value.target, null)
value.task.items[0].skuProcessedPackages = 1
value.task.items[0].relabelledPackages = 1
assert.equal(value.labelBlockReason, '')
await value.confirmTargetAndBind()
assert.equal(binds, 1)
})
2026-10-10 13:47:56 +08:00
test('source scan asks before accepting a listed pallet and rejects unknown pallets', async () => {
const value = context()
value.sourceRows = [{ positionId: '1', palletCode: 'SOURCE', movablePackages: 2 }]
value.sourceCode = 'SOURCE'
let prompts = 0
value.resolve = async () => { value.source = { palletCode: 'SOURCE' } }
value.confirmBindingChoice = async () => { prompts++; return false }
await value.confirmSourceScan()
assert.equal(value.source, null)
value.confirmBindingChoice = async () => { prompts++; return true }
await value.confirmSourceScan()
assert.equal(value.source.palletCode, 'SOURCE')
value.sourceCode = 'OTHER'
value.showBindingMessage = () => {}
value.resolve = async () => assert.fail('unknown pallet must not resolve')
await value.confirmSourceScan()
assert.equal(value.source, null)
assert.equal(prompts, 2)
})
test('declining target confirmation does not move stock', async () => {
const value = context()
value.quantity = '1'; value.source = { palletId: '20', palletCode: 'SOURCE' }; value.targetCode = 'TARGET'
value.resolve = async () => { value.target = { palletId: '30', palletCode: 'TARGET' } }
value.confirmBindingChoice = async () => false
value.bind = async () => assert.fail('cancelled target must not bind')
await value.confirmTargetAndBind()
assert.equal(value.target, null)
})
test('simplified split automatically fills a batch and counts saved bindings without repeats', async () => {
const value = context()
value.task.items = ['A', 'B', 'C'].map(cartonMark => ({ cartonMark, operationType: 'CARTON_LABEL', expectedPackages: 1, relabelledPackages: 1, destinationFbaWarehouseCode: 'ONT8' }))
value.task.history = [{ action: 'PALLET_BIND', detail: JSON.stringify({ cartonMarks: ['A'], targetPallets: ['OLD-TARGET'] }) }]
assert.equal(value.requiredSplitPackages, 3)
assert.equal(value.splitPackages, 1)
value.quantity = '1'; value.marks = []; value.source = { palletId: '20', palletCode: 'SOURCE' }; value.targetCode = 'TARGET'
value.resolve = async () => { value.target = { palletId: '30', palletCode: 'TARGET' } }
value.bind = async () => { assert.deepEqual(value.marks, ['B']); assert.equal(value.readyToBind, true) }
await value.confirmTargetAndBind()
assert.doesNotMatch(template, /按箱数填入本批换标箱|选择本批新目的仓|本批确认|@tap="toggleMark/)
value.task.history.push({ action: 'REWORK_ARCHIVE', detail: '{}' })
assert.equal(value.splitPackages, 0)
})
2026-09-17 13:36:08 +08:00
test('missing source scan shows modal and never submits a binding', async () => {
const value = context(); value.quantity = '1'; value.fillBatch(); value.targetCode = 'TARGET'
let message = ''
value.showBindingMessage = (title, content) => { message = content }
value.bind = async () => assert.fail('must not bind without source confirmation')
value.resolve = async () => assert.fail('must validate source first')
await value.confirmTargetAndBind()
2026-10-16 18:26:06 +08:00
assert.match(message, /扫描确认来源托盘/)
2026-09-17 13:36:08 +08:00
})
test('target lookup failure cannot submit binding or report success', async () => {
const value = context(); value.quantity = '1'; value.fillBatch()
value.source = { palletId: '20', palletCode: 'SOURCE' }; value.sourceCode = 'SOURCE'; value.targetCode = 'TARGET'
2026-09-17 13:36:08 +08:00
value.resolve = async () => { value.error = '目标托盘不可用' }
let title = ''
value.showBindingMessage = heading => { title = heading }
value.bind = async () => assert.fail('must not bind after lookup failure')
await value.confirmTargetAndBind()
assert.equal(title, '绑定失败')
})