@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.
Files changed (59) hide show
  1. package/README.zh-CN.md +82 -1
  2. package/dist/index.d.ts +1 -2
  3. package/dist/index.mjs +0 -1
  4. package/package.json +3 -3
  5. package/src/index.ts +0 -3
  6. package/dist/index.test.d.ts +0 -1
  7. package/dist/index.test.mjs +0 -8
  8. package/dist/location.test.d.ts +0 -8
  9. package/dist/location.test.mjs +0 -370
  10. package/dist/matcher.test.d.ts +0 -1
  11. package/dist/matcher.test.mjs +0 -1492
  12. package/dist/micro-app.dom.test.d.ts +0 -1
  13. package/dist/micro-app.dom.test.mjs +0 -532
  14. package/dist/navigation.test.d.ts +0 -1
  15. package/dist/navigation.test.mjs +0 -681
  16. package/dist/route-task.test.d.ts +0 -1
  17. package/dist/route-task.test.mjs +0 -673
  18. package/dist/route-transition.test.d.ts +0 -1
  19. package/dist/route-transition.test.mjs +0 -146
  20. package/dist/route.test.d.ts +0 -1
  21. package/dist/route.test.mjs +0 -1664
  22. package/dist/router-back.test.d.ts +0 -1
  23. package/dist/router-back.test.mjs +0 -361
  24. package/dist/router-forward.test.d.ts +0 -1
  25. package/dist/router-forward.test.mjs +0 -376
  26. package/dist/router-go.test.d.ts +0 -1
  27. package/dist/router-go.test.mjs +0 -73
  28. package/dist/router-guards-cleanup.test.d.ts +0 -1
  29. package/dist/router-guards-cleanup.test.mjs +0 -437
  30. package/dist/router-push.test.d.ts +0 -1
  31. package/dist/router-push.test.mjs +0 -115
  32. package/dist/router-replace.test.d.ts +0 -1
  33. package/dist/router-replace.test.mjs +0 -114
  34. package/dist/router-resolve.test.d.ts +0 -1
  35. package/dist/router-resolve.test.mjs +0 -393
  36. package/dist/router-restart-app.dom.test.d.ts +0 -1
  37. package/dist/router-restart-app.dom.test.mjs +0 -616
  38. package/dist/router-window-navigation.test.d.ts +0 -1
  39. package/dist/router-window-navigation.test.mjs +0 -359
  40. package/dist/util.test.d.ts +0 -1
  41. package/dist/util.test.mjs +0 -1020
  42. package/src/index.test.ts +0 -9
  43. package/src/location.test.ts +0 -406
  44. package/src/matcher.test.ts +0 -1685
  45. package/src/micro-app.dom.test.ts +0 -708
  46. package/src/navigation.test.ts +0 -858
  47. package/src/route-task.test.ts +0 -901
  48. package/src/route-transition.test.ts +0 -178
  49. package/src/route.test.ts +0 -2014
  50. package/src/router-back.test.ts +0 -487
  51. package/src/router-forward.test.ts +0 -506
  52. package/src/router-go.test.ts +0 -91
  53. package/src/router-guards-cleanup.test.ts +0 -595
  54. package/src/router-push.test.ts +0 -140
  55. package/src/router-replace.test.ts +0 -139
  56. package/src/router-resolve.test.ts +0 -475
  57. package/src/router-restart-app.dom.test.ts +0 -783
  58. package/src/router-window-navigation.test.ts +0 -457
  59. package/src/util.test.ts +0 -1262
