@esmx/router 3.0.0-rc.18 → 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 -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,1664 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { parsedOptions } from "./options.mjs";
|
|
3
|
+
import { NON_ENUMERABLE_PROPERTIES, Route, applyRouteParams } from "./route.mjs";
|
|
4
|
+
import { RouteType, RouterMode } from "./types.mjs";
|
|
5
|
+
describe("Route Class Complete Test Suite", () => {
|
|
6
|
+
const createOptions = (overrides = {}) => {
|
|
7
|
+
const base = new URL("http://localhost:3000/app/");
|
|
8
|
+
const mockRoutes = [
|
|
9
|
+
{
|
|
10
|
+
path: "/users/:id",
|
|
11
|
+
meta: { title: "User Detail", requireAuth: true }
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
path: "/posts/:postId/comments/:commentId",
|
|
15
|
+
meta: { title: "Comment Detail" }
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
path: "/admin/(.*)",
|
|
19
|
+
meta: { title: "Admin", role: "admin" }
|
|
20
|
+
}
|
|
21
|
+
];
|
|
22
|
+
const routerOptions = {
|
|
23
|
+
root: "#test",
|
|
24
|
+
context: { version: "1.0.0" },
|
|
25
|
+
routes: mockRoutes,
|
|
26
|
+
mode: RouterMode.history,
|
|
27
|
+
base,
|
|
28
|
+
req: null,
|
|
29
|
+
res: null,
|
|
30
|
+
apps: {},
|
|
31
|
+
normalizeURL: (url) => url,
|
|
32
|
+
fallback: () => {
|
|
33
|
+
},
|
|
34
|
+
rootStyle: false,
|
|
35
|
+
handleBackBoundary: () => {
|
|
36
|
+
},
|
|
37
|
+
handleLayerClose: () => {
|
|
38
|
+
},
|
|
39
|
+
...overrides
|
|
40
|
+
};
|
|
41
|
+
return parsedOptions(routerOptions);
|
|
42
|
+
};
|
|
43
|
+
describe("\u{1F3D7}\uFE0F Constructor Tests", () => {
|
|
44
|
+
describe("Basic Construction", () => {
|
|
45
|
+
it("should create route with default options", () => {
|
|
46
|
+
const route = new Route();
|
|
47
|
+
expect(route.type).toBe(RouteType.push);
|
|
48
|
+
expect(route.isPush).toBe(true);
|
|
49
|
+
expect(route.path).toBe("/");
|
|
50
|
+
expect(route.state).toEqual({});
|
|
51
|
+
expect(route.params).toEqual({});
|
|
52
|
+
expect(route.query).toEqual({});
|
|
53
|
+
expect(route.queryArray).toEqual({});
|
|
54
|
+
});
|
|
55
|
+
it("should correctly handle string path", () => {
|
|
56
|
+
const options = createOptions();
|
|
57
|
+
const route = new Route({
|
|
58
|
+
options,
|
|
59
|
+
toType: RouteType.push,
|
|
60
|
+
toInput: "/users/123"
|
|
61
|
+
});
|
|
62
|
+
expect(route.path).toBe("/users/123");
|
|
63
|
+
expect(route.params.id).toBe("123");
|
|
64
|
+
expect(route.type).toBe(RouteType.push);
|
|
65
|
+
expect(route.isPush).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
it("should correctly handle object-form route location", () => {
|
|
68
|
+
const options = createOptions();
|
|
69
|
+
const route = new Route({
|
|
70
|
+
options,
|
|
71
|
+
toType: RouteType.replace,
|
|
72
|
+
toInput: {
|
|
73
|
+
path: "/users/456",
|
|
74
|
+
query: { tab: "profile" },
|
|
75
|
+
state: { fromPage: "dashboard" },
|
|
76
|
+
keepScrollPosition: true
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
expect(route.path).toBe("/users/456");
|
|
80
|
+
expect(route.params.id).toBe("456");
|
|
81
|
+
expect(route.query.tab).toBe("profile");
|
|
82
|
+
expect(route.state.fromPage).toBe("dashboard");
|
|
83
|
+
expect(route.keepScrollPosition).toBe(true);
|
|
84
|
+
expect(route.isPush).toBe(false);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
describe("URL Parsing and Matching", () => {
|
|
88
|
+
it("should correctly parse complex URL", () => {
|
|
89
|
+
const options = createOptions();
|
|
90
|
+
const route = new Route({
|
|
91
|
+
options,
|
|
92
|
+
toType: RouteType.push,
|
|
93
|
+
toInput: "/users/123?tab=profile&sort=name#section1"
|
|
94
|
+
});
|
|
95
|
+
expect(route.path).toBe("/users/123");
|
|
96
|
+
expect(route.fullPath).toBe(
|
|
97
|
+
"/users/123?tab=profile&sort=name#section1"
|
|
98
|
+
);
|
|
99
|
+
expect(route.query.tab).toBe("profile");
|
|
100
|
+
expect(route.query.sort).toBe("name");
|
|
101
|
+
expect(route.url.hash).toBe("#section1");
|
|
102
|
+
});
|
|
103
|
+
it("should handle multi-value query parameters", () => {
|
|
104
|
+
const options = createOptions();
|
|
105
|
+
const route = new Route({
|
|
106
|
+
options,
|
|
107
|
+
toType: RouteType.push,
|
|
108
|
+
toInput: "/users/123?tags=js&tags=react&tags=vue"
|
|
109
|
+
});
|
|
110
|
+
expect(route.query.tags).toBe("js");
|
|
111
|
+
expect(route.queryArray.tags).toEqual(["js", "react", "vue"]);
|
|
112
|
+
});
|
|
113
|
+
it("should correctly match nested route parameters", () => {
|
|
114
|
+
const options = createOptions();
|
|
115
|
+
const route = new Route({
|
|
116
|
+
options,
|
|
117
|
+
toType: RouteType.push,
|
|
118
|
+
toInput: "/posts/456/comments/789"
|
|
119
|
+
});
|
|
120
|
+
expect(route.params.postId).toBe("456");
|
|
121
|
+
expect(route.params.commentId).toBe("789");
|
|
122
|
+
expect(route.matched.length).toBeGreaterThan(0);
|
|
123
|
+
});
|
|
124
|
+
it("should handle unmatched routes", () => {
|
|
125
|
+
const options = createOptions();
|
|
126
|
+
const route = new Route({
|
|
127
|
+
options,
|
|
128
|
+
toType: RouteType.push,
|
|
129
|
+
toInput: "/unknown/path"
|
|
130
|
+
});
|
|
131
|
+
expect(route.matched).toHaveLength(0);
|
|
132
|
+
expect(route.config).toBeNull();
|
|
133
|
+
expect(route.meta).toEqual({});
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
describe("State and Metadata Handling", () => {
|
|
137
|
+
it("should correctly set route metadata", () => {
|
|
138
|
+
const options = createOptions();
|
|
139
|
+
const route = new Route({
|
|
140
|
+
options,
|
|
141
|
+
toType: RouteType.push,
|
|
142
|
+
toInput: "/users/123"
|
|
143
|
+
});
|
|
144
|
+
expect(route.meta.title).toBe("User Detail");
|
|
145
|
+
expect(route.meta.requireAuth).toBe(true);
|
|
146
|
+
});
|
|
147
|
+
it("should correctly initialize state object", () => {
|
|
148
|
+
const options = createOptions();
|
|
149
|
+
const initialState = {
|
|
150
|
+
userId: 123,
|
|
151
|
+
permissions: ["read", "write"]
|
|
152
|
+
};
|
|
153
|
+
const route = new Route({
|
|
154
|
+
options,
|
|
155
|
+
toType: RouteType.push,
|
|
156
|
+
toInput: { path: "/users/123", state: initialState }
|
|
157
|
+
});
|
|
158
|
+
expect(route.state).toEqual(initialState);
|
|
159
|
+
expect(route.state).not.toBe(initialState);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
describe("\u{1F50D} Cross-domain and Path Calculation Tests", () => {
|
|
163
|
+
it("should handle cross-domain URLs (different origin)", () => {
|
|
164
|
+
const options = createOptions({
|
|
165
|
+
base: new URL("http://localhost:3000/app/")
|
|
166
|
+
});
|
|
167
|
+
const route = new Route({
|
|
168
|
+
options,
|
|
169
|
+
toType: RouteType.push,
|
|
170
|
+
toInput: "https://external.com/api/data"
|
|
171
|
+
});
|
|
172
|
+
expect(route.matched).toHaveLength(0);
|
|
173
|
+
expect(route.config).toBeNull();
|
|
174
|
+
expect(route.path).toBe("/api/data");
|
|
175
|
+
expect(route.fullPath).toBe("/api/data");
|
|
176
|
+
});
|
|
177
|
+
it("should handle URLs with different base paths", () => {
|
|
178
|
+
const options = createOptions({
|
|
179
|
+
base: new URL("http://localhost:3000/app/")
|
|
180
|
+
});
|
|
181
|
+
const route = new Route({
|
|
182
|
+
options,
|
|
183
|
+
toType: RouteType.push,
|
|
184
|
+
toInput: "http://localhost:3000/other/path"
|
|
185
|
+
});
|
|
186
|
+
expect(route.matched).toHaveLength(0);
|
|
187
|
+
expect(route.config).toBeNull();
|
|
188
|
+
expect(route.path).toBe("/other/path");
|
|
189
|
+
});
|
|
190
|
+
it("should correctly calculate path when matched", () => {
|
|
191
|
+
const options = createOptions({
|
|
192
|
+
base: new URL("http://localhost:3000/app/")
|
|
193
|
+
});
|
|
194
|
+
const route = new Route({
|
|
195
|
+
options,
|
|
196
|
+
toType: RouteType.push,
|
|
197
|
+
toInput: "http://localhost:3000/app/users/123"
|
|
198
|
+
});
|
|
199
|
+
expect(route.path).toBe("/users/123");
|
|
200
|
+
expect(route.matched.length).toBeGreaterThan(0);
|
|
201
|
+
});
|
|
202
|
+
it("should correctly calculate fullPath when unmatched", () => {
|
|
203
|
+
const options = createOptions();
|
|
204
|
+
const route = new Route({
|
|
205
|
+
options,
|
|
206
|
+
toType: RouteType.push,
|
|
207
|
+
toInput: "https://external.com/api/data?key=value#section"
|
|
208
|
+
});
|
|
209
|
+
expect(route.fullPath).toBe("/api/data?key=value#section");
|
|
210
|
+
expect(route.path).toBe("/api/data");
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
describe("\u{1F527} normalizeURL Integration Tests", () => {
|
|
214
|
+
it("should use custom normalizeURL function", () => {
|
|
215
|
+
const customNormalizeURL = vi.fn(
|
|
216
|
+
(url, from) => {
|
|
217
|
+
url.pathname = url.pathname.toLowerCase();
|
|
218
|
+
return url;
|
|
219
|
+
}
|
|
220
|
+
);
|
|
221
|
+
const options = createOptions({
|
|
222
|
+
normalizeURL: customNormalizeURL
|
|
223
|
+
});
|
|
224
|
+
const route = new Route({
|
|
225
|
+
options,
|
|
226
|
+
toType: RouteType.push,
|
|
227
|
+
toInput: "/USERS/123"
|
|
228
|
+
});
|
|
229
|
+
expect(customNormalizeURL).toHaveBeenCalled();
|
|
230
|
+
expect(route.path).toBe("/users/123");
|
|
231
|
+
});
|
|
232
|
+
it("should pass from parameter to normalizeURL", () => {
|
|
233
|
+
const customNormalizeURL = vi.fn(
|
|
234
|
+
(url, from) => url
|
|
235
|
+
);
|
|
236
|
+
const options = createOptions({
|
|
237
|
+
normalizeURL: customNormalizeURL
|
|
238
|
+
});
|
|
239
|
+
const fromURL = new URL("http://localhost:3000/app/previous");
|
|
240
|
+
const route = new Route({
|
|
241
|
+
options,
|
|
242
|
+
toType: RouteType.push,
|
|
243
|
+
toInput: "/users/123",
|
|
244
|
+
from: fromURL
|
|
245
|
+
});
|
|
246
|
+
expect(customNormalizeURL).toHaveBeenCalledWith(
|
|
247
|
+
expect.any(URL),
|
|
248
|
+
fromURL
|
|
249
|
+
);
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
describe("Property Enumerability", () => {
|
|
253
|
+
it("should correctly set non-enumerable properties", () => {
|
|
254
|
+
const options = createOptions();
|
|
255
|
+
const route = new Route({
|
|
256
|
+
options,
|
|
257
|
+
toType: RouteType.push,
|
|
258
|
+
toInput: "/users/123"
|
|
259
|
+
});
|
|
260
|
+
NON_ENUMERABLE_PROPERTIES.forEach((prop) => {
|
|
261
|
+
const descriptor = Object.getOwnPropertyDescriptor(
|
|
262
|
+
route,
|
|
263
|
+
prop
|
|
264
|
+
);
|
|
265
|
+
expect(descriptor == null ? void 0 : descriptor.enumerable).toBe(false);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
it("should keep user properties enumerable", () => {
|
|
269
|
+
const options = createOptions();
|
|
270
|
+
const route = new Route({
|
|
271
|
+
options,
|
|
272
|
+
toType: RouteType.push,
|
|
273
|
+
toInput: "/users/123"
|
|
274
|
+
});
|
|
275
|
+
const userProperties = [
|
|
276
|
+
"path",
|
|
277
|
+
"fullPath",
|
|
278
|
+
"params",
|
|
279
|
+
"query",
|
|
280
|
+
"meta",
|
|
281
|
+
"state"
|
|
282
|
+
];
|
|
283
|
+
userProperties.forEach((prop) => {
|
|
284
|
+
const descriptor = Object.getOwnPropertyDescriptor(
|
|
285
|
+
route,
|
|
286
|
+
prop
|
|
287
|
+
);
|
|
288
|
+
expect(descriptor == null ? void 0 : descriptor.enumerable).toBe(true);
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
describe("\u{1F527} Property Tests", () => {
|
|
294
|
+
describe("Read-only Property Validation", () => {
|
|
295
|
+
it("should validate property existence", () => {
|
|
296
|
+
const options = createOptions();
|
|
297
|
+
const route = new Route({
|
|
298
|
+
options,
|
|
299
|
+
toType: RouteType.push,
|
|
300
|
+
toInput: "/users/123"
|
|
301
|
+
});
|
|
302
|
+
expect(route.path).toBeDefined();
|
|
303
|
+
expect(route.fullPath).toBeDefined();
|
|
304
|
+
expect(route.url).toBeDefined();
|
|
305
|
+
expect(route.params).toBeDefined();
|
|
306
|
+
expect(route.query).toBeDefined();
|
|
307
|
+
expect(route.matched).toBeDefined();
|
|
308
|
+
expect(route.config).toBeDefined();
|
|
309
|
+
expect(route.meta).toBeDefined();
|
|
310
|
+
expect(route.confirm).toBeDefined();
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
describe("Computed Property Correctness", () => {
|
|
314
|
+
it("should correctly calculate isPush property", () => {
|
|
315
|
+
const options = createOptions();
|
|
316
|
+
const pushRoute = new Route({
|
|
317
|
+
options,
|
|
318
|
+
toType: RouteType.push,
|
|
319
|
+
toInput: "/test"
|
|
320
|
+
});
|
|
321
|
+
expect(pushRoute.isPush).toBe(true);
|
|
322
|
+
const pushWindowRoute = new Route({
|
|
323
|
+
options,
|
|
324
|
+
toType: RouteType.pushWindow,
|
|
325
|
+
toInput: "/test"
|
|
326
|
+
});
|
|
327
|
+
expect(pushWindowRoute.isPush).toBe(true);
|
|
328
|
+
const replaceRoute = new Route({
|
|
329
|
+
options,
|
|
330
|
+
toType: RouteType.replace,
|
|
331
|
+
toInput: "/test"
|
|
332
|
+
});
|
|
333
|
+
expect(replaceRoute.isPush).toBe(false);
|
|
334
|
+
const goRoute = new Route({
|
|
335
|
+
options,
|
|
336
|
+
toType: RouteType.go,
|
|
337
|
+
toInput: "/test"
|
|
338
|
+
});
|
|
339
|
+
expect(goRoute.isPush).toBe(false);
|
|
340
|
+
});
|
|
341
|
+
it("should correctly calculate fullPath", () => {
|
|
342
|
+
const options = createOptions();
|
|
343
|
+
const route = new Route({
|
|
344
|
+
options,
|
|
345
|
+
toType: RouteType.push,
|
|
346
|
+
toInput: "/users/123?tab=profile#section1"
|
|
347
|
+
});
|
|
348
|
+
expect(route.fullPath).toBe("/users/123?tab=profile#section1");
|
|
349
|
+
expect(route.path).toBe("/users/123");
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
describe("Type Validation", () => {
|
|
353
|
+
it("should correctly set all RouteType", () => {
|
|
354
|
+
const options = createOptions();
|
|
355
|
+
Object.values(RouteType).forEach((type) => {
|
|
356
|
+
const route = new Route({
|
|
357
|
+
options,
|
|
358
|
+
toType: type,
|
|
359
|
+
toInput: "/test"
|
|
360
|
+
});
|
|
361
|
+
expect(route.type).toBe(type);
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
describe("Confirm Handler Tests", () => {
|
|
366
|
+
it("should initialize confirm as null when not provided", () => {
|
|
367
|
+
const options = createOptions();
|
|
368
|
+
const route = new Route({
|
|
369
|
+
options,
|
|
370
|
+
toType: RouteType.push,
|
|
371
|
+
toInput: "/users/123"
|
|
372
|
+
});
|
|
373
|
+
expect(route.confirm).toBeNull();
|
|
374
|
+
});
|
|
375
|
+
it("should set confirm handler when provided in RouteLocation", () => {
|
|
376
|
+
const options = createOptions();
|
|
377
|
+
const mockConfirmHandler = vi.fn();
|
|
378
|
+
const route = new Route({
|
|
379
|
+
options,
|
|
380
|
+
toType: RouteType.push,
|
|
381
|
+
toInput: {
|
|
382
|
+
path: "/users/123",
|
|
383
|
+
confirm: mockConfirmHandler
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
expect(route.confirm).toBe(mockConfirmHandler);
|
|
387
|
+
expect(typeof route.confirm).toBe("function");
|
|
388
|
+
});
|
|
389
|
+
it("should handle confirm as null when toInput is string", () => {
|
|
390
|
+
const options = createOptions();
|
|
391
|
+
const route = new Route({
|
|
392
|
+
options,
|
|
393
|
+
toType: RouteType.push,
|
|
394
|
+
toInput: "/users/123"
|
|
395
|
+
});
|
|
396
|
+
expect(route.confirm).toBeNull();
|
|
397
|
+
});
|
|
398
|
+
it("should preserve confirm handler during route cloning", () => {
|
|
399
|
+
const options = createOptions();
|
|
400
|
+
const mockConfirmHandler = vi.fn();
|
|
401
|
+
const originalRoute = new Route({
|
|
402
|
+
options,
|
|
403
|
+
toType: RouteType.push,
|
|
404
|
+
toInput: {
|
|
405
|
+
path: "/users/123",
|
|
406
|
+
confirm: mockConfirmHandler
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
const clonedRoute = originalRoute.clone();
|
|
410
|
+
expect(clonedRoute.confirm).toBe(mockConfirmHandler);
|
|
411
|
+
expect(clonedRoute.confirm).toBe(originalRoute.confirm);
|
|
412
|
+
});
|
|
413
|
+
it("should handle null confirm during route cloning", () => {
|
|
414
|
+
const options = createOptions();
|
|
415
|
+
const originalRoute = new Route({
|
|
416
|
+
options,
|
|
417
|
+
toType: RouteType.push,
|
|
418
|
+
toInput: "/users/123"
|
|
419
|
+
});
|
|
420
|
+
const clonedRoute = originalRoute.clone();
|
|
421
|
+
expect(clonedRoute.confirm).toBeNull();
|
|
422
|
+
expect(clonedRoute.confirm).toBe(originalRoute.confirm);
|
|
423
|
+
});
|
|
424
|
+
it("should make confirm property non-enumerable", () => {
|
|
425
|
+
const options = createOptions();
|
|
426
|
+
const mockConfirmHandler = vi.fn();
|
|
427
|
+
const route = new Route({
|
|
428
|
+
options,
|
|
429
|
+
toType: RouteType.push,
|
|
430
|
+
toInput: {
|
|
431
|
+
path: "/users/123",
|
|
432
|
+
confirm: mockConfirmHandler
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
const keys = Object.keys(route);
|
|
436
|
+
const propertyNames = Object.getOwnPropertyNames(route);
|
|
437
|
+
const descriptor = Object.getOwnPropertyDescriptor(
|
|
438
|
+
route,
|
|
439
|
+
"confirm"
|
|
440
|
+
);
|
|
441
|
+
expect(keys).not.toContain("confirm");
|
|
442
|
+
expect(propertyNames).toContain("confirm");
|
|
443
|
+
expect(descriptor == null ? void 0 : descriptor.enumerable).toBe(false);
|
|
444
|
+
});
|
|
445
|
+
it("should be included in NON_ENUMERABLE_PROPERTIES list", () => {
|
|
446
|
+
expect(NON_ENUMERABLE_PROPERTIES).toContain("confirm");
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
});
|
|
450
|
+
describe("\u{1F3AF} Handle mechanism tests", () => {
|
|
451
|
+
describe("Handle setting and getting", () => {
|
|
452
|
+
it("should correctly set and get handle function", () => {
|
|
453
|
+
const options = createOptions();
|
|
454
|
+
const route = new Route({
|
|
455
|
+
options,
|
|
456
|
+
toType: RouteType.push,
|
|
457
|
+
toInput: "/users/123"
|
|
458
|
+
});
|
|
459
|
+
const mockHandle = vi.fn(
|
|
460
|
+
(to, from, router) => ({
|
|
461
|
+
result: "test"
|
|
462
|
+
})
|
|
463
|
+
);
|
|
464
|
+
route.handle = mockHandle;
|
|
465
|
+
expect(route.handle).toBeDefined();
|
|
466
|
+
expect(typeof route.handle).toBe("function");
|
|
467
|
+
});
|
|
468
|
+
it("should handle null handle", () => {
|
|
469
|
+
const options = createOptions();
|
|
470
|
+
const route = new Route({
|
|
471
|
+
options,
|
|
472
|
+
toType: RouteType.push,
|
|
473
|
+
toInput: "/users/123"
|
|
474
|
+
});
|
|
475
|
+
route.handle = null;
|
|
476
|
+
expect(route.handle).toBeNull();
|
|
477
|
+
});
|
|
478
|
+
it("should handle non-function type handle", () => {
|
|
479
|
+
const options = createOptions();
|
|
480
|
+
const route = new Route({
|
|
481
|
+
options,
|
|
482
|
+
toType: RouteType.push,
|
|
483
|
+
toInput: "/users/123"
|
|
484
|
+
});
|
|
485
|
+
route.handle = "not a function";
|
|
486
|
+
expect(route.handle).toBeNull();
|
|
487
|
+
});
|
|
488
|
+
});
|
|
489
|
+
describe("Handle execution validation", () => {
|
|
490
|
+
it("should execute handle in correct state", () => {
|
|
491
|
+
const options = createOptions();
|
|
492
|
+
const route = new Route({
|
|
493
|
+
options,
|
|
494
|
+
toType: RouteType.push,
|
|
495
|
+
toInput: "/users/123"
|
|
496
|
+
});
|
|
497
|
+
const mockRouter = {};
|
|
498
|
+
const mockHandle = vi.fn(
|
|
499
|
+
(to, from, router) => ({
|
|
500
|
+
result: "success"
|
|
501
|
+
})
|
|
502
|
+
);
|
|
503
|
+
route.handle = mockHandle;
|
|
504
|
+
const result = route.handle(route, null, mockRouter);
|
|
505
|
+
expect(result).toEqual({ result: "success" });
|
|
506
|
+
expect(mockHandle).toHaveBeenCalledWith(
|
|
507
|
+
route,
|
|
508
|
+
null,
|
|
509
|
+
mockRouter
|
|
510
|
+
);
|
|
511
|
+
});
|
|
512
|
+
it("should throw exception in error state", () => {
|
|
513
|
+
const options = createOptions();
|
|
514
|
+
const route = new Route({
|
|
515
|
+
options,
|
|
516
|
+
toType: RouteType.push,
|
|
517
|
+
toInput: "/users/123"
|
|
518
|
+
});
|
|
519
|
+
const mockRouter = {};
|
|
520
|
+
const mockHandle = vi.fn();
|
|
521
|
+
route.handle = mockHandle;
|
|
522
|
+
expect(() => {
|
|
523
|
+
route.handle(route, null, mockRouter);
|
|
524
|
+
}).not.toThrow();
|
|
525
|
+
});
|
|
526
|
+
it("should prevent repeated handle calls", () => {
|
|
527
|
+
const options = createOptions();
|
|
528
|
+
const route = new Route({
|
|
529
|
+
options,
|
|
530
|
+
toType: RouteType.push,
|
|
531
|
+
toInput: "/users/123"
|
|
532
|
+
});
|
|
533
|
+
const mockRouter = {};
|
|
534
|
+
const mockHandle = vi.fn(
|
|
535
|
+
(to, from, router) => ({
|
|
536
|
+
result: "test"
|
|
537
|
+
})
|
|
538
|
+
);
|
|
539
|
+
route.handle = mockHandle;
|
|
540
|
+
route.handle(route, null, mockRouter);
|
|
541
|
+
expect(() => {
|
|
542
|
+
route.handle(route, null, mockRouter);
|
|
543
|
+
}).toThrow(
|
|
544
|
+
"Route handle hook can only be called once per navigation"
|
|
545
|
+
);
|
|
546
|
+
});
|
|
547
|
+
});
|
|
548
|
+
describe("HandleResult management", () => {
|
|
549
|
+
it("should correctly set and get handleResult", () => {
|
|
550
|
+
const options = createOptions();
|
|
551
|
+
const route = new Route({
|
|
552
|
+
options,
|
|
553
|
+
toType: RouteType.push,
|
|
554
|
+
toInput: "/users/123"
|
|
555
|
+
});
|
|
556
|
+
const result = { data: "test", status: "ok" };
|
|
557
|
+
route.handleResult = result;
|
|
558
|
+
expect(route.handleResult).toBe(result);
|
|
559
|
+
route.handleResult = null;
|
|
560
|
+
expect(route.handleResult).toBeNull();
|
|
561
|
+
});
|
|
562
|
+
});
|
|
563
|
+
describe("Handle wrapper function tests", () => {
|
|
564
|
+
it("should test handle calls with double-call prevention", () => {
|
|
565
|
+
const options = createOptions();
|
|
566
|
+
const route = new Route({
|
|
567
|
+
options,
|
|
568
|
+
toType: RouteType.push,
|
|
569
|
+
toInput: "/users/123"
|
|
570
|
+
});
|
|
571
|
+
const mockRouter = {};
|
|
572
|
+
const mockHandle = vi.fn(
|
|
573
|
+
(to, from, router) => ({
|
|
574
|
+
result: "test"
|
|
575
|
+
})
|
|
576
|
+
);
|
|
577
|
+
route.handle = mockHandle;
|
|
578
|
+
const firstResult = route.handle(route, null, mockRouter);
|
|
579
|
+
expect(firstResult).toEqual({ result: "test" });
|
|
580
|
+
expect(() => route.handle(route, null, mockRouter)).toThrow(
|
|
581
|
+
"Route handle hook can only be called once per navigation"
|
|
582
|
+
);
|
|
583
|
+
});
|
|
584
|
+
it("should correctly pass this context and parameters", () => {
|
|
585
|
+
const options = createOptions();
|
|
586
|
+
const route = new Route({
|
|
587
|
+
options,
|
|
588
|
+
toType: RouteType.push,
|
|
589
|
+
toInput: "/users/123"
|
|
590
|
+
});
|
|
591
|
+
const mockRouter = {};
|
|
592
|
+
const mockHandle = vi.fn(function(to, from, router) {
|
|
593
|
+
expect(this).toBe(route);
|
|
594
|
+
return { context: this, to, from, router };
|
|
595
|
+
});
|
|
596
|
+
route.handle = mockHandle;
|
|
597
|
+
const fromRoute = new Route({
|
|
598
|
+
options,
|
|
599
|
+
toType: RouteType.push,
|
|
600
|
+
toInput: "/home"
|
|
601
|
+
});
|
|
602
|
+
const result = route.handle(route, fromRoute, mockRouter);
|
|
603
|
+
expect(mockHandle).toHaveBeenCalledWith(
|
|
604
|
+
route,
|
|
605
|
+
fromRoute,
|
|
606
|
+
mockRouter
|
|
607
|
+
);
|
|
608
|
+
expect(result).toEqual({
|
|
609
|
+
context: route,
|
|
610
|
+
to: route,
|
|
611
|
+
from: fromRoute,
|
|
612
|
+
router: mockRouter
|
|
613
|
+
});
|
|
614
|
+
});
|
|
615
|
+
it("should handle handle function exceptions", () => {
|
|
616
|
+
const options = createOptions();
|
|
617
|
+
const route = new Route({
|
|
618
|
+
options,
|
|
619
|
+
toType: RouteType.push,
|
|
620
|
+
toInput: "/users/123"
|
|
621
|
+
});
|
|
622
|
+
const mockRouter = {};
|
|
623
|
+
const errorHandle = vi.fn(() => {
|
|
624
|
+
throw new Error("Handle execution failed");
|
|
625
|
+
});
|
|
626
|
+
route.handle = errorHandle;
|
|
627
|
+
expect(() => route.handle(route, null, mockRouter)).toThrow(
|
|
628
|
+
"Handle execution failed"
|
|
629
|
+
);
|
|
630
|
+
expect(errorHandle).toHaveBeenCalledOnce();
|
|
631
|
+
});
|
|
632
|
+
it("should handle setHandle boundary cases", () => {
|
|
633
|
+
const options = createOptions();
|
|
634
|
+
const route = new Route({
|
|
635
|
+
options,
|
|
636
|
+
toType: RouteType.push,
|
|
637
|
+
toInput: "/users/123"
|
|
638
|
+
});
|
|
639
|
+
route.setHandle(void 0);
|
|
640
|
+
expect(route.handle).toBeNull();
|
|
641
|
+
route.setHandle(123);
|
|
642
|
+
expect(route.handle).toBeNull();
|
|
643
|
+
route.setHandle("string");
|
|
644
|
+
expect(route.handle).toBeNull();
|
|
645
|
+
route.setHandle({});
|
|
646
|
+
expect(route.handle).toBeNull();
|
|
647
|
+
route.setHandle([]);
|
|
648
|
+
expect(route.handle).toBeNull();
|
|
649
|
+
});
|
|
650
|
+
});
|
|
651
|
+
});
|
|
652
|
+
describe("\u{1F4CA} State management tests", () => {
|
|
653
|
+
describe("Navigation state application", () => {
|
|
654
|
+
it("should correctly apply navigation state", () => {
|
|
655
|
+
const options = createOptions();
|
|
656
|
+
const route = new Route({
|
|
657
|
+
options,
|
|
658
|
+
toType: RouteType.push,
|
|
659
|
+
toInput: { path: "/users/123", state: { a: 1, b: 2 } }
|
|
660
|
+
});
|
|
661
|
+
route.applyNavigationState({ b: 3, c: 4 });
|
|
662
|
+
expect(route.state).toEqual({ a: 1, b: 3, c: 4 });
|
|
663
|
+
});
|
|
664
|
+
it("should handle empty navigation state application", () => {
|
|
665
|
+
const options = createOptions();
|
|
666
|
+
const route = new Route({
|
|
667
|
+
options,
|
|
668
|
+
toType: RouteType.push,
|
|
669
|
+
toInput: "/users/123"
|
|
670
|
+
});
|
|
671
|
+
route.applyNavigationState({ __pageId__: 123 });
|
|
672
|
+
expect(route.state).toEqual({ __pageId__: 123 });
|
|
673
|
+
});
|
|
674
|
+
});
|
|
675
|
+
describe("Direct state assignment", () => {
|
|
676
|
+
it("should correctly set single state value", () => {
|
|
677
|
+
const options = createOptions();
|
|
678
|
+
const route = new Route({
|
|
679
|
+
options,
|
|
680
|
+
toType: RouteType.push,
|
|
681
|
+
toInput: "/users/123"
|
|
682
|
+
});
|
|
683
|
+
route.state.userId = 123;
|
|
684
|
+
route.state.userName = "john";
|
|
685
|
+
expect(route.state.userId).toBe(123);
|
|
686
|
+
expect(route.state.userName).toBe("john");
|
|
687
|
+
});
|
|
688
|
+
it("should overwrite existing state value", () => {
|
|
689
|
+
const options = createOptions();
|
|
690
|
+
const route = new Route({
|
|
691
|
+
options,
|
|
692
|
+
toType: RouteType.push,
|
|
693
|
+
toInput: { path: "/users/123", state: { count: 1 } }
|
|
694
|
+
});
|
|
695
|
+
route.state.count = 2;
|
|
696
|
+
expect(route.state.count).toBe(2);
|
|
697
|
+
});
|
|
698
|
+
});
|
|
699
|
+
describe("State isolation", () => {
|
|
700
|
+
it("should ensure different routes' states are independent", () => {
|
|
701
|
+
const options = createOptions();
|
|
702
|
+
const route1 = new Route({
|
|
703
|
+
options,
|
|
704
|
+
toType: RouteType.push,
|
|
705
|
+
toInput: { path: "/route1", state: { shared: "value1" } }
|
|
706
|
+
});
|
|
707
|
+
const route2 = new Route({
|
|
708
|
+
options,
|
|
709
|
+
toType: RouteType.push,
|
|
710
|
+
toInput: { path: "/route2", state: { shared: "value2" } }
|
|
711
|
+
});
|
|
712
|
+
route1.state.shared = "modified1";
|
|
713
|
+
expect(route2.state.shared).toBe("value2");
|
|
714
|
+
});
|
|
715
|
+
});
|
|
716
|
+
describe("StatusCode tests", () => {
|
|
717
|
+
it("should correctly set default statusCode", () => {
|
|
718
|
+
const options = createOptions();
|
|
719
|
+
const routeWithoutCode = new Route({
|
|
720
|
+
options,
|
|
721
|
+
toType: RouteType.push,
|
|
722
|
+
toInput: "/users/123"
|
|
723
|
+
});
|
|
724
|
+
expect(routeWithoutCode.statusCode).toBe(null);
|
|
725
|
+
const unmatchedRoute = new Route({
|
|
726
|
+
options,
|
|
727
|
+
toType: RouteType.push,
|
|
728
|
+
toInput: "/completely/unknown/path/that/does/not/match"
|
|
729
|
+
});
|
|
730
|
+
expect(unmatchedRoute.statusCode).toBe(null);
|
|
731
|
+
});
|
|
732
|
+
it("should support statusCode input from RouteLocation", () => {
|
|
733
|
+
const options = createOptions();
|
|
734
|
+
const routeWithCode = new Route({
|
|
735
|
+
options,
|
|
736
|
+
toType: RouteType.push,
|
|
737
|
+
toInput: { path: "/users/123", statusCode: 201 }
|
|
738
|
+
});
|
|
739
|
+
expect(routeWithCode.statusCode).toBe(201);
|
|
740
|
+
const routeWithNull = new Route({
|
|
741
|
+
options,
|
|
742
|
+
toType: RouteType.push,
|
|
743
|
+
toInput: { path: "/users/123", statusCode: null }
|
|
744
|
+
});
|
|
745
|
+
expect(routeWithNull.statusCode).toBe(null);
|
|
746
|
+
});
|
|
747
|
+
it("should set statusCode as non-enumerable", () => {
|
|
748
|
+
const options = createOptions();
|
|
749
|
+
const route = new Route({
|
|
750
|
+
options,
|
|
751
|
+
toType: RouteType.push,
|
|
752
|
+
toInput: "/users/123"
|
|
753
|
+
});
|
|
754
|
+
const descriptor = Object.getOwnPropertyDescriptor(
|
|
755
|
+
route,
|
|
756
|
+
"statusCode"
|
|
757
|
+
);
|
|
758
|
+
expect(descriptor == null ? void 0 : descriptor.enumerable).toBe(false);
|
|
759
|
+
const keys = Object.keys(route);
|
|
760
|
+
expect(keys).not.toContain("statusCode");
|
|
761
|
+
});
|
|
762
|
+
it("should correctly copy statusCode in clone", () => {
|
|
763
|
+
const options = createOptions();
|
|
764
|
+
const originalRoute = new Route({
|
|
765
|
+
options,
|
|
766
|
+
toType: RouteType.push,
|
|
767
|
+
toInput: { path: "/users/123", statusCode: 500 }
|
|
768
|
+
});
|
|
769
|
+
const clonedRoute = originalRoute.clone();
|
|
770
|
+
expect(clonedRoute.statusCode).toBe(500);
|
|
771
|
+
expect(clonedRoute.statusCode).toBe(500);
|
|
772
|
+
expect(originalRoute.statusCode).toBe(500);
|
|
773
|
+
});
|
|
774
|
+
});
|
|
775
|
+
});
|
|
776
|
+
describe("\u{1F504} Clone function tests", () => {
|
|
777
|
+
describe("Object independence", () => {
|
|
778
|
+
it("should create completely independent cloned object", () => {
|
|
779
|
+
const options = createOptions();
|
|
780
|
+
const original = new Route({
|
|
781
|
+
options,
|
|
782
|
+
toType: RouteType.push,
|
|
783
|
+
toInput: { path: "/users/123", state: { test: "value" } }
|
|
784
|
+
});
|
|
785
|
+
const cloned = original.clone();
|
|
786
|
+
expect(cloned).not.toBe(original);
|
|
787
|
+
expect(cloned.state).not.toBe(original.state);
|
|
788
|
+
expect(cloned.params).not.toBe(original.params);
|
|
789
|
+
});
|
|
790
|
+
it("should keep attribute values equal", () => {
|
|
791
|
+
const options = createOptions();
|
|
792
|
+
const original = new Route({
|
|
793
|
+
options,
|
|
794
|
+
toType: RouteType.push,
|
|
795
|
+
toInput: {
|
|
796
|
+
path: "/users/123",
|
|
797
|
+
state: { userId: 123, preferences: { theme: "dark" } }
|
|
798
|
+
}
|
|
799
|
+
});
|
|
800
|
+
const cloned = original.clone();
|
|
801
|
+
expect(cloned.path).toBe(original.path);
|
|
802
|
+
expect(cloned.type).toBe(original.type);
|
|
803
|
+
expect(cloned.state).toEqual(original.state);
|
|
804
|
+
expect(cloned.params).toEqual(original.params);
|
|
805
|
+
});
|
|
806
|
+
});
|
|
807
|
+
describe("State deep copy", () => {
|
|
808
|
+
it("should deep copy state object", () => {
|
|
809
|
+
const options = createOptions();
|
|
810
|
+
const original = new Route({
|
|
811
|
+
options,
|
|
812
|
+
toType: RouteType.push,
|
|
813
|
+
toInput: {
|
|
814
|
+
path: "/users/123",
|
|
815
|
+
state: {
|
|
816
|
+
user: { id: 123, name: "John" },
|
|
817
|
+
settings: { theme: "dark" }
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
});
|
|
821
|
+
const cloned = original.clone();
|
|
822
|
+
cloned.state.newProp = "newValue";
|
|
823
|
+
expect(original.state.newProp).toBeUndefined();
|
|
824
|
+
});
|
|
825
|
+
});
|
|
826
|
+
describe("Attribute completeness", () => {
|
|
827
|
+
it("should keep all important attributes", () => {
|
|
828
|
+
const options = createOptions();
|
|
829
|
+
const original = new Route({
|
|
830
|
+
options,
|
|
831
|
+
toType: RouteType.pushWindow,
|
|
832
|
+
toInput: "/users/123?tab=profile#section1"
|
|
833
|
+
});
|
|
834
|
+
const cloned = original.clone();
|
|
835
|
+
expect(cloned.type).toBe(original.type);
|
|
836
|
+
expect(cloned.isPush).toBe(original.isPush);
|
|
837
|
+
expect(cloned.path).toBe(original.path);
|
|
838
|
+
expect(cloned.fullPath).toBe(original.fullPath);
|
|
839
|
+
expect(cloned.query).toEqual(original.query);
|
|
840
|
+
expect(cloned.params).toEqual(original.params);
|
|
841
|
+
expect(cloned.meta).toEqual(original.meta);
|
|
842
|
+
});
|
|
843
|
+
});
|
|
844
|
+
});
|
|
845
|
+
describe("\u26A0\uFE0F Edge case tests", () => {
|
|
846
|
+
describe("Exception input handling", () => {
|
|
847
|
+
it("should handle invalid route type", () => {
|
|
848
|
+
const options = createOptions();
|
|
849
|
+
expect(() => {
|
|
850
|
+
new Route({
|
|
851
|
+
options,
|
|
852
|
+
toType: "invalid",
|
|
853
|
+
toInput: "/test"
|
|
854
|
+
});
|
|
855
|
+
}).not.toThrow();
|
|
856
|
+
});
|
|
857
|
+
it("should handle empty string path", () => {
|
|
858
|
+
const options = createOptions();
|
|
859
|
+
const route = new Route({
|
|
860
|
+
options,
|
|
861
|
+
toType: RouteType.push,
|
|
862
|
+
toInput: ""
|
|
863
|
+
});
|
|
864
|
+
expect(route.path).toBeDefined();
|
|
865
|
+
expect(route.fullPath).toBeDefined();
|
|
866
|
+
});
|
|
867
|
+
it("should handle special character path", () => {
|
|
868
|
+
const options = createOptions();
|
|
869
|
+
const route = new Route({
|
|
870
|
+
options,
|
|
871
|
+
toType: RouteType.push,
|
|
872
|
+
toInput: "/users/\u6D4B\u8BD5\u7528\u6237/profile?name=\u5F20\u4E09"
|
|
873
|
+
});
|
|
874
|
+
expect(route.path).toContain("users");
|
|
875
|
+
expect(route.path).toContain("profile");
|
|
876
|
+
expect(route.query.name).toBe("\u5F20\u4E09");
|
|
877
|
+
});
|
|
878
|
+
});
|
|
879
|
+
describe("Extreme value tests", () => {
|
|
880
|
+
it("should handle very long path", () => {
|
|
881
|
+
const options = createOptions();
|
|
882
|
+
const longPath = "/users/" + "a".repeat(1e3);
|
|
883
|
+
expect(() => {
|
|
884
|
+
new Route({
|
|
885
|
+
options,
|
|
886
|
+
toType: RouteType.push,
|
|
887
|
+
toInput: longPath
|
|
888
|
+
});
|
|
889
|
+
}).not.toThrow();
|
|
890
|
+
});
|
|
891
|
+
it("should handle large number of query parameters", () => {
|
|
892
|
+
const options = createOptions();
|
|
893
|
+
const queryParams = Array.from(
|
|
894
|
+
{ length: 100 },
|
|
895
|
+
(_, i) => `param${i}=value${i}`
|
|
896
|
+
).join("&");
|
|
897
|
+
const path = `/test?${queryParams}`;
|
|
898
|
+
const route = new Route({
|
|
899
|
+
options,
|
|
900
|
+
toType: RouteType.push,
|
|
901
|
+
toInput: path
|
|
902
|
+
});
|
|
903
|
+
expect(Object.keys(route.query)).toHaveLength(100);
|
|
904
|
+
expect(route.query.param0).toBe("value0");
|
|
905
|
+
expect(route.query.param99).toBe("value99");
|
|
906
|
+
});
|
|
907
|
+
});
|
|
908
|
+
});
|
|
909
|
+
describe("\u{1F527} Tool function tests", () => {
|
|
910
|
+
describe("applyRouteParams function", () => {
|
|
911
|
+
it("should correctly apply route parameters", () => {
|
|
912
|
+
const base = new URL("http://localhost:3000/app/");
|
|
913
|
+
const options = createOptions({ base });
|
|
914
|
+
const to = new URL("http://localhost:3000/app/users/old-id");
|
|
915
|
+
const match = options.matcher(to, base);
|
|
916
|
+
const toInput = {
|
|
917
|
+
path: "/users/old-id",
|
|
918
|
+
params: { id: "new-id" }
|
|
919
|
+
};
|
|
920
|
+
applyRouteParams(match, toInput, base, to);
|
|
921
|
+
expect(to.pathname).toBe("/app/users/new-id");
|
|
922
|
+
expect(match.params.id).toBe("new-id");
|
|
923
|
+
});
|
|
924
|
+
it("should handle multiple parameters", () => {
|
|
925
|
+
const base = new URL("http://localhost:3000/app/");
|
|
926
|
+
const options = createOptions({
|
|
927
|
+
base,
|
|
928
|
+
routes: [{ path: "/posts/:postId/comments/:commentId" }]
|
|
929
|
+
});
|
|
930
|
+
const to = new URL(
|
|
931
|
+
"http://localhost:3000/app/posts/123/comments/456"
|
|
932
|
+
);
|
|
933
|
+
const match = options.matcher(to, base);
|
|
934
|
+
const toInput = {
|
|
935
|
+
path: "/posts/123/comments/456",
|
|
936
|
+
params: { postId: "post-999", commentId: "comment-888" }
|
|
937
|
+
};
|
|
938
|
+
applyRouteParams(match, toInput, base, to);
|
|
939
|
+
expect(to.pathname).toBe(
|
|
940
|
+
"/app/posts/post-999/comments/comment-888"
|
|
941
|
+
);
|
|
942
|
+
expect(match.params.postId).toBe("post-999");
|
|
943
|
+
expect(match.params.commentId).toBe("comment-888");
|
|
944
|
+
});
|
|
945
|
+
it("should return directly when unmatched", () => {
|
|
946
|
+
const base = new URL("http://localhost:3000/app/");
|
|
947
|
+
const options = createOptions({ routes: [] });
|
|
948
|
+
const to = new URL("http://localhost:3000/app/unknown");
|
|
949
|
+
const originalPathname = to.pathname;
|
|
950
|
+
const match = options.matcher(to, base);
|
|
951
|
+
const toInput = { path: "/unknown", params: { id: "test" } };
|
|
952
|
+
applyRouteParams(match, toInput, base, to);
|
|
953
|
+
expect(to.pathname).toBe(originalPathname);
|
|
954
|
+
});
|
|
955
|
+
it("should handle non-object toInput parameters", () => {
|
|
956
|
+
const base = new URL("http://localhost:3000/app/");
|
|
957
|
+
const options = createOptions();
|
|
958
|
+
const to = new URL("http://localhost:3000/app/users/123");
|
|
959
|
+
const originalPathname = to.pathname;
|
|
960
|
+
const match = options.matcher(to, base);
|
|
961
|
+
applyRouteParams(match, "/users/123", base, to);
|
|
962
|
+
expect(to.pathname).toBe(originalPathname);
|
|
963
|
+
applyRouteParams(match, null, base, to);
|
|
964
|
+
expect(to.pathname).toBe(originalPathname);
|
|
965
|
+
applyRouteParams(match, void 0, base, to);
|
|
966
|
+
expect(to.pathname).toBe(originalPathname);
|
|
967
|
+
});
|
|
968
|
+
it("should handle empty params object", () => {
|
|
969
|
+
const base = new URL("http://localhost:3000/app/");
|
|
970
|
+
const options = createOptions();
|
|
971
|
+
const to = new URL("http://localhost:3000/app/users/123");
|
|
972
|
+
const originalPathname = to.pathname;
|
|
973
|
+
const match = options.matcher(to, base);
|
|
974
|
+
const toInput = { path: "/users/123", params: {} };
|
|
975
|
+
applyRouteParams(match, toInput, base, to);
|
|
976
|
+
expect(to.pathname).toBe(originalPathname);
|
|
977
|
+
const toInput2 = {
|
|
978
|
+
path: "/users/123",
|
|
979
|
+
params: void 0
|
|
980
|
+
};
|
|
981
|
+
applyRouteParams(match, toInput2, base, to);
|
|
982
|
+
expect(to.pathname).toBe(originalPathname);
|
|
983
|
+
});
|
|
984
|
+
it("should handle complex path replacement logic", () => {
|
|
985
|
+
const base = new URL("http://localhost:3000/app/");
|
|
986
|
+
const options = createOptions({
|
|
987
|
+
base,
|
|
988
|
+
routes: [{ path: "/users/:id/posts/:postId" }]
|
|
989
|
+
});
|
|
990
|
+
const to = new URL(
|
|
991
|
+
"http://localhost:3000/app/users/123/posts/456"
|
|
992
|
+
);
|
|
993
|
+
const match = options.matcher(to, base);
|
|
994
|
+
const toInput = {
|
|
995
|
+
path: "/users/123/posts/456",
|
|
996
|
+
params: { id: "user-999", postId: "post-888" }
|
|
997
|
+
};
|
|
998
|
+
applyRouteParams(match, toInput, base, to);
|
|
999
|
+
expect(to.pathname).toBe("/app/users/user-999/posts/post-888");
|
|
1000
|
+
expect(match.params.id).toBe("user-999");
|
|
1001
|
+
expect(match.params.postId).toBe("post-888");
|
|
1002
|
+
});
|
|
1003
|
+
it("should handle path segment empty cases", () => {
|
|
1004
|
+
const base = new URL("http://localhost:3000/app/");
|
|
1005
|
+
const options = createOptions({
|
|
1006
|
+
base,
|
|
1007
|
+
routes: [{ path: "/users/:id" }]
|
|
1008
|
+
});
|
|
1009
|
+
const to = new URL("http://localhost:3000/app/users/123");
|
|
1010
|
+
const match = options.matcher(to, base);
|
|
1011
|
+
const originalCompile = match.matches[0].compile;
|
|
1012
|
+
match.matches[0].compile = vi.fn(() => "/users/");
|
|
1013
|
+
const toInput = { path: "/users/123", params: { id: "" } };
|
|
1014
|
+
applyRouteParams(match, toInput, base, to);
|
|
1015
|
+
expect(to.pathname).toBe("/app/users/123");
|
|
1016
|
+
match.matches[0].compile = originalCompile;
|
|
1017
|
+
});
|
|
1018
|
+
});
|
|
1019
|
+
});
|
|
1020
|
+
describe("\u{1F517} Integration tests", () => {
|
|
1021
|
+
describe("With router options integration", () => {
|
|
1022
|
+
it("should correctly use custom normalizeURL", () => {
|
|
1023
|
+
const customNormalizeURL = vi.fn((url) => {
|
|
1024
|
+
url.pathname = url.pathname.toLowerCase();
|
|
1025
|
+
return url;
|
|
1026
|
+
});
|
|
1027
|
+
const options = createOptions({
|
|
1028
|
+
normalizeURL: customNormalizeURL
|
|
1029
|
+
});
|
|
1030
|
+
const route = new Route({
|
|
1031
|
+
options,
|
|
1032
|
+
toType: RouteType.push,
|
|
1033
|
+
toInput: "/USERS/123"
|
|
1034
|
+
});
|
|
1035
|
+
expect(customNormalizeURL).toHaveBeenCalled();
|
|
1036
|
+
expect(route.path).toBe("/users/123");
|
|
1037
|
+
});
|
|
1038
|
+
it("should correctly handle SSR related attributes", () => {
|
|
1039
|
+
const mockReq = {};
|
|
1040
|
+
const mockRes = {};
|
|
1041
|
+
const options = createOptions({ req: mockReq, res: mockRes });
|
|
1042
|
+
const route = new Route({
|
|
1043
|
+
options,
|
|
1044
|
+
toType: RouteType.push,
|
|
1045
|
+
toInput: "/users/123"
|
|
1046
|
+
});
|
|
1047
|
+
expect(route.req).toBe(mockReq);
|
|
1048
|
+
expect(route.res).toBe(mockRes);
|
|
1049
|
+
});
|
|
1050
|
+
});
|
|
1051
|
+
describe("With route configuration integration", () => {
|
|
1052
|
+
it("should correctly handle nested route configuration", () => {
|
|
1053
|
+
const nestedRoutes = [
|
|
1054
|
+
{
|
|
1055
|
+
path: "/admin",
|
|
1056
|
+
meta: { requireAuth: true },
|
|
1057
|
+
children: [
|
|
1058
|
+
{
|
|
1059
|
+
path: "/users",
|
|
1060
|
+
meta: { title: "User Management" }
|
|
1061
|
+
}
|
|
1062
|
+
]
|
|
1063
|
+
}
|
|
1064
|
+
];
|
|
1065
|
+
const options = createOptions({ routes: nestedRoutes });
|
|
1066
|
+
const route = new Route({
|
|
1067
|
+
options,
|
|
1068
|
+
toType: RouteType.push,
|
|
1069
|
+
toInput: "/admin/users"
|
|
1070
|
+
});
|
|
1071
|
+
expect(route.matched.length).toBeGreaterThan(0);
|
|
1072
|
+
expect(route.meta.title).toBe("User Management");
|
|
1073
|
+
});
|
|
1074
|
+
});
|
|
1075
|
+
});
|
|
1076
|
+
describe("\u{1F3AD} Performance tests", () => {
|
|
1077
|
+
it("should create a large number of route instances within reasonable time", () => {
|
|
1078
|
+
const options = createOptions();
|
|
1079
|
+
const startTime = performance.now();
|
|
1080
|
+
for (let i = 0; i < 1e3; i++) {
|
|
1081
|
+
new Route({
|
|
1082
|
+
options,
|
|
1083
|
+
toType: RouteType.push,
|
|
1084
|
+
toInput: `/users/${i}`
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
const endTime = performance.now();
|
|
1088
|
+
const duration = endTime - startTime;
|
|
1089
|
+
expect(duration).toBeLessThan(100);
|
|
1090
|
+
});
|
|
1091
|
+
it("should efficiently handle state operations", () => {
|
|
1092
|
+
const options = createOptions();
|
|
1093
|
+
const route = new Route({
|
|
1094
|
+
options,
|
|
1095
|
+
toType: RouteType.push,
|
|
1096
|
+
toInput: "/test"
|
|
1097
|
+
});
|
|
1098
|
+
const startTime = performance.now();
|
|
1099
|
+
for (let i = 0; i < 1e3; i++) {
|
|
1100
|
+
route.state[`key${i}`] = `value${i}`;
|
|
1101
|
+
}
|
|
1102
|
+
const endTime = performance.now();
|
|
1103
|
+
const duration = endTime - startTime;
|
|
1104
|
+
expect(duration).toBeLessThan(50);
|
|
1105
|
+
expect(Object.keys(route.state)).toHaveLength(1e3);
|
|
1106
|
+
});
|
|
1107
|
+
});
|
|
1108
|
+
});
|
|
1109
|
+
describe("\u{1F50D} Route Class Depth Test - Missing Scenario Supplement", () => {
|
|
1110
|
+
const createOptions = (overrides = {}) => {
|
|
1111
|
+
const base = new URL("http://localhost:3000/app/");
|
|
1112
|
+
const mockRoutes = [
|
|
1113
|
+
{
|
|
1114
|
+
path: "/users/:id",
|
|
1115
|
+
meta: { title: "User Detail", requireAuth: true }
|
|
1116
|
+
},
|
|
1117
|
+
{
|
|
1118
|
+
path: "/posts/:postId/comments/:commentId",
|
|
1119
|
+
meta: { title: "Comment Detail" }
|
|
1120
|
+
}
|
|
1121
|
+
];
|
|
1122
|
+
const routerOptions = {
|
|
1123
|
+
root: "#test",
|
|
1124
|
+
context: { version: "1.0.0" },
|
|
1125
|
+
routes: mockRoutes,
|
|
1126
|
+
mode: RouterMode.history,
|
|
1127
|
+
base,
|
|
1128
|
+
req: null,
|
|
1129
|
+
res: null,
|
|
1130
|
+
apps: {},
|
|
1131
|
+
normalizeURL: (url) => url,
|
|
1132
|
+
fallback: () => {
|
|
1133
|
+
},
|
|
1134
|
+
rootStyle: false,
|
|
1135
|
+
handleBackBoundary: () => {
|
|
1136
|
+
},
|
|
1137
|
+
handleLayerClose: () => {
|
|
1138
|
+
},
|
|
1139
|
+
...overrides
|
|
1140
|
+
};
|
|
1141
|
+
return parsedOptions(routerOptions);
|
|
1142
|
+
};
|
|
1143
|
+
describe("\u{1F527} applyRouteParams Boundary Condition Tests", () => {
|
|
1144
|
+
it("should handle non-object toInput parameters", () => {
|
|
1145
|
+
const base = new URL("http://localhost:3000/app/");
|
|
1146
|
+
const options = createOptions();
|
|
1147
|
+
const to = new URL("http://localhost:3000/app/users/123");
|
|
1148
|
+
const originalPathname = to.pathname;
|
|
1149
|
+
const match = options.matcher(to, base);
|
|
1150
|
+
applyRouteParams(match, "/users/123", base, to);
|
|
1151
|
+
expect(to.pathname).toBe(originalPathname);
|
|
1152
|
+
applyRouteParams(match, null, base, to);
|
|
1153
|
+
expect(to.pathname).toBe(originalPathname);
|
|
1154
|
+
applyRouteParams(match, void 0, base, to);
|
|
1155
|
+
expect(to.pathname).toBe(originalPathname);
|
|
1156
|
+
});
|
|
1157
|
+
it("should handle empty params object", () => {
|
|
1158
|
+
const base = new URL("http://localhost:3000/app/");
|
|
1159
|
+
const options = createOptions();
|
|
1160
|
+
const to = new URL("http://localhost:3000/app/users/123");
|
|
1161
|
+
const originalPathname = to.pathname;
|
|
1162
|
+
const match = options.matcher(to, base);
|
|
1163
|
+
const toInput = { path: "/users/123", params: {} };
|
|
1164
|
+
applyRouteParams(match, toInput, base, to);
|
|
1165
|
+
expect(to.pathname).toBe(originalPathname);
|
|
1166
|
+
const toInput2 = { path: "/users/123", params: void 0 };
|
|
1167
|
+
applyRouteParams(match, toInput2, base, to);
|
|
1168
|
+
expect(to.pathname).toBe(originalPathname);
|
|
1169
|
+
});
|
|
1170
|
+
it("should handle path segment empty cases", () => {
|
|
1171
|
+
const base = new URL("http://localhost:3000/app/");
|
|
1172
|
+
const options = createOptions({
|
|
1173
|
+
base,
|
|
1174
|
+
routes: [{ path: "/users/:id" }]
|
|
1175
|
+
});
|
|
1176
|
+
const to = new URL("http://localhost:3000/app/users/123");
|
|
1177
|
+
const match = options.matcher(to, base);
|
|
1178
|
+
const originalCompile = match.matches[0].compile;
|
|
1179
|
+
match.matches[0].compile = vi.fn(() => "/users/");
|
|
1180
|
+
const toInput = { path: "/users/123", params: { id: "" } };
|
|
1181
|
+
applyRouteParams(match, toInput, base, to);
|
|
1182
|
+
expect(to.pathname).toBe("/app/users/123");
|
|
1183
|
+
match.matches[0].compile = originalCompile;
|
|
1184
|
+
});
|
|
1185
|
+
});
|
|
1186
|
+
describe("\u{1F3AF} Query Parameter Processing Depth Test", () => {
|
|
1187
|
+
it("should handle query parameter de-duplication logic", () => {
|
|
1188
|
+
const options = createOptions();
|
|
1189
|
+
const route = new Route({
|
|
1190
|
+
options,
|
|
1191
|
+
toType: RouteType.push,
|
|
1192
|
+
toInput: "/users/123?name=john&name=jane&age=25&name=bob"
|
|
1193
|
+
});
|
|
1194
|
+
expect(route.query.name).toBe("john");
|
|
1195
|
+
expect(route.query.age).toBe("25");
|
|
1196
|
+
expect(route.queryArray.name).toEqual(["john", "jane", "bob"]);
|
|
1197
|
+
expect(route.queryArray.age).toEqual(["25"]);
|
|
1198
|
+
});
|
|
1199
|
+
it("should handle empty query parameter values", () => {
|
|
1200
|
+
const options = createOptions();
|
|
1201
|
+
const route = new Route({
|
|
1202
|
+
options,
|
|
1203
|
+
toType: RouteType.push,
|
|
1204
|
+
toInput: "/users/123?empty=&name=john&blank&value=test"
|
|
1205
|
+
});
|
|
1206
|
+
expect(route.query.empty).toBe("");
|
|
1207
|
+
expect(route.query.name).toBe("john");
|
|
1208
|
+
expect(route.query.blank).toBe("");
|
|
1209
|
+
expect(route.query.value).toBe("test");
|
|
1210
|
+
});
|
|
1211
|
+
it("should handle special character query parameters", () => {
|
|
1212
|
+
const options = createOptions();
|
|
1213
|
+
const route = new Route({
|
|
1214
|
+
options,
|
|
1215
|
+
toType: RouteType.push,
|
|
1216
|
+
toInput: "/users/123?name=%E5%BC%A0%E4%B8%89&symbol=%26%3D%3F%23"
|
|
1217
|
+
});
|
|
1218
|
+
expect(route.query.name).toBe("\u5F20\u4E09");
|
|
1219
|
+
expect(route.query.symbol).toBe("&=?#");
|
|
1220
|
+
});
|
|
1221
|
+
});
|
|
1222
|
+
describe("\u{1F504} Clone Function Depth Test", () => {
|
|
1223
|
+
it("should correctly clone complex state object", () => {
|
|
1224
|
+
const options = createOptions();
|
|
1225
|
+
const complexState = {
|
|
1226
|
+
user: { id: 123, name: "John", roles: ["admin", "user"] },
|
|
1227
|
+
settings: { theme: "dark", notifications: true },
|
|
1228
|
+
metadata: { created: /* @__PURE__ */ new Date(), version: 1 }
|
|
1229
|
+
};
|
|
1230
|
+
const original = new Route({
|
|
1231
|
+
options,
|
|
1232
|
+
toType: RouteType.push,
|
|
1233
|
+
toInput: { path: "/users/123", state: complexState }
|
|
1234
|
+
});
|
|
1235
|
+
const cloned = original.clone();
|
|
1236
|
+
expect(cloned.state).toEqual(original.state);
|
|
1237
|
+
expect(cloned.state).not.toBe(original.state);
|
|
1238
|
+
cloned.state.newProp = "newValue";
|
|
1239
|
+
expect(original.state.newProp).toBeUndefined();
|
|
1240
|
+
});
|
|
1241
|
+
it("should keep cloned object _options reference", () => {
|
|
1242
|
+
const options = createOptions();
|
|
1243
|
+
const original = new Route({
|
|
1244
|
+
options,
|
|
1245
|
+
toType: RouteType.push,
|
|
1246
|
+
toInput: "/users/123"
|
|
1247
|
+
});
|
|
1248
|
+
const cloned = original.clone();
|
|
1249
|
+
expect(cloned._options).toBe(original._options);
|
|
1250
|
+
});
|
|
1251
|
+
it("should correctly clone routes with query parameters and hash", () => {
|
|
1252
|
+
const options = createOptions();
|
|
1253
|
+
const original = new Route({
|
|
1254
|
+
options,
|
|
1255
|
+
toType: RouteType.pushWindow,
|
|
1256
|
+
toInput: "/users/123?tab=profile&sort=name#section1"
|
|
1257
|
+
});
|
|
1258
|
+
const cloned = original.clone();
|
|
1259
|
+
expect(cloned.fullPath).toBe(original.fullPath);
|
|
1260
|
+
expect(cloned.query).toEqual(original.query);
|
|
1261
|
+
expect(cloned.type).toBe(original.type);
|
|
1262
|
+
expect(cloned.isPush).toBe(original.isPush);
|
|
1263
|
+
});
|
|
1264
|
+
});
|
|
1265
|
+
describe("\u{1F3D7}\uFE0F Constructor Boundary Condition Tests", () => {
|
|
1266
|
+
it("should handle keepScrollPosition various values", () => {
|
|
1267
|
+
const options = createOptions();
|
|
1268
|
+
const route1 = new Route({
|
|
1269
|
+
options,
|
|
1270
|
+
toType: RouteType.push,
|
|
1271
|
+
toInput: { path: "/test", keepScrollPosition: true }
|
|
1272
|
+
});
|
|
1273
|
+
expect(route1.keepScrollPosition).toBe(true);
|
|
1274
|
+
const route2 = new Route({
|
|
1275
|
+
options,
|
|
1276
|
+
toType: RouteType.push,
|
|
1277
|
+
toInput: { path: "/test", keepScrollPosition: false }
|
|
1278
|
+
});
|
|
1279
|
+
expect(route2.keepScrollPosition).toBe(false);
|
|
1280
|
+
const route3 = new Route({
|
|
1281
|
+
options,
|
|
1282
|
+
toType: RouteType.push,
|
|
1283
|
+
toInput: { path: "/test", keepScrollPosition: "yes" }
|
|
1284
|
+
});
|
|
1285
|
+
expect(route3.keepScrollPosition).toBe(true);
|
|
1286
|
+
const route4 = new Route({
|
|
1287
|
+
options,
|
|
1288
|
+
toType: RouteType.push,
|
|
1289
|
+
toInput: { path: "/test", keepScrollPosition: 0 }
|
|
1290
|
+
});
|
|
1291
|
+
expect(route4.keepScrollPosition).toBe(false);
|
|
1292
|
+
const route5 = new Route({
|
|
1293
|
+
options,
|
|
1294
|
+
toType: RouteType.push,
|
|
1295
|
+
toInput: "/test"
|
|
1296
|
+
});
|
|
1297
|
+
expect(route5.keepScrollPosition).toBe(false);
|
|
1298
|
+
});
|
|
1299
|
+
it("should correctly handle config and meta calculation", () => {
|
|
1300
|
+
const options = createOptions();
|
|
1301
|
+
const matchedRoute = new Route({
|
|
1302
|
+
options,
|
|
1303
|
+
toType: RouteType.push,
|
|
1304
|
+
toInput: "/users/123"
|
|
1305
|
+
});
|
|
1306
|
+
expect(matchedRoute.config).not.toBeNull();
|
|
1307
|
+
expect(matchedRoute.meta.title).toBe("User Detail");
|
|
1308
|
+
const unmatchedRoute = new Route({
|
|
1309
|
+
options,
|
|
1310
|
+
toType: RouteType.push,
|
|
1311
|
+
toInput: "/unknown"
|
|
1312
|
+
});
|
|
1313
|
+
expect(unmatchedRoute.config).toBeNull();
|
|
1314
|
+
expect(unmatchedRoute.meta).toEqual({});
|
|
1315
|
+
});
|
|
1316
|
+
it("should correctly handle matched array freezing", () => {
|
|
1317
|
+
const options = createOptions();
|
|
1318
|
+
const route = new Route({
|
|
1319
|
+
options,
|
|
1320
|
+
toType: RouteType.push,
|
|
1321
|
+
toInput: "/users/123"
|
|
1322
|
+
});
|
|
1323
|
+
expect(Object.isFrozen(route.matched)).toBe(true);
|
|
1324
|
+
expect(() => {
|
|
1325
|
+
route.matched.push({});
|
|
1326
|
+
}).toThrow();
|
|
1327
|
+
});
|
|
1328
|
+
});
|
|
1329
|
+
describe("\u{1F512} Property Immutable Test", () => {
|
|
1330
|
+
it("should verify read-only property behavior", () => {
|
|
1331
|
+
const options = createOptions();
|
|
1332
|
+
const route = new Route({
|
|
1333
|
+
options,
|
|
1334
|
+
toType: RouteType.push,
|
|
1335
|
+
toInput: "/users/123"
|
|
1336
|
+
});
|
|
1337
|
+
expect(route.params).toBeDefined();
|
|
1338
|
+
expect(route.query).toBeDefined();
|
|
1339
|
+
expect(route.url).toBeDefined();
|
|
1340
|
+
expect(typeof route.params).toBe("object");
|
|
1341
|
+
expect(typeof route.query).toBe("object");
|
|
1342
|
+
expect(route.url instanceof URL).toBe(true);
|
|
1343
|
+
});
|
|
1344
|
+
});
|
|
1345
|
+
describe("\u{1F3A8} State Management Special Cases", () => {
|
|
1346
|
+
it("should handle state object special keys", () => {
|
|
1347
|
+
const options = createOptions();
|
|
1348
|
+
const route = new Route({
|
|
1349
|
+
options,
|
|
1350
|
+
toType: RouteType.push,
|
|
1351
|
+
toInput: {
|
|
1352
|
+
path: "/test",
|
|
1353
|
+
state: {
|
|
1354
|
+
normalKey: "value",
|
|
1355
|
+
specialKey: "specialValue"
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
});
|
|
1359
|
+
expect(route.state.normalKey).toBe("value");
|
|
1360
|
+
expect(route.state.specialKey).toBe("specialValue");
|
|
1361
|
+
});
|
|
1362
|
+
it("should handle state synchronization special keys", () => {
|
|
1363
|
+
const options = createOptions();
|
|
1364
|
+
const sourceRoute = new Route({
|
|
1365
|
+
options,
|
|
1366
|
+
toType: RouteType.push,
|
|
1367
|
+
toInput: {
|
|
1368
|
+
path: "/source",
|
|
1369
|
+
state: {
|
|
1370
|
+
normal: "source",
|
|
1371
|
+
special: "sourceSpecial"
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
});
|
|
1375
|
+
const targetRoute = new Route({
|
|
1376
|
+
options,
|
|
1377
|
+
toType: RouteType.push,
|
|
1378
|
+
toInput: {
|
|
1379
|
+
path: "/target",
|
|
1380
|
+
state: {
|
|
1381
|
+
existing: "target",
|
|
1382
|
+
special: "targetSpecial"
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
});
|
|
1386
|
+
sourceRoute.syncTo(targetRoute);
|
|
1387
|
+
expect(targetRoute.state.normal).toBe("source");
|
|
1388
|
+
expect(targetRoute.state.existing).toBeUndefined();
|
|
1389
|
+
expect(targetRoute.state.special).toBe("sourceSpecial");
|
|
1390
|
+
});
|
|
1391
|
+
});
|
|
1392
|
+
describe("\u{1F504} syncTo Method Tests", () => {
|
|
1393
|
+
it("should fully synchronize all route attributes", () => {
|
|
1394
|
+
const options = createOptions();
|
|
1395
|
+
const sourceRoute = new Route({
|
|
1396
|
+
options,
|
|
1397
|
+
toType: RouteType.push,
|
|
1398
|
+
toInput: {
|
|
1399
|
+
path: "/users/456",
|
|
1400
|
+
state: { userId: 456, name: "Jane" },
|
|
1401
|
+
statusCode: 200
|
|
1402
|
+
}
|
|
1403
|
+
});
|
|
1404
|
+
const targetRoute = new Route({
|
|
1405
|
+
options,
|
|
1406
|
+
toType: RouteType.replace,
|
|
1407
|
+
toInput: {
|
|
1408
|
+
path: "/old/path",
|
|
1409
|
+
state: { oldData: "old" }
|
|
1410
|
+
}
|
|
1411
|
+
});
|
|
1412
|
+
sourceRoute.syncTo(targetRoute);
|
|
1413
|
+
expect(targetRoute.statusCode).toBe(200);
|
|
1414
|
+
expect(targetRoute.state.userId).toBe(456);
|
|
1415
|
+
expect(targetRoute.state.name).toBe("Jane");
|
|
1416
|
+
expect(targetRoute.state.oldData).toBeUndefined();
|
|
1417
|
+
expect(targetRoute.type).toBe(RouteType.push);
|
|
1418
|
+
expect(targetRoute.path).toBe("/users/456");
|
|
1419
|
+
expect(targetRoute.fullPath).toBe("/users/456");
|
|
1420
|
+
expect(targetRoute.params.id).toBe("456");
|
|
1421
|
+
});
|
|
1422
|
+
it("should synchronize params object", () => {
|
|
1423
|
+
const options = createOptions();
|
|
1424
|
+
const sourceRoute = new Route({
|
|
1425
|
+
options,
|
|
1426
|
+
toType: RouteType.push,
|
|
1427
|
+
toInput: "/users/789"
|
|
1428
|
+
});
|
|
1429
|
+
const targetRoute = new Route({
|
|
1430
|
+
options,
|
|
1431
|
+
toType: RouteType.push,
|
|
1432
|
+
toInput: "/posts/123"
|
|
1433
|
+
});
|
|
1434
|
+
sourceRoute.syncTo(targetRoute);
|
|
1435
|
+
expect(targetRoute.params.id).toBe("789");
|
|
1436
|
+
expect(targetRoute.params.postId).toBeUndefined();
|
|
1437
|
+
});
|
|
1438
|
+
it("should synchronize query parameters", () => {
|
|
1439
|
+
const options = createOptions();
|
|
1440
|
+
const sourceRoute = new Route({
|
|
1441
|
+
options,
|
|
1442
|
+
toType: RouteType.push,
|
|
1443
|
+
toInput: "/search?q=test&page=2"
|
|
1444
|
+
});
|
|
1445
|
+
const targetRoute = new Route({
|
|
1446
|
+
options,
|
|
1447
|
+
toType: RouteType.push,
|
|
1448
|
+
toInput: "/old?old=value"
|
|
1449
|
+
});
|
|
1450
|
+
sourceRoute.syncTo(targetRoute);
|
|
1451
|
+
expect(targetRoute.query.q).toBe("test");
|
|
1452
|
+
expect(targetRoute.query.page).toBe("2");
|
|
1453
|
+
expect(targetRoute.query.old).toBeUndefined();
|
|
1454
|
+
});
|
|
1455
|
+
it("should synchronize handle related attributes", () => {
|
|
1456
|
+
const options = createOptions();
|
|
1457
|
+
const sourceRoute = new Route({
|
|
1458
|
+
options,
|
|
1459
|
+
toType: RouteType.push,
|
|
1460
|
+
toInput: "/test"
|
|
1461
|
+
});
|
|
1462
|
+
const mockRouter = {};
|
|
1463
|
+
const mockHandle = vi.fn();
|
|
1464
|
+
sourceRoute.setHandle(mockHandle);
|
|
1465
|
+
sourceRoute._handleResult = { success: true };
|
|
1466
|
+
sourceRoute._handled = true;
|
|
1467
|
+
const targetRoute = new Route({
|
|
1468
|
+
options,
|
|
1469
|
+
toType: RouteType.push,
|
|
1470
|
+
toInput: "/other"
|
|
1471
|
+
});
|
|
1472
|
+
sourceRoute.syncTo(targetRoute);
|
|
1473
|
+
expect(targetRoute._handle).toBe(
|
|
1474
|
+
sourceRoute._handle
|
|
1475
|
+
);
|
|
1476
|
+
expect(targetRoute._handleResult).toEqual({
|
|
1477
|
+
success: true
|
|
1478
|
+
});
|
|
1479
|
+
expect(targetRoute._handled).toBe(true);
|
|
1480
|
+
});
|
|
1481
|
+
});
|
|
1482
|
+
describe("\u{1F3D7}\uFE0F Layer Field Tests", () => {
|
|
1483
|
+
describe("Layer Field Initialization", () => {
|
|
1484
|
+
it("should initialize layer as null for non-pushLayer route types", () => {
|
|
1485
|
+
const options = createOptions();
|
|
1486
|
+
const routeTypes = [
|
|
1487
|
+
RouteType.push,
|
|
1488
|
+
RouteType.replace,
|
|
1489
|
+
RouteType.back,
|
|
1490
|
+
RouteType.forward,
|
|
1491
|
+
RouteType.go,
|
|
1492
|
+
RouteType.pushWindow,
|
|
1493
|
+
RouteType.replaceWindow,
|
|
1494
|
+
RouteType.restartApp,
|
|
1495
|
+
RouteType.unknown
|
|
1496
|
+
];
|
|
1497
|
+
routeTypes.forEach((routeType) => {
|
|
1498
|
+
const route = new Route({
|
|
1499
|
+
options,
|
|
1500
|
+
toType: routeType,
|
|
1501
|
+
toInput: {
|
|
1502
|
+
path: "/test",
|
|
1503
|
+
layer: { zIndex: 1e3 }
|
|
1504
|
+
}
|
|
1505
|
+
});
|
|
1506
|
+
expect(route.layer).toBeNull();
|
|
1507
|
+
});
|
|
1508
|
+
});
|
|
1509
|
+
it("should set layer value for pushLayer route type", () => {
|
|
1510
|
+
var _a, _b;
|
|
1511
|
+
const options = createOptions();
|
|
1512
|
+
const layerConfig = {
|
|
1513
|
+
zIndex: 1e3,
|
|
1514
|
+
params: { userId: 123, mode: "edit" },
|
|
1515
|
+
autoPush: false,
|
|
1516
|
+
push: true
|
|
1517
|
+
};
|
|
1518
|
+
const route = new Route({
|
|
1519
|
+
options,
|
|
1520
|
+
toType: RouteType.pushLayer,
|
|
1521
|
+
toInput: {
|
|
1522
|
+
path: "/user/123",
|
|
1523
|
+
layer: layerConfig
|
|
1524
|
+
}
|
|
1525
|
+
});
|
|
1526
|
+
expect(route.layer).toBe(layerConfig);
|
|
1527
|
+
expect((_a = route.layer) == null ? void 0 : _a.zIndex).toBe(1e3);
|
|
1528
|
+
expect((_b = route.layer) == null ? void 0 : _b.autoPush).toBe(false);
|
|
1529
|
+
});
|
|
1530
|
+
it("should set layer as null for pushLayer without layer config", () => {
|
|
1531
|
+
const options = createOptions();
|
|
1532
|
+
const route = new Route({
|
|
1533
|
+
options,
|
|
1534
|
+
toType: RouteType.pushLayer,
|
|
1535
|
+
toInput: "/test"
|
|
1536
|
+
});
|
|
1537
|
+
expect(route.layer).toBeNull();
|
|
1538
|
+
});
|
|
1539
|
+
it("should set layer as null for pushLayer with string toInput", () => {
|
|
1540
|
+
const options = createOptions();
|
|
1541
|
+
const route = new Route({
|
|
1542
|
+
options,
|
|
1543
|
+
toType: RouteType.pushLayer,
|
|
1544
|
+
toInput: "/test"
|
|
1545
|
+
});
|
|
1546
|
+
expect(route.layer).toBeNull();
|
|
1547
|
+
});
|
|
1548
|
+
});
|
|
1549
|
+
describe("Layer Field Non-Enumerable Property", () => {
|
|
1550
|
+
it("should make layer property non-enumerable", () => {
|
|
1551
|
+
const options = createOptions();
|
|
1552
|
+
const layerConfig = { zIndex: 1e3 };
|
|
1553
|
+
const route = new Route({
|
|
1554
|
+
options,
|
|
1555
|
+
toType: RouteType.pushLayer,
|
|
1556
|
+
toInput: {
|
|
1557
|
+
path: "/test",
|
|
1558
|
+
layer: layerConfig
|
|
1559
|
+
}
|
|
1560
|
+
});
|
|
1561
|
+
const keys = Object.keys(route);
|
|
1562
|
+
const propertyNames = Object.getOwnPropertyNames(route);
|
|
1563
|
+
const descriptor = Object.getOwnPropertyDescriptor(
|
|
1564
|
+
route,
|
|
1565
|
+
"layer"
|
|
1566
|
+
);
|
|
1567
|
+
expect(keys).not.toContain("layer");
|
|
1568
|
+
expect(propertyNames).toContain("layer");
|
|
1569
|
+
expect(descriptor == null ? void 0 : descriptor.enumerable).toBe(false);
|
|
1570
|
+
});
|
|
1571
|
+
it("should be included in NON_ENUMERABLE_PROPERTIES list", () => {
|
|
1572
|
+
expect(NON_ENUMERABLE_PROPERTIES).toContain("layer");
|
|
1573
|
+
});
|
|
1574
|
+
});
|
|
1575
|
+
describe("Layer Field in Route Operations", () => {
|
|
1576
|
+
it("should preserve layer during route cloning", () => {
|
|
1577
|
+
const options = createOptions();
|
|
1578
|
+
const layerConfig = {
|
|
1579
|
+
zIndex: 2e3,
|
|
1580
|
+
params: { modal: true }
|
|
1581
|
+
};
|
|
1582
|
+
const originalRoute = new Route({
|
|
1583
|
+
options,
|
|
1584
|
+
toType: RouteType.pushLayer,
|
|
1585
|
+
toInput: {
|
|
1586
|
+
path: "/modal",
|
|
1587
|
+
layer: layerConfig
|
|
1588
|
+
}
|
|
1589
|
+
});
|
|
1590
|
+
const clonedRoute = originalRoute.clone();
|
|
1591
|
+
expect(clonedRoute.layer).toBe(layerConfig);
|
|
1592
|
+
expect(clonedRoute.layer).toBe(originalRoute.layer);
|
|
1593
|
+
});
|
|
1594
|
+
it("should preserve null layer during route cloning", () => {
|
|
1595
|
+
const options = createOptions();
|
|
1596
|
+
const originalRoute = new Route({
|
|
1597
|
+
options,
|
|
1598
|
+
toType: RouteType.push,
|
|
1599
|
+
toInput: "/test"
|
|
1600
|
+
});
|
|
1601
|
+
const clonedRoute = originalRoute.clone();
|
|
1602
|
+
expect(clonedRoute.layer).toBeNull();
|
|
1603
|
+
expect(clonedRoute.layer).toBe(originalRoute.layer);
|
|
1604
|
+
});
|
|
1605
|
+
it("should synchronize layer field during syncTo operation", () => {
|
|
1606
|
+
const options = createOptions();
|
|
1607
|
+
const layerConfig = {
|
|
1608
|
+
zIndex: 3e3,
|
|
1609
|
+
params: { popup: true }
|
|
1610
|
+
};
|
|
1611
|
+
const sourceRoute = new Route({
|
|
1612
|
+
options,
|
|
1613
|
+
toType: RouteType.pushLayer,
|
|
1614
|
+
toInput: {
|
|
1615
|
+
path: "/popup",
|
|
1616
|
+
layer: layerConfig
|
|
1617
|
+
}
|
|
1618
|
+
});
|
|
1619
|
+
const targetRoute = new Route({
|
|
1620
|
+
options,
|
|
1621
|
+
toType: RouteType.push,
|
|
1622
|
+
toInput: "/target"
|
|
1623
|
+
});
|
|
1624
|
+
sourceRoute.syncTo(targetRoute);
|
|
1625
|
+
expect(targetRoute.layer).toBe(layerConfig);
|
|
1626
|
+
expect(targetRoute.layer).toBe(sourceRoute.layer);
|
|
1627
|
+
});
|
|
1628
|
+
it("should handle complex layer configuration", () => {
|
|
1629
|
+
var _a, _b, _c;
|
|
1630
|
+
const options = createOptions();
|
|
1631
|
+
const complexLayerConfig = {
|
|
1632
|
+
zIndex: 5e3,
|
|
1633
|
+
params: {
|
|
1634
|
+
userId: 456,
|
|
1635
|
+
permissions: ["read", "write"],
|
|
1636
|
+
metadata: {
|
|
1637
|
+
title: "User Settings",
|
|
1638
|
+
description: "Edit user profile"
|
|
1639
|
+
}
|
|
1640
|
+
},
|
|
1641
|
+
shouldClose: () => true,
|
|
1642
|
+
autoPush: true,
|
|
1643
|
+
push: false,
|
|
1644
|
+
routerOptions: {
|
|
1645
|
+
mode: RouterMode.memory
|
|
1646
|
+
}
|
|
1647
|
+
};
|
|
1648
|
+
const route = new Route({
|
|
1649
|
+
options,
|
|
1650
|
+
toType: RouteType.pushLayer,
|
|
1651
|
+
toInput: {
|
|
1652
|
+
path: "/settings",
|
|
1653
|
+
layer: complexLayerConfig
|
|
1654
|
+
}
|
|
1655
|
+
});
|
|
1656
|
+
expect(route.layer).toBe(complexLayerConfig);
|
|
1657
|
+
expect(typeof ((_a = route.layer) == null ? void 0 : _a.shouldClose)).toBe("function");
|
|
1658
|
+
expect((_c = (_b = route.layer) == null ? void 0 : _b.routerOptions) == null ? void 0 : _c.mode).toBe(
|
|
1659
|
+
RouterMode.memory
|
|
1660
|
+
);
|
|
1661
|
+
});
|
|
1662
|
+
});
|
|
1663
|
+
});
|
|
1664
|
+
});
|