@esmx/router 3.0.0-rc.29 → 3.0.0-rc.30
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/README.zh-CN.md +82 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.mjs +0 -1
- package/package.json +3 -3
- package/src/index.ts +0 -3
- package/dist/index.test.d.ts +0 -1
- package/dist/index.test.mjs +0 -8
- package/dist/location.test.d.ts +0 -8
- package/dist/location.test.mjs +0 -370
- package/dist/matcher.test.d.ts +0 -1
- package/dist/matcher.test.mjs +0 -1492
- package/dist/micro-app.dom.test.d.ts +0 -1
- package/dist/micro-app.dom.test.mjs +0 -532
- package/dist/navigation.test.d.ts +0 -1
- package/dist/navigation.test.mjs +0 -681
- package/dist/route-task.test.d.ts +0 -1
- package/dist/route-task.test.mjs +0 -673
- package/dist/route-transition.test.d.ts +0 -1
- package/dist/route-transition.test.mjs +0 -146
- package/dist/route.test.d.ts +0 -1
- package/dist/route.test.mjs +0 -1664
- package/dist/router-back.test.d.ts +0 -1
- package/dist/router-back.test.mjs +0 -361
- package/dist/router-forward.test.d.ts +0 -1
- package/dist/router-forward.test.mjs +0 -376
- package/dist/router-go.test.d.ts +0 -1
- package/dist/router-go.test.mjs +0 -73
- package/dist/router-guards-cleanup.test.d.ts +0 -1
- package/dist/router-guards-cleanup.test.mjs +0 -437
- package/dist/router-push.test.d.ts +0 -1
- package/dist/router-push.test.mjs +0 -115
- package/dist/router-replace.test.d.ts +0 -1
- package/dist/router-replace.test.mjs +0 -114
- package/dist/router-resolve.test.d.ts +0 -1
- package/dist/router-resolve.test.mjs +0 -393
- package/dist/router-restart-app.dom.test.d.ts +0 -1
- package/dist/router-restart-app.dom.test.mjs +0 -616
- package/dist/router-window-navigation.test.d.ts +0 -1
- package/dist/router-window-navigation.test.mjs +0 -359
- package/dist/util.test.d.ts +0 -1
- package/dist/util.test.mjs +0 -1020
- package/src/index.test.ts +0 -9
- package/src/location.test.ts +0 -406
- package/src/matcher.test.ts +0 -1685
- package/src/micro-app.dom.test.ts +0 -708
- package/src/navigation.test.ts +0 -858
- package/src/route-task.test.ts +0 -901
- package/src/route-transition.test.ts +0 -178
- package/src/route.test.ts +0 -2014
- package/src/router-back.test.ts +0 -487
- package/src/router-forward.test.ts +0 -506
- package/src/router-go.test.ts +0 -91
- package/src/router-guards-cleanup.test.ts +0 -595
- package/src/router-push.test.ts +0 -140
- package/src/router-replace.test.ts +0 -139
- package/src/router-resolve.test.ts +0 -475
- package/src/router-restart-app.dom.test.ts +0 -783
- package/src/router-window-navigation.test.ts +0 -457
- package/src/util.test.ts +0 -1262
|
@@ -1,783 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @vitest-environment happy-dom
|
|
3
|
-
*/
|
|
4
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
-
import { Router } from './router';
|
|
6
|
-
import { RouteType, RouterMode } from './types';
|
|
7
|
-
import type { Route, RouteLocationInput, RouterOptions } from './types';
|
|
8
|
-
|
|
9
|
-
describe('Router.restartApp Focused Tests', () => {
|
|
10
|
-
let router: Router;
|
|
11
|
-
let mockApps: Record<string, ReturnType<typeof vi.fn>>;
|
|
12
|
-
|
|
13
|
-
beforeEach(async () => {
|
|
14
|
-
mockApps = {
|
|
15
|
-
home: vi.fn(() => ({ mount: vi.fn(), unmount: vi.fn() })),
|
|
16
|
-
about: vi.fn(() => ({ mount: vi.fn(), unmount: vi.fn() })),
|
|
17
|
-
user: vi.fn(() => ({ mount: vi.fn(), unmount: vi.fn() })),
|
|
18
|
-
products: vi.fn(() => ({ mount: vi.fn(), unmount: vi.fn() }))
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const options: RouterOptions = {
|
|
22
|
-
routes: [
|
|
23
|
-
{ path: '/', app: 'home' },
|
|
24
|
-
{ path: '/about', app: 'about' },
|
|
25
|
-
{ path: '/user/:id', app: 'user' },
|
|
26
|
-
{ path: '/products/:category', app: 'products' }
|
|
27
|
-
],
|
|
28
|
-
apps: mockApps
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
router = new Router(options);
|
|
32
|
-
await router.push('/');
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
afterEach(() => {
|
|
36
|
-
router.destroy();
|
|
37
|
-
vi.clearAllMocks();
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
describe('🎯 Core Functionality Tests', () => {
|
|
41
|
-
it('should support parameterless restart (restart to current path)', async () => {
|
|
42
|
-
await router.push('/user/123');
|
|
43
|
-
expect(router.route.url.pathname).toBe('/user/123');
|
|
44
|
-
|
|
45
|
-
// Parameterless restart
|
|
46
|
-
const result = await router.restartApp();
|
|
47
|
-
|
|
48
|
-
expect(result.type).toBe(RouteType.restartApp);
|
|
49
|
-
expect(result.url.pathname).toBe('/user/123');
|
|
50
|
-
expect(result.handle).not.toBeNull();
|
|
51
|
-
expect(router.route).toBe(result);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('should support string path restart', async () => {
|
|
55
|
-
const result = await router.restartApp('/about');
|
|
56
|
-
|
|
57
|
-
expect(result.type).toBe(RouteType.restartApp);
|
|
58
|
-
expect(result.url.pathname).toBe('/about');
|
|
59
|
-
expect(result.handle).not.toBeNull();
|
|
60
|
-
expect(router.route).toBe(result);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('should support object parameter restart', async () => {
|
|
64
|
-
const result = await router.restartApp({
|
|
65
|
-
path: '/user/456',
|
|
66
|
-
query: { tab: 'profile', mode: 'edit' },
|
|
67
|
-
hash: '#section1'
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
expect(result.type).toBe(RouteType.restartApp);
|
|
71
|
-
expect(result.url.pathname).toBe('/user/456');
|
|
72
|
-
expect(result.query.tab).toBe('profile');
|
|
73
|
-
expect(result.query.mode).toBe('edit');
|
|
74
|
-
expect(result.url.hash).toBe('#section1');
|
|
75
|
-
expect(result.params.id).toBe('456');
|
|
76
|
-
expect(result.handle).not.toBeNull();
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('should support restart with state', async () => {
|
|
80
|
-
const customState = { userId: 123, preferences: { theme: 'dark' } };
|
|
81
|
-
const result = await router.restartApp({
|
|
82
|
-
path: '/about',
|
|
83
|
-
state: customState
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
// State will be merged, including system-added fields
|
|
87
|
-
expect(result.state).toEqual(expect.objectContaining(customState));
|
|
88
|
-
const state = result.state as typeof customState;
|
|
89
|
-
expect(state.userId).toBe(123);
|
|
90
|
-
expect(state.preferences.theme).toBe('dark');
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it('should correctly handle route parameters', async () => {
|
|
94
|
-
const result = await router.restartApp('/user/789');
|
|
95
|
-
|
|
96
|
-
expect(result.params.id).toBe('789');
|
|
97
|
-
expect(result.matched.length).toBeGreaterThan(0);
|
|
98
|
-
expect(result.matched[0].path).toBe('/user/:id');
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('should correctly handle query parameters', async () => {
|
|
102
|
-
const result = await router.restartApp('/about?tab=info&mode=edit');
|
|
103
|
-
|
|
104
|
-
expect(result.query.tab).toBe('info');
|
|
105
|
-
expect(result.query.mode).toBe('edit');
|
|
106
|
-
expect(result.url.search).toBe('?tab=info&mode=edit');
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('should correctly handle hash', async () => {
|
|
110
|
-
const result = await router.restartApp('/about#section2');
|
|
111
|
-
|
|
112
|
-
expect(result.url.hash).toBe('#section2');
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
describe('🔄 restartApp-Specific Behavior Tests', () => {
|
|
117
|
-
it('should force update MicroApp (even with same application)', async () => {
|
|
118
|
-
await router.push('/about');
|
|
119
|
-
const firstCallCount = mockApps.about.mock.calls.length;
|
|
120
|
-
|
|
121
|
-
// Restart to same path, should force update
|
|
122
|
-
await router.restartApp('/about');
|
|
123
|
-
const secondCallCount = mockApps.about.mock.calls.length;
|
|
124
|
-
|
|
125
|
-
expect(secondCallCount).toBeGreaterThan(firstCallCount);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it('should call navigation.replace instead of push', async () => {
|
|
129
|
-
const replaceSpy = vi.spyOn(router.navigation, 'replace');
|
|
130
|
-
const pushSpy = vi.spyOn(router.navigation, 'push');
|
|
131
|
-
|
|
132
|
-
await router.restartApp('/about');
|
|
133
|
-
|
|
134
|
-
expect(replaceSpy).toHaveBeenCalled();
|
|
135
|
-
expect(pushSpy).not.toHaveBeenCalled();
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it('should correctly update router current route state', async () => {
|
|
139
|
-
const result = await router.restartApp('/about');
|
|
140
|
-
|
|
141
|
-
expect(router.route.url.pathname).toBe('/about');
|
|
142
|
-
expect(router.route.type).toBe(RouteType.restartApp);
|
|
143
|
-
expect(router.route).toBe(result);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it('should differentiate from other routing method behaviors', async () => {
|
|
147
|
-
// push navigation
|
|
148
|
-
await router.push('/user/123');
|
|
149
|
-
expect(router.route.type).toBe(RouteType.push);
|
|
150
|
-
|
|
151
|
-
// replace navigation
|
|
152
|
-
await router.replace('/about');
|
|
153
|
-
expect(router.route.type).toBe(RouteType.replace);
|
|
154
|
-
|
|
155
|
-
// restartApp navigation
|
|
156
|
-
const result = await router.restartApp('/products/electronics');
|
|
157
|
-
expect(result.type).toBe(RouteType.restartApp);
|
|
158
|
-
expect(router.route.type).toBe(RouteType.restartApp);
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
describe('🎭 Edge Cases Tests', () => {
|
|
163
|
-
it('should handle non-existent routes', async () => {
|
|
164
|
-
const result = await router.restartApp('/nonexistent');
|
|
165
|
-
|
|
166
|
-
expect(result.matched.length).toBe(0);
|
|
167
|
-
expect(result.config).toBeNull();
|
|
168
|
-
expect(result.url.pathname).toBe('/nonexistent');
|
|
169
|
-
expect(result.type).toBe(RouteType.restartApp);
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
it('should handle empty string path', async () => {
|
|
173
|
-
const result = await router.restartApp('');
|
|
174
|
-
|
|
175
|
-
expect(result.url.pathname).toBe('/');
|
|
176
|
-
expect(result.type).toBe(RouteType.restartApp);
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
it('should handle root path', async () => {
|
|
180
|
-
const result = await router.restartApp('/');
|
|
181
|
-
|
|
182
|
-
expect(result.url.pathname).toBe('/');
|
|
183
|
-
expect(result.matched.length).toBeGreaterThan(0);
|
|
184
|
-
expect(result.type).toBe(RouteType.restartApp);
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
it('should handle complex query parameters', async () => {
|
|
188
|
-
const result = await router.restartApp('/about?a=1&b=2&a=3&c=');
|
|
189
|
-
|
|
190
|
-
// Query parameter handling may vary by implementation, testing basic functionality here
|
|
191
|
-
expect(result.query.b).toBe('2');
|
|
192
|
-
expect(result.query.c).toBe('');
|
|
193
|
-
expect(result.url.search).toContain('a=');
|
|
194
|
-
expect(result.url.search).toContain('b=2');
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
it('should handle special character paths', async () => {
|
|
198
|
-
const result = await router.restartApp('/user/测试用户');
|
|
199
|
-
|
|
200
|
-
expect(result.url.pathname).toContain('/user/');
|
|
201
|
-
// Parameters may be URL encoded, need to decode
|
|
202
|
-
expect(decodeURIComponent(result.params.id)).toBe('测试用户');
|
|
203
|
-
});
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
describe('🔗 URL Parsing Tests', () => {
|
|
207
|
-
it('should correctly parse absolute paths', async () => {
|
|
208
|
-
await router.push('/user/123');
|
|
209
|
-
|
|
210
|
-
// Restart to absolute path
|
|
211
|
-
const result = await router.restartApp('/about');
|
|
212
|
-
|
|
213
|
-
expect(result.url.pathname).toBe('/about');
|
|
214
|
-
expect(result.url.href).toMatch(/\/about$/);
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
it('should correctly handle relative paths', async () => {
|
|
218
|
-
await router.push('/user/123');
|
|
219
|
-
|
|
220
|
-
const result = await router.restartApp('456');
|
|
221
|
-
|
|
222
|
-
// Relative path handling depends on current base URL implementation
|
|
223
|
-
expect(result.url.pathname).toContain('456');
|
|
224
|
-
if (
|
|
225
|
-
result.matched.length > 0 &&
|
|
226
|
-
result.matched[0].path === '/user/:id'
|
|
227
|
-
) {
|
|
228
|
-
expect(result.params.id).toBe('456');
|
|
229
|
-
}
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
it('should correctly handle complete URLs', async () => {
|
|
233
|
-
const result = await router.restartApp('http://example.com/test');
|
|
234
|
-
|
|
235
|
-
expect(result.url.href).toBe('http://example.com/test');
|
|
236
|
-
expect(result.url.pathname).toBe('/test');
|
|
237
|
-
});
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
describe('🎯 Type Overload Tests', () => {
|
|
241
|
-
it('should support parameterless calls', async () => {
|
|
242
|
-
await router.push('/user/123');
|
|
243
|
-
|
|
244
|
-
const result: Route = await router.restartApp();
|
|
245
|
-
|
|
246
|
-
expect(result.url.pathname).toBe('/user/123');
|
|
247
|
-
expect(result.type).toBe(RouteType.restartApp);
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
it('should support string parameter calls', async () => {
|
|
251
|
-
const result: Route = await router.restartApp('/about');
|
|
252
|
-
|
|
253
|
-
expect(result.url.pathname).toBe('/about');
|
|
254
|
-
expect(result.type).toBe(RouteType.restartApp);
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
it('should support object parameter calls', async () => {
|
|
258
|
-
const routeLocation: RouteLocationInput = {
|
|
259
|
-
path: '/user/456',
|
|
260
|
-
query: { tab: 'settings' }
|
|
261
|
-
};
|
|
262
|
-
|
|
263
|
-
const result: Route = await router.restartApp(routeLocation);
|
|
264
|
-
|
|
265
|
-
expect(result.url.pathname).toBe('/user/456');
|
|
266
|
-
expect(result.query.tab).toBe('settings');
|
|
267
|
-
expect(result.type).toBe(RouteType.restartApp);
|
|
268
|
-
});
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
describe('🔄 Multiple Restart Tests', () => {
|
|
272
|
-
it('should support consecutive multiple restarts', async () => {
|
|
273
|
-
const paths = ['/about', '/user/123', '/products/electronics', '/'];
|
|
274
|
-
|
|
275
|
-
for (const path of paths) {
|
|
276
|
-
const result = await router.restartApp(path);
|
|
277
|
-
expect(result.type).toBe(RouteType.restartApp);
|
|
278
|
-
expect(result.handle).not.toBeNull();
|
|
279
|
-
expect(router.route).toBe(result);
|
|
280
|
-
}
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
it('should create new application instances on each restart', async () => {
|
|
284
|
-
await router.restartApp('/about');
|
|
285
|
-
const firstCallCount = mockApps.about.mock.calls.length;
|
|
286
|
-
|
|
287
|
-
await router.restartApp('/about');
|
|
288
|
-
const secondCallCount = mockApps.about.mock.calls.length;
|
|
289
|
-
|
|
290
|
-
await router.restartApp('/about');
|
|
291
|
-
const thirdCallCount = mockApps.about.mock.calls.length;
|
|
292
|
-
|
|
293
|
-
expect(secondCallCount).toBeGreaterThan(firstCallCount);
|
|
294
|
-
expect(thirdCallCount).toBeGreaterThan(secondCallCount);
|
|
295
|
-
});
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
describe('🎨 Status Code Handling Tests', () => {
|
|
299
|
-
it('should support custom status codes', async () => {
|
|
300
|
-
const result = await router.restartApp({
|
|
301
|
-
path: '/about',
|
|
302
|
-
statusCode: 201
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
expect(result.statusCode).toBe(201);
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
it('should maintain default status code as null', async () => {
|
|
309
|
-
const result = await router.restartApp('/about');
|
|
310
|
-
|
|
311
|
-
expect(result.statusCode).toBeNull();
|
|
312
|
-
});
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
describe('🔧 Consistency Tests with resolve Method', () => {
|
|
316
|
-
it('should maintain consistency with resolve method results in URL parsing', async () => {
|
|
317
|
-
const resolvedRoute = router.resolve('/user/789');
|
|
318
|
-
const restartedRoute = await router.restartApp('/user/789');
|
|
319
|
-
|
|
320
|
-
expect(restartedRoute.url.href).toBe(resolvedRoute.url.href);
|
|
321
|
-
expect(restartedRoute.params).toEqual(resolvedRoute.params);
|
|
322
|
-
expect(restartedRoute.matched).toEqual(resolvedRoute.matched);
|
|
323
|
-
expect(restartedRoute.type).toBe(RouteType.restartApp);
|
|
324
|
-
expect(resolvedRoute.type).toBe(RouteType.push);
|
|
325
|
-
});
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
describe('🛡️ Route Guards Integration Tests', () => {
|
|
329
|
-
let guardExecutionLog: string[];
|
|
330
|
-
|
|
331
|
-
beforeEach(() => {
|
|
332
|
-
guardExecutionLog = [];
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
it('should correctly execute beforeEach guards', async () => {
|
|
336
|
-
const unregister = router.beforeEach(async (to, from) => {
|
|
337
|
-
guardExecutionLog.push(
|
|
338
|
-
`beforeEach-${to.path}-from-${from?.path || 'null'}`
|
|
339
|
-
);
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
await router.restartApp('/about');
|
|
343
|
-
|
|
344
|
-
expect(guardExecutionLog).toContain('beforeEach-/about-from-/');
|
|
345
|
-
unregister();
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
it('should correctly execute afterEach guards', async () => {
|
|
349
|
-
const unregister = router.afterEach((to, from) => {
|
|
350
|
-
guardExecutionLog.push(
|
|
351
|
-
`afterEach-${to.path}-from-${from?.path || 'null'}`
|
|
352
|
-
);
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
await router.restartApp('/about');
|
|
356
|
-
|
|
357
|
-
expect(guardExecutionLog).toContain('afterEach-/about-from-/');
|
|
358
|
-
unregister();
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
it('should abort restart when beforeEach guard returns false', async () => {
|
|
362
|
-
const unregister = router.beforeEach(async (to, from) => {
|
|
363
|
-
if (to.path === '/about') {
|
|
364
|
-
return false;
|
|
365
|
-
}
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
await expect(router.restartApp('/about')).rejects.toThrow();
|
|
369
|
-
expect(router.route.path).toBe('/'); // Should maintain original route
|
|
370
|
-
unregister();
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
it('should support guard redirects', async () => {
|
|
374
|
-
const unregister = router.beforeEach(async (to, from) => {
|
|
375
|
-
if (to.path === '/about') {
|
|
376
|
-
return '/user/redirected';
|
|
377
|
-
}
|
|
378
|
-
});
|
|
379
|
-
|
|
380
|
-
const result = await router.restartApp('/about');
|
|
381
|
-
|
|
382
|
-
expect(result.path).toBe('/user/redirected');
|
|
383
|
-
expect(result.params.id).toBe('redirected');
|
|
384
|
-
expect(result.handle).not.toBeNull();
|
|
385
|
-
unregister();
|
|
386
|
-
});
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
describe('🧩 Async Component Handling Tests', () => {
|
|
390
|
-
let asyncRouter: Router;
|
|
391
|
-
|
|
392
|
-
beforeEach(async () => {
|
|
393
|
-
const asyncOptions: RouterOptions = {
|
|
394
|
-
routes: [
|
|
395
|
-
{
|
|
396
|
-
path: '/',
|
|
397
|
-
app: 'home',
|
|
398
|
-
component: () => 'HomeComponent'
|
|
399
|
-
},
|
|
400
|
-
{
|
|
401
|
-
path: '/async',
|
|
402
|
-
app: 'async',
|
|
403
|
-
asyncComponent: async () => {
|
|
404
|
-
await new Promise((resolve) =>
|
|
405
|
-
setTimeout(resolve, 10)
|
|
406
|
-
);
|
|
407
|
-
return () => 'AsyncComponent';
|
|
408
|
-
}
|
|
409
|
-
},
|
|
410
|
-
{
|
|
411
|
-
path: '/async-error',
|
|
412
|
-
app: 'async-error',
|
|
413
|
-
asyncComponent: async () => {
|
|
414
|
-
throw new Error('Component load failed');
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
],
|
|
418
|
-
apps: mockApps
|
|
419
|
-
};
|
|
420
|
-
|
|
421
|
-
asyncRouter = new Router(asyncOptions);
|
|
422
|
-
await asyncRouter.push('/');
|
|
423
|
-
});
|
|
424
|
-
|
|
425
|
-
afterEach(() => {
|
|
426
|
-
asyncRouter.destroy();
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
it('should correctly handle async component loading', async () => {
|
|
430
|
-
const result = await asyncRouter.restartApp('/async');
|
|
431
|
-
|
|
432
|
-
expect(result.handle).not.toBeNull();
|
|
433
|
-
expect(result.matched[0].component).toBeDefined();
|
|
434
|
-
expect(typeof result.matched[0].component).toBe('function');
|
|
435
|
-
});
|
|
436
|
-
|
|
437
|
-
it('should handle async component loading failures', async () => {
|
|
438
|
-
await expect(
|
|
439
|
-
asyncRouter.restartApp('/async-error')
|
|
440
|
-
).rejects.toThrow();
|
|
441
|
-
});
|
|
442
|
-
});
|
|
443
|
-
|
|
444
|
-
describe('⚡ Task Cancellation and Concurrency Control Tests', () => {
|
|
445
|
-
it('should cancel tasks interrupted by new restartApp calls', async () => {
|
|
446
|
-
const unregister = router.beforeEach(async (to, from) => {
|
|
447
|
-
if (to.path === '/user/slow') {
|
|
448
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
449
|
-
}
|
|
450
|
-
});
|
|
451
|
-
|
|
452
|
-
// Rapidly consecutive restartApp calls
|
|
453
|
-
const promises = [
|
|
454
|
-
router.restartApp('/user/slow').catch((err) => err),
|
|
455
|
-
router.restartApp('/about').catch((err) => err)
|
|
456
|
-
];
|
|
457
|
-
const results = await Promise.all(promises);
|
|
458
|
-
|
|
459
|
-
expect(results[0] instanceof Error).toBe(true);
|
|
460
|
-
expect(results[1].handle).not.toBeNull();
|
|
461
|
-
expect(router.route.path).toBe('/about');
|
|
462
|
-
|
|
463
|
-
unregister();
|
|
464
|
-
});
|
|
465
|
-
|
|
466
|
-
it('should correctly handle multiple concurrent restartApp calls', async () => {
|
|
467
|
-
const paths = ['/user/1', '/user/2', '/user/3'];
|
|
468
|
-
const promises = paths.map((path) =>
|
|
469
|
-
router.restartApp(path).catch((err) => err)
|
|
470
|
-
);
|
|
471
|
-
const results = await Promise.all(promises);
|
|
472
|
-
|
|
473
|
-
expect(results[0] instanceof Error).toBe(true);
|
|
474
|
-
expect(results[1] instanceof Error).toBe(true);
|
|
475
|
-
expect(results[2].handle).not.toBeNull();
|
|
476
|
-
expect(router.route.path).toBe('/user/3');
|
|
477
|
-
});
|
|
478
|
-
});
|
|
479
|
-
|
|
480
|
-
describe('🌍 Route Override Tests', () => {
|
|
481
|
-
let overrideRouter: Router;
|
|
482
|
-
|
|
483
|
-
beforeEach(async () => {
|
|
484
|
-
const overrideOptions: RouterOptions = {
|
|
485
|
-
routes: [
|
|
486
|
-
{
|
|
487
|
-
path: '/',
|
|
488
|
-
app: 'home',
|
|
489
|
-
component: () => 'HomeComponent'
|
|
490
|
-
},
|
|
491
|
-
{
|
|
492
|
-
path: '/override-test',
|
|
493
|
-
app: 'override',
|
|
494
|
-
component: () => 'OverrideComponent',
|
|
495
|
-
override: (to, from) => {
|
|
496
|
-
if (to.query.native === 'true') {
|
|
497
|
-
return async () => {
|
|
498
|
-
return { native: true, path: to.path };
|
|
499
|
-
};
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
},
|
|
503
|
-
{
|
|
504
|
-
path: '/hybrid-page',
|
|
505
|
-
app: 'hybrid',
|
|
506
|
-
component: () => 'HybridComponent',
|
|
507
|
-
override: (to, from) => {
|
|
508
|
-
// Always override for this test
|
|
509
|
-
return async () => {
|
|
510
|
-
return {
|
|
511
|
-
hybrid: 'native',
|
|
512
|
-
component: 'NativeComponent'
|
|
513
|
-
};
|
|
514
|
-
};
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
],
|
|
518
|
-
apps: mockApps
|
|
519
|
-
};
|
|
520
|
-
|
|
521
|
-
overrideRouter = new Router(overrideOptions);
|
|
522
|
-
await overrideRouter.push('/');
|
|
523
|
-
});
|
|
524
|
-
|
|
525
|
-
afterEach(() => {
|
|
526
|
-
overrideRouter.destroy();
|
|
527
|
-
});
|
|
528
|
-
|
|
529
|
-
it('should use override when condition is met', async () => {
|
|
530
|
-
const result = await overrideRouter.restartApp(
|
|
531
|
-
'/override-test?native=true'
|
|
532
|
-
);
|
|
533
|
-
|
|
534
|
-
expect(result.handle).not.toBeNull();
|
|
535
|
-
// restartApp doesn't execute override tasks, so handleResult should be undefined
|
|
536
|
-
expect(result.handleResult).toBeUndefined();
|
|
537
|
-
expect(result.config?.override).toBeDefined();
|
|
538
|
-
});
|
|
539
|
-
|
|
540
|
-
it('should use default behavior when override returns nothing', async () => {
|
|
541
|
-
const result = await overrideRouter.restartApp(
|
|
542
|
-
'/override-test?native=false'
|
|
543
|
-
);
|
|
544
|
-
|
|
545
|
-
expect(result.handle).not.toBeNull();
|
|
546
|
-
expect(result.handle).not.toBeNull();
|
|
547
|
-
});
|
|
548
|
-
|
|
549
|
-
it('should always use override when function always returns handler', async () => {
|
|
550
|
-
const result = await overrideRouter.restartApp('/hybrid-page');
|
|
551
|
-
|
|
552
|
-
expect(result.handle).not.toBeNull();
|
|
553
|
-
// restartApp doesn't execute override tasks, so handleResult should be undefined
|
|
554
|
-
expect(result.handleResult).toBeUndefined();
|
|
555
|
-
expect(result.config?.override).toBeDefined();
|
|
556
|
-
});
|
|
557
|
-
});
|
|
558
|
-
|
|
559
|
-
describe('❌ Error Handling and Exception Scenario Tests', () => {
|
|
560
|
-
it('should handle exceptions thrown in guards', async () => {
|
|
561
|
-
const unregister = router.beforeEach(async (to, from) => {
|
|
562
|
-
if (to.path === '/about') {
|
|
563
|
-
throw new Error('Guard error');
|
|
564
|
-
}
|
|
565
|
-
});
|
|
566
|
-
|
|
567
|
-
await expect(router.restartApp('/about')).rejects.toThrow(
|
|
568
|
-
'Guard error'
|
|
569
|
-
);
|
|
570
|
-
unregister();
|
|
571
|
-
});
|
|
572
|
-
|
|
573
|
-
it('should handle MicroApp update exceptions', async () => {
|
|
574
|
-
const originalUpdate = router.microApp._update;
|
|
575
|
-
router.microApp._update = vi.fn().mockImplementation(() => {
|
|
576
|
-
throw new Error('MicroApp update failed');
|
|
577
|
-
});
|
|
578
|
-
|
|
579
|
-
// MicroApp update exceptions will cause entire route handling to fail
|
|
580
|
-
await expect(router.restartApp('/about')).rejects.toThrow(
|
|
581
|
-
'MicroApp update failed'
|
|
582
|
-
);
|
|
583
|
-
|
|
584
|
-
router.microApp._update = originalUpdate;
|
|
585
|
-
});
|
|
586
|
-
|
|
587
|
-
it('should handle navigation.replace exceptions', async () => {
|
|
588
|
-
const originalReplace = router.navigation.replace;
|
|
589
|
-
router.navigation.replace = vi.fn().mockImplementation(() => {
|
|
590
|
-
throw new Error('Navigation replace failed');
|
|
591
|
-
});
|
|
592
|
-
|
|
593
|
-
// navigation.replace exceptions will cause entire route handling to fail
|
|
594
|
-
await expect(router.restartApp('/about')).rejects.toThrow(
|
|
595
|
-
'Navigation replace failed'
|
|
596
|
-
);
|
|
597
|
-
|
|
598
|
-
router.navigation.replace = originalReplace;
|
|
599
|
-
});
|
|
600
|
-
});
|
|
601
|
-
|
|
602
|
-
describe('🔄 Route Lifecycle Integrity Tests', () => {
|
|
603
|
-
let lifecycleRouter: Router;
|
|
604
|
-
let lifecycleLog: string[];
|
|
605
|
-
|
|
606
|
-
beforeEach(async () => {
|
|
607
|
-
lifecycleLog = [];
|
|
608
|
-
|
|
609
|
-
const lifecycleOptions: RouterOptions = {
|
|
610
|
-
routes: [
|
|
611
|
-
{
|
|
612
|
-
path: '/',
|
|
613
|
-
app: 'home',
|
|
614
|
-
component: () => 'HomeComponent',
|
|
615
|
-
beforeLeave: async (to, from) => {
|
|
616
|
-
lifecycleLog.push('home-beforeLeave');
|
|
617
|
-
}
|
|
618
|
-
},
|
|
619
|
-
{
|
|
620
|
-
path: '/lifecycle',
|
|
621
|
-
app: 'lifecycle',
|
|
622
|
-
component: () => 'LifecycleComponent',
|
|
623
|
-
beforeEnter: async (to, from) => {
|
|
624
|
-
lifecycleLog.push('lifecycle-beforeEnter');
|
|
625
|
-
},
|
|
626
|
-
beforeUpdate: async (to, from) => {
|
|
627
|
-
lifecycleLog.push('lifecycle-beforeUpdate');
|
|
628
|
-
},
|
|
629
|
-
beforeLeave: async (to, from) => {
|
|
630
|
-
lifecycleLog.push('lifecycle-beforeLeave');
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
],
|
|
634
|
-
apps: mockApps
|
|
635
|
-
};
|
|
636
|
-
|
|
637
|
-
lifecycleRouter = new Router(lifecycleOptions);
|
|
638
|
-
await lifecycleRouter.push('/');
|
|
639
|
-
});
|
|
640
|
-
|
|
641
|
-
afterEach(() => {
|
|
642
|
-
lifecycleRouter.destroy();
|
|
643
|
-
});
|
|
644
|
-
|
|
645
|
-
it('should correctly execute complete route lifecycle', async () => {
|
|
646
|
-
// Global guards
|
|
647
|
-
const unregisterBefore = lifecycleRouter.beforeEach((to, from) => {
|
|
648
|
-
lifecycleLog.push(`global-beforeEach-${to.path}`);
|
|
649
|
-
});
|
|
650
|
-
const unregisterAfter = lifecycleRouter.afterEach((to, from) => {
|
|
651
|
-
lifecycleLog.push(`global-afterEach-${to.path}`);
|
|
652
|
-
});
|
|
653
|
-
|
|
654
|
-
await lifecycleRouter.restartApp('/lifecycle');
|
|
655
|
-
|
|
656
|
-
expect(lifecycleLog).toEqual([
|
|
657
|
-
'home-beforeLeave',
|
|
658
|
-
'global-beforeEach-/lifecycle',
|
|
659
|
-
'lifecycle-beforeEnter',
|
|
660
|
-
'global-afterEach-/lifecycle'
|
|
661
|
-
]);
|
|
662
|
-
|
|
663
|
-
unregisterBefore();
|
|
664
|
-
unregisterAfter();
|
|
665
|
-
});
|
|
666
|
-
|
|
667
|
-
it('should execute beforeUpdate when restarting same route', async () => {
|
|
668
|
-
await lifecycleRouter.push('/lifecycle');
|
|
669
|
-
lifecycleLog = []; // Clear log
|
|
670
|
-
|
|
671
|
-
// Restart to same route with different parameters
|
|
672
|
-
await lifecycleRouter.restartApp('/lifecycle?version=2');
|
|
673
|
-
|
|
674
|
-
expect(lifecycleLog).toContain('lifecycle-beforeUpdate');
|
|
675
|
-
expect(lifecycleLog).not.toContain('lifecycle-beforeEnter');
|
|
676
|
-
});
|
|
677
|
-
});
|
|
678
|
-
|
|
679
|
-
describe('🎯 Special Route Configuration Tests', () => {
|
|
680
|
-
it('should handle routes with custom location handler', async () => {
|
|
681
|
-
let locationCalled = false;
|
|
682
|
-
const customLocationRouter = new Router({
|
|
683
|
-
routes: [{ path: '/', app: 'home' }],
|
|
684
|
-
apps: mockApps,
|
|
685
|
-
fallback: (to, from) => {
|
|
686
|
-
locationCalled = true;
|
|
687
|
-
return { customLocation: true, path: to.path };
|
|
688
|
-
}
|
|
689
|
-
});
|
|
690
|
-
|
|
691
|
-
await customLocationRouter.push('/');
|
|
692
|
-
const result =
|
|
693
|
-
await customLocationRouter.restartApp('/nonexistent');
|
|
694
|
-
|
|
695
|
-
// Location handler should be called because route doesn't exist
|
|
696
|
-
expect(locationCalled).toBe(true);
|
|
697
|
-
expect(result.matched.length).toBe(0); // Non-existent route
|
|
698
|
-
expect(typeof result.handle).toBe('function');
|
|
699
|
-
expect(result.handleResult).toEqual({
|
|
700
|
-
customLocation: true,
|
|
701
|
-
path: '/nonexistent'
|
|
702
|
-
});
|
|
703
|
-
customLocationRouter.destroy();
|
|
704
|
-
});
|
|
705
|
-
|
|
706
|
-
it('should handle complex nested route restarts', async () => {
|
|
707
|
-
const nestedRouter = new Router({
|
|
708
|
-
routes: [
|
|
709
|
-
{
|
|
710
|
-
path: '/',
|
|
711
|
-
app: 'home',
|
|
712
|
-
children: [
|
|
713
|
-
{
|
|
714
|
-
path: 'nested/:id',
|
|
715
|
-
app: 'nested',
|
|
716
|
-
children: [
|
|
717
|
-
{
|
|
718
|
-
path: 'deep/:subId',
|
|
719
|
-
app: 'deep'
|
|
720
|
-
}
|
|
721
|
-
]
|
|
722
|
-
}
|
|
723
|
-
]
|
|
724
|
-
}
|
|
725
|
-
],
|
|
726
|
-
apps: mockApps
|
|
727
|
-
});
|
|
728
|
-
|
|
729
|
-
await nestedRouter.push('/');
|
|
730
|
-
const result = await nestedRouter.restartApp(
|
|
731
|
-
'/nested/123/deep/456'
|
|
732
|
-
);
|
|
733
|
-
|
|
734
|
-
expect(result.params.id).toBe('123');
|
|
735
|
-
expect(result.params.subId).toBe('456');
|
|
736
|
-
expect(result.matched.length).toBe(3); // Three levels of nesting
|
|
737
|
-
nestedRouter.destroy();
|
|
738
|
-
});
|
|
739
|
-
});
|
|
740
|
-
|
|
741
|
-
describe('📊 Performance and Memory Tests', () => {
|
|
742
|
-
it('should correctly clean up resources after extensive restarts', async () => {
|
|
743
|
-
const initialAppsCallCount = Object.values(mockApps).reduce(
|
|
744
|
-
(sum, app) => sum + app.mock.calls.length,
|
|
745
|
-
0
|
|
746
|
-
);
|
|
747
|
-
|
|
748
|
-
// Execute extensive restart operations
|
|
749
|
-
for (let i = 0; i < 50; i++) {
|
|
750
|
-
await router.restartApp(`/user/${i}`);
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
const finalAppsCallCount = Object.values(mockApps).reduce(
|
|
754
|
-
(sum, app) => sum + app.mock.calls.length,
|
|
755
|
-
0
|
|
756
|
-
);
|
|
757
|
-
|
|
758
|
-
expect(finalAppsCallCount).toBeGreaterThan(initialAppsCallCount);
|
|
759
|
-
|
|
760
|
-
expect(router.route.params.id).toBe('49');
|
|
761
|
-
});
|
|
762
|
-
|
|
763
|
-
it('should correctly handle rapid consecutive restart calls', async () => {
|
|
764
|
-
const startTime = Date.now();
|
|
765
|
-
|
|
766
|
-
// Rapid consecutive calls
|
|
767
|
-
const promises = Array.from({ length: 10 }, (_, i) =>
|
|
768
|
-
router.restartApp(`/user/${i}`).catch((err) => err)
|
|
769
|
-
);
|
|
770
|
-
|
|
771
|
-
const results = await Promise.all(promises);
|
|
772
|
-
const endTime = Date.now();
|
|
773
|
-
|
|
774
|
-
const successfulResults = results.filter(
|
|
775
|
-
(r) => !(r instanceof Error) && r.handle !== null
|
|
776
|
-
);
|
|
777
|
-
expect(successfulResults).toHaveLength(1);
|
|
778
|
-
expect(successfulResults[0].params.id).toBe('9');
|
|
779
|
-
|
|
780
|
-
expect(endTime - startTime).toBeLessThan(1000);
|
|
781
|
-
});
|
|
782
|
-
});
|
|
783
|
-
});
|