aps-agent/tests/node/planner-data-card.test.mjs

147 lines
7.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { createRequire } from 'node:module';
import test from 'node:test';
const requireWeb = createRequire(new URL('../../apps/web/package.json', import.meta.url));
const ts = requireWeb('typescript');
const React = requireWeb('react');
const { renderToStaticMarkup } = requireWeb('react-dom/server');
const source = readFileSync(new URL('../../apps/web/src/chat/PlanningDataCard.tsx', import.meta.url), 'utf8');
const compiled = ts.transpileModule(source, { compilerOptions: {
jsx: ts.JsxEmit.ReactJSX, module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022,
}}).outputText;
const exported = {};
new Function('require', 'exports', compiled)(requireWeb, exported);
const block = { type: 'folder-pack', props: { canSchedule: true, projectName: '客户订单',
coverageDetail: [{ key: 'orders', count: 6 }, { key: 'equipment', count: 57 }],
files: [{ name: '订单.xlsx', okCount: 165, samples: ['type=RAW_MATERIAL,stock=0'] }],
}};
const render = extra => renderToStaticMarkup(React.createElement(exported.PlanningDataCard, {
block, active: true, formatSize: String, onSend() {}, ...extra,
}));
test('data check explains the stage and keeps raw fields out of the summary', () => {
const html = render({});
assert.match(html, /尚未生成方案/);
assert.match(html, /确认数据,生成方案/);
assert.match(html, /<details/);
assert.doesNotMatch(html, /RAW_MATERIAL|P2|工程目录数据包/);
});
test('analysis summary keeps the first screen to conclusion, key counts, and next step', () => {
const html = render({ block: { ...block, props: { ...block.props, reviewOnly: true, canSchedule: false,
analysisSummary: {
headline: '资料已读取,确认采用后才会写入项目主数据。',
status: 'review', statusLabel: '待确认采用',
summaryLine: '订单 5 · 产品和物料 45 · 加工步骤 15 · 设备 15',
metrics: [
{ key: 'orders', label: '订单', value: 5 },
{ key: 'materials', label: '产品和物料', value: 45 },
{ key: 'routing', label: '加工步骤', value: 15 },
{ key: 'equipment', label: '设备', value: 15 },
],
issues: ['喷涂没有对应技能人员'], issueOverflow: 2, nextStep: null,
},
nextActions: [{ id: 'review_missing', label: '查看缺少资料', command: '排产还缺什么', enabled: true }],
} } });
assert.match(html, /确认采用后才会写入项目主数据/);
assert.match(html, /订单 5/);
assert.match(html, /喷涂没有对应技能人员/);
assert.match(html, /另有 2 项/);
assert.match(html, /查看文件、订单与检查明细/);
assert.doesNotMatch(html, /全部工作表已逐项检查/);
assert.doesNotMatch(html, /查看缺少资料/);
});
test('old records cannot restart scheduling', () => {
const html = render({ active: false });
assert.match(html, /历史记录/);
assert.equal((html.match(/aria-label="查看[^"]+明细"/g) || []).length, 4);
assert.doesNotMatch(html, /确认数据,生成方案/);
});
test('adoption review keeps one disabled scheduling entry instead of a second call to action', () => {
const html = render({ block: { ...block, props: { ...block.props, reviewOnly: true, canSchedule: false,
entityCounts: { personnel: 12, wip: 3, sandboxOrders: 1 },
analysisSummary: {
headline: '资料已读取,确认采用后才会写入项目主数据。',
status: 'review', statusLabel: '待核对采用',
summaryLine: '订单记录 5 · 产品和物料 45 · 加工步骤 15 · 设备记录 15',
metrics: [
{ key: 'orders', label: '订单记录', value: 5 },
{ key: 'materials', label: '产品和物料', value: 45 },
{ key: 'routing', label: '加工步骤', value: 15 },
{ key: 'equipment', label: '设备记录', value: 15 },
],
issues: [], issueOverflow: 0,
nextStep: { label: '开始排产', command: '立即排产', enabled: false,
reason: '需先确认采用本次资料;采用前不会写入主数据,也不会排产。' },
recap: '现状:本次只完成核对,尚未写入主数据;请在下方确认卡核对并采用。',
},
nextActions: [{ id: 'review_missing', label: '查看缺少资料', command: '排产还缺什么', enabled: true }],
} } });
assert.match(html, /排产资料核对/);
assert.match(html, /<button[^>]*disabled[^>]*>开始排产<\/button>/);
assert.match(html, /需先确认采用本次资料/);
assert.match(html, /尚未写入主数据/);
assert.match(html, /人员 12 名/);
assert.doesNotMatch(html, /查看缺少资料/);
});
test('existing plan offers review instead of first scheduling', () => {
const html = render({ viewCommand: '查看排产结果' });
assert.match(html, /查看当前方案/);
assert.doesNotMatch(html, /确认数据,生成方案|尚未生成方案/);
});
test('unknown server state cannot offer a new schedule or label a current card historical', () => {
const html = render({ actionsReady: false });
assert.match(html, /正在核对当前方案状态/);
assert.equal((html.match(/aria-label="查看[^\"]+明细"/g) || []).length, 4);
assert.doesNotMatch(html, /历史记录|尚未生成方案/);
});
const details = props => renderToStaticMarkup(React.createElement(exported.PlanningDataDetails, {
onRecheck() {}, onClose() {}, ...props,
}));
test('detail panel renders supplied columns and ten actual rows with accessible search and pagination', () => {
const rows = Array.from({ length: 24 }, (_, index) => ({ code: `EQ-${index}`, available: index !== 0, speed: index }));
const html = details({ view: { label: '设备', columns: [
{ key: 'code', label: '设备编号' }, { key: 'available', label: '可生产' }, { key: 'speed', label: '速度' },
], rows, total: 24 }, snapshotMode: 'current', sourceSha: 'fingerprint' });
assert.match(html, /当前主数据/);
assert.match(html, /文件指纹:fingerprint/);
assert.match(html, /搜索设备/);
assert.match(html, /type="search"/);
assert.match(html, /第 1 \/ 3 页/);
assert.match(html, /设备编号/);
assert.match(html, /EQ-9/);
assert.doesNotMatch(html, /EQ-10/);
assert.match(html, /<td>否<\/td><td>0<\/td>/);
});
test('old missing details and a genuinely empty dataset are distinct', () => {
const old = details({});
assert.match(old, /旧记录缺明细,请重新检查资料/);
assert.match(old, /重新检查资料/);
assert.doesNotMatch(old, /<table/);
const empty = details({ view: { label: '订单', columns: [{ key: 'orderNo', label: '订单编号' }], rows: [], total: 0 } });
assert.match(empty, /这项资料没有记录/);
assert.doesNotMatch(empty, /旧记录缺明细/);
});
test('source snapshot warns when rows do not agree with the supplied total', () => {
const html = details({ snapshotMode: 'source', view: { label: '材料', columns: [{ key: 'code', label: '编码' }],
rows: [{ code: 'ACTUAL-ROW' }], total: 2 } });
assert.match(html, /原文件资料/);
assert.match(html, /数量与汇总不一致/);
assert.match(html, /ACTUAL-ROW/);
});
test('recovery proposal explains each existing record without acting on it', () => {
const html = render({ block: { ...block, props: { ...block.props, recoveryProposed: true,
intakeConflicts: [{ orderNo: 'ORDER-A', reason: '旧记录误入正式池', canRecover: true },
{ orderNo: 'ORDER-B', reason: '已有执行记录', canRecover: false }] } } });
assert.match(html, /ORDER-A:旧记录误入正式池/);
assert.match(html, /可在确认后整理/);
assert.match(html, /需要人工核实,暂不能自动整理/);
assert.match(html, /确认前,已有记录保持不变/);
});