@@ -1,457 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from 'vitest';
2
- import { Router } from './router';
3
- import { RouteType, RouterMode } from './types';
4
- import type { Route, RouteLocationInput, RouterOptions } from './types';
5
-
6
- describe('Router Window Navigation Tests', () => {
7
- let router: Router;
8
- let mockApps: Record<string, any>;
9
-
10
- beforeEach(() => {
11
- vi.clearAllMocks();
12
-
13
- mockApps = {
14
- home: vi.fn(() => ({
15
- mount: vi.fn(),
16
- unmount: vi.fn(),
17
- renderToString: vi.fn().mockResolvedValue('<div>Home</div>')
18
- })),
19
- about: vi.fn(() => ({
20
- mount: vi.fn(),
21
- unmount: vi.fn(),
22
- renderToString: vi.fn().mockResolvedValue('<div>About</div>')
23
- })),
24
- user: vi.fn(() => ({
25
- mount: vi.fn(),
26
- unmount: vi.fn(),
27
- renderToString: vi.fn().mockResolvedValue('<div>User</div>')
28
- }))
29
- };
30
-
31
- router = new Router({
32
- routes: [
33
- { path: '/', app: 'home' },
34
- { path: '/about', app: 'about' },
35
- { path: '/user/:id', app: 'user' }
36
- ],
37
- apps: mockApps
38
- });
39
- });
40
-
41
- /**
42
- * Generic window navigation test function
43
- * @param methodName Method name ('pushWindow' or 'replaceWindow')
44
- * @param expectedIsPush Expected isPush value
45
- */
46
- function createWindowNavigationTests(
47
- methodName: 'pushWindow' | 'replaceWindow',
48
- expectedIsPush: boolean
49
- ) {
50
- describe(`🪟 ${methodName} Core Functionality Tests`, () => {
51
- it(`should support using current route path`, async () => {
52
- await router.push('/about');
53
- const result = await router[methodName]('/about');
54
-
55
- expect(result.type).toBe(RouteType[methodName]);
56
- expect(result.path).toBe('/about');
57
- expect(result.isPush).toBe(expectedIsPush);
58
- expect(result.handle).not.toBeNull();
59
- });
60
-
61
- it(`should support string path parameters`, async () => {
62
- const result = await router[methodName]('/user/123');
63
-
64
- expect(result.type).toBe(RouteType[methodName]);
65
- expect(result.path).toBe('/user/123');
66
- expect(result.params.id).toBe('123');
67
- expect(result.isPush).toBe(expectedIsPush);
68
- expect(result.handle).not.toBeNull();
69
- });
70
-
71
- it(`should support object parameters`, async () => {
72
- const result = await router[methodName]({
73
- path: '/user/456',
74
- query: { tab: 'profile' },
75
- hash: 'section1'
76
- });
77
-
78
- expect(result.type).toBe(RouteType[methodName]);
79
- expect(result.path).toBe('/user/456');
80
- expect(result.params.id).toBe('456');
81
- expect(result.query.tab).toBe('profile');
82
- expect(result.url.hash).toBe('#section1');
83
- expect(result.isPush).toBe(expectedIsPush);
84
- expect(result.handle).not.toBeNull();
85
- });
86
-
87
- it(`should correctly handle complete URLs`, async () => {
88
- const result = await router[methodName](
89
- 'https://example.com/user/789?sort=name#top'
90
- );
91
-
92
- expect(result.type).toBe(RouteType[methodName]);
93
- expect(result.url.href).toBe(
94
- 'https://example.com/user/789?sort=name#top'
95
- );
96
- expect(result.isPush).toBe(expectedIsPush);
97
- expect(result.handle).not.toBeNull();
98
- });
99
- });
100
-
101
- describe(`🎯 ${methodName} Specific Behavior Tests`, () => {
102
- it(`should set correct isPush flag`, async () => {
103
- const result = await router[methodName]('/about');
104
-
105
- expect(result.isPush).toBe(expectedIsPush);
106
- expect(result.type).toBe(RouteType[methodName]);
107
- });
108
-
109
- it(`should call location handler`, async () => {
110
- let locationCalled = false;
111
- let receivedRoute: Route | null = null;
112
-
113
- const windowRouter = new Router({
114
- routes: [{ path: '/', app: 'home' }],
115
- apps: mockApps,
116
- fallback: (to, from) => {
117
- locationCalled = true;
118
- receivedRoute = to;
119
- return { windowNavigation: true };
120
- }
121
- });
122
-
123
- await windowRouter.push('/');
124
- const result = await windowRouter[methodName]('/about');
125
-
126
- expect(locationCalled).toBe(true);
127
- expect(receivedRoute!.isPush).toBe(expectedIsPush);
128
- expect(result.handleResult).toEqual({ windowNavigation: true });
129
- windowRouter.destroy();
130
- });
131
-
132
- it(`should not update current route state`, async () => {
133
- await router.push('/about');
134
- const beforeRoute = router.route;
135
-
136
- await router[methodName]('/user/123');
137
- const afterRoute = router.route;
138
-
139
- expect(afterRoute.path).toBe(beforeRoute.path);
140
- expect(afterRoute.url.href).toBe(beforeRoute.url.href);
141
- });
142
-
143
- it(`should not trigger MicroApp update`, async () => {
144
- const updateSpy = vi.spyOn(router.microApp, '_update');
145
-
146
- await router[methodName]('/user/123');
147
-
148
- expect(updateSpy).not.toHaveBeenCalled();
149
- });
150
- });
151
-
152
- describe(`🛡️ ${methodName} Route Guards Tests`, () => {
153
- it(`should execute beforeEach guards`, async () => {
154
- let guardCalled = false;
155
- const unregister = router.beforeEach(async (to, from) => {
156
- guardCalled = true;
157
- expect(to.isPush).toBe(expectedIsPush);
158
- });
159
-
160
- await router[methodName]('/about');
161
-
162
- expect(guardCalled).toBe(true);
163
- unregister();
164
- });
165
-
166
- it(`should abort navigation when guard returns false`, async () => {
167
- const unregister = router.beforeEach((to, from) => {
168
- return false;
169
- });
170
-
171
- await expect(router[methodName]('/about')).rejects.toThrow(
172
- 'Navigation was aborted'
173
- );
174
- unregister();
175
- });
176
-
177
- it(`should support guard redirects`, async () => {
178
- const unregister = router.beforeEach(async (to) => {
179
- if (to.path === '/about') {
180
- return '/user/redirect';
181
- }
182
- });
183
-
184
- const result = await router[methodName]('/about');
185
-
186
- expect(result.path).toBe('/user/redirect');
187
- expect(result.params.id).toBe('redirect');
188
- unregister();
189
- });
190
-
191
- it(`should execute afterEach guards`, async () => {
192
- let guardCalled = false;
193
- const unregister = router.afterEach((to, from) => {
194
- guardCalled = true;
195
- expect(to.isPush).toBe(expectedIsPush);
196
- });
197
-
198
- await router[methodName]('/about');
199
-
200
- expect(guardCalled).toBe(true);
201
- unregister();
202
- });
203
- });
204
-
205
- describe(`🎭 ${methodName} Edge Cases Tests`, () => {
206
- it(`should handle non-existent routes`, async () => {
207
- const result = await router[methodName]('/nonexistent');
208
-
209
- expect(result.handle).not.toBeNull();
210
- expect(result.matched.length).toBe(0);
211
- expect(result.isPush).toBe(expectedIsPush);
212
- });
213
-
214
- it(`should handle empty string path`, async () => {
215
- const result = await router[methodName]('');
216
-
217
- expect(result.handle).not.toBeNull();
218
- expect(result.isPush).toBe(expectedIsPush);
219
- });
220
-
221
- it(`should handle special characters`, async () => {
222
- const result = await router[methodName](
223
- '/user/测试用户?name=张三&age=25#个人信息'
224
- );
225
-
226
- expect(result.handle).not.toBeNull();
227
- expect(result.isPush).toBe(expectedIsPush);
228
- expect(result.url.pathname).toContain(
229
- '%E6%B5%8B%E8%AF%95%E7%94%A8%E6%88%B7'
230
- );
231
- });
232
- });
233
-
234
- describe(`⚡ ${methodName} Task Cancellation and Concurrency Control`, () => {
235
- it(`should support concurrent calls`, async () => {
236
- const promises = [
237
- router[methodName]('/user/1').catch((err) => err),
238
- router[methodName]('/user/2').catch((err) => err),
239
- router[methodName]('/user/3').catch((err) => err)
240
- ];
241
-
242
- const results = await Promise.all(promises);
243
-
244
- const successResults = results.filter(
245
- (r) => !(r instanceof Error) && r.handle !== null
246
- );
247
- const errorResults = results.filter((r) => r instanceof Error);
248
-
249
- // At least one should succeed, others may be cancelled
250
- expect(successResults.length).toBeGreaterThan(0);
251
- expect(successResults.length + errorResults.length).toBe(3);
252
-
253
- successResults.forEach((result) => {
254
- expect(result.isPush).toBe(expectedIsPush);
255
- });
256
- });
257
-
258
- it(`should correctly handle rapid consecutive calls`, async () => {
259
- const results: Route[] = [];
260
-
261
- for (let i = 0; i < 5; i++) {
262
- results.push(await router[methodName](`/user/${i}`));
263
- }
264
-
265
- results.forEach((result, index) => {
266
- expect(result.handle).not.toBeNull();
267
- expect(result.params.id).toBe(String(index));
268
- expect(result.isPush).toBe(expectedIsPush);
269
- });
270
- });
271
- });
272
-
273
- describe(`❌ ${methodName} Error Handling`, () => {
274
- it(`should handle exceptions in guards`, async () => {
275
- const unregister = router.beforeEach(async () => {
276
- throw new Error('Guard error');
277
- });
278
-
279
- await expect(router[methodName]('/about')).rejects.toThrow();
280
- unregister();
281
- });
282
-
283
- it(`should handle location handler exceptions`, async () => {
284
- const windowRouter = new Router({
285
- routes: [{ path: '/', app: 'home' }],
286
- apps: mockApps,
287
- fallback: () => {
288
- throw new Error('Location handler error');
289
- }
290
- });
291
-
292
- await windowRouter.push('/');
293
-
294
- // Location handler exceptions will cause entire route handling to fail
295
- await expect(
296
- windowRouter[methodName]('/about')
297
- ).rejects.toThrow();
298
-
299
- windowRouter.destroy();
300
- });
301
- });
302
-
303
- describe(`🧩 ${methodName} Async Component Handling`, () => {
304
- it(`should correctly handle async components`, async () => {
305
- const asyncRouter = new Router({
306
- routes: [
307
- {
308
- path: '/async',
309
- app: 'home',
310
- asyncComponent: async () => {
311
- await new Promise((resolve) =>
312
- setTimeout(resolve, 10)
313
- );
314
- return () => 'AsyncComponent';
315
- }
316
- }
317
- ],
318
- apps: mockApps,
319
- fallback: (to, from) => {
320
- // For window navigation, fallback directly returns the result
321
- return { windowNavigation: true };
322
- }
323
- });
324
-
325
- const result = await asyncRouter[methodName]('/async');
326
-
327
- expect(result.handle).not.toBeNull();
328
- expect(result.isPush).toBe(expectedIsPush);
329
- // so the component remains undefined, only asyncComponent config exists
330
- expect(result.matched[0].component).toBeUndefined();
331
- expect(result.matched[0].asyncComponent).toBeDefined();
332
- expect(result.handleResult).toEqual({ windowNavigation: true });
333
- asyncRouter.destroy();
334
- });
335
-
336
- it(`should handle async component loading failures`, async () => {
337
- const asyncRouter = new Router({
338
- routes: [
339
- {
340
- path: '/async-error',
341
- app: 'home',
342
- asyncComponent: async () => {
343
- throw new Error('Component load failed');
344
- }
345
- }
346
- ],
347
- apps: mockApps,
348
- fallback: (to, from) => {
349
- return { fallbackHandled: true };
350
- }
351
- });
352
-
353
- const result = await asyncRouter[methodName]('/async-error');
354
-
355
- expect(result.handle).not.toBeNull();
356
- // Component should remain undefined since asyncComponent task is not executed
357
- expect(result.matched[0].component).toBeUndefined();
358
- expect(result.matched[0].asyncComponent).toBeDefined();
359
- expect(result.handleResult).toEqual({ fallbackHandled: true });
360
- asyncRouter.destroy();
361
- });
362
- });
363
-
364
- describe(`🔧 ${methodName} Differences from Other Methods`, () => {
365
- it(`should behave differently from push/replace methods`, async () => {
366
- await router.push('/about');
367
- const pushResult = await router.push('/user/123');
368
- const windowResult = await router[methodName]('/user/456');
369
-
370
- // push will update current route
371
- expect(router.route.path).toBe('/user/123');
372
-
373
- expect(windowResult.isPush).toBe(expectedIsPush);
374
- expect(windowResult.type).toBe(RouteType[methodName]);
375
-
376
- // Types are different
377
- expect(pushResult.type).toBe(RouteType.push);
378
- expect(windowResult.type).toBe(RouteType[methodName]);
379
- });
380
-
381
- it(`should maintain consistency with resolve method in URL parsing`, async () => {
382
- const resolvedRoute = router.resolve('/user/789');
383
- const windowRoute = await router[methodName]('/user/789');
384
-
385
- expect(windowRoute.url.href).toBe(resolvedRoute.url.href);
386
- expect(windowRoute.params).toEqual(resolvedRoute.params);
387
- expect(windowRoute.matched).toEqual(resolvedRoute.matched);
388
-
389
- expect(windowRoute.type).toBe(RouteType[methodName]);
390
- expect(resolvedRoute.type).toBe(RouteType.push);
391
- expect(windowRoute.isPush).toBe(expectedIsPush);
392
- expect(resolvedRoute.isPush).toBe(true);
393
- });
394
- });
395
- }
396
-
397
- createWindowNavigationTests('pushWindow', true);
398
-
399
- createWindowNavigationTests('replaceWindow', false);
400
-
401
- describe('🔄 pushWindow and replaceWindow Comparison Tests', () => {
402
- it('the only difference between the two methods should be the isPush flag', async () => {
403
- const pushResult = await router.pushWindow('/user/123');
404
- const replaceResult = await router.replaceWindow('/user/123');
405
-
406
- expect(pushResult.url.href).toBe(replaceResult.url.href);
407
- expect(pushResult.params).toEqual(replaceResult.params);
408
- expect(pushResult.query).toEqual(replaceResult.query);
409
- expect(pushResult.url.hash).toBe(replaceResult.url.hash);
410
- expect(pushResult.matched).toEqual(replaceResult.matched);
411
-
412
- expect(pushResult.isPush).toBe(true);
413
- expect(replaceResult.isPush).toBe(false);
414
- expect(pushResult.type).toBe(RouteType.pushWindow);
415
- expect(replaceResult.type).toBe(RouteType.replaceWindow);
416
- });
417
-
418
- it('both methods should call the same location handler', async () => {
419
- const locationCalls: Array<{
420
- method: string;
421
- isPush: boolean;
422
- path: string;
423
- }> = [];
424
-
425
- const windowRouter = new Router({
426
- routes: [{ path: '/', app: 'home' }],
427
- apps: mockApps,
428
- fallback: (to, from) => {
429
- locationCalls.push({
430
- method: to.type,
431
- isPush: to.isPush,
432
- path: to.path
433
- });
434
- return { called: true };
435
- }
436
- });
437
-
438
- await windowRouter.push('/');
439
- await windowRouter.pushWindow('/test');
440
- await windowRouter.replaceWindow('/test');
441
-
442
- expect(locationCalls).toHaveLength(2);
443
- expect(locationCalls[0]).toEqual({
444
- method: 'pushWindow',
445
- isPush: true,
446
- path: '/test'
447
- });
448
- expect(locationCalls[1]).toEqual({
449
- method: 'replaceWindow',
450
- isPush: false,
451
- path: '/test'
452
- });
453
-
454
- windowRouter.destroy();
455
- });
456
- });
457
- });