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('')[0] const script = source.split('')[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) }) value.confirmBindingChoice = async () => true value.selectSource({ positionId: '1', palletId: '20', palletCode: 'SOURCE', movablePackages: 2, cartonMarks: ['A', 'B', 'C'] }) 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', () => { 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' 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' } 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) }) test('quantity disassembly has no extra carton scanner', () => { assert.doesNotMatch(source, /relabel-move-carton/) assert.match(source, /actualSource:/) assert.match(source, /来源已确认:\{\{ source.palletCode \}\}<\/text>/) assert.match(source, /this\.message = `托盘 \$\{this\.target\.palletCode\} 已绑定`/) }) 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, />托盘刷新托盘号<\/text>\{\{ row\.palletCode \}\}/) assert.doesNotMatch(template, /入仓码|row\.inboundCode/) assert.match(source, /可拆\{\{ isReturnBulk \? '件数' : '箱数' \}\}:\{\{ row\.movablePackages \}\}/) assert.doesNotMatch(template, /刷新来源托盘/) assert.doesNotMatch(template, /按原入仓单拆托/) assert.doesNotMatch(template, /确认后直接提交本批拆托合托/) }) 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() assert.match(message, /已绑定此托盘/) }) 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' let binds = 0 value.resolve = async kind => { assert.equal(kind, 'target'); value.target = { palletId: '30', palletCode: 'TARGET' } } value.bind = async () => { binds++ } await value.confirmTargetAndBind() assert.equal(binds, 1) assert.match(source, /@submit="confirmTargetAndBind"/) assert.match(source, /@tap="confirmTargetAndBind"/) assert.doesNotMatch(source, /@tap="bind"/) }) 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) }) 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) }) 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() assert.match(message, /扫描确认来源托盘/) }) 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' 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, '绑定失败') })