154 lines
8.0 KiB
JavaScript
154 lines
8.0 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const vm = require('node:vm');
|
|
|
|
const root = path.resolve(__dirname, '..');
|
|
const manifest = JSON.parse(fs.readFileSync(path.join(root, 'prototype-src', 'manifest.json'), 'utf8'));
|
|
const html = fs.readFileSync(path.join(root, manifest.source), 'utf8');
|
|
const failures = [];
|
|
|
|
function expect(condition, message) {
|
|
if (!condition) failures.push(message);
|
|
}
|
|
|
|
function section(startMarker, endMarker) {
|
|
const start = html.indexOf(startMarker);
|
|
const end = html.indexOf(endMarker, start + startMarker.length);
|
|
return start >= 0 && end > start ? html.slice(start, end) : '';
|
|
}
|
|
|
|
function functionSource(name) {
|
|
const start = html.indexOf(`function ${name}(`);
|
|
if (start < 0) return '';
|
|
const braceStart = html.indexOf('{', start);
|
|
let depth = 0;
|
|
let quote = '';
|
|
let escaped = false;
|
|
for (let index = braceStart; index < html.length; index += 1) {
|
|
const char = html[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 html.slice(start, index + 1);
|
|
}
|
|
return '';
|
|
}
|
|
|
|
const ctPage = section('<!-- ==================== CONTAINER LIST ==================== -->', '<!-- ==================== CONTAINER MANIFEST ==================== -->');
|
|
const lastMilePage = section('<div class="page" id="page-last-mile-orders">', '<!-- ==================== OVERSEAS WAREHOUSE - 尾程订单详情');
|
|
const abnormalModal = section('<div class="modal-overlay" id="modal-ct-abnormal">', '<!-- ==================== 新增货柜费用 MODAL');
|
|
|
|
expect(/>货柜状态<\/th>\s*<th[^>]*>是否异常<\/th>/.test(ctPage), '货柜列表应在货柜状态后显示“是否异常”列');
|
|
expect(/>集装箱号<\/th>\s*<th[^>]*>货柜状态<\/th>\s*<th[^>]*>是否异常<\/th>/.test(lastMilePage), '尾程订单列顺序应为“集装箱号、货柜状态、是否异常”');
|
|
expect(/id="ct-abnormal-remark"[^>]*maxlength="200"/.test(abnormalModal), '异常处理弹窗应提供最多 200 字的备注输入');
|
|
expect(/id="ct-abnormal-submit"/.test(abnormalModal), '异常处理弹窗应提供动态提交按钮');
|
|
expect(/id="ct-abnormal-hint"/.test(abnormalModal), '异常处理弹窗应说明备注规则');
|
|
|
|
const names = [
|
|
'findContainerByNo',
|
|
'renderContainerStatusTag',
|
|
'getContainerAbnormalState',
|
|
'renderAbnormalTag',
|
|
'openCtAbnormalModal',
|
|
'saveCtAbnormalState'
|
|
];
|
|
const sources = Object.fromEntries(names.map(name => [name, functionSource(name)]));
|
|
names.forEach(name => expect(Boolean(sources[name]), `缺少 ${name} 函数`));
|
|
|
|
const ctRenderer = functionSource('renderCtList');
|
|
const lastMileRenderer = functionSource('renderLastMileOrders');
|
|
const detailRenderer = functionSource('renderLastMileOrderDetail');
|
|
expect(/openCtAbnormalModal/.test(ctRenderer), '货柜操作栏应提供异常标记或取消异常入口');
|
|
expect(/renderContainerStatusTag/.test(ctRenderer), '货柜列表应复用公共货柜状态标签');
|
|
expect(/findContainerByNo/.test(lastMileRenderer) && /renderContainerStatusTag/.test(lastMileRenderer), '尾程列表应实时查找并渲染货柜状态');
|
|
expect(/getContainerAbnormalState/.test(lastMileRenderer), '尾程列表应按集装箱号读取货柜异常状态');
|
|
expect(/renderAbnormalTag/.test(lastMileRenderer), '尾程列表应渲染异常状态标签');
|
|
expect(/colspan="33"/.test(lastMileRenderer), '尾程列表空状态应覆盖新增后的 33 列');
|
|
expect(/货柜异常/.test(detailRenderer) && /异常原因/.test(detailRenderer), '尾程详情应显示货柜异常状态和原因');
|
|
expect(/containerNo:'FFAU6685194'/.test(html), '应提供一条与货柜列表可见关联的尾程样例');
|
|
|
|
if (names.every(name => sources[name])) {
|
|
const elements = {
|
|
'ct-abnormal-title': {textContent: ''},
|
|
'ct-abnormal-container': {textContent: ''},
|
|
'ct-abnormal-label': {textContent: ''},
|
|
'ct-abnormal-required': {textContent: ''},
|
|
'ct-abnormal-hint': {textContent: ''},
|
|
'ct-abnormal-remark': {value: '', placeholder: '', focus() {}},
|
|
'ct-abnormal-submit': {textContent: '', className: ''}
|
|
};
|
|
const calls = {ct: 0, lmo: 0, toast: [], shown: [], hidden: []};
|
|
const context = {
|
|
ctListData: [{no: 'CT-TEST', status: '待装柜', isAbnormal: false, abnormalReason: '', abnormalLogs: []}],
|
|
lastMileOrderData: [{no: 'LMO-TEST', containerNo: 'CT-TEST'}],
|
|
currentCtAbnormalNo: '',
|
|
currentCtAbnormalAction: '',
|
|
document: {getElementById: id => elements[id] || null},
|
|
showModal: id => calls.shown.push(id),
|
|
hideModal: id => calls.hidden.push(id),
|
|
showToast: (message, type) => calls.toast.push({message, type}),
|
|
renderCtList: () => { calls.ct += 1; },
|
|
renderLastMileOrders: () => { calls.lmo += 1; },
|
|
getLastMileFilteredOrders: () => context.lastMileOrderData,
|
|
lmoNow: () => '2026-08-11 20:30',
|
|
lmoEsc: value => String(value == null ? '' : value)
|
|
.replace(/[&<>"']/g, char => ({'&': '&', '<': '<', '>': '>', '"': '"', "'": '''}[char]))
|
|
};
|
|
vm.createContext(context);
|
|
try {
|
|
vm.runInContext(names.map(name => sources[name]).join('\n'), context, {timeout: 1000});
|
|
|
|
expect(context.findContainerByNo(' ct-test ') === context.ctListData[0], '货柜查找应忽略首尾空格和大小写');
|
|
expect(/待装柜/.test(context.renderContainerStatusTag(context.findContainerByNo('CT-TEST').status)), '关联货柜应显示同步状态');
|
|
const unknownContainer = context.findContainerByNo('UNKNOWN');
|
|
expect(context.renderContainerStatusTag(unknownContainer && unknownContainer.status) === '—', '未关联货柜应显示“—”');
|
|
|
|
context.openCtAbnormalModal('CT-TEST');
|
|
expect(context.currentCtAbnormalAction === 'mark', '正常货柜应进入标记异常模式');
|
|
elements['ct-abnormal-remark'].value = '';
|
|
expect(context.saveCtAbnormalState() === true, '标记异常时空原因应允许提交');
|
|
expect(context.ctListData[0].isAbnormal === true, '标记后货柜应为异常状态');
|
|
expect(context.getContainerAbnormalState('CT-TEST').isAbnormal === true, '关联尾程应读取到异常状态');
|
|
expect(context.ctListData[0].abnormalLogs.length === 1, '标记异常应新增一条历史记录');
|
|
|
|
context.openCtAbnormalModal('CT-TEST');
|
|
expect(context.currentCtAbnormalAction === 'cancel', '异常货柜应进入取消异常模式');
|
|
elements['ct-abnormal-remark'].value = ' ';
|
|
expect(context.saveCtAbnormalState() === false, '取消异常时空备注应被拦截');
|
|
expect(context.ctListData[0].isAbnormal === true, '取消备注为空时应保持异常状态');
|
|
expect(calls.toast.some(item => /取消备注/.test(item.message)), '取消备注为空时应显示明确提示');
|
|
|
|
elements['ct-abnormal-remark'].value = '问题已核实并解除';
|
|
expect(context.saveCtAbnormalState() === true, '填写取消备注后应允许提交');
|
|
expect(context.ctListData[0].isAbnormal === false, '取消后货柜应恢复正常');
|
|
expect(context.getContainerAbnormalState('CT-TEST').isAbnormal === false, '关联尾程应同步恢复正常');
|
|
expect(context.ctListData[0].abnormalLogs.length === 2, '取消异常应保留第二条历史记录');
|
|
expect(calls.ct === 2 && calls.lmo === 2, '成功变更后应同时刷新货柜和尾程列表');
|
|
|
|
context.ctListData[0].isAbnormal = true;
|
|
context.ctListData[0].abnormalReason = '<异常原因>';
|
|
const tag = context.renderAbnormalTag(context.getContainerAbnormalState('CT-TEST'));
|
|
expect(/异常/.test(tag) && /<异常原因>/.test(tag), '异常标签应安全显示异常原因提示');
|
|
} catch (error) {
|
|
failures.push(`异常状态行为验证执行失败:${error.message}`);
|
|
}
|
|
}
|
|
|
|
if (failures.length) {
|
|
console.error('CONTAINER_ABNORMAL_SYNC_CHECK_FAIL');
|
|
failures.forEach(failure => console.error(`- ${failure}`));
|
|
process.exitCode = 1;
|
|
} else {
|
|
console.log('CONTAINER_ABNORMAL_SYNC_CHECK_PASS');
|
|
}
|