@lobehub/lobehub 2.0.0-next.353 → 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 (119) 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 +33 -0
  32. package/CLAUDE.md +57 -46
  33. package/GEMINI.md +47 -39
  34. package/changelog/v1.json +9 -0
  35. package/package.json +1 -1
  36. package/src/features/FileViewer/Renderer/PDF/index.tsx +2 -3
  37. package/src/features/ShareModal/SharePdf/PdfPreview.tsx +1 -2
  38. package/src/libs/pdfjs/index.tsx +25 -0
  39. package/src/store/test-coverage.md +5 -5
  40. package/.cursor/rules/add-provider-doc.mdc +0 -183
  41. package/.cursor/rules/add-setting-env.mdc +0 -175
  42. package/.cursor/rules/cursor-rules.mdc +0 -28
  43. package/.cursor/rules/db-migrations.mdc +0 -46
  44. package/.cursor/rules/debug-usage.mdc +0 -86
  45. package/.cursor/rules/desktop-controller-tests.mdc +0 -189
  46. package/.cursor/rules/desktop-feature-implementation.mdc +0 -155
  47. package/.cursor/rules/desktop-local-tools-implement.mdc +0 -81
  48. package/.cursor/rules/desktop-menu-configuration.mdc +0 -209
  49. package/.cursor/rules/desktop-window-management.mdc +0 -301
  50. package/.cursor/rules/drizzle-schema-style-guide.mdc +0 -218
  51. package/.cursor/rules/hotkey.mdc +0 -162
  52. package/.cursor/rules/linear.mdc +0 -53
  53. package/.cursor/rules/microcopy-cn.mdc +0 -158
  54. package/.cursor/rules/microcopy-en.mdc +0 -148
  55. package/.cursor/rules/modal-imperative.mdc +0 -162
  56. package/.cursor/rules/packages/react-layout-kit.mdc +0 -122
  57. package/.cursor/rules/project-introduce.mdc +0 -36
  58. package/.cursor/rules/react.mdc +0 -169
  59. package/.cursor/rules/recent-data-usage.mdc +0 -139
  60. package/.cursor/rules/rules-index.mdc +0 -44
  61. package/.cursor/rules/testing-guide/agent-runtime-e2e.mdc +0 -285
  62. package/.cursor/rules/testing-guide/db-model-test.mdc +0 -455
  63. package/.cursor/rules/testing-guide/electron-ipc-test.mdc +0 -80
  64. package/.cursor/rules/testing-guide/testing-guide.mdc +0 -534
  65. package/.cursor/rules/testing-guide/zustand-store-action-test.mdc +0 -574
  66. package/.cursor/rules/typescript.mdc +0 -55
  67. package/.cursor/rules/zustand-action-patterns.mdc +0 -328
  68. package/.cursor/rules/zustand-slice-organization.mdc +0 -308
  69. package/src/libs/pdfjs/pdf.worker.ts +0 -1
  70. package/src/libs/pdfjs/worker.ts +0 -12
  71. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/AGENTS.md +0 -0
  72. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/SKILL.md +0 -0
  73. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/advanced-event-handler-refs.md +0 -0
  74. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/advanced-use-latest.md +0 -0
  75. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-api-routes.md +0 -0
  76. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-defer-await.md +0 -0
  77. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-dependencies.md +0 -0
  78. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-parallel.md +0 -0
  79. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-suspense-boundaries.md +0 -0
  80. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-barrel-imports.md +0 -0
  81. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-conditional.md +0 -0
  82. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-defer-third-party.md +0 -0
  83. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-dynamic-imports.md +0 -0
  84. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-preload.md +0 -0
  85. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/client-event-listeners.md +0 -0
  86. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/client-localstorage-schema.md +0 -0
  87. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/client-passive-event-listeners.md +0 -0
  88. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/client-swr-dedup.md +0 -0
  89. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-batch-dom-css.md +0 -0
  90. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-cache-function-results.md +0 -0
  91. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-cache-property-access.md +0 -0
  92. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-cache-storage.md +0 -0
  93. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-combine-iterations.md +0 -0
  94. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-early-exit.md +0 -0
  95. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-hoist-regexp.md +0 -0
  96. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-index-maps.md +0 -0
  97. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-length-check-first.md +0 -0
  98. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-min-max-loop.md +0 -0
  99. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-set-map-lookups.md +0 -0
  100. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-tosorted-immutable.md +0 -0
  101. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-activity.md +0 -0
  102. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-animate-svg-wrapper.md +0 -0
  103. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-conditional-render.md +0 -0
  104. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-content-visibility.md +0 -0
  105. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-hoist-jsx.md +0 -0
  106. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-hydration-no-flicker.md +0 -0
  107. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-svg-precision.md +0 -0
  108. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-defer-reads.md +0 -0
  109. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-dependencies.md +0 -0
  110. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-derived-state.md +0 -0
  111. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-functional-setstate.md +0 -0
  112. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-lazy-state-init.md +0 -0
  113. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-memo.md +0 -0
  114. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-transitions.md +0 -0
  115. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-after-nonblocking.md +0 -0
  116. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-cache-lru.md +0 -0
  117. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-cache-react.md +0 -0
  118. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-parallel-fetching.md +0 -0
  119. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-serialization.md +0 -0
