@zjex/git-workflow 0.2.23 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/.github/workflows/deploy-docs.yml +68 -0
  2. package/.github/workflows/test.yml +53 -0
  3. package/.husky/pre-commit +19 -0
  4. package/README.md +74 -1013
  5. package/TESTING.md +436 -0
  6. package/dist/index.js +104 -14
  7. package/docs/.vitepress/cache/deps/_metadata.json +52 -0
  8. package/docs/.vitepress/cache/deps/chunk-2CLQ7TTZ.js +9719 -0
  9. package/docs/.vitepress/cache/deps/chunk-2CLQ7TTZ.js.map +7 -0
  10. package/docs/.vitepress/cache/deps/chunk-LE5NDSFD.js +12824 -0
  11. package/docs/.vitepress/cache/deps/chunk-LE5NDSFD.js.map +7 -0
  12. package/docs/.vitepress/cache/deps/package.json +3 -0
  13. package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +4505 -0
  14. package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +7 -0
  15. package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +583 -0
  16. package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +7 -0
  17. package/docs/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js +1352 -0
  18. package/docs/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js.map +7 -0
  19. package/docs/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js +1665 -0
  20. package/docs/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js.map +7 -0
  21. package/docs/.vitepress/cache/deps/vitepress___minisearch.js +1813 -0
  22. package/docs/.vitepress/cache/deps/vitepress___minisearch.js.map +7 -0
  23. package/docs/.vitepress/cache/deps/vue.js +347 -0
  24. package/docs/.vitepress/cache/deps/vue.js.map +7 -0
  25. package/docs/.vitepress/cache/deps_temp_44e2fb0f/chunk-2CLQ7TTZ.js +9719 -0
  26. package/docs/.vitepress/cache/deps_temp_44e2fb0f/chunk-2CLQ7TTZ.js.map +7 -0
  27. package/docs/.vitepress/cache/deps_temp_44e2fb0f/chunk-LE5NDSFD.js +12824 -0
  28. package/docs/.vitepress/cache/deps_temp_44e2fb0f/chunk-LE5NDSFD.js.map +7 -0
  29. package/docs/.vitepress/cache/deps_temp_44e2fb0f/package.json +3 -0
  30. package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___@vue_devtools-api.js +4505 -0
  31. package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___@vue_devtools-api.js.map +7 -0
  32. package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___@vueuse_core.js +583 -0
  33. package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___@vueuse_core.js.map +7 -0
  34. package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___@vueuse_integrations_useFocusTrap.js +1352 -0
  35. package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___@vueuse_integrations_useFocusTrap.js.map +7 -0
  36. package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___mark__js_src_vanilla__js.js +1665 -0
  37. package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___mark__js_src_vanilla__js.js.map +7 -0
  38. package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___minisearch.js +1813 -0
  39. package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___minisearch.js.map +7 -0
  40. package/docs/.vitepress/cache/deps_temp_44e2fb0f/vue.js +347 -0
  41. package/docs/.vitepress/cache/deps_temp_44e2fb0f/vue.js.map +7 -0
  42. package/docs/.vitepress/config.ts +167 -0
  43. package/docs/.vitepress/theme/custom.css +39 -0
  44. package/docs/.vitepress/theme/index.ts +4 -0
  45. package/docs/README.md +82 -0
  46. package/docs/commands/branch.md +468 -0
  47. package/docs/commands/commit.md +554 -0
  48. package/docs/commands/config.md +346 -0
  49. package/docs/commands/index.md +312 -0
  50. package/docs/commands/interactive.md +384 -0
  51. package/docs/commands/release.md +300 -0
  52. package/docs/commands/stash.md +309 -0
  53. package/docs/commands/tag.md +278 -0
  54. package/docs/commands/update.md +347 -0
  55. package/docs/config/ai-config.md +160 -0
  56. package/docs/config/branch-config.md +133 -0
  57. package/docs/config/commit-config.md +185 -0
  58. package/docs/config/config-file.md +776 -0
  59. package/docs/config/examples.md +279 -0
  60. package/docs/config/index.md +478 -0
  61. package/docs/guide/ai-commit.md +576 -0
  62. package/docs/guide/basic-usage.md +522 -0
  63. package/docs/guide/best-practices.md +426 -0
  64. package/docs/guide/branch-management.md +712 -0
  65. package/docs/guide/getting-started.md +294 -0
  66. package/docs/guide/index.md +168 -0
  67. package/docs/guide/installation.md +449 -0
  68. package/docs/guide/release-management.md +744 -0
  69. package/docs/guide/stash-management.md +608 -0
  70. package/docs/guide/tag-management.md +614 -0
  71. package/docs/index.md +205 -0
  72. package/docs/public/favicon.svg +21 -0
  73. package/docs/public/hero-logo.svg +43 -0
  74. package/docs/public/logo.svg +20 -0
  75. package/package.json +19 -3
  76. package/scripts/publish.js +55 -8
  77. package/scripts/publish.sh +20 -2
  78. package/scripts/release.sh +20 -2
  79. package/scripts/update-test-count.js +55 -0
  80. package/src/ai-service.ts +101 -15
  81. package/src/commands/init.ts +18 -0
  82. package/src/commands/tag.ts +1 -1
  83. package/src/config.ts +1 -0
  84. package/tests/COVERAGE_REPORT.md +222 -0
  85. package/tests/QUICK_START.md +242 -0
  86. package/tests/README.md +119 -0
  87. package/tests/TEST_SUMMARY.md +330 -0
  88. package/tests/ai-service.test.ts +705 -0
  89. package/tests/branch.test.ts +255 -0
  90. package/tests/commit.test.ts +85 -0
  91. package/tests/config.test.ts +311 -0
  92. package/tests/help.test.ts +134 -0
  93. package/tests/init.test.ts +582 -0
  94. package/tests/release.test.ts +333 -0
  95. package/tests/setup.ts +21 -0
  96. package/tests/stash.test.ts +376 -0
  97. package/tests/tag.test.ts +396 -0
  98. package/tests/update-notifier.test.ts +384 -0
  99. package/tests/update.test.ts +402 -0
  100. package/tests/utils.test.ts +229 -0
  101. package/vitest.config.ts +22 -0
  102. package/zjex-logo.svg +22 -0
  103. package/zjex-optimized.svg +34 -0
  104. package/zjex.svg +1 -0
