@lobehub/chat 1.114.5 → 1.115.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.
- package/.cursor/rules/project-introduce.mdc +1 -15
- package/.cursor/rules/project-structure.mdc +227 -0
- package/.cursor/rules/testing-guide/db-model-test.mdc +5 -3
- package/.cursor/rules/testing-guide/testing-guide.mdc +153 -168
- package/.github/workflows/claude.yml +1 -1
- package/.github/workflows/test.yml +9 -0
- package/.prettierignore +0 -1
- package/.vscode/settings.json +86 -80
- package/CHANGELOG.md +50 -0
- package/CLAUDE.md +11 -27
- package/changelog/v1.json +10 -0
- package/docs/development/basic/feature-development.mdx +1 -1
- package/docs/development/basic/feature-development.zh-CN.mdx +1 -1
- package/package.json +5 -5
- package/packages/const/src/image.ts +28 -0
- package/packages/const/src/index.ts +1 -0
- package/packages/database/package.json +4 -2
- package/packages/database/src/repositories/aiInfra/index.ts +1 -1
- package/packages/database/tests/setup-db.ts +3 -0
- package/packages/database/vitest.config.mts +33 -0
- package/packages/model-runtime/src/utils/modelParse.ts +1 -1
- package/packages/utils/src/client/imageDimensions.test.ts +95 -0
- package/packages/utils/src/client/imageDimensions.ts +54 -0
- package/packages/utils/src/number.test.ts +3 -1
- package/packages/utils/src/number.ts +1 -2
- package/src/app/[variants]/(main)/files/[id]/page.tsx +0 -2
- package/src/app/[variants]/(main)/image/@menu/components/SeedNumberInput/index.tsx +1 -1
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/DimensionControlGroup.tsx +0 -1
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/ImageUpload.tsx +206 -185
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/ImageUrl.tsx +16 -4
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/ImageUrlsUpload.tsx +52 -3
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/MultiImagesUpload/ImageManageModal.tsx +33 -19
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/MultiImagesUpload/index.tsx +40 -12
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/hooks/useAutoDimensions.ts +56 -0
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/hooks/useUploadFilesValidation.ts +77 -0
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/index.tsx +82 -5
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/utils/__tests__/dimensionConstraints.test.ts +235 -0
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/utils/__tests__/imageValidation.test.ts +401 -0
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/utils/dimensionConstraints.ts +54 -0
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/utils/imageValidation.ts +117 -0
- package/src/app/[variants]/(main)/image/@topic/features/Topics/TopicItem.tsx +3 -1
- package/src/app/[variants]/(main)/image/@topic/features/Topics/TopicList.tsx +15 -2
- package/src/app/[variants]/(main)/image/features/GenerationFeed/GenerationItem/utils.ts +5 -4
- package/src/libs/standard-parameters/index.ts +4 -1
- package/src/locales/default/components.ts +8 -0
- package/src/server/services/generation/index.ts +1 -1
- package/src/store/aiInfra/slices/aiProvider/__tests__/action.test.ts +29 -29
- package/src/store/aiInfra/slices/aiProvider/action.ts +80 -36
- package/src/store/chat/slices/builtinTool/actions/dalle.test.ts +20 -13
- package/src/store/file/slices/upload/action.ts +18 -7
- package/src/store/image/slices/generationConfig/hooks.ts +11 -1
- package/tsconfig.json +1 -10
- package/packages/const/src/imageGeneration.ts +0 -16
- package/src/app/(backend)/trpc/desktop/[trpc]/route.ts +0 -26
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/AspectRatioSelect.tsx +0 -24
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/SizeSliderInput.tsx +0 -15
- package/src/app/[variants]/(main)/image/@topic/features/Topics/TopicItemContainer.tsx +0 -91
- package/src/app/desktop/devtools/page.tsx +0 -89
- package/src/app/desktop/layout.tsx +0 -31
- /package/apps/desktop/{vitest.config.ts → vitest.config.mts} +0 -0
- /package/packages/database/{vitest.config.ts → vitest.config.server.mts} +0 -0
- /package/packages/electron-server-ipc/{vitest.config.ts → vitest.config.mts} +0 -0
- /package/packages/file-loaders/{vitest.config.ts → vitest.config.mts} +0 -0
- /package/packages/model-runtime/{vitest.config.ts → vitest.config.mts} +0 -0
- /package/packages/prompts/{vitest.config.ts → vitest.config.mts} +0 -0
- /package/packages/utils/{vitest.config.ts → vitest.config.mts} +0 -0
- /package/packages/web-crawler/{vitest.config.ts → vitest.config.mts} +0 -0
- /package/{vitest.config.ts → vitest.config.mts} +0 -0
@@ -5,11 +5,11 @@ alwaysApply: false
|
|
5
5
|
|
6
6
|
# 测试指南 - LobeChat Testing Guide
|
7
7
|
|
8
|
-
##
|
8
|
+
## 测试环境概览
|
9
9
|
|
10
10
|
LobeChat 项目使用 Vitest 测试库,配置了两种不同的测试环境:
|
11
11
|
|
12
|
-
###
|
12
|
+
### 客户端数据库测试环境 (DOM Environment)
|
13
13
|
|
14
14
|
- **配置文件**: [vitest.config.ts](mdc:vitest.config.ts)
|
15
15
|
- **环境**: Happy DOM (浏览器环境模拟)
|
@@ -17,60 +17,62 @@ LobeChat 项目使用 Vitest 测试库,配置了两种不同的测试环境:
|
|
17
17
|
- **用途**: 测试前端组件、客户端逻辑、React 组件等
|
18
18
|
- **设置文件**: [tests/setup.ts](mdc:tests/setup.ts)
|
19
19
|
|
20
|
-
###
|
20
|
+
### 服务端数据库测试环境 (Node Environment)
|
21
21
|
|
22
|
-
|
22
|
+
目前只有 `packages/database` 下的测试可以通过配置 `TEST_SERVER_DB=1` 环境变量来使用服务端数据库测试
|
23
|
+
|
24
|
+
- **配置文件**: [packages/database/vitest.config.mts](mdc:packages/database/vitest.config.mts) 并且设置环境变量 `TEST_SERVER_DB=1`
|
23
25
|
- **环境**: Node.js
|
24
26
|
- **数据库**: 真实的 PostgreSQL 数据库
|
25
27
|
- **并发限制**: 单线程运行 (`singleFork: true`)
|
26
28
|
- **用途**: 测试数据库模型、服务端逻辑、API 端点等
|
27
|
-
- **设置文件**: [tests/setup-db.ts](mdc:tests/setup-db.ts)
|
29
|
+
- **设置文件**: [packages/database/tests/setup-db.ts](mdc:packages/database/tests/setup-db.ts)
|
28
30
|
|
29
|
-
##
|
31
|
+
## 测试运行命令
|
30
32
|
|
31
|
-
|
33
|
+
** 性能警告**: 项目包含 3000+ 测试用例,完整运行需要约 10 分钟。务必使用文件过滤或测试名称过滤。
|
32
34
|
|
33
|
-
###
|
35
|
+
### 正确的命令格式
|
34
36
|
|
35
37
|
```bash
|
36
38
|
# 运行所有客户端/服务端测试
|
37
|
-
|
38
|
-
|
39
|
+
bunx vitest run --silent='passed-only' # 客户端测试
|
40
|
+
cd packages/database && TEST_SERVER_DB=1 bunx vitest run --silent='passed-only' # 服务端测试
|
39
41
|
|
40
42
|
# 运行特定测试文件 (支持模糊匹配)
|
41
|
-
|
43
|
+
bunx vitest run --silent='passed-only' user.test.ts
|
42
44
|
|
43
45
|
# 运行特定测试用例名称 (使用 -t 参数)
|
44
|
-
|
46
|
+
bunx vitest run --silent='passed-only' -t "test case name"
|
45
47
|
|
46
48
|
# 组合使用文件和测试名称过滤
|
47
|
-
|
49
|
+
bunx vitest run --silent='passed-only' filename.test.ts -t "specific test"
|
48
50
|
|
49
51
|
# 生成覆盖率报告 (使用 --coverage 参数)
|
50
|
-
|
52
|
+
bunx vitest run --silent='passed-only' --coverage
|
51
53
|
```
|
52
54
|
|
53
|
-
###
|
55
|
+
### 避免的命令格式
|
54
56
|
|
55
57
|
```bash
|
56
|
-
#
|
58
|
+
# 这些命令会运行所有 3000+ 测试用例,耗时约 10 分钟!
|
57
59
|
npm test
|
58
60
|
npm test some-file.test.ts
|
59
61
|
|
60
|
-
#
|
62
|
+
# 不要使用裸 vitest (会进入 watch 模式)
|
61
63
|
vitest test-file.test.ts
|
62
64
|
```
|
63
65
|
|
64
|
-
##
|
66
|
+
## 测试修复原则
|
65
67
|
|
66
|
-
### 核心原则
|
68
|
+
### 核心原则
|
67
69
|
|
68
70
|
1. **充分阅读测试代码**: 在修复测试之前,必须完整理解测试的意图和实现
|
69
71
|
2. **测试优先修复**: 如果是测试本身写错了,修改测试而不是实现代码
|
70
72
|
3. **专注单一问题**: 只修复指定的测试,不要添加额外测试或功能
|
71
73
|
4. **不自作主张**: 不要因为发现其他问题就直接修改,先提出再讨论
|
72
74
|
|
73
|
-
### 测试协作最佳实践
|
75
|
+
### 测试协作最佳实践
|
74
76
|
|
75
77
|
基于实际开发经验总结的重要协作原则:
|
76
78
|
|
@@ -84,10 +86,10 @@ vitest test-file.test.ts
|
|
84
86
|
- **避免陷阱**: 不要陷入"不断尝试相同或类似方法"的循环
|
85
87
|
|
86
88
|
```typescript
|
87
|
-
//
|
89
|
+
// 错误做法:连续失败后继续盲目尝试
|
88
90
|
// 第3次、第4次仍在用相似的方法修复同一个问题
|
89
91
|
|
90
|
-
//
|
92
|
+
// 正确做法:失败1-2次后总结问题
|
91
93
|
/*
|
92
94
|
问题总结:
|
93
95
|
1. 尝试过的方法:修改 mock 数据结构
|
@@ -106,7 +108,7 @@ vitest test-file.test.ts
|
|
106
108
|
- **保持稳定性**: 测试名称应该在代码重构后仍然有意义
|
107
109
|
|
108
110
|
```typescript
|
109
|
-
//
|
111
|
+
// 错误的测试命名
|
110
112
|
describe('User component coverage', () => {
|
111
113
|
it('covers line 45-50 in getUserData', () => {
|
112
114
|
// 为了覆盖第45-50行而写的测试
|
@@ -117,7 +119,7 @@ describe('User component coverage', () => {
|
|
117
119
|
});
|
118
120
|
});
|
119
121
|
|
120
|
-
//
|
122
|
+
// 正确的测试命名
|
121
123
|
describe('<UserAvatar />', () => {
|
122
124
|
it('should render fallback icon when image url is not provided', () => {
|
123
125
|
// 测试具体的业务场景,自然会覆盖相关代码分支
|
@@ -131,8 +133,8 @@ describe('<UserAvatar />', () => {
|
|
131
133
|
|
132
134
|
**覆盖率提升的正确思路**:
|
133
135
|
|
134
|
-
-
|
135
|
-
-
|
136
|
+
- 通过设计各种业务场景(正常流程、边缘情况、错误处理)来自然提升覆盖率
|
137
|
+
- 不要为了达到覆盖率数字而写测试,更不要在测试中注释"为了覆盖 xxx 行"
|
136
138
|
|
137
139
|
#### 3. 测试组织结构
|
138
140
|
|
@@ -143,7 +145,7 @@ describe('<UserAvatar />', () => {
|
|
143
145
|
- **避免碎片化**: 不要为了单个测试用例就创建新的顶级 `describe` 块
|
144
146
|
|
145
147
|
```typescript
|
146
|
-
//
|
148
|
+
// 错误的组织方式:创建过多顶级块
|
147
149
|
describe('<UserProfile />', () => {
|
148
150
|
it('should render user name', () => {});
|
149
151
|
});
|
@@ -158,7 +160,7 @@ describe('UserProfile edge cases', () => {
|
|
158
160
|
it('should handle missing avatar', () => {});
|
159
161
|
});
|
160
162
|
|
161
|
-
//
|
163
|
+
// 正确的组织方式:合并相关测试
|
162
164
|
describe('<UserProfile />', () => {
|
163
165
|
it('should render user name', () => {});
|
164
166
|
|
@@ -214,9 +216,9 @@ describe('<UserProfile />', () => {
|
|
214
216
|
**修复方法**: 更新了测试文件中的 mock 数据结构,使其与最新的 API 响应格式保持一致。具体修改了 `user.test.ts` 中的 `mockUserData` 对象结构。
|
215
217
|
```
|
216
218
|
|
217
|
-
##
|
219
|
+
## 测试编写最佳实践
|
218
220
|
|
219
|
-
### Mock 数据策略:追求"低成本的真实性"
|
221
|
+
### Mock 数据策略:追求"低成本的真实性"
|
220
222
|
|
221
223
|
**核心原则**: 测试数据应默认追求真实性,只有在引入"高昂的测试成本"时才进行简化。
|
222
224
|
|
@@ -228,10 +230,10 @@ describe('<UserProfile />', () => {
|
|
228
230
|
- **网络请求**:HTTP 调用、数据库连接
|
229
231
|
- **系统调用**:获取系统时间、环境变量等
|
230
232
|
|
231
|
-
####
|
233
|
+
#### 推荐做法:Mock 依赖,保留真实数据
|
232
234
|
|
233
235
|
```typescript
|
234
|
-
//
|
236
|
+
// 好的做法:Mock I/O 操作,但使用真实的文件内容格式
|
235
237
|
describe('parseContentType', () => {
|
236
238
|
beforeEach(() => {
|
237
239
|
// Mock 文件读取操作(避免真实 I/O)
|
@@ -249,7 +251,7 @@ describe('parseContentType', () => {
|
|
249
251
|
});
|
250
252
|
});
|
251
253
|
|
252
|
-
//
|
254
|
+
// 过度简化:使用不真实的数据
|
253
255
|
describe('parseContentType', () => {
|
254
256
|
it('should detect PDF content type correctly', () => {
|
255
257
|
// 这种简化数据没有测试价值
|
@@ -259,78 +261,113 @@ describe('parseContentType', () => {
|
|
259
261
|
});
|
260
262
|
```
|
261
263
|
|
262
|
-
####
|
264
|
+
#### 真实标识符的价值
|
263
265
|
|
264
266
|
```typescript
|
265
|
-
// ✅
|
266
|
-
|
267
|
-
const result = parseModelString('openai', '+gpt-4,+gpt-3.5-turbo');
|
268
|
-
expect(result.add).toHaveLength(2);
|
269
|
-
expect(result.add[0].id).toBe('gpt-4');
|
270
|
-
});
|
267
|
+
// ✅ 使用真实标识符
|
268
|
+
const result = parseModelString('openai', '+gpt-4,+gpt-3.5-turbo');
|
271
269
|
|
272
|
-
// ❌
|
273
|
-
|
274
|
-
const result = parseModelString('test-provider', '+model1,+model2');
|
275
|
-
expect(result.add).toHaveLength(2);
|
276
|
-
// 这种测试对理解真实场景帮助不大
|
277
|
-
});
|
270
|
+
// ❌ 使用占位符(价值较低)
|
271
|
+
const result = parseModelString('test-provider', '+model1,+model2');
|
278
272
|
```
|
279
273
|
|
280
|
-
###
|
274
|
+
### 现代化Mock技巧:环境设置与Mock方法
|
281
275
|
|
282
|
-
|
276
|
+
**环境设置 + Mock方法结合使用**
|
283
277
|
|
284
|
-
|
278
|
+
客户端代码测试时,推荐使用环境注释配合现代化Mock方法:
|
285
279
|
|
286
280
|
```typescript
|
287
|
-
|
288
|
-
|
289
|
-
|
281
|
+
/**
|
282
|
+
* @vitest-environment happy-dom // 提供浏览器API
|
283
|
+
*/
|
284
|
+
import { beforeEach, vi } from 'vitest';
|
285
|
+
|
286
|
+
beforeEach(() => {
|
287
|
+
// 现代方法1:使用vi.stubGlobal替代global.xxx = ...
|
288
|
+
const mockImage = vi.fn().mockImplementation(() => ({
|
289
|
+
addEventListener: vi.fn(),
|
290
|
+
naturalHeight: 600,
|
291
|
+
naturalWidth: 800,
|
292
|
+
}));
|
293
|
+
vi.stubGlobal('Image', mockImage);
|
294
|
+
|
295
|
+
// 现代方法2:使用vi.spyOn保留原功能,只mock特定方法
|
296
|
+
vi.spyOn(URL, 'createObjectURL').mockReturnValue('blob:mock-url');
|
297
|
+
vi.spyOn(URL, 'revokeObjectURL').mockImplementation(() => {});
|
290
298
|
});
|
299
|
+
```
|
291
300
|
|
292
|
-
|
293
|
-
it('should throw ValidationError for invalid data', () => {
|
294
|
-
expect(() => validateUser({})).toThrow(ValidationError);
|
295
|
-
});
|
301
|
+
**环境选择优先级**
|
296
302
|
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
expect.objectContaining({
|
301
|
-
code: 'INVALID_PAYMENT_DATA',
|
302
|
-
statusCode: 400,
|
303
|
-
}),
|
304
|
-
);
|
305
|
-
});
|
306
|
-
```
|
303
|
+
1. **@vitest-environment happy-dom** (推荐) - 轻量、快速,项目已安装
|
304
|
+
2. **@vitest-environment jsdom** - 功能完整,但需要额外安装jsdom包
|
305
|
+
3. **不设置环境** - Node.js环境,需要手动mock所有浏览器API
|
307
306
|
|
308
|
-
|
307
|
+
**Mock方法对比**
|
309
308
|
|
310
309
|
```typescript
|
311
|
-
// ❌
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
310
|
+
// ❌ 旧方法:直接操作global对象(类型问题)
|
311
|
+
global.Image = mockImage;
|
312
|
+
global.URL = { ...global.URL, createObjectURL: mockFn };
|
313
|
+
|
314
|
+
// ✅ 现代方法:类型安全的vi API
|
315
|
+
vi.stubGlobal('Image', mockImage); // 完全替换全局对象
|
316
|
+
vi.spyOn(URL, 'createObjectURL'); // 部分mock,保留其他功能
|
316
317
|
```
|
317
318
|
|
318
|
-
|
319
|
+
### 测试覆盖率原则:代码分支优于用例数量
|
320
|
+
|
321
|
+
**核心原则**: 优先覆盖所有代码分支,而非编写大量重复用例
|
319
322
|
|
320
323
|
```typescript
|
321
|
-
//
|
322
|
-
|
323
|
-
|
324
|
-
|
324
|
+
// ❌ 过度测试:29个测试用例都验证相同分支
|
325
|
+
describe('getImageDimensions', () => {
|
326
|
+
it('should reject .txt files');
|
327
|
+
it('should reject .pdf files');
|
328
|
+
// ... 25个类似测试,都走相同的验证分支
|
325
329
|
});
|
326
330
|
|
327
|
-
// ✅
|
328
|
-
|
329
|
-
|
331
|
+
// ✅ 精简测试:4个核心用例覆盖所有分支
|
332
|
+
describe('getImageDimensions', () => {
|
333
|
+
it('should return dimensions for valid File object'); // 成功路径 - File
|
334
|
+
it('should return dimensions for valid data URI'); // 成功路径 - String
|
335
|
+
it('should return undefined for invalid inputs'); // 输入验证分支
|
336
|
+
it('should return undefined when image fails to load'); // 错误处理分支
|
330
337
|
});
|
331
338
|
```
|
332
339
|
|
333
|
-
|
340
|
+
**分支覆盖策略**
|
341
|
+
|
342
|
+
1. **成功路径** - 每种输入类型1个测试即可
|
343
|
+
2. **边界条件** - 合并类似场景到单个测试
|
344
|
+
3. **错误处理** - 测试代表性错误即可
|
345
|
+
4. **业务逻辑** - 覆盖所有if/else分支
|
346
|
+
|
347
|
+
**合理测试数量**
|
348
|
+
- 简单工具函数:2-5个测试
|
349
|
+
- 复杂业务逻辑:5-10个测试
|
350
|
+
- 核心安全功能:适当增加,但避免重复路径
|
351
|
+
|
352
|
+
### 错误处理测试:测试"行为"而非"文本"
|
353
|
+
|
354
|
+
**核心原则**: 测试应该验证程序在错误发生时的行为是可预测的,而不是验证易变的错误信息文本。
|
355
|
+
|
356
|
+
#### 推荐的错误测试方式
|
357
|
+
|
358
|
+
```typescript
|
359
|
+
// ✅ 测试错误类型和属性
|
360
|
+
expect(() => validateUser({})).toThrow(ValidationError);
|
361
|
+
expect(() => processPayment({})).toThrow(expect.objectContaining({
|
362
|
+
code: 'INVALID_PAYMENT_DATA',
|
363
|
+
statusCode: 400,
|
364
|
+
}));
|
365
|
+
|
366
|
+
// ❌ 避免测试具体错误文本
|
367
|
+
expect(() => processUser({})).toThrow('用户数据不能为空,请检查输入参数');
|
368
|
+
```
|
369
|
+
|
370
|
+
### 疑难解答:警惕模块污染
|
334
371
|
|
335
372
|
**识别信号**: 当你的测试出现以下"灵异"现象时,优先怀疑模块污染:
|
336
373
|
|
@@ -341,55 +378,25 @@ it('should include field name in validation error', () => {
|
|
341
378
|
#### 典型场景:动态 Mock 同一模块
|
342
379
|
|
343
380
|
```typescript
|
344
|
-
// ❌
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
const { getSettings } = await import('./configService'); // 第一次加载
|
349
|
-
expect(getSettings().debugMode).toBe(true);
|
350
|
-
});
|
351
|
-
|
352
|
-
it('should work in production mode', async () => {
|
353
|
-
vi.doMock('./config', () => ({ isDev: false }));
|
354
|
-
const { getSettings } = await import('./configService'); // 可能使用缓存的旧版本!
|
355
|
-
expect(getSettings().debugMode).toBe(false); // ❌ 可能失败
|
356
|
-
});
|
381
|
+
// ❌ 问题:动态Mock同一模块
|
382
|
+
it('dev mode', async () => {
|
383
|
+
vi.doMock('./config', () => ({ isDev: true }));
|
384
|
+
const { getSettings } = await import('./service'); // 可能使用缓存
|
357
385
|
});
|
358
386
|
|
359
|
-
// ✅
|
360
|
-
|
361
|
-
|
362
|
-
vi.resetModules(); // 清除模块缓存,确保每个测试都是干净的环境
|
363
|
-
});
|
364
|
-
|
365
|
-
it('should work in development mode', async () => {
|
366
|
-
vi.doMock('./config', () => ({ isDev: true }));
|
367
|
-
const { getSettings } = await import('./configService');
|
368
|
-
expect(getSettings().debugMode).toBe(true);
|
369
|
-
});
|
370
|
-
|
371
|
-
it('should work in production mode', async () => {
|
372
|
-
vi.doMock('./config', () => ({ isDev: false }));
|
373
|
-
const { getSettings } = await import('./configService');
|
374
|
-
expect(getSettings().debugMode).toBe(false); // ✅ 测试通过
|
375
|
-
});
|
387
|
+
// ✅ 解决:清除模块缓存
|
388
|
+
beforeEach(() => {
|
389
|
+
vi.resetModules(); // 确保每个测试都是干净环境
|
376
390
|
});
|
377
391
|
```
|
378
392
|
|
379
|
-
|
380
|
-
|
381
|
-
1. **识别问题**: 测试失败时,首先问自己:"是否有多个测试在 Mock 同一个模块?"
|
382
|
-
2. **添加隔离**: 在 `beforeEach` 中添加 `vi.resetModules()`
|
383
|
-
3. **验证修复**: 重新运行测试,确认问题解决
|
393
|
+
**记住**: `vi.resetModules()` 是解决测试"灵异"失败的终极武器。
|
384
394
|
|
385
|
-
|
386
|
-
|
387
|
-
## 📂 测试文件组织
|
395
|
+
## 测试文件组织
|
388
396
|
|
389
397
|
### 文件命名约定
|
390
398
|
|
391
|
-
|
392
|
-
- **服务端测试**: `src/database/models/**/*.test.ts`, `src/database/server/**/*.test.ts` (限定路径)
|
399
|
+
`*.test.ts`, `*.test.tsx` (任意位置)
|
393
400
|
|
394
401
|
### 测试文件组织风格
|
395
402
|
|
@@ -406,7 +413,10 @@ src/components/Button/
|
|
406
413
|
└── index.test.tsx # 测试文件
|
407
414
|
```
|
408
415
|
|
409
|
-
|
416
|
+
- 也有少数情况会统一放到 `__tests__` 文件夹, 例如 `packages/database/src/models/__tests__`
|
417
|
+
- 测试使用的辅助文件放到 fixtures 文件夹
|
418
|
+
|
419
|
+
## 测试调试技巧
|
410
420
|
|
411
421
|
### 测试调试步骤
|
412
422
|
|
@@ -415,40 +425,28 @@ src/components/Button/
|
|
415
425
|
3. **分析错误**: 仔细阅读错误信息、堆栈跟踪和最近的文件修改记录
|
416
426
|
4. **添加调试**: 在测试中添加 `console.log` 了解执行流程
|
417
427
|
|
418
|
-
### TypeScript 类型处理
|
428
|
+
### TypeScript 类型处理
|
419
429
|
|
420
430
|
在测试中,为了提高编写效率和可读性,可以适当放宽 TypeScript 类型检测:
|
421
431
|
|
422
|
-
####
|
432
|
+
#### 推荐的类型放宽策略
|
423
433
|
|
424
434
|
```typescript
|
425
|
-
//
|
435
|
+
// 使用非空断言访问测试中确定存在的属性
|
426
436
|
const result = await someFunction();
|
427
437
|
expect(result!.data).toBeDefined();
|
428
438
|
expect(result!.status).toBe('success');
|
429
439
|
|
430
|
-
//
|
440
|
+
// 使用 any 类型简化复杂的 Mock 设置
|
431
441
|
const mockStream = new ReadableStream() as any;
|
432
442
|
mockStream.toReadableStream = () => mockStream;
|
433
443
|
|
434
|
-
//
|
435
|
-
|
436
|
-
|
437
|
-
private getFromCache(key: string) { /* ... */ }
|
438
|
-
}
|
439
|
-
|
440
|
-
const instance = new MyClass();
|
441
|
-
|
442
|
-
// 推荐:使用中括号访问私有成员
|
443
|
-
await instance['getFromCache']('test-key');
|
444
|
-
expect(instance['_cache'].size).toBe(1);
|
445
|
-
|
446
|
-
// 避免:使用 as any 访问私有成员
|
447
|
-
await (instance as any).getFromCache('test-key'); // ❌ 不推荐
|
448
|
-
expect((instance as any)._cache.size).toBe(1); // ❌ 不推荐
|
444
|
+
// 访问私有成员
|
445
|
+
await instance['getFromCache']('key'); // 推荐中括号
|
446
|
+
await (instance as any).getFromCache('key'); // 避免as any
|
449
447
|
```
|
450
448
|
|
451
|
-
####
|
449
|
+
#### 适用场景
|
452
450
|
|
453
451
|
- **Mock 对象**: 对于测试用的 Mock 数据,使用 `as any` 避免复杂的类型定义
|
454
452
|
- **第三方库**: 处理复杂的第三方库类型时,适当使用 `any` 提高效率
|
@@ -456,44 +454,31 @@ expect((instance as any)._cache.size).toBe(1); // ❌ 不推荐
|
|
456
454
|
- **私有成员访问**: 优先使用中括号 `instance['privateMethod']()` 而不是 `(instance as any).privateMethod()`
|
457
455
|
- **临时调试**: 快速编写测试时,先用 `any` 保证功能,后续可选择性地优化类型
|
458
456
|
|
459
|
-
####
|
457
|
+
#### 注意事项
|
460
458
|
|
461
459
|
- **适度使用**: 不要过度依赖 `any`,核心业务逻辑的类型仍应保持严格
|
462
460
|
- **私有成员访问优先级**: 中括号访问 > `as any` 转换,保持更好的类型安全性
|
463
461
|
- **文档说明**: 对于使用 `any` 的复杂场景,添加注释说明原因
|
464
462
|
- **测试覆盖**: 确保即使使用了 `any`,测试仍能有效验证功能正确性
|
465
463
|
|
466
|
-
### 检查最近修改记录 🔍
|
467
464
|
|
468
|
-
|
465
|
+
### 检查最近修改记录
|
469
466
|
|
470
|
-
|
471
|
-
|
472
|
-
**Step 1: 查看当前状态**
|
473
|
-
|
474
|
-
```bash
|
475
|
-
git status # 查看未提交的修改
|
476
|
-
git diff path/to/component.test.ts | cat # 查看测试文件修改
|
477
|
-
git diff path/to/component.ts | cat # 查看实现文件修改
|
478
|
-
```
|
479
|
-
|
480
|
-
**Step 2: 查看提交历史**
|
481
|
-
|
482
|
-
```bash
|
483
|
-
git log --pretty=format:"%h %ad %s" --date=relative -3 path/to/component.ts | cat
|
484
|
-
```
|
467
|
+
**核心原则**:测试突然失败时,优先检查最近的代码修改。
|
485
468
|
|
486
|
-
|
469
|
+
#### 快速检查方法
|
487
470
|
|
488
471
|
```bash
|
489
|
-
git
|
472
|
+
git status # 查看当前修改状态
|
473
|
+
git diff HEAD -- '*.test.*' # 检查测试文件改动
|
474
|
+
git diff main...HEAD # 对比主分支差异
|
475
|
+
gh pr diff # 查看PR中的所有改动
|
490
476
|
```
|
491
477
|
|
492
|
-
####
|
478
|
+
#### 常见原因与解决
|
493
479
|
|
494
|
-
- **
|
495
|
-
-
|
496
|
-
- **超过1周的提交**: ⚪ **低相关性** - 除非重大重构
|
480
|
+
- **最新提交引入bug** → 检查并修复实现代码
|
481
|
+
- **分支代码滞后** → `git rebase main` 同步主分支
|
497
482
|
|
498
483
|
## 特殊场景的测试
|
499
484
|
|
@@ -502,9 +487,9 @@ git show HEAD -- path/to/component.ts | cat # 查看最新提交的修改
|
|
502
487
|
- [Electron IPC 接口测试策略](mdc:./electron-ipc-test.mdc)
|
503
488
|
- [数据库 Model 测试指南](mdc:./db-model-test.mdc)
|
504
489
|
|
505
|
-
##
|
490
|
+
## 核心要点
|
506
491
|
|
507
|
-
- **命令格式**: 使用 `
|
492
|
+
- **命令格式**: 使用 `bunx vitest run --silent='passed-only'` 并指定文件过滤
|
508
493
|
- **修复原则**: 失败1-2次后寻求帮助,测试命名关注行为而非实现细节
|
509
494
|
- **调试流程**: 复现 → 分析 → 假设 → 修复 → 验证 → 总结
|
510
495
|
- **文件组织**: 优先在现有 `describe` 块中添加测试,避免创建冗余顶级块
|
@@ -51,7 +51,7 @@ jobs:
|
|
51
51
|
# assignee_trigger: "claude-bot"
|
52
52
|
|
53
53
|
# Optional: Allow Claude to run specific commands
|
54
|
-
allowed_tools: 'Bash(bun run:*),Bash(pnpm run:*),Bash(npm run:*),Bash(npx
|
54
|
+
allowed_tools: 'Bash(bun run:*),Bash(pnpm run:*),Bash(npm run:*),Bash(npx:*),Bash(bunx:*),Bash(vitest:*),Bash(rg:*),Bash(find:*),Bash(sed:*),Bash(grep:*),Bash(awk:*),Bash(wc:*),Bash(xargs:*)'
|
55
55
|
|
56
56
|
# Optional: Add custom instructions for Claude to customize its behavior for your project
|
57
57
|
# custom_instructions: |
|
@@ -87,6 +87,8 @@ jobs:
|
|
87
87
|
POSTGRES_PASSWORD: postgres
|
88
88
|
options: >-
|
89
89
|
--health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
|
90
|
+
|
91
|
+
|
90
92
|
ports:
|
91
93
|
- 5432:5432
|
92
94
|
|
@@ -111,6 +113,13 @@ jobs:
|
|
111
113
|
|
112
114
|
- uses: pnpm/action-setup@v4
|
113
115
|
|
116
|
+
- name: Test Client DB
|
117
|
+
run: pnpm --filter @lobechat/database test:client-db
|
118
|
+
env:
|
119
|
+
KEY_VAULTS_SECRET: LA7n9k3JdEcbSgml2sxfw+4TV1AzaaFU5+R176aQz4s=
|
120
|
+
S3_PUBLIC_DOMAIN: https://example.com
|
121
|
+
APP_URL: https://home.com
|
122
|
+
|
114
123
|
- name: Test Coverage
|
115
124
|
run: pnpm --filter @lobechat/database test:coverage
|
116
125
|
env:
|