Files
docs/悦/货代SAAS平台后台/tests/verify-inventory-pallet-box-display.cjs
T
2026-08-21 09:20:26 +08:00

236 lines
14 KiB
JavaScript

#!/usr/bin/env node
const fs = require('node:fs');
const path = require('node:path');
const html = fs.readFileSync(path.resolve(__dirname, '..', 'index.html'), 'utf8');
const failures = [];
function expect(condition, label) {
if (!condition) failures.push(label);
}
function extractFunction(name, source) {
const start = source.search(new RegExp(`function\\s+${name}\\s*\\(`));
if (start < 0) return '';
const bodyStart = source.indexOf('{', start);
let depth = 0;
for (let index = bodyStart; index < source.length; index += 1) {
if (source[index] === '{') depth += 1;
if (source[index] === '}') depth -= 1;
if (depth === 0) return source.slice(start, index + 1);
}
return '';
}
const overviewStart = html.indexOf('<div class="page" id="page-inventory-overview">');
const overviewEnd = html.indexOf('<!-- ==================== WAREHOUSE STOCK', overviewStart);
const overview = overviewStart >= 0 && overviewEnd > overviewStart
? html.slice(overviewStart, overviewEnd)
: '';
const detailStart = html.indexOf('<div class="page" id="page-warehouse-stock-detail">');
const detailEnd = html.indexOf('<!--', detailStart + 1);
const detail = detailStart >= 0 && detailEnd > detailStart
? html.slice(detailStart, detailEnd)
: '';
const scriptStart = html.indexOf('// ==================== UNIFIED OVERSEAS INVENTORY');
const scriptEnd = html.indexOf('// ====================', scriptStart + 10);
const wmsScript = scriptStart >= 0 && scriptEnd > scriptStart
? html.slice(scriptStart, scriptEnd)
: '';
expect(Boolean(overview), 'missing WMS inventory overview section');
expect(!/wms-summary-pending|>待上架<|pendingPutawayStock/.test(overview), 'WMS overview still contains pending-putaway UI');
expect(!/待上架|pendingPutawayStock|wms-summary-pending/.test(wmsScript), 'unified WMS script still contains pending-putaway logic');
expect(!/待上架|pendingPutawayStock/.test(detail), 'WMS detail still contains pending-putaway summary');
expect(/min-width:1860px/.test(overview), 'WMS table width is not 1860px');
expect(/colspan="13"[^>]*>暂无符合条件的库存信息/.test(wmsScript), 'WMS empty row is not 13 visible columns');
expect(/id="page-wh-inbound"[\s\S]*?>待上架</.test(html), 'inbound pending-putaway flow was removed');
expect(/id="page-wh-shelve"[\s\S]*?>待上架</.test(html), 'shelving pending-putaway flow was removed');
const helperNames = ['wmsNumber', 'createWmsUnits', 'addWmsUnits', 'combineWmsUnits', 'hasWmsUnits', 'formatWmsUnits'];
const helperSource = helperNames.map(name => extractFunction(name, wmsScript)).join('\n');
helperNames.forEach(name => expect(Boolean(extractFunction(name, wmsScript)), `missing helper ${name}`));
if (helperNames.every(name => extractFunction(name, wmsScript))) {
try {
const helpers = new Function(`${helperSource}; return {createWmsUnits,addWmsUnits,combineWmsUnits,hasWmsUnits,formatWmsUnits};`)();
const combined = helpers.combineWmsUnits(
helpers.createWmsUnits(2, 12),
helpers.createWmsUnits(3, 8)
);
expect(combined.pallets === 5 && combined.boxes === 20, 'unit helpers do not total pallets and boxes independently');
expect(helpers.formatWmsUnits({pallets: 2, boxes: 12}) === '2托 / 12箱', 'combined pallet/box format is incorrect');
expect(helpers.formatWmsUnits({pallets: 1234, boxes: 0}) === '1,234托', 'pallet-only zh-CN format is incorrect');
expect(helpers.formatWmsUnits({pallets: 0, boxes: 1234}) === '1,234箱', 'box-only zh-CN format is incorrect');
expect(helpers.formatWmsUnits({pallets: 0, boxes: 0}) === '—', 'zero inventory does not use em-dash placeholder');
expect(helpers.hasWmsUnits({pallets: 0, boxes: 1}) && !helpers.hasWmsUnits({pallets: 0, boxes: 0}), 'hasWmsUnits zero detection is incorrect');
} catch (error) {
failures.push(`unit helpers could not execute: ${error.message}`);
}
}
[
'availableStock',
'lockedStock',
'holdStock',
'defectiveStock',
'inTransitStock',
'purchaseStock',
'onHandStock',
'expectedStock'
].forEach(field => {
expect(new RegExp(`${field}:createWmsUnits\\(`).test(wmsScript), `${field} is not initialized as pallet/box units`);
});
expect(/formatWmsUnits\(item\.onHandStock\)/.test(wmsScript), 'on-hand table cell is not unit formatted');
['availableStock', 'holdStock', 'defectiveStock', 'inTransitStock', 'purchaseStock'].forEach(field => {
expect(new RegExp(`formatWmsUnits\\(item\\.${field}\\)`).test(wmsScript), `${field} table cell is not unit formatted`);
});
expect(!/formatWmsUnits\(item\.lockedStock\)/.test(wmsScript), 'lockedStock table cell is still visible');
expect(/node\.textContent=formatWmsUnits\(totals\[field\]\)/.test(wmsScript), 'summary totals are not unit formatted');
expect(/const key=JSON\.stringify\(\[owner,warehouse,sku\]\)/.test(wmsScript), 'inventory aggregation key is still delimiter-ambiguous');
expect(/const warehouseLookup=new Map\(/.test(wmsScript), 'hold aggregation does not build a warehouse lookup map');
const behaviorNames = [
'normalizeWmsWarehouse',
...helperNames,
'getWmsInventoryStatus',
'buildUnifiedInventoryData',
'renderWmsInventorySummary',
'renderUnifiedInventory',
'wmsDocumentLink',
'renderWarehouseStock'
];
const behaviorSources = behaviorNames.map(name => extractFunction(name, html));
behaviorNames.forEach((name, index) => expect(Boolean(behaviorSources[index]), `missing behavior function ${name}`));
if (behaviorSources.every(Boolean)) {
try {
const warehouseStockData = [
{owner:"O'Reilly & Sons",wh:'WH (A)',sku:'SKU(\')"<',cnName:'Special',pallets:2,boxes:12,inTransitPallets:0,inTransitBoxes:5,actualStock:10,availQty:10,loc:'A-1',status:'在库'},
{owner:'Locked',wh:'Lock WH',sku:'LOCK',cnName:'Locked',pallets:1,boxes:0,inTransitPallets:0,inTransitBoxes:0,actualStock:1,availQty:1,loc:'L-1',status:'已锁定'},
{owner:'Zero',wh:'Zero WH',sku:'ZERO',cnName:'Zero',pallets:0,boxes:0,inTransitPallets:0,inTransitBoxes:0,actualStock:0,availQty:0,loc:'Z-1',status:'在库'},
{owner:'A|B',wh:'C',sku:'D',cnName:'Collision A',pallets:0,boxes:1,inTransitPallets:0,inTransitBoxes:0,actualStock:1,availQty:1,loc:'C-1',status:'在库'},
{owner:'A',wh:'B|C',sku:'D',cnName:'Collision B',pallets:0,boxes:2,inTransitPallets:0,inTransitBoxes:0,actualStock:2,availQty:2,loc:'C-2',status:'在库'}
];
const overseasStockData = [
{owner:'Hold',wh:'Hold WH',sku:'HOLD',cnName:'Hold',enName:'Hold',stock:4,palletNo:'',ctBoxes:4,batchNo:'H-1'},
{owner:'Zero Hold',wh:'Zero Hold WH',sku:'ZERO-HOLD',cnName:'Zero Hold',enName:'Zero Hold',stock:0,palletNo:'PAL-0',ctBoxes:9,batchNo:'H-0'}
];
const looseboardStockData = [
{owner:'Looseboard',wh:'Looseboard WH',sku:'LB',cnName:'Looseboard',pallets:1,boxes:3,batchNo:'LB-1',palletNo:'PAL-LB-1',loc:'LB-A-1'}
];
const purchaseStockData = [
{owner:'Purchase Pallets',purchaseWh:'Purchase WH',sku:'PUR-P',cnName:'Purchase Pallets',actualStock:3,pallets:3,boxes:0,batchNo:'P-1'},
{owner:'Purchase Boxes',purchaseWh:'Purchase Box WH',sku:'PUR-B',cnName:'Purchase Boxes',actualStock:7,pallets:0,boxes:7,batchNo:'P-2'},
{owner:'Zero Purchase',purchaseWh:'Zero Purchase WH',sku:'PUR-0',cnName:'Zero Purchase',actualStock:0,pallets:9,boxes:9,batchNo:'P-0'}
];
const nodes = new Map();
const summaryIds = {
onHandStock:'wms-summary-onhand',
availableStock:'wms-summary-available',
holdStock:'wms-summary-hold',
defectiveStock:'wms-summary-defective',
inTransitStock:'wms-summary-in-transit',
purchaseStock:'wms-summary-purchase'
};
[...Object.values(summaryIds), 'tbody-inventory-overview', 'inventory-overview-total', 'tbody-warehouse-stock', 'warehouse-stock-total'].forEach(id => {
nodes.set(id, {textContent:'',innerHTML:''});
});
const document = {getElementById(id){return nodes.get(id)||null;}};
const lmoEsc = value => String(value == null ? '' : value).replace(/[&<>"']/g, character => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[character]));
const runtime = new Function(
'warehouseStockData',
'overseasStockData',
'looseboardStockData',
'purchaseStockData',
'purchaseStockOwnerBySku',
'document',
'lmoEsc',
'warehouseStockStatusTag',
`${behaviorSources.join('\n')}; return {buildUnifiedInventoryData,renderWmsInventorySummary,renderUnifiedInventory,wmsDocumentLink,renderWarehouseStock};`
)(warehouseStockData,overseasStockData,looseboardStockData,purchaseStockData,{},document,lmoEsc,{'在库':'tag-green','部分在库':'tag-orange','已锁定':'tag-orange','已处理':'tag-blue'});
const records = runtime.buildUnifiedInventoryData();
expect(records.length === 11, 'real aggregation did not retain all source rows or delimiter-collision records');
const special = records.find(record => record.owner === "O'Reilly & Sons");
const locked = records.find(record => record.sku === 'LOCK');
const hold = records.find(record => record.sku === 'HOLD');
const purchasePallets = records.find(record => record.sku === 'PUR-P');
const purchaseBoxes = records.find(record => record.sku === 'PUR-B');
const looseboard = records.find(record => record.sku === 'LB');
const zero = records.find(record => record.sku === 'ZERO');
expect(special && special.availableStock.pallets === 2 && special.availableStock.boxes === 12, 'warehouse source did not aggregate pallet/box inventory');
expect(special && special.inTransitStock.pallets === 0 && special.inTransitStock.boxes === 5, 'warehouse source did not aggregate box-only in-transit inventory');
expect(locked && locked.lockedStock.pallets === 1 && locked.lockedStock.boxes === 0, 'locked warehouse source did not aggregate pallet-only inventory');
expect(hold && hold.holdStock.pallets === 0 && hold.holdStock.boxes === 4, 'overseas source did not aggregate box-only hold inventory');
expect(purchasePallets && purchasePallets.purchaseStock.pallets === 3 && purchasePallets.purchaseStock.boxes === 0, 'purchase source did not aggregate pallet-only inventory');
expect(purchaseBoxes && purchaseBoxes.purchaseStock.pallets === 0 && purchaseBoxes.purchaseStock.boxes === 7, 'purchase source did not aggregate box-only inventory');
expect(looseboard && looseboard.defectiveStock.pallets === 1 && looseboard.defectiveStock.boxes === 3, 'looseboard source did not aggregate pallet/box inventory');
expect(zero && zero.status === '无可用库存', 'zero inventory status is incorrect');
runtime.renderWmsInventorySummary(records);
const expectedSummary = {
onHandStock:'4托 / 22箱',
availableStock:'2托 / 15箱',
holdStock:'4箱',
defectiveStock:'1托 / 3箱',
inTransitStock:'5箱',
purchaseStock:'3托 / 7箱'
};
Object.entries(expectedSummary).forEach(([field, expected]) => {
expect(nodes.get(summaryIds[field]).textContent === expected, `${field} summary rendered ${nodes.get(summaryIds[field]).textContent || '(empty)'} instead of ${expected}`);
});
runtime.renderUnifiedInventory([special]);
const inventoryMarkup = nodes.get('tbody-inventory-overview').innerHTML;
expect((inventoryMarkup.match(/<td\b/g)||[]).length === 13, 'rendered inventory row does not contain 13 visible cells');
expect(!/onclick=|decodeURIComponent|待上架/.test(inventoryMarkup), 'inventory detail link still embeds executable data or pending-putaway text');
expect(/data-wms-action="stock-detail"/.test(inventoryMarkup), 'inventory detail link lacks delegated action data');
expect(inventoryMarkup.includes('O&#39;Reilly &amp; Sons'), 'special-character owner is not safely stored in inventory link data');
expect(inventoryMarkup.includes('2托 / 12箱') && inventoryMarkup.includes('5箱') && inventoryMarkup.includes('—'), 'table rendering does not cover combined, single-unit, and zero placeholders');
runtime.renderWarehouseStock([warehouseStockData[0]]);
const warehouseMarkup = nodes.get('tbody-warehouse-stock').innerHTML;
expect(!/onclick=|decodeURIComponent/.test(warehouseMarkup), 'warehouse stock detail link still embeds executable data');
expect(/data-wms-action="stock-detail"/.test(warehouseMarkup), 'warehouse stock detail link lacks delegated action data');
const documentLink = runtime.wmsDocumentLink('business', "DOC'()\"<>");
expect(!/onclick=|decodeURIComponent/.test(documentLink), 'inventory document link still embeds executable data');
expect(/data-wms-action="related-document"/.test(documentLink), 'inventory document link lacks delegated action data');
} catch (error) {
failures.push(`real WMS aggregation/rendering could not execute: ${error.stack || error.message}`);
}
}
const clickHandlerSource = extractFunction('handleWmsInventoryClick', wmsScript);
expect(Boolean(clickHandlerSource), 'missing delegated WMS click handler');
expect(/document\.addEventListener\(['"]click['"],handleWmsInventoryClick\)/.test(wmsScript), 'delegated WMS click handler is not registered');
if (clickHandlerSource) {
try {
const calls = [];
const handler = new Function('openWarehouseStockDetail','openInventoryRelatedDocument',`${clickHandlerSource}; return handleWmsInventoryClick;`)(
(...args) => calls.push(['stock',...args]),
(...args) => calls.push(['document',...args])
);
const specialValues = {wmsOwner:"O'Reilly",wmsWarehouse:'WH (A)',wmsSku:'SKU(\')"<'};
handler({preventDefault(){calls.push(['prevented']);},target:{closest(){return {dataset:{wmsAction:'stock-detail',...specialValues}};}}});
expect(JSON.stringify(calls[1]) === JSON.stringify(['stock',specialValues.wmsOwner,specialValues.wmsWarehouse,specialValues.wmsSku]), 'delegated handler did not preserve special-character stock identifiers');
} catch (error) {
failures.push(`delegated WMS click handler could not execute: ${error.message}`);
}
}
if (failures.length) {
console.error('INVENTORY_PALLET_BOX_DISPLAY_CHECK_FAIL');
failures.forEach(failure => console.error(`- ${failure}`));
process.exitCode = 1;
} else {
console.log('INVENTORY_PALLET_BOX_DISPLAY_CHECK_PASS');
}