@marktoflow/gui 2.0.0-alpha.1
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/.turbo/turbo-build.log +26 -0
- package/.turbo/turbo-test.log +22 -0
- package/README.md +179 -0
- package/dist/client/assets/index-DwTI8opO.js +608 -0
- package/dist/client/assets/index-DwTI8opO.js.map +1 -0
- package/dist/client/assets/index-RoEdL6gO.css +1 -0
- package/dist/client/index.html +20 -0
- package/dist/client/vite.svg +9 -0
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +56 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/routes/ai.js +50 -0
- package/dist/server/routes/ai.js.map +1 -0
- package/dist/server/routes/execute.js +62 -0
- package/dist/server/routes/execute.js.map +1 -0
- package/dist/server/routes/workflows.js +99 -0
- package/dist/server/routes/workflows.js.map +1 -0
- package/dist/server/server/index.js +95 -0
- package/dist/server/server/index.js.map +1 -0
- package/dist/server/server/routes/ai.js +87 -0
- package/dist/server/server/routes/ai.js.map +1 -0
- package/dist/server/server/routes/execute.js +63 -0
- package/dist/server/server/routes/execute.js.map +1 -0
- package/dist/server/server/routes/tools.js +518 -0
- package/dist/server/server/routes/tools.js.map +1 -0
- package/dist/server/server/routes/workflows.js +99 -0
- package/dist/server/server/routes/workflows.js.map +1 -0
- package/dist/server/server/services/AIService.js +69 -0
- package/dist/server/server/services/AIService.js.map +1 -0
- package/dist/server/server/services/FileWatcher.js +60 -0
- package/dist/server/server/services/FileWatcher.js.map +1 -0
- package/dist/server/server/services/WorkflowService.js +363 -0
- package/dist/server/server/services/WorkflowService.js.map +1 -0
- package/dist/server/server/services/agents/claude-code-provider.js +250 -0
- package/dist/server/server/services/agents/claude-code-provider.js.map +1 -0
- package/dist/server/server/services/agents/claude-provider.js +204 -0
- package/dist/server/server/services/agents/claude-provider.js.map +1 -0
- package/dist/server/server/services/agents/copilot-provider.js +227 -0
- package/dist/server/server/services/agents/copilot-provider.js.map +1 -0
- package/dist/server/server/services/agents/demo-provider.js +167 -0
- package/dist/server/server/services/agents/demo-provider.js.map +1 -0
- package/dist/server/server/services/agents/index.js +31 -0
- package/dist/server/server/services/agents/index.js.map +1 -0
- package/dist/server/server/services/agents/ollama-provider.js +220 -0
- package/dist/server/server/services/agents/ollama-provider.js.map +1 -0
- package/dist/server/server/services/agents/prompts.js +436 -0
- package/dist/server/server/services/agents/prompts.js.map +1 -0
- package/dist/server/server/services/agents/registry.js +242 -0
- package/dist/server/server/services/agents/registry.js.map +1 -0
- package/dist/server/server/services/agents/types.js +6 -0
- package/dist/server/server/services/agents/types.js.map +1 -0
- package/dist/server/server/websocket/index.js +85 -0
- package/dist/server/server/websocket/index.js.map +1 -0
- package/dist/server/services/AIService.d.ts +30 -0
- package/dist/server/services/AIService.d.ts.map +1 -0
- package/dist/server/services/AIService.js +216 -0
- package/dist/server/services/AIService.js.map +1 -0
- package/dist/server/services/FileWatcher.d.ts +10 -0
- package/dist/server/services/FileWatcher.d.ts.map +1 -0
- package/dist/server/services/FileWatcher.js +62 -0
- package/dist/server/services/FileWatcher.js.map +1 -0
- package/dist/server/services/WorkflowService.d.ts +54 -0
- package/dist/server/services/WorkflowService.d.ts.map +1 -0
- package/dist/server/services/WorkflowService.js +323 -0
- package/dist/server/services/WorkflowService.js.map +1 -0
- package/dist/server/shared/constants.js +175 -0
- package/dist/server/shared/constants.js.map +1 -0
- package/dist/server/shared/types.js +3 -0
- package/dist/server/shared/types.js.map +1 -0
- package/dist/server/websocket/index.d.ts +10 -0
- package/dist/server/websocket/index.d.ts.map +1 -0
- package/dist/server/websocket/index.js +85 -0
- package/dist/server/websocket/index.js.map +1 -0
- package/index.html +19 -0
- package/package.json +96 -0
- package/playwright.config.ts +27 -0
- package/postcss.config.js +6 -0
- package/public/vite.svg +9 -0
- package/src/client/App.tsx +520 -0
- package/src/client/components/Canvas/Canvas.tsx +405 -0
- package/src/client/components/Canvas/ExecutionOverlay.tsx +847 -0
- package/src/client/components/Canvas/NodeContextMenu.tsx +188 -0
- package/src/client/components/Canvas/OutputNode.tsx +111 -0
- package/src/client/components/Canvas/StepNode.tsx +106 -0
- package/src/client/components/Canvas/SubWorkflowNode.tsx +141 -0
- package/src/client/components/Canvas/Toolbar.tsx +189 -0
- package/src/client/components/Canvas/TriggerNode.tsx +128 -0
- package/src/client/components/Editor/InputsEditor.tsx +458 -0
- package/src/client/components/Editor/NewStepWizard.tsx +344 -0
- package/src/client/components/Editor/StepEditor.tsx +532 -0
- package/src/client/components/Editor/YamlEditor.tsx +160 -0
- package/src/client/components/Panels/PropertiesPanel.tsx +589 -0
- package/src/client/components/Prompt/ChangePreview.tsx +281 -0
- package/src/client/components/Prompt/PromptHistoryPanel.tsx +209 -0
- package/src/client/components/Prompt/PromptInput.tsx +108 -0
- package/src/client/components/Sidebar/Sidebar.tsx +343 -0
- package/src/client/components/common/Breadcrumb.tsx +40 -0
- package/src/client/components/common/Button.tsx +68 -0
- package/src/client/components/common/ContextMenu.tsx +202 -0
- package/src/client/components/common/KeyboardShortcuts.tsx +143 -0
- package/src/client/components/common/Modal.tsx +93 -0
- package/src/client/components/common/Tabs.tsx +57 -0
- package/src/client/components/common/ThemeToggle.tsx +63 -0
- package/src/client/components/index.ts +32 -0
- package/src/client/hooks/index.ts +4 -0
- package/src/client/hooks/useAIPrompt.ts +108 -0
- package/src/client/hooks/useCanvas.ts +247 -0
- package/src/client/hooks/useWebSocket.ts +164 -0
- package/src/client/hooks/useWorkflow.ts +138 -0
- package/src/client/main.tsx +10 -0
- package/src/client/stores/canvasStore.ts +348 -0
- package/src/client/stores/editorStore.ts +133 -0
- package/src/client/stores/executionStore.ts +440 -0
- package/src/client/stores/index.ts +4 -0
- package/src/client/stores/layoutStore.ts +103 -0
- package/src/client/stores/navigationStore.ts +49 -0
- package/src/client/stores/promptStore.ts +113 -0
- package/src/client/stores/themeStore.ts +75 -0
- package/src/client/stores/workflowStore.ts +177 -0
- package/src/client/styles/globals.css +346 -0
- package/src/client/utils/cn.ts +9 -0
- package/src/client/utils/index.ts +4 -0
- package/src/client/utils/serviceIcons.tsx +64 -0
- package/src/client/utils/stepValidation.ts +155 -0
- package/src/client/utils/workflowToGraph.ts +299 -0
- package/src/server/index.ts +114 -0
- package/src/server/routes/ai.ts +91 -0
- package/src/server/routes/execute.ts +71 -0
- package/src/server/routes/tools.ts +564 -0
- package/src/server/routes/workflows.ts +106 -0
- package/src/server/services/AIService.ts +105 -0
- package/src/server/services/FileWatcher.ts +69 -0
- package/src/server/services/WorkflowService.ts +441 -0
- package/src/server/services/agents/claude-code-provider.ts +320 -0
- package/src/server/services/agents/claude-provider.ts +248 -0
- package/src/server/services/agents/copilot-provider.ts +311 -0
- package/src/server/services/agents/demo-provider.ts +184 -0
- package/src/server/services/agents/index.ts +31 -0
- package/src/server/services/agents/ollama-provider.ts +267 -0
- package/src/server/services/agents/prompts.ts +482 -0
- package/src/server/services/agents/registry.ts +289 -0
- package/src/server/services/agents/types.ts +146 -0
- package/src/server/websocket/index.ts +104 -0
- package/src/shared/constants.ts +180 -0
- package/src/shared/types.ts +179 -0
- package/tailwind.config.ts +73 -0
- package/tests/e2e/app.spec.ts +90 -0
- package/tests/e2e/canvas.spec.ts +128 -0
- package/tests/e2e/workflow.spec.ts +185 -0
- package/tests/integration/api.test.ts +250 -0
- package/tests/integration/testApp.ts +31 -0
- package/tests/setup.ts +37 -0
- package/tests/unit/canvasStore.test.ts +502 -0
- package/tests/unit/components.test.tsx +151 -0
- package/tests/unit/executionStore.test.ts +527 -0
- package/tests/unit/layoutStore.test.ts +194 -0
- package/tests/unit/navigationStore.test.ts +152 -0
- package/tests/unit/stepValidation.test.ts +226 -0
- package/tests/unit/themeStore.test.ts +141 -0
- package/tests/unit/workflowToGraph.test.ts +289 -0
- package/tsconfig.json +29 -0
- package/tsconfig.server.json +28 -0
- package/vite.config.ts +31 -0
- package/vitest.config.ts +26 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import request from 'supertest';
|
|
3
|
+
import { createTestApp } from './testApp.js';
|
|
4
|
+
|
|
5
|
+
const app = createTestApp();
|
|
6
|
+
|
|
7
|
+
describe('API Integration Tests', () => {
|
|
8
|
+
describe('GET /api/health', () => {
|
|
9
|
+
it('should return health status', async () => {
|
|
10
|
+
const response = await request(app).get('/api/health');
|
|
11
|
+
|
|
12
|
+
expect(response.status).toBe(200);
|
|
13
|
+
expect(response.body).toEqual({
|
|
14
|
+
status: 'ok',
|
|
15
|
+
version: '2.0.0-alpha.1',
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('Tools API', () => {
|
|
21
|
+
describe('GET /api/tools', () => {
|
|
22
|
+
it('should return list of available tools', async () => {
|
|
23
|
+
const response = await request(app).get('/api/tools');
|
|
24
|
+
|
|
25
|
+
expect(response.status).toBe(200);
|
|
26
|
+
expect(response.body).toHaveProperty('tools');
|
|
27
|
+
expect(Array.isArray(response.body.tools)).toBe(true);
|
|
28
|
+
expect(response.body.tools.length).toBeGreaterThan(0);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should include expected tool properties', async () => {
|
|
32
|
+
const response = await request(app).get('/api/tools');
|
|
33
|
+
const tool = response.body.tools[0];
|
|
34
|
+
|
|
35
|
+
expect(tool).toHaveProperty('id');
|
|
36
|
+
expect(tool).toHaveProperty('name');
|
|
37
|
+
expect(tool).toHaveProperty('icon');
|
|
38
|
+
expect(tool).toHaveProperty('category');
|
|
39
|
+
expect(tool).toHaveProperty('description');
|
|
40
|
+
expect(tool).toHaveProperty('actionCount');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should include slack tool', async () => {
|
|
44
|
+
const response = await request(app).get('/api/tools');
|
|
45
|
+
const slackTool = response.body.tools.find((t: { id: string }) => t.id === 'slack');
|
|
46
|
+
|
|
47
|
+
expect(slackTool).toBeDefined();
|
|
48
|
+
expect(slackTool.name).toBe('Slack');
|
|
49
|
+
expect(slackTool.sdk).toBe('@slack/web-api');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should include github tool', async () => {
|
|
53
|
+
const response = await request(app).get('/api/tools');
|
|
54
|
+
const githubTool = response.body.tools.find((t: { id: string }) => t.id === 'github');
|
|
55
|
+
|
|
56
|
+
expect(githubTool).toBeDefined();
|
|
57
|
+
expect(githubTool.name).toBe('GitHub');
|
|
58
|
+
expect(githubTool.sdk).toBe('@octokit/rest');
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('GET /api/tools/:toolId', () => {
|
|
63
|
+
it('should return tool details', async () => {
|
|
64
|
+
const response = await request(app).get('/api/tools/slack');
|
|
65
|
+
|
|
66
|
+
expect(response.status).toBe(200);
|
|
67
|
+
expect(response.body).toHaveProperty('tool');
|
|
68
|
+
expect(response.body.tool.id).toBe('slack');
|
|
69
|
+
expect(response.body.tool.actions).toBeDefined();
|
|
70
|
+
expect(Array.isArray(response.body.tool.actions)).toBe(true);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should include action details', async () => {
|
|
74
|
+
const response = await request(app).get('/api/tools/slack');
|
|
75
|
+
const actions = response.body.tool.actions;
|
|
76
|
+
|
|
77
|
+
expect(actions.length).toBeGreaterThan(0);
|
|
78
|
+
|
|
79
|
+
const postMessage = actions.find((a: { id: string }) => a.id === 'chat.postMessage');
|
|
80
|
+
expect(postMessage).toBeDefined();
|
|
81
|
+
expect(postMessage.name).toBe('Post Message');
|
|
82
|
+
expect(postMessage.inputs).toBeDefined();
|
|
83
|
+
expect(postMessage.output).toBeDefined();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should return 404 for unknown tool', async () => {
|
|
87
|
+
const response = await request(app).get('/api/tools/unknown-tool');
|
|
88
|
+
|
|
89
|
+
expect(response.status).toBe(404);
|
|
90
|
+
expect(response.body.error).toBe('Tool not found');
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('GET /api/tools/:toolId/actions/:actionId', () => {
|
|
95
|
+
it('should return action details', async () => {
|
|
96
|
+
const response = await request(app).get('/api/tools/github/actions/pulls.get');
|
|
97
|
+
|
|
98
|
+
expect(response.status).toBe(200);
|
|
99
|
+
expect(response.body).toHaveProperty('action');
|
|
100
|
+
expect(response.body.action.id).toBe('pulls.get');
|
|
101
|
+
expect(response.body.action.name).toBe('Get Pull Request');
|
|
102
|
+
expect(response.body.action.inputs).toBeDefined();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should include tool info in response', async () => {
|
|
106
|
+
const response = await request(app).get('/api/tools/github/actions/pulls.get');
|
|
107
|
+
|
|
108
|
+
expect(response.body).toHaveProperty('tool');
|
|
109
|
+
expect(response.body.tool.id).toBe('github');
|
|
110
|
+
expect(response.body.tool.name).toBe('GitHub');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should return 404 for unknown action', async () => {
|
|
114
|
+
const response = await request(app).get('/api/tools/slack/actions/unknown-action');
|
|
115
|
+
|
|
116
|
+
expect(response.status).toBe(404);
|
|
117
|
+
expect(response.body.error).toBe('Action not found');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should return 404 for unknown tool', async () => {
|
|
121
|
+
const response = await request(app).get('/api/tools/unknown/actions/any');
|
|
122
|
+
|
|
123
|
+
expect(response.status).toBe(404);
|
|
124
|
+
expect(response.body.error).toBe('Tool not found');
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe('Execute API', () => {
|
|
130
|
+
describe('POST /api/execute/:path', () => {
|
|
131
|
+
it('should start workflow execution', async () => {
|
|
132
|
+
const response = await request(app)
|
|
133
|
+
.post('/api/execute/test-workflow.md')
|
|
134
|
+
.send({ inputs: { foo: 'bar' } });
|
|
135
|
+
|
|
136
|
+
expect(response.status).toBe(200);
|
|
137
|
+
expect(response.body).toHaveProperty('runId');
|
|
138
|
+
expect(response.body).toHaveProperty('status', 'started');
|
|
139
|
+
// The workflowPath is decoded from URL params
|
|
140
|
+
expect(response.body.workflowPath).toBeDefined();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should support dry run mode', async () => {
|
|
144
|
+
const response = await request(app)
|
|
145
|
+
.post('/api/execute/test-workflow.md')
|
|
146
|
+
.send({ inputs: {}, dryRun: true });
|
|
147
|
+
|
|
148
|
+
expect(response.status).toBe(200);
|
|
149
|
+
expect(response.body.dryRun).toBe(true);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe('GET /api/execute/status/:runId', () => {
|
|
154
|
+
it('should return execution status', async () => {
|
|
155
|
+
const response = await request(app).get('/api/execute/status/run-123');
|
|
156
|
+
|
|
157
|
+
expect(response.status).toBe(200);
|
|
158
|
+
expect(response.body).toHaveProperty('runId', 'run-123');
|
|
159
|
+
expect(response.body).toHaveProperty('status');
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe('POST /api/execute/cancel/:runId', () => {
|
|
164
|
+
it('should cancel execution', async () => {
|
|
165
|
+
const response = await request(app).post('/api/execute/cancel/run-123');
|
|
166
|
+
|
|
167
|
+
expect(response.status).toBe(200);
|
|
168
|
+
expect(response.body).toHaveProperty('runId', 'run-123');
|
|
169
|
+
expect(response.body).toHaveProperty('status', 'cancelled');
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe('Workflows API', () => {
|
|
175
|
+
describe('GET /api/workflows', () => {
|
|
176
|
+
it('should return list of workflows', async () => {
|
|
177
|
+
const response = await request(app).get('/api/workflows');
|
|
178
|
+
|
|
179
|
+
expect(response.status).toBe(200);
|
|
180
|
+
expect(response.body).toHaveProperty('workflows');
|
|
181
|
+
expect(Array.isArray(response.body.workflows)).toBe(true);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
describe('POST /api/workflows', () => {
|
|
186
|
+
it('should require name parameter', async () => {
|
|
187
|
+
const response = await request(app)
|
|
188
|
+
.post('/api/workflows')
|
|
189
|
+
.send({});
|
|
190
|
+
|
|
191
|
+
expect(response.status).toBe(400);
|
|
192
|
+
expect(response.body.error).toBe('Name is required');
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
describe('PUT /api/workflows/:path', () => {
|
|
197
|
+
it('should require workflow data', async () => {
|
|
198
|
+
const response = await request(app)
|
|
199
|
+
.put('/api/workflows/test.md')
|
|
200
|
+
.send({});
|
|
201
|
+
|
|
202
|
+
expect(response.status).toBe(400);
|
|
203
|
+
expect(response.body.error).toBe('Workflow data is required');
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
describe('AI API', () => {
|
|
209
|
+
describe('POST /api/ai/prompt', () => {
|
|
210
|
+
it('should require prompt parameter', async () => {
|
|
211
|
+
const response = await request(app)
|
|
212
|
+
.post('/api/ai/prompt')
|
|
213
|
+
.send({});
|
|
214
|
+
|
|
215
|
+
expect(response.status).toBe(400);
|
|
216
|
+
expect(response.body.error).toBe('Prompt is required');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('should handle missing workflow gracefully', async () => {
|
|
220
|
+
const response = await request(app)
|
|
221
|
+
.post('/api/ai/prompt')
|
|
222
|
+
.send({ prompt: 'Add a step' });
|
|
223
|
+
|
|
224
|
+
// Service handles missing workflow, may return error
|
|
225
|
+
expect([200, 500]).toContain(response.status);
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
describe('GET /api/ai/history', () => {
|
|
230
|
+
it('should return prompt history', async () => {
|
|
231
|
+
const response = await request(app).get('/api/ai/history');
|
|
232
|
+
|
|
233
|
+
expect(response.status).toBe(200);
|
|
234
|
+
expect(response.body).toHaveProperty('history');
|
|
235
|
+
expect(Array.isArray(response.body.history)).toBe(true);
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
describe('POST /api/ai/suggestions', () => {
|
|
240
|
+
it('should return suggestions for workflow', async () => {
|
|
241
|
+
const response = await request(app)
|
|
242
|
+
.post('/api/ai/suggestions')
|
|
243
|
+
.send({ workflow: { steps: [] } });
|
|
244
|
+
|
|
245
|
+
expect(response.status).toBe(200);
|
|
246
|
+
expect(response.body).toHaveProperty('suggestions');
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import express, { type Express } from 'express';
|
|
2
|
+
import cors from 'cors';
|
|
3
|
+
import { workflowRoutes } from '../../src/server/routes/workflows.js';
|
|
4
|
+
import { aiRoutes } from '../../src/server/routes/ai.js';
|
|
5
|
+
import { executeRoutes } from '../../src/server/routes/execute.js';
|
|
6
|
+
import { toolsRoutes } from '../../src/server/routes/tools.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Create a test Express app with all routes configured
|
|
10
|
+
* This allows testing the API without starting an actual server
|
|
11
|
+
*/
|
|
12
|
+
export function createTestApp(): Express {
|
|
13
|
+
const app = express();
|
|
14
|
+
|
|
15
|
+
// Middleware
|
|
16
|
+
app.use(cors());
|
|
17
|
+
app.use(express.json());
|
|
18
|
+
|
|
19
|
+
// Routes
|
|
20
|
+
app.use('/api/workflows', workflowRoutes);
|
|
21
|
+
app.use('/api/ai', aiRoutes);
|
|
22
|
+
app.use('/api/execute', executeRoutes);
|
|
23
|
+
app.use('/api/tools', toolsRoutes);
|
|
24
|
+
|
|
25
|
+
// Health check
|
|
26
|
+
app.get('/api/health', (_req, res) => {
|
|
27
|
+
res.json({ status: 'ok', version: '2.0.0-alpha.1' });
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return app;
|
|
31
|
+
}
|
package/tests/setup.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import '@testing-library/jest-dom';
|
|
2
|
+
import { vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
// Mock window.matchMedia
|
|
5
|
+
Object.defineProperty(window, 'matchMedia', {
|
|
6
|
+
writable: true,
|
|
7
|
+
value: vi.fn().mockImplementation((query: string) => ({
|
|
8
|
+
matches: false,
|
|
9
|
+
media: query,
|
|
10
|
+
onchange: null,
|
|
11
|
+
addListener: vi.fn(),
|
|
12
|
+
removeListener: vi.fn(),
|
|
13
|
+
addEventListener: vi.fn(),
|
|
14
|
+
removeEventListener: vi.fn(),
|
|
15
|
+
dispatchEvent: vi.fn(),
|
|
16
|
+
})),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// Mock ResizeObserver
|
|
20
|
+
global.ResizeObserver = vi.fn().mockImplementation(() => ({
|
|
21
|
+
observe: vi.fn(),
|
|
22
|
+
unobserve: vi.fn(),
|
|
23
|
+
disconnect: vi.fn(),
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
// Mock IntersectionObserver
|
|
27
|
+
global.IntersectionObserver = vi.fn().mockImplementation(() => ({
|
|
28
|
+
observe: vi.fn(),
|
|
29
|
+
unobserve: vi.fn(),
|
|
30
|
+
disconnect: vi.fn(),
|
|
31
|
+
root: null,
|
|
32
|
+
rootMargin: '',
|
|
33
|
+
thresholds: [],
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
// Suppress console errors during tests (optional)
|
|
37
|
+
vi.spyOn(console, 'error').mockImplementation(() => {});
|