@lobehub/lobehub 2.0.0-next.352 → 2.0.0-next.354

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 (124) hide show
  1. package/.agents/skills/add-provider-doc/SKILL.md +90 -0
  2. package/.agents/skills/add-setting-env/SKILL.md +106 -0
  3. package/.agents/skills/debug/SKILL.md +66 -0
  4. package/.agents/skills/desktop/SKILL.md +78 -0
  5. package/.agents/skills/desktop/references/feature-implementation.md +99 -0
  6. package/.agents/skills/desktop/references/local-tools.md +133 -0
  7. package/.agents/skills/desktop/references/menu-config.md +103 -0
  8. package/.agents/skills/desktop/references/window-management.md +143 -0
  9. package/.agents/skills/drizzle/SKILL.md +129 -0
  10. package/.agents/skills/drizzle/references/db-migrations.md +50 -0
  11. package/.agents/skills/hotkey/SKILL.md +90 -0
  12. package/{.cursor/rules/i18n.mdc → .agents/skills/i18n/SKILL.md} +14 -23
  13. package/.agents/skills/linear/SKILL.md +51 -0
  14. package/.agents/skills/microcopy/SKILL.md +83 -0
  15. package/.agents/skills/modal/SKILL.md +102 -0
  16. package/{.cursor/rules/project-structure.mdc → .agents/skills/project-overview/SKILL.md} +65 -37
  17. package/.agents/skills/react/SKILL.md +73 -0
  18. package/.agents/skills/react/references/layout-kit.md +100 -0
  19. package/.agents/skills/recent-data/SKILL.md +108 -0
  20. package/.agents/skills/testing/SKILL.md +89 -0
  21. package/.agents/skills/testing/references/agent-runtime-e2e.md +126 -0
  22. package/.agents/skills/testing/references/db-model-test.md +124 -0
  23. package/.agents/skills/testing/references/desktop-controller-test.md +124 -0
  24. package/.agents/skills/testing/references/electron-ipc-test.md +63 -0
  25. package/.agents/skills/testing/references/zustand-store-action-test.md +150 -0
  26. package/.agents/skills/typescript/SKILL.md +52 -0
  27. package/.agents/skills/zustand/SKILL.md +78 -0
  28. package/.agents/skills/zustand/references/action-patterns.md +125 -0
  29. package/.agents/skills/zustand/references/slice-organization.md +125 -0
  30. package/AGENTS.md +42 -55
  31. package/CHANGELOG.md +58 -0
  32. package/CLAUDE.md +57 -46
  33. package/GEMINI.md +47 -39
  34. package/changelog/v1.json +14 -0
  35. package/docs/development/database-schema.dbml +5 -0
  36. package/package.json +1 -1
  37. package/packages/database/migrations/0071_add_async_task_extend.sql +5 -0
  38. package/packages/database/migrations/meta/0071_snapshot.json +10720 -0
  39. package/packages/database/migrations/meta/_journal.json +7 -0
  40. package/packages/database/src/schemas/asyncTask.ts +12 -2
  41. package/src/features/FileViewer/Renderer/PDF/index.tsx +2 -3
  42. package/src/features/ShareModal/SharePdf/PdfPreview.tsx +1 -2
  43. package/src/libs/pdfjs/index.tsx +25 -0
  44. package/src/store/test-coverage.md +5 -5
  45. package/.cursor/rules/add-provider-doc.mdc +0 -183
  46. package/.cursor/rules/add-setting-env.mdc +0 -175
  47. package/.cursor/rules/cursor-rules.mdc +0 -28
  48. package/.cursor/rules/db-migrations.mdc +0 -46
  49. package/.cursor/rules/debug-usage.mdc +0 -86
  50. package/.cursor/rules/desktop-controller-tests.mdc +0 -189
  51. package/.cursor/rules/desktop-feature-implementation.mdc +0 -155
  52. package/.cursor/rules/desktop-local-tools-implement.mdc +0 -81
  53. package/.cursor/rules/desktop-menu-configuration.mdc +0 -209
  54. package/.cursor/rules/desktop-window-management.mdc +0 -301
  55. package/.cursor/rules/drizzle-schema-style-guide.mdc +0 -218
  56. package/.cursor/rules/hotkey.mdc +0 -162
  57. package/.cursor/rules/linear.mdc +0 -53
  58. package/.cursor/rules/microcopy-cn.mdc +0 -158
  59. package/.cursor/rules/microcopy-en.mdc +0 -148
  60. package/.cursor/rules/modal-imperative.mdc +0 -162
  61. package/.cursor/rules/packages/react-layout-kit.mdc +0 -122
  62. package/.cursor/rules/project-introduce.mdc +0 -36
  63. package/.cursor/rules/react.mdc +0 -169
  64. package/.cursor/rules/recent-data-usage.mdc +0 -139
  65. package/.cursor/rules/rules-index.mdc +0 -44
  66. package/.cursor/rules/testing-guide/agent-runtime-e2e.mdc +0 -285
  67. package/.cursor/rules/testing-guide/db-model-test.mdc +0 -455
  68. package/.cursor/rules/testing-guide/electron-ipc-test.mdc +0 -80
  69. package/.cursor/rules/testing-guide/testing-guide.mdc +0 -534
  70. package/.cursor/rules/testing-guide/zustand-store-action-test.mdc +0 -574
  71. package/.cursor/rules/typescript.mdc +0 -55
  72. package/.cursor/rules/zustand-action-patterns.mdc +0 -328
  73. package/.cursor/rules/zustand-slice-organization.mdc +0 -308
  74. package/src/libs/pdfjs/pdf.worker.ts +0 -1
  75. package/src/libs/pdfjs/worker.ts +0 -12
  76. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/AGENTS.md +0 -0
  77. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/SKILL.md +0 -0
  78. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/advanced-event-handler-refs.md +0 -0
  79. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/advanced-use-latest.md +0 -0
  80. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-api-routes.md +0 -0
  81. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-defer-await.md +0 -0
  82. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-dependencies.md +0 -0
  83. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-parallel.md +0 -0
  84. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-suspense-boundaries.md +0 -0
  85. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-barrel-imports.md +0 -0
  86. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-conditional.md +0 -0
  87. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-defer-third-party.md +0 -0
  88. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-dynamic-imports.md +0 -0
  89. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-preload.md +0 -0
  90. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/client-event-listeners.md +0 -0
  91. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/client-localstorage-schema.md +0 -0
  92. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/client-passive-event-listeners.md +0 -0
  93. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/client-swr-dedup.md +0 -0
  94. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-batch-dom-css.md +0 -0
  95. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-cache-function-results.md +0 -0
  96. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-cache-property-access.md +0 -0
  97. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-cache-storage.md +0 -0
  98. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-combine-iterations.md +0 -0
  99. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-early-exit.md +0 -0
  100. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-hoist-regexp.md +0 -0
  101. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-index-maps.md +0 -0
  102. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-length-check-first.md +0 -0
  103. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-min-max-loop.md +0 -0
  104. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-set-map-lookups.md +0 -0
  105. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-tosorted-immutable.md +0 -0
  106. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-activity.md +0 -0
  107. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-animate-svg-wrapper.md +0 -0
  108. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-conditional-render.md +0 -0
  109. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-content-visibility.md +0 -0
  110. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-hoist-jsx.md +0 -0
  111. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-hydration-no-flicker.md +0 -0
  112. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-svg-precision.md +0 -0
  113. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-defer-reads.md +0 -0
  114. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-dependencies.md +0 -0
  115. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-derived-state.md +0 -0
  116. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-functional-setstate.md +0 -0
  117. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-lazy-state-init.md +0 -0
  118. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-memo.md +0 -0
  119. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-transitions.md +0 -0
  120. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-after-nonblocking.md +0 -0
  121. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-cache-lru.md +0 -0
  122. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-cache-react.md +0 -0
  123. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-parallel-fetching.md +0 -0
  124. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-serialization.md +0 -0
