Files
docs/悦/货代SAAS平台后台/tests/verify-profit-business-tabs.cjs
T
2026-08-21 09:20:26 +08:00

176 lines
6.2 KiB
JavaScript

#!/usr/bin/env node
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const root = path.resolve(__dirname, '..');
const financeHtml = fs.readFileSync(path.join(root, 'prototype-src', 'modules', '08-finance.html'), 'utf8');
const appJs = fs.readFileSync(path.join(root, 'prototype-src', 'app.js'), 'utf8');
function assertPattern(source, pattern, message) {
assert.ok(pattern.test(source), message);
}
function elementSourceById(source, id) {
const escaped = id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const opening = new RegExp(`<div\\b[^>]*\\bid=["']${escaped}["'][^>]*>`, 'i').exec(source);
if (!opening) return '';
const tagPattern = /<div\b[^>]*>|<\/div\s*>/gi;
tagPattern.lastIndex = opening.index;
let depth = 0;
let match;
while ((match = tagPattern.exec(source))) {
if (/^<div\b/i.test(match[0])) depth += 1;
else if (--depth === 0) return source.slice(opening.index, tagPattern.lastIndex);
}
return '';
}
function constObjectSource(source, name) {
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const declaration = new RegExp(`const\\s+${escaped}\\s*=\\s*\\{`).exec(source);
if (!declaration) return '';
const braceStart = source.indexOf('{', declaration.index);
let depth = 0;
let quote = '';
let escapedChar = false;
for (let index = braceStart; index < source.length; index += 1) {
const char = source[index];
if (quote) {
if (escapedChar) escapedChar = false;
else if (char === '\\') escapedChar = true;
else if (char === quote) quote = '';
continue;
}
if (char === "'" || char === '"' || char === '`') {
quote = char;
continue;
}
if (char === '{') depth += 1;
if (char === '}' && --depth === 0) return source.slice(declaration.index, index + 1);
}
return '';
}
function functionSource(source, name) {
const start = source.indexOf(`function ${name}(`);
if (start < 0) return '';
const braceStart = source.indexOf('{', start);
if (braceStart < 0) return '';
let depth = 0;
let quote = '';
let escaped = false;
for (let index = braceStart; index < source.length; index += 1) {
const char = source[index];
if (quote) {
if (escaped) escaped = false;
else if (char === '\\') escaped = true;
else if (char === quote) quote = '';
continue;
}
if (char === "'" || char === '"' || char === '`') {
quote = char;
continue;
}
if (char === '{') depth += 1;
if (char === '}' && --depth === 0) return source.slice(start, index + 1);
}
return '';
}
const businessTabs = {
firstMile: '头程',
lastMile: '尾程',
};
const businessTabsSource = elementSourceById(financeHtml, 'profit-business-tabs');
assert.ok(businessTabsSource, '利润表缺少业务类型页签容器');
Object.entries(businessTabs).forEach(([key, label]) => {
assertPattern(
businessTabsSource,
new RegExp(`data-profit-business=["']${key}["'][^>]*>\\s*${label}`),
`利润表缺少“${label}”业务页签`,
);
});
assert.ok(
!/data-profit-business=["'](warehouseRent|relabel|warehouseTransfer)["']/.test(businessTabsSource),
'利润表不应继续展示仓租、贴标或仓储转运业务页签',
);
const orderTypeMatch = /<select\b[^>]*\bid=["']profit-order-type["'][^>]*>[\s\S]*?<\/select>/i.exec(financeHtml);
const orderTypeSource = orderTypeMatch ? orderTypeMatch[0] : '';
assert.ok(orderTypeSource, '利润表缺少订单类型查询下拉');
['全部订单类型', '暂扣', '退货', '仓储转运', '换标'].forEach((label) => {
assertPattern(orderTypeSource, new RegExp('>\\s*' + label + '\\s*</option>'), `订单类型下拉缺少“${label}”`);
});
const profitBusinessDataSource = constObjectSource(appJs, 'profitBusinessData');
assert.ok(profitBusinessDataSource, '缺少利润表业务数据对象');
assertPattern(appJs, /let\s+profitBusiness\s*=\s*["']firstMile["']/, '利润表默认业务类型应为头程');
assertPattern(appJs, /function\s+switchProfitBusiness\s*\(/, '缺少利润表业务类型切换函数');
assertPattern(appJs, /function\s+getActiveProfitTickets\s*\(/, '缺少当前业务利润单票读取函数');
Object.keys(businessTabs).forEach((key) => {
assertPattern(profitBusinessDataSource, new RegExp(`(?:["']${key}["']|\\b${key})\\s*:\\s*\\[`), `利润表业务数据缺少 ${key} 数组`);
});
assertPattern(appJs, /profit-order-type/, '利润表脚本未读取订单类型筛选');
assertPattern(appJs, /orderType/, '利润表数据或过滤逻辑缺少订单类型字段');
const profitBusinessLabelsSource = constObjectSource(appJs, 'profitBusinessLabels');
assert.ok(profitBusinessLabelsSource, '缺少利润表业务名称对象');
Object.entries(businessTabs).forEach(([key, label]) => {
assertPattern(
profitBusinessLabelsSource,
new RegExp(`(?:["']${key}["']|\\b${key})\\s*:\\s*["']${label}["']`),
`利润表业务名称 ${key} 应映射为“${label}”`,
);
});
const profitFeeTemplatesSource = constObjectSource(appJs, 'profitFeeTemplates');
assert.ok(profitFeeTemplatesSource, '缺少利润表费用模板对象');
Object.keys(businessTabs).forEach((key) => {
assertPattern(
profitFeeTemplatesSource,
new RegExp(`(?:["']${key}["']|\\b${key})\\s*:`),
`利润表费用模板缺少 ${key} 配置`,
);
});
for (const feeLabel of ['头程运费收入', '尾程服务收入']) {
assertPattern(
profitFeeTemplatesSource,
new RegExp("[\"']" + feeLabel + "[\"']"),
'利润表费用模板缺少“' + feeLabel + '”',
);
}
const profitDetailSource = functionSource(appJs, 'openProfitDetail');
assert.ok(profitDetailSource, '缺少利润明细函数');
assertPattern(
profitDetailSource,
/profitBusinessLabels\s*\[\s*profitBusiness\s*\]/,
'利润单票明细应展示当前业务类型',
);
const profitResetSource = functionSource(appJs, 'profitReset');
assert.ok(profitResetSource, '缺少利润表重置函数');
assertPattern(
profitResetSource,
/profitBusiness\s*=\s*["']firstMile["']/,
'利润表重置时应恢复头程业务类型',
);
assertPattern(
profitResetSource,
/profit-order-type[\s\S]*value\s*=\s*["']?["']?/,
'利润表重置时应清空订单类型',
);
console.log('利润表业务类型页签回归校验通过');