@esmx/router 3.0.0-rc.18 → 3.0.0-rc.20

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 (158) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +70 -0
  3. package/README.zh-CN.md +70 -0
  4. package/dist/error.d.ts +23 -0
  5. package/dist/error.mjs +61 -0
  6. package/dist/increment-id.d.ts +7 -0
  7. package/dist/increment-id.mjs +11 -0
  8. package/dist/index.d.ts +5 -3
  9. package/dist/index.mjs +14 -3
  10. package/dist/index.test.mjs +8 -0
  11. package/dist/location.d.ts +15 -0
  12. package/dist/location.mjs +53 -0
  13. package/dist/location.test.d.ts +8 -0
  14. package/dist/location.test.mjs +370 -0
  15. package/dist/matcher.d.ts +3 -0
  16. package/dist/matcher.mjs +44 -0
  17. package/dist/matcher.test.mjs +1492 -0
  18. package/dist/micro-app.d.ts +18 -0
  19. package/dist/micro-app.dom.test.d.ts +1 -0
  20. package/dist/micro-app.dom.test.mjs +532 -0
  21. package/dist/micro-app.mjs +80 -0
  22. package/dist/navigation.d.ts +43 -0
  23. package/dist/navigation.mjs +143 -0
  24. package/dist/navigation.test.d.ts +1 -0
  25. package/dist/navigation.test.mjs +681 -0
  26. package/dist/options.d.ts +4 -0
  27. package/dist/options.mjs +88 -0
  28. package/dist/route-task.d.ts +40 -0
  29. package/dist/route-task.mjs +75 -0
  30. package/dist/route-task.test.d.ts +1 -0
  31. package/dist/route-task.test.mjs +673 -0
  32. package/dist/route-transition.d.ts +53 -0
  33. package/dist/route-transition.mjs +307 -0
  34. package/dist/route-transition.test.d.ts +1 -0
  35. package/dist/route-transition.test.mjs +146 -0
  36. package/dist/route.d.ts +72 -0
  37. package/dist/route.mjs +194 -0
  38. package/dist/route.test.d.ts +1 -0
  39. package/dist/route.test.mjs +1664 -0
  40. package/dist/router-back.test.d.ts +1 -0
  41. package/dist/router-back.test.mjs +361 -0
  42. package/dist/router-forward.test.d.ts +1 -0
  43. package/dist/router-forward.test.mjs +376 -0
  44. package/dist/router-go.test.d.ts +1 -0
  45. package/dist/router-go.test.mjs +73 -0
  46. package/dist/router-guards-cleanup.test.d.ts +1 -0
  47. package/dist/router-guards-cleanup.test.mjs +437 -0
  48. package/dist/router-link.d.ts +10 -0
  49. package/dist/router-link.mjs +126 -0
  50. package/dist/router-push.test.d.ts +1 -0
  51. package/dist/router-push.test.mjs +115 -0
  52. package/dist/router-replace.test.d.ts +1 -0
  53. package/dist/router-replace.test.mjs +114 -0
  54. package/dist/router-resolve.test.d.ts +1 -0
  55. package/dist/router-resolve.test.mjs +393 -0
  56. package/dist/router-restart-app.dom.test.d.ts +1 -0
  57. package/dist/router-restart-app.dom.test.mjs +616 -0
  58. package/dist/router-window-navigation.test.d.ts +1 -0
  59. package/dist/router-window-navigation.test.mjs +359 -0
  60. package/dist/router.d.ts +109 -102
  61. package/dist/router.mjs +260 -361
  62. package/dist/types.d.ts +246 -0
  63. package/dist/types.mjs +18 -0
  64. package/dist/util.d.ts +26 -0
  65. package/dist/util.mjs +53 -0
  66. package/dist/util.test.d.ts +1 -0
  67. package/dist/util.test.mjs +1020 -0
  68. package/package.json +10 -13
  69. package/src/error.ts +84 -0
  70. package/src/increment-id.ts +12 -0
  71. package/src/index.test.ts +9 -0
  72. package/src/index.ts +54 -3
  73. package/src/location.test.ts +406 -0
  74. package/src/location.ts +96 -0
  75. package/src/matcher.test.ts +1685 -0
  76. package/src/matcher.ts +59 -0
  77. package/src/micro-app.dom.test.ts +708 -0
  78. package/src/micro-app.ts +101 -0
  79. package/src/navigation.test.ts +858 -0
  80. package/src/navigation.ts +195 -0
  81. package/src/options.ts +131 -0
  82. package/src/route-task.test.ts +901 -0
  83. package/src/route-task.ts +105 -0
  84. package/src/route-transition.test.ts +178 -0
  85. package/src/route-transition.ts +425 -0
  86. package/src/route.test.ts +2014 -0
  87. package/src/route.ts +308 -0
  88. package/src/router-back.test.ts +487 -0
  89. package/src/router-forward.test.ts +506 -0
  90. package/src/router-go.test.ts +91 -0
  91. package/src/router-guards-cleanup.test.ts +595 -0
  92. package/src/router-link.ts +235 -0
  93. package/src/router-push.test.ts +140 -0
  94. package/src/router-replace.test.ts +139 -0
  95. package/src/router-resolve.test.ts +475 -0
  96. package/src/router-restart-app.dom.test.ts +783 -0
  97. package/src/router-window-navigation.test.ts +457 -0
  98. package/src/router.ts +289 -470
  99. package/src/types.ts +341 -0
  100. package/src/util.test.ts +1262 -0
  101. package/src/util.ts +116 -0
  102. package/dist/history/abstract.d.ts +0 -29
  103. package/dist/history/abstract.mjs +0 -107
  104. package/dist/history/base.d.ts +0 -79
  105. package/dist/history/base.mjs +0 -275
  106. package/dist/history/html.d.ts +0 -30
  107. package/dist/history/html.mjs +0 -183
  108. package/dist/history/index.d.ts +0 -7
  109. package/dist/history/index.mjs +0 -16
  110. package/dist/matcher/create-matcher.d.ts +0 -5
  111. package/dist/matcher/create-matcher.mjs +0 -218
  112. package/dist/matcher/create-matcher.spec.mjs +0 -0
  113. package/dist/matcher/index.d.ts +0 -1
  114. package/dist/matcher/index.mjs +0 -1
  115. package/dist/task-pipe/index.d.ts +0 -1
  116. package/dist/task-pipe/index.mjs +0 -1
  117. package/dist/task-pipe/task.d.ts +0 -30
  118. package/dist/task-pipe/task.mjs +0 -66
  119. package/dist/types/index.d.ts +0 -694
  120. package/dist/types/index.mjs +0 -6
  121. package/dist/utils/bom.d.ts +0 -5
  122. package/dist/utils/bom.mjs +0 -10
  123. package/dist/utils/encoding.d.ts +0 -48
  124. package/dist/utils/encoding.mjs +0 -44
  125. package/dist/utils/guards.d.ts +0 -9
  126. package/dist/utils/guards.mjs +0 -12
  127. package/dist/utils/index.d.ts +0 -7
  128. package/dist/utils/index.mjs +0 -27
  129. package/dist/utils/path.d.ts +0 -60
  130. package/dist/utils/path.mjs +0 -282
  131. package/dist/utils/path.spec.mjs +0 -27
  132. package/dist/utils/scroll.d.ts +0 -25
  133. package/dist/utils/scroll.mjs +0 -59
  134. package/dist/utils/utils.d.ts +0 -16
  135. package/dist/utils/utils.mjs +0 -11
  136. package/dist/utils/warn.d.ts +0 -2
  137. package/dist/utils/warn.mjs +0 -12
  138. package/src/history/abstract.ts +0 -149
  139. package/src/history/base.ts +0 -408
  140. package/src/history/html.ts +0 -228
  141. package/src/history/index.ts +0 -20
  142. package/src/matcher/create-matcher.spec.ts +0 -3
  143. package/src/matcher/create-matcher.ts +0 -292
  144. package/src/matcher/index.ts +0 -1
  145. package/src/task-pipe/index.ts +0 -1
  146. package/src/task-pipe/task.ts +0 -97
  147. package/src/types/index.ts +0 -858
  148. package/src/utils/bom.ts +0 -14
  149. package/src/utils/encoding.ts +0 -153
  150. package/src/utils/guards.ts +0 -25
  151. package/src/utils/index.ts +0 -27
  152. package/src/utils/path.spec.ts +0 -32
  153. package/src/utils/path.ts +0 -418
  154. package/src/utils/scroll.ts +0 -120
  155. package/src/utils/utils.ts +0 -30
  156. package/src/utils/warn.ts +0 -13
  157. /package/dist/{matcher/create-matcher.spec.d.ts → index.test.d.ts} +0 -0
  158. /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
+ });