Files
docs/悦/货代SAAS平台后台/tests/verify-delivery-fleet-quote.cjs
T

85 lines
6.7 KiB
JavaScript
Raw Normal View History

2026-08-21 09:20:26 +08:00
#!/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)>'), '&lt;img src=x onerror=alert(1)&gt;', '用户输入必须转义后渲染');
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');