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 { 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); 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'); }); 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); });