@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,616 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { Router } from "./router.mjs";
|
|
3
|
+
import { RouteType } from "./types.mjs";
|
|
4
|
+
describe("Router.restartApp Focused Tests", () => {
|
|
5
|
+
let router;
|
|
6
|
+
let mockApps;
|
|
7
|
+
beforeEach(async () => {
|
|
8
|
+
mockApps = {
|
|
9
|
+
home: vi.fn(() => ({ mount: vi.fn(), unmount: vi.fn() })),
|
|
10
|
+
about: vi.fn(() => ({ mount: vi.fn(), unmount: vi.fn() })),
|
|
11
|
+
user: vi.fn(() => ({ mount: vi.fn(), unmount: vi.fn() })),
|
|
12
|
+
products: vi.fn(() => ({ mount: vi.fn(), unmount: vi.fn() }))
|
|
13
|
+
};
|
|
14
|
+
const options = {
|
|
15
|
+
routes: [
|
|
16
|
+
{ path: "/", app: "home" },
|
|
17
|
+
{ path: "/about", app: "about" },
|
|
18
|
+
{ path: "/user/:id", app: "user" },
|
|
19
|
+
{ path: "/products/:category", app: "products" }
|
|
20
|
+
],
|
|
21
|
+
apps: mockApps
|
|
22
|
+
};
|
|
23
|
+
router = new Router(options);
|
|
24
|
+
await router.push("/");
|
|
25
|
+
});
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
router.destroy();
|
|
28
|
+
vi.clearAllMocks();
|
|
29
|
+
});
|
|
30
|
+
describe("\u{1F3AF} Core Functionality Tests", () => {
|
|
31
|
+
it("should support parameterless restart (restart to current path)", async () => {
|
|
32
|
+
await router.push("/user/123");
|
|
33
|
+
expect(router.route.url.pathname).toBe("/user/123");
|
|
34
|
+
const result = await router.restartApp();
|
|
35
|
+
expect(result.type).toBe(RouteType.restartApp);
|
|
36
|
+
expect(result.url.pathname).toBe("/user/123");
|
|
37
|
+
expect(result.handle).not.toBeNull();
|
|
38
|
+
expect(router.route).toBe(result);
|
|
39
|
+
});
|
|
40
|
+
it("should support string path restart", async () => {
|
|
41
|
+
const result = await router.restartApp("/about");
|
|
42
|
+
expect(result.type).toBe(RouteType.restartApp);
|
|
43
|
+
expect(result.url.pathname).toBe("/about");
|
|
44
|
+
expect(result.handle).not.toBeNull();
|
|
45
|
+
expect(router.route).toBe(result);
|
|
46
|
+
});
|
|
47
|
+
it("should support object parameter restart", async () => {
|
|
48
|
+
const result = await router.restartApp({
|
|
49
|
+
path: "/user/456",
|
|
50
|
+
query: { tab: "profile", mode: "edit" },
|
|
51
|
+
hash: "#section1"
|
|
52
|
+
});
|
|
53
|
+
expect(result.type).toBe(RouteType.restartApp);
|
|
54
|
+
expect(result.url.pathname).toBe("/user/456");
|
|
55
|
+
expect(result.query.tab).toBe("profile");
|
|
56
|
+
expect(result.query.mode).toBe("edit");
|
|
57
|
+
expect(result.url.hash).toBe("#section1");
|
|
58
|
+
expect(result.params.id).toBe("456");
|
|
59
|
+
expect(result.handle).not.toBeNull();
|
|
60
|
+
});
|
|
61
|
+
it("should support restart with state", async () => {
|
|
62
|
+
const customState = { userId: 123, preferences: { theme: "dark" } };
|
|
63
|
+
const result = await router.restartApp({
|
|
64
|
+
path: "/about",
|
|
65
|
+
state: customState
|
|
66
|
+
});
|
|
67
|
+
expect(result.state).toEqual(expect.objectContaining(customState));
|
|
68
|
+
const state = result.state;
|
|
69
|
+
expect(state.userId).toBe(123);
|
|
70
|
+
expect(state.preferences.theme).toBe("dark");
|
|
71
|
+
});
|
|
72
|
+
it("should correctly handle route parameters", async () => {
|
|
73
|
+
const result = await router.restartApp("/user/789");
|
|
74
|
+
expect(result.params.id).toBe("789");
|
|
75
|
+
expect(result.matched.length).toBeGreaterThan(0);
|
|
76
|
+
expect(result.matched[0].path).toBe("/user/:id");
|
|
77
|
+
});
|
|
78
|
+
it("should correctly handle query parameters", async () => {
|
|
79
|
+
const result = await router.restartApp("/about?tab=info&mode=edit");
|
|
80
|
+
expect(result.query.tab).toBe("info");
|
|
81
|
+
expect(result.query.mode).toBe("edit");
|
|
82
|
+
expect(result.url.search).toBe("?tab=info&mode=edit");
|
|
83
|
+
});
|
|
84
|
+
it("should correctly handle hash", async () => {
|
|
85
|
+
const result = await router.restartApp("/about#section2");
|
|
86
|
+
expect(result.url.hash).toBe("#section2");
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
describe("\u{1F504} restartApp-Specific Behavior Tests", () => {
|
|
90
|
+
it("should force update MicroApp (even with same application)", async () => {
|
|
91
|
+
await router.push("/about");
|
|
92
|
+
const firstCallCount = mockApps.about.mock.calls.length;
|
|
93
|
+
await router.restartApp("/about");
|
|
94
|
+
const secondCallCount = mockApps.about.mock.calls.length;
|
|
95
|
+
expect(secondCallCount).toBeGreaterThan(firstCallCount);
|
|
96
|
+
});
|
|
97
|
+
it("should call navigation.replace instead of push", async () => {
|
|
98
|
+
const replaceSpy = vi.spyOn(router.navigation, "replace");
|
|
99
|
+
const pushSpy = vi.spyOn(router.navigation, "push");
|
|
100
|
+
await router.restartApp("/about");
|
|
101
|
+
expect(replaceSpy).toHaveBeenCalled();
|
|
102
|
+
expect(pushSpy).not.toHaveBeenCalled();
|
|
103
|
+
});
|
|
104
|
+
it("should correctly update router current route state", async () => {
|
|
105
|
+
const result = await router.restartApp("/about");
|
|
106
|
+
expect(router.route.url.pathname).toBe("/about");
|
|
107
|
+
expect(router.route.type).toBe(RouteType.restartApp);
|
|
108
|
+
expect(router.route).toBe(result);
|
|
109
|
+
});
|
|
110
|
+
it("should differentiate from other routing method behaviors", async () => {
|
|
111
|
+
await router.push("/user/123");
|
|
112
|
+
expect(router.route.type).toBe(RouteType.push);
|
|
113
|
+
await router.replace("/about");
|
|
114
|
+
expect(router.route.type).toBe(RouteType.replace);
|
|
115
|
+
const result = await router.restartApp("/products/electronics");
|
|
116
|
+
expect(result.type).toBe(RouteType.restartApp);
|
|
117
|
+
expect(router.route.type).toBe(RouteType.restartApp);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
describe("\u{1F3AD} Edge Cases Tests", () => {
|
|
121
|
+
it("should handle non-existent routes", async () => {
|
|
122
|
+
const result = await router.restartApp("/nonexistent");
|
|
123
|
+
expect(result.matched.length).toBe(0);
|
|
124
|
+
expect(result.config).toBeNull();
|
|
125
|
+
expect(result.url.pathname).toBe("/nonexistent");
|
|
126
|
+
expect(result.type).toBe(RouteType.restartApp);
|
|
127
|
+
});
|
|
128
|
+
it("should handle empty string path", async () => {
|
|
129
|
+
const result = await router.restartApp("");
|
|
130
|
+
expect(result.url.pathname).toBe("/");
|
|
131
|
+
expect(result.type).toBe(RouteType.restartApp);
|
|
132
|
+
});
|
|
133
|
+
it("should handle root path", async () => {
|
|
134
|
+
const result = await router.restartApp("/");
|
|
135
|
+
expect(result.url.pathname).toBe("/");
|
|
136
|
+
expect(result.matched.length).toBeGreaterThan(0);
|
|
137
|
+
expect(result.type).toBe(RouteType.restartApp);
|
|
138
|
+
});
|
|
139
|
+
it("should handle complex query parameters", async () => {
|
|
140
|
+
const result = await router.restartApp("/about?a=1&b=2&a=3&c=");
|
|
141
|
+
expect(result.query.b).toBe("2");
|
|
142
|
+
expect(result.query.c).toBe("");
|
|
143
|
+
expect(result.url.search).toContain("a=");
|
|
144
|
+
expect(result.url.search).toContain("b=2");
|
|
145
|
+
});
|
|
146
|
+
it("should handle special character paths", async () => {
|
|
147
|
+
const result = await router.restartApp("/user/\u6D4B\u8BD5\u7528\u6237");
|
|
148
|
+
expect(result.url.pathname).toContain("/user/");
|
|
149
|
+
expect(decodeURIComponent(result.params.id)).toBe("\u6D4B\u8BD5\u7528\u6237");
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
describe("\u{1F517} URL Parsing Tests", () => {
|
|
153
|
+
it("should correctly parse absolute paths", async () => {
|
|
154
|
+
await router.push("/user/123");
|
|
155
|
+
const result = await router.restartApp("/about");
|
|
156
|
+
expect(result.url.pathname).toBe("/about");
|
|
157
|
+
expect(result.url.href).toMatch(/\/about$/);
|
|
158
|
+
});
|
|
159
|
+
it("should correctly handle relative paths", async () => {
|
|
160
|
+
await router.push("/user/123");
|
|
161
|
+
const result = await router.restartApp("456");
|
|
162
|
+
expect(result.url.pathname).toContain("456");
|
|
163
|
+
if (result.matched.length > 0 && result.matched[0].path === "/user/:id") {
|
|
164
|
+
expect(result.params.id).toBe("456");
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
it("should correctly handle complete URLs", async () => {
|
|
168
|
+
const result = await router.restartApp("http://example.com/test");
|
|
169
|
+
expect(result.url.href).toBe("http://example.com/test");
|
|
170
|
+
expect(result.url.pathname).toBe("/test");
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
describe("\u{1F3AF} Type Overload Tests", () => {
|
|
174
|
+
it("should support parameterless calls", async () => {
|
|
175
|
+
await router.push("/user/123");
|
|
176
|
+
const result = await router.restartApp();
|
|
177
|
+
expect(result.url.pathname).toBe("/user/123");
|
|
178
|
+
expect(result.type).toBe(RouteType.restartApp);
|
|
179
|
+
});
|
|
180
|
+
it("should support string parameter calls", async () => {
|
|
181
|
+
const result = await router.restartApp("/about");
|
|
182
|
+
expect(result.url.pathname).toBe("/about");
|
|
183
|
+
expect(result.type).toBe(RouteType.restartApp);
|
|
184
|
+
});
|
|
185
|
+
it("should support object parameter calls", async () => {
|
|
186
|
+
const routeLocation = {
|
|
187
|
+
path: "/user/456",
|
|
188
|
+
query: { tab: "settings" }
|
|
189
|
+
};
|
|
190
|
+
const result = await router.restartApp(routeLocation);
|
|
191
|
+
expect(result.url.pathname).toBe("/user/456");
|
|
192
|
+
expect(result.query.tab).toBe("settings");
|
|
193
|
+
expect(result.type).toBe(RouteType.restartApp);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
describe("\u{1F504} Multiple Restart Tests", () => {
|
|
197
|
+
it("should support consecutive multiple restarts", async () => {
|
|
198
|
+
const paths = ["/about", "/user/123", "/products/electronics", "/"];
|
|
199
|
+
for (const path of paths) {
|
|
200
|
+
const result = await router.restartApp(path);
|
|
201
|
+
expect(result.type).toBe(RouteType.restartApp);
|
|
202
|
+
expect(result.handle).not.toBeNull();
|
|
203
|
+
expect(router.route).toBe(result);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
it("should create new application instances on each restart", async () => {
|
|
207
|
+
await router.restartApp("/about");
|
|
208
|
+
const firstCallCount = mockApps.about.mock.calls.length;
|
|
209
|
+
await router.restartApp("/about");
|
|
210
|
+
const secondCallCount = mockApps.about.mock.calls.length;
|
|
211
|
+
await router.restartApp("/about");
|
|
212
|
+
const thirdCallCount = mockApps.about.mock.calls.length;
|
|
213
|
+
expect(secondCallCount).toBeGreaterThan(firstCallCount);
|
|
214
|
+
expect(thirdCallCount).toBeGreaterThan(secondCallCount);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
describe("\u{1F3A8} Status Code Handling Tests", () => {
|
|
218
|
+
it("should support custom status codes", async () => {
|
|
219
|
+
const result = await router.restartApp({
|
|
220
|
+
path: "/about",
|
|
221
|
+
statusCode: 201
|
|
222
|
+
});
|
|
223
|
+
expect(result.statusCode).toBe(201);
|
|
224
|
+
});
|
|
225
|
+
it("should maintain default status code as null", async () => {
|
|
226
|
+
const result = await router.restartApp("/about");
|
|
227
|
+
expect(result.statusCode).toBeNull();
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
describe("\u{1F527} Consistency Tests with resolve Method", () => {
|
|
231
|
+
it("should maintain consistency with resolve method results in URL parsing", async () => {
|
|
232
|
+
const resolvedRoute = router.resolve("/user/789");
|
|
233
|
+
const restartedRoute = await router.restartApp("/user/789");
|
|
234
|
+
expect(restartedRoute.url.href).toBe(resolvedRoute.url.href);
|
|
235
|
+
expect(restartedRoute.params).toEqual(resolvedRoute.params);
|
|
236
|
+
expect(restartedRoute.matched).toEqual(resolvedRoute.matched);
|
|
237
|
+
expect(restartedRoute.type).toBe(RouteType.restartApp);
|
|
238
|
+
expect(resolvedRoute.type).toBe(RouteType.push);
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
describe("\u{1F6E1}\uFE0F Route Guards Integration Tests", () => {
|
|
242
|
+
let guardExecutionLog;
|
|
243
|
+
beforeEach(() => {
|
|
244
|
+
guardExecutionLog = [];
|
|
245
|
+
});
|
|
246
|
+
it("should correctly execute beforeEach guards", async () => {
|
|
247
|
+
const unregister = router.beforeEach(async (to, from) => {
|
|
248
|
+
guardExecutionLog.push(
|
|
249
|
+
`beforeEach-${to.path}-from-${(from == null ? void 0 : from.path) || "null"}`
|
|
250
|
+
);
|
|
251
|
+
});
|
|
252
|
+
await router.restartApp("/about");
|
|
253
|
+
expect(guardExecutionLog).toContain("beforeEach-/about-from-/");
|
|
254
|
+
unregister();
|
|
255
|
+
});
|
|
256
|
+
it("should correctly execute afterEach guards", async () => {
|
|
257
|
+
const unregister = router.afterEach((to, from) => {
|
|
258
|
+
guardExecutionLog.push(
|
|
259
|
+
`afterEach-${to.path}-from-${(from == null ? void 0 : from.path) || "null"}`
|
|
260
|
+
);
|
|
261
|
+
});
|
|
262
|
+
await router.restartApp("/about");
|
|
263
|
+
expect(guardExecutionLog).toContain("afterEach-/about-from-/");
|
|
264
|
+
unregister();
|
|
265
|
+
});
|
|
266
|
+
it("should abort restart when beforeEach guard returns false", async () => {
|
|
267
|
+
const unregister = router.beforeEach(async (to, from) => {
|
|
268
|
+
if (to.path === "/about") {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
await expect(router.restartApp("/about")).rejects.toThrow();
|
|
273
|
+
expect(router.route.path).toBe("/");
|
|
274
|
+
unregister();
|
|
275
|
+
});
|
|
276
|
+
it("should support guard redirects", async () => {
|
|
277
|
+
const unregister = router.beforeEach(async (to, from) => {
|
|
278
|
+
if (to.path === "/about") {
|
|
279
|
+
return "/user/redirected";
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
const result = await router.restartApp("/about");
|
|
283
|
+
expect(result.path).toBe("/user/redirected");
|
|
284
|
+
expect(result.params.id).toBe("redirected");
|
|
285
|
+
expect(result.handle).not.toBeNull();
|
|
286
|
+
unregister();
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
describe("\u{1F9E9} Async Component Handling Tests", () => {
|
|
290
|
+
let asyncRouter;
|
|
291
|
+
beforeEach(async () => {
|
|
292
|
+
const asyncOptions = {
|
|
293
|
+
routes: [
|
|
294
|
+
{
|
|
295
|
+
path: "/",
|
|
296
|
+
app: "home",
|
|
297
|
+
component: () => "HomeComponent"
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
path: "/async",
|
|
301
|
+
app: "async",
|
|
302
|
+
asyncComponent: async () => {
|
|
303
|
+
await new Promise(
|
|
304
|
+
(resolve) => setTimeout(resolve, 10)
|
|
305
|
+
);
|
|
306
|
+
return () => "AsyncComponent";
|
|
307
|
+
}
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
path: "/async-error",
|
|
311
|
+
app: "async-error",
|
|
312
|
+
asyncComponent: async () => {
|
|
313
|
+
throw new Error("Component load failed");
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
],
|
|
317
|
+
apps: mockApps
|
|
318
|
+
};
|
|
319
|
+
asyncRouter = new Router(asyncOptions);
|
|
320
|
+
await asyncRouter.push("/");
|
|
321
|
+
});
|
|
322
|
+
afterEach(() => {
|
|
323
|
+
asyncRouter.destroy();
|
|
324
|
+
});
|
|
325
|
+
it("should correctly handle async component loading", async () => {
|
|
326
|
+
const result = await asyncRouter.restartApp("/async");
|
|
327
|
+
expect(result.handle).not.toBeNull();
|
|
328
|
+
expect(result.matched[0].component).toBeDefined();
|
|
329
|
+
expect(typeof result.matched[0].component).toBe("function");
|
|
330
|
+
});
|
|
331
|
+
it("should handle async component loading failures", async () => {
|
|
332
|
+
await expect(
|
|
333
|
+
asyncRouter.restartApp("/async-error")
|
|
334
|
+
).rejects.toThrow();
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
describe("\u26A1 Task Cancellation and Concurrency Control Tests", () => {
|
|
338
|
+
it("should cancel tasks interrupted by new restartApp calls", async () => {
|
|
339
|
+
const unregister = router.beforeEach(async (to, from) => {
|
|
340
|
+
if (to.path === "/user/slow") {
|
|
341
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
const promises = [
|
|
345
|
+
router.restartApp("/user/slow").catch((err) => err),
|
|
346
|
+
router.restartApp("/about").catch((err) => err)
|
|
347
|
+
];
|
|
348
|
+
const results = await Promise.all(promises);
|
|
349
|
+
expect(results[0] instanceof Error).toBe(true);
|
|
350
|
+
expect(results[1].handle).not.toBeNull();
|
|
351
|
+
expect(router.route.path).toBe("/about");
|
|
352
|
+
unregister();
|
|
353
|
+
});
|
|
354
|
+
it("should correctly handle multiple concurrent restartApp calls", async () => {
|
|
355
|
+
const paths = ["/user/1", "/user/2", "/user/3"];
|
|
356
|
+
const promises = paths.map(
|
|
357
|
+
(path) => router.restartApp(path).catch((err) => err)
|
|
358
|
+
);
|
|
359
|
+
const results = await Promise.all(promises);
|
|
360
|
+
expect(results[0] instanceof Error).toBe(true);
|
|
361
|
+
expect(results[1] instanceof Error).toBe(true);
|
|
362
|
+
expect(results[2].handle).not.toBeNull();
|
|
363
|
+
expect(router.route.path).toBe("/user/3");
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
describe("\u{1F30D} Route Override Tests", () => {
|
|
367
|
+
let overrideRouter;
|
|
368
|
+
beforeEach(async () => {
|
|
369
|
+
const overrideOptions = {
|
|
370
|
+
routes: [
|
|
371
|
+
{
|
|
372
|
+
path: "/",
|
|
373
|
+
app: "home",
|
|
374
|
+
component: () => "HomeComponent"
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
path: "/override-test",
|
|
378
|
+
app: "override",
|
|
379
|
+
component: () => "OverrideComponent",
|
|
380
|
+
override: (to, from) => {
|
|
381
|
+
if (to.query.native === "true") {
|
|
382
|
+
return async () => {
|
|
383
|
+
return { native: true, path: to.path };
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
path: "/hybrid-page",
|
|
390
|
+
app: "hybrid",
|
|
391
|
+
component: () => "HybridComponent",
|
|
392
|
+
override: (to, from) => {
|
|
393
|
+
return async () => {
|
|
394
|
+
return {
|
|
395
|
+
hybrid: "native",
|
|
396
|
+
component: "NativeComponent"
|
|
397
|
+
};
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
],
|
|
402
|
+
apps: mockApps
|
|
403
|
+
};
|
|
404
|
+
overrideRouter = new Router(overrideOptions);
|
|
405
|
+
await overrideRouter.push("/");
|
|
406
|
+
});
|
|
407
|
+
afterEach(() => {
|
|
408
|
+
overrideRouter.destroy();
|
|
409
|
+
});
|
|
410
|
+
it("should use override when condition is met", async () => {
|
|
411
|
+
var _a;
|
|
412
|
+
const result = await overrideRouter.restartApp(
|
|
413
|
+
"/override-test?native=true"
|
|
414
|
+
);
|
|
415
|
+
expect(result.handle).not.toBeNull();
|
|
416
|
+
expect(result.handleResult).toBeUndefined();
|
|
417
|
+
expect((_a = result.config) == null ? void 0 : _a.override).toBeDefined();
|
|
418
|
+
});
|
|
419
|
+
it("should use default behavior when override returns nothing", async () => {
|
|
420
|
+
const result = await overrideRouter.restartApp(
|
|
421
|
+
"/override-test?native=false"
|
|
422
|
+
);
|
|
423
|
+
expect(result.handle).not.toBeNull();
|
|
424
|
+
expect(result.handle).not.toBeNull();
|
|
425
|
+
});
|
|
426
|
+
it("should always use override when function always returns handler", async () => {
|
|
427
|
+
var _a;
|
|
428
|
+
const result = await overrideRouter.restartApp("/hybrid-page");
|
|
429
|
+
expect(result.handle).not.toBeNull();
|
|
430
|
+
expect(result.handleResult).toBeUndefined();
|
|
431
|
+
expect((_a = result.config) == null ? void 0 : _a.override).toBeDefined();
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
describe("\u274C Error Handling and Exception Scenario Tests", () => {
|
|
435
|
+
it("should handle exceptions thrown in guards", async () => {
|
|
436
|
+
const unregister = router.beforeEach(async (to, from) => {
|
|
437
|
+
if (to.path === "/about") {
|
|
438
|
+
throw new Error("Guard error");
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
await expect(router.restartApp("/about")).rejects.toThrow(
|
|
442
|
+
"Guard error"
|
|
443
|
+
);
|
|
444
|
+
unregister();
|
|
445
|
+
});
|
|
446
|
+
it("should handle MicroApp update exceptions", async () => {
|
|
447
|
+
const originalUpdate = router.microApp._update;
|
|
448
|
+
router.microApp._update = vi.fn().mockImplementation(() => {
|
|
449
|
+
throw new Error("MicroApp update failed");
|
|
450
|
+
});
|
|
451
|
+
await expect(router.restartApp("/about")).rejects.toThrow(
|
|
452
|
+
"MicroApp update failed"
|
|
453
|
+
);
|
|
454
|
+
router.microApp._update = originalUpdate;
|
|
455
|
+
});
|
|
456
|
+
it("should handle navigation.replace exceptions", async () => {
|
|
457
|
+
const originalReplace = router.navigation.replace;
|
|
458
|
+
router.navigation.replace = vi.fn().mockImplementation(() => {
|
|
459
|
+
throw new Error("Navigation replace failed");
|
|
460
|
+
});
|
|
461
|
+
await expect(router.restartApp("/about")).rejects.toThrow(
|
|
462
|
+
"Navigation replace failed"
|
|
463
|
+
);
|
|
464
|
+
router.navigation.replace = originalReplace;
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
describe("\u{1F504} Route Lifecycle Integrity Tests", () => {
|
|
468
|
+
let lifecycleRouter;
|
|
469
|
+
let lifecycleLog;
|
|
470
|
+
beforeEach(async () => {
|
|
471
|
+
lifecycleLog = [];
|
|
472
|
+
const lifecycleOptions = {
|
|
473
|
+
routes: [
|
|
474
|
+
{
|
|
475
|
+
path: "/",
|
|
476
|
+
app: "home",
|
|
477
|
+
component: () => "HomeComponent",
|
|
478
|
+
beforeLeave: async (to, from) => {
|
|
479
|
+
lifecycleLog.push("home-beforeLeave");
|
|
480
|
+
}
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
path: "/lifecycle",
|
|
484
|
+
app: "lifecycle",
|
|
485
|
+
component: () => "LifecycleComponent",
|
|
486
|
+
beforeEnter: async (to, from) => {
|
|
487
|
+
lifecycleLog.push("lifecycle-beforeEnter");
|
|
488
|
+
},
|
|
489
|
+
beforeUpdate: async (to, from) => {
|
|
490
|
+
lifecycleLog.push("lifecycle-beforeUpdate");
|
|
491
|
+
},
|
|
492
|
+
beforeLeave: async (to, from) => {
|
|
493
|
+
lifecycleLog.push("lifecycle-beforeLeave");
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
],
|
|
497
|
+
apps: mockApps
|
|
498
|
+
};
|
|
499
|
+
lifecycleRouter = new Router(lifecycleOptions);
|
|
500
|
+
await lifecycleRouter.push("/");
|
|
501
|
+
});
|
|
502
|
+
afterEach(() => {
|
|
503
|
+
lifecycleRouter.destroy();
|
|
504
|
+
});
|
|
505
|
+
it("should correctly execute complete route lifecycle", async () => {
|
|
506
|
+
const unregisterBefore = lifecycleRouter.beforeEach((to, from) => {
|
|
507
|
+
lifecycleLog.push(`global-beforeEach-${to.path}`);
|
|
508
|
+
});
|
|
509
|
+
const unregisterAfter = lifecycleRouter.afterEach((to, from) => {
|
|
510
|
+
lifecycleLog.push(`global-afterEach-${to.path}`);
|
|
511
|
+
});
|
|
512
|
+
await lifecycleRouter.restartApp("/lifecycle");
|
|
513
|
+
expect(lifecycleLog).toEqual([
|
|
514
|
+
"home-beforeLeave",
|
|
515
|
+
"global-beforeEach-/lifecycle",
|
|
516
|
+
"lifecycle-beforeEnter",
|
|
517
|
+
"global-afterEach-/lifecycle"
|
|
518
|
+
]);
|
|
519
|
+
unregisterBefore();
|
|
520
|
+
unregisterAfter();
|
|
521
|
+
});
|
|
522
|
+
it("should execute beforeUpdate when restarting same route", async () => {
|
|
523
|
+
await lifecycleRouter.push("/lifecycle");
|
|
524
|
+
lifecycleLog = [];
|
|
525
|
+
await lifecycleRouter.restartApp("/lifecycle?version=2");
|
|
526
|
+
expect(lifecycleLog).toContain("lifecycle-beforeUpdate");
|
|
527
|
+
expect(lifecycleLog).not.toContain("lifecycle-beforeEnter");
|
|
528
|
+
});
|
|
529
|
+
});
|
|
530
|
+
describe("\u{1F3AF} Special Route Configuration Tests", () => {
|
|
531
|
+
it("should handle routes with custom location handler", async () => {
|
|
532
|
+
let locationCalled = false;
|
|
533
|
+
const customLocationRouter = new Router({
|
|
534
|
+
routes: [{ path: "/", app: "home" }],
|
|
535
|
+
apps: mockApps,
|
|
536
|
+
fallback: (to, from) => {
|
|
537
|
+
locationCalled = true;
|
|
538
|
+
return { customLocation: true, path: to.path };
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
await customLocationRouter.push("/");
|
|
542
|
+
const result = await customLocationRouter.restartApp("/nonexistent");
|
|
543
|
+
expect(locationCalled).toBe(true);
|
|
544
|
+
expect(result.matched.length).toBe(0);
|
|
545
|
+
expect(typeof result.handle).toBe("function");
|
|
546
|
+
expect(result.handleResult).toEqual({
|
|
547
|
+
customLocation: true,
|
|
548
|
+
path: "/nonexistent"
|
|
549
|
+
});
|
|
550
|
+
customLocationRouter.destroy();
|
|
551
|
+
});
|
|
552
|
+
it("should handle complex nested route restarts", async () => {
|
|
553
|
+
const nestedRouter = new Router({
|
|
554
|
+
routes: [
|
|
555
|
+
{
|
|
556
|
+
path: "/",
|
|
557
|
+
app: "home",
|
|
558
|
+
children: [
|
|
559
|
+
{
|
|
560
|
+
path: "nested/:id",
|
|
561
|
+
app: "nested",
|
|
562
|
+
children: [
|
|
563
|
+
{
|
|
564
|
+
path: "deep/:subId",
|
|
565
|
+
app: "deep"
|
|
566
|
+
}
|
|
567
|
+
]
|
|
568
|
+
}
|
|
569
|
+
]
|
|
570
|
+
}
|
|
571
|
+
],
|
|
572
|
+
apps: mockApps
|
|
573
|
+
});
|
|
574
|
+
await nestedRouter.push("/");
|
|
575
|
+
const result = await nestedRouter.restartApp(
|
|
576
|
+
"/nested/123/deep/456"
|
|
577
|
+
);
|
|
578
|
+
expect(result.params.id).toBe("123");
|
|
579
|
+
expect(result.params.subId).toBe("456");
|
|
580
|
+
expect(result.matched.length).toBe(3);
|
|
581
|
+
nestedRouter.destroy();
|
|
582
|
+
});
|
|
583
|
+
});
|
|
584
|
+
describe("\u{1F4CA} Performance and Memory Tests", () => {
|
|
585
|
+
it("should correctly clean up resources after extensive restarts", async () => {
|
|
586
|
+
const initialAppsCallCount = Object.values(mockApps).reduce(
|
|
587
|
+
(sum, app) => sum + app.mock.calls.length,
|
|
588
|
+
0
|
|
589
|
+
);
|
|
590
|
+
for (let i = 0; i < 50; i++) {
|
|
591
|
+
await router.restartApp(`/user/${i}`);
|
|
592
|
+
}
|
|
593
|
+
const finalAppsCallCount = Object.values(mockApps).reduce(
|
|
594
|
+
(sum, app) => sum + app.mock.calls.length,
|
|
595
|
+
0
|
|
596
|
+
);
|
|
597
|
+
expect(finalAppsCallCount).toBeGreaterThan(initialAppsCallCount);
|
|
598
|
+
expect(router.route.params.id).toBe("49");
|
|
599
|
+
});
|
|
600
|
+
it("should correctly handle rapid consecutive restart calls", async () => {
|
|
601
|
+
const startTime = Date.now();
|
|
602
|
+
const promises = Array.from(
|
|
603
|
+
{ length: 10 },
|
|
604
|
+
(_, i) => router.restartApp(`/user/${i}`).catch((err) => err)
|
|
605
|
+
);
|
|
606
|
+
const results = await Promise.all(promises);
|
|
607
|
+
const endTime = Date.now();
|
|
608
|
+
const successfulResults = results.filter(
|
|
609
|
+
(r) => !(r instanceof Error) && r.handle !== null
|
|
610
|
+
);
|
|
611
|
+
expect(successfulResults).toHaveLength(1);
|
|
612
|
+
expect(successfulResults[0].params.id).toBe("9");
|
|
613
|
+
expect(endTime - startTime).toBeLessThan(1e3);
|
|
614
|
+
});
|
|
615
|
+
});
|
|
616
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|