@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.
- 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 -30
- 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/types/index.d.ts +0 -694
- package/dist/types/index.mjs +0 -6
- 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 -282
- 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 -292
- 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/types/index.ts +0 -858
- 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 -418
- 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,595 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
|
2
|
+
import type { Route } from './route';
|
|
3
|
+
import { Router } from './router';
|
|
4
|
+
import { RouterMode } from './types';
|
|
5
|
+
import { removeFromArray } from './util';
|
|
6
|
+
|
|
7
|
+
describe('Router Guards Cleanup Tests', () => {
|
|
8
|
+
let router: Router;
|
|
9
|
+
|
|
10
|
+
beforeEach(async () => {
|
|
11
|
+
router = new Router({
|
|
12
|
+
mode: RouterMode.memory,
|
|
13
|
+
base: new URL('http://localhost:3000/'),
|
|
14
|
+
routes: [
|
|
15
|
+
{
|
|
16
|
+
path: '/',
|
|
17
|
+
component: () => 'Home'
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
path: '/test1',
|
|
21
|
+
component: () => 'Test1'
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
path: '/test2',
|
|
25
|
+
component: () => 'Test2'
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
path: '/test3',
|
|
29
|
+
component: () => 'Test3'
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
await router.replace('/');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
router.destroy();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('🔥 Guard Cleanup Effect Verification', () => {
|
|
42
|
+
describe('beforeEach cleanup effects', () => {
|
|
43
|
+
test('guard should not execute after cleanup', async () => {
|
|
44
|
+
const spy = vi.fn();
|
|
45
|
+
const unregister = router.beforeEach(spy);
|
|
46
|
+
|
|
47
|
+
await router.push('/test1');
|
|
48
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
49
|
+
|
|
50
|
+
// Cleanup guard
|
|
51
|
+
unregister();
|
|
52
|
+
|
|
53
|
+
await router.push('/test2');
|
|
54
|
+
expect(spy).toHaveBeenCalledTimes(1); // Still 1 time, no increase
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('guard should not execute after cleanup for multiple navigations', async () => {
|
|
58
|
+
const spy = vi.fn();
|
|
59
|
+
const unregister = router.beforeEach(spy);
|
|
60
|
+
|
|
61
|
+
await router.push('/test1');
|
|
62
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
63
|
+
|
|
64
|
+
unregister();
|
|
65
|
+
|
|
66
|
+
await router.push('/test2');
|
|
67
|
+
await router.push('/test3');
|
|
68
|
+
await router.push('/');
|
|
69
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('async guard should not execute after cleanup', async () => {
|
|
73
|
+
const spy = vi.fn();
|
|
74
|
+
const asyncGuard = async (to: Route, from: Route | null) => {
|
|
75
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
76
|
+
spy(to, from);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const unregister = router.beforeEach(asyncGuard);
|
|
80
|
+
|
|
81
|
+
await router.push('/test1');
|
|
82
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
83
|
+
|
|
84
|
+
unregister();
|
|
85
|
+
|
|
86
|
+
await router.push('/test2');
|
|
87
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('afterEach cleanup effects', () => {
|
|
92
|
+
test('guard should not execute after cleanup', async () => {
|
|
93
|
+
const spy = vi.fn();
|
|
94
|
+
const unregister = router.afterEach(spy);
|
|
95
|
+
|
|
96
|
+
await router.push('/test1');
|
|
97
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
98
|
+
|
|
99
|
+
unregister();
|
|
100
|
+
|
|
101
|
+
await router.push('/test2');
|
|
102
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('guard should not execute after cleanup for multiple navigations', async () => {
|
|
106
|
+
const spy = vi.fn();
|
|
107
|
+
const unregister = router.afterEach(spy);
|
|
108
|
+
|
|
109
|
+
await router.push('/test1');
|
|
110
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
111
|
+
|
|
112
|
+
unregister();
|
|
113
|
+
|
|
114
|
+
await router.push('/test2');
|
|
115
|
+
await router.push('/test3');
|
|
116
|
+
await router.push('/');
|
|
117
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('⚡ Multiple Guards Independent Cleanup', () => {
|
|
123
|
+
describe('beforeEach multiple guards cleanup', () => {
|
|
124
|
+
test('cleaning single guard should not affect other guards', async () => {
|
|
125
|
+
const spy1 = vi.fn();
|
|
126
|
+
const spy2 = vi.fn();
|
|
127
|
+
const spy3 = vi.fn();
|
|
128
|
+
|
|
129
|
+
const unregister1 = router.beforeEach(spy1);
|
|
130
|
+
const unregister2 = router.beforeEach(spy2);
|
|
131
|
+
const unregister3 = router.beforeEach(spy3);
|
|
132
|
+
|
|
133
|
+
await router.push('/test1');
|
|
134
|
+
expect(spy1).toHaveBeenCalledTimes(1);
|
|
135
|
+
expect(spy2).toHaveBeenCalledTimes(1);
|
|
136
|
+
expect(spy3).toHaveBeenCalledTimes(1);
|
|
137
|
+
|
|
138
|
+
unregister2();
|
|
139
|
+
|
|
140
|
+
await router.push('/test2');
|
|
141
|
+
expect(spy1).toHaveBeenCalledTimes(2);
|
|
142
|
+
expect(spy2).toHaveBeenCalledTimes(1); // No increase
|
|
143
|
+
expect(spy3).toHaveBeenCalledTimes(2);
|
|
144
|
+
|
|
145
|
+
// Cleanup remaining guards
|
|
146
|
+
unregister1();
|
|
147
|
+
unregister3();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test('cleaning multiple guards should work correctly', async () => {
|
|
151
|
+
const spy1 = vi.fn();
|
|
152
|
+
const spy2 = vi.fn();
|
|
153
|
+
const spy3 = vi.fn();
|
|
154
|
+
const spy4 = vi.fn();
|
|
155
|
+
|
|
156
|
+
const unregister1 = router.beforeEach(spy1);
|
|
157
|
+
const unregister2 = router.beforeEach(spy2);
|
|
158
|
+
const unregister3 = router.beforeEach(spy3);
|
|
159
|
+
const unregister4 = router.beforeEach(spy4);
|
|
160
|
+
|
|
161
|
+
await router.push('/test1');
|
|
162
|
+
expect(spy1).toHaveBeenCalledTimes(1);
|
|
163
|
+
expect(spy2).toHaveBeenCalledTimes(1);
|
|
164
|
+
expect(spy3).toHaveBeenCalledTimes(1);
|
|
165
|
+
expect(spy4).toHaveBeenCalledTimes(1);
|
|
166
|
+
|
|
167
|
+
// Cleanup 1st and 3rd guards
|
|
168
|
+
unregister1();
|
|
169
|
+
unregister3();
|
|
170
|
+
|
|
171
|
+
spy1.mockClear();
|
|
172
|
+
spy2.mockClear();
|
|
173
|
+
spy3.mockClear();
|
|
174
|
+
spy4.mockClear();
|
|
175
|
+
|
|
176
|
+
await router.push('/test2');
|
|
177
|
+
expect(spy1).not.toHaveBeenCalled();
|
|
178
|
+
expect(spy2).toHaveBeenCalledTimes(1);
|
|
179
|
+
expect(spy3).not.toHaveBeenCalled();
|
|
180
|
+
expect(spy4).toHaveBeenCalledTimes(1);
|
|
181
|
+
|
|
182
|
+
// Cleanup remaining guards
|
|
183
|
+
unregister2();
|
|
184
|
+
unregister4();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test('guard execution order should remain correct after cleanup', async () => {
|
|
188
|
+
const executionOrder: string[] = [];
|
|
189
|
+
|
|
190
|
+
const guard1 = () => {
|
|
191
|
+
executionOrder.push('guard1');
|
|
192
|
+
};
|
|
193
|
+
const guard2 = () => {
|
|
194
|
+
executionOrder.push('guard2');
|
|
195
|
+
};
|
|
196
|
+
const guard3 = () => {
|
|
197
|
+
executionOrder.push('guard3');
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
router.beforeEach(guard1);
|
|
201
|
+
const unregister2 = router.beforeEach(guard2);
|
|
202
|
+
router.beforeEach(guard3);
|
|
203
|
+
|
|
204
|
+
await router.push('/test1');
|
|
205
|
+
expect(executionOrder).toEqual(['guard1', 'guard2', 'guard3']);
|
|
206
|
+
|
|
207
|
+
executionOrder.length = 0;
|
|
208
|
+
unregister2(); // Cleanup middle guard
|
|
209
|
+
|
|
210
|
+
await router.push('/test2');
|
|
211
|
+
expect(executionOrder).toEqual(['guard1', 'guard3']);
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
describe('afterEach multiple guards cleanup', () => {
|
|
216
|
+
test('cleaning single afterEach guard should not affect other guards', async () => {
|
|
217
|
+
const spy1 = vi.fn();
|
|
218
|
+
const spy2 = vi.fn();
|
|
219
|
+
const spy3 = vi.fn();
|
|
220
|
+
|
|
221
|
+
const unregister1 = router.afterEach(spy1);
|
|
222
|
+
const unregister2 = router.afterEach(spy2);
|
|
223
|
+
const unregister3 = router.afterEach(spy3);
|
|
224
|
+
|
|
225
|
+
await router.push('/test1');
|
|
226
|
+
expect(spy1).toHaveBeenCalledTimes(1);
|
|
227
|
+
expect(spy2).toHaveBeenCalledTimes(1);
|
|
228
|
+
expect(spy3).toHaveBeenCalledTimes(1);
|
|
229
|
+
|
|
230
|
+
unregister2();
|
|
231
|
+
|
|
232
|
+
await router.push('/test2');
|
|
233
|
+
expect(spy1).toHaveBeenCalledTimes(2);
|
|
234
|
+
expect(spy2).toHaveBeenCalledTimes(1);
|
|
235
|
+
expect(spy3).toHaveBeenCalledTimes(2);
|
|
236
|
+
|
|
237
|
+
unregister1();
|
|
238
|
+
unregister3();
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test('afterEach guard execution order should remain correct after cleanup', async () => {
|
|
242
|
+
const executionOrder: string[] = [];
|
|
243
|
+
|
|
244
|
+
const guard1 = () => {
|
|
245
|
+
executionOrder.push('after1');
|
|
246
|
+
};
|
|
247
|
+
const guard2 = () => {
|
|
248
|
+
executionOrder.push('after2');
|
|
249
|
+
};
|
|
250
|
+
const guard3 = () => {
|
|
251
|
+
executionOrder.push('after3');
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
router.afterEach(guard1);
|
|
255
|
+
const unregister2 = router.afterEach(guard2);
|
|
256
|
+
router.afterEach(guard3);
|
|
257
|
+
|
|
258
|
+
await router.push('/test1');
|
|
259
|
+
expect(executionOrder).toEqual(['after1', 'after2', 'after3']);
|
|
260
|
+
|
|
261
|
+
executionOrder.length = 0;
|
|
262
|
+
unregister2();
|
|
263
|
+
|
|
264
|
+
await router.push('/test2');
|
|
265
|
+
expect(executionOrder).toEqual(['after1', 'after3']);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
describe('mixed guards cleanup', () => {
|
|
270
|
+
test('beforeEach and afterEach guards should cleanup independently', async () => {
|
|
271
|
+
const beforeSpy = vi.fn();
|
|
272
|
+
const afterSpy = vi.fn();
|
|
273
|
+
|
|
274
|
+
const unregisterBefore = router.beforeEach(beforeSpy);
|
|
275
|
+
const unregisterAfter = router.afterEach(afterSpy);
|
|
276
|
+
|
|
277
|
+
await router.push('/test1');
|
|
278
|
+
expect(beforeSpy).toHaveBeenCalledTimes(1);
|
|
279
|
+
expect(afterSpy).toHaveBeenCalledTimes(1);
|
|
280
|
+
|
|
281
|
+
unregisterBefore();
|
|
282
|
+
|
|
283
|
+
await router.push('/test2');
|
|
284
|
+
expect(beforeSpy).toHaveBeenCalledTimes(1); // No increase
|
|
285
|
+
expect(afterSpy).toHaveBeenCalledTimes(2); // Continue to increase
|
|
286
|
+
|
|
287
|
+
// Cleanup afterEach
|
|
288
|
+
unregisterAfter();
|
|
289
|
+
|
|
290
|
+
await router.push('/test3');
|
|
291
|
+
expect(beforeSpy).toHaveBeenCalledTimes(1);
|
|
292
|
+
expect(afterSpy).toHaveBeenCalledTimes(2); // No increase
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
describe('🛡️ Edge Cases Testing', () => {
|
|
298
|
+
describe('repeated cleanup safety', () => {
|
|
299
|
+
test('repeated cleanup function calls should be safe', () => {
|
|
300
|
+
const spy = vi.fn();
|
|
301
|
+
const unregister = router.beforeEach(spy);
|
|
302
|
+
|
|
303
|
+
expect(() => {
|
|
304
|
+
unregister();
|
|
305
|
+
unregister();
|
|
306
|
+
unregister();
|
|
307
|
+
}).not.toThrow();
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
test('guard should still not execute after repeated cleanup', async () => {
|
|
311
|
+
const spy = vi.fn();
|
|
312
|
+
const unregister = router.beforeEach(spy);
|
|
313
|
+
|
|
314
|
+
await router.push('/test1');
|
|
315
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
316
|
+
|
|
317
|
+
unregister();
|
|
318
|
+
unregister();
|
|
319
|
+
unregister();
|
|
320
|
+
|
|
321
|
+
await router.push('/test2');
|
|
322
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
describe('cleanup and re-registration', () => {
|
|
327
|
+
test('can re-register same guard after cleanup', async () => {
|
|
328
|
+
const spy = vi.fn();
|
|
329
|
+
|
|
330
|
+
const unregister1 = router.beforeEach(spy);
|
|
331
|
+
await router.push('/test1');
|
|
332
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
333
|
+
|
|
334
|
+
unregister1();
|
|
335
|
+
|
|
336
|
+
// Re-register same guard
|
|
337
|
+
const unregister2 = router.beforeEach(spy);
|
|
338
|
+
await router.push('/test2');
|
|
339
|
+
expect(spy).toHaveBeenCalledTimes(2);
|
|
340
|
+
|
|
341
|
+
unregister2();
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
test('re-registration after cleanup should work normally', async () => {
|
|
345
|
+
const spy = vi.fn();
|
|
346
|
+
|
|
347
|
+
let unregister = router.beforeEach(spy);
|
|
348
|
+
await router.push('/test1');
|
|
349
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
350
|
+
|
|
351
|
+
// Cleanup
|
|
352
|
+
unregister();
|
|
353
|
+
|
|
354
|
+
// Re-register and navigate
|
|
355
|
+
unregister = router.beforeEach(spy);
|
|
356
|
+
await router.push('/test2');
|
|
357
|
+
expect(spy).toHaveBeenCalledTimes(2);
|
|
358
|
+
|
|
359
|
+
// Cleanup and re-register again
|
|
360
|
+
unregister();
|
|
361
|
+
unregister = router.beforeEach(spy);
|
|
362
|
+
await router.push('/test3');
|
|
363
|
+
expect(spy).toHaveBeenCalledTimes(3);
|
|
364
|
+
|
|
365
|
+
// Final cleanup
|
|
366
|
+
unregister();
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
describe('same guard multiple registrations', () => {
|
|
371
|
+
test('same guard function registered multiple times should handle correctly', async () => {
|
|
372
|
+
const spy = vi.fn();
|
|
373
|
+
|
|
374
|
+
const unregister1 = router.beforeEach(spy);
|
|
375
|
+
const unregister2 = router.beforeEach(spy); // Same function
|
|
376
|
+
|
|
377
|
+
await router.push('/test1');
|
|
378
|
+
expect(spy).toHaveBeenCalledTimes(2); // Should execute twice
|
|
379
|
+
|
|
380
|
+
// Cleanup first registration
|
|
381
|
+
unregister1();
|
|
382
|
+
|
|
383
|
+
await router.push('/test2');
|
|
384
|
+
expect(spy).toHaveBeenCalledTimes(3); // Should execute once more
|
|
385
|
+
|
|
386
|
+
unregister2();
|
|
387
|
+
|
|
388
|
+
await router.push('/test3');
|
|
389
|
+
expect(spy).toHaveBeenCalledTimes(3); // No more execution
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
test('complex scenario with same guard multiple registrations and cleanups', async () => {
|
|
393
|
+
const spy = vi.fn();
|
|
394
|
+
|
|
395
|
+
// Register same guard 3 times
|
|
396
|
+
const unregister1 = router.beforeEach(spy);
|
|
397
|
+
const unregister2 = router.beforeEach(spy);
|
|
398
|
+
const unregister3 = router.beforeEach(spy);
|
|
399
|
+
|
|
400
|
+
await router.push('/test1');
|
|
401
|
+
expect(spy).toHaveBeenCalledTimes(3);
|
|
402
|
+
|
|
403
|
+
// Cleanup middle registration
|
|
404
|
+
unregister2();
|
|
405
|
+
|
|
406
|
+
await router.push('/test2');
|
|
407
|
+
expect(spy).toHaveBeenCalledTimes(5); // 3 + 2
|
|
408
|
+
|
|
409
|
+
// Cleanup remaining
|
|
410
|
+
unregister1();
|
|
411
|
+
unregister3();
|
|
412
|
+
|
|
413
|
+
await router.push('/test3');
|
|
414
|
+
expect(spy).toHaveBeenCalledTimes(5); // No increase
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
describe('empty array and non-existent element handling', () => {
|
|
419
|
+
test('removing element from empty array should be safe', () => {
|
|
420
|
+
const emptyArray: unknown[] = [];
|
|
421
|
+
const element = vi.fn();
|
|
422
|
+
|
|
423
|
+
expect(() => {
|
|
424
|
+
removeFromArray(emptyArray, element);
|
|
425
|
+
}).not.toThrow();
|
|
426
|
+
|
|
427
|
+
expect(emptyArray).toEqual([]);
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
test('removing non-existent element should be safe', () => {
|
|
431
|
+
const array = [vi.fn(), vi.fn(), vi.fn()];
|
|
432
|
+
const nonExistentElement = vi.fn();
|
|
433
|
+
const originalLength = array.length;
|
|
434
|
+
|
|
435
|
+
expect(() => {
|
|
436
|
+
removeFromArray(array, nonExistentElement);
|
|
437
|
+
}).not.toThrow();
|
|
438
|
+
|
|
439
|
+
expect(array).toHaveLength(originalLength);
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
describe('special value handling', () => {
|
|
444
|
+
test('removeFromArray should handle NaN values correctly', () => {
|
|
445
|
+
const arr = [1, Number.NaN, 3, Number.NaN, 5];
|
|
446
|
+
removeFromArray(arr, Number.NaN);
|
|
447
|
+
expect(arr).toEqual([1, 3, Number.NaN, 5]); // Only remove first NaN
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
test('removeFromArray should handle function references correctly', () => {
|
|
451
|
+
const func1 = () => 'func1';
|
|
452
|
+
const func2 = () => 'func2';
|
|
453
|
+
const func3 = () => 'func3';
|
|
454
|
+
const arr = [func1, func2, func3];
|
|
455
|
+
|
|
456
|
+
removeFromArray(arr, func2);
|
|
457
|
+
expect(arr).toEqual([func1, func3]);
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
test('removeFromArray should handle object references correctly', () => {
|
|
461
|
+
const obj1 = { id: 1 };
|
|
462
|
+
const obj2 = { id: 2 };
|
|
463
|
+
const obj3 = { id: 3 };
|
|
464
|
+
const arr = [obj1, obj2, obj3];
|
|
465
|
+
|
|
466
|
+
removeFromArray(arr, obj2);
|
|
467
|
+
expect(arr).toEqual([obj1, obj3]);
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
describe('memory leak protection', () => {
|
|
472
|
+
test('large number of guard registrations and cleanups should not cause memory leaks', () => {
|
|
473
|
+
const unregisters: Array<() => void> = [];
|
|
474
|
+
|
|
475
|
+
// Register many guards
|
|
476
|
+
for (let i = 0; i < 100; i++) {
|
|
477
|
+
const guard = vi.fn();
|
|
478
|
+
const unregister = router.beforeEach(guard);
|
|
479
|
+
unregisters.push(unregister);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
expect(router.transition.guards.beforeEach).toHaveLength(100);
|
|
483
|
+
|
|
484
|
+
// Cleanup all guards
|
|
485
|
+
unregisters.forEach((unregister) => unregister());
|
|
486
|
+
|
|
487
|
+
expect(router.transition.guards.beforeEach).toHaveLength(0);
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
test('mixed registration and cleanup should not cause memory leaks', () => {
|
|
491
|
+
const guards: Array<() => void> = [];
|
|
492
|
+
const unregisters: Array<() => void> = [];
|
|
493
|
+
|
|
494
|
+
// Mixed registration and cleanup
|
|
495
|
+
for (let i = 0; i < 50; i++) {
|
|
496
|
+
const guard = vi.fn();
|
|
497
|
+
guards.push(guard);
|
|
498
|
+
const unregister = router.beforeEach(guard);
|
|
499
|
+
unregisters.push(unregister);
|
|
500
|
+
|
|
501
|
+
// Cleanup every 5th guard
|
|
502
|
+
if (i % 5 === 0 && i > 0) {
|
|
503
|
+
unregisters[i - 5]();
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Cleanup remaining guards
|
|
508
|
+
unregisters.forEach((unregister) => unregister());
|
|
509
|
+
|
|
510
|
+
expect(router.transition.guards.beforeEach).toHaveLength(0);
|
|
511
|
+
expect(router.transition.guards.afterEach).toHaveLength(0);
|
|
512
|
+
});
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
describe('concurrent cleanup safety', () => {
|
|
516
|
+
test('cleaning guard during navigation should be safe', async () => {
|
|
517
|
+
let unregister: (() => void) | null = null;
|
|
518
|
+
let guardExecuted = false;
|
|
519
|
+
|
|
520
|
+
const guard = async () => {
|
|
521
|
+
guardExecuted = true;
|
|
522
|
+
// Cleanup self during guard execution
|
|
523
|
+
unregister?.();
|
|
524
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
unregister = router.beforeEach(guard);
|
|
528
|
+
|
|
529
|
+
// Should not throw exception
|
|
530
|
+
await expect(router.push('/test1')).resolves.toBeDefined();
|
|
531
|
+
expect(guardExecuted).toBe(true);
|
|
532
|
+
|
|
533
|
+
// Subsequent navigation guard should not execute
|
|
534
|
+
guardExecuted = false;
|
|
535
|
+
await router.push('/test2');
|
|
536
|
+
expect(guardExecuted).toBe(false);
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
test('cleaning multiple guards simultaneously should be safe', async () => {
|
|
540
|
+
const guards = Array.from({ length: 10 }, () => vi.fn());
|
|
541
|
+
const unregisters = guards.map((guard) =>
|
|
542
|
+
router.beforeEach(guard)
|
|
543
|
+
);
|
|
544
|
+
|
|
545
|
+
await router.push('/test1');
|
|
546
|
+
guards.forEach((guard) =>
|
|
547
|
+
expect(guard).toHaveBeenCalledTimes(1)
|
|
548
|
+
);
|
|
549
|
+
|
|
550
|
+
// Cleanup all guards simultaneously
|
|
551
|
+
unregisters.forEach((unregister) => unregister());
|
|
552
|
+
|
|
553
|
+
await router.push('/test2');
|
|
554
|
+
guards.forEach((guard) =>
|
|
555
|
+
expect(guard).toHaveBeenCalledTimes(1)
|
|
556
|
+
);
|
|
557
|
+
});
|
|
558
|
+
});
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
describe('🔧 Router destroy cleanup verification', () => {
|
|
562
|
+
test('Router destroy should cleanup all guards', async () => {
|
|
563
|
+
const beforeSpy = vi.fn();
|
|
564
|
+
const afterSpy = vi.fn();
|
|
565
|
+
|
|
566
|
+
router.beforeEach(beforeSpy);
|
|
567
|
+
router.afterEach(afterSpy);
|
|
568
|
+
|
|
569
|
+
await router.push('/test1');
|
|
570
|
+
expect(beforeSpy).toHaveBeenCalledTimes(1);
|
|
571
|
+
expect(afterSpy).toHaveBeenCalledTimes(1);
|
|
572
|
+
|
|
573
|
+
// Destroy router
|
|
574
|
+
router.destroy();
|
|
575
|
+
|
|
576
|
+
const newRouter = new Router({
|
|
577
|
+
mode: RouterMode.memory,
|
|
578
|
+
base: new URL('http://localhost:3000/'),
|
|
579
|
+
routes: [
|
|
580
|
+
{ path: '/', component: () => 'Home' },
|
|
581
|
+
{ path: '/test', component: () => 'Test' }
|
|
582
|
+
]
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
await newRouter.replace('/');
|
|
586
|
+
await newRouter.push('/test');
|
|
587
|
+
|
|
588
|
+
// Original guards should not be executed
|
|
589
|
+
expect(beforeSpy).toHaveBeenCalledTimes(1);
|
|
590
|
+
expect(afterSpy).toHaveBeenCalledTimes(1);
|
|
591
|
+
|
|
592
|
+
newRouter.destroy();
|
|
593
|
+
});
|
|
594
|
+
});
|
|
595
|
+
});
|