@zjex/git-workflow 0.3.8 → 0.3.9
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.
- package/README.md +1 -1
- package/dist/index.js +34 -6
- package/package.json +1 -1
- package/src/commands/branch.ts +22 -5
- package/src/commands/init.ts +39 -15
- package/src/config.ts +4 -0
- package/src/update-notifier.ts +1 -2
- package/tests/branch.test.ts +116 -0
- package/tests/commit.test.ts +186 -1
- package/tests/init.test.ts +240 -297
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
<a href="https://github.com/iamzjt-front-end/git-workflow"><img src="https://img.shields.io/github/stars/iamzjt-front-end/git-workflow?style=flat&colorA=18181B&colorB=F59E0B" alt="github stars"></a>
|
|
13
13
|
<a href="https://github.com/iamzjt-front-end/git-workflow/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@zjex/git-workflow?style=flat&colorA=18181B&colorB=10B981" alt="license"></a>
|
|
14
14
|
<a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%3E%3D18-339933?style=flat&logo=node.js&logoColor=white&colorA=18181B" alt="node version"></a>
|
|
15
|
-
<a href="https://github.com/iamzjt-front-end/git-workflow/actions"><img src="https://img.shields.io/badge/tests-
|
|
15
|
+
<a href="https://github.com/iamzjt-front-end/git-workflow/actions"><img src="https://img.shields.io/badge/tests-290%20passed-success?style=flat&colorA=18181B" alt="tests"></a>
|
|
16
16
|
<a href="https://github.com/iamzjt-front-end/git-workflow/issues"><img src="https://img.shields.io/github/issues/iamzjt-front-end/git-workflow?style=flat&colorA=18181B&colorB=EC4899" alt="issues"></a>
|
|
17
17
|
</p>
|
|
18
18
|
|
package/dist/index.js
CHANGED
|
@@ -179,11 +179,10 @@ function showSimpleNotification(current, latest) {
|
|
|
179
179
|
)} \u2192 ${colors.green(latest)} ${colors.dim("\u8FD0\u884C")} ${colors.cyan(
|
|
180
180
|
"gw update"
|
|
181
181
|
)} ${colors.dim("\u66F4\u65B0")}`;
|
|
182
|
-
console.log("");
|
|
183
182
|
console.log(
|
|
184
183
|
boxen(message, {
|
|
185
184
|
padding: { top: 0, bottom: 0, left: 2, right: 2 },
|
|
186
|
-
margin: { top: 0, bottom:
|
|
185
|
+
margin: { top: 0, bottom: 0, left: 0, right: 0 },
|
|
187
186
|
borderStyle: "round",
|
|
188
187
|
borderColor: "yellow",
|
|
189
188
|
align: "center"
|
|
@@ -431,12 +430,22 @@ async function getBranchName(type) {
|
|
|
431
430
|
console.log(colors.red(`${idLabel}\u4E0D\u80FD\u4E3A\u7A7A`));
|
|
432
431
|
return null;
|
|
433
432
|
}
|
|
434
|
-
const
|
|
435
|
-
|
|
433
|
+
const requireDescription = type === "feature" ? config2.featureRequireDescription ?? false : config2.hotfixRequireDescription ?? false;
|
|
434
|
+
const descMessage = requireDescription ? "\u8BF7\u8F93\u5165\u63CF\u8FF0:" : "\u8BF7\u8F93\u5165\u63CF\u8FF0 (\u53EF\u8DF3\u8FC7):";
|
|
435
|
+
const description = await input({ message: descMessage, theme });
|
|
436
|
+
if (requireDescription && !description) {
|
|
436
437
|
console.log(colors.red("\u63CF\u8FF0\u4E0D\u80FD\u4E3A\u7A7A"));
|
|
437
438
|
return null;
|
|
438
439
|
}
|
|
439
|
-
|
|
440
|
+
if (id && description) {
|
|
441
|
+
return `${branchPrefix}/${TODAY}-${id}-${description}`;
|
|
442
|
+
} else if (id) {
|
|
443
|
+
return `${branchPrefix}/${TODAY}-${id}`;
|
|
444
|
+
} else if (description) {
|
|
445
|
+
return `${branchPrefix}/${TODAY}-${description}`;
|
|
446
|
+
} else {
|
|
447
|
+
return `${branchPrefix}/${TODAY}`;
|
|
448
|
+
}
|
|
440
449
|
}
|
|
441
450
|
async function createBranch(type, baseBranchArg) {
|
|
442
451
|
const config2 = getConfig();
|
|
@@ -1327,6 +1336,25 @@ async function init() {
|
|
|
1327
1336
|
});
|
|
1328
1337
|
config2.hotfixIdLabel = hotfixIdLabel;
|
|
1329
1338
|
divider();
|
|
1339
|
+
const featureRequireDescription = await select4({
|
|
1340
|
+
message: "Feature \u5206\u652F\u662F\u5426\u8981\u6C42\u5FC5\u586B\u63CF\u8FF0?",
|
|
1341
|
+
choices: [
|
|
1342
|
+
{ name: "\u5426", value: false },
|
|
1343
|
+
{ name: "\u662F", value: true }
|
|
1344
|
+
],
|
|
1345
|
+
theme
|
|
1346
|
+
});
|
|
1347
|
+
config2.featureRequireDescription = featureRequireDescription;
|
|
1348
|
+
const hotfixRequireDescription = await select4({
|
|
1349
|
+
message: "Hotfix \u5206\u652F\u662F\u5426\u8981\u6C42\u5FC5\u586B\u63CF\u8FF0?",
|
|
1350
|
+
choices: [
|
|
1351
|
+
{ name: "\u5426", value: false },
|
|
1352
|
+
{ name: "\u662F", value: true }
|
|
1353
|
+
],
|
|
1354
|
+
theme
|
|
1355
|
+
});
|
|
1356
|
+
config2.hotfixRequireDescription = hotfixRequireDescription;
|
|
1357
|
+
divider();
|
|
1330
1358
|
const defaultTagPrefix = await input3({
|
|
1331
1359
|
message: "\u9ED8\u8BA4 Tag \u524D\u7F00 (\u7559\u7A7A\u5219\u6BCF\u6B21\u9009\u62E9):",
|
|
1332
1360
|
theme
|
|
@@ -2916,7 +2944,7 @@ process.on("SIGTERM", () => {
|
|
|
2916
2944
|
console.log("");
|
|
2917
2945
|
process.exit(0);
|
|
2918
2946
|
});
|
|
2919
|
-
var version = true ? "0.3.
|
|
2947
|
+
var version = true ? "0.3.9" : "0.0.0-dev";
|
|
2920
2948
|
async function mainMenu() {
|
|
2921
2949
|
console.log(
|
|
2922
2950
|
colors.green(`
|
package/package.json
CHANGED
package/src/commands/branch.ts
CHANGED
|
@@ -32,15 +32,32 @@ export async function getBranchName(type: BranchType): Promise<string | null> {
|
|
|
32
32
|
return null;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
// 描述是否必填,默认非必填
|
|
36
|
+
const requireDescription =
|
|
37
|
+
type === "feature"
|
|
38
|
+
? config.featureRequireDescription ?? false
|
|
39
|
+
: config.hotfixRequireDescription ?? false;
|
|
40
|
+
const descMessage = requireDescription
|
|
41
|
+
? "请输入描述:"
|
|
42
|
+
: "请输入描述 (可跳过):";
|
|
43
|
+
|
|
44
|
+
const description = await input({ message: descMessage, theme });
|
|
45
|
+
|
|
46
|
+
if (requireDescription && !description) {
|
|
37
47
|
console.log(colors.red("描述不能为空"));
|
|
38
48
|
return null;
|
|
39
49
|
}
|
|
40
50
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
51
|
+
// 构建分支名
|
|
52
|
+
if (id && description) {
|
|
53
|
+
return `${branchPrefix}/${TODAY}-${id}-${description}`;
|
|
54
|
+
} else if (id) {
|
|
55
|
+
return `${branchPrefix}/${TODAY}-${id}`;
|
|
56
|
+
} else if (description) {
|
|
57
|
+
return `${branchPrefix}/${TODAY}-${description}`;
|
|
58
|
+
} else {
|
|
59
|
+
return `${branchPrefix}/${TODAY}`;
|
|
60
|
+
}
|
|
44
61
|
}
|
|
45
62
|
|
|
46
63
|
export async function createBranch(
|
package/src/commands/init.ts
CHANGED
|
@@ -118,6 +118,29 @@ export async function init(): Promise<void> {
|
|
|
118
118
|
|
|
119
119
|
divider();
|
|
120
120
|
|
|
121
|
+
// 描述必填配置
|
|
122
|
+
const featureRequireDescription = await select({
|
|
123
|
+
message: "Feature 分支是否要求必填描述?",
|
|
124
|
+
choices: [
|
|
125
|
+
{ name: "否", value: false },
|
|
126
|
+
{ name: "是", value: true },
|
|
127
|
+
],
|
|
128
|
+
theme,
|
|
129
|
+
});
|
|
130
|
+
config.featureRequireDescription = featureRequireDescription;
|
|
131
|
+
|
|
132
|
+
const hotfixRequireDescription = await select({
|
|
133
|
+
message: "Hotfix 分支是否要求必填描述?",
|
|
134
|
+
choices: [
|
|
135
|
+
{ name: "否", value: false },
|
|
136
|
+
{ name: "是", value: true },
|
|
137
|
+
],
|
|
138
|
+
theme,
|
|
139
|
+
});
|
|
140
|
+
config.hotfixRequireDescription = hotfixRequireDescription;
|
|
141
|
+
|
|
142
|
+
divider();
|
|
143
|
+
|
|
121
144
|
// Tag 配置
|
|
122
145
|
const defaultTagPrefix = await input({
|
|
123
146
|
message: "默认 Tag 前缀 (留空则每次选择):",
|
|
@@ -269,15 +292,16 @@ export async function init(): Promise<void> {
|
|
|
269
292
|
const detailedDescription = await select({
|
|
270
293
|
message: "是否生成详细的修改点描述?",
|
|
271
294
|
choices: [
|
|
272
|
-
{
|
|
273
|
-
name: "是(包含修改点列表,推荐)",
|
|
295
|
+
{
|
|
296
|
+
name: "是(包含修改点列表,推荐)",
|
|
274
297
|
value: true,
|
|
275
|
-
description:
|
|
298
|
+
description:
|
|
299
|
+
"如:feat(auth): 添加用户登录功能\n\n- 实现用户名密码登录接口\n- 添加登录状态验证中间件",
|
|
276
300
|
},
|
|
277
|
-
{
|
|
278
|
-
name: "否(仅生成标题)",
|
|
301
|
+
{
|
|
302
|
+
name: "否(仅生成标题)",
|
|
279
303
|
value: false,
|
|
280
|
-
description: "如:feat(auth): 添加用户登录功能"
|
|
304
|
+
description: "如:feat(auth): 添加用户登录功能",
|
|
281
305
|
},
|
|
282
306
|
],
|
|
283
307
|
theme,
|
|
@@ -286,20 +310,20 @@ export async function init(): Promise<void> {
|
|
|
286
310
|
const aiUseEmoji = await select({
|
|
287
311
|
message: "AI 生成的 commit message 是否包含 emoji?",
|
|
288
312
|
choices: [
|
|
289
|
-
{
|
|
290
|
-
name: "是(推荐)",
|
|
313
|
+
{
|
|
314
|
+
name: "是(推荐)",
|
|
291
315
|
value: true,
|
|
292
|
-
description: "如:✨ feat(auth): 添加用户登录功能"
|
|
316
|
+
description: "如:✨ feat(auth): 添加用户登录功能",
|
|
293
317
|
},
|
|
294
|
-
{
|
|
295
|
-
name: "否",
|
|
318
|
+
{
|
|
319
|
+
name: "否",
|
|
296
320
|
value: false,
|
|
297
|
-
description: "如:feat(auth): 添加用户登录功能"
|
|
321
|
+
description: "如:feat(auth): 添加用户登录功能",
|
|
298
322
|
},
|
|
299
|
-
{
|
|
300
|
-
name: "跟随全局设置",
|
|
323
|
+
{
|
|
324
|
+
name: "跟随全局设置",
|
|
301
325
|
value: undefined,
|
|
302
|
-
description: `当前全局设置:${useEmoji ?
|
|
326
|
+
description: `当前全局设置:${useEmoji ? "启用" : "禁用"} emoji`,
|
|
303
327
|
},
|
|
304
328
|
],
|
|
305
329
|
theme,
|
package/src/config.ts
CHANGED
|
@@ -12,6 +12,10 @@ export interface GwConfig {
|
|
|
12
12
|
hotfixPrefix: string;
|
|
13
13
|
// 是否要求必填 ID,默认 false
|
|
14
14
|
requireId: boolean;
|
|
15
|
+
// feature 分支是否要求必填描述,默认 false
|
|
16
|
+
featureRequireDescription?: boolean;
|
|
17
|
+
// hotfix 分支是否要求必填描述,默认 false
|
|
18
|
+
hotfixRequireDescription?: boolean;
|
|
15
19
|
// ID 标签名称
|
|
16
20
|
featureIdLabel: string;
|
|
17
21
|
hotfixIdLabel: string;
|
package/src/update-notifier.ts
CHANGED
|
@@ -151,11 +151,10 @@ function showSimpleNotification(current: string, latest: string): void {
|
|
|
151
151
|
"gw update"
|
|
152
152
|
)} ${colors.dim("更新")}`;
|
|
153
153
|
|
|
154
|
-
console.log("");
|
|
155
154
|
console.log(
|
|
156
155
|
boxen(message, {
|
|
157
156
|
padding: { top: 0, bottom: 0, left: 2, right: 2 },
|
|
158
|
-
margin: { top: 0, bottom:
|
|
157
|
+
margin: { top: 0, bottom: 0, left: 0, right: 0 },
|
|
159
158
|
borderStyle: "round",
|
|
160
159
|
borderColor: "yellow",
|
|
161
160
|
align: "center",
|
package/tests/branch.test.ts
CHANGED
|
@@ -253,3 +253,119 @@ describe("Branch 功能测试", () => {
|
|
|
253
253
|
});
|
|
254
254
|
});
|
|
255
255
|
});
|
|
256
|
+
|
|
257
|
+
describe("描述必填配置", () => {
|
|
258
|
+
it("featureRequireDescription 为 true 时描述不能为空", () => {
|
|
259
|
+
const featureRequireDescription = true;
|
|
260
|
+
const description = "";
|
|
261
|
+
|
|
262
|
+
if (featureRequireDescription && !description) {
|
|
263
|
+
expect(description).toBe("");
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it("featureRequireDescription 为 false 时描述可以为空", () => {
|
|
268
|
+
const featureRequireDescription = false;
|
|
269
|
+
const description = "";
|
|
270
|
+
|
|
271
|
+
if (!featureRequireDescription) {
|
|
272
|
+
expect(description).toBe("");
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it("hotfixRequireDescription 为 true 时描述不能为空", () => {
|
|
277
|
+
const hotfixRequireDescription = true;
|
|
278
|
+
const description = "";
|
|
279
|
+
|
|
280
|
+
if (hotfixRequireDescription && !description) {
|
|
281
|
+
expect(description).toBe("");
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it("hotfixRequireDescription 为 false 时描述可以为空", () => {
|
|
286
|
+
const hotfixRequireDescription = false;
|
|
287
|
+
const description = "";
|
|
288
|
+
|
|
289
|
+
if (!hotfixRequireDescription) {
|
|
290
|
+
expect(description).toBe("");
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it("应该生成只有 ID 的 feature 分支名(描述为空)", () => {
|
|
295
|
+
const prefix = "feature";
|
|
296
|
+
const id = "PROJ-123";
|
|
297
|
+
const description = "";
|
|
298
|
+
const branchName = id ? `${prefix}/${TODAY}-${id}` : `${prefix}/${TODAY}`;
|
|
299
|
+
|
|
300
|
+
expect(branchName).toMatch(/^feature\/\d{8}-PROJ-123$/);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it("应该生成只有描述的 feature 分支名(ID 为空)", () => {
|
|
304
|
+
const prefix = "feature";
|
|
305
|
+
const id = "";
|
|
306
|
+
const description = "add-login";
|
|
307
|
+
const branchName = description
|
|
308
|
+
? `${prefix}/${TODAY}-${description}`
|
|
309
|
+
: `${prefix}/${TODAY}`;
|
|
310
|
+
|
|
311
|
+
expect(branchName).toMatch(/^feature\/\d{8}-add-login$/);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it("应该生成只有日期的 feature 分支名(ID 和描述都为空)", () => {
|
|
315
|
+
const prefix = "feature";
|
|
316
|
+
const id = "";
|
|
317
|
+
const description = "";
|
|
318
|
+
const branchName = `${prefix}/${TODAY}`;
|
|
319
|
+
|
|
320
|
+
expect(branchName).toMatch(/^feature\/\d{8}$/);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it("应该生成只有 ID 的 hotfix 分支名(描述为空)", () => {
|
|
324
|
+
const prefix = "hotfix";
|
|
325
|
+
const id = "BUG-456";
|
|
326
|
+
const description = "";
|
|
327
|
+
const branchName = id ? `${prefix}/${TODAY}-${id}` : `${prefix}/${TODAY}`;
|
|
328
|
+
|
|
329
|
+
expect(branchName).toMatch(/^hotfix\/\d{8}-BUG-456$/);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it("应该生成只有描述的 hotfix 分支名(ID 为空)", () => {
|
|
333
|
+
const prefix = "hotfix";
|
|
334
|
+
const id = "";
|
|
335
|
+
const description = "fix-crash";
|
|
336
|
+
const branchName = description
|
|
337
|
+
? `${prefix}/${TODAY}-${description}`
|
|
338
|
+
: `${prefix}/${TODAY}`;
|
|
339
|
+
|
|
340
|
+
expect(branchName).toMatch(/^hotfix\/\d{8}-fix-crash$/);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it("应该生成只有日期的 hotfix 分支名(ID 和描述都为空)", () => {
|
|
344
|
+
const prefix = "hotfix";
|
|
345
|
+
const id = "";
|
|
346
|
+
const description = "";
|
|
347
|
+
const branchName = `${prefix}/${TODAY}`;
|
|
348
|
+
|
|
349
|
+
expect(branchName).toMatch(/^hotfix\/\d{8}$/);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it("feature 和 hotfix 可以有不同的描述必填配置", () => {
|
|
353
|
+
const featureRequireDescription = true;
|
|
354
|
+
const hotfixRequireDescription = false;
|
|
355
|
+
|
|
356
|
+
expect(featureRequireDescription).toBe(true);
|
|
357
|
+
expect(hotfixRequireDescription).toBe(false);
|
|
358
|
+
expect(featureRequireDescription).not.toBe(hotfixRequireDescription);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it("描述必填配置默认应该为 false", () => {
|
|
362
|
+
const featureRequireDescription = undefined;
|
|
363
|
+
const hotfixRequireDescription = undefined;
|
|
364
|
+
|
|
365
|
+
const featureRequired = featureRequireDescription ?? false;
|
|
366
|
+
const hotfixRequired = hotfixRequireDescription ?? false;
|
|
367
|
+
|
|
368
|
+
expect(featureRequired).toBe(false);
|
|
369
|
+
expect(hotfixRequired).toBe(false);
|
|
370
|
+
});
|
|
371
|
+
});
|
package/tests/commit.test.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { execSync } from "child_process";
|
|
3
|
+
|
|
4
|
+
// Mock child_process
|
|
5
|
+
vi.mock("child_process", () => ({
|
|
6
|
+
execSync: vi.fn(),
|
|
7
|
+
}));
|
|
2
8
|
|
|
3
9
|
describe("Commit 功能测试", () => {
|
|
4
10
|
describe("提交类型", () => {
|
|
@@ -82,4 +88,183 @@ describe("Commit 功能测试", () => {
|
|
|
82
88
|
expect(extraSpace).toBe("");
|
|
83
89
|
});
|
|
84
90
|
});
|
|
91
|
+
|
|
92
|
+
describe("自动暂存功能", () => {
|
|
93
|
+
beforeEach(() => {
|
|
94
|
+
vi.clearAllMocks();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
afterEach(() => {
|
|
98
|
+
vi.restoreAllMocks();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("autoStage 为 true 时应该执行 git add -A", () => {
|
|
102
|
+
const autoStage = true;
|
|
103
|
+
|
|
104
|
+
if (autoStage) {
|
|
105
|
+
execSync("git add -A", { stdio: "pipe" });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
expect(execSync).toHaveBeenCalledWith("git add -A", { stdio: "pipe" });
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("autoStage 为 false 时不应该自动执行 git add -A", () => {
|
|
112
|
+
const autoStage = false;
|
|
113
|
+
|
|
114
|
+
if (autoStage) {
|
|
115
|
+
execSync("git add -A", { stdio: "pipe" });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
expect(execSync).not.toHaveBeenCalled();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("提交前应该再次执行 git add -A 确保文件被暂存", () => {
|
|
122
|
+
const autoStage = true;
|
|
123
|
+
|
|
124
|
+
// 模拟提交前的暂存操作
|
|
125
|
+
if (autoStage) {
|
|
126
|
+
execSync("git add -A", { stdio: "pipe" });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 模拟提交前再次暂存
|
|
130
|
+
if (autoStage) {
|
|
131
|
+
execSync("git add -A", { stdio: "pipe" });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 应该被调用两次
|
|
135
|
+
expect(execSync).toHaveBeenCalledTimes(2);
|
|
136
|
+
expect(execSync).toHaveBeenCalledWith("git add -A", { stdio: "pipe" });
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("默认 autoStage 应该为 true", () => {
|
|
140
|
+
const config = {};
|
|
141
|
+
const autoStage = (config as any).autoStage ?? true;
|
|
142
|
+
|
|
143
|
+
expect(autoStage).toBe(true);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("配置 autoStage 为 false 时应该覆盖默认值", () => {
|
|
147
|
+
const config = { autoStage: false };
|
|
148
|
+
const autoStage = config.autoStage ?? true;
|
|
149
|
+
|
|
150
|
+
expect(autoStage).toBe(false);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe("Git 状态解析", () => {
|
|
155
|
+
it("应该正确解析已暂存的文件", () => {
|
|
156
|
+
// 模拟 git status --porcelain 输出
|
|
157
|
+
// M = 已暂存的修改
|
|
158
|
+
// A = 已暂存的新文件
|
|
159
|
+
const output = "M src/index.ts\nA src/new.ts";
|
|
160
|
+
const lines = output.split("\n");
|
|
161
|
+
|
|
162
|
+
const staged: { status: string; file: string }[] = [];
|
|
163
|
+
|
|
164
|
+
for (const line of lines) {
|
|
165
|
+
if (!line) continue;
|
|
166
|
+
const indexStatus = line[0];
|
|
167
|
+
const file = line.slice(3);
|
|
168
|
+
|
|
169
|
+
if (indexStatus !== " " && indexStatus !== "?") {
|
|
170
|
+
staged.push({ status: indexStatus, file });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
expect(staged).toHaveLength(2);
|
|
175
|
+
expect(staged[0]).toEqual({ status: "M", file: "src/index.ts" });
|
|
176
|
+
expect(staged[1]).toEqual({ status: "A", file: "src/new.ts" });
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("应该正确解析未暂存的文件", () => {
|
|
180
|
+
// 模拟 git status --porcelain 输出
|
|
181
|
+
// " M" = 未暂存的修改
|
|
182
|
+
// "??" = 未跟踪的文件
|
|
183
|
+
const output = " M src/modified.ts\n?? src/untracked.ts";
|
|
184
|
+
const lines = output.split("\n");
|
|
185
|
+
|
|
186
|
+
const unstaged: { status: string; file: string }[] = [];
|
|
187
|
+
|
|
188
|
+
for (const line of lines) {
|
|
189
|
+
if (!line) continue;
|
|
190
|
+
const indexStatus = line[0];
|
|
191
|
+
const workTreeStatus = line[1];
|
|
192
|
+
const file = line.slice(3);
|
|
193
|
+
|
|
194
|
+
if (workTreeStatus !== " " || indexStatus === "?") {
|
|
195
|
+
const status = indexStatus === "?" ? "?" : workTreeStatus;
|
|
196
|
+
unstaged.push({ status, file });
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
expect(unstaged).toHaveLength(2);
|
|
201
|
+
expect(unstaged[0]).toEqual({ status: "M", file: "src/modified.ts" });
|
|
202
|
+
expect(unstaged[1]).toEqual({ status: "?", file: "src/untracked.ts" });
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("空输出应该返回空数组", () => {
|
|
206
|
+
const output = "";
|
|
207
|
+
const staged: any[] = [];
|
|
208
|
+
const unstaged: any[] = [];
|
|
209
|
+
|
|
210
|
+
if (!output) {
|
|
211
|
+
expect(staged).toHaveLength(0);
|
|
212
|
+
expect(unstaged).toHaveLength(0);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it("应该正确处理同时有暂存和未暂存状态的文件", () => {
|
|
217
|
+
// "MM" = 已暂存且有新的未暂存修改
|
|
218
|
+
const output = "MM src/both.ts";
|
|
219
|
+
const lines = output.split("\n");
|
|
220
|
+
|
|
221
|
+
const staged: { status: string; file: string }[] = [];
|
|
222
|
+
const unstaged: { status: string; file: string }[] = [];
|
|
223
|
+
|
|
224
|
+
for (const line of lines) {
|
|
225
|
+
if (!line) continue;
|
|
226
|
+
const indexStatus = line[0];
|
|
227
|
+
const workTreeStatus = line[1];
|
|
228
|
+
const file = line.slice(3);
|
|
229
|
+
|
|
230
|
+
if (indexStatus !== " " && indexStatus !== "?") {
|
|
231
|
+
staged.push({ status: indexStatus, file });
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (workTreeStatus !== " " || indexStatus === "?") {
|
|
235
|
+
const status = indexStatus === "?" ? "?" : workTreeStatus;
|
|
236
|
+
unstaged.push({ status, file });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
expect(staged).toHaveLength(1);
|
|
241
|
+
expect(unstaged).toHaveLength(1);
|
|
242
|
+
expect(staged[0]).toEqual({ status: "M", file: "src/both.ts" });
|
|
243
|
+
expect(unstaged[0]).toEqual({ status: "M", file: "src/both.ts" });
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
describe("临时文件提交", () => {
|
|
248
|
+
it("应该使用临时文件传递多行 commit message", () => {
|
|
249
|
+
const message = "feat: 新功能\n\n- 详细描述1\n- 详细描述2";
|
|
250
|
+
const lines = message.split("\n");
|
|
251
|
+
|
|
252
|
+
expect(lines).toHaveLength(4);
|
|
253
|
+
expect(lines[0]).toBe("feat: 新功能");
|
|
254
|
+
expect(lines[1]).toBe("");
|
|
255
|
+
expect(lines[2]).toBe("- 详细描述1");
|
|
256
|
+
expect(lines[3]).toBe("- 详细描述2");
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it("临时文件名应该包含时间戳避免冲突", () => {
|
|
260
|
+
const timestamp1 = Date.now();
|
|
261
|
+
const tmpFile1 = `.gw-commit-msg-${timestamp1}`;
|
|
262
|
+
|
|
263
|
+
// 模拟短暂延迟
|
|
264
|
+
const timestamp2 = timestamp1 + 1;
|
|
265
|
+
const tmpFile2 = `.gw-commit-msg-${timestamp2}`;
|
|
266
|
+
|
|
267
|
+
expect(tmpFile1).not.toBe(tmpFile2);
|
|
268
|
+
});
|
|
269
|
+
});
|
|
85
270
|
});
|
package/tests/init.test.ts
CHANGED
|
@@ -43,8 +43,6 @@ describe("Init 模块测试", () => {
|
|
|
43
43
|
beforeEach(() => {
|
|
44
44
|
vi.clearAllMocks();
|
|
45
45
|
vi.spyOn(console, "log").mockImplementation(() => {});
|
|
46
|
-
|
|
47
|
-
// Default mocks
|
|
48
46
|
mockHomedir.mockReturnValue("/home/user");
|
|
49
47
|
mockJoin.mockReturnValue("/home/user/.gwrc.json");
|
|
50
48
|
});
|
|
@@ -56,28 +54,25 @@ describe("Init 模块测试", () => {
|
|
|
56
54
|
describe("配置范围选择", () => {
|
|
57
55
|
it("应该支持全局配置", async () => {
|
|
58
56
|
mockExistsSync.mockReturnValue(false);
|
|
59
|
-
|
|
60
57
|
const { select, input } = await import("@inquirer/prompts");
|
|
61
58
|
vi.mocked(select)
|
|
62
|
-
.mockResolvedValueOnce("global")
|
|
63
|
-
.mockResolvedValueOnce(false)
|
|
64
|
-
.mockResolvedValueOnce(
|
|
65
|
-
.mockResolvedValueOnce(
|
|
66
|
-
.mockResolvedValueOnce(
|
|
67
|
-
.mockResolvedValueOnce(
|
|
68
|
-
|
|
59
|
+
.mockResolvedValueOnce("global")
|
|
60
|
+
.mockResolvedValueOnce(false)
|
|
61
|
+
.mockResolvedValueOnce(false)
|
|
62
|
+
.mockResolvedValueOnce(false)
|
|
63
|
+
.mockResolvedValueOnce("ask")
|
|
64
|
+
.mockResolvedValueOnce(true)
|
|
65
|
+
.mockResolvedValueOnce(true)
|
|
66
|
+
.mockResolvedValueOnce(false);
|
|
69
67
|
vi.mocked(input)
|
|
70
|
-
.mockResolvedValueOnce("")
|
|
71
|
-
.mockResolvedValueOnce("feature")
|
|
72
|
-
.mockResolvedValueOnce("hotfix")
|
|
73
|
-
.mockResolvedValueOnce("Story ID")
|
|
74
|
-
.mockResolvedValueOnce("Issue ID")
|
|
75
|
-
.mockResolvedValueOnce("");
|
|
76
|
-
|
|
68
|
+
.mockResolvedValueOnce("")
|
|
69
|
+
.mockResolvedValueOnce("feature")
|
|
70
|
+
.mockResolvedValueOnce("hotfix")
|
|
71
|
+
.mockResolvedValueOnce("Story ID")
|
|
72
|
+
.mockResolvedValueOnce("Issue ID")
|
|
73
|
+
.mockResolvedValueOnce("");
|
|
77
74
|
const { init } = await import("../src/commands/init.js");
|
|
78
|
-
|
|
79
75
|
await init();
|
|
80
|
-
|
|
81
76
|
expect(mockJoin).toHaveBeenCalledWith("/home/user", ".gwrc.json");
|
|
82
77
|
expect(mockWriteFileSync).toHaveBeenCalledWith(
|
|
83
78
|
"/home/user/.gwrc.json",
|
|
@@ -87,28 +82,25 @@ describe("Init 模块测试", () => {
|
|
|
87
82
|
|
|
88
83
|
it("应该支持项目配置", async () => {
|
|
89
84
|
mockExistsSync.mockReturnValue(false);
|
|
90
|
-
|
|
91
85
|
const { select, input } = await import("@inquirer/prompts");
|
|
92
86
|
vi.mocked(select)
|
|
93
|
-
.mockResolvedValueOnce("project")
|
|
94
|
-
.mockResolvedValueOnce(false)
|
|
95
|
-
.mockResolvedValueOnce(
|
|
96
|
-
.mockResolvedValueOnce(
|
|
97
|
-
.mockResolvedValueOnce(
|
|
98
|
-
.mockResolvedValueOnce(
|
|
99
|
-
|
|
87
|
+
.mockResolvedValueOnce("project")
|
|
88
|
+
.mockResolvedValueOnce(false)
|
|
89
|
+
.mockResolvedValueOnce(false)
|
|
90
|
+
.mockResolvedValueOnce(false)
|
|
91
|
+
.mockResolvedValueOnce("ask")
|
|
92
|
+
.mockResolvedValueOnce(true)
|
|
93
|
+
.mockResolvedValueOnce(true)
|
|
94
|
+
.mockResolvedValueOnce(false);
|
|
100
95
|
vi.mocked(input)
|
|
101
|
-
.mockResolvedValueOnce("")
|
|
102
|
-
.mockResolvedValueOnce("feature")
|
|
103
|
-
.mockResolvedValueOnce("hotfix")
|
|
104
|
-
.mockResolvedValueOnce("Story ID")
|
|
105
|
-
.mockResolvedValueOnce("Issue ID")
|
|
106
|
-
.mockResolvedValueOnce("");
|
|
107
|
-
|
|
96
|
+
.mockResolvedValueOnce("")
|
|
97
|
+
.mockResolvedValueOnce("feature")
|
|
98
|
+
.mockResolvedValueOnce("hotfix")
|
|
99
|
+
.mockResolvedValueOnce("Story ID")
|
|
100
|
+
.mockResolvedValueOnce("Issue ID")
|
|
101
|
+
.mockResolvedValueOnce("");
|
|
108
102
|
const { init } = await import("../src/commands/init.js");
|
|
109
|
-
|
|
110
103
|
await init();
|
|
111
|
-
|
|
112
104
|
expect(mockWriteFileSync).toHaveBeenCalledWith(
|
|
113
105
|
".gwrc.json",
|
|
114
106
|
expect.stringContaining('"featurePrefix": "feature"')
|
|
@@ -119,44 +111,37 @@ describe("Init 模块测试", () => {
|
|
|
119
111
|
describe("配置文件覆盖", () => {
|
|
120
112
|
it("应该处理配置文件已存在的情况", async () => {
|
|
121
113
|
mockExistsSync.mockReturnValue(true);
|
|
122
|
-
|
|
123
114
|
const { select, input } = await import("@inquirer/prompts");
|
|
124
115
|
vi.mocked(select)
|
|
125
|
-
.mockResolvedValueOnce("global")
|
|
126
|
-
.mockResolvedValueOnce(true)
|
|
127
|
-
.mockResolvedValueOnce(false)
|
|
128
|
-
.mockResolvedValueOnce(
|
|
129
|
-
.mockResolvedValueOnce(
|
|
130
|
-
.mockResolvedValueOnce(
|
|
131
|
-
.mockResolvedValueOnce(
|
|
132
|
-
|
|
116
|
+
.mockResolvedValueOnce("global")
|
|
117
|
+
.mockResolvedValueOnce(true)
|
|
118
|
+
.mockResolvedValueOnce(false)
|
|
119
|
+
.mockResolvedValueOnce(false)
|
|
120
|
+
.mockResolvedValueOnce(false)
|
|
121
|
+
.mockResolvedValueOnce("ask")
|
|
122
|
+
.mockResolvedValueOnce(true)
|
|
123
|
+
.mockResolvedValueOnce(true)
|
|
124
|
+
.mockResolvedValueOnce(false);
|
|
133
125
|
vi.mocked(input)
|
|
134
|
-
.mockResolvedValueOnce("")
|
|
135
|
-
.mockResolvedValueOnce("feature")
|
|
136
|
-
.mockResolvedValueOnce("hotfix")
|
|
137
|
-
.mockResolvedValueOnce("Story ID")
|
|
138
|
-
.mockResolvedValueOnce("Issue ID")
|
|
139
|
-
.mockResolvedValueOnce("");
|
|
140
|
-
|
|
126
|
+
.mockResolvedValueOnce("")
|
|
127
|
+
.mockResolvedValueOnce("feature")
|
|
128
|
+
.mockResolvedValueOnce("hotfix")
|
|
129
|
+
.mockResolvedValueOnce("Story ID")
|
|
130
|
+
.mockResolvedValueOnce("Issue ID")
|
|
131
|
+
.mockResolvedValueOnce("");
|
|
141
132
|
const { init } = await import("../src/commands/init.js");
|
|
142
|
-
|
|
143
133
|
await init();
|
|
144
|
-
|
|
145
134
|
expect(mockWriteFileSync).toHaveBeenCalled();
|
|
146
135
|
});
|
|
147
136
|
|
|
148
137
|
it("应该处理用户取消覆盖", async () => {
|
|
149
138
|
mockExistsSync.mockReturnValue(true);
|
|
150
|
-
|
|
151
139
|
const { select } = await import("@inquirer/prompts");
|
|
152
140
|
vi.mocked(select)
|
|
153
|
-
.mockResolvedValueOnce("global")
|
|
154
|
-
.mockResolvedValueOnce(false);
|
|
155
|
-
|
|
141
|
+
.mockResolvedValueOnce("global")
|
|
142
|
+
.mockResolvedValueOnce(false);
|
|
156
143
|
const { init } = await import("../src/commands/init.js");
|
|
157
|
-
|
|
158
144
|
await init();
|
|
159
|
-
|
|
160
145
|
expect(console.log).toHaveBeenCalledWith("已取消");
|
|
161
146
|
expect(mockWriteFileSync).not.toHaveBeenCalled();
|
|
162
147
|
});
|
|
@@ -165,31 +150,27 @@ describe("Init 模块测试", () => {
|
|
|
165
150
|
describe("基础配置", () => {
|
|
166
151
|
it("应该正确配置分支前缀", async () => {
|
|
167
152
|
mockExistsSync.mockReturnValue(false);
|
|
168
|
-
|
|
169
153
|
const { select, input } = await import("@inquirer/prompts");
|
|
170
154
|
vi.mocked(select)
|
|
171
|
-
.mockResolvedValueOnce("project")
|
|
172
|
-
.mockResolvedValueOnce(false)
|
|
173
|
-
.mockResolvedValueOnce(
|
|
174
|
-
.mockResolvedValueOnce(
|
|
175
|
-
.mockResolvedValueOnce(
|
|
176
|
-
.mockResolvedValueOnce(
|
|
177
|
-
|
|
155
|
+
.mockResolvedValueOnce("project")
|
|
156
|
+
.mockResolvedValueOnce(false)
|
|
157
|
+
.mockResolvedValueOnce(false)
|
|
158
|
+
.mockResolvedValueOnce(false)
|
|
159
|
+
.mockResolvedValueOnce("ask")
|
|
160
|
+
.mockResolvedValueOnce(true)
|
|
161
|
+
.mockResolvedValueOnce(true)
|
|
162
|
+
.mockResolvedValueOnce(false);
|
|
178
163
|
vi.mocked(input)
|
|
179
|
-
.mockResolvedValueOnce("develop")
|
|
180
|
-
.mockResolvedValueOnce("feat")
|
|
181
|
-
.mockResolvedValueOnce("fix")
|
|
182
|
-
.mockResolvedValueOnce("Jira ID")
|
|
183
|
-
.mockResolvedValueOnce("Bug ID")
|
|
184
|
-
.mockResolvedValueOnce("v");
|
|
185
|
-
|
|
164
|
+
.mockResolvedValueOnce("develop")
|
|
165
|
+
.mockResolvedValueOnce("feat")
|
|
166
|
+
.mockResolvedValueOnce("fix")
|
|
167
|
+
.mockResolvedValueOnce("Jira ID")
|
|
168
|
+
.mockResolvedValueOnce("Bug ID")
|
|
169
|
+
.mockResolvedValueOnce("v");
|
|
186
170
|
const { init } = await import("../src/commands/init.js");
|
|
187
|
-
|
|
188
171
|
await init();
|
|
189
|
-
|
|
190
172
|
const writtenConfig = mockWriteFileSync.mock.calls[0][1] as string;
|
|
191
173
|
const config = JSON.parse(writtenConfig);
|
|
192
|
-
|
|
193
174
|
expect(config.baseBranch).toBe("develop");
|
|
194
175
|
expect(config.featurePrefix).toBe("feat");
|
|
195
176
|
expect(config.hotfixPrefix).toBe("fix");
|
|
@@ -200,61 +181,53 @@ describe("Init 模块测试", () => {
|
|
|
200
181
|
|
|
201
182
|
it("应该正确配置 ID 要求", async () => {
|
|
202
183
|
mockExistsSync.mockReturnValue(false);
|
|
203
|
-
|
|
204
184
|
const { select, input } = await import("@inquirer/prompts");
|
|
205
185
|
vi.mocked(select)
|
|
206
|
-
.mockResolvedValueOnce("project")
|
|
207
|
-
.mockResolvedValueOnce(true)
|
|
208
|
-
.mockResolvedValueOnce(
|
|
209
|
-
.mockResolvedValueOnce(
|
|
210
|
-
.mockResolvedValueOnce(
|
|
211
|
-
.mockResolvedValueOnce(
|
|
212
|
-
|
|
186
|
+
.mockResolvedValueOnce("project")
|
|
187
|
+
.mockResolvedValueOnce(true)
|
|
188
|
+
.mockResolvedValueOnce(false)
|
|
189
|
+
.mockResolvedValueOnce(false)
|
|
190
|
+
.mockResolvedValueOnce("ask")
|
|
191
|
+
.mockResolvedValueOnce(true)
|
|
192
|
+
.mockResolvedValueOnce(true)
|
|
193
|
+
.mockResolvedValueOnce(false);
|
|
213
194
|
vi.mocked(input)
|
|
214
|
-
.mockResolvedValueOnce("")
|
|
215
|
-
.mockResolvedValueOnce("feature")
|
|
216
|
-
.mockResolvedValueOnce("hotfix")
|
|
217
|
-
.mockResolvedValueOnce("Story ID")
|
|
218
|
-
.mockResolvedValueOnce("Issue ID")
|
|
219
|
-
.mockResolvedValueOnce("");
|
|
220
|
-
|
|
195
|
+
.mockResolvedValueOnce("")
|
|
196
|
+
.mockResolvedValueOnce("feature")
|
|
197
|
+
.mockResolvedValueOnce("hotfix")
|
|
198
|
+
.mockResolvedValueOnce("Story ID")
|
|
199
|
+
.mockResolvedValueOnce("Issue ID")
|
|
200
|
+
.mockResolvedValueOnce("");
|
|
221
201
|
const { init } = await import("../src/commands/init.js");
|
|
222
|
-
|
|
223
202
|
await init();
|
|
224
|
-
|
|
225
203
|
const writtenConfig = mockWriteFileSync.mock.calls[0][1] as string;
|
|
226
204
|
const config = JSON.parse(writtenConfig);
|
|
227
|
-
|
|
228
205
|
expect(config.requireId).toBe(true);
|
|
229
206
|
});
|
|
230
207
|
|
|
231
208
|
it("应该正确配置自动推送选项", async () => {
|
|
232
209
|
mockExistsSync.mockReturnValue(false);
|
|
233
|
-
|
|
234
210
|
const { select, input } = await import("@inquirer/prompts");
|
|
235
211
|
vi.mocked(select)
|
|
236
|
-
.mockResolvedValueOnce("project")
|
|
237
|
-
.mockResolvedValueOnce(false)
|
|
238
|
-
.mockResolvedValueOnce(
|
|
239
|
-
.mockResolvedValueOnce(
|
|
240
|
-
.mockResolvedValueOnce(
|
|
241
|
-
.mockResolvedValueOnce(
|
|
242
|
-
|
|
212
|
+
.mockResolvedValueOnce("project")
|
|
213
|
+
.mockResolvedValueOnce(false)
|
|
214
|
+
.mockResolvedValueOnce(false)
|
|
215
|
+
.mockResolvedValueOnce(false)
|
|
216
|
+
.mockResolvedValueOnce("yes")
|
|
217
|
+
.mockResolvedValueOnce(true)
|
|
218
|
+
.mockResolvedValueOnce(true)
|
|
219
|
+
.mockResolvedValueOnce(false);
|
|
243
220
|
vi.mocked(input)
|
|
244
|
-
.mockResolvedValueOnce("")
|
|
245
|
-
.mockResolvedValueOnce("feature")
|
|
246
|
-
.mockResolvedValueOnce("hotfix")
|
|
247
|
-
.mockResolvedValueOnce("Story ID")
|
|
248
|
-
.mockResolvedValueOnce("Issue ID")
|
|
249
|
-
.mockResolvedValueOnce("");
|
|
250
|
-
|
|
221
|
+
.mockResolvedValueOnce("")
|
|
222
|
+
.mockResolvedValueOnce("feature")
|
|
223
|
+
.mockResolvedValueOnce("hotfix")
|
|
224
|
+
.mockResolvedValueOnce("Story ID")
|
|
225
|
+
.mockResolvedValueOnce("Issue ID")
|
|
226
|
+
.mockResolvedValueOnce("");
|
|
251
227
|
const { init } = await import("../src/commands/init.js");
|
|
252
|
-
|
|
253
228
|
await init();
|
|
254
|
-
|
|
255
229
|
const writtenConfig = mockWriteFileSync.mock.calls[0][1] as string;
|
|
256
230
|
const config = JSON.parse(writtenConfig);
|
|
257
|
-
|
|
258
231
|
expect(config.autoPush).toBe(true);
|
|
259
232
|
});
|
|
260
233
|
});
|
|
@@ -262,34 +235,32 @@ describe("Init 模块测试", () => {
|
|
|
262
235
|
describe("AI 配置", () => {
|
|
263
236
|
it("应该正确配置 GitHub Models", async () => {
|
|
264
237
|
mockExistsSync.mockReturnValue(false);
|
|
265
|
-
|
|
266
238
|
const { select, input } = await import("@inquirer/prompts");
|
|
267
239
|
vi.mocked(select)
|
|
268
|
-
.mockResolvedValueOnce("project")
|
|
269
|
-
.mockResolvedValueOnce(false)
|
|
270
|
-
.mockResolvedValueOnce(
|
|
271
|
-
.mockResolvedValueOnce(
|
|
272
|
-
.mockResolvedValueOnce(
|
|
273
|
-
.mockResolvedValueOnce(true)
|
|
274
|
-
.mockResolvedValueOnce(
|
|
275
|
-
.mockResolvedValueOnce(
|
|
276
|
-
|
|
240
|
+
.mockResolvedValueOnce("project")
|
|
241
|
+
.mockResolvedValueOnce(false)
|
|
242
|
+
.mockResolvedValueOnce(false)
|
|
243
|
+
.mockResolvedValueOnce(false)
|
|
244
|
+
.mockResolvedValueOnce("ask")
|
|
245
|
+
.mockResolvedValueOnce(true)
|
|
246
|
+
.mockResolvedValueOnce(true)
|
|
247
|
+
.mockResolvedValueOnce(true)
|
|
248
|
+
.mockResolvedValueOnce("github")
|
|
249
|
+
.mockResolvedValueOnce("zh-CN")
|
|
250
|
+
.mockResolvedValueOnce(true)
|
|
251
|
+
.mockResolvedValueOnce(true);
|
|
277
252
|
vi.mocked(input)
|
|
278
|
-
.mockResolvedValueOnce("")
|
|
279
|
-
.mockResolvedValueOnce("feature")
|
|
280
|
-
.mockResolvedValueOnce("hotfix")
|
|
281
|
-
.mockResolvedValueOnce("Story ID")
|
|
282
|
-
.mockResolvedValueOnce("Issue ID")
|
|
283
|
-
.mockResolvedValueOnce("")
|
|
284
|
-
.mockResolvedValueOnce("ghp_test_token");
|
|
285
|
-
|
|
253
|
+
.mockResolvedValueOnce("")
|
|
254
|
+
.mockResolvedValueOnce("feature")
|
|
255
|
+
.mockResolvedValueOnce("hotfix")
|
|
256
|
+
.mockResolvedValueOnce("Story ID")
|
|
257
|
+
.mockResolvedValueOnce("Issue ID")
|
|
258
|
+
.mockResolvedValueOnce("")
|
|
259
|
+
.mockResolvedValueOnce("ghp_test_token");
|
|
286
260
|
const { init } = await import("../src/commands/init.js");
|
|
287
|
-
|
|
288
261
|
await init();
|
|
289
|
-
|
|
290
262
|
const writtenConfig = mockWriteFileSync.mock.calls[0][1] as string;
|
|
291
263
|
const config = JSON.parse(writtenConfig);
|
|
292
|
-
|
|
293
264
|
expect(config.aiCommit.enabled).toBe(true);
|
|
294
265
|
expect(config.aiCommit.provider).toBe("github");
|
|
295
266
|
expect(config.aiCommit.apiKey).toBe("ghp_test_token");
|
|
@@ -299,34 +270,32 @@ describe("Init 模块测试", () => {
|
|
|
299
270
|
|
|
300
271
|
it("应该正确配置 OpenAI", async () => {
|
|
301
272
|
mockExistsSync.mockReturnValue(false);
|
|
302
|
-
|
|
303
273
|
const { select, input } = await import("@inquirer/prompts");
|
|
304
274
|
vi.mocked(select)
|
|
305
|
-
.mockResolvedValueOnce("project")
|
|
306
|
-
.mockResolvedValueOnce(false)
|
|
307
|
-
.mockResolvedValueOnce(
|
|
308
|
-
.mockResolvedValueOnce(
|
|
309
|
-
.mockResolvedValueOnce(
|
|
310
|
-
.mockResolvedValueOnce(true)
|
|
311
|
-
.mockResolvedValueOnce(
|
|
312
|
-
.mockResolvedValueOnce(
|
|
313
|
-
|
|
275
|
+
.mockResolvedValueOnce("project")
|
|
276
|
+
.mockResolvedValueOnce(false)
|
|
277
|
+
.mockResolvedValueOnce(false)
|
|
278
|
+
.mockResolvedValueOnce(false)
|
|
279
|
+
.mockResolvedValueOnce("ask")
|
|
280
|
+
.mockResolvedValueOnce(true)
|
|
281
|
+
.mockResolvedValueOnce(true)
|
|
282
|
+
.mockResolvedValueOnce(true)
|
|
283
|
+
.mockResolvedValueOnce("openai")
|
|
284
|
+
.mockResolvedValueOnce("en-US")
|
|
285
|
+
.mockResolvedValueOnce(true)
|
|
286
|
+
.mockResolvedValueOnce(true);
|
|
314
287
|
vi.mocked(input)
|
|
315
|
-
.mockResolvedValueOnce("")
|
|
316
|
-
.mockResolvedValueOnce("feature")
|
|
317
|
-
.mockResolvedValueOnce("hotfix")
|
|
318
|
-
.mockResolvedValueOnce("Story ID")
|
|
319
|
-
.mockResolvedValueOnce("Issue ID")
|
|
320
|
-
.mockResolvedValueOnce("")
|
|
321
|
-
.mockResolvedValueOnce("sk-test-key");
|
|
322
|
-
|
|
288
|
+
.mockResolvedValueOnce("")
|
|
289
|
+
.mockResolvedValueOnce("feature")
|
|
290
|
+
.mockResolvedValueOnce("hotfix")
|
|
291
|
+
.mockResolvedValueOnce("Story ID")
|
|
292
|
+
.mockResolvedValueOnce("Issue ID")
|
|
293
|
+
.mockResolvedValueOnce("")
|
|
294
|
+
.mockResolvedValueOnce("sk-test-key");
|
|
323
295
|
const { init } = await import("../src/commands/init.js");
|
|
324
|
-
|
|
325
296
|
await init();
|
|
326
|
-
|
|
327
297
|
const writtenConfig = mockWriteFileSync.mock.calls[0][1] as string;
|
|
328
298
|
const config = JSON.parse(writtenConfig);
|
|
329
|
-
|
|
330
299
|
expect(config.aiCommit.enabled).toBe(true);
|
|
331
300
|
expect(config.aiCommit.provider).toBe("openai");
|
|
332
301
|
expect(config.aiCommit.apiKey).toBe("sk-test-key");
|
|
@@ -336,33 +305,31 @@ describe("Init 模块测试", () => {
|
|
|
336
305
|
|
|
337
306
|
it("应该正确配置 Ollama", async () => {
|
|
338
307
|
mockExistsSync.mockReturnValue(false);
|
|
339
|
-
|
|
340
308
|
const { select, input } = await import("@inquirer/prompts");
|
|
341
309
|
vi.mocked(select)
|
|
342
|
-
.mockResolvedValueOnce("project")
|
|
343
|
-
.mockResolvedValueOnce(false)
|
|
344
|
-
.mockResolvedValueOnce(
|
|
345
|
-
.mockResolvedValueOnce(
|
|
346
|
-
.mockResolvedValueOnce(
|
|
347
|
-
.mockResolvedValueOnce(true)
|
|
348
|
-
.mockResolvedValueOnce(
|
|
349
|
-
.mockResolvedValueOnce(
|
|
350
|
-
|
|
310
|
+
.mockResolvedValueOnce("project")
|
|
311
|
+
.mockResolvedValueOnce(false)
|
|
312
|
+
.mockResolvedValueOnce(false)
|
|
313
|
+
.mockResolvedValueOnce(false)
|
|
314
|
+
.mockResolvedValueOnce("ask")
|
|
315
|
+
.mockResolvedValueOnce(true)
|
|
316
|
+
.mockResolvedValueOnce(true)
|
|
317
|
+
.mockResolvedValueOnce(true)
|
|
318
|
+
.mockResolvedValueOnce("ollama")
|
|
319
|
+
.mockResolvedValueOnce("zh-CN")
|
|
320
|
+
.mockResolvedValueOnce(true)
|
|
321
|
+
.mockResolvedValueOnce(true);
|
|
351
322
|
vi.mocked(input)
|
|
352
|
-
.mockResolvedValueOnce("")
|
|
353
|
-
.mockResolvedValueOnce("feature")
|
|
354
|
-
.mockResolvedValueOnce("hotfix")
|
|
355
|
-
.mockResolvedValueOnce("Story ID")
|
|
356
|
-
.mockResolvedValueOnce("Issue ID")
|
|
357
|
-
.mockResolvedValueOnce("");
|
|
358
|
-
|
|
323
|
+
.mockResolvedValueOnce("")
|
|
324
|
+
.mockResolvedValueOnce("feature")
|
|
325
|
+
.mockResolvedValueOnce("hotfix")
|
|
326
|
+
.mockResolvedValueOnce("Story ID")
|
|
327
|
+
.mockResolvedValueOnce("Issue ID")
|
|
328
|
+
.mockResolvedValueOnce("");
|
|
359
329
|
const { init } = await import("../src/commands/init.js");
|
|
360
|
-
|
|
361
330
|
await init();
|
|
362
|
-
|
|
363
331
|
const writtenConfig = mockWriteFileSync.mock.calls[0][1] as string;
|
|
364
332
|
const config = JSON.parse(writtenConfig);
|
|
365
|
-
|
|
366
333
|
expect(config.aiCommit.enabled).toBe(true);
|
|
367
334
|
expect(config.aiCommit.provider).toBe("ollama");
|
|
368
335
|
expect(config.aiCommit.apiKey).toBeUndefined();
|
|
@@ -372,31 +339,27 @@ describe("Init 模块测试", () => {
|
|
|
372
339
|
|
|
373
340
|
it("应该正确配置禁用 AI", async () => {
|
|
374
341
|
mockExistsSync.mockReturnValue(false);
|
|
375
|
-
|
|
376
342
|
const { select, input } = await import("@inquirer/prompts");
|
|
377
343
|
vi.mocked(select)
|
|
378
|
-
.mockResolvedValueOnce("project")
|
|
379
|
-
.mockResolvedValueOnce(false)
|
|
380
|
-
.mockResolvedValueOnce(
|
|
381
|
-
.mockResolvedValueOnce(
|
|
382
|
-
.mockResolvedValueOnce(
|
|
383
|
-
.mockResolvedValueOnce(
|
|
384
|
-
|
|
344
|
+
.mockResolvedValueOnce("project")
|
|
345
|
+
.mockResolvedValueOnce(false)
|
|
346
|
+
.mockResolvedValueOnce(false)
|
|
347
|
+
.mockResolvedValueOnce(false)
|
|
348
|
+
.mockResolvedValueOnce("ask")
|
|
349
|
+
.mockResolvedValueOnce(true)
|
|
350
|
+
.mockResolvedValueOnce(true)
|
|
351
|
+
.mockResolvedValueOnce(false);
|
|
385
352
|
vi.mocked(input)
|
|
386
|
-
.mockResolvedValueOnce("")
|
|
387
|
-
.mockResolvedValueOnce("feature")
|
|
388
|
-
.mockResolvedValueOnce("hotfix")
|
|
389
|
-
.mockResolvedValueOnce("Story ID")
|
|
390
|
-
.mockResolvedValueOnce("Issue ID")
|
|
391
|
-
.mockResolvedValueOnce("");
|
|
392
|
-
|
|
353
|
+
.mockResolvedValueOnce("")
|
|
354
|
+
.mockResolvedValueOnce("feature")
|
|
355
|
+
.mockResolvedValueOnce("hotfix")
|
|
356
|
+
.mockResolvedValueOnce("Story ID")
|
|
357
|
+
.mockResolvedValueOnce("Issue ID")
|
|
358
|
+
.mockResolvedValueOnce("");
|
|
393
359
|
const { init } = await import("../src/commands/init.js");
|
|
394
|
-
|
|
395
360
|
await init();
|
|
396
|
-
|
|
397
361
|
const writtenConfig = mockWriteFileSync.mock.calls[0][1] as string;
|
|
398
362
|
const config = JSON.parse(writtenConfig);
|
|
399
|
-
|
|
400
363
|
expect(config.aiCommit.enabled).toBe(false);
|
|
401
364
|
});
|
|
402
365
|
});
|
|
@@ -404,41 +367,36 @@ describe("Init 模块测试", () => {
|
|
|
404
367
|
describe("配置验证", () => {
|
|
405
368
|
it("应该验证 GitHub Token 不为空", async () => {
|
|
406
369
|
mockExistsSync.mockReturnValue(false);
|
|
407
|
-
|
|
408
370
|
const { select, input } = await import("@inquirer/prompts");
|
|
409
371
|
vi.mocked(select)
|
|
410
|
-
.mockResolvedValueOnce("project")
|
|
411
|
-
.mockResolvedValueOnce(false)
|
|
412
|
-
.mockResolvedValueOnce(
|
|
413
|
-
.mockResolvedValueOnce(
|
|
414
|
-
.mockResolvedValueOnce(
|
|
415
|
-
.mockResolvedValueOnce(true)
|
|
416
|
-
.mockResolvedValueOnce(
|
|
417
|
-
.mockResolvedValueOnce(
|
|
418
|
-
|
|
372
|
+
.mockResolvedValueOnce("project")
|
|
373
|
+
.mockResolvedValueOnce(false)
|
|
374
|
+
.mockResolvedValueOnce(false)
|
|
375
|
+
.mockResolvedValueOnce(false)
|
|
376
|
+
.mockResolvedValueOnce("ask")
|
|
377
|
+
.mockResolvedValueOnce(true)
|
|
378
|
+
.mockResolvedValueOnce(true)
|
|
379
|
+
.mockResolvedValueOnce(true)
|
|
380
|
+
.mockResolvedValueOnce("github")
|
|
381
|
+
.mockResolvedValueOnce("zh-CN")
|
|
382
|
+
.mockResolvedValueOnce(true)
|
|
383
|
+
.mockResolvedValueOnce(true);
|
|
419
384
|
vi.mocked(input)
|
|
420
|
-
.mockResolvedValueOnce("")
|
|
421
|
-
.mockResolvedValueOnce("feature")
|
|
422
|
-
.mockResolvedValueOnce("hotfix")
|
|
423
|
-
.mockResolvedValueOnce("Story ID")
|
|
424
|
-
.mockResolvedValueOnce("Issue ID")
|
|
425
|
-
.mockResolvedValueOnce("")
|
|
426
|
-
.mockResolvedValueOnce("ghp_valid_token");
|
|
427
|
-
|
|
385
|
+
.mockResolvedValueOnce("")
|
|
386
|
+
.mockResolvedValueOnce("feature")
|
|
387
|
+
.mockResolvedValueOnce("hotfix")
|
|
388
|
+
.mockResolvedValueOnce("Story ID")
|
|
389
|
+
.mockResolvedValueOnce("Issue ID")
|
|
390
|
+
.mockResolvedValueOnce("")
|
|
391
|
+
.mockResolvedValueOnce("ghp_valid_token");
|
|
428
392
|
const { init } = await import("../src/commands/init.js");
|
|
429
|
-
|
|
430
393
|
await init();
|
|
431
|
-
|
|
432
|
-
// 验证 input 被调用时包含验证函数
|
|
433
394
|
const inputCalls = vi.mocked(input).mock.calls;
|
|
434
|
-
const tokenInputCall = inputCalls.find(call =>
|
|
395
|
+
const tokenInputCall = inputCalls.find((call) =>
|
|
435
396
|
call[0].message?.includes("GitHub Token")
|
|
436
397
|
);
|
|
437
|
-
|
|
438
398
|
expect(tokenInputCall).toBeDefined();
|
|
439
399
|
expect(tokenInputCall![0].validate).toBeDefined();
|
|
440
|
-
|
|
441
|
-
// 测试验证函数
|
|
442
400
|
const validate = tokenInputCall![0].validate!;
|
|
443
401
|
expect(validate("")).toBe("GitHub Token 不能为空");
|
|
444
402
|
expect(validate("valid-token")).toBe(true);
|
|
@@ -446,41 +404,36 @@ describe("Init 模块测试", () => {
|
|
|
446
404
|
|
|
447
405
|
it("应该验证 OpenAI API Key 不为空", async () => {
|
|
448
406
|
mockExistsSync.mockReturnValue(false);
|
|
449
|
-
|
|
450
407
|
const { select, input } = await import("@inquirer/prompts");
|
|
451
408
|
vi.mocked(select)
|
|
452
|
-
.mockResolvedValueOnce("project")
|
|
453
|
-
.mockResolvedValueOnce(false)
|
|
454
|
-
.mockResolvedValueOnce(
|
|
455
|
-
.mockResolvedValueOnce(
|
|
456
|
-
.mockResolvedValueOnce(
|
|
457
|
-
.mockResolvedValueOnce(true)
|
|
458
|
-
.mockResolvedValueOnce(
|
|
459
|
-
.mockResolvedValueOnce(
|
|
460
|
-
|
|
409
|
+
.mockResolvedValueOnce("project")
|
|
410
|
+
.mockResolvedValueOnce(false)
|
|
411
|
+
.mockResolvedValueOnce(false)
|
|
412
|
+
.mockResolvedValueOnce(false)
|
|
413
|
+
.mockResolvedValueOnce("ask")
|
|
414
|
+
.mockResolvedValueOnce(true)
|
|
415
|
+
.mockResolvedValueOnce(true)
|
|
416
|
+
.mockResolvedValueOnce(true)
|
|
417
|
+
.mockResolvedValueOnce("openai")
|
|
418
|
+
.mockResolvedValueOnce("en-US")
|
|
419
|
+
.mockResolvedValueOnce(true)
|
|
420
|
+
.mockResolvedValueOnce(true);
|
|
461
421
|
vi.mocked(input)
|
|
462
|
-
.mockResolvedValueOnce("")
|
|
463
|
-
.mockResolvedValueOnce("feature")
|
|
464
|
-
.mockResolvedValueOnce("hotfix")
|
|
465
|
-
.mockResolvedValueOnce("Story ID")
|
|
466
|
-
.mockResolvedValueOnce("Issue ID")
|
|
467
|
-
.mockResolvedValueOnce("")
|
|
468
|
-
.mockResolvedValueOnce("sk-valid-key");
|
|
469
|
-
|
|
422
|
+
.mockResolvedValueOnce("")
|
|
423
|
+
.mockResolvedValueOnce("feature")
|
|
424
|
+
.mockResolvedValueOnce("hotfix")
|
|
425
|
+
.mockResolvedValueOnce("Story ID")
|
|
426
|
+
.mockResolvedValueOnce("Issue ID")
|
|
427
|
+
.mockResolvedValueOnce("")
|
|
428
|
+
.mockResolvedValueOnce("sk-valid-key");
|
|
470
429
|
const { init } = await import("../src/commands/init.js");
|
|
471
|
-
|
|
472
430
|
await init();
|
|
473
|
-
|
|
474
|
-
// 验证 input 被调用时包含验证函数
|
|
475
431
|
const inputCalls = vi.mocked(input).mock.calls;
|
|
476
|
-
const keyInputCall = inputCalls.find(call =>
|
|
432
|
+
const keyInputCall = inputCalls.find((call) =>
|
|
477
433
|
call[0].message?.includes("OpenAI API Key")
|
|
478
434
|
);
|
|
479
|
-
|
|
480
435
|
expect(keyInputCall).toBeDefined();
|
|
481
436
|
expect(keyInputCall![0].validate).toBeDefined();
|
|
482
|
-
|
|
483
|
-
// 测试验证函数
|
|
484
437
|
const validate = keyInputCall![0].validate!;
|
|
485
438
|
expect(validate("")).toBe("API Key 不能为空");
|
|
486
439
|
expect(validate("valid-key")).toBe(true);
|
|
@@ -490,31 +443,27 @@ describe("Init 模块测试", () => {
|
|
|
490
443
|
describe("配置输出", () => {
|
|
491
444
|
it("应该包含默认的 commit emojis", async () => {
|
|
492
445
|
mockExistsSync.mockReturnValue(false);
|
|
493
|
-
|
|
494
446
|
const { select, input } = await import("@inquirer/prompts");
|
|
495
447
|
vi.mocked(select)
|
|
496
|
-
.mockResolvedValueOnce("project")
|
|
497
|
-
.mockResolvedValueOnce(false)
|
|
498
|
-
.mockResolvedValueOnce(
|
|
499
|
-
.mockResolvedValueOnce(
|
|
500
|
-
.mockResolvedValueOnce(
|
|
501
|
-
.mockResolvedValueOnce(
|
|
502
|
-
|
|
448
|
+
.mockResolvedValueOnce("project")
|
|
449
|
+
.mockResolvedValueOnce(false)
|
|
450
|
+
.mockResolvedValueOnce(false)
|
|
451
|
+
.mockResolvedValueOnce(false)
|
|
452
|
+
.mockResolvedValueOnce("ask")
|
|
453
|
+
.mockResolvedValueOnce(true)
|
|
454
|
+
.mockResolvedValueOnce(true)
|
|
455
|
+
.mockResolvedValueOnce(false);
|
|
503
456
|
vi.mocked(input)
|
|
504
|
-
.mockResolvedValueOnce("")
|
|
505
|
-
.mockResolvedValueOnce("feature")
|
|
506
|
-
.mockResolvedValueOnce("hotfix")
|
|
507
|
-
.mockResolvedValueOnce("Story ID")
|
|
508
|
-
.mockResolvedValueOnce("Issue ID")
|
|
509
|
-
.mockResolvedValueOnce("");
|
|
510
|
-
|
|
457
|
+
.mockResolvedValueOnce("")
|
|
458
|
+
.mockResolvedValueOnce("feature")
|
|
459
|
+
.mockResolvedValueOnce("hotfix")
|
|
460
|
+
.mockResolvedValueOnce("Story ID")
|
|
461
|
+
.mockResolvedValueOnce("Issue ID")
|
|
462
|
+
.mockResolvedValueOnce("");
|
|
511
463
|
const { init } = await import("../src/commands/init.js");
|
|
512
|
-
|
|
513
464
|
await init();
|
|
514
|
-
|
|
515
465
|
const writtenConfig = mockWriteFileSync.mock.calls[0][1] as string;
|
|
516
466
|
const config = JSON.parse(writtenConfig);
|
|
517
|
-
|
|
518
467
|
expect(config.commitEmojis).toBeDefined();
|
|
519
468
|
expect(config.commitEmojis.feat).toBe("✨");
|
|
520
469
|
expect(config.commitEmojis.fix).toBe("🐛");
|
|
@@ -523,28 +472,25 @@ describe("Init 模块测试", () => {
|
|
|
523
472
|
|
|
524
473
|
it("应该显示成功消息", async () => {
|
|
525
474
|
mockExistsSync.mockReturnValue(false);
|
|
526
|
-
|
|
527
475
|
const { select, input } = await import("@inquirer/prompts");
|
|
528
476
|
vi.mocked(select)
|
|
529
|
-
.mockResolvedValueOnce("global")
|
|
530
|
-
.mockResolvedValueOnce(false)
|
|
531
|
-
.mockResolvedValueOnce(
|
|
532
|
-
.mockResolvedValueOnce(
|
|
533
|
-
.mockResolvedValueOnce(
|
|
534
|
-
.mockResolvedValueOnce(
|
|
535
|
-
|
|
477
|
+
.mockResolvedValueOnce("global")
|
|
478
|
+
.mockResolvedValueOnce(false)
|
|
479
|
+
.mockResolvedValueOnce(false)
|
|
480
|
+
.mockResolvedValueOnce(false)
|
|
481
|
+
.mockResolvedValueOnce("ask")
|
|
482
|
+
.mockResolvedValueOnce(true)
|
|
483
|
+
.mockResolvedValueOnce(true)
|
|
484
|
+
.mockResolvedValueOnce(false);
|
|
536
485
|
vi.mocked(input)
|
|
537
|
-
.mockResolvedValueOnce("")
|
|
538
|
-
.mockResolvedValueOnce("feature")
|
|
539
|
-
.mockResolvedValueOnce("hotfix")
|
|
540
|
-
.mockResolvedValueOnce("Story ID")
|
|
541
|
-
.mockResolvedValueOnce("Issue ID")
|
|
542
|
-
.mockResolvedValueOnce("");
|
|
543
|
-
|
|
486
|
+
.mockResolvedValueOnce("")
|
|
487
|
+
.mockResolvedValueOnce("feature")
|
|
488
|
+
.mockResolvedValueOnce("hotfix")
|
|
489
|
+
.mockResolvedValueOnce("Story ID")
|
|
490
|
+
.mockResolvedValueOnce("Issue ID")
|
|
491
|
+
.mockResolvedValueOnce("");
|
|
544
492
|
const { init } = await import("../src/commands/init.js");
|
|
545
|
-
|
|
546
493
|
await init();
|
|
547
|
-
|
|
548
494
|
expect(console.log).toHaveBeenCalledWith(
|
|
549
495
|
expect.stringContaining("✓ 配置已保存到 全局配置文件")
|
|
550
496
|
);
|
|
@@ -552,31 +498,28 @@ describe("Init 模块测试", () => {
|
|
|
552
498
|
|
|
553
499
|
it("应该显示全局配置的提示信息", async () => {
|
|
554
500
|
mockExistsSync.mockReturnValue(false);
|
|
555
|
-
|
|
556
501
|
const { select, input } = await import("@inquirer/prompts");
|
|
557
502
|
vi.mocked(select)
|
|
558
|
-
.mockResolvedValueOnce("global")
|
|
559
|
-
.mockResolvedValueOnce(false)
|
|
560
|
-
.mockResolvedValueOnce(
|
|
561
|
-
.mockResolvedValueOnce(
|
|
562
|
-
.mockResolvedValueOnce(
|
|
563
|
-
.mockResolvedValueOnce(
|
|
564
|
-
|
|
503
|
+
.mockResolvedValueOnce("global")
|
|
504
|
+
.mockResolvedValueOnce(false)
|
|
505
|
+
.mockResolvedValueOnce(false)
|
|
506
|
+
.mockResolvedValueOnce(false)
|
|
507
|
+
.mockResolvedValueOnce("ask")
|
|
508
|
+
.mockResolvedValueOnce(true)
|
|
509
|
+
.mockResolvedValueOnce(true)
|
|
510
|
+
.mockResolvedValueOnce(false);
|
|
565
511
|
vi.mocked(input)
|
|
566
|
-
.mockResolvedValueOnce("")
|
|
567
|
-
.mockResolvedValueOnce("feature")
|
|
568
|
-
.mockResolvedValueOnce("hotfix")
|
|
569
|
-
.mockResolvedValueOnce("Story ID")
|
|
570
|
-
.mockResolvedValueOnce("Issue ID")
|
|
571
|
-
.mockResolvedValueOnce("");
|
|
572
|
-
|
|
512
|
+
.mockResolvedValueOnce("")
|
|
513
|
+
.mockResolvedValueOnce("feature")
|
|
514
|
+
.mockResolvedValueOnce("hotfix")
|
|
515
|
+
.mockResolvedValueOnce("Story ID")
|
|
516
|
+
.mockResolvedValueOnce("Issue ID")
|
|
517
|
+
.mockResolvedValueOnce("");
|
|
573
518
|
const { init } = await import("../src/commands/init.js");
|
|
574
|
-
|
|
575
519
|
await init();
|
|
576
|
-
|
|
577
520
|
expect(console.log).toHaveBeenCalledWith(
|
|
578
521
|
expect.stringContaining("全局配置对所有项目生效")
|
|
579
522
|
);
|
|
580
523
|
});
|
|
581
524
|
});
|
|
582
|
-
});
|
|
525
|
+
});
|