Files
massive-cloud/client/src/components/demoFlowAggregation.test.ts

157 lines
6.9 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { aggregateDemoFlowResults, demoFlowStatusLabel, type DemoFlowSourceResult } from './demoFlowAggregation';
function result(id: number, index: number, status: string, resultData: Record<string, unknown>): DemoFlowSourceResult {
return { id, type: 'plugin', pluginId: `plugin-${id}`, status, resultData: { demoFlowIndex: index, ...resultData } };
}
const nameOf = (item: DemoFlowSourceResult) => `算法${item.id}`;
describe('aggregateDemoFlowResults', () => {
it('按全流程顺序合并测量项,并保留来源和算法内顺序', () => {
const model = aggregateDemoFlowResults([
result(20, 2, 'completed', { measurements: [{ name: 'B1', value: 2 }, { name: 'B2', value: 3 }] }),
result(10, 1, 'completed', { measurements: [{ name: 'A1', value: 1 }] }),
], nameOf);
expect(model.measurementRows.map((row) => [row.measurement.name, row.resultId, row.flowIndex, row.measurementIndex])).toEqual([
['A1', 10, 1, 0],
['B1', 20, 2, 0],
['B2', 20, 2, 1],
]);
});
it('原样保留空值、公差文本、基准长度和自定义结论文案,并忽略空测量', () => {
const measurement = {
name: '斜率', value: null, unit: 'mm', tolerance: 0.5, toleranceLabel: '+8.5/-3', baseline: 1000,
status: 'warning' as const, passLabel: '达标', warningLabel: '复核', failLabel: '未达标',
};
const model = aggregateDemoFlowResults([
result(10, 1, 'completed', { measurements: [] }),
result(20, 2, 'completed', { measurements: [measurement] }),
], nameOf);
expect(model.measurementRows).toHaveLength(1);
expect(model.measurementRows[0].measurement).toEqual(measurement);
});
it('默认不生成算法分组行,开启后按算法生成分组', () => {
const inputs = [
result(10, 1, 'completed', { measurements: [{ name: 'A', value: 1 }] }),
result(20, 2, 'completed', { measurements: [{ name: 'B', value: 2 }] }),
];
expect(aggregateDemoFlowResults(inputs, nameOf).tableRows.every((row) => row.kind === 'measurement')).toBe(true);
expect(aggregateDemoFlowResults(inputs, nameOf, { includeAlgorithmGroups: true }).tableRows.map((row) => row.kind)).toEqual([
'algorithm', 'measurement', 'algorithm', 'measurement',
]);
});
it('过滤参考项,同时保留其他状态和未设置状态的测量项', () => {
const model = aggregateDemoFlowResults([
result(10, 1, 'completed', {
measurements: [
{ name: '参考项', value: 0.1, status: 'info' },
{ name: '合格项', value: 1, status: 'pass' },
{ name: '警告项', value: 2, status: 'warning' },
{ name: '失败项', value: 3, status: 'fail' },
{ name: '无状态项', value: 4 },
],
}),
], nameOf);
expect(model.measurementRows.map((row) => row.measurement.name)).toEqual([
'合格项', '警告项', '失败项', '无状态项',
]);
expect(model.tableRows).toHaveLength(4);
});
it('过滤全流程指定的中间指标,同时保持其他有效测量项的原顺序', () => {
const model = aggregateDemoFlowResults([
result(10, 1, 'completed', {
measurements: [
{ name: '车体长度', value: 100, status: 'pass' },
{ name: '前后端平移差', value: 0.1, status: 'pass' },
{ name: '车体宽度', value: 20, status: 'warning' },
{ name: 'ICP fitness', value: 0.98, status: 'pass' },
{ name: '配准 RMSE', value: 0.02, status: 'pass' },
{ name: '车体高度', value: 30, status: 'fail' },
],
}),
], nameOf);
expect(model.measurementRows.map((row) => row.measurement.name)).toEqual([
'车体长度', '车体宽度', '车体高度',
]);
});
it('全流程结果使用统一结论文案,不采用算法自定义文案', () => {
expect(demoFlowStatusLabel({ status: 'pass' })).toBe('合格');
expect(demoFlowStatusLabel({ status: 'warning' })).toBe('警告');
expect(demoFlowStatusLabel({ status: 'fail' })).toBe('超差');
expect(demoFlowStatusLabel({ status: 'info' })).toBe('参考');
expect(demoFlowStatusLabel({})).toBe('参考');
const model = aggregateDemoFlowResults([
result(10, 1, 'completed', {
measurements: [
{ name: '通过项', value: 1, status: 'pass', passLabel: '通过' },
{ name: '警告项', value: 2, status: 'warning', warningLabel: '部分成功' },
{ name: '失败项', value: 3, status: 'fail', failLabel: '失败' },
],
}),
], nameOf);
expect(model.measurementRows.map((row) => demoFlowStatusLabel(row.measurement))).toEqual([
'合格', '警告', '超差',
]);
});
it('全部为参考项时进入空测量结果状态,且不生成算法分组行', () => {
const model = aggregateDemoFlowResults([
result(10, 1, 'completed', {
measurements: [
{ name: '配准误差 p95', value: 0.1, status: 'info' },
{ name: '配准误差均值', value: 0.05, status: 'info' },
],
}),
], nameOf, { includeAlgorithmGroups: true });
expect(model.measurementRows).toEqual([]);
expect(model.tableRows).toEqual([]);
});
it('让下载、图片、3D 和原始字段始终关联各自算法与结果 ID', () => {
const model = aggregateDemoFlowResults([
result(10, 1, 'completed', {
files: ['a.xlsx'], images: ['a.png'], overlay3d: { kind: 'ply', file: 'a.ply' }, extra: { source: 'a' },
}),
result(20, 2, 'completed', {
files: [{ name: '报表B', path: 'nested/b.xlsx' }], images: ['b.png'], overlay3d: { kind: 'none' }, extra: { source: 'b' },
}),
], nameOf);
expect(model.artifactGroups).toMatchObject([
{ resultId: 10, sourceAlgorithmId: 'plugin-10', files: [{ label: 'a.xlsx', path: 'a.xlsx' }], images: ['a.png'], extra: { source: 'a' } },
{ resultId: 20, sourceAlgorithmId: 'plugin-20', files: [{ label: '报表B', path: 'nested/b.xlsx' }], images: ['b.png'], extra: { source: 'b' } },
]);
});
it('为运行中、排队、失败和停止状态生成提示,同时保留部分完成结果', () => {
const model = aggregateDemoFlowResults([
result(10, 1, 'completed', { measurements: [{ name: '已完成', value: 1 }] }),
result(20, 2, 'processing', { progress: 0.42, stage: '计算中' }),
result(30, 3, 'pending', { stage: '等待资源' }),
result(40, 4, 'error', { error: '输入无效' }),
result(50, 5, 'cancelled', {}),
], nameOf);
expect(model.measurementRows.map((row) => row.measurement.name)).toEqual(['已完成']);
expect(model.statusNotices).toMatchObject([
{ resultId: 20, status: 'processing', progress: 0.42, detail: '计算中' },
{ resultId: 30, status: 'pending', detail: '等待资源' },
{ resultId: 40, status: 'error', detail: '输入无效' },
{ resultId: 50, status: 'cancelled' },
]);
});
});