package/src/ai-service.ts CHANGED
@@ -60,11 +60,66 @@ function getGitDiff(): string {
60
60
  /**
61
61
  * 构建 AI prompt
62
62
  */
63
- function buildPrompt(diff: string, language: string): string {
63
+ function buildPrompt(diff: string, language: string, detailedDescription: boolean = false): string {
64
64
  const isZh = language === "zh-CN";
65
65
 
66
- const systemPrompt = isZh
67
- ? `你是一个专业的 Git commit message 生成助手。请根据提供的 git diff 生成符合 Conventional Commits 规范的 commit message。
66
+ if (detailedDescription) {
67
+ // 详细模式:生成包含修改点的完整 commit message
68
+ const systemPrompt = isZh
69
+ ? `你是一个专业的 Git commit message 生成助手。请根据提供的 git diff 生成符合 Conventional Commits 规范的详细 commit message。
70
+
71
+ 格式要求:
72
+ 1. 第一行:<type>(<scope>): <subject>
73
+ 2. 空行
74
+ 3. 详细描述:列出主要修改点,每个修改点一行,以 "- " 开头
75
+
76
+ 规则:
77
+ - type 必须是以下之一:feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
78
+ - scope 是可选的,表示影响范围
79
+ - subject 用中文描述,简洁明了,不超过 50 字
80
+ - 详细描述要列出 3-6 个主要修改点,每个修改点简洁明了
81
+ - 如果修改较少,可以只列出 2-3 个修改点
82
+ - 不要有其他解释或多余内容
83
+
84
+ 示例:
85
+ feat(auth): 添加用户登录功能
86
+
87
+ - 实现用户名密码登录接口
88
+ - 添加登录状态验证中间件
89
+ - 完善登录错误处理逻辑
90
+ - 更新用户认证相关文档`
91
+ : `You are a professional Git commit message generator. Generate a detailed commit message following Conventional Commits specification based on the provided git diff.
92
+
93
+ Format requirements:
94
+ 1. First line: <type>(<scope>): <subject>
95
+ 2. Empty line
96
+ 3. Detailed description: List main changes, one per line, starting with "- "
97
+
98
+ Rules:
99
+ - type must be one of: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
100
+ - scope is optional, indicates the affected area
101
+ - subject should be concise, no more than 50 characters
102
+ - Detailed description should list 3-6 main changes, each change should be concise
103
+ - If changes are minimal, list 2-3 changes
104
+ - No explanations or extra content
105
+
106
+ Example:
107
+ feat(auth): add user login functionality
108
+
109
+ - Implement username/password login API
110
+ - Add login status validation middleware
111
+ - Improve login error handling logic
112
+ - Update user authentication documentation`;
113
+
114
+ const userPrompt = isZh
115
+ ? `请根据以下 git diff 生成详细的 commit message(包含修改点列表):\n\n${diff}`
116
+ : `Generate a detailed commit message (including change list) based on the following git diff:\n\n${diff}`;
117
+
118
+ return `${systemPrompt}\n\n${userPrompt}`;
119
+ } else {
120
+ // 简洁模式:只生成标题
121
+ const systemPrompt = isZh
122
+ ? `你是一个专业的 Git commit message 生成助手。请根据提供的 git diff 生成符合 Conventional Commits 规范的 commit message。
68
123
 
69
124
  规则:
70
125
  1. 格式:<type>(<scope>): <subject>
@@ -78,7 +133,7 @@ function buildPrompt(diff: string, language: string): string {
78
133
  - feat(auth): 添加用户登录功能
79
134
  - fix(api): 修复数据获取失败的问题
80
135
  - docs(readme): 更新安装说明`
81
- : `You are a professional Git commit message generator. Generate a commit message following Conventional Commits specification based on the provided git diff.
136
+ : `You are a professional Git commit message generator. Generate a commit message following Conventional Commits specification based on the provided git diff.
82
137
 
83
138
  Rules:
84
139
  1. Format: <type>(<scope>): <subject>
@@ -93,11 +148,12 @@ Examples:
93
148
  - fix(api): resolve data fetching failure
94
149
  - docs(readme): update installation guide`;
95
150
 
96
- const userPrompt = isZh
97
- ? `请根据以下 git diff 生成 commit message:\n\n${diff}`
98
- : `Generate a commit message based on the following git diff:\n\n${diff}`;
151
+ const userPrompt = isZh
152
+ ? `请根据以下 git diff 生成 commit message:\n\n${diff}`
153
+ : `Generate a commit message based on the following git diff:\n\n${diff}`;
99
154
 
100
- return `${systemPrompt}\n\n${userPrompt}`;
155
+ return `${systemPrompt}\n\n${userPrompt}`;
156
+ }
101
157
  }
102
158
 
103
159
  /**
@@ -233,6 +289,27 @@ async function callOllamaAPI(
233
289
  }
234
290
  }
235
291
 
292
+ /**
293
+ * 清理AI生成的commit message
294
+ * 移除重复行和多余的空行
295
+ */
296
+ function cleanAIResponse(response: string): string {
297
+ const lines = response.split('\n').map(line => line.trim()).filter(line => line);
298
+
299
+ // 移除重复的行
300
+ const uniqueLines = [];
301
+ const seen = new Set();
302
+
303
+ for (const line of lines) {
304
+ if (!seen.has(line)) {
305
+ seen.add(line);
306
+ uniqueLines.push(line);
307
+ }
308
+ }
309
+
310
+ return uniqueLines.join('\n');
311
+ }
312
+
236
313
  /**
237
314
  * 生成 AI commit message
238
315
  */
@@ -242,7 +319,8 @@ export async function generateAICommitMessage(
242
319
  const aiConfig = config.aiCommit || {};
243
320
  const provider = aiConfig.provider || "github";
244
321
  const language = aiConfig.language || "zh-CN";
245
- const maxTokens = aiConfig.maxTokens || 200;
322
+ const detailedDescription = aiConfig.detailedDescription !== false; // 默认启用详细描述
323
+ const maxTokens = aiConfig.maxTokens || (detailedDescription ? 400 : 200);
246
324
 
247
325
  // 获取 git diff
248
326
  const diff = getGitDiff();
@@ -251,12 +329,12 @@ export async function generateAICommitMessage(
251
329
  }
252
330
 
253
331
  // 限制 diff 长度,避免超过 token 限制
254
- const maxDiffLength = 4000;
332
+ const maxDiffLength = detailedDescription ? 6000 : 4000;
255
333
  const truncatedDiff =
256
334
  diff.length > maxDiffLength ? diff.slice(0, maxDiffLength) + "\n..." : diff;
257
335
 
258
336
  // 构建 prompt
259
- const prompt = buildPrompt(truncatedDiff, language);
337
+ const prompt = buildPrompt(truncatedDiff, language, detailedDescription);
260
338
 
261
339
  // 根据提供商调用对应的 API
262
340
  const providerInfo = AI_PROVIDERS[provider];
@@ -276,18 +354,26 @@ export async function generateAICommitMessage(
276
354
  }
277
355
 
278
356
  // 调用 API
357
+ let response: string;
279
358
  switch (provider) {
280
359
  case "github":
281
- return await callGitHubAPI(prompt, apiKey, model, maxTokens);
360
+ response = await callGitHubAPI(prompt, apiKey, model, maxTokens);
361
+ break;
282
362
  case "openai":
283
- return await callOpenAIAPI(prompt, apiKey, model, maxTokens);
363
+ response = await callOpenAIAPI(prompt, apiKey, model, maxTokens);
364
+ break;
284
365
  case "claude":
285
- return await callClaudeAPI(prompt, apiKey, model, maxTokens);
366
+ response = await callClaudeAPI(prompt, apiKey, model, maxTokens);
367
+ break;
286
368
  case "ollama":
287
- return await callOllamaAPI(prompt, model, maxTokens);
369
+ response = await callOllamaAPI(prompt, model, maxTokens);
370
+ break;
288
371
  default:
289
372
  throw new Error(`不支持的 AI 提供商: ${provider}`);
290
373
  }
374
+
375
+ // 清理AI响应,移除重复内容
376
+ return cleanAIResponse(response);
291
377
  }
292
378
 
293
379
  /**
@@ -266,11 +266,29 @@ export async function init(): Promise<void> {
266
266
  theme,
267
267
  });
268
268
 
269
+ const detailedDescription = await select({
270
+ message: "是否生成详细的修改点描述?",
271
+ choices: [
272
+ {
273
+ name: "是(包含修改点列表,推荐)",
274
+ value: true,
275
+ description: "如:feat(auth): 添加用户登录功能\n\n- 实现用户名密码登录接口\n- 添加登录状态验证中间件"
276
+ },
277
+ {
278
+ name: "否(仅生成标题)",
279
+ value: false,
280
+ description: "如:feat(auth): 添加用户登录功能"
281
+ },
282
+ ],
283
+ theme,
284
+ });
285
+
269
286
  config.aiCommit = {
270
287
  enabled: true,
271
288
  provider: aiProvider as "github" | "openai" | "claude" | "ollama",
272
289
  apiKey: apiKey || undefined,
273
290
  language: language as "zh-CN" | "en-US",
291
+ detailedDescription,
274
292
  };
275
293
 
276
294
  // 根据提供商设置默认模型
@@ -43,7 +43,7 @@ export async function listTags(prefix?: string): Promise<void> {
43
43
  const grouped = new Map<string, string[]>();
44
44
  tags.forEach((tag) => {
45
45
  // 提取前缀:去掉数字及之后的部分,如 "v0.1.0" -> "v"
46
- const prefix = tag.replace(/[0-9].*/, "") || "无前缀";
46
+ const prefix = tag.replace(/[0-9].*/, "") || "(无前缀)";
47
47
  if (!grouped.has(prefix)) {
48
48
  grouped.set(prefix, []);
49
49
  }
package/src/config.ts CHANGED
@@ -45,6 +45,7 @@ export interface GwConfig {
45
45
  model?: string; // 模型名称
46
46
  language?: "zh-CN" | "en-US"; // 生成语言,默认 zh-CN
47
47
  maxTokens?: number; // 最大 token 数,默认 200
48
+ detailedDescription?: boolean; // 是否生成详细的修改点描述,默认 false
48
49
  };
49
50
  }
