feat(test-case+bug): 测试用例模块 + Bug 模块完整实现
需求验收体系: - 测试用例:五态状态机(待执行/执行中/通过/失败/阻塞) - Bug:六态状态机(待修复/修复中/已修复/验证中/已关闭/已拒绝) - Bug 通过测试用例间接关联需求(不冗余存版本) - Bug 默认修复人 = 关联需求的开发任务负责人 - 测试进度 = 已执行用例/总用例, 通过率 = 通过/已执行 UI: - 版本详情页新增"测试用例" Tab + "BUG" Tab - 测试用例:统计栏+筛选+按需求分组列表+详情抽屉+提BUG入口 - Bug:统计栏+筛选+列表+详情抽屉(链式跳转用例→需求) - 概览胶囊"测试"阶段进度联动 - "与我相关"新增测试用例/Bug分组 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
69
apps/web/lib/test-case.ts
Normal file
69
apps/web/lib/test-case.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import type { Priority } from './derive';
|
||||
|
||||
export type TestCaseStatus = 'pending' | 'running' | 'passed' | 'failed' | 'blocked';
|
||||
|
||||
export interface TestCase {
|
||||
id: string;
|
||||
caseNo: string;
|
||||
requirementId: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
priority: Priority;
|
||||
assigneeId?: string;
|
||||
status: TestCaseStatus;
|
||||
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 };
|
||||
}
|
||||
Reference in New Issue
Block a user