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'], requirementCoverage: [ { requirementId: 'r1', status: 'completed', currentWorkStartedAt: '2026-06-29T09:30:00.000Z', updatedAt: '2026-06-28T12:00:00.000Z', updatedBy: 'PM', }, ], }), { 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.requirementCoverage?.[0]?.currentWorkStartedAt, undefined); 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 端筛选规则未完成'); assert.equal(next.logs?.[0]?.workStartedAt, '2026-06-29T09:30:00.000Z'); }); test('starts requirement coverage work while preserving existing progress', () => { const startRequirementCoverageWork = (versionPlan as any).startRequirementCoverageWork as undefined | ((item: VersionPlan, input: { requirementId: string; startedAt: string; updatedBy: string; }) => any); assert.equal(typeof startRequirementCoverageWork, 'function'); const next = startRequirementCoverageWork!(plan({ requirementCoverage: [ { requirementId: 'r1', status: 'partial', completedContent: '完成主流程', remainingContent: '补充异常状态', updatedAt: '2026-06-28T12:00:00.000Z', updatedBy: 'PM', }, ], }), { requirementId: 'r1', startedAt: '2026-06-29T09:30:00.000Z', updatedBy: 'PM', }); assert.equal(next.requirementCoverage?.[0]?.status, 'partial'); assert.equal(next.requirementCoverage?.[0]?.completedContent, '完成主流程'); assert.equal(next.requirementCoverage?.[0]?.remainingContent, '补充异常状态'); assert.equal(next.requirementCoverage?.[0]?.currentWorkStartedAt, '2026-06-29T09:30:00.000Z'); assert.equal(next.requirementCoverage?.[0]?.updatedBy, 'PM'); }); test('validates requirement coverage record drafts by started work session', () => { const canSaveRequirementCoverageDraft = (versionPlan as any).canSaveRequirementCoverageDraft as undefined | (( status: string, completedContent?: string, remainingContent?: string, workStartedAt?: string, ) => boolean); assert.equal(typeof canSaveRequirementCoverageDraft, 'function'); assert.equal(canSaveRequirementCoverageDraft!('completed'), false); assert.equal(canSaveRequirementCoverageDraft!('completed', undefined, undefined, '2026-06-29T09:30:00.000Z'), true); assert.equal(canSaveRequirementCoverageDraft!('partial', '完成主流程', '', '2026-06-29T09:30:00.000Z'), false); assert.equal(canSaveRequirementCoverageDraft!('partial', '完成主流程', '补充异常状态'), false); assert.equal(canSaveRequirementCoverageDraft!('partial', '完成主流程', '补充异常状态', '2026-06-29T09:30:00.000Z'), true); assert.equal(canSaveRequirementCoverageDraft!('not_started', undefined, undefined, '2026-06-29T09:30:00.000Z'), 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); }); test('keeps research direction presets visible while disabling selected presets', () => { const getResearchDirectionPresetOptions = (versionPlan as any).getResearchDirectionPresetOptions as undefined | (( tasks: Array<{ title: string }>, ) => Array<{ title: string; disabled: boolean }>); assert.equal(typeof getResearchDirectionPresetOptions, 'function'); const options = getResearchDirectionPresetOptions!([{ title: '竞品分析' }]); assert.ok(options.length >= 6); assert.equal(options.find((option) => option.title === '竞品分析')?.disabled, true); assert.equal(options.find((option) => option.title === '用户访谈')?.disabled, false); });