@lobehub/chat 1.114.6 → 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.
Files changed (59) hide show
  1. package/.cursor/rules/project-introduce.mdc +1 -15
  2. package/.cursor/rules/project-structure.mdc +227 -0
  3. package/.cursor/rules/testing-guide/db-model-test.mdc +5 -3
  4. package/.cursor/rules/testing-guide/testing-guide.mdc +153 -168
  5. package/.github/workflows/claude.yml +1 -1
  6. package/.github/workflows/test.yml +9 -0
  7. package/CHANGELOG.md +25 -0
  8. package/CLAUDE.md +11 -27
  9. package/changelog/v1.json +5 -0
  10. package/docs/development/basic/feature-development.mdx +1 -1
  11. package/docs/development/basic/feature-development.zh-CN.mdx +1 -1
  12. package/package.json +4 -4
  13. package/packages/const/src/image.ts +28 -0
  14. package/packages/const/src/index.ts +1 -0
  15. package/packages/database/package.json +4 -2
  16. package/packages/database/src/repositories/aiInfra/index.ts +1 -1
  17. package/packages/database/tests/setup-db.ts +3 -0
  18. package/packages/database/vitest.config.mts +33 -0
  19. package/packages/model-runtime/src/utils/modelParse.ts +1 -1
  20. package/packages/utils/src/client/imageDimensions.test.ts +95 -0
  21. package/packages/utils/src/client/imageDimensions.ts +54 -0
  22. package/packages/utils/src/number.test.ts +3 -1
  23. package/packages/utils/src/number.ts +1 -2
  24. package/src/app/[variants]/(main)/image/@menu/components/SeedNumberInput/index.tsx +1 -1
  25. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/DimensionControlGroup.tsx +0 -1
  26. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/ImageUpload.tsx +16 -6
  27. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/ImageUrl.tsx +14 -2
  28. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/ImageUrlsUpload.tsx +27 -2
  29. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/MultiImagesUpload/index.tsx +23 -5
  30. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/hooks/useAutoDimensions.ts +56 -0
  31. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/index.tsx +82 -5
  32. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/utils/__tests__/dimensionConstraints.test.ts +235 -0
  33. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/utils/__tests__/imageValidation.test.ts +401 -0
  34. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/utils/dimensionConstraints.ts +54 -0
  35. package/src/app/[variants]/(main)/image/@topic/features/Topics/TopicItem.tsx +3 -1
  36. package/src/app/[variants]/(main)/image/@topic/features/Topics/TopicList.tsx +15 -2
  37. package/src/app/[variants]/(main)/image/features/GenerationFeed/GenerationItem/utils.ts +5 -4
  38. package/src/libs/standard-parameters/index.ts +1 -1
  39. package/src/server/services/generation/index.ts +1 -1
  40. package/src/store/chat/slices/builtinTool/actions/dalle.test.ts +20 -13
  41. package/src/store/file/slices/upload/action.ts +18 -7
  42. package/src/store/image/slices/generationConfig/hooks.ts +1 -1
  43. package/tsconfig.json +1 -10
  44. package/packages/const/src/imageGeneration.ts +0 -16
  45. package/src/app/(backend)/trpc/desktop/[trpc]/route.ts +0 -26
  46. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/AspectRatioSelect.tsx +0 -24
  47. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/SizeSliderInput.tsx +0 -15
  48. package/src/app/[variants]/(main)/image/@topic/features/Topics/TopicItemContainer.tsx +0 -91
  49. package/src/app/desktop/devtools/page.tsx +0 -89
  50. package/src/app/desktop/layout.tsx +0 -31
  51. /package/apps/desktop/{vitest.config.ts → vitest.config.mts} +0 -0
  52. /package/packages/database/{vitest.config.ts → vitest.config.server.mts} +0 -0
  53. /package/packages/electron-server-ipc/{vitest.config.ts → vitest.config.mts} +0 -0
  54. /package/packages/file-loaders/{vitest.config.ts → vitest.config.mts} +0 -0
  55. /package/packages/model-runtime/{vitest.config.ts → vitest.config.mts} +0 -0
  56. /package/packages/prompts/{vitest.config.ts → vitest.config.mts} +0 -0
  57. /package/packages/utils/{vitest.config.ts → vitest.config.mts} +0 -0
  58. /package/packages/web-crawler/{vitest.config.ts → vitest.config.mts} +0 -0
  59. /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
- ### 客户端测试环境 (DOM Environment)
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
- ### 服务端测试环境 (Node Environment)
20
+ ### 服务端数据库测试环境 (Node Environment)
21
21
 
22
- - **配置文件**: [vitest.config.server.ts](mdc:vitest.config.server.ts)
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
- **🚨 性能警告**: 项目包含 3000+ 测试用例,完整运行需要约 10 分钟。务必使用文件过滤或测试名称过滤。
33
+ ** 性能警告**: 项目包含 3000+ 测试用例,完整运行需要约 10 分钟。务必使用文件过滤或测试名称过滤。
32
34
 
33
- ### 正确的命令格式
35
+ ### 正确的命令格式
34
36
 
