@esmx/router 3.0.0-rc.17 → 3.0.0-rc.19
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/LICENSE +1 -1
- package/README.md +70 -0
- package/README.zh-CN.md +70 -0
- package/dist/error.d.ts +23 -0
- package/dist/error.mjs +61 -0
- package/dist/increment-id.d.ts +7 -0
- package/dist/increment-id.mjs +11 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.mjs +14 -3
- package/dist/index.test.mjs +8 -0
- package/dist/location.d.ts +15 -0
- package/dist/location.mjs +53 -0
- package/dist/location.test.d.ts +8 -0
- package/dist/location.test.mjs +370 -0
- package/dist/matcher.d.ts +3 -0
- package/dist/matcher.mjs +44 -0
- package/dist/matcher.test.mjs +1492 -0
- package/dist/micro-app.d.ts +18 -0
- package/dist/micro-app.dom.test.d.ts +1 -0
- package/dist/micro-app.dom.test.mjs +532 -0
- package/dist/micro-app.mjs +80 -0
- package/dist/navigation.d.ts +43 -0
- package/dist/navigation.mjs +143 -0
- package/dist/navigation.test.d.ts +1 -0
- package/dist/navigation.test.mjs +681 -0
- package/dist/options.d.ts +4 -0
- package/dist/options.mjs +88 -0
- package/dist/route-task.d.ts +40 -0
- package/dist/route-task.mjs +75 -0
- package/dist/route-task.test.d.ts +1 -0
- package/dist/route-task.test.mjs +673 -0
- package/dist/route-transition.d.ts +53 -0
- package/dist/route-transition.mjs +307 -0
- package/dist/route-transition.test.d.ts +1 -0
- package/dist/route-transition.test.mjs +146 -0
- package/dist/route.d.ts +72 -0
- package/dist/route.mjs +194 -0
- package/dist/route.test.d.ts +1 -0
- package/dist/route.test.mjs +1664 -0
- package/dist/router-back.test.d.ts +1 -0
- package/dist/router-back.test.mjs +361 -0
- package/dist/router-forward.test.d.ts +1 -0
- package/dist/router-forward.test.mjs +376 -0
- package/dist/router-go.test.d.ts +1 -0
- package/dist/router-go.test.mjs +73 -0
- package/dist/router-guards-cleanup.test.d.ts +1 -0
- package/dist/router-guards-cleanup.test.mjs +437 -0
- package/dist/router-link.d.ts +10 -0
- package/dist/router-link.mjs +126 -0
- package/dist/router-push.test.d.ts +1 -0
- package/dist/router-push.test.mjs +115 -0
- package/dist/router-replace.test.d.ts +1 -0
- package/dist/router-replace.test.mjs +114 -0
- package/dist/router-resolve.test.d.ts +1 -0
- package/dist/router-resolve.test.mjs +393 -0
- package/dist/router-restart-app.dom.test.d.ts +1 -0
- package/dist/router-restart-app.dom.test.mjs +616 -0
- package/dist/router-window-navigation.test.d.ts +1 -0
- package/dist/router-window-navigation.test.mjs +359 -0
- package/dist/router.d.ts +109 -102
- package/dist/router.mjs +260 -361
- package/dist/types.d.ts +246 -0
- package/dist/types.mjs +18 -0
- package/dist/util.d.ts +26 -0
- package/dist/util.mjs +53 -0
- package/dist/util.test.d.ts +1 -0
- package/dist/util.test.mjs +1020 -0
- package/package.json +10 -13
- package/src/error.ts +84 -0
- package/src/increment-id.ts +12 -0
- package/src/index.test.ts +9 -0
- package/src/index.ts +54 -3
- package/src/location.test.ts +406 -0
- package/src/location.ts +96 -0
- package/src/matcher.test.ts +1685 -0
- package/src/matcher.ts +59 -0
- package/src/micro-app.dom.test.ts +708 -0
- package/src/micro-app.ts +101 -0
- package/src/navigation.test.ts +858 -0
- package/src/navigation.ts +195 -0
- package/src/options.ts +131 -0
- package/src/route-task.test.ts +901 -0
- package/src/route-task.ts +105 -0
- package/src/route-transition.test.ts +178 -0
- package/src/route-transition.ts +425 -0
- package/src/route.test.ts +2014 -0
- package/src/route.ts +308 -0
- package/src/router-back.test.ts +487 -0
- package/src/router-forward.test.ts +506 -0
- package/src/router-go.test.ts +91 -0
- package/src/router-guards-cleanup.test.ts +595 -0
- package/src/router-link.ts +235 -0
- package/src/router-push.test.ts +140 -0
- package/src/router-replace.test.ts +139 -0
- package/src/router-resolve.test.ts +475 -0
- package/src/router-restart-app.dom.test.ts +783 -0
- package/src/router-window-navigation.test.ts +457 -0
- package/src/router.ts +289 -470
- package/src/types.ts +341 -0
- package/src/util.test.ts +1262 -0
- package/src/util.ts +116 -0
- package/dist/history/abstract.d.ts +0 -29
- package/dist/history/abstract.mjs +0 -107
- package/dist/history/base.d.ts +0 -79
- package/dist/history/base.mjs +0 -275
- package/dist/history/html.d.ts +0 -22
- package/dist/history/html.mjs +0 -183
- package/dist/history/index.d.ts +0 -7
- package/dist/history/index.mjs +0 -16
- package/dist/matcher/create-matcher.d.ts +0 -5
- package/dist/matcher/create-matcher.mjs +0 -218
- package/dist/matcher/create-matcher.spec.mjs +0 -0
- package/dist/matcher/index.d.ts +0 -1
- package/dist/matcher/index.mjs +0 -1
- package/dist/task-pipe/index.d.ts +0 -1
- package/dist/task-pipe/index.mjs +0 -1
- package/dist/task-pipe/task.d.ts +0 -30
- package/dist/task-pipe/task.mjs +0 -66
- package/dist/utils/bom.d.ts +0 -5
- package/dist/utils/bom.mjs +0 -10
- package/dist/utils/encoding.d.ts +0 -48
- package/dist/utils/encoding.mjs +0 -44
- package/dist/utils/guards.d.ts +0 -9
- package/dist/utils/guards.mjs +0 -12
- package/dist/utils/index.d.ts +0 -7
- package/dist/utils/index.mjs +0 -27
- package/dist/utils/path.d.ts +0 -60
- package/dist/utils/path.mjs +0 -281
- package/dist/utils/path.spec.mjs +0 -27
- package/dist/utils/scroll.d.ts +0 -25
- package/dist/utils/scroll.mjs +0 -59
- package/dist/utils/utils.d.ts +0 -16
- package/dist/utils/utils.mjs +0 -11
- package/dist/utils/warn.d.ts +0 -2
- package/dist/utils/warn.mjs +0 -12
- package/src/history/abstract.ts +0 -149
- package/src/history/base.ts +0 -408
- package/src/history/html.ts +0 -228
- package/src/history/index.ts +0 -20
- package/src/matcher/create-matcher.spec.ts +0 -3
- package/src/matcher/create-matcher.ts +0 -293
- package/src/matcher/index.ts +0 -1
- package/src/task-pipe/index.ts +0 -1
- package/src/task-pipe/task.ts +0 -97
- package/src/utils/bom.ts +0 -14
- package/src/utils/encoding.ts +0 -153
- package/src/utils/guards.ts +0 -25
- package/src/utils/index.ts +0 -27
- package/src/utils/path.spec.ts +0 -32
- package/src/utils/path.ts +0 -417
- package/src/utils/scroll.ts +0 -120
- package/src/utils/utils.ts +0 -30
- package/src/utils/warn.ts +0 -13
- /package/dist/{matcher/create-matcher.spec.d.ts → index.test.d.ts} +0 -0
- /package/dist/{utils/path.spec.d.ts → matcher.test.d.ts} +0 -0
|
@@ -0,0 +1,457 @@
|
|
|
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
|
+
});
|