174 lines
8.1 KiB
JavaScript
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');
|
|
}
|