50
51
 
@@ -0,0 +1,222 @@
1
+ # 测试覆盖率报告
2
+
3
+ ## 📊 总体统计
4
+
5
+ ✅ **153 个测试全部通过**
6
+
7
+ - **7 个测试文件**
8
+ - **153 个测试用例**
9
+ - **0 个失败**
10
+ - **运行时间**: ~230ms
11
+
12
+ ## 📁 测试文件清单
13
+
14
+ | 文件 | 测试数 | 状态 | 覆盖模块 |
15
+ | ------------------------- | ------ | ---- | ------------------------ |
16
+ | `utils.test.ts` | 23 | ✅ | 工具函数、颜色、命令执行 |
17
+ | `config.test.ts` | 12 | ✅ | 配置加载、合并、优先级 |
18
+ | `ai-service.test.ts` | 21 | ✅ | AI commit 生成、多提供商 |
19
+ | `tag.test.ts` | 37 | ✅ | Tag 管理、版本控制 |
20
+ | `commit.test.ts` | 7 | ✅ | 提交类型、消息格式 |
21
+ | `update-notifier.test.ts` | 20 | ✅ | 更新检查、通知、缓存 |
22
+ | `branch.test.ts` | 33 | ✅ | 分支命名、验证、配置 |
23
+
24
+ ## ✅ 已测试的模块
25
+
26
+ ### 1. Utils 模块 (23 tests)
27
+
28
+ - Colors 工具 (7)
29
+ - TODAY 常量 (2)
30
+ - exec 函数 (3)
31
+ - execOutput 函数 (2)
32
+ - checkGitRepo 函数 (2)
33
+ - getMainBranch 函数 (3)
34
+ - divider 函数 (1)
35
+ - theme 对象 (3)
36
+
37
+ ### 2. Config 模块 (12 tests)
38
+
39
+ - 默认配置 (1)
40
+ - 项目配置 (3)
41
+ - 全局配置 (1)
42
+ - 配置合并 (2)
43
+ - 错误处理 (2)
44
+ - 其他 (3)
45
+
46
+ ### 3. AI Service 模块 (21 tests)
47
+
48
+ - isAICommitAvailable (3)
49
+ - getProviderInfo (5)
50
+ - generateAICommitMessage (12)
51
+ - Ollama 特殊情况 (1)
52
+
53
+ ### 4. Tag 功能 (37 tests)
54
+
55
+ - 前缀提取 (7)
56
+ - Tag 分组 (3)
57
+ - 显示逻辑 (6)
58
+ - 列宽计算 (4)
59
+ - 版本号解析 (5)
60
+ - 版本号递增 (5)
61
+ - Tag 排序 (2)
62
+ - 多列显示 (2)
63
+ - 表头格式化 (3)
64
+
65
+ ### 5. Commit 功能 (7 tests)
66
+
67
+ - 提交类型 (3)
68
+ - 提交消息格式 (2)
69
+ - Refactor 对齐处理 (2)
70
+
71
+ ### 6. Update Notifier 模块 (20 tests)
72
+
73
+ - clearUpdateCache (3)
74
+ - checkForUpdates (8)
75
+ - Volta 检测 (2)
76
+ - 版本比较 (1)
77
+ - 缓存读写 (3)
78
+ - 网络请求 (2)
79
+
80
+ ### 7. Branch 功能 (33 tests)
81
+
82
+ - 分支命名规范 (5)
83
+ - 分支前缀配置 (2)
84
+ - ID 验证 (3)
85
+ - 描述验证 (3)
86
+ - 基础分支选择 (4)
87
+ - 分支名称格式 (5)
88
+ - 分支类型 (2)
89
+ - autoPush 配置 (3)
90
+ - 分支名称边界情况 (4)
91
+ - 日期格式 (2)
92
+
93
+ ## ⚠️ 待测试的模块
94
+
95
+ 以下模块尚未添加测试:
96
+
97
+ - ❌ `index.ts` - 主入口
98
+ - ❌ `commands/help.ts` - 帮助命令
99
+ - ❌ `commands/init.ts` - 初始化配置
100
+ - ❌ `commands/release.ts` - 版本发布
101
+ - ❌ `commands/stash.ts` - Stash 管理
102
+ - ❌ `commands/update.ts` - 更新命令
103
+
104
+ ## 📈 覆盖率分析
105
+
106
+ ### 核心功能覆盖率
107
+
108
+ | 模块 | 覆盖率 | 状态 |
109
+ | --------------- | ------ | ---- |
110
+ | Utils | 100% | ✅ |
111
+ | Config | 100% | ✅ |
112
+ | AI Service | 100% | ✅ |
113
+ | Tag (逻辑) | 100% | ✅ |
114
+ | Commit (逻辑) | 100% | ✅ |
115
+ | Update Notifier | 100% | ✅ |
116
+ | Branch (逻辑) | 100% | ✅ |
117
+ | Commands | ~40% | ⚠️ |
118
+
119
+ ### 测试类型分布
120
+
121
+ ```
122
+ 单元测试: 153 个 (100%)
123
+ 集成测试: 0 个 (0%)
124
+ E2E 测试: 0 个 (0%)
125
+ ```
126
+
127
+ ## 🎯 测试质量指标
128
+
129
+ ### 测试特点
130
+
131
+ - ✅ **快速** - 所有测试在 230ms 内完成
132
+ - ✅ **独立** - 每个测试独立运行,互不影响
133
+ - ✅ **可重复** - 使用 Mock 确保结果一致
134
+ - ✅ **清晰** - 描述性的测试名称
135
+ - ✅ **全面** - 覆盖正常流程和边界情况
136
+
137
+ ### 测试覆盖的场景
138
+
139
+ - ✅ 正常流程
140
+ - ✅ 边界情况
141
+ - ✅ 错误处理
142
+ - ✅ 配置缺失
143
+ - ✅ API 失败
144
+ - ✅ 无效输入
145
+ - ✅ 空数据
146
+
147
+ ## 🚀 运行测试
148
+
149
+ ```bash
150
+ # 运行所有测试
151
+ npm test
152
+
153
+ # 监听模式(开发时推荐)
154
+ npm run test:watch
155
+
156
+ # 可视化界面
157
+ npm run test:ui
158
+
159
+ # 生成覆盖率报告
160
+ npm run test:coverage
161
+ ```
162
+
163
+ ## 🔒 质量保障
164
+
165
+ ### CI/CD 集成
166
+
167
+ - ✅ GitHub Actions - 每次 push/PR 自动运行
168
+ - ✅ Pre-commit Hook - 提交前自动运行
169
+ - ✅ 多 Node 版本 - 18.x 和 20.x
170
+
171
+ ### 测试失败处理
172
+
173
+ - ✅ 测试失败阻止提交
174
+ - ✅ CI 失败阻止合并
175
+ - ✅ 详细的错误信息
176
+
177
+ ## 📋 未来计划
178
+
179
+ ### 短期 (1-2 周)
180
+
181
+ - [ ] 添加 Help 命令测试
182
+ - [ ] 添加 Init 命令测试
183
+ - [ ] 添加 Release 命令测试
184
+ - [ ] 添加 Stash 命令测试
185
+ - [ ] 添加 Update 命令测试
186
+ - [ ] 添加 Index 入口测试
187
+
188
+ ### 中期 (1-2 月)
189
+
190
+ - [ ] 添加集成测试
191
+ - [ ] 添加 E2E 测试
192
+ - [ ] 提高覆盖率到 95%+
193
+ - [ ] 添加性能测试
194
+
195
+ ### 长期 (3+ 月)
196
+
197
+ - [ ] 自动化测试报告
198
+ - [ ] 测试覆盖率徽章
199
+ - [ ] 持续改进测试质量
200
+ - [ ] 添加快照测试
201
+
202
+ ## 💡 贡献指南
203
+
204
+ 添加新功能时,请确保:
205
+
206
+ 1. ✅ 为新功能编写测试
207
+ 2. ✅ 运行 `npm test` 确保所有测试通过
208
+ 3. ✅ 运行 `npm run test:coverage` 检查覆盖率
209
+ 4. ✅ 测试覆盖率不低于现有水平
210
+ 5. ✅ 测试名称清晰描述测试内容
211
+
212
+ ## 🎉 总结
213
+
214
+ 我们已经建立了一个全面的测试体系:
215
+
216
+ - ✅ **153 个测试用例**全部通过
217
+ - ✅ 覆盖**所有核心功能**
218
+ - ✅ **自动化测试**集成到 CI/CD
219
+ - ✅ **Pre-commit Hook**确保代码质量
220
+ - ✅ **完善的文档**帮助开发者快速上手
221
+
222
+ 这个测试体系确保了每次代码变更都不会破坏现有功能,让我们可以放心地重构和添加新功能!🎉
@@ -0,0 +1,242 @@
1
+ # 测试快速开始
2
+
3
+ ## 5 分钟上手测试
4
+
5
+ ### 1. 运行现有测试
6
+
7
+ ```bash
8
+ # 运行所有测试
9
+ npm test
10
+
11
+ # 输出示例:
12
+ # ✓ tests/utils.test.ts (3 tests) 2ms
13
+ # ✓ tests/tag.test.ts (10 tests) 3ms
14
+ # ✓ tests/commit.test.ts (7 tests) 3ms
15
+ #
16
+ # Test Files 3 passed (3)
17
+ # Tests 20 passed (20)
18
+ ```
19
+
20
+ ### 2. 监听模式(推荐开发时使用)
21
+
22
+ ```bash
23
+ npm run test:watch
24
+ ```
25
+
26
+ 修改代码后,测试会自动重新运行。
27
+
28
+ ### 3. 可视化界面
29
+
30
+ ```bash
31
+ npm run test:ui
32
+ ```
33
+
34
+ 在浏览器中打开 `http://localhost:51204/__vitest__/`,可以:
35
+
36
+ - 查看测试树状结构
37
+ - 点击运行单个测试
38
+ - 查看测试覆盖率
39
+ - 查看失败详情
40
+
41
+ ### 4. 编写你的第一个测试
42
+
43
+ 创建 `tests/example.test.ts`:
44
+
45
+ ```typescript
46
+ import { describe, it, expect } from "vitest";
47
+
48
+ describe("我的第一个测试", () => {
49
+ it("1 + 1 应该等于 2", () => {
50
+ expect(1 + 1).toBe(2);
51
+ });
52
+
53
+ it("数组应该包含元素", () => {
54
+ const arr = ["a", "b", "c"];
55
+ expect(arr).toContain("b");
56
+ });
57
+
58
+ it("对象应该有属性", () => {
59
+ const obj = { name: "test", value: 123 };
60
+ expect(obj).toHaveProperty("name");
61
+ expect(obj.name).toBe("test");
62
+ });
63
+ });
64
+ ```
65
+
66
+ 运行测试:
67
+
68
+ ```bash
69
+ npm test
70
+ ```
71
+
72
+ ### 5. 测试实际功能
73
+
74
+ 测试 tag 前缀提取:
75
+
76
+ ```typescript
77
+ import { describe, it, expect } from "vitest";
78
+
79
+ describe("Tag 前缀提取", () => {
80
+ it("应该提取 v 前缀", () => {
81
+ const tag = "v1.0.0";
82
+ const prefix = tag.replace(/[0-9].*/, "");
83
+ expect(prefix).toBe("v");
84
+ });
85
+
86
+ it("应该提取 release- 前缀", () => {
87
+ const tag = "release-1.0.0";
88
+ const prefix = tag.replace(/[0-9].*/, "");
89
+ expect(prefix).toBe("release-");
90
+ });
91
+
92
+ it("应该处理无前缀的 tag", () => {
93
+ const tag = "1.0.0";
94
+ const prefix = tag.replace(/[0-9].*/, "") || "(无前缀)";
95
+ expect(prefix).toBe("(无前缀)");
96
+ });
97
+ });
98
+ ```
99
+
100
+ ### 6. 常用断言
101
+
102
+ ```typescript
103
+ // 相等性
104
+ expect(value).toBe(expected); // 严格相等 ===
105
+ expect(value).toEqual(expected); // 深度相等(对象、数组)
106
+
107
+ // 真值
108
+ expect(value).toBeTruthy(); // 真值
109
+ expect(value).toBeFalsy(); // 假值
110
+ expect(value).toBeDefined(); // 已定义
111
+ expect(value).toBeUndefined(); // 未定义
112
+ expect(value).toBeNull(); // null
113
+
114
+ // 数字
115
+ expect(value).toBeGreaterThan(3); // > 3
116
+ expect(value).toBeGreaterThanOrEqual(3); // >= 3
117
+ expect(value).toBeLessThan(5); // < 5
118
+ expect(value).toBeLessThanOrEqual(5); // <= 5
119
+
120
+ // 字符串
121
+ expect(string).toMatch(/pattern/); // 匹配正则
122
+ expect(string).toContain("substring"); // 包含子串
123
+
124
+ // 数组
125
+ expect(array).toContain(item); // 包含元素
126
+ expect(array).toHaveLength(3); // 长度为 3
127
+
128
+ // 对象
129
+ expect(object).toHaveProperty("key"); // 有属性
130
+ expect(object).toMatchObject({ key: value }); // 匹配部分属性
131
+
132
+ // 函数
133
+ expect(fn).toThrow(); // 抛出错误
134
+ expect(fn).toThrow("error message"); // 抛出特定错误
135
+ ```
136
+
137
+ ### 7. 测试异步代码
138
+
139
+ ```typescript
140
+ it("应该异步获取数据", async () => {
141
+ const data = await fetchData();
142
+ expect(data).toBeDefined();
143
+ });
144
+
145
+ it("应该处理 Promise", () => {
146
+ return fetchData().then((data) => {
147
+ expect(data).toBeDefined();
148
+ });
149
+ });
150
+
151
+ it("应该处理 Promise 拒绝", async () => {
152
+ await expect(fetchData()).rejects.toThrow("error");
153
+ });
154
+ ```
155
+
156
+ ### 8. 使用 Mock
157
+
158
+ ```typescript
159
+ import { vi } from "vitest";
160
+
161
+ it("应该 mock 函数", () => {
162
+ const mockFn = vi.fn();
163
+ mockFn("hello");
164
+
165
+ expect(mockFn).toHaveBeenCalled();
166
+ expect(mockFn).toHaveBeenCalledWith("hello");
167
+ expect(mockFn).toHaveBeenCalledTimes(1);
168
+ });
169
+
170
+ it("应该 mock 返回值", () => {
171
+ const mockFn = vi.fn().mockReturnValue("mocked");
172
+ const result = mockFn();
173
+
174
+ expect(result).toBe("mocked");
175
+ });
176
+ ```
177
+
178
+ ### 9. 分组测试
179
+
180
+ ```typescript
181
+ describe("外层分组", () => {
182
+ describe("内层分组 1", () => {
183
+ it("测试 1", () => {});
184
+ it("测试 2", () => {});
185
+ });
186
+
187
+ describe("内层分组 2", () => {
188
+ it("测试 3", () => {});
189
+ it("测试 4", () => {});
190
+ });
191
+ });
192
+ ```
193
+
194
+ ### 10. 生成覆盖率报告
195
+
196
+ ```bash
197
+ npm run test:coverage
198
+ ```
199
+
200
+ 输出示例:
201
+
202
+ ```
203
+ % Coverage report from v8
204
+ --------------------|---------|----------|---------|---------|
205
+ File | % Stmts | % Branch | % Funcs | % Lines |
206
+ --------------------|---------|----------|---------|---------|
207
+ All files | 85.71 | 83.33 | 88.88 | 85.71 |
208
+ src/commands | 90.00 | 85.00 | 92.00 | 90.00 |
209
+ tag.ts | 92.00 | 88.00 | 95.00 | 92.00 |
210
+ commit.ts | 88.00 | 82.00 | 89.00 | 88.00 |
211
+ --------------------|---------|----------|---------|---------|
212
+ ```
213
+
214
+ ## 下一步
215
+
216
+ - 查看 [tests/README.md](./README.md) 了解更多测试技巧
217
+ - 查看 [TESTING.md](../TESTING.md) 了解测试体系
218
+ - 查看现有测试文件学习最佳实践:
219
+ - `tests/tag.test.ts` - Tag 功能测试
220
+ - `tests/commit.test.ts` - Commit 功能测试
221
+ - `tests/utils.test.ts` - 工具函数测试
222
+
223
+ ## 常用命令速查
224
+
225
+ ```bash
226
+ npm test # 运行所有测试
227
+ npm run test:watch # 监听模式
228
+ npm run test:ui # 可视化界面
229
+ npm run test:coverage # 覆盖率报告
230
+
231
+ npx vitest tests/tag.test.ts # 运行单个文件
232
+ npx vitest -t "前缀提取" # 运行匹配的测试
233
+ ```
234
+
235
+ ## 提交前检查清单
236
+
237
+ - [ ] 运行 `npm test` 确保所有测试通过
238
+ - [ ] 运行 `npm run build` 确保构建成功
239
+ - [ ] 新功能已添加测试
240
+ - [ ] 测试覆盖率没有下降
241
+
242
+ Happy Testing! 🎉