@lobehub/lobehub 2.0.0-next.142 → 2.0.0-next.144
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/CHANGELOG.md +50 -0
- package/apps/desktop/package.json +1 -0
- package/apps/desktop/src/main/core/ui/__tests__/MenuManager.test.ts +320 -0
- package/apps/desktop/src/main/core/ui/__tests__/Tray.test.ts +518 -0
- package/apps/desktop/src/main/core/ui/__tests__/TrayManager.test.ts +360 -0
- package/apps/desktop/src/main/menus/impls/BaseMenuPlatform.test.ts +49 -0
- package/apps/desktop/src/main/menus/impls/linux.test.ts +552 -0
- package/apps/desktop/src/main/menus/impls/macOS.test.ts +464 -0
- package/apps/desktop/src/main/menus/impls/windows.test.ts +429 -0
- package/apps/desktop/src/main/modules/fileSearch/__tests__/macOS.integration.test.ts +2 -2
- package/apps/desktop/src/main/services/__tests__/fileSearchSrv.test.ts +402 -0
- package/apps/desktop/src/main/utils/__tests__/file-system.test.ts +91 -0
- package/apps/desktop/src/main/utils/__tests__/logger.test.ts +229 -0
- package/apps/desktop/src/preload/electronApi.test.ts +142 -0
- package/apps/desktop/src/preload/invoke.test.ts +145 -0
- package/apps/desktop/src/preload/routeInterceptor.test.ts +374 -0
- package/apps/desktop/src/preload/streamer.test.ts +365 -0
- package/apps/desktop/vitest.config.mts +1 -0
- package/changelog/v1.json +18 -0
- package/locales/ar/marketAuth.json +13 -0
- package/locales/bg-BG/marketAuth.json +13 -0
- package/locales/de-DE/marketAuth.json +13 -0
- package/locales/en-US/marketAuth.json +13 -0
- package/locales/es-ES/marketAuth.json +13 -0
- package/locales/fa-IR/marketAuth.json +13 -0
- package/locales/fr-FR/marketAuth.json +13 -0
- package/locales/it-IT/marketAuth.json +13 -0
- package/locales/ja-JP/marketAuth.json +13 -0
- package/locales/ko-KR/marketAuth.json +13 -0
- package/locales/nl-NL/marketAuth.json +13 -0
- package/locales/pl-PL/marketAuth.json +13 -0
- package/locales/pt-BR/marketAuth.json +13 -0
- package/locales/ru-RU/marketAuth.json +13 -0
- package/locales/tr-TR/marketAuth.json +13 -0
- package/locales/vi-VN/marketAuth.json +13 -0
- package/locales/zh-CN/marketAuth.json +13 -0
- package/locales/zh-TW/marketAuth.json +13 -0
- package/package.json +1 -1
- package/packages/database/migrations/0054_better_auth_two_factor.sql +2 -0
- package/packages/database/src/core/migrations.json +1 -1
- package/packages/database/src/models/user.ts +27 -5
- package/packages/types/src/discover/mcp.ts +2 -1
- package/packages/types/src/tool/plugin.ts +2 -1
- package/scripts/migrateServerDB/errorHint.js +26 -0
- package/scripts/migrateServerDB/index.ts +5 -1
- package/src/app/[variants]/(main)/chat/settings/features/SmartAgentActionButton/MarketPublishButton.tsx +0 -2
- package/src/app/[variants]/(main)/discover/(detail)/mcp/features/Sidebar/ActionButton/index.tsx +33 -7
- package/src/features/PluginStore/McpList/List/Action.tsx +20 -1
- package/src/layout/AuthProvider/MarketAuth/MarketAuthConfirmModal.tsx +158 -0
- package/src/layout/AuthProvider/MarketAuth/MarketAuthProvider.tsx +130 -14
- package/src/libs/mcp/types.ts +8 -0
- package/src/locales/default/marketAuth.ts +13 -0
- package/src/server/routers/lambda/market/index.ts +85 -2
- package/src/server/services/discover/index.ts +45 -4
- package/src/services/discover.ts +1 -1
- package/src/services/mcp.ts +18 -3
- package/src/store/tool/slices/mcpStore/action.test.ts +141 -0
- package/src/store/tool/slices/mcpStore/action.ts +153 -11
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vitest-environment happy-dom
|
|
3
|
+
*/
|
|
4
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
+
|
|
6
|
+
import { invoke } from './invoke';
|
|
7
|
+
|
|
8
|
+
// Mock dependencies
|
|
9
|
+
vi.mock('./invoke', () => ({
|
|
10
|
+
invoke: vi.fn(),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
vi.mock('~common/routes', () => ({
|
|
14
|
+
findMatchingRoute: vi.fn(),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
const { findMatchingRoute } = await import('~common/routes');
|
|
18
|
+
const { setupRouteInterceptors } = await import('./routeInterceptor');
|
|
19
|
+
|
|
20
|
+
describe('setupRouteInterceptors', () => {
|
|
21
|
+
let consoleLogSpy: ReturnType<typeof vi.spyOn>;
|
|
22
|
+
let consoleErrorSpy: ReturnType<typeof vi.spyOn>;
|
|
23
|
+
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
vi.clearAllMocks();
|
|
26
|
+
|
|
27
|
+
// Mock console methods
|
|
28
|
+
consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
29
|
+
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
30
|
+
|
|
31
|
+
// Setup happy-dom window and document
|
|
32
|
+
vi.stubGlobal('location', {
|
|
33
|
+
href: 'http://localhost:3000/chat',
|
|
34
|
+
origin: 'http://localhost:3000',
|
|
35
|
+
pathname: '/chat',
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Clear existing event listeners by resetting document
|
|
39
|
+
document.body.innerHTML = '';
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('window.open interception', () => {
|
|
43
|
+
it('should intercept external URL and invoke openExternalLink', () => {
|
|
44
|
+
setupRouteInterceptors();
|
|
45
|
+
|
|
46
|
+
const externalUrl = 'https://google.com';
|
|
47
|
+
const result = window.open(externalUrl, '_blank');
|
|
48
|
+
|
|
49
|
+
expect(invoke).toHaveBeenCalledWith('openExternalLink', externalUrl);
|
|
50
|
+
expect(result).toBeNull();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should intercept URL object for external link', () => {
|
|
54
|
+
setupRouteInterceptors();
|
|
55
|
+
|
|
56
|
+
const externalUrl = new URL('https://github.com');
|
|
57
|
+
const result = window.open(externalUrl, '_blank');
|
|
58
|
+
|
|
59
|
+
expect(invoke).toHaveBeenCalledWith('openExternalLink', 'https://github.com/');
|
|
60
|
+
expect(result).toBeNull();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should allow internal link to proceed with original window.open', () => {
|
|
64
|
+
setupRouteInterceptors();
|
|
65
|
+
|
|
66
|
+
const originalWindowOpen = window.open;
|
|
67
|
+
const internalUrl = 'http://localhost:3000/settings';
|
|
68
|
+
|
|
69
|
+
// We can't fully test the original behavior in happy-dom, but we can verify invoke is not called
|
|
70
|
+
window.open(internalUrl);
|
|
71
|
+
|
|
72
|
+
expect(invoke).not.toHaveBeenCalledWith('openExternalLink', expect.anything());
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should handle relative URL that resolves as internal link', () => {
|
|
76
|
+
setupRouteInterceptors();
|
|
77
|
+
|
|
78
|
+
// In happy-dom, 'invalid-url' is resolved relative to window.location.href
|
|
79
|
+
// So it becomes 'http://localhost:3000/invalid-url' which is internal
|
|
80
|
+
const relativeUrl = 'invalid-url';
|
|
81
|
+
window.open(relativeUrl);
|
|
82
|
+
|
|
83
|
+
// Since it's internal, it won't call invoke for external link
|
|
84
|
+
expect(invoke).not.toHaveBeenCalledWith('openExternalLink', expect.anything());
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('link click interception', () => {
|
|
89
|
+
it('should intercept external link clicks', async () => {
|
|
90
|
+
setupRouteInterceptors();
|
|
91
|
+
|
|
92
|
+
const link = document.createElement('a');
|
|
93
|
+
link.href = 'https://example.com';
|
|
94
|
+
document.body.append(link);
|
|
95
|
+
|
|
96
|
+
const clickEvent = new MouseEvent('click', { bubbles: true, cancelable: true });
|
|
97
|
+
const preventDefaultSpy = vi.spyOn(clickEvent, 'preventDefault');
|
|
98
|
+
const stopPropagationSpy = vi.spyOn(clickEvent, 'stopPropagation');
|
|
99
|
+
|
|
100
|
+
link.dispatchEvent(clickEvent);
|
|
101
|
+
|
|
102
|
+
// Wait for async handling
|
|
103
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
104
|
+
|
|
105
|
+
expect(invoke).toHaveBeenCalledWith('openExternalLink', 'https://example.com/');
|
|
106
|
+
expect(preventDefaultSpy).toHaveBeenCalled();
|
|
107
|
+
expect(stopPropagationSpy).toHaveBeenCalled();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should intercept internal link matching route pattern', async () => {
|
|
111
|
+
setupRouteInterceptors();
|
|
112
|
+
|
|
113
|
+
const matchedRoute = { pathPrefix: '/desktop/devtools', targetWindow: 'devtools' };
|
|
114
|
+
vi.mocked(findMatchingRoute).mockReturnValue(matchedRoute);
|
|
115
|
+
|
|
116
|
+
const link = document.createElement('a');
|
|
117
|
+
link.href = 'http://localhost:3000/desktop/devtools';
|
|
118
|
+
document.body.append(link);
|
|
119
|
+
|
|
120
|
+
const clickEvent = new MouseEvent('click', { bubbles: true, cancelable: true });
|
|
121
|
+
const preventDefaultSpy = vi.spyOn(clickEvent, 'preventDefault');
|
|
122
|
+
|
|
123
|
+
link.dispatchEvent(clickEvent);
|
|
124
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
125
|
+
|
|
126
|
+
expect(findMatchingRoute).toHaveBeenCalledWith('/desktop/devtools');
|
|
127
|
+
expect(invoke).toHaveBeenCalledWith('interceptRoute', {
|
|
128
|
+
path: '/desktop/devtools',
|
|
129
|
+
source: 'link-click',
|
|
130
|
+
url: 'http://localhost:3000/desktop/devtools',
|
|
131
|
+
});
|
|
132
|
+
expect(preventDefaultSpy).toHaveBeenCalled();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should not intercept if already on target page', async () => {
|
|
136
|
+
setupRouteInterceptors();
|
|
137
|
+
|
|
138
|
+
// Set current location to be in the target page
|
|
139
|
+
vi.stubGlobal('location', {
|
|
140
|
+
href: 'http://localhost:3000/desktop/devtools/console',
|
|
141
|
+
origin: 'http://localhost:3000',
|
|
142
|
+
pathname: '/desktop/devtools/console',
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const matchedRoute = { pathPrefix: '/desktop/devtools', targetWindow: 'devtools' };
|
|
146
|
+
vi.mocked(findMatchingRoute).mockReturnValue(matchedRoute);
|
|
147
|
+
|
|
148
|
+
const link = document.createElement('a');
|
|
149
|
+
link.href = 'http://localhost:3000/desktop/devtools/network';
|
|
150
|
+
document.body.append(link);
|
|
151
|
+
|
|
152
|
+
const clickEvent = new MouseEvent('click', { bubbles: true, cancelable: true });
|
|
153
|
+
const preventDefaultSpy = vi.spyOn(clickEvent, 'preventDefault');
|
|
154
|
+
|
|
155
|
+
link.dispatchEvent(clickEvent);
|
|
156
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
157
|
+
|
|
158
|
+
expect(preventDefaultSpy).not.toHaveBeenCalled();
|
|
159
|
+
expect(invoke).not.toHaveBeenCalledWith('interceptRoute', expect.anything());
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('should handle non-HTTP link protocols as external links', async () => {
|
|
163
|
+
setupRouteInterceptors();
|
|
164
|
+
|
|
165
|
+
const link = document.createElement('a');
|
|
166
|
+
link.href = 'mailto:test@example.com';
|
|
167
|
+
document.body.append(link);
|
|
168
|
+
|
|
169
|
+
const clickEvent = new MouseEvent('click', { bubbles: true, cancelable: true });
|
|
170
|
+
const preventDefaultSpy = vi.spyOn(clickEvent, 'preventDefault');
|
|
171
|
+
|
|
172
|
+
link.dispatchEvent(clickEvent);
|
|
173
|
+
|
|
174
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
175
|
+
|
|
176
|
+
// mailto: links are treated as external links by the URL constructor
|
|
177
|
+
expect(invoke).toHaveBeenCalledWith('openExternalLink', 'mailto:test@example.com');
|
|
178
|
+
expect(preventDefaultSpy).toHaveBeenCalled();
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe('history.pushState interception', () => {
|
|
183
|
+
it('should intercept pushState for matched routes', () => {
|
|
184
|
+
setupRouteInterceptors();
|
|
185
|
+
|
|
186
|
+
const matchedRoute = { pathPrefix: '/desktop/devtools', targetWindow: 'devtools' };
|
|
187
|
+
vi.mocked(findMatchingRoute).mockReturnValue(matchedRoute);
|
|
188
|
+
|
|
189
|
+
const originalLength = history.length;
|
|
190
|
+
history.pushState({}, '', '/desktop/devtools');
|
|
191
|
+
|
|
192
|
+
expect(findMatchingRoute).toHaveBeenCalledWith('/desktop/devtools');
|
|
193
|
+
expect(invoke).toHaveBeenCalledWith('interceptRoute', {
|
|
194
|
+
path: '/desktop/devtools',
|
|
195
|
+
source: 'push-state',
|
|
196
|
+
url: 'http://localhost:3000/desktop/devtools',
|
|
197
|
+
});
|
|
198
|
+
// Ensure navigation was prevented
|
|
199
|
+
expect(history.length).toBe(originalLength);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('should not intercept if already on target page', () => {
|
|
203
|
+
setupRouteInterceptors();
|
|
204
|
+
|
|
205
|
+
vi.stubGlobal('location', {
|
|
206
|
+
href: 'http://localhost:3000/desktop/devtools/console',
|
|
207
|
+
origin: 'http://localhost:3000',
|
|
208
|
+
pathname: '/desktop/devtools/console',
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const matchedRoute = { pathPrefix: '/desktop/devtools', targetWindow: 'devtools' };
|
|
212
|
+
vi.mocked(findMatchingRoute).mockReturnValue(matchedRoute);
|
|
213
|
+
|
|
214
|
+
history.pushState({}, '', '/desktop/devtools/network');
|
|
215
|
+
|
|
216
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
217
|
+
expect.stringContaining('Skip pushState interception'),
|
|
218
|
+
);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('should allow pushState for non-matched routes', () => {
|
|
222
|
+
setupRouteInterceptors();
|
|
223
|
+
|
|
224
|
+
vi.mocked(findMatchingRoute).mockReturnValue(undefined);
|
|
225
|
+
|
|
226
|
+
history.pushState({}, '', '/chat/new');
|
|
227
|
+
|
|
228
|
+
expect(invoke).not.toHaveBeenCalledWith('interceptRoute', expect.anything());
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('should handle pushState errors gracefully', () => {
|
|
232
|
+
setupRouteInterceptors();
|
|
233
|
+
|
|
234
|
+
vi.mocked(findMatchingRoute).mockImplementation(() => {
|
|
235
|
+
throw new Error('Route matching error');
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
history.pushState({}, '', '/some/path');
|
|
239
|
+
|
|
240
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
241
|
+
expect.stringContaining('pushState interception error'),
|
|
242
|
+
expect.any(Error),
|
|
243
|
+
);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
describe('history.replaceState interception', () => {
|
|
248
|
+
it('should intercept replaceState for matched routes', () => {
|
|
249
|
+
setupRouteInterceptors();
|
|
250
|
+
|
|
251
|
+
const matchedRoute = { pathPrefix: '/desktop/devtools', targetWindow: 'devtools' };
|
|
252
|
+
vi.mocked(findMatchingRoute).mockReturnValue(matchedRoute);
|
|
253
|
+
|
|
254
|
+
history.replaceState({}, '', '/desktop/devtools');
|
|
255
|
+
|
|
256
|
+
expect(findMatchingRoute).toHaveBeenCalledWith('/desktop/devtools');
|
|
257
|
+
expect(invoke).toHaveBeenCalledWith('interceptRoute', {
|
|
258
|
+
path: '/desktop/devtools',
|
|
259
|
+
source: 'replace-state',
|
|
260
|
+
url: 'http://localhost:3000/desktop/devtools',
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('should not intercept if already on target page', () => {
|
|
265
|
+
setupRouteInterceptors();
|
|
266
|
+
|
|
267
|
+
vi.stubGlobal('location', {
|
|
268
|
+
href: 'http://localhost:3000/desktop/devtools/console',
|
|
269
|
+
origin: 'http://localhost:3000',
|
|
270
|
+
pathname: '/desktop/devtools/console',
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
const matchedRoute = { pathPrefix: '/desktop/devtools', targetWindow: 'devtools' };
|
|
274
|
+
vi.mocked(findMatchingRoute).mockReturnValue(matchedRoute);
|
|
275
|
+
|
|
276
|
+
history.replaceState({}, '', '/desktop/devtools/network');
|
|
277
|
+
|
|
278
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
279
|
+
expect.stringContaining('Skip replaceState interception'),
|
|
280
|
+
);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('should allow replaceState for non-matched routes', () => {
|
|
284
|
+
setupRouteInterceptors();
|
|
285
|
+
|
|
286
|
+
vi.mocked(findMatchingRoute).mockReturnValue(undefined);
|
|
287
|
+
|
|
288
|
+
history.replaceState({}, '', '/chat/session-123');
|
|
289
|
+
|
|
290
|
+
expect(invoke).not.toHaveBeenCalledWith('interceptRoute', expect.anything());
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
describe('error event interception', () => {
|
|
295
|
+
it('should prevent navigation errors for prevented paths', () => {
|
|
296
|
+
setupRouteInterceptors();
|
|
297
|
+
|
|
298
|
+
// First trigger a route interception to add path to preventedPaths
|
|
299
|
+
const matchedRoute = { pathPrefix: '/desktop/devtools', targetWindow: 'devtools' };
|
|
300
|
+
vi.mocked(findMatchingRoute).mockReturnValue(matchedRoute);
|
|
301
|
+
history.pushState({}, '', '/desktop/devtools');
|
|
302
|
+
|
|
303
|
+
// Now trigger an error event with navigation in the message
|
|
304
|
+
const errorEvent = new ErrorEvent('error', {
|
|
305
|
+
bubbles: true,
|
|
306
|
+
cancelable: true,
|
|
307
|
+
message: 'navigation error occurred',
|
|
308
|
+
});
|
|
309
|
+
const preventDefaultSpy = vi.spyOn(errorEvent, 'preventDefault');
|
|
310
|
+
|
|
311
|
+
window.dispatchEvent(errorEvent);
|
|
312
|
+
|
|
313
|
+
expect(preventDefaultSpy).toHaveBeenCalled();
|
|
314
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
315
|
+
expect.stringContaining('Captured possible routing error'),
|
|
316
|
+
);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('should not prevent non-navigation errors', () => {
|
|
320
|
+
setupRouteInterceptors();
|
|
321
|
+
|
|
322
|
+
const errorEvent = new ErrorEvent('error', {
|
|
323
|
+
bubbles: true,
|
|
324
|
+
cancelable: true,
|
|
325
|
+
message: 'some other error',
|
|
326
|
+
});
|
|
327
|
+
const preventDefaultSpy = vi.spyOn(errorEvent, 'preventDefault');
|
|
328
|
+
|
|
329
|
+
window.dispatchEvent(errorEvent);
|
|
330
|
+
|
|
331
|
+
expect(preventDefaultSpy).not.toHaveBeenCalled();
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
describe('interceptRoute helper', () => {
|
|
336
|
+
it('should handle successful route interception', async () => {
|
|
337
|
+
vi.mocked(invoke).mockResolvedValue(undefined);
|
|
338
|
+
|
|
339
|
+
setupRouteInterceptors();
|
|
340
|
+
|
|
341
|
+
const matchedRoute = { pathPrefix: '/desktop/devtools', targetWindow: 'devtools' };
|
|
342
|
+
vi.mocked(findMatchingRoute).mockReturnValue(matchedRoute);
|
|
343
|
+
|
|
344
|
+
history.pushState({}, '', '/desktop/devtools');
|
|
345
|
+
|
|
346
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
347
|
+
|
|
348
|
+
expect(invoke).toHaveBeenCalledWith('interceptRoute', {
|
|
349
|
+
path: '/desktop/devtools',
|
|
350
|
+
source: 'push-state',
|
|
351
|
+
url: 'http://localhost:3000/desktop/devtools',
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('should handle route interception errors gracefully', async () => {
|
|
356
|
+
const error = new Error('IPC communication failed');
|
|
357
|
+
vi.mocked(invoke).mockRejectedValue(error);
|
|
358
|
+
|
|
359
|
+
setupRouteInterceptors();
|
|
360
|
+
|
|
361
|
+
const matchedRoute = { pathPrefix: '/desktop/devtools', targetWindow: 'devtools' };
|
|
362
|
+
vi.mocked(findMatchingRoute).mockReturnValue(matchedRoute);
|
|
363
|
+
|
|
364
|
+
history.pushState({}, '', '/desktop/devtools');
|
|
365
|
+
|
|
366
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
367
|
+
|
|
368
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
369
|
+
expect.stringContaining('Route interception (push-state) call failed'),
|
|
370
|
+
error,
|
|
371
|
+
);
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
});
|