@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,506 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
|
2
|
+
import { Router } from './router';
|
|
3
|
+
import { RouteType, RouterMode } from './types';
|
|
4
|
+
import type { Route } from './types';
|
|
5
|
+
|
|
6
|
+
describe('Router.forward Tests', () => {
|
|
7
|
+
let router: Router;
|
|
8
|
+
let executionLog: string[];
|
|
9
|
+
|
|
10
|
+
beforeEach(async () => {
|
|
11
|
+
executionLog = [];
|
|
12
|
+
|
|
13
|
+
router = new Router({
|
|
14
|
+
mode: RouterMode.memory,
|
|
15
|
+
base: new URL('http://localhost:3000/'),
|
|
16
|
+
fallback: (to, from) => {
|
|
17
|
+
executionLog.push(`location-handler-${to.path}`);
|
|
18
|
+
},
|
|
19
|
+
routes: [
|
|
20
|
+
{
|
|
21
|
+
path: '/',
|
|
22
|
+
component: () => 'Home'
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
path: '/about',
|
|
26
|
+
component: () => 'About'
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
path: '/user/:id',
|
|
30
|
+
component: () => 'User',
|
|
31
|
+
beforeEnter: (to) => {
|
|
32
|
+
if (to.params.id === 'blocked') {
|
|
33
|
+
return false; // Block navigation
|
|
34
|
+
}
|
|
35
|
+
if (to.params.id === 'redirect') {
|
|
36
|
+
return '/about'; // Redirect
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
path: '/async',
|
|
42
|
+
asyncComponent: () =>
|
|
43
|
+
new Promise((resolve) => {
|
|
44
|
+
setTimeout(() => resolve('AsyncComponent'), 10);
|
|
45
|
+
})
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
path: '/async-error',
|
|
49
|
+
asyncComponent: () =>
|
|
50
|
+
new Promise((_, reject) => {
|
|
51
|
+
setTimeout(
|
|
52
|
+
() => reject(new Error('Load failed')),
|
|
53
|
+
10
|
|
54
|
+
);
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
await router.push('/');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
afterEach(() => {
|
|
64
|
+
router.destroy();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('🎯 Core Behavior', () => {
|
|
68
|
+
test('forward should return Promise<Route | null>', async () => {
|
|
69
|
+
await router.push('/about');
|
|
70
|
+
await router.back();
|
|
71
|
+
|
|
72
|
+
const route = await router.forward();
|
|
73
|
+
|
|
74
|
+
expect(route).toBeInstanceOf(Object);
|
|
75
|
+
expect(route?.path).toBe('/about');
|
|
76
|
+
expect(route?.handle).not.toBeNull();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('forward should navigate to next route', async () => {
|
|
80
|
+
await router.push('/about');
|
|
81
|
+
await router.push('/user/123');
|
|
82
|
+
await router.back(); // Back to /about
|
|
83
|
+
|
|
84
|
+
// Go forward to /user/123
|
|
85
|
+
const forwardRoute = await router.forward();
|
|
86
|
+
expect(forwardRoute?.path).toBe('/user/123');
|
|
87
|
+
expect(router.route.path).toBe('/user/123');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('forward should update router state', async () => {
|
|
91
|
+
await router.push('/about');
|
|
92
|
+
await router.back();
|
|
93
|
+
await router.forward();
|
|
94
|
+
|
|
95
|
+
expect(router.route.path).toBe('/about');
|
|
96
|
+
expect(router.route.handle).not.toBeNull();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe('🔄 History Navigation Logic', () => {
|
|
101
|
+
test('forward should navigate based on history stack', async () => {
|
|
102
|
+
// Establish history: / -> /about -> /user/123
|
|
103
|
+
await router.push('/about');
|
|
104
|
+
await router.push('/user/123');
|
|
105
|
+
|
|
106
|
+
// Go back twice to get to root
|
|
107
|
+
await router.back(); // to /about
|
|
108
|
+
await router.back(); // to /
|
|
109
|
+
|
|
110
|
+
// Go forward to /about
|
|
111
|
+
const route1 = await router.forward();
|
|
112
|
+
expect(route1?.path).toBe('/about');
|
|
113
|
+
expect(router.route.path).toBe('/about');
|
|
114
|
+
|
|
115
|
+
// Go forward again to /user/123
|
|
116
|
+
const route2 = await router.forward();
|
|
117
|
+
expect(route2?.path).toBe('/user/123');
|
|
118
|
+
expect(router.route.path).toBe('/user/123');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('forward beyond history boundaries should return null', async () => {
|
|
122
|
+
await router.push('/about');
|
|
123
|
+
await router.push('/user/123');
|
|
124
|
+
|
|
125
|
+
// We're already at the end of history
|
|
126
|
+
const route = await router.forward();
|
|
127
|
+
expect(route).toBe(null);
|
|
128
|
+
expect(router.route.path).toBe('/user/123'); // Route state unchanged
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('forward should return correct RouteType', async () => {
|
|
132
|
+
await router.push('/about');
|
|
133
|
+
await router.back();
|
|
134
|
+
|
|
135
|
+
const route = await router.forward();
|
|
136
|
+
|
|
137
|
+
expect(route?.type).toBe(RouteType.forward);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test('forward should keep isPush as false', async () => {
|
|
141
|
+
await router.push('/about');
|
|
142
|
+
await router.back();
|
|
143
|
+
|
|
144
|
+
const route = await router.forward();
|
|
145
|
+
|
|
146
|
+
expect(route?.isPush).toBe(false);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe('🏃 Concurrency Control', () => {
|
|
151
|
+
test('later initiated forward should cancel earlier forward', async () => {
|
|
152
|
+
await router.push('/about');
|
|
153
|
+
await router.push('/user/123');
|
|
154
|
+
await router.back(); // Back to /about
|
|
155
|
+
await router.back(); // Back to /
|
|
156
|
+
|
|
157
|
+
const [firstResult, secondResult] = await Promise.all([
|
|
158
|
+
router.forward(), // First operation, should succeed
|
|
159
|
+
router.forward() // Second operation, returns null due to first one in progress
|
|
160
|
+
]);
|
|
161
|
+
|
|
162
|
+
expect(firstResult?.handle).not.toBeNull();
|
|
163
|
+
expect(secondResult).toBe(null);
|
|
164
|
+
expect(router.route.path).toBe('/about'); // First operation result
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test('cancelled tasks should not affect micro-app state', async () => {
|
|
168
|
+
const updateSpy = vi.spyOn(router.microApp, '_update');
|
|
169
|
+
|
|
170
|
+
await router.push('/about');
|
|
171
|
+
await router.push('/user/123');
|
|
172
|
+
await router.back(); // Back to /about
|
|
173
|
+
await router.back(); // Back to /
|
|
174
|
+
|
|
175
|
+
updateSpy.mockClear();
|
|
176
|
+
|
|
177
|
+
const [firstResult, secondResult] = await Promise.all([
|
|
178
|
+
router.forward(), // First operation succeeds
|
|
179
|
+
router.forward() // Second operation returns null
|
|
180
|
+
]);
|
|
181
|
+
|
|
182
|
+
expect(firstResult?.handle).not.toBeNull();
|
|
183
|
+
expect(secondResult).toBe(null);
|
|
184
|
+
|
|
185
|
+
// Micro-app update should only be called by the first successful operation
|
|
186
|
+
expect(updateSpy).toHaveBeenCalledTimes(1);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe('🎭 Micro-app Integration', () => {
|
|
191
|
+
test('forward should trigger micro-app update', async () => {
|
|
192
|
+
const updateSpy = vi.spyOn(router.microApp, '_update');
|
|
193
|
+
|
|
194
|
+
await router.push('/about');
|
|
195
|
+
await router.back();
|
|
196
|
+
await router.forward();
|
|
197
|
+
|
|
198
|
+
expect(updateSpy).toHaveBeenCalled();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test('micro-app update should happen after route state update', async () => {
|
|
202
|
+
let routePathWhenUpdated: string | null = null;
|
|
203
|
+
|
|
204
|
+
vi.spyOn(router.microApp, '_update').mockImplementation(() => {
|
|
205
|
+
routePathWhenUpdated = router.route.path;
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
await router.push('/about');
|
|
209
|
+
await router.back();
|
|
210
|
+
await router.forward();
|
|
211
|
+
|
|
212
|
+
expect(routePathWhenUpdated).toBe('/about');
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
describe('⚡ Async Components & Forward', () => {
|
|
217
|
+
test('forward to async component route should wait for component loading', async () => {
|
|
218
|
+
await router.push('/async');
|
|
219
|
+
await router.back();
|
|
220
|
+
|
|
221
|
+
const route = await router.forward();
|
|
222
|
+
expect(route?.path).toBe('/async');
|
|
223
|
+
expect(route?.handle).not.toBeNull();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test('forward to failed async component route should return error status', async () => {
|
|
227
|
+
await router.push('/about');
|
|
228
|
+
|
|
229
|
+
// Try to push to async-error (this should fail)
|
|
230
|
+
await expect(router.push('/async-error')).rejects.toThrow();
|
|
231
|
+
|
|
232
|
+
// Should still be at /about since navigation failed
|
|
233
|
+
expect(router.route.path).toBe('/about');
|
|
234
|
+
|
|
235
|
+
// Forward should work normally (no async-error in history)
|
|
236
|
+
const route = await router.forward();
|
|
237
|
+
expect(route).toBe(null); // No forward history
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
describe('🛡️ Forward Guard Behavior', () => {
|
|
242
|
+
test('forward to guard-blocked route should throw navigation aborted error', async () => {
|
|
243
|
+
// Try to push to blocked route first (should fail)
|
|
244
|
+
await expect(router.push('/user/blocked')).rejects.toThrow();
|
|
245
|
+
|
|
246
|
+
// Router should still be at initial route
|
|
247
|
+
expect(router.route.path).toBe('/');
|
|
248
|
+
|
|
249
|
+
// Forward should return null since there's no forward history
|
|
250
|
+
const result = await router.forward();
|
|
251
|
+
expect(result).toBe(null);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test('forward to route with redirect guard should navigate to redirect route', async () => {
|
|
255
|
+
await router.push('/user/redirect');
|
|
256
|
+
await router.back();
|
|
257
|
+
|
|
258
|
+
const route = await router.forward();
|
|
259
|
+
expect(route?.path).toBe('/about'); // Should redirect to /about
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test('afterEach only executes when forward succeeds', async () => {
|
|
263
|
+
let afterEachCalled = false;
|
|
264
|
+
|
|
265
|
+
const testRouter = new Router({
|
|
266
|
+
mode: RouterMode.memory,
|
|
267
|
+
base: new URL('http://localhost:3000/'),
|
|
268
|
+
routes: [
|
|
269
|
+
{ path: '/', component: 'Home' },
|
|
270
|
+
{ path: '/about', component: 'About' },
|
|
271
|
+
{
|
|
272
|
+
path: '/blocked',
|
|
273
|
+
component: 'Blocked',
|
|
274
|
+
beforeEnter: () => false
|
|
275
|
+
}
|
|
276
|
+
]
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
testRouter.afterEach(() => {
|
|
280
|
+
afterEachCalled = true;
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
await testRouter.push('/about');
|
|
284
|
+
afterEachCalled = false; // Reset after successful navigation
|
|
285
|
+
|
|
286
|
+
await testRouter.back(); // Go back to /
|
|
287
|
+
afterEachCalled = false; // Reset after back navigation
|
|
288
|
+
|
|
289
|
+
// This forward should succeed and trigger afterEach
|
|
290
|
+
await testRouter.forward(); // Forward to /about
|
|
291
|
+
expect(afterEachCalled).toBe(true);
|
|
292
|
+
|
|
293
|
+
testRouter.destroy();
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test('beforeEach guard should be called during forward operation', async () => {
|
|
297
|
+
let beforeEachCalled = false;
|
|
298
|
+
|
|
299
|
+
router.beforeEach(() => {
|
|
300
|
+
beforeEachCalled = true;
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
await router.push('/about');
|
|
304
|
+
await router.back();
|
|
305
|
+
await router.forward();
|
|
306
|
+
|
|
307
|
+
expect(beforeEachCalled).toBe(true);
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
describe('💾 History Management', () => {
|
|
312
|
+
test('forward should navigate correctly in history stack', async () => {
|
|
313
|
+
// Build history stack
|
|
314
|
+
await router.push('/about');
|
|
315
|
+
await router.push('/user/123');
|
|
316
|
+
await router.back(); // Back to /about
|
|
317
|
+
|
|
318
|
+
const route = await router.forward();
|
|
319
|
+
expect(route?.path).toBe('/user/123');
|
|
320
|
+
expect(router.route.path).toBe('/user/123');
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
test('forward operation should not create new history entries', async () => {
|
|
324
|
+
await router.push('/about');
|
|
325
|
+
await router.push('/user/123');
|
|
326
|
+
await router.back(); // Back to /about
|
|
327
|
+
|
|
328
|
+
// Go forward
|
|
329
|
+
await router.forward();
|
|
330
|
+
|
|
331
|
+
// Should be able to go back again
|
|
332
|
+
const backRoute = await router.back();
|
|
333
|
+
expect(backRoute?.path).toBe('/about');
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
describe('❌ Error Handling', () => {
|
|
338
|
+
test('forward to non-existent route should trigger location handling', async () => {
|
|
339
|
+
// This tests the boundary case where the router falls back to location handling
|
|
340
|
+
const fallbackSpy = vi.fn();
|
|
341
|
+
const testRouter = new Router({
|
|
342
|
+
mode: RouterMode.memory,
|
|
343
|
+
base: new URL('http://localhost:3000/'),
|
|
344
|
+
fallback: fallbackSpy,
|
|
345
|
+
routes: [{ path: '/', component: 'Home' }]
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
await testRouter.push('/');
|
|
349
|
+
|
|
350
|
+
// Since we only have one route, forward should return null
|
|
351
|
+
const result = await testRouter.forward();
|
|
352
|
+
expect(result).toBe(null);
|
|
353
|
+
|
|
354
|
+
testRouter.destroy();
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test('exceptions during forward process should propagate correctly', async () => {
|
|
358
|
+
const testRouter = new Router({
|
|
359
|
+
mode: RouterMode.memory,
|
|
360
|
+
base: new URL('http://localhost:3000/'),
|
|
361
|
+
routes: [
|
|
362
|
+
{ path: '/', component: 'Home' },
|
|
363
|
+
{ path: '/about', component: 'About' }
|
|
364
|
+
]
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
await testRouter.push('/about');
|
|
368
|
+
await testRouter.back();
|
|
369
|
+
|
|
370
|
+
// Add guard that throws error after history is established
|
|
371
|
+
testRouter.beforeEach(() => {
|
|
372
|
+
throw new Error('Guard error');
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
await expect(testRouter.forward()).rejects.toThrow('Guard error');
|
|
376
|
+
|
|
377
|
+
testRouter.destroy();
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
describe('🔍 Edge Cases', () => {
|
|
382
|
+
test('forward should handle special character paths correctly', async () => {
|
|
383
|
+
const testRouter = new Router({
|
|
384
|
+
mode: RouterMode.memory,
|
|
385
|
+
base: new URL('http://localhost:3000/'),
|
|
386
|
+
routes: [
|
|
387
|
+
{ path: '/', component: 'Home' },
|
|
388
|
+
{ path: '/special', component: 'Special' }
|
|
389
|
+
],
|
|
390
|
+
fallback: () => ({ component: 'Fallback' })
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
// Initialize router at root
|
|
394
|
+
await testRouter.push('/');
|
|
395
|
+
|
|
396
|
+
// Navigate to simple route
|
|
397
|
+
await testRouter.push('/special');
|
|
398
|
+
expect(testRouter.route.path).toBe('/special');
|
|
399
|
+
|
|
400
|
+
// Go back to root
|
|
401
|
+
await testRouter.back();
|
|
402
|
+
expect(testRouter.route.path).toBe('/');
|
|
403
|
+
|
|
404
|
+
// Forward to special route
|
|
405
|
+
const route = await testRouter.forward();
|
|
406
|
+
|
|
407
|
+
expect(route?.path).toBe('/special');
|
|
408
|
+
expect(testRouter.route.path).toBe('/special');
|
|
409
|
+
|
|
410
|
+
testRouter.destroy();
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
describe('🔗 Integration with Other Navigation Methods', () => {
|
|
415
|
+
test('forward should behave consistently with go(1)', async () => {
|
|
416
|
+
await router.push('/about');
|
|
417
|
+
await router.push('/user/123');
|
|
418
|
+
await router.back(); // Back to /about
|
|
419
|
+
await router.back(); // Back to /
|
|
420
|
+
|
|
421
|
+
const forwardRoute = await router.forward();
|
|
422
|
+
const goRoute = await router.go(1);
|
|
423
|
+
|
|
424
|
+
expect(forwardRoute?.path).toBe('/about');
|
|
425
|
+
expect(goRoute?.path).toBe('/user/123');
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
test('push after forward should handle history correctly', async () => {
|
|
429
|
+
await router.push('/about');
|
|
430
|
+
await router.push('/user/123');
|
|
431
|
+
await router.back(); // Back to /about
|
|
432
|
+
|
|
433
|
+
// Go forward
|
|
434
|
+
await router.forward();
|
|
435
|
+
|
|
436
|
+
// Push new route
|
|
437
|
+
await router.push('/user/456');
|
|
438
|
+
|
|
439
|
+
expect(router.route.path).toBe('/user/456');
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
describe('🔧 handleBackBoundary Callback Tests', () => {
|
|
444
|
+
test('forward beyond boundaries should not trigger handleBackBoundary', async () => {
|
|
445
|
+
let handleBackBoundaryCalled = false;
|
|
446
|
+
|
|
447
|
+
const testRouter = new Router({
|
|
448
|
+
mode: RouterMode.memory,
|
|
449
|
+
base: new URL('http://localhost:3000/'),
|
|
450
|
+
routes: [{ path: '/', component: 'Home' }],
|
|
451
|
+
handleBackBoundary: () => {
|
|
452
|
+
handleBackBoundaryCalled = true;
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
await testRouter.push('/');
|
|
457
|
+
|
|
458
|
+
const result = await testRouter.forward();
|
|
459
|
+
expect(result).toBe(null);
|
|
460
|
+
expect(handleBackBoundaryCalled).toBe(false); // Should NOT be called for forward
|
|
461
|
+
|
|
462
|
+
testRouter.destroy();
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
test('should not error when no handleBackBoundary callback', async () => {
|
|
466
|
+
const testRouter = new Router({
|
|
467
|
+
mode: RouterMode.memory,
|
|
468
|
+
base: new URL('http://localhost:3000/'),
|
|
469
|
+
routes: [{ path: '/', component: 'Home' }]
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
await testRouter.push('/');
|
|
473
|
+
|
|
474
|
+
const result = await testRouter.forward();
|
|
475
|
+
expect(result).toBe(null);
|
|
476
|
+
|
|
477
|
+
testRouter.destroy();
|
|
478
|
+
});
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
describe('🔄 Navigation Result Handling', () => {
|
|
482
|
+
test('should correctly handle successful navigation result', async () => {
|
|
483
|
+
await router.push('/about');
|
|
484
|
+
await router.back();
|
|
485
|
+
const route = await router.forward();
|
|
486
|
+
|
|
487
|
+
expect(route?.path).toBe('/about');
|
|
488
|
+
expect(router.route.path).toBe('/about');
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
test('should return null directly when Navigation returns null', async () => {
|
|
492
|
+
const testRouter = new Router({
|
|
493
|
+
mode: RouterMode.memory,
|
|
494
|
+
base: new URL('http://localhost:3000/'),
|
|
495
|
+
routes: [{ path: '/', component: 'Home' }]
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
await testRouter.push('/');
|
|
499
|
+
|
|
500
|
+
const result = await testRouter.forward();
|
|
501
|
+
expect(result).toBe(null);
|
|
502
|
+
|
|
503
|
+
testRouter.destroy();
|
|
504
|
+
});
|
|
505
|
+
});
|
|
506
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
|
|
2
|
+
import { Router } from './router';
|
|
3
|
+
import { RouterMode } from './types';
|
|
4
|
+
|
|
5
|
+
describe('Router Go Tests', () => {
|
|
6
|
+
let router: Router;
|
|
7
|
+
|
|
8
|
+
beforeEach(async () => {
|
|
9
|
+
router = new Router({
|
|
10
|
+
mode: RouterMode.memory,
|
|
11
|
+
base: new URL('http://localhost:3000/'),
|
|
12
|
+
routes: [
|
|
13
|
+
{ path: '/', component: () => 'Home' },
|
|
14
|
+
{ path: '/user/:id', component: () => 'User' },
|
|
15
|
+
{ path: '/about', component: () => 'About' }
|
|
16
|
+
]
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
await router.replace('/');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
router.destroy();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe('Basic go navigation', () => {
|
|
27
|
+
test('should go back successfully', async () => {
|
|
28
|
+
await router.push('/about');
|
|
29
|
+
await router.push('/user/123');
|
|
30
|
+
|
|
31
|
+
const route = await router.go(-1);
|
|
32
|
+
expect(route?.path).toBe('/about');
|
|
33
|
+
expect(router.route.path).toBe('/about');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('should go forward successfully', async () => {
|
|
37
|
+
await router.push('/about');
|
|
38
|
+
await router.push('/user/123');
|
|
39
|
+
await router.back();
|
|
40
|
+
|
|
41
|
+
const route = await router.go(1);
|
|
42
|
+
expect(route?.path).toBe('/user/123');
|
|
43
|
+
expect(router.route.path).toBe('/user/123');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('should handle go to specific position', async () => {
|
|
47
|
+
await router.push('/about');
|
|
48
|
+
await router.push('/user/123');
|
|
49
|
+
await router.push('/user/456');
|
|
50
|
+
|
|
51
|
+
const route = await router.go(-2);
|
|
52
|
+
expect(route?.path).toBe('/about');
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('Error handling', () => {
|
|
57
|
+
test('should handle go when history is empty', async () => {
|
|
58
|
+
const route = await router.go(-1);
|
|
59
|
+
expect(route).toBe(null);
|
|
60
|
+
|
|
61
|
+
const route2 = await router.go(1);
|
|
62
|
+
expect(route2).toBe(null);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('should handle invalid delta values', async () => {
|
|
66
|
+
const route = await router.go(0);
|
|
67
|
+
expect(route).toBe(null); // go(0) should return null as no navigation occurs
|
|
68
|
+
|
|
69
|
+
const route2 = await router.go(-10);
|
|
70
|
+
expect(route2).toBe(null);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('History boundaries', () => {
|
|
75
|
+
test('should return null when going beyond history', async () => {
|
|
76
|
+
const route = await router.go(-10);
|
|
77
|
+
expect(route).toBe(null);
|
|
78
|
+
|
|
79
|
+
const route2 = await router.go(10);
|
|
80
|
+
expect(route2).toBe(null);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('should handle boundary conditions gracefully', async () => {
|
|
84
|
+
await router.push('/about');
|
|
85
|
+
|
|
86
|
+
const route = await router.go(-2);
|
|
87
|
+
expect(route).toBe(null);
|
|
88
|
+
expect(router.route.path).toBe('/about');
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
});
|