35
37
  ```bash
36
38
  # 运行所有客户端/服务端测试
37
- npx vitest run --config vitest.config.ts # 客户端测试
38
- npx vitest run --config vitest.config.server.ts # 服务端测试
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
- npx vitest run --config vitest.config.ts user.test.ts
43
+ bunx vitest run --silent='passed-only' user.test.ts
42
44
 
43
45
  # 运行特定测试用例名称 (使用 -t 参数)
44
- npx vitest run --config vitest.config.ts -t "test case name"
46
+ bunx vitest run --silent='passed-only' -t "test case name"
45
47
 
46
48
  # 组合使用文件和测试名称过滤
47
- npx vitest run --config vitest.config.ts filename.test.ts -t "specific test"
49
+ bunx vitest run --silent='passed-only' filename.test.ts -t "specific test"
48
50
 
49
51
  # 生成覆盖率报告 (使用 --coverage 参数)
50
- npx vitest run --config vitest.config.ts --coverage
52
+ bunx vitest run --silent='passed-only' --coverage
51
53
  ```
52
54
 
53
- ### 避免的命令格式
55
+ ### 避免的命令格式
54
56
 
55
57
  ```bash
56
- #这些命令会运行所有 3000+ 测试用例,耗时约 10 分钟!
58
+ # 这些命令会运行所有 3000+ 测试用例,耗时约 10 分钟!
57
59
  npm test
58
60
  npm test some-file.test.ts
59
61
 
60
- #不要使用裸 vitest (会进入 watch 模式)
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
- //正确做法:失败1-2次后总结问题
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
- - 不要为了达到覆盖率数字而写测试,更不要在测试中注释"为了覆盖 xxx 行"
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
- #### 推荐做法:Mock 依赖,保留真实数据
233
+ #### 推荐做法:Mock 依赖,保留真实数据
232
234
 
233
235
  ```typescript
234
- //好的做法:Mock I/O 操作,但使用真实的文件内容格式
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
- it('should parse OpenAI model list correctly', () => {
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
- it('should parse model list correctly', () => {
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
- it('should throw error when invalid input provided', () => {
289
- expect(() => processInput(null)).toThrow();
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
- it('should throw error with correct error code', () => {
299
- expect(() => processPayment({})).toThrow(
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
- it('should throw specific error message', () => {
313
- expect(() => processUser({})).toThrow('用户数据不能为空,请检查输入参数');
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
- // 测试标准 API 错误(这是契约的一部分)
322
- it('should return proper HTTP error for API', () => {
323
- expect(response.statusCode).toBe(400);
324
- expect(response.error).toBe('Bad Request');
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
- it('should include field name in validation error', () => {
329
- expect(() => validateField('email', '')).toThrow(/email/i);
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
- describe('ConfigService', () => {
346
- it('should work in development mode', async () => {
347
- vi.doMock('./config', () => ({ isDev: true }));
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
- // ✅ 使用 resetModules 解决模块污染
360
- describe('ConfigService', () => {
361
- beforeEach(() => {
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
- **记住**: `vi.resetModules()` 是解决测试"灵异"失败的终极武器,当常规调试方法都无效时,它往往能一针见血地解决问题。
386
-
387
- ## 📂 测试文件组织
395
+ ## 测试文件组织
388
396
 
389
397
  ### 文件命名约定
390
398
 
391
- - **客户端测试**: `*.test.ts`, `*.test.tsx` (任意位置)
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
- //使用 any 类型简化复杂的 Mock 设置
440
+ // 使用 any 类型简化复杂的 Mock 设置
431
441
  const mockStream = new ReadableStream() as any;
432
442
  mockStream.toReadableStream = () => mockStream;
433
443
 
434
- // ✅ 使用中括号访问私有属性和方法(推荐)
435
- class MyClass {
436
- private _cache = new Map();
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
- **Step 3: 查看具体修改内容**
469
+ #### 快速检查方法
487
470
 
488
471
  ```bash
489
- git show HEAD -- path/to/component.ts | cat # 查看最新提交的修改
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
- - **24小时内的提交**: 🔴 **高度相关** - 很可能是直接原因
495
- - **1-7天内的提交**: 🟡 **中等相关** - 需要仔细分析
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
- - **命令格式**: 使用 `npx vitest run --config [config-file]` 并指定文件过滤
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 vitest:*),Bash(rg:*),Bash(find:*),Bash(sed:*),Bash(grep:*),Bash(awk:*),Bash(wc:*),Bash(xargs:*)'
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:
package/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 1.115.0](https://github.com/lobehub/lobe-chat/compare/v1.114.6...v1.115.0)
6
+
7
+ <sup>Released on **2025-08-26**</sup>
8
+
9
+ #### ✨ Features
10
+
11
+ - **image**: Polish ai image.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's improved
19
+
20
+ - **image**: Polish ai image, closes [#8915](https://github.com/lobehub/lobe-chat/issues/8915) ([0efe28d](https://github.com/lobehub/lobe-chat/commit/0efe28d))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
5
30
  ### [Version 1.114.6](https://github.com/lobehub/lobe-chat/compare/v1.114.5...v1.114.6)
6
31
 
7
32
  <sup>Released on **2025-08-22**</sup>