Files
ftb-project-management/apps/web/lib/test-case.ts
Script Generator 8b3b3b7e21 feat(test-case): 测试耗时自动计算(逻辑同开发任务)
- TestCase 新增 startedAt / completedAt 字段
- 流转到"执行中"时记录 startedAt
- 流转到"通过/失败/阻塞"时记录 completedAt
- 回退到执行中时清除 completedAt
- 测试耗时 = startedAt → completedAt 工作日 × 8h
- 列表行显示耗时,详情抽屉显示开始/完成/耗时

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-12 15:38:48 +08:00

73 lines
2.4 KiB
TypeScript

import type { Priority } from './derive';
export type TestCaseStatus = 'pending' | 'running' | 'passed' | 'failed' | 'blocked';
export interface TestCase {
id: string;
caseNo: string;
versionId: string;
requirementId?: string;
title: string;
description?: string;
priority: Priority;
assigneeId?: string;
status: TestCaseStatus;
startedAt?: string;
completedAt?: string;
executedAt?: string;
executedBy?: string;
failReason?: string;
blockReason?: string;
createdBy: string;
createdAt: string;
updatedAt: string;
}
export const TEST_CASE_STATUS_LABEL: Record<TestCaseStatus, string> = {
pending: '待执行',
running: '执行中',
passed: '通过',
failed: '失败',
blocked: '阻塞',
};
export const TEST_CASE_STATUS_COLOR: Record<TestCaseStatus, string> = {
pending: 'bg-zinc-100 text-zinc-600',
running: 'bg-blue-50 text-blue-600',
passed: 'bg-emerald-50 text-emerald-600',
failed: 'bg-red-50 text-red-600',
blocked: 'bg-orange-50 text-orange-600',
};
export const TC_ALLOWED_TRANSITIONS: Record<TestCaseStatus, TestCaseStatus[]> = {
pending: ['running'],
running: ['passed', 'failed', 'blocked'],
passed: ['running'],
failed: ['running'],
blocked: ['running'],
};
export function canTcTransition(from: TestCaseStatus, to: TestCaseStatus): boolean {
return TC_ALLOWED_TRANSITIONS[from].includes(to);
}
export function generateCaseNo(existingCases: TestCase[]): string {
const maxNum = existingCases.reduce((max, c) => {
const num = parseInt(c.caseNo.replace('TC-', ''), 10);
return isNaN(num) ? max : Math.max(max, num);
}, 0);
return `TC-${String(maxNum + 1).padStart(3, '0')}`;
}
export function calcTestProgress(cases: TestCase[]): { total: number; executed: number; passed: number; failed: number; blocked: number; passRate: number; completionRate: number } {
const total = cases.length;
if (total === 0) return { total: 0, executed: 0, passed: 0, failed: 0, blocked: 0, passRate: 0, completionRate: 0 };
const passed = cases.filter((c) => c.status === 'passed').length;
const failed = cases.filter((c) => c.status === 'failed').length;
const blocked = cases.filter((c) => c.status === 'blocked').length;
const executed = passed + failed + blocked;
const passRate = (passed + failed) > 0 ? Math.round((passed / (passed + failed)) * 100) : 0;
const completionRate = Math.round((executed / total) * 100);
return { total, executed, passed, failed, blocked, passRate, completionRate };
}