@@ -1,574 +0,0 @@
1
- ---
2
- description: Best practices for testing Zustand store actions
3
- globs: src/store/**/*.test.ts
4
- alwaysApply: false
5
- ---
6
-
7
- # Zustand Store Action Testing Guide
8
-
9
- This guide provides best practices for testing Zustand store actions, based on our proven testing patterns.
10
-
11
- ## Basic Test Structure
12
-
13
- ```typescript
14
- import { act, renderHook } from '@testing-library/react';
15
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
16
-
17
- import { messageService } from '@/services/message';
18
-
19
- import { useChatStore } from '../../store';
20
-
21
- // Keep zustand mock as it's needed globally
22
- vi.mock('zustand/traditional');
23
-
24
- beforeEach(() => {
25
- // Reset store state
26
- vi.clearAllMocks();
27
- useChatStore.setState(
28
- {
29
- activeId: 'test-session-id',
30
- messagesMap: {},
31
- loadingIds: [],
32
- },
33
- false,
34
- );
35
-
36
- // ✅ Setup only spies that MOST tests need
37
- vi.spyOn(messageService, 'createMessage').mockResolvedValue('new-message-id');
38
- // ❌ Don't setup spies that only few tests need - spy only when needed
39
-
40
- // Setup common mock methods
41
- act(() => {
42
- useChatStore.setState({
43
- refreshMessages: vi.fn(),
44
- internal_coreProcessMessage: vi.fn(),
45
- });
46
- });
47
- });
48
-
49
- afterEach(() => {
50
- vi.restoreAllMocks();
51
- });
52
-
53
- describe('action name', () => {
54
- describe('validation', () => {
55
- // Validation tests
56
- });
57
-
58
- describe('normal flow', () => {
59
- // Happy path tests
60
- });
61
-
62
- describe('error handling', () => {
63
- // Error case tests
64
- });
65
- });
66
- ```
67
-
68
- ## Testing Best Practices
69
-
70
- ### 1. Test Layering - Spy Direct Dependencies Only
71
-
72
- ✅ **Good**: Spy on the direct dependency
73
-
74
- ```typescript
75
- // When testing internal_coreProcessMessage, spy its direct dependency
76
- const fetchAIChatSpy = vi
77
- .spyOn(result.current, 'internal_fetchAIChatMessage')
78
- .mockResolvedValue({ isFunctionCall: false, content: 'AI response' });
79
- ```
80
-
81
- ❌ **Bad**: Spy on lower-level implementation details
82
-
83
- ```typescript
84
- // Don't spy on services that internal_fetchAIChatMessage uses
85
- const streamSpy = vi
86
- .spyOn(chatService, 'createAssistantMessageStream')
87
- .mockImplementation(...);
88
- ```
89
-
90
- **Why**: Each test should only mock its direct dependencies, not the entire call chain. This makes tests more maintainable and less brittle.
91
-
92
- ### 2. Mock Management - Minimize Global Spies
93
-
94
- ✅ **Good**: Spy only when needed
95
-
96
- ```typescript
97
- beforeEach(() => {
98
- // ✅ Only spy services that most tests need
99
- vi.spyOn(messageService, 'createMessage').mockResolvedValue('new-message-id');
100
- // ✅ Don't spy chatService globally
101
- });
102
-
103
- it('should process message', async () => {
104
- // ✅ Spy chatService only in tests that need it
105
- const streamSpy = vi.spyOn(chatService, 'createAssistantMessageStream')
106
- .mockImplementation(...);
107
-
108
- // test logic
109
-
110
- streamSpy.mockRestore();
111
- });
112
- ```
113
-
114
- ❌ **Bad**: Setup all spies globally
115
-
116
- ```typescript
117
- beforeEach(() => {
118
- vi.spyOn(messageService, 'createMessage').mockResolvedValue('id');
119
- vi.spyOn(chatService, 'createAssistantMessageStream').mockResolvedValue({}); // ❌ Not all tests need this
120
- vi.spyOn(fileService, 'uploadFile').mockResolvedValue({}); // ❌ Creates implicit coupling
121
- });
122
- ```
123
-
124
- ### 3. Service Mocking - Mock the Correct Layer
125
-
126
- ✅ **Good**: Mock the service method
127
-
128
- ```typescript
129
- it('should fetch AI chat response', async () => {
130
- const streamSpy = vi
131
- .spyOn(chatService, 'createAssistantMessageStream')
132
- .mockImplementation(async ({ onMessageHandle, onFinish }) => {
133
- await onMessageHandle?.({ type: 'text', text: 'Hello' } as any);
134
- await onFinish?.('Hello', {});
135
- });
136
-
137
- // test logic
138
- });
139
- ```
140
-
141
- ❌ **Bad**: Mock global fetch
142
-
143
- ```typescript
144
- it('should fetch AI chat response', async () => {
145
- global.fetch = vi.fn().mockResolvedValue(...); // ❌ Too low level
146
- });
147
- ```
148
-
149
- ### 4. Test Organization - Use Descriptive Nesting
150
-
151
- ✅ **Good**: Clear nested structure
152
-
153
- ```typescript
154
- describe('sendMessage', () => {
155
- describe('validation', () => {
156
- it('should not send when session is inactive', async () => {});
157
- it('should not send when message is empty', async () => {});
158
- });
159
-
160
- describe('message creation', () => {
161
- it('should create user message and trigger AI processing', async () => {});
162
- it('should send message with files attached', async () => {});
163
- });
164
-
165
- describe('error handling', () => {
166
- it('should handle message creation errors gracefully', async () => {});
167
- });
168
- });
169
- ```
170
-
171
- ❌ **Bad**: Flat structure
172
-
173
- ```typescript
174
- describe('sendMessage', () => {
175
- it('test 1', async () => {});
176
- it('test 2', async () => {});
177
- it('test 3', async () => {});
178
- });
179
- ```
180
-
181
- ### 5. Testing Async Actions
182
-
183
- Always wrap async operations in `act()`:
184
-
185
- ```typescript
186
- it('should send message', async () => {
187
- const { result } = renderHook(() => useChatStore());
188
-
189
- await act(async () => {
190
- await result.current.sendMessage({ message: 'Hello' });
191
- });
192
-
193
- expect(messageService.createMessage).toHaveBeenCalled();
194
- });
195
- ```
196
-
197
- ### 6. State Setup - Use act() for setState
198
-
199
- ```typescript
200
- it('should handle disabled state', async () => {
201
- act(() => {
202
- useChatStore.setState({ activeId: undefined });
203
- });
204
-
205
- const { result } = renderHook(() => useChatStore());
206
- // test logic
207
- });
208
- ```
209
-
210
- ### 7. Testing Complex Flows
211
-
212
- For complex flows with multiple steps, use clear spy setup:
213
-
214
- ```typescript
215
- it('should handle topic creation flow', async () => {
216
- // Setup store state
217
- act(() => {
218
- useChatStore.setState({
219
- activeTopicId: undefined,
220
- messagesMap: {
221
- 'test-session-id': [
222
- { id: 'msg-1', role: 'user', content: 'Message 1' },
223
- { id: 'msg-2', role: 'assistant', content: 'Response 1' },
224
- { id: 'msg-3', role: 'user', content: 'Message 2' },
225
- ],
226
- },
227
- });
228
- });
229
-
230
- const { result } = renderHook(() => useChatStore());
231
-
232
- // Spy on action dependencies
233
- const createTopicSpy = vi.spyOn(result.current, 'createTopic').mockResolvedValue('new-topic-id');
234
- const toggleLoadingSpy = vi.spyOn(result.current, 'internal_toggleMessageLoading');
235
-
236
- // Execute
237
- await act(async () => {
238
- await result.current.sendMessage({ message: 'Test message' });
239
- });
240
-
241
- // Assert
242
- expect(createTopicSpy).toHaveBeenCalled();
243
- expect(toggleLoadingSpy).toHaveBeenCalledWith(true, expect.any(String));
244
- });
245
- ```
246
-
247
- ### 8. Streaming Response Mocking
248
-
249
- When testing streaming responses, simulate the flow properly:
250
-
251
- ```typescript
252
- it('should handle streaming chunks', async () => {
253
- const { result } = renderHook(() => useChatStore());
254
- const messages = [{ id: 'msg-1', role: 'user', content: 'Hello', sessionId: 'test-session' }];
255
-
256
- const streamSpy = vi
257
- .spyOn(chatService, 'createAssistantMessageStream')
258
- .mockImplementation(async ({ onMessageHandle, onFinish }) => {
259
- // Simulate streaming chunks
260
- await onMessageHandle?.({ type: 'text', text: 'Hello' } as any);
261
- await onMessageHandle?.({ type: 'text', text: ' World' } as any);
262
- await onFinish?.('Hello World', {});
263
- });
264
-
265
- await act(async () => {
266
- await result.current.internal_fetchAIChatMessage({
267
- messages,
268
- messageId: 'test-message-id',
269
- model: 'gpt-4o-mini',
270
- provider: 'openai',
271
- });
272
- });
273
-
274
- expect(result.current.internal_dispatchMessage).toHaveBeenCalled();
275
-
276
- streamSpy.mockRestore();
277
- });
278
- ```
279
-
280
- ### 9. Error Handling Tests
281
-
282
- Always test error scenarios:
283
-
284
- ```typescript
285
- it('should handle errors gracefully', async () => {
286
- const { result } = renderHook(() => useChatStore());
287
-
288
- vi.spyOn(messageService, 'createMessage').mockRejectedValue(new Error('create message error'));
289
-
290
- await act(async () => {
291
- try {
292
- await result.current.sendMessage({ message: 'Test message' });
293
- } catch {
294
- // Expected to throw
295
- }
296
- });
297
-
298
- expect(result.current.internal_coreProcessMessage).not.toHaveBeenCalled();
299
- });
300
- ```
301
-
302
- ### 10. Cleanup After Tests
303
-
304
- Always restore mocks after each test:
305
-
306
- ```typescript
307
- afterEach(() => {
308
- vi.restoreAllMocks();
309
- });
310
-
311
- // For individual test cleanup:
312
- it('should test something', async () => {
313
- const spy = vi.spyOn(service, 'method').mockImplementation(...);
314
-
315
- // test logic
316
-
317
- spy.mockRestore(); // Optional: cleanup immediately after test
318
- });
319
- ```
320
-
321
- ## Common Patterns
322
-
323
- ### Testing Store Methods That Call Other Store Methods
324
-
325
- ```typescript
326
- it('should call internal methods', async () => {
327
- const { result } = renderHook(() => useChatStore());
328
-
329
- const internalMethodSpy = vi.spyOn(result.current, 'internal_method').mockResolvedValue();
330
-
331
- await act(async () => {
332
- await result.current.publicMethod();
333
- });
334
-
335
- expect(internalMethodSpy).toHaveBeenCalledWith(
336
- expect.any(String),
337
- expect.objectContaining({ key: 'value' }),
338
- );
339
- });
340
- ```
341
-
342
- ### Testing Conditional Logic
343
-
344
- ```typescript
345
- describe('conditional behavior', () => {
346
- it('should execute when condition is true', async () => {
347
- const { result } = renderHook(() => useChatStore());
348
- vi.spyOn(result.current, 'internal_shouldUseRAG').mockReturnValue(true);
349
-
350
- await act(async () => {
351
- await result.current.sendMessage({ message: 'test' });
352
- });
353
-
354
- expect(result.current.internal_retrieveChunks).toHaveBeenCalled();
355
- });
356
-
357
- it('should not execute when condition is false', async () => {
358
- const { result } = renderHook(() => useChatStore());
359
- vi.spyOn(result.current, 'internal_shouldUseRAG').mockReturnValue(false);
360
-
361
- await act(async () => {
362
- await result.current.sendMessage({ message: 'test' });
363
- });
364
-
365
- expect(result.current.internal_retrieveChunks).not.toHaveBeenCalled();
366
- });
367
- });
368
- ```
369
-
370
- ### Testing AbortController
371
-
372
- ```typescript
373
- it('should abort generation and clear loading state', () => {
374
- const abortController = new AbortController();
375
-
376
- act(() => {
377
- useChatStore.setState({ chatLoadingIdsAbortController: abortController });
378
- });
379
-
380
- const { result } = renderHook(() => useChatStore());
381
- const toggleLoadingSpy = vi.spyOn(result.current, 'internal_toggleChatLoading');
382
-
383
- act(() => {
384
- result.current.stopGenerateMessage();
385
- });
386
-
387
- expect(abortController.signal.aborted).toBe(true);
388
- expect(toggleLoadingSpy).toHaveBeenCalledWith(false, undefined, expect.any(String));
389
- });
390
- ```
391
-
392
- ## Anti-Patterns to Avoid
393
-
394
- ❌ **Don't**: Mock the entire store
395
-
396
- ```typescript
397
- vi.mock('../../store', () => ({
398
- useChatStore: vi.fn(() => ({
399
- sendMessage: vi.fn(),
400
- })),
401
- }));
402
- ```
403
-
404
- ❌ **Don't**: Test implementation details
405
-
406
- ```typescript
407
- // Bad: testing internal state structure
408
- expect(result.current.messagesMap).toHaveProperty('test-session');
409
-
410
- // Good: testing behavior
411
- expect(result.current.refreshMessages).toHaveBeenCalled();
412
- ```
413
-
414
- ❌ **Don't**: Create tight coupling between tests
415
-
416
- ```typescript
417
- // Bad: Tests depend on order
418
- let messageId: string;
419
-
420
- it('test 1', () => {
421
- messageId = 'some-id'; // Side effect
422
- });
423
-
424
- it('test 2', () => {
425
- expect(messageId).toBeDefined(); // Depends on test 1
426
- });
427
- ```
428
-
429
- ❌ **Don't**: Over-mock services
430
-
431
- ```typescript
432
- // Bad: Mocking everything
433
- beforeEach(() => {
434
- vi.mock('@/services/chat');
435
- vi.mock('@/services/message');
436
- vi.mock('@/services/file');
437
- vi.mock('@/services/agent');
438
- // ... too many global mocks
439
- });
440
- ```
441
-
442
- ## Testing SWR Hooks in Zustand Stores
443
-
444
- Some Zustand store slices use SWR hooks for data fetching. These require a different testing approach.
445
-
446
- ### Basic SWR Hook Test Structure
447
-
448
- ```typescript
449
- import { renderHook, waitFor } from '@testing-library/react';
450
- import { beforeEach, describe, expect, it, vi } from 'vitest';
451
-
452
- import { discoverService } from '@/services/discover';
453
- import { globalHelpers } from '@/store/global/helpers';
454
-
455
- import { useDiscoverStore as useStore } from '../../store';
456
-
457
- vi.mock('zustand/traditional');
458
-
459
- beforeEach(() => {
460
- vi.clearAllMocks();
461
- });
462
-
463
- describe('SWR Hook Actions', () => {
464
- it('should fetch data and return correct response', async () => {
465
- const mockData = [{ id: '1', name: 'Item 1' }];
466
-
467
- // Mock the service call (the fetcher)
468
- vi.spyOn(discoverService, 'getPluginCategories').mockResolvedValue(mockData as any);
469
- vi.spyOn(globalHelpers, 'getCurrentLanguage').mockReturnValue('en-US');
470
-
471
- const params = {} as any;
472
- const { result } = renderHook(() => useStore.getState().usePluginCategories(params));
473
-
474
- // Use waitFor to wait for async data loading
475
- await waitFor(() => {
476
- expect(result.current.data).toEqual(mockData);
477
- });
478
-
479
- expect(discoverService.getPluginCategories).toHaveBeenCalledWith(params);
480
- });
481
- });
482
- ```
483
-
484
- **Key points**:
485
-
486
- - **DO NOT mock useSWR** - let it use the real implementation
487
- - Only mock the **service methods** (fetchers)
488
- - Use `waitFor` from `@testing-library/react` to wait for async operations
489
- - Check `result.current.data` directly after waitFor completes
490
-
491
- ### Testing SWR Key Generation
492
-
493
- ```typescript
494
- it('should generate correct SWR key with locale and params', () => {
495
- vi.spyOn(globalHelpers, 'getCurrentLanguage').mockReturnValue('zh-CN');
496
-
497
- const useSWRMock = vi.mocked(useSWR);
498
- let capturedKey: string | null = null;
499
- useSWRMock.mockImplementation(((key: string) => {
500
- capturedKey = key;
501
- return { data: undefined, error: undefined, isValidating: false, mutate: vi.fn() };
502
- }) as any);
503
-
504
- const params = { page: 2, category: 'tools' } as any;
505
- renderHook(() => useStore.getState().usePluginList(params));
506
-
507
- expect(capturedKey).toBe('plugin-list-zh-CN-2-tools');
508
- });
509
- ```
510
-
511
- ### Testing SWR Configuration
512
-
513
- ```typescript
514
- it('should have correct SWR configuration', () => {
515
- const useSWRMock = vi.mocked(useSWR);
516
- let capturedOptions: any = null;
517
- useSWRMock.mockImplementation(((key: string, fetcher: any, options: any) => {
518
- capturedOptions = options;
519
- return { data: undefined, error: undefined, isValidating: false, mutate: vi.fn() };
520
- }) as any);
521
-
522
- renderHook(() => useStore.getState().usePluginIdentifiers());
523
-
524
- expect(capturedOptions).toMatchObject({ revalidateOnFocus: false });
525
- });
526
- ```
527
-
528
- ### Testing Conditional Fetching
529
-
530
- ```typescript
531
- it('should not fetch when required parameter is missing', () => {
532
- const useSWRMock = vi.mocked(useSWR);
533
- let capturedKey: string | null = null;
534
- useSWRMock.mockImplementation(((key: string | null) => {
535
- capturedKey = key;
536
- return { data: undefined, error: undefined, isValidating: false, mutate: vi.fn() };
537
- }) as any);
538
-
539
- // When identifier is undefined, SWR key should be null
540
- renderHook(() => useStore.getState().usePluginDetail({ identifier: undefined }));
541
-
542
- expect(capturedKey).toBeNull();
543
- });
544
- ```
545
-
546
- ### Key Differences from Regular Action Tests
547
-
548
- 1. **Mock useSWR globally**: Use `vi.mock('swr')` at the top level
549
- 2. **Mock the fetcher, not the result**:
550
- - ✅ **Correct**: `const data = fetcher?.()` - call fetcher and return its Promise
551
- - ❌ **Wrong**: `return { data: mockData }` - hardcode the result
552
- 3. **Await Promise results**: The `data` field is a Promise, use `await result.current.data`
553
- 4. **No act() wrapper needed**: SWR hooks don't trigger React state updates in these tests
554
- 5. **Test SWR key generation**: Verify keys include locale and parameters
555
- 6. **Test configuration**: Verify revalidation and other SWR options
556
- 7. **Type assertions**: Use `as any` for test mock data where type definitions are strict
557
-
558
- **Why this matters**:
559
-
560
- - The fetcher (service method) is what we're testing - it must be called
561
- - Hardcoding the return value bypasses the actual fetcher logic
562
- - SWR returns Promises in real usage, tests should mirror this behavior
563
-
564
- ## Benefits of This Approach
565
-
566
- ✅ **Clear test layers** - Each test only spies on direct dependencies ✅ **Correct mocks** - Mocks match actual implementation ✅ **Better maintainability** - Changes to implementation require fewer test updates ✅ **Improved coverage** - Structured approach ensures all branches are tested ✅ **Reduced coupling** - Tests are independent and can run in any order
567
-
568
- ## Reference
569
-
570
- See example implementation in:
571
-
572
- - `src/store/chat/slices/aiChat/actions/__tests__/generateAIChat.test.ts` (Regular actions)
573
- - `src/store/discover/slices/plugin/action.test.ts` (SWR hooks)
574
- - `src/store/discover/slices/mcp/action.test.ts` (SWR hooks)
@@ -1,55 +0,0 @@
1
- ---
2
- description: TypeScript code style and optimization guidelines
3
- globs: *.ts,*.tsx,*.mts
4
- alwaysApply: false
5
- ---
6
-
7
- # TypeScript Code Style Guide
8
-
9
- ## Types and Type Safety
10
-
11
- - avoid explicit type annotations when TypeScript can infer types.
12
- - avoid implicitly `any` variables; explicitly type when necessary (e.g., `let a: number` instead of `let a`).
13
- - use the most accurate type possible (e.g., prefer `Record<PropertyKey, unknown>` over `object` and `any`).
14
- - prefer `interface` over `type` for object shapes (e.g., React component props). Keep `type` for unions, intersections, and utility types.
15
- - prefer `as const satisfies XyzInterface` over plain `as const` when suitable.
16
- - prefer `@ts-expect-error` over `@ts-ignore` over `as any`
17
- - Avoid meaningless null/undefined parameters; design strict function contracts.
18
-
19
- ## Asynchronous Patterns and Concurrency
20
-
21
- - Prefer `async`/`await` over callbacks or chained `.then` promises.
22
- - Prefer async APIs over sync ones (avoid `*Sync`).
23
- - Prefer promise-based variants (e.g., `import { readFile } from 'fs/promises'`) over callback-based APIs from `fs`.
24
- - Where safe, convert sequential async flows to concurrent ones with `Promise.all`, `Promise.race`, etc.
25
-
26
- ## Code Structure and Readability
27
-
28
- - Prefer object destructuring when accessing and using properties.
29
- - Use consistent, descriptive naming; avoid obscure abbreviations.
30
- - Use semantically meaningful variable, function, and class names.
31
- - Replace magic numbers or strings with well-named constants.
32
- - Defer formatting to tooling; ignore purely formatting-only issues and autofixable lint problems.
33
-
34
- ## UI and Theming
35
-
36
- - Use components from `@lobehub/ui`, Ant Design, or existing design system components instead of raw HTML tags (e.g., `Button` vs. `button`).
37
- - Design for dark mode and mobile responsiveness:
38
- - Use the `antd-style` token system instead of hard-coded colors.
39
- - Select appropriate component variants.
40
-
41
- ## Performance
42
-
43
- - Prefer `for…of` loops to index-based `for` loops when feasible.
44
- - Reuse existing utils inside `packages/utils` or installed npm packages rather than reinventing the wheel.
45
- - Query only the required columns from a database rather than selecting entire rows.
46
-
47
- ## Time and Consistency
48
-
49
- - Instead of calling `Date.now()` multiple times, assign it to a constant once and reuse it to ensure consistency and improve readability.
50
-
51
- ## Logging
52
-
53
- - Never log user private information like api key, etc
54
- - Don't use `import { log } from 'debug'` to log messages, because it will directly log the message to the console.
55
- - Use console.error instead of debug package to log error message in catch block.