Files
docs/悦/货代SAAS平台后台/tests/verify-domestic-pallet-purchase-price.cjs
T
2026-08-19 14:28:55 +08:00

174 lines
8.1 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 moduleHtml = fs.readFileSync(path.join(root, 'prototype-src', 'modules', '02-domestic-warehouse.html'), 'utf8');
const appJs = fs.readFileSync(path.join(root, 'prototype-src', 'app.js'), 'utf8');
const agentsMd = fs.readFileSync(path.join(root, 'AGENTS.md'), 'utf8');
const manifest = JSON.parse(fs.readFileSync(path.join(root, 'prototype-src', 'manifest.json'), 'utf8'));
const failures = [];
function escapeRegExp(value) {
return value.replace(/[.*+?^\${}()|[\]\\]/g, '\\$&');
}
function requireMatch(pattern, label, source) {
if (!pattern.test(source)) failures.push('missing ' + label);
}
function findClosingBrace(source, openingIndex) {
let depth = 0;
let quote = '';
let lineComment = false;
let blockComment = false;
for (let index = openingIndex; index < source.length; index += 1) {
const character = source[index];
const next = source[index + 1];
if (lineComment) {
if (character === '\n' || character === '\r') lineComment = false;
continue;
}
if (blockComment) {
if (character === '*' && next === '/') { blockComment = false; index += 1; }
continue;
}
if (quote) {
if (character === '\\') index += 1;
else if (character === quote) quote = '';
continue;
}
if (character === '/' && next === '/') { lineComment = true; index += 1; continue; }
if (character === '/' && next === '*') { blockComment = true; index += 1; continue; }
if (character === "'" || character === '"' || character === '\x60') { quote = character; continue; }
if (character === '{') depth += 1;
if (character === '}') {
depth -= 1;
if (depth === 0) return index;
}
}
return -1;
}
function getFunctionSource(functionName) {
const pattern = new RegExp('function\\s+' + escapeRegExp(functionName) + '\\s*\\([^)]*\\)\\s*\\{');
const declaration = pattern.exec(appJs);
if (!declaration) return '';
const openingBrace = appJs.indexOf('{', declaration.index);
const closingBrace = findClosingBrace(appJs, openingBrace);
return closingBrace < 0 ? '' : appJs.slice(declaration.index, closingBrace + 1);
}
function getPageSource(pageId) {
const marker = 'id="page-' + pageId + '"';
const start = moduleHtml.indexOf(marker);
if (start < 0) return '';
const opening = moduleHtml.lastIndexOf('<div class="page"', start);
const next = moduleHtml.indexOf('<div class="page"', start + marker.length);
return moduleHtml.slice(opening, next < 0 ? moduleHtml.length : next);
}
const listPage = getPageSource('pallet-manage');
const detailPage = getPageSource('pallet-detail');
requireMatch(/<th[^>]*>\s*采购价格\s*<\/th>/, '列表采购价格列', listPage);
requireMatch(/<input\b(?=[^>]*\bid=["']pallet-purchase-price-in["'])(?=[^>]*\btype=["']number["'])(?=[^>]*\bmin=["']0["'])(?=[^>]*\bstep=["']0\.01["'])[^>]*>/i, '采购价格非负两位小数输入框', listPage);
requireMatch(/采购价格(元)\s*\*/, '采购价格必填标签', listPage);
requireMatch(/id=["']page-pallet-detail["']/, '国内仓托盘详情页', detailPage);
requireMatch(/id=["']pallet-detail-no["']/, '详情托盘编码节点', detailPage);
requireMatch(/id=["']pallet-detail-status["']/, '详情状态节点', detailPage);
requireMatch(/id=["']pallet-detail-content["']/, '详情内容节点', detailPage);
requireMatch(/backToPalletManage\(\)/, '详情返回入口', detailPage);
const domesticModule = manifest.modules.find((item) => item.file === 'modules/02-domestic-warehouse.html');
if (!manifest.pageOrder.includes('pallet-detail')) failures.push('missing manifest.pageOrder 中的 pallet-detail');
if (!domesticModule || !domesticModule.pages.includes('pallet-detail')) failures.push('missing 国内仓模块 pages 中的 pallet-detail');
requireMatch(/国内仓[^\n]*托盘及详情/, 'AGENTS.md 国内仓托盘详情索引', agentsMd);
const palletDeclaration = /const overseasPalletData=\[[\s\S]*?\];/.exec(appJs);
const stateDeclaration = /let editingPalletIdx=-1;/.exec(appJs);
const functionNames = ['renderOverseasPallet', 'openPalletModal', 'savePallet', 'openPalletDetail', 'backToPalletManage'];
const functionSources = functionNames.map((name) => {
const source = getFunctionSource(name);
if (!source) failures.push('missing ' + name + ' 函数');
return source;
}).filter(Boolean).join('\n');
if (!palletDeclaration) failures.push('missing overseasPalletData 数据');
if (!stateDeclaration) failures.push('missing editingPalletIdx 状态');
requireMatch(/purchasePrice\s*:/, '托盘 purchasePrice 示例数据', appJs);
requireMatch(/Number\(d\.purchasePrice\)\.toFixed\(2\)/, '列表采购价格两位小数格式', getFunctionSource('renderOverseasPallet'));
requireMatch(/openPalletDetail\(/, '列表详情入口', getFunctionSource('renderOverseasPallet'));
requireMatch(/pallet-purchase-price-in/, '编辑采购价格回填', getFunctionSource('openPalletModal'));
requireMatch(/请填写有效的采购价格/, '采购价格保存校验提示', getFunctionSource('savePallet'));
if (!failures.length) {
try {
const ids = ['tbody-pallet-manage','pallet-total','pallet-modal-title','pallet-code-in','pallet-type-in','pallet-vol-in','pallet-weight-in','pallet-purchase-price-in','pallet-status-in','pallet-remark-in','pallet-detail-no','pallet-detail-status','pallet-detail-content'];
const elements = Object.fromEntries(ids.map((id) => {
let value = '';
return [id, {
get value() { return value; },
set value(nextValue) { value = String(nextValue); },
textContent: '',
innerHTML: '',
className: '',
}];
}));
const modalCalls = [];
const hiddenModals = [];
const toasts = [];
const pageCalls = [];
const runtimeSource = palletDeclaration[0] + '\n' + stateDeclaration[0] + '\n' + functionSources + '\nreturn {data:overseasPalletData,renderOverseasPallet,openPalletModal,savePallet,openPalletDetail,backToPalletManage};';
const runtime = new Function('document','showModal','hideModal','showToast','switchPage',runtimeSource)(
{getElementById:(id)=>elements[id]||null},
(id)=>modalCalls.push(id),
(id)=>hiddenModals.push(id),
(message,type)=>toasts.push([message,type]),
(pageId)=>pageCalls.push(pageId),
);
runtime.data[0].purchasePrice = 120;
runtime.renderOverseasPallet([runtime.data[0]]);
assert.match(elements['tbody-pallet-manage'].innerHTML, /¥120\.00/);
assert.match(elements['tbody-pallet-manage'].innerHTML, /openPalletDetail\(['"]TP-001['"]\)/);
runtime.openPalletModal(0);
assert.equal(String(elements['pallet-purchase-price-in'].value), '120');
elements['pallet-purchase-price-in'].value = '-0.01';
runtime.savePallet();
assert.deepEqual(toasts.at(-1), ['请填写有效的采购价格','warning']);
assert.equal(hiddenModals.length, 0);
elements['pallet-purchase-price-in'].value = '88.6';
runtime.savePallet();
assert.equal(runtime.data[0].purchasePrice, 88.6);
assert.deepEqual(hiddenModals, ['modal-pallet']);
runtime.openPalletDetail('TP-001');
assert.equal(elements['pallet-detail-no'].textContent, 'TP-001');
assert.match(elements['pallet-detail-content'].innerHTML, /采购价格/);
assert.match(elements['pallet-detail-content'].innerHTML, /¥88\.60/);
assert.equal(pageCalls.at(-1), 'pallet-detail');
runtime.openPalletDetail('NOT-FOUND');
assert.deepEqual(toasts.at(-1), ['未找到托盘记录','error']);
assert.equal(pageCalls.at(-1), 'pallet-detail');
runtime.backToPalletManage();
assert.equal(pageCalls.at(-1), 'pallet-manage');
runtime.openPalletModal(-1);
assert.equal(elements['pallet-purchase-price-in'].value, '');
} catch (error) {
failures.push('采购价格行为错误:' + error.message);
}
}
if (failures.length) {
console.error('DOMESTIC_PALLET_PURCHASE_PRICE_CHECK_FAIL');
failures.forEach((failure) => console.error('- ' + failure));
process.exitCode = 1;
} else {
console.log('DOMESTIC_PALLET_PURCHASE_PRICE_CHECK_PASS');
}