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

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

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

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

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

196 lines
6.7 KiB
TypeScript

import test from 'node:test';
import assert from 'node:assert/strict';
import * as versionPlan from './version-plan';
import type { VersionPlan } from './version-plan';
function plan(patch: Partial<VersionPlan>): VersionPlan {
return {
id: 'plan-1',
versionId: 'version-1',
type: 'product',
title: '产品方案',
owner: 'PM',
startTime: '2026-06-25T09:00',
endTime: '2026-06-25T18:00',
status: 'pending',
createdAt: '2026-06-25',
addedBy: 'PM',
...patch,
};
}
test('sortPlansNewestFirst places newly created plans before older plans', () => {
const sortPlansNewestFirst = (versionPlan as any).sortPlansNewestFirst as undefined | ((plans: VersionPlan[]) => VersionPlan[]);
assert.equal(typeof sortPlansNewestFirst, 'function');
const plans = [
plan({ id: 'plan-100', createdAt: '2026-06-26' }),
plan({ id: 'manual-old', createdAt: '2026-06-25' }),
plan({ id: 'plan-300', createdAt: '2026-06-26' }),
];
const sorted = (sortPlansNewestFirst as (items: VersionPlan[]) => VersionPlan[])(plans);
assert.deepEqual(sorted.map((item) => item.id), ['plan-300', 'plan-100', 'manual-old']);
assert.deepEqual(plans.map((item) => item.id), ['plan-100', 'manual-old', 'plan-300']);
});
test('collects logs across plans with plan context and newest first', () => {
const getPlanLogsForPlans = (versionPlan as any).getPlanLogsForPlans as undefined | ((plans: VersionPlan[]) => Array<{
id: string;
planId: string;
planTitle: string;
createdAt: string;
}>);
assert.equal(typeof getPlanLogsForPlans, 'function');
const logs = getPlanLogsForPlans!([
plan({
id: 'plan-a',
title: '产品方案第一天',
logs: [
{
id: 'log-old',
type: 'system',
title: '开始计划',
actor: 'PM',
createdAt: '2026-06-29T09:00:00.000Z',
},
],
}),
plan({
id: 'plan-b',
title: '产品方案第二天',
logs: [
{
id: 'log-new',
type: 'requirement_progress',
title: '更新需求进度',
actor: 'PM',
createdAt: '2026-06-29T15:00:00.000Z',
},
],
}),
]);
assert.deepEqual(logs.map((log) => ({
id: log.id,
planId: log.planId,
planTitle: log.planTitle,
})), [
{ id: 'log-new', planId: 'plan-b', planTitle: '产品方案第二天' },
{ id: 'log-old', planId: 'plan-a', planTitle: '产品方案第一天' },
]);
});
test('derives requirement coverage from new records and legacy completed ids', () => {
const getRequirementCoverageStatus = (versionPlan as any).getRequirementCoverageStatus as undefined | ((item: VersionPlan, requirementId: string) => string);
const getRequirementCoverageSummary = (versionPlan as any).getRequirementCoverageSummary as undefined | ((item: VersionPlan) => {
total: number;
completed: number;
partial: number;
notStarted: number;
percent: number;
});
assert.equal(typeof getRequirementCoverageStatus, 'function');
assert.equal(typeof getRequirementCoverageSummary, 'function');
const item = plan({
linkedRequirementIds: ['r1', 'r2', 'r3', 'r4'],
completedRequirementIds: ['r2'],
requirementCoverage: [
{
requirementId: 'r1',
status: 'partial',
completedContent: '完成主流程原型',
remainingContent: '补充异常状态',
updatedAt: '2026-06-29T09:00:00.000Z',
updatedBy: 'PM',
},
{
requirementId: 'r3',
status: 'completed',
completedContent: '已覆盖列表和详情',
updatedAt: '2026-06-29T10:00:00.000Z',
updatedBy: 'PM',
},
],
} as Partial<VersionPlan>);
assert.equal(getRequirementCoverageStatus!(item, 'r1'), 'partial');
assert.equal(getRequirementCoverageStatus!(item, 'r2'), 'completed');
assert.equal(getRequirementCoverageStatus!(item, 'r4'), 'not_started');
assert.deepEqual(getRequirementCoverageSummary!(item), {
total: 4,
completed: 2,
partial: 1,
notStarted: 1,
percent: 50,
});
});
test('updates requirement coverage, syncs legacy completed ids, and creates a plan log', () => {
const updateRequirementCoverage = (versionPlan as any).updateRequirementCoverage as undefined | ((item: VersionPlan, input: {
requirementId: string;
status: string;
completedContent?: string;
remainingContent?: string;
updatedBy: string;
updatedAt: string;
requirementCode?: string;
requirementTitle?: string;
}) => any);
assert.equal(typeof updateRequirementCoverage, 'function');
const next = updateRequirementCoverage!(plan({
linkedRequirementIds: ['r1'],
completedRequirementIds: ['r1'],
}), {
requirementId: 'r1',
status: 'partial',
completedContent: '完成移动端主流程',
remainingContent: 'PC 端筛选规则未完成',
updatedBy: 'PM',
updatedAt: '2026-06-29T12:00:00.000Z',
requirementCode: 'QY0001',
requirementTitle: '需求池筛选',
});
assert.deepEqual(next.completedRequirementIds, []);
assert.equal(next.requirementCoverage?.[0]?.status, 'partial');
assert.equal(next.logs?.length, 1);
assert.equal(next.logs?.[0]?.type, 'requirement_progress');
assert.equal(next.logs?.[0]?.actor, 'PM');
assert.equal(next.logs?.[0]?.requirementCode, 'QY0001');
assert.equal(next.logs?.[0]?.completedContent, '完成移动端主流程');
assert.equal(next.logs?.[0]?.remainingContent, 'PC 端筛选规则未完成');
});
test('validates requirement coverage record drafts by status', () => {
const canSaveRequirementCoverageDraft = (versionPlan as any).canSaveRequirementCoverageDraft as undefined | ((
status: string,
completedContent?: string,
remainingContent?: string,
) => boolean);
assert.equal(typeof canSaveRequirementCoverageDraft, 'function');
assert.equal(canSaveRequirementCoverageDraft!('completed'), true);
assert.equal(canSaveRequirementCoverageDraft!('partial', '完成主流程', ''), false);
assert.equal(canSaveRequirementCoverageDraft!('partial', '完成主流程', '补充异常状态'), true);
assert.equal(canSaveRequirementCoverageDraft!('not_started'), false);
});
test('hides coverage record action after a requirement is completed', () => {
const canOpenRequirementCoverageRecord = (versionPlan as any).canOpenRequirementCoverageRecord as undefined | ((
status: string,
canEdit: boolean,
) => boolean);
assert.equal(typeof canOpenRequirementCoverageRecord, 'function');
assert.equal(canOpenRequirementCoverageRecord!('not_started', true), true);
assert.equal(canOpenRequirementCoverageRecord!('partial', true), true);
assert.equal(canOpenRequirementCoverageRecord!('completed', true), false);
assert.equal(canOpenRequirementCoverageRecord!('partial', false), false);
});