@zjex/git-workflow 0.2.23 → 0.2.24
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/.github/workflows/test.yml +33 -0
- package/.husky/pre-commit +5 -0
- package/README.md +55 -0
- package/TESTING.md +436 -0
- package/dist/index.js +2 -2
- package/package.json +9 -2
- package/src/commands/tag.ts +1 -1
- package/tests/COVERAGE_REPORT.md +222 -0
- package/tests/QUICK_START.md +242 -0
- package/tests/README.md +119 -0
- package/tests/TEST_SUMMARY.md +330 -0
- package/tests/ai-service.test.ts +470 -0
- package/tests/branch.test.ts +255 -0
- package/tests/commit.test.ts +85 -0
- package/tests/config.test.ts +311 -0
- package/tests/tag.test.ts +396 -0
- package/tests/update-notifier.test.ts +384 -0
- package/tests/utils.test.ts +229 -0
- package/vitest.config.ts +19 -0
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
|
|
3
|
+
describe("Tag 功能测试", () => {
|
|
4
|
+
describe("前缀提取", () => {
|
|
5
|
+
it("应该正确提取 v 前缀", () => {
|
|
6
|
+
const tag = "v0.1.0";
|
|
7
|
+
const prefix = tag.replace(/[0-9].*/, "");
|
|
8
|
+
expect(prefix).toBe("v");
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("应该正确提取 release- 前缀", () => {
|
|
12
|
+
const tag = "release-1.0.0";
|
|
13
|
+
const prefix = tag.replace(/[0-9].*/, "");
|
|
14
|
+
expect(prefix).toBe("release-");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("应该正确提取 @ 开头的 scope 前缀", () => {
|
|
18
|
+
const tag = "@scope/package@1.0.0";
|
|
19
|
+
const prefix = tag.replace(/[0-9].*/, "");
|
|
20
|
+
expect(prefix).toBe("@scope/package@");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("应该正确处理无前缀 tag", () => {
|
|
24
|
+
const tag = "1.0.0";
|
|
25
|
+
const prefix = tag.replace(/[0-9].*/, "") || "(无前缀)";
|
|
26
|
+
expect(prefix).toBe("(无前缀)");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("应该正确提取 g 前缀", () => {
|
|
30
|
+
const tag = "g0.1.0";
|
|
31
|
+
const prefix = tag.replace(/[0-9].*/, "");
|
|
32
|
+
expect(prefix).toBe("g");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("应该正确提取带下划线的前缀", () => {
|
|
36
|
+
const tag = "version_1.0.0";
|
|
37
|
+
const prefix = tag.replace(/[0-9].*/, "");
|
|
38
|
+
expect(prefix).toBe("version_");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("应该正确提取带点的前缀", () => {
|
|
42
|
+
const tag = "v.1.0.0";
|
|
43
|
+
const prefix = tag.replace(/[0-9].*/, "");
|
|
44
|
+
expect(prefix).toBe("v.");
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("Tag 分组", () => {
|
|
49
|
+
it("应该正确按前缀分组", () => {
|
|
50
|
+
const tags = ["v0.1.0", "v0.2.0", "g0.1.0", "1.0.0"];
|
|
51
|
+
const grouped = new Map<string, string[]>();
|
|
52
|
+
|
|
53
|
+
tags.forEach((tag) => {
|
|
54
|
+
const prefix = tag.replace(/[0-9].*/, "") || "(无前缀)";
|
|
55
|
+
if (!grouped.has(prefix)) {
|
|
56
|
+
grouped.set(prefix, []);
|
|
57
|
+
}
|
|
58
|
+
grouped.get(prefix)!.push(tag);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
expect(grouped.size).toBe(3);
|
|
62
|
+
expect(grouped.get("v")).toEqual(["v0.1.0", "v0.2.0"]);
|
|
63
|
+
expect(grouped.get("g")).toEqual(["g0.1.0"]);
|
|
64
|
+
expect(grouped.get("(无前缀)")).toEqual(["1.0.0"]);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("应该处理多个不同前缀", () => {
|
|
68
|
+
const tags = [
|
|
69
|
+
"v1.0.0",
|
|
70
|
+
"release-1.0.0",
|
|
71
|
+
"hotfix-1.0.0",
|
|
72
|
+
"1.0.0",
|
|
73
|
+
"v2.0.0",
|
|
74
|
+
];
|
|
75
|
+
const grouped = new Map<string, string[]>();
|
|
76
|
+
|
|
77
|
+
tags.forEach((tag) => {
|
|
78
|
+
const prefix = tag.replace(/[0-9].*/, "") || "(无前缀)";
|
|
79
|
+
if (!grouped.has(prefix)) {
|
|
80
|
+
grouped.set(prefix, []);
|
|
81
|
+
}
|
|
82
|
+
grouped.get(prefix)!.push(tag);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
expect(grouped.size).toBe(4);
|
|
86
|
+
expect(grouped.get("v")?.length).toBe(2);
|
|
87
|
+
expect(grouped.get("release-")?.length).toBe(1);
|
|
88
|
+
expect(grouped.get("hotfix-")?.length).toBe(1);
|
|
89
|
+
expect(grouped.get("(无前缀)")?.length).toBe(1);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("应该保持 tag 的原始顺序", () => {
|
|
93
|
+
const tags = ["v0.1.0", "v0.3.0", "v0.2.0"];
|
|
94
|
+
const grouped = new Map<string, string[]>();
|
|
95
|
+
|
|
96
|
+
tags.forEach((tag) => {
|
|
97
|
+
const prefix = tag.replace(/[0-9].*/, "") || "(无前缀)";
|
|
98
|
+
if (!grouped.has(prefix)) {
|
|
99
|
+
grouped.set(prefix, []);
|
|
100
|
+
}
|
|
101
|
+
grouped.get(prefix)!.push(tag);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
expect(grouped.get("v")).toEqual(["v0.1.0", "v0.3.0", "v0.2.0"]);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe("Tag 显示逻辑", () => {
|
|
109
|
+
it("应该只显示最后 5 个 tag", () => {
|
|
110
|
+
const tags = [
|
|
111
|
+
"v0.1.0",
|
|
112
|
+
"v0.2.0",
|
|
113
|
+
"v0.3.0",
|
|
114
|
+
"v0.4.0",
|
|
115
|
+
"v0.5.0",
|
|
116
|
+
"v0.6.0",
|
|
117
|
+
"v0.7.0",
|
|
118
|
+
];
|
|
119
|
+
const displayTags = tags.slice(-5);
|
|
120
|
+
|
|
121
|
+
expect(displayTags).toEqual([
|
|
122
|
+
"v0.3.0",
|
|
123
|
+
"v0.4.0",
|
|
124
|
+
"v0.5.0",
|
|
125
|
+
"v0.6.0",
|
|
126
|
+
"v0.7.0",
|
|
127
|
+
]);
|
|
128
|
+
expect(displayTags.length).toBe(5);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("少于 5 个 tag 时应该显示全部", () => {
|
|
132
|
+
const tags = ["v0.1.0", "v0.2.0", "v0.3.0"];
|
|
133
|
+
const displayTags = tags.length > 5 ? tags.slice(-5) : tags;
|
|
134
|
+
|
|
135
|
+
expect(displayTags).toEqual(tags);
|
|
136
|
+
expect(displayTags.length).toBe(3);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("正好 5 个 tag 时应该显示全部", () => {
|
|
140
|
+
const tags = ["v0.1.0", "v0.2.0", "v0.3.0", "v0.4.0", "v0.5.0"];
|
|
141
|
+
const displayTags = tags.length > 5 ? tags.slice(-5) : tags;
|
|
142
|
+
|
|
143
|
+
expect(displayTags).toEqual(tags);
|
|
144
|
+
expect(displayTags.length).toBe(5);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("应该标记是否有更多 tag", () => {
|
|
148
|
+
const tags = ["v0.1.0", "v0.2.0", "v0.3.0", "v0.4.0", "v0.5.0", "v0.6.0"];
|
|
149
|
+
const hasMore = tags.length > 5;
|
|
150
|
+
|
|
151
|
+
expect(hasMore).toBe(true);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("少于 5 个 tag 时不应该有省略号", () => {
|
|
155
|
+
const tags = ["v0.1.0", "v0.2.0", "v0.3.0"];
|
|
156
|
+
const hasMore = tags.length > 5;
|
|
157
|
+
|
|
158
|
+
expect(hasMore).toBe(false);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("空数组应该返回空", () => {
|
|
162
|
+
const tags: string[] = [];
|
|
163
|
+
const displayTags = tags.length > 5 ? tags.slice(-5) : tags;
|
|
164
|
+
|
|
165
|
+
expect(displayTags).toEqual([]);
|
|
166
|
+
expect(displayTags.length).toBe(0);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe("列宽计算", () => {
|
|
171
|
+
it("应该计算正确的列宽", () => {
|
|
172
|
+
const tags = ["v0.1.0", "v0.2.0", "release-1.0.0"];
|
|
173
|
+
const maxLength = Math.max(...tags.map((t) => t.length));
|
|
174
|
+
const columnWidth = Math.max(maxLength + 4, 20);
|
|
175
|
+
|
|
176
|
+
expect(maxLength).toBe(13); // "release-1.0.0" 的长度
|
|
177
|
+
expect(columnWidth).toBe(20); // 至少 20
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("超长 tag 应该增加列宽", () => {
|
|
181
|
+
const tags = ["very-long-tag-name-1.0.0"];
|
|
182
|
+
const maxLength = Math.max(...tags.map((t) => t.length));
|
|
183
|
+
const columnWidth = Math.max(maxLength + 4, 20);
|
|
184
|
+
|
|
185
|
+
expect(columnWidth).toBeGreaterThan(20);
|
|
186
|
+
expect(columnWidth).toBe(maxLength + 4);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("短 tag 应该使用最小列宽", () => {
|
|
190
|
+
const tags = ["v1", "v2"];
|
|
191
|
+
const maxLength = Math.max(...tags.map((t) => t.length));
|
|
192
|
+
const columnWidth = Math.max(maxLength + 4, 20);
|
|
193
|
+
|
|
194
|
+
expect(columnWidth).toBe(20);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("应该处理多列的最大宽度", () => {
|
|
198
|
+
const columns = [
|
|
199
|
+
{ tags: ["v0.1.0", "v0.2.0"] },
|
|
200
|
+
{ tags: ["release-1.0.0", "release-2.0.0"] },
|
|
201
|
+
{ tags: ["1.0.0"] },
|
|
202
|
+
];
|
|
203
|
+
|
|
204
|
+
const maxLength = Math.max(
|
|
205
|
+
...columns.flatMap((col) => col.tags.map((t) => t.length))
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
expect(maxLength).toBe(13);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe("版本号解析", () => {
|
|
213
|
+
it("应该解析标准 semver 版本", () => {
|
|
214
|
+
const version = "1.2.3";
|
|
215
|
+
const match = version.match(/^(\d+)\.(\d+)\.(\d+)$/);
|
|
216
|
+
|
|
217
|
+
expect(match).toBeTruthy();
|
|
218
|
+
expect(match![1]).toBe("1");
|
|
219
|
+
expect(match![2]).toBe("2");
|
|
220
|
+
expect(match![3]).toBe("3");
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("应该解析预发布版本", () => {
|
|
224
|
+
const version = "1.2.3-beta.1";
|
|
225
|
+
const match = version.match(/^(\d+)\.(\d+)\.(\d+)-([a-zA-Z]+)\.(\d+)$/);
|
|
226
|
+
|
|
227
|
+
expect(match).toBeTruthy();
|
|
228
|
+
expect(match![1]).toBe("1");
|
|
229
|
+
expect(match![2]).toBe("2");
|
|
230
|
+
expect(match![3]).toBe("3");
|
|
231
|
+
expect(match![4]).toBe("beta");
|
|
232
|
+
expect(match![5]).toBe("1");
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it("应该解析两位版本号", () => {
|
|
236
|
+
const version = "1.2";
|
|
237
|
+
const match = version.match(/^(\d+)\.(\d+)$/);
|
|
238
|
+
|
|
239
|
+
expect(match).toBeTruthy();
|
|
240
|
+
expect(match![1]).toBe("1");
|
|
241
|
+
expect(match![2]).toBe("2");
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("应该解析单位版本号", () => {
|
|
245
|
+
const version = "1";
|
|
246
|
+
const match = version.match(/^(\d+)$/);
|
|
247
|
+
|
|
248
|
+
expect(match).toBeTruthy();
|
|
249
|
+
expect(match![1]).toBe("1");
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("不应该匹配无效版本号", () => {
|
|
253
|
+
const version = "abc";
|
|
254
|
+
const match = version.match(/^(\d+)\.(\d+)\.(\d+)$/);
|
|
255
|
+
|
|
256
|
+
expect(match).toBeNull();
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
describe("版本号递增", () => {
|
|
261
|
+
it("应该正确递增 patch 版本", () => {
|
|
262
|
+
const version = "1.2.3";
|
|
263
|
+
const match = version.match(/^(\d+)\.(\d+)\.(\d+)$/);
|
|
264
|
+
const [, major, minor, patch] = match!;
|
|
265
|
+
const newVersion = `${major}.${minor}.${Number(patch) + 1}`;
|
|
266
|
+
|
|
267
|
+
expect(newVersion).toBe("1.2.4");
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it("应该正确递增 minor 版本", () => {
|
|
271
|
+
const version = "1.2.3";
|
|
272
|
+
const match = version.match(/^(\d+)\.(\d+)\.(\d+)$/);
|
|
273
|
+
const [, major, minor] = match!;
|
|
274
|
+
const newVersion = `${major}.${Number(minor) + 1}.0`;
|
|
275
|
+
|
|
276
|
+
expect(newVersion).toBe("1.3.0");
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it("应该正确递增 major 版本", () => {
|
|
280
|
+
const version = "1.2.3";
|
|
281
|
+
const match = version.match(/^(\d+)\.(\d+)\.(\d+)$/);
|
|
282
|
+
const [, major] = match!;
|
|
283
|
+
const newVersion = `${Number(major) + 1}.0.0`;
|
|
284
|
+
|
|
285
|
+
expect(newVersion).toBe("2.0.0");
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it("应该正确递增预发布版本号", () => {
|
|
289
|
+
const version = "1.2.3-beta.1";
|
|
290
|
+
const match = version.match(/^(\d+)\.(\d+)\.(\d+)-([a-zA-Z]+)\.(\d+)$/);
|
|
291
|
+
const [, major, minor, patch, preTag, preNum] = match!;
|
|
292
|
+
const newVersion = `${major}.${minor}.${patch}-${preTag}.${
|
|
293
|
+
Number(preNum) + 1
|
|
294
|
+
}`;
|
|
295
|
+
|
|
296
|
+
expect(newVersion).toBe("1.2.3-beta.2");
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it("应该从预发布版本转为正式版本", () => {
|
|
300
|
+
const version = "1.2.3-beta.1";
|
|
301
|
+
const match = version.match(/^(\d+)\.(\d+)\.(\d+)-([a-zA-Z]+)\.(\d+)$/);
|
|
302
|
+
const [, major, minor, patch] = match!;
|
|
303
|
+
const newVersion = `${major}.${minor}.${patch}`;
|
|
304
|
+
|
|
305
|
+
expect(newVersion).toBe("1.2.3");
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
describe("Tag 排序", () => {
|
|
310
|
+
it("应该按版本号升序排序", () => {
|
|
311
|
+
const tags = ["v0.2.0", "v0.1.0", "v0.3.0"];
|
|
312
|
+
// 模拟 git tag -l --sort=v:refname 的行为
|
|
313
|
+
const sorted = [...tags].sort((a, b) => {
|
|
314
|
+
const aVer = a.replace("v", "");
|
|
315
|
+
const bVer = b.replace("v", "");
|
|
316
|
+
return aVer.localeCompare(bVer, undefined, { numeric: true });
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
expect(sorted).toEqual(["v0.1.0", "v0.2.0", "v0.3.0"]);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it("应该正确排序多位版本号", () => {
|
|
323
|
+
const tags = ["v0.10.0", "v0.2.0", "v0.9.0"];
|
|
324
|
+
const sorted = [...tags].sort((a, b) => {
|
|
325
|
+
const aVer = a.replace("v", "");
|
|
326
|
+
const bVer = b.replace("v", "");
|
|
327
|
+
return aVer.localeCompare(bVer, undefined, { numeric: true });
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
expect(sorted).toEqual(["v0.2.0", "v0.9.0", "v0.10.0"]);
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
describe("多列显示", () => {
|
|
335
|
+
it("应该计算正确的行数", () => {
|
|
336
|
+
const columns = [
|
|
337
|
+
{ tags: ["v0.1.0", "v0.2.0", "v0.3.0"] },
|
|
338
|
+
{ tags: ["g0.1.0", "g0.2.0"] },
|
|
339
|
+
{ tags: ["1.0.0"] },
|
|
340
|
+
];
|
|
341
|
+
|
|
342
|
+
const maxRows = Math.max(...columns.map((col) => col.tags.length));
|
|
343
|
+
|
|
344
|
+
expect(maxRows).toBe(3);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it("应该正确填充空单元格", () => {
|
|
348
|
+
const columns = [
|
|
349
|
+
{ tags: ["v0.1.0", "v0.2.0", "v0.3.0"] },
|
|
350
|
+
{ tags: ["g0.1.0"] },
|
|
351
|
+
];
|
|
352
|
+
|
|
353
|
+
const maxRows = Math.max(...columns.map((col) => col.tags.length));
|
|
354
|
+
const rows: string[][] = [];
|
|
355
|
+
|
|
356
|
+
for (let i = 0; i < maxRows; i++) {
|
|
357
|
+
const row = columns.map((col) => col.tags[i] || "");
|
|
358
|
+
rows.push(row);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
expect(rows).toEqual([
|
|
362
|
+
["v0.1.0", "g0.1.0"],
|
|
363
|
+
["v0.2.0", ""],
|
|
364
|
+
["v0.3.0", ""],
|
|
365
|
+
]);
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
describe("表头格式化", () => {
|
|
370
|
+
it("应该格式化表头显示总数", () => {
|
|
371
|
+
const prefix = "v";
|
|
372
|
+
const total = 10;
|
|
373
|
+
const header = `${prefix} (${total})`;
|
|
374
|
+
|
|
375
|
+
expect(header).toBe("v (10)");
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it("应该格式化无前缀的表头", () => {
|
|
379
|
+
const prefix = "(无前缀)";
|
|
380
|
+
const total = 3;
|
|
381
|
+
const header = `${prefix} (${total})`;
|
|
382
|
+
|
|
383
|
+
expect(header).toBe("(无前缀) (3)");
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it("应该对齐表头", () => {
|
|
387
|
+
const headers = ["v (10)", "release- (5)", "(无前缀) (3)"];
|
|
388
|
+
const columnWidth = 20;
|
|
389
|
+
const paddedHeaders = headers.map((h) => h.padEnd(columnWidth));
|
|
390
|
+
|
|
391
|
+
paddedHeaders.forEach((h) => {
|
|
392
|
+
expect(h.length).toBe(columnWidth);
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
});
|