Files
ftb-project-management/apps/web/lib/xiaobao-warning-view.test.ts
Script Generator 04520bd8c5 feat(版本): 记录计划需求进展活动
关键改动:

- 记录产品/UI 计划需求覆盖进展,并写入工作活动与实体日志

- 日报和小宝预警补充需求进展活动证据

- 调整版本列表与需求覆盖面板展示,并补充测试

Co-Authored-By: Codex GPT-5 <codex@openai.com>
2026-06-30 15:35:36 +08:00

160 lines
5.5 KiB
TypeScript
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 test from 'node:test';
import type { VersionWithContext } from './derive';
import type { XiaobaoVersionRisk } from './xiaobao-risk';
import {
filterXiaobaoRiskWarnings,
filterXiaobaoWarningVersions,
formatRemainingWork,
getRiskScoreTone,
getXiaobaoWarningRiskCount,
sanitizeRiskInsight,
} from './xiaobao-warning-view';
function version(patch: Partial<VersionWithContext> = {}): VersionWithContext {
return {
id: 'ver-1',
name: 'V1.0',
status: 'developing',
releaseDate: null,
createdAt: '2026-06-29T00:00:00.000Z',
productId: 'prod-1',
productName: 'FTB',
projectId: 'proj-1',
projectName: 'Project',
expectedReleaseDate: '2026-07-05T10:00:00.000Z',
members: [{ name: 'Alice', role: 'frontend' }],
...patch,
};
}
function risk(patch: Partial<XiaobaoVersionRisk> = {}): XiaobaoVersionRisk {
return {
versionId: 'ver-1',
versionName: 'V1.0',
productId: 'prod-1',
productName: 'Product A',
projectId: 'proj-1',
projectName: 'Project A',
riskScore: 68,
riskLevel: 'attention',
expectedReleaseDate: '2026-07-05',
confidence: 80,
confidenceLevel: 'high',
delayDays: 0,
remainingWorkHours: 8,
reasons: [],
silentRisks: [],
signals: {
unfinishedCount: 1,
openBugCount: 0,
criticalBugCount: 0,
failedTestCount: 0,
blockedCount: 0,
silentRiskCount: 0,
},
currentSnapshot: {
versionId: 'ver-1',
date: '2026-06-30',
riskScore: 68,
riskLevel: 'attention',
openBugCount: 0,
criticalBugCount: 0,
failedTestCount: 0,
blockedCount: 0,
silentRiskCount: 0,
confidence: 80,
createdAt: '2026-06-30T10:00:00.000Z',
},
trend: { direction: 'flat', delta: 0, summary: 'Risk is stable.', pattern: 'stable' },
...patch,
} as XiaobaoVersionRisk;
}
test('filterXiaobaoWarningVersions lets managers see every unfinished version', () => {
const result = filterXiaobaoWarningVersions(
[
version({ id: 'ver-1', status: 'developing', members: [{ name: 'Alice', role: 'frontend' }] }),
version({ id: 'ver-2', status: 'planned', members: [{ name: 'Bob', role: 'testing' }] }),
version({ id: 'ver-3', status: 'released', members: [{ name: 'Alice', role: 'frontend' }] }),
],
{ canManage: true, userName: 'Alice' },
);
assert.deepEqual(result.map((item) => item.id), ['ver-1', 'ver-2']);
});
test('filterXiaobaoWarningVersions limits non-managers to versions where they are a member', () => {
const result = filterXiaobaoWarningVersions(
[
version({ id: 'ver-1', status: 'developing', members: [{ name: 'Alice', role: 'frontend' }] }),
version({ id: 'ver-2', status: 'developing', members: [{ name: 'Bob', role: 'testing' }] }),
version({ id: 'ver-3', status: 'closed', members: [{ name: 'Alice', role: 'frontend' }] }),
],
{ canManage: false, userName: 'Alice' },
);
assert.deepEqual(result.map((item) => item.id), ['ver-1']);
});
test('filterXiaobaoRiskWarnings hides on_track risks and badge count follows visible risks', () => {
const risks = [
risk({ versionId: 'ver-1', riskLevel: 'on_track', riskScore: 10 }),
risk({ versionId: 'ver-2', riskLevel: 'attention', riskScore: 38 }),
risk({ versionId: 'ver-3', riskLevel: 'blocked', riskScore: 100 }),
];
assert.deepEqual(filterXiaobaoRiskWarnings(risks).map((item) => item.versionId), ['ver-2', 'ver-3']);
assert.equal(getXiaobaoWarningRiskCount(risks), 2);
});
test('filterXiaobaoRiskWarnings filters by product project and risk tier', () => {
const risks = [
risk({ versionId: 'ver-1', productId: 'prod-1', projectId: 'proj-1', riskLevel: 'attention', riskScore: 38 }),
risk({ versionId: 'ver-2', productId: 'prod-1', projectId: 'proj-2', riskLevel: 'likely_delayed', riskScore: 88 }),
risk({ versionId: 'ver-3', productId: 'prod-2', projectId: 'proj-3', riskLevel: 'blocked', riskScore: 100 }),
];
assert.deepEqual(
filterXiaobaoRiskWarnings(risks, { productId: 'prod-1' }).map((item) => item.versionId),
['ver-1', 'ver-2'],
);
assert.deepEqual(
filterXiaobaoRiskWarnings(risks, { productId: 'prod-1', projectId: 'proj-2' }).map((item) => item.versionId),
['ver-2'],
);
assert.deepEqual(
filterXiaobaoRiskWarnings(risks, { riskFilter: 'high' }).map((item) => item.versionId),
['ver-2', 'ver-3'],
);
});
test('formatRemainingWork keeps hours and adds work-day conversion', () => {
assert.equal(formatRemainingWork(0), '0h / 0天');
assert.equal(formatRemainingWork(8), '8h / 1天');
assert.equal(formatRemainingWork(12), '12h / 1.5天');
assert.equal(formatRemainingWork(0.5), '0.5h / 0.1天');
});
test('getRiskScoreTone follows Xiaobao warning score thresholds', () => {
assert.equal(getRiskScoreTone(75), 'danger');
assert.equal(getRiskScoreTone(55), 'warn');
assert.equal(getRiskScoreTone(54), 'ok');
});
test('sanitizeRiskInsight filters invalid page refresh suggested actions', () => {
const result = sanitizeRiskInsight({
summary: '风险上升',
why: ['P1 Bug 增加'],
forecast: '预计延期 1 天',
suggestedActions: [
'手动触发页面刷新,等待小宝预警自动更新',
'优先处理 3 个 P1 Bug并同步测试负责人复测',
],
ownerHints: ['研发负责人协调修复顺序'],
generatedAt: '2026-06-30T10:00:00.000Z',
});
assert.deepEqual(result.suggestedActions, ['优先处理 3 个 P1 Bug并同步测试负责人复测']);
});