@@ -0,0 +1,126 @@
1
+ # Agent Runtime E2E Testing Guide
2
+
3
+ ## Core Principles
4
+
5
+ ### Minimal Mock Principle
6
+
7
+ Only mock **three external dependencies**:
8
+
9
+ | Dependency | Mock | Description |
10
+ |------------|------|-------------|
11
+ | Database | PGLite | In-memory database from `@lobechat/database/test-utils` |
12
+ | Redis | InMemoryAgentStateManager | Memory implementation |
13
+ | Redis | InMemoryStreamEventManager | Memory implementation |
14
+
15
+ **NOT mocked:**
16
+ - `model-bank` - Uses real model config
17
+ - `Mecha` (AgentToolsEngine, ContextEngineering)
18
+ - `AgentRuntimeService`
19
+ - `AgentRuntimeCoordinator`
20
+
21
+ ### Use vi.spyOn, not vi.mock
22
+
23
+ Different tests need different LLM responses. `vi.spyOn` provides:
24
+ - Flexible return values per test
25
+ - Easy testing of different scenarios
26
+ - Better test isolation
27
+
28
+ ### Default Model: gpt-5
29
+
30
+ - Always available in `model-bank`
31
+ - Stable across model updates
32
+
33
+ ## Technical Implementation
34
+
35
+ ### Database Setup
36
+
37
+ ```typescript
38
+ import { LobeChatDatabase } from '@lobechat/database';
39
+ import { getTestDB } from '@lobechat/database/test-utils';
40
+
41
+ let testDB: LobeChatDatabase;
42
+
43
+ beforeEach(async () => {
44
+ testDB = await getTestDB();
45
+ });
46
+ ```
47
+
48
+ ### OpenAI Stream Response Helper
49
+
50
+ ```typescript
51
+ export const createOpenAIStreamResponse = (options: {
52
+ content?: string;
53
+ toolCalls?: Array<{ id: string; name: string; arguments: string }>;
54
+ finishReason?: 'stop' | 'tool_calls';
55
+ }) => {
56
+ const { content, toolCalls, finishReason = 'stop' } = options;
57
+
58
+ return new Response(
59
+ new ReadableStream({
60
+ start(controller) {
61
+ const encoder = new TextEncoder();
62
+
63
+ if (content) {
64
+ const chunk = {
65
+ id: 'chatcmpl-mock',
66
+ object: 'chat.completion.chunk',
67
+ model: 'gpt-5',
68
+ choices: [{ index: 0, delta: { content }, finish_reason: null }],
69
+ };
70
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(chunk)}\n\n`));
71
+ }
72
+
73
+ // ... tool_calls handling
74
+ // ... finish chunk
75
+ controller.enqueue(encoder.encode('data: [DONE]\n\n'));
76
+ controller.close();
77
+ },
78
+ }),
79
+ { headers: { 'content-type': 'text/event-stream' } }
80
+ );
81
+ };
82
+ ```
83
+
84
+ ### State Management
85
+
86
+ ```typescript
87
+ import { InMemoryAgentStateManager, InMemoryStreamEventManager } from '@/server/modules/AgentRuntime';
88
+
89
+ const stateManager = new InMemoryAgentStateManager();
90
+ const streamEventManager = new InMemoryStreamEventManager();
91
+
92
+ const service = new AgentRuntimeService(serverDB, userId, {
93
+ coordinatorOptions: { stateManager, streamEventManager },
94
+ queueService: null,
95
+ streamEventManager,
96
+ });
97
+ ```
98
+
99
+ ### Mock OpenAI API
100
+
101
+ ```typescript
102
+ const fetchSpy = vi.spyOn(globalThis, 'fetch');
103
+
104
+ it('should handle text response', async () => {
105
+ fetchSpy.mockResolvedValueOnce(createOpenAIStreamResponse({ content: 'Response text' }));
106
+ // ... execute test
107
+ });
108
+
109
+ it('should handle tool calls', async () => {
110
+ fetchSpy.mockResolvedValueOnce(createOpenAIStreamResponse({
111
+ toolCalls: [{
112
+ id: 'call_123',
113
+ name: 'lobe-web-browsing____search____builtin',
114
+ arguments: JSON.stringify({ query: 'weather' }),
115
+ }],
116
+ finishReason: 'tool_calls',
117
+ }));
118
+ // ... execute test
119
+ });
120
+ ```
121
+
122
+ ## Notes
123
+
124
+ 1. **Test isolation**: Clean `InMemoryAgentStateManager` and `InMemoryStreamEventManager` after each test
125
+ 2. **Timeout**: E2E tests may need longer timeouts
126
+ 3. **Debug**: Use `DEBUG=lobe-server:*` for detailed logs
@@ -0,0 +1,124 @@
1
+ # Database Model Testing Guide
2
+
3
+ Test `packages/database` Model layer.
4
+
5
+ ## Dual Environment Verification (Required)
6
+
7
+ ```bash
8
+ # 1. Client environment (fast)
9
+ cd packages/database && TEST_SERVER_DB=0 bunx vitest run --silent='passed-only' '[file]'
10
+
11
+ # 2. Server environment (compatibility)
12
+ cd packages/database && TEST_SERVER_DB=1 bunx vitest run --silent='passed-only' '[file]'
13
+ ```
14
+
15
+ ## User Permission Check - Security First 🔒
16
+
17
+ **Critical security requirement**: All user data operations must include permission checks.
18
+
19
+ ```typescript
20
+ // ❌ DANGEROUS: Missing permission check
21
+ update = async (id: string, data: Partial<MyModel>) => {
22
+ return this.db.update(myTable).set(data)
23
+ .where(eq(myTable.id, id)) // Only checks ID
24
+ .returning();
25
+ };
26
+
27
+ // ✅ SECURE: Permission check included
28
+ update = async (id: string, data: Partial<MyModel>) => {
29
+ return this.db.update(myTable).set(data)
30
+ .where(and(
31
+ eq(myTable.id, id),
32
+ eq(myTable.userId, this.userId) // ✅ Permission check
33
+ ))
34
+ .returning();
35
+ };
36
+ ```
37
+
38
+ ## Test File Structure
39
+
40
+ ```typescript
41
+ // @vitest-environment node
42
+ describe('MyModel', () => {
43
+ describe('create', () => { /* ... */ });
44
+ describe('queryAll', () => { /* ... */ });
45
+ describe('update', () => {
46
+ it('should update own records');
47
+ it('should NOT update other users records'); // 🔒 Security
48
+ });
49
+ describe('delete', () => {
50
+ it('should delete own records');
51
+ it('should NOT delete other users records'); // 🔒 Security
52
+ });
53
+ describe('user isolation', () => {
54
+ it('should enforce user data isolation'); // 🔒 Core security
55
+ });
56
+ });
57
+ ```
58
+
59
+ ## Security Test Example
60
+
61
+ ```typescript
62
+ it('should not update records of other users', async () => {
63
+ const [otherUserRecord] = await serverDB
64
+ .insert(myTable)
65
+ .values({ userId: 'other-user', data: 'original' })
66
+ .returning();
67
+
68
+ const result = await myModel.update(otherUserRecord.id, { data: 'hacked' });
69
+
70
+ expect(result).toBeUndefined();
71
+ const unchanged = await serverDB.query.myTable.findFirst({
72
+ where: eq(myTable.id, otherUserRecord.id),
73
+ });
74
+ expect(unchanged?.data).toBe('original');
75
+ });
76
+ ```
77
+
78
+ ## Data Management
79
+
80
+ ```typescript
81
+ const userId = 'test-user';
82
+ const otherUserId = 'other-user';
83
+
84
+ beforeEach(async () => {
85
+ await serverDB.delete(users);
86
+ await serverDB.insert(users).values([{ id: userId }, { id: otherUserId }]);
87
+ });
88
+
89
+ afterEach(async () => {
90
+ await serverDB.delete(users);
91
+ });
92
+ ```
93
+
94
+ ## Foreign Key Handling
95
+
96
+ ```typescript
97
+ // ❌ Wrong: Invalid foreign key
98
+ const testData = { asyncTaskId: 'invalid-uuid', fileId: 'non-existent' };
99
+
100
+ // ✅ Correct: Use null
101
+ const testData = { asyncTaskId: null, fileId: null };
102
+
103
+ // ✅ Or: Create referenced record first
104
+ beforeEach(async () => {
105
+ const [asyncTask] = await serverDB.insert(asyncTasks)
106
+ .values({ id: 'valid-id', status: 'pending' }).returning();
107
+ testData.asyncTaskId = asyncTask.id;
108
+ });
109
+ ```
110
+
111
+ ## Predictable Sorting
112
+
113
+ ```typescript
114
+ // ✅ Use explicit timestamps
115
+ const oldDate = new Date('2024-01-01T10:00:00Z');
116
+ const newDate = new Date('2024-01-02T10:00:00Z');
117
+ await serverDB.insert(table).values([
118
+ { ...data1, createdAt: oldDate },
119
+ { ...data2, createdAt: newDate },
120
+ ]);
121
+
122
+ // ❌ Don't rely on insert order
123
+ await serverDB.insert(table).values([data1, data2]); // Unpredictable
124
+ ```
@@ -0,0 +1,124 @@
1
+ # Desktop Controller Unit Testing Guide
2
+
3
+ ## Testing Framework & Directory Structure
4
+
5
+ LobeChat Desktop uses Vitest as the test framework. Controller unit tests should be placed in the `__tests__` directory adjacent to the controller file, named with the original controller filename plus `.test.ts`.
6
+
7
+ ```plaintext
8
+ apps/desktop/src/main/controllers/
9
+ ├── __tests__/
10
+ │ ├── index.test.ts
11
+ │ ├── MenuCtr.test.ts
12
+ │ └── ...
13
+ ├── McpCtr.ts
14
+ ├── MenuCtr.ts
15
+ └── ...
16
+ ```
17
+
18
+ ## Basic Test File Structure
19
+
20
+ ```typescript
21
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
22
+
23
+ import type { App } from '@/core/App';
24
+
25
+ import YourController from '../YourControllerName';
26
+
27
+ // Mock dependencies
28
+ vi.mock('dependency-module', () => ({
29
+ dependencyFunction: vi.fn(),
30
+ }));
31
+
32
+ // Mock App instance
33
+ const mockApp = {
34
+ // Mock necessary App properties and methods as needed
35
+ } as unknown as App;
36
+
37
+ describe('YourController', () => {
38
+ let controller: YourController;
39
+
40
+ beforeEach(() => {
41
+ vi.clearAllMocks();
42
+ controller = new YourController(mockApp);
43
+ });
44
+
45
+ describe('methodName', () => {
46
+ it('test scenario description', async () => {
47
+ // Prepare test data
48
+
49
+ // Execute method under test
50
+ const result = await controller.methodName(params);
51
+
52
+ // Verify results
53
+ expect(result).toMatchObject(expectedResult);
54
+ });
55
+ });
56
+ });
57
+ ```
58
+
59
+ ## Mocking External Dependencies
60
+
61
+ ### Module Functions
62
+
63
+ ```typescript
64
+ const mockFunction = vi.fn();
65
+
66
+ vi.mock('module-name', () => ({
67
+ functionName: mockFunction,
68
+ }));
69
+ ```
70
+
71
+ ### Node.js Core Modules
72
+
73
+ Example: mocking `child_process.exec` and `util.promisify`:
74
+
75
+ ```typescript
76
+ const mockExecImpl = vi.fn();
77
+
78
+ vi.mock('child_process', () => ({
79
+ exec: vi.fn((cmd, callback) => {
80
+ return mockExecImpl(cmd, callback);
81
+ }),
82
+ }));
83
+
84
+ vi.mock('util', () => ({
85
+ promisify: vi.fn((fn) => {
86
+ return async (cmd: string) => {
87
+ return new Promise((resolve, reject) => {
88
+ mockExecImpl(cmd, (error: Error | null, result: any) => {
89
+ if (error) reject(error);
90
+ else resolve(result);
91
+ });
92
+ });
93
+ };
94
+ }),
95
+ }));
96
+ ```
97
+
98
+ ## Best Practices
99
+
100
+ 1. **Isolate tests**: Use `beforeEach` to reset mocks and state
101
+ 2. **Comprehensive coverage**: Test normal flows, edge cases, and error handling
102
+ 3. **Clear naming**: Test names should describe content and expected results
103
+ 4. **Avoid implementation details**: Test behavior, not implementation
104
+ 5. **Mock external dependencies**: Use `vi.mock()` for all external dependencies
105
+
106
+ ## Example: Testing IPC Event Handler
107
+
108
+ ```typescript
109
+ it('should handle IPC event correctly', async () => {
110
+ mockSomething.mockReturnValue({ result: 'success' });
111
+
112
+ const result = await controller.ipcMethodName({
113
+ param1: 'value1',
114
+ param2: 'value2',
115
+ });
116
+
117
+ expect(result).toEqual({
118
+ success: true,
119
+ data: { result: 'success' },
120
+ });
121
+
122
+ expect(mockSomething).toHaveBeenCalledWith('value1', 'value2');
123
+ });
124
+ ```
@@ -0,0 +1,63 @@
1
+ # Electron IPC Testing Strategy
2
+
3
+ For Electron IPC tests, use **Mock return values** instead of real Electron environment.
4
+
5
+ ## Basic Mock Setup
6
+
7
+ ```typescript
8
+ import { vi } from 'vitest';
9
+ import { electronIpcClient } from '@/server/modules/ElectronIPCClient';
10
+
11
+ vi.mock('@/server/modules/ElectronIPCClient', () => ({
12
+ electronIpcClient: {
13
+ getFilePathById: vi.fn(),
14
+ deleteFiles: vi.fn(),
15
+ },
16
+ }));
17
+ ```
18
+
19
+ ## Setting Mock Behavior
20
+
21
+ ```typescript
22
+ beforeEach(() => {
23
+ vi.resetAllMocks();
24
+ vi.mocked(electronIpcClient.getFilePathById).mockResolvedValue('/path/to/file.txt');
25
+ vi.mocked(electronIpcClient.deleteFiles).mockResolvedValue({ success: true });
26
+ });
27
+ ```
28
+
29
+ ## Testing Different Scenarios
30
+
31
+ ```typescript
32
+ it('should handle successful file deletion', async () => {
33
+ vi.mocked(electronIpcClient.deleteFiles).mockResolvedValue({ success: true });
34
+
35
+ const result = await service.deleteFiles(['desktop://file1.txt']);
36
+
37
+ expect(electronIpcClient.deleteFiles).toHaveBeenCalledWith(['desktop://file1.txt']);
38
+ expect(result.success).toBe(true);
39
+ });
40
+
41
+ it('should handle file deletion failure', async () => {
42
+ vi.mocked(electronIpcClient.deleteFiles).mockRejectedValue(new Error('Delete failed'));
43
+
44
+ const result = await service.deleteFiles(['desktop://file1.txt']);
45
+
46
+ expect(result.success).toBe(false);
47
+ expect(result.errors).toBeDefined();
48
+ });
49
+ ```
50
+
51
+ ## Advantages
52
+
53
+ 1. **Environment simplification**: No complex Electron setup
54
+ 2. **Controlled testing**: Precise control over IPC return values
55
+ 3. **Scenario coverage**: Easy to test success/failure cases
56
+ 4. **Speed**: Mock calls are faster than real IPC
57
+
58
+ ## Notes
59
+
60
+ - Ensure mock behavior matches real IPC interface
61
+ - Use `vi.mocked()` for type safety
62
+ - Reset mocks in `beforeEach` to avoid test interference
63
+ - Verify both return values and that IPC methods were called correctly
@@ -0,0 +1,150 @@
1
+ # Zustand Store Action Testing Guide
2
+
3
+ ## Basic Structure
4
+
5
+ ```typescript
6
+ import { act, renderHook } from '@testing-library/react';
7
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
8
+ import { useChatStore } from '../../store';
9
+
10
+ vi.mock('zustand/traditional');
11
+
12
+ beforeEach(() => {
13
+ vi.clearAllMocks();
14
+ useChatStore.setState({
15
+ activeId: 'test-session-id',
16
+ messagesMap: {},
17
+ loadingIds: [],
18
+ }, false);
19
+
20
+ vi.spyOn(messageService, 'createMessage').mockResolvedValue('new-message-id');
21
+
22
+ act(() => {
23
+ useChatStore.setState({
24
+ refreshMessages: vi.fn(),
25
+ internal_coreProcessMessage: vi.fn(),
26
+ });
27
+ });
28
+ });
29
+
30
+ afterEach(() => {
31
+ vi.restoreAllMocks();
32
+ });
33
+ ```
34
+
35
+ ## Key Principles
36
+
37
+ ### 1. Spy Direct Dependencies Only
38
+
39
+ ```typescript
40
+ // ✅ Good: Spy on direct dependency
41
+ const fetchAIChatSpy = vi.spyOn(result.current, 'internal_fetchAIChatMessage')
42
+ .mockResolvedValue({ isFunctionCall: false, content: 'AI response' });
43
+
44
+ // ❌ Bad: Spy on lower-level implementation
45
+ const streamSpy = vi.spyOn(chatService, 'createAssistantMessageStream')
46
+ .mockImplementation(...);
47
+ ```
48
+
49
+ ### 2. Minimize Global Spies
50
+
51
+ ```typescript
52
+ // ✅ Spy only when needed
53
+ it('should process message', async () => {
54
+ const streamSpy = vi.spyOn(chatService, 'createAssistantMessageStream')
55
+ .mockImplementation(...);
56
+ // test logic
57
+ streamSpy.mockRestore();
58
+ });
59
+
60
+ // ❌ Don't setup all spies globally
61
+ beforeEach(() => {
62
+ vi.spyOn(chatService, 'createAssistantMessageStream').mockResolvedValue({});
63
+ vi.spyOn(fileService, 'uploadFile').mockResolvedValue({});
64
+ });
65
+ ```
66
+
67
+ ### 3. Use act() for Async Operations
68
+
69
+ ```typescript
70
+ it('should send message', async () => {
71
+ const { result } = renderHook(() => useChatStore());
72
+
73
+ await act(async () => {
74
+ await result.current.sendMessage({ message: 'Hello' });
75
+ });
76
+
77
+ expect(messageService.createMessage).toHaveBeenCalled();
78
+ });
79
+ ```
80
+
81
+ ### 4. Test Organization
82
+
83
+ ```typescript
84
+ describe('sendMessage', () => {
85
+ describe('validation', () => {
86
+ it('should not send when session is inactive');
87
+ it('should not send when message is empty');
88
+ });
89
+ describe('message creation', () => {
90
+ it('should create user message and trigger AI processing');
91
+ });
92
+ describe('error handling', () => {
93
+ it('should handle message creation errors gracefully');
94
+ });
95
+ });
96
+ ```
97
+
98
+ ## Streaming Response Mock
99
+
100
+ ```typescript
101
+ it('should handle streaming chunks', async () => {
102
+ const { result } = renderHook(() => useChatStore());
103
+
104
+ const streamSpy = vi.spyOn(chatService, 'createAssistantMessageStream')
105
+ .mockImplementation(async ({ onMessageHandle, onFinish }) => {
106
+ await onMessageHandle?.({ type: 'text', text: 'Hello' } as any);
107
+ await onMessageHandle?.({ type: 'text', text: ' World' } as any);
108
+ await onFinish?.('Hello World', {});
109
+ });
110
+
111
+ await act(async () => {
112
+ await result.current.internal_fetchAIChatMessage({...});
113
+ });
114
+
115
+ streamSpy.mockRestore();
116
+ });
117
+ ```
118
+
119
+ ## SWR Hook Testing
120
+
121
+ ```typescript
122
+ it('should fetch data', async () => {
123
+ const mockData = [{ id: '1', name: 'Item 1' }];
124
+ vi.spyOn(discoverService, 'getPluginCategories').mockResolvedValue(mockData);
125
+
126
+ const { result } = renderHook(() => useStore.getState().usePluginCategories(params));
127
+
128
+ await waitFor(() => {
129
+ expect(result.current.data).toEqual(mockData);
130
+ });
131
+ });
132
+ ```
133
+
134
+ **Key points for SWR:**
135
+ - DO NOT mock useSWR - let it use real implementation
136
+ - Only mock service methods (fetchers)
137
+ - Use `waitFor` for async operations
138
+
139
+ ## Anti-Patterns
140
+
141
+ ```typescript
142
+ // ❌ Don't mock entire store
143
+ vi.mock('../../store', () => ({ useChatStore: vi.fn(() => ({...})) }));
144
+
145
+ // ❌ Don't test internal state structure
146
+ expect(result.current.messagesMap).toHaveProperty('test-session');
147
+
148
+ // ✅ Test behavior instead
149
+ expect(result.current.refreshMessages).toHaveBeenCalled();
150
+ ```
@@ -0,0 +1,52 @@
1
+ ---
2
+ name: typescript
3
+ description: TypeScript code style and optimization guidelines. Use when writing TypeScript code (.ts, .tsx, .mts files), reviewing code quality, or implementing type-safe patterns. Triggers on TypeScript development, type safety questions, or code style discussions.
4
+ ---
5
+
6
+ # TypeScript Code Style Guide
7
+
8
+ ## Types and Type Safety
9
+
10
+ - Avoid explicit type annotations when TypeScript can infer
11
+ - Avoid implicitly `any`; explicitly type when necessary
12
+ - Use accurate types: prefer `Record<PropertyKey, unknown>` over `object` or `any`
13
+ - Prefer `interface` for object shapes (e.g., React props); use `type` for unions/intersections
14
+ - Prefer `as const satisfies XyzInterface` over plain `as const`
15
+ - Prefer `@ts-expect-error` over `@ts-ignore` over `as any`
16
+ - Avoid meaningless null/undefined parameters; design strict function contracts
17
+
18
+ ## Async Patterns
19
+
20
+ - Prefer `async`/`await` over callbacks or `.then()` chains
21
+ - Prefer async APIs over sync ones (avoid `*Sync`)
22
+ - Use promise-based variants: `import { readFile } from 'fs/promises'`
23
+ - Use `Promise.all`, `Promise.race` for concurrent operations where safe
24
+
25
+ ## Code Structure
26
+
27
+ - Prefer object destructuring
28
+ - Use consistent, descriptive naming; avoid obscure abbreviations
29
+ - Replace magic numbers/strings with well-named constants
30
+ - Defer formatting to tooling
31
+
32
+ ## UI and Theming
33
+
34
+ - Use `@lobehub/ui`, Ant Design components instead of raw HTML tags
35
+ - Design for dark mode and mobile responsiveness
36
+ - Use `antd-style` token system instead of hard-coded colors
37
+
38
+ ## Performance
39
+
40
+ - Prefer `for…of` loops over index-based `for` loops
41
+ - Reuse existing utils in `packages/utils` or installed npm packages
42
+ - Query only required columns from database
43
+
44
+ ## Time Consistency
45
+
46
+ - Assign `Date.now()` to a constant once and reuse for consistency
47
+
48
+ ## Logging
49
+
50
+ - Never log user private information (API keys, etc.)
51
+ - Don't use `import { log } from 'debug'` directly (logs to console)
52
+ - Use `console.error` in catch blocks instead of debug package