85 lines
6.7 KiB
JavaScript
85 lines
6.7 KiB
JavaScript
#!/usr/bin/env node
|
|||
|
|
|
||
|
|
const assert = require('node:assert/strict');
|
||
|
|
const fs = require('node:fs');
|
||
|
|
const path = require('node:path');
|
||
|
|
const vm = require('node:vm');
|
||
|
|
|
||
|
|
const root = path.resolve(__dirname, '..');
|
||
|
|
const suppliers = fs.readFileSync(path.join(root, 'prototype-src/modules/05-suppliers.html'), 'utf8');
|
||
|
|
const general = fs.readFileSync(path.join(root, 'prototype-src/modules/10-general.html'), 'utf8');
|
||
|
|
const shell = fs.readFileSync(path.join(root, 'prototype-src/shell.html'), 'utf8');
|
||
|
|
const app = fs.readFileSync(path.join(root, 'prototype-src/app.js'), 'utf8');
|
||
|
|
const manifest = JSON.parse(fs.readFileSync(path.join(root, 'prototype-src/manifest.json'), 'utf8'));
|
||
|
|
|
||
|
|
const supplierModule = manifest.modules.find(module => module.file === 'modules/05-suppliers.html');
|
||
|
|
assert.ok(supplierModule, '缺少供应商模块');
|
||
|
|
const generalModule = manifest.modules.find(module => module.file === 'modules/10-general.html');
|
||
|
|
assert.ok(generalModule, '缺少通用资料模块');
|
||
|
|
assert.ok(!supplierModule.pages.includes('delivery-fleet-quote'), '供应商模块不应继续登记派送车队报价表');
|
||
|
|
assert.ok(generalModule.pages.includes('delivery-fleet-quote'), '通用资料模块缺少派送车队报价表');
|
||
|
|
assert.equal(generalModule.pages.indexOf('delivery-fleet-quote'), generalModule.pages.indexOf('vehicle') + 1, '通用模块中派送车队报价表应紧跟车辆列表');
|
||
|
|
assert.equal(manifest.pageOrder.indexOf('delivery-fleet-quote'), manifest.pageOrder.indexOf('vehicle') + 1, '顶层页面顺序中派送车队报价表应紧跟车辆列表');
|
||
|
|
assert.equal((shell.match(/switchPage\('delivery-fleet-quote',this\)/g) || []).length, 1, '侧栏只能保留一个派送车队报价表入口');
|
||
|
|
assert.match(shell, /switchPage\('vehicle',this\)[^>]*>车辆列表<\/div>\s*<div class="sub-menu-item" onclick="switchPage\('delivery-fleet-quote',this\)">派送车队报价<\/div>\s*<div class="sub-menu-item" onclick="switchPage\('gen-download',this\)">下载中心<\/div>/, '通用菜单中派送车队报价后应紧跟下载中心');
|
||
|
|
assert.match(general, /<strong>派送车队报价<\/strong>/, '页面面包屑应使用派送车队报价');
|
||
|
|
assert.match(general, /<div class="table-toolbar"><span class="title">派送车队报价<\/span>/, '页面标题应使用派送车队报价');
|
||
|
|
assert.doesNotMatch(suppliers, /id="page-delivery-fleet-quote"/, '供应商模块不应继续包含派送车队报价页面');
|
||
|
|
assert.match(general, /id="page-delivery-fleet-quote"/);
|
||
|
|
assert.match(general, /#page-delivery-fleet-quote \.dfq-table th\{[^}]*text-align:left/);
|
||
|
|
assert.match(general, /#page-delivery-fleet-quote \.dfq-table td\{[^}]*text-align:left/);
|
||
|
|
assert.match(general, /#page-delivery-fleet-quote \.dfq-table \.dfq-price-cell\{[^}]*text-align:left/);
|
||
|
|
assert.match(general, /#modal-delivery-fleet-quote \.dfq-modal-grid\{display:grid/);
|
||
|
|
assert.match(general, /id="dfq-search-keyword"/);
|
||
|
|
assert.match(general, /onclick="openDeliveryFleetQuoteImport\(\)"[^>]*>批量导入</);
|
||
|
|
assert.match(general, /id="modal-delivery-fleet-quote-import"/);
|
||
|
|
assert.match(general, /id="dfq-import-file"[^>]*accept="\.csv"/);
|
||
|
|
['发货地(城市)', '目的仓(FBA仓点)', '迈数(预估距离)', '车队渠道', '整车自提', '整车派送', '散板派送'].forEach(label => assert.ok(general.includes(label), `缺少字段:${label}`));
|
||
|
|
['renderDeliveryFleetQuotes', 'resetDeliveryFleetQuoteSearch', 'openDeliveryFleetQuoteModal', 'saveDeliveryFleetQuote', 'toggleDeliveryFleetQuoteStatus', 'deleteDeliveryFleetQuote', 'openDeliveryFleetQuoteImport', 'downloadDeliveryFleetQuoteImportTemplate', 'handleDeliveryFleetQuoteImportFile', 'confirmDeliveryFleetQuoteImport'].forEach(name => assert.match(app, new RegExp(`function\\s+${name}\\s*\\(`), `缺少函数:${name}`));
|
||
|
|
assert.match(app, /deliveryFleetQuoteData\s*=\s*\[/);
|
||
|
|
|
||
|
|
const helperNames = [
|
||
|
|
'deliveryFleetQuoteHasPrice',
|
||
|
|
'escapeDeliveryFleetQuoteHtml',
|
||
|
|
'filterDeliveryFleetQuoteRows',
|
||
|
|
'validateDeliveryFleetQuoteDraft',
|
||
|
|
'deliveryFleetQuoteKey',
|
||
|
|
'hasDuplicateDeliveryFleetQuote',
|
||
|
|
'splitDeliveryFleetQuoteCsvLine',
|
||
|
|
'parseDeliveryFleetQuoteCsv'
|
||
|
|
];
|
||
|
|
const helperStart = app.indexOf('function deliveryFleetQuoteHasPrice(');
|
||
|
|
const helperEnd = app.indexOf('function formatDeliveryFleetQuotePrice(', helperStart);
|
||
|
|
assert.ok(helperStart >= 0 && helperEnd > helperStart, '报价表纯函数边界缺失');
|
||
|
|
const context = {};
|
||
|
|
const helperSource = `${app.slice(helperStart, helperEnd)}\nthis.api={${helperNames.join(',')}};`;
|
||
|
|
vm.runInNewContext(helperSource, context);
|
||
|
|
|
||
|
|
const rows = [
|
||
|
|
{id:'A',origin:'洛杉矶, CA',warehouse:'LAX9',miles:42,fleet:'SMART',selfPickup:0,fullDelivery:null,looseDelivery:115,status:'启用'},
|
||
|
|
{id:'B',origin:'芝加哥, IL',warehouse:'ORD2',miles:28,fleet:'RIGLL',selfPickup:null,fullDelivery:230,looseDelivery:null,status:'停用'}
|
||
|
|
];
|
||
|
|
assert.equal(context.api.filterDeliveryFleetQuoteRows(rows, {keyword:'smart'}).length, 1, '关键字应跨字段匹配车队渠道');
|
||
|
|
assert.equal(context.api.filterDeliveryFleetQuoteRows(rows, {keyword:'ord2'}).length, 1, '关键字应跨字段匹配 FBA 仓点');
|
||
|
|
assert.equal(context.api.filterDeliveryFleetQuoteRows(rows, {method:'selfPickup'}).length, 1, '零报价仍应视为已配置报价');
|
||
|
|
assert.equal(context.api.escapeDeliveryFleetQuoteHtml('<img src=x onerror=alert(1)>'), '<img src=x onerror=alert(1)>', '用户输入必须转义后渲染');
|
||
|
|
assert.equal(context.api.validateDeliveryFleetQuoteDraft(rows[0]), '', '零报价应通过校验');
|
||
|
|
assert.match(context.api.validateDeliveryFleetQuoteDraft({...rows[0], selfPickup:-1}), /非负数字/, '负报价必须拒绝');
|
||
|
|
assert.match(context.api.validateDeliveryFleetQuoteDraft({...rows[0], selfPickup:null, looseDelivery:null}), /至少填写一种/, '三种空报价必须拒绝');
|
||
|
|
assert.equal(context.api.hasDuplicateDeliveryFleetQuote(rows, {...rows[0], id:'C'}, ''), true, '相同维度组合必须识别为重复');
|
||
|
|
assert.equal(context.api.hasDuplicateDeliveryFleetQuote(rows, {...rows[0], id:'A'}, 'A'), false, '编辑当前记录不应误判为重复');
|
||
|
|
const csv = [
|
||
|
|
'发货城市,目的FBA仓点,迈数,车队渠道,整车自提,整车派送,散板派送,状态,备注',
|
||
|
|
'西雅图,SEA8,32,NORTH,0,180,120,启用,正常报价',
|
||
|
|
'洛杉矶, LAX9,42,SMART,155,175,115,启用,重复报价',
|
||
|
|
'奥克兰,OAK4,20,WEST,-1,,,启用,负数报价'
|
||
|
|
].join('\n');
|
||
|
|
const imported = context.api.parseDeliveryFleetQuoteCsv(csv, rows);
|
||
|
|
assert.equal(imported.length, 3, '应解析全部 CSV 数据行');
|
||
|
|
assert.equal(imported[0].valid, true, '合法报价行应通过');
|
||
|
|
assert.equal(imported[0].data.selfPickup, 0, '导入应保留零报价');
|
||
|
|
assert.match(imported[1].error, /重复/, '重复维度组合应拒绝');
|
||
|
|
assert.match(imported[2].error, /非负数字/, '负数报价应拒绝');
|
||
|
|
|
||
|
|
console.log('DELIVERY_FLEET_QUOTE_CHECK_PASS');
|