157 lines
6.9 KiB
TypeScript
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' },
|
|
]);
|
|
});
|
|
});
|