@esmx/router 3.0.0-rc.27 → 3.0.0-rc.30
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/README.zh-CN.md +82 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.mjs +0 -1
- package/package.json +4 -4
- package/src/index.ts +0 -3
- package/dist/index.test.d.ts +0 -1
- package/dist/index.test.mjs +0 -8
- package/dist/location.test.d.ts +0 -8
- package/dist/location.test.mjs +0 -370
- package/dist/matcher.test.d.ts +0 -1
- package/dist/matcher.test.mjs +0 -1492
- package/dist/micro-app.dom.test.d.ts +0 -1
- package/dist/micro-app.dom.test.mjs +0 -532
- package/dist/navigation.test.d.ts +0 -1
- package/dist/navigation.test.mjs +0 -681
- package/dist/route-task.test.d.ts +0 -1
- package/dist/route-task.test.mjs +0 -673
- package/dist/route-transition.test.d.ts +0 -1
- package/dist/route-transition.test.mjs +0 -146
- package/dist/route.test.d.ts +0 -1
- package/dist/route.test.mjs +0 -1664
- package/dist/router-back.test.d.ts +0 -1
- package/dist/router-back.test.mjs +0 -361
- package/dist/router-forward.test.d.ts +0 -1
- package/dist/router-forward.test.mjs +0 -376
- package/dist/router-go.test.d.ts +0 -1
- package/dist/router-go.test.mjs +0 -73
- package/dist/router-guards-cleanup.test.d.ts +0 -1
- package/dist/router-guards-cleanup.test.mjs +0 -437
- package/dist/router-push.test.d.ts +0 -1
- package/dist/router-push.test.mjs +0 -115
- package/dist/router-replace.test.d.ts +0 -1
- package/dist/router-replace.test.mjs +0 -114
- package/dist/router-resolve.test.d.ts +0 -1
- package/dist/router-resolve.test.mjs +0 -393
- package/dist/router-restart-app.dom.test.d.ts +0 -1
- package/dist/router-restart-app.dom.test.mjs +0 -616
- package/dist/router-window-navigation.test.d.ts +0 -1
- package/dist/router-window-navigation.test.mjs +0 -359
- package/dist/util.test.d.ts +0 -1
- package/dist/util.test.mjs +0 -1020
- package/src/index.test.ts +0 -9
- package/src/location.test.ts +0 -406
- package/src/matcher.test.ts +0 -1685
- package/src/micro-app.dom.test.ts +0 -708
- package/src/navigation.test.ts +0 -858
- package/src/route-task.test.ts +0 -901
- package/src/route-transition.test.ts +0 -178
- package/src/route.test.ts +0 -2014
- package/src/router-back.test.ts +0 -487
- package/src/router-forward.test.ts +0 -506
- package/src/router-go.test.ts +0 -91
- package/src/router-guards-cleanup.test.ts +0 -595
- package/src/router-push.test.ts +0 -140
- package/src/router-replace.test.ts +0 -139
- package/src/router-resolve.test.ts +0 -475
- package/src/router-restart-app.dom.test.ts +0 -783
- package/src/router-window-navigation.test.ts +0 -457
- package/src/util.test.ts +0 -1262
package/dist/matcher.test.mjs
DELETED
|
@@ -1,1492 +0,0 @@
|
|
|
1
|
-
import { assert, describe, test } from "vitest";
|
|
2
|
-
import { createMatcher, joinPathname } from "./matcher.mjs";
|
|
3
|
-
const BASE_URL = new URL("https://www.esmx.dev");
|
|
4
|
-
describe("joinPathname", () => {
|
|
5
|
-
const testCases = [
|
|
6
|
-
{
|
|
7
|
-
description: "Basic path joining",
|
|
8
|
-
cases: [
|
|
9
|
-
{ path: "test", expected: "/test" },
|
|
10
|
-
{ path: "/test", expected: "/test" },
|
|
11
|
-
{ path: "test/", expected: "/test" },
|
|
12
|
-
{ path: "/test/", expected: "/test" }
|
|
13
|
-
]
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
description: "Path joining with a base",
|
|
17
|
-
cases: [
|
|
18
|
-
{ path: "test", base: "/api", expected: "/api/test" },
|
|
19
|
-
{ path: "/test", base: "/api", expected: "/api/test" },
|
|
20
|
-
{ path: "test", base: "api", expected: "/api/test" },
|
|
21
|
-
{ path: "/test", base: "api", expected: "/api/test" }
|
|
22
|
-
]
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
description: "Multi-level path joining",
|
|
26
|
-
cases: [
|
|
27
|
-
{ path: "test/path", expected: "/test/path" },
|
|
28
|
-
{ path: "/test/path", expected: "/test/path" },
|
|
29
|
-
{ path: "test/path/", expected: "/test/path" },
|
|
30
|
-
{ path: "/test/path/", expected: "/test/path" }
|
|
31
|
-
]
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
description: "Multi-level path joining with a base",
|
|
35
|
-
cases: [
|
|
36
|
-
{ path: "test/path", base: "/api", expected: "/api/test/path" },
|
|
37
|
-
{ path: "/test/path", base: "/api", expected: "/api/test/path" },
|
|
38
|
-
{ path: "test/path", base: "api", expected: "/api/test/path" },
|
|
39
|
-
{ path: "/test/path", base: "api", expected: "/api/test/path" }
|
|
40
|
-
]
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
description: "Handling duplicate slashes",
|
|
44
|
-
cases: [
|
|
45
|
-
{ path: "//test", expected: "/test" },
|
|
46
|
-
{ path: "test//path", expected: "/test/path" },
|
|
47
|
-
{ path: "//test//path//", expected: "/test/path" },
|
|
48
|
-
{ path: "test//path", base: "/api//", expected: "/api/test/path" }
|
|
49
|
-
]
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
description: "Handling empty values",
|
|
53
|
-
cases: [
|
|
54
|
-
{ path: "", expected: "/" },
|
|
55
|
-
{ path: "", base: "", expected: "/" },
|
|
56
|
-
{ path: "test", base: "", expected: "/test" },
|
|
57
|
-
{ path: "", base: "api", expected: "/api" }
|
|
58
|
-
]
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
description: "Paths with special characters",
|
|
62
|
-
cases: [
|
|
63
|
-
{ path: "test-path", expected: "/test-path" },
|
|
64
|
-
{ path: "test_path", expected: "/test_path" },
|
|
65
|
-
{ path: "test.path", expected: "/test.path" },
|
|
66
|
-
{ path: "test:path", expected: "/test:path" },
|
|
67
|
-
{ path: "test@path", expected: "/test@path" }
|
|
68
|
-
]
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
description: "Support for Chinese characters in paths",
|
|
72
|
-
cases: [
|
|
73
|
-
{ path: "\u6D4B\u8BD5", expected: "/\u6D4B\u8BD5" },
|
|
74
|
-
{ path: "\u6D4B\u8BD5/\u8DEF\u5F84", expected: "/\u6D4B\u8BD5/\u8DEF\u5F84" },
|
|
75
|
-
{ path: "\u6D4B\u8BD5", base: "/api", expected: "/api/\u6D4B\u8BD5" }
|
|
76
|
-
]
|
|
77
|
-
}
|
|
78
|
-
];
|
|
79
|
-
const edgeCases = [
|
|
80
|
-
{
|
|
81
|
-
description: "Paths with only slashes or empty strings",
|
|
82
|
-
cases: [
|
|
83
|
-
{ path: "", expected: "/" },
|
|
84
|
-
{ path: "/", expected: "/" },
|
|
85
|
-
{ path: "///", expected: "/" },
|
|
86
|
-
{ path: "/", base: "/", expected: "/" },
|
|
87
|
-
{ path: "/", base: "//", expected: "/" },
|
|
88
|
-
{ path: "//", base: "/", expected: "/" },
|
|
89
|
-
{ path: "//", base: "//", expected: "/" }
|
|
90
|
-
]
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
description: "Extremely long path joining",
|
|
94
|
-
cases: () => {
|
|
95
|
-
const longSegment = "very-long-segment-name-that-could-cause-issues";
|
|
96
|
-
const base = Array(10).fill(longSegment).join("/");
|
|
97
|
-
const path = Array(10).fill(longSegment).join("/");
|
|
98
|
-
const expected = "/".concat(base, "/").concat(path);
|
|
99
|
-
return [{ path, base, expected }];
|
|
100
|
-
}
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
description: "Joining paths with special characters",
|
|
104
|
-
cases: [
|
|
105
|
-
{ path: "\u6D4B\u8BD5\u8DEF\u5F84", base: "\u57FA\u7840", expected: "/\u57FA\u7840/\u6D4B\u8BD5\u8DEF\u5F84" },
|
|
106
|
-
{ path: "path with spaces", base: "base", expected: "/base/path with spaces" },
|
|
107
|
-
{ path: "path-with-dashes", base: "base_with_underscores", expected: "/base_with_underscores/path-with-dashes" }
|
|
108
|
-
]
|
|
109
|
-
},
|
|
110
|
-
{
|
|
111
|
-
description: "Handling URL-encoded characters",
|
|
112
|
-
cases: [
|
|
113
|
-
{ path: "hello%20world", base: "api", expected: "/api/hello%20world" },
|
|
114
|
-
{ path: "user%2Fprofile", base: "v1", expected: "/v1/user%2Fprofile" }
|
|
115
|
-
]
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
description: "Handling dot segments in paths",
|
|
119
|
-
cases: [
|
|
120
|
-
{ path: ".", expected: "/." },
|
|
121
|
-
{ path: "..", expected: "/.." },
|
|
122
|
-
{ path: "./relative", base: "base", expected: "/base/./relative" },
|
|
123
|
-
{ path: "../parent", base: "base", expected: "/base/../parent" }
|
|
124
|
-
]
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
description: "Query parameters and hash do not affect joining",
|
|
128
|
-
cases: [
|
|
129
|
-
{ path: "path?query=1", base: "base", expected: "/base/path?query=1" },
|
|
130
|
-
{ path: "path#hash", base: "base", expected: "/base/path#hash" },
|
|
131
|
-
{ path: "path?q=1#hash", base: "base", expected: "/base/path?q=1#hash" }
|
|
132
|
-
]
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
description: "Paths starting with a colon (route parameters)",
|
|
136
|
-
cases: [
|
|
137
|
-
{ path: ":id", base: "users", expected: "/users/:id" },
|
|
138
|
-
{ path: ":userId/profile", base: "api", expected: "/api/:userId/profile" }
|
|
139
|
-
]
|
|
140
|
-
},
|
|
141
|
-
{
|
|
142
|
-
description: "Paths with wildcard asterisks",
|
|
143
|
-
cases: [
|
|
144
|
-
{ path: ":rest*", base: "files", expected: "/files/:rest*" },
|
|
145
|
-
{ path: ":rest*", base: "assets", expected: "/assets/:rest*" },
|
|
146
|
-
{ path: "images/:rest*", base: "static", expected: "/static/images/:rest*" },
|
|
147
|
-
{ path: "/*splat", base: "base", expected: "/base/*splat" }
|
|
148
|
-
]
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
description: "Optional paths",
|
|
152
|
-
cases: [
|
|
153
|
-
{ path: ":id?", base: "posts", expected: "/posts/:id?" },
|
|
154
|
-
{ path: "comments/:commentId?", base: "articles", expected: "/articles/comments/:commentId?" },
|
|
155
|
-
{ path: "/users{/:id}/delete?", base: "base", expected: "/base/users{/:id}/delete?" }
|
|
156
|
-
]
|
|
157
|
-
},
|
|
158
|
-
{
|
|
159
|
-
description: "Combination of numbers and special symbols",
|
|
160
|
-
cases: [
|
|
161
|
-
{ path: "v1.2.3", base: "api", expected: "/api/v1.2.3" },
|
|
162
|
-
{ path: "user@domain", base: "profile", expected: "/profile/user@domain" },
|
|
163
|
-
{ path: "item_123", base: "products", expected: "/products/item_123" }
|
|
164
|
-
]
|
|
165
|
-
},
|
|
166
|
-
{
|
|
167
|
-
description: "Whitespace character handling",
|
|
168
|
-
cases: [
|
|
169
|
-
{ path: " path ", base: " base ", expected: "/ base / path " },
|
|
170
|
-
{ path: " path ", base: " base ", expected: "/ base / path " }
|
|
171
|
-
]
|
|
172
|
-
},
|
|
173
|
-
{
|
|
174
|
-
description: "Boolean and numeric paths (boundary test)",
|
|
175
|
-
cases: [
|
|
176
|
-
{ path: "true", base: "false", expected: "/false/true" },
|
|
177
|
-
{ path: "0", base: "1", expected: "/1/0" },
|
|
178
|
-
{ path: "NaN", base: "undefined", expected: "/undefined/NaN" }
|
|
179
|
-
]
|
|
180
|
-
},
|
|
181
|
-
{
|
|
182
|
-
description: "Extreme cases of path normalization",
|
|
183
|
-
cases: [
|
|
184
|
-
// Test normalization of multiple slashes
|
|
185
|
-
{ path: "///path///", base: "///base///", expected: "/base/path" },
|
|
186
|
-
{ path: "path////with////slashes", base: "base////with////slashes", expected: "/base/with/slashes/path/with/slashes" }
|
|
187
|
-
]
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
description: "Handling of non-ASCII character paths",
|
|
191
|
-
cases: [
|
|
192
|
-
{ path: "\u043F\u0443\u0442\u044C", base: "\u0431\u0430\u0437\u0430", expected: "/\u0431\u0430\u0437\u0430/\u043F\u0443\u0442\u044C" },
|
|
193
|
-
// Russian
|
|
194
|
-
{ path: "\u30D1\u30B9", base: "\u30D9\u30FC\u30B9", expected: "/\u30D9\u30FC\u30B9/\u30D1\u30B9" },
|
|
195
|
-
// Japanese
|
|
196
|
-
{ path: "\uACBD\uB85C", base: "\uAE30\uBCF8", expected: "/\uAE30\uBCF8/\uACBD\uB85C" },
|
|
197
|
-
// Korean
|
|
198
|
-
{ path: "\u0645\u0633\u0627\u0631", base: "\u0642\u0627\u0639\u062F\u0629", expected: "/\u0642\u0627\u0639\u062F\u0629/\u0645\u0633\u0627\u0631" }
|
|
199
|
-
// Arabic
|
|
200
|
-
]
|
|
201
|
-
},
|
|
202
|
-
{
|
|
203
|
-
description: "Handling of special symbols and punctuation",
|
|
204
|
-
cases: [
|
|
205
|
-
{ path: "path!@#$%^&\\*()", base: "base!@#$%^&\\*()", expected: "/base!@#$%^&\\*()/path!@#$%^&\\*()" },
|
|
206
|
-
{ path: "path\\[]{};:\"'<>\\?", base: "base\\[]{};:\"'<>\\?", expected: "/base\\[]{};:\"'<>\\?/path\\[]{};:\"'<>\\?" },
|
|
207
|
-
{ path: "path\\backslash", base: "base\\backslash\\", expected: "/base\\backslash\\/path\\backslash" }
|
|
208
|
-
]
|
|
209
|
-
},
|
|
210
|
-
{
|
|
211
|
-
description: "Paths with combinations of numbers and symbols",
|
|
212
|
-
cases: [
|
|
213
|
-
{ path: "123.456.789", base: "v1.0.0", expected: "/v1.0.0/123.456.789" },
|
|
214
|
-
{ path: "item-123_abc", base: "category-456_def", expected: "/category-456_def/item-123_abc" },
|
|
215
|
-
{ path: "2023-12-31", base: "2024-01-01", expected: "/2024-01-01/2023-12-31" }
|
|
216
|
-
]
|
|
217
|
-
},
|
|
218
|
-
{
|
|
219
|
-
description: "Various forms of whitespace characters",
|
|
220
|
-
cases: [
|
|
221
|
-
{ path: " ", base: " ", expected: "/ / " },
|
|
222
|
-
{ path: "\n", base: " ", expected: "/ /\n" },
|
|
223
|
-
{ path: "\r\n", base: " \r", expected: "/ \r/\r\n" },
|
|
224
|
-
// Test carriage return and line feed
|
|
225
|
-
{ path: "\xA0", base: "\u2000", expected: "/\u2000/\xA0" }
|
|
226
|
-
// Non-breaking space and em space
|
|
227
|
-
]
|
|
228
|
-
},
|
|
229
|
-
{
|
|
230
|
-
description: "Handling of very long paths",
|
|
231
|
-
cases: () => {
|
|
232
|
-
const veryLongSegment = "a".repeat(1e3);
|
|
233
|
-
const path = veryLongSegment + "/segment";
|
|
234
|
-
const base = "base/" + veryLongSegment;
|
|
235
|
-
const expected = "/" + base + "/" + path;
|
|
236
|
-
return [{ path, base, expected }];
|
|
237
|
-
}
|
|
238
|
-
},
|
|
239
|
-
{
|
|
240
|
-
description: "Boundary cases for path separators",
|
|
241
|
-
cases: [
|
|
242
|
-
{ path: "/", base: "/", expected: "/" },
|
|
243
|
-
{ path: "//", base: "//", expected: "/" },
|
|
244
|
-
{ path: "///", base: "///", expected: "/" },
|
|
245
|
-
{ path: "path/", base: "/base", expected: "/base/path" },
|
|
246
|
-
{ path: "/path/", base: "/base/", expected: "/base/path" }
|
|
247
|
-
]
|
|
248
|
-
},
|
|
249
|
-
{
|
|
250
|
-
description: "URL-encoded path segments",
|
|
251
|
-
cases: [
|
|
252
|
-
{ path: "%20space%20", base: "%20base%20", expected: "/%20base%20/%20space%20" },
|
|
253
|
-
{ path: "%2F%2F", base: "%2F", expected: "/%2F/%2F%2F" },
|
|
254
|
-
{ path: "path%3Fquery%3D1", base: "base%23hash", expected: "/base%23hash/path%3Fquery%3D1" }
|
|
255
|
-
]
|
|
256
|
-
},
|
|
257
|
-
{
|
|
258
|
-
description: "Numeric type paths (type boundary)",
|
|
259
|
-
cases: [
|
|
260
|
-
{ path: "123", base: "456", expected: "/456/123" },
|
|
261
|
-
{ path: "0", expected: "/0" },
|
|
262
|
-
{ path: "", base: "0", expected: "/0" }
|
|
263
|
-
]
|
|
264
|
-
},
|
|
265
|
-
{
|
|
266
|
-
description: "Complex cases with dot notation in paths",
|
|
267
|
-
cases: [
|
|
268
|
-
{ path: "../../../path", base: "../../base", expected: "/../../base/../../../path" },
|
|
269
|
-
{ path: "./././path", base: "./././base", expected: "/./././base/./././path" },
|
|
270
|
-
{ path: "path/./file", base: "base/../dir", expected: "/base/../dir/path/./file" }
|
|
271
|
-
]
|
|
272
|
-
},
|
|
273
|
-
{
|
|
274
|
-
description: "Paths with mixed character sets",
|
|
275
|
-
cases: [
|
|
276
|
-
{ path: "\u4E2D\u6587/english/\u0440\u0443\u0441\u0441\u043A\u0438\u0439", base: "\u65E5\u672C\u8A9E/\u0627\u0644\u0639\u0631\u0628\u064A\u0629", expected: "/\u65E5\u672C\u8A9E/\u0627\u0644\u0639\u0631\u0628\u064A\u0629/\u4E2D\u6587/english/\u0440\u0443\u0441\u0441\u043A\u0438\u0439" },
|
|
277
|
-
{ path: "\u6D4B\u8BD5-test-\u0442\u0435\u0441\u0442", base: "\u57FA\u7840-base-\u0431\u0430\u0437\u0430", expected: "/\u57FA\u7840-base-\u0431\u0430\u0437\u0430/\u6D4B\u8BD5-test-\u0442\u0435\u0441\u0442" }
|
|
278
|
-
]
|
|
279
|
-
},
|
|
280
|
-
{
|
|
281
|
-
description: "Handling of control characters",
|
|
282
|
-
cases: [
|
|
283
|
-
// Test control characters (though uncommon in actual URLs)
|
|
284
|
-
{ path: "", base: "", expected: "//" },
|
|
285
|
-
{ path: "path\x7F", base: "base\x7F", expected: "/base\x7F/path\x7F" }
|
|
286
|
-
]
|
|
287
|
-
},
|
|
288
|
-
{
|
|
289
|
-
description: "Various characters at the end of a path",
|
|
290
|
-
cases: [
|
|
291
|
-
{ path: "path.", base: "base.", expected: "/base./path." },
|
|
292
|
-
{ path: "path-", base: "base-", expected: "/base-/path-" },
|
|
293
|
-
{ path: "path_", base: "base_", expected: "/base_/path_" },
|
|
294
|
-
{ path: "path~", base: "base~", expected: "/base~/path~" }
|
|
295
|
-
]
|
|
296
|
-
}
|
|
297
|
-
];
|
|
298
|
-
const runTests = (testCases2) => testCases2.forEach(({ description, cases }) => {
|
|
299
|
-
if (typeof cases === "function") {
|
|
300
|
-
cases = cases();
|
|
301
|
-
}
|
|
302
|
-
test(description, () => {
|
|
303
|
-
for (const { path, base, expected } of cases) {
|
|
304
|
-
assert.equal(joinPathname(path, base), expected);
|
|
305
|
-
}
|
|
306
|
-
});
|
|
307
|
-
});
|
|
308
|
-
runTests(testCases);
|
|
309
|
-
describe("Edge Cases", () => runTests(edgeCases));
|
|
310
|
-
});
|
|
311
|
-
describe("createMatcher", () => {
|
|
312
|
-
test("Basic route matching", () => {
|
|
313
|
-
const matcher = createMatcher([
|
|
314
|
-
{ path: "/news" },
|
|
315
|
-
{ path: "/news/:id" }
|
|
316
|
-
]);
|
|
317
|
-
const result = matcher(new URL("/news/123", BASE_URL), BASE_URL);
|
|
318
|
-
assert.equal(result.matches.length, 1);
|
|
319
|
-
assert.equal(result.matches[0].path, "/news/:id");
|
|
320
|
-
assert.equal(result.params.id, "123");
|
|
321
|
-
});
|
|
322
|
-
test("Exact route matching priority", () => {
|
|
323
|
-
const matcher = createMatcher([
|
|
324
|
-
{ path: "/news/:id" },
|
|
325
|
-
{ path: "/news" }
|
|
326
|
-
]);
|
|
327
|
-
const result = matcher(new URL("/news", BASE_URL), BASE_URL);
|
|
328
|
-
assert.equal(result.matches.length, 1);
|
|
329
|
-
assert.equal(result.matches[0].path, "/news");
|
|
330
|
-
assert.deepEqual(result.params, {});
|
|
331
|
-
});
|
|
332
|
-
test("Nested route matching", () => {
|
|
333
|
-
const matcher = createMatcher([
|
|
334
|
-
{
|
|
335
|
-
path: "/news",
|
|
336
|
-
children: [{ path: ":id" }]
|
|
337
|
-
}
|
|
338
|
-
]);
|
|
339
|
-
const result = matcher(new URL("/news/123", BASE_URL), BASE_URL);
|
|
340
|
-
assert.equal(result.matches.length, 2);
|
|
341
|
-
assert.equal(result.matches[0].path, "/news");
|
|
342
|
-
assert.equal(result.matches[1].path, ":id");
|
|
343
|
-
assert.equal(result.params.id, "123");
|
|
344
|
-
});
|
|
345
|
-
test("Deeply nested route matching", () => {
|
|
346
|
-
const matcher = createMatcher([
|
|
347
|
-
{
|
|
348
|
-
path: "/user",
|
|
349
|
-
children: [
|
|
350
|
-
{
|
|
351
|
-
path: ":userId",
|
|
352
|
-
children: [{ path: "profile" }, { path: "settings" }]
|
|
353
|
-
}
|
|
354
|
-
]
|
|
355
|
-
}
|
|
356
|
-
]);
|
|
357
|
-
const result = matcher(
|
|
358
|
-
new URL("/user/123/profile", BASE_URL),
|
|
359
|
-
BASE_URL
|
|
360
|
-
);
|
|
361
|
-
assert.equal(result.matches.length, 3);
|
|
362
|
-
assert.equal(result.matches[0].path, "/user");
|
|
363
|
-
assert.equal(result.matches[1].path, ":userId");
|
|
364
|
-
assert.equal(result.matches[2].path, "profile");
|
|
365
|
-
assert.equal(result.params.userId, "123");
|
|
366
|
-
});
|
|
367
|
-
test("Multiple parameter route matching", () => {
|
|
368
|
-
const matcher = createMatcher([{ path: "/user/:userId/post/:postId" }]);
|
|
369
|
-
const result = matcher(
|
|
370
|
-
new URL("/user/123/post/456", BASE_URL),
|
|
371
|
-
BASE_URL
|
|
372
|
-
);
|
|
373
|
-
assert.equal(result.matches.length, 1);
|
|
374
|
-
assert.equal(result.matches[0].path, "/user/:userId/post/:postId");
|
|
375
|
-
assert.equal(result.params.userId, "123");
|
|
376
|
-
assert.equal(result.params.postId, "456");
|
|
377
|
-
});
|
|
378
|
-
test("Optional parameter route matching", () => {
|
|
379
|
-
const matcher = createMatcher([{ path: "/posts/:id?" }]);
|
|
380
|
-
const resultWithParam = matcher(
|
|
381
|
-
new URL("/posts/123", BASE_URL),
|
|
382
|
-
BASE_URL
|
|
383
|
-
);
|
|
384
|
-
assert.equal(resultWithParam.matches.length, 1);
|
|
385
|
-
assert.equal(resultWithParam.params.id, "123");
|
|
386
|
-
const resultWithoutParam = matcher(
|
|
387
|
-
new URL("/posts", BASE_URL),
|
|
388
|
-
BASE_URL
|
|
389
|
-
);
|
|
390
|
-
assert.equal(resultWithoutParam.matches.length, 1);
|
|
391
|
-
assert.equal(resultWithoutParam.params.id, void 0);
|
|
392
|
-
});
|
|
393
|
-
test("Numeric parameter route matching", () => {
|
|
394
|
-
const matcher = createMatcher([{ path: "/posts/:id(\\d+)" }]);
|
|
395
|
-
const resultWithParam = matcher(
|
|
396
|
-
new URL("/posts/123", BASE_URL),
|
|
397
|
-
BASE_URL
|
|
398
|
-
);
|
|
399
|
-
assert.equal(resultWithParam.matches.length, 1);
|
|
400
|
-
assert.equal(resultWithParam.params.id, "123");
|
|
401
|
-
const resultWithoutParam = matcher(
|
|
402
|
-
new URL("/posts/123a", BASE_URL),
|
|
403
|
-
BASE_URL
|
|
404
|
-
);
|
|
405
|
-
assert.equal(resultWithoutParam.matches.length, 0);
|
|
406
|
-
const resultWithNaN = matcher(
|
|
407
|
-
new URL("/posts/NaN", BASE_URL),
|
|
408
|
-
BASE_URL
|
|
409
|
-
);
|
|
410
|
-
assert.equal(resultWithNaN.matches.length, 0);
|
|
411
|
-
});
|
|
412
|
-
test("Wildcard route matching", () => {
|
|
413
|
-
const matcher = createMatcher([{ path: "/files/:rest*" }]);
|
|
414
|
-
const result = matcher(
|
|
415
|
-
new URL("/files/documents/readme.txt", BASE_URL),
|
|
416
|
-
BASE_URL
|
|
417
|
-
);
|
|
418
|
-
assert.equal(result.matches.length, 1);
|
|
419
|
-
assert.equal(result.matches[0].path, "/files/:rest*");
|
|
420
|
-
assert.deepEqual(result.params.rest, ["documents", "readme.txt"]);
|
|
421
|
-
});
|
|
422
|
-
test("RegExp parameter matching", () => {
|
|
423
|
-
const matcher = createMatcher([{ path: "/api/v:version(\\d+)" }]);
|
|
424
|
-
const result = matcher(new URL("/api/v1", BASE_URL), BASE_URL);
|
|
425
|
-
assert.equal(result.matches.length, 1);
|
|
426
|
-
assert.equal(result.params.version, "1");
|
|
427
|
-
});
|
|
428
|
-
test("No matching route", () => {
|
|
429
|
-
const matcher = createMatcher([{ path: "/news" }]);
|
|
430
|
-
const result = matcher(new URL("/blog", BASE_URL), BASE_URL);
|
|
431
|
-
assert.equal(result.matches.length, 0);
|
|
432
|
-
assert.deepEqual(result.params, {});
|
|
433
|
-
});
|
|
434
|
-
test("Empty route configuration", () => {
|
|
435
|
-
const matcher = createMatcher([]);
|
|
436
|
-
const result = matcher(new URL("/any", BASE_URL), BASE_URL);
|
|
437
|
-
assert.equal(result.matches.length, 0);
|
|
438
|
-
assert.deepEqual(result.params, {});
|
|
439
|
-
});
|
|
440
|
-
test("Route meta information passing", () => {
|
|
441
|
-
var _a, _b;
|
|
442
|
-
const matcher = createMatcher([
|
|
443
|
-
{
|
|
444
|
-
path: "/protected",
|
|
445
|
-
meta: { requiresAuth: true }
|
|
446
|
-
}
|
|
447
|
-
]);
|
|
448
|
-
const result = matcher(new URL("/protected", BASE_URL), BASE_URL);
|
|
449
|
-
assert.equal(result.matches.length, 1);
|
|
450
|
-
assert.equal((_b = (_a = result.matches[0]) == null ? void 0 : _a.meta) == null ? void 0 : _b.requiresAuth, true);
|
|
451
|
-
});
|
|
452
|
-
test("Complex nested routes with parameters", () => {
|
|
453
|
-
var _a, _b;
|
|
454
|
-
const matcher = createMatcher([
|
|
455
|
-
{
|
|
456
|
-
path: "/admin",
|
|
457
|
-
meta: { role: "admin" },
|
|
458
|
-
children: [
|
|
459
|
-
{
|
|
460
|
-
path: "users",
|
|
461
|
-
children: [
|
|
462
|
-
{
|
|
463
|
-
path: ":userId",
|
|
464
|
-
children: [{ path: "edit" }]
|
|
465
|
-
}
|
|
466
|
-
]
|
|
467
|
-
}
|
|
468
|
-
]
|
|
469
|
-
}
|
|
470
|
-
]);
|
|
471
|
-
const result = matcher(
|
|
472
|
-
new URL("/admin/users/123/edit", BASE_URL),
|
|
473
|
-
BASE_URL
|
|
474
|
-
);
|
|
475
|
-
assert.equal(result.matches.length, 4);
|
|
476
|
-
assert.equal(result.matches[0].path, "/admin");
|
|
477
|
-
assert.equal(result.matches[1].path, "users");
|
|
478
|
-
assert.equal(result.matches[2].path, ":userId");
|
|
479
|
-
assert.equal(result.matches[3].path, "edit");
|
|
480
|
-
assert.equal(result.params.userId, "123");
|
|
481
|
-
assert.equal((_b = (_a = result.matches[0]) == null ? void 0 : _a.meta) == null ? void 0 : _b.role, "admin");
|
|
482
|
-
});
|
|
483
|
-
test("baseURL with directory", () => {
|
|
484
|
-
const matcher = createMatcher([{ path: "/api" }]);
|
|
485
|
-
const customBaseURL = new URL("https://www.esmx.dev/app/");
|
|
486
|
-
const result = matcher(
|
|
487
|
-
new URL("https://www.esmx.dev/app/api"),
|
|
488
|
-
customBaseURL
|
|
489
|
-
);
|
|
490
|
-
assert.equal(result.matches.length, 1);
|
|
491
|
-
assert.equal(result.matches[0].path, "/api");
|
|
492
|
-
});
|
|
493
|
-
test("URL-encoded parameter handling", () => {
|
|
494
|
-
const matcher = createMatcher([{ path: "/search/:query" }]);
|
|
495
|
-
const result = matcher(
|
|
496
|
-
new URL("/search/hello world", BASE_URL),
|
|
497
|
-
BASE_URL
|
|
498
|
-
);
|
|
499
|
-
assert.equal(result.matches.length, 1);
|
|
500
|
-
assert.equal(result.params.query, "hello%20world");
|
|
501
|
-
});
|
|
502
|
-
test("Chinese path parameters", () => {
|
|
503
|
-
const matcher = createMatcher([
|
|
504
|
-
{ path: "/".concat(encodeURIComponent("\u5206\u7C7B"), "/:name") }
|
|
505
|
-
]);
|
|
506
|
-
const result = matcher(new URL("/\u5206\u7C7B/\u6280\u672F", BASE_URL), BASE_URL);
|
|
507
|
-
assert.equal(result.matches.length, 1);
|
|
508
|
-
assert.equal(result.params.name, encodeURIComponent("\u6280\u672F"));
|
|
509
|
-
});
|
|
510
|
-
test("Duplicate parameter name handling", () => {
|
|
511
|
-
const matcher = createMatcher([
|
|
512
|
-
{
|
|
513
|
-
path: "/parent/:id",
|
|
514
|
-
children: [{ path: "child/:childId" }]
|
|
515
|
-
}
|
|
516
|
-
]);
|
|
517
|
-
const result = matcher(
|
|
518
|
-
new URL("/parent/123/child/456", BASE_URL),
|
|
519
|
-
BASE_URL
|
|
520
|
-
);
|
|
521
|
-
assert.equal(result.matches.length, 2);
|
|
522
|
-
assert.equal(result.params.id, "123");
|
|
523
|
-
assert.equal(result.params.childId, "456");
|
|
524
|
-
});
|
|
525
|
-
test.todo("Route matching order consistency", () => {
|
|
526
|
-
var _a, _b, _c, _d;
|
|
527
|
-
const matcher = createMatcher([
|
|
528
|
-
{
|
|
529
|
-
path: "/a/:id",
|
|
530
|
-
meta: { order: 1 }
|
|
531
|
-
},
|
|
532
|
-
{
|
|
533
|
-
path: "/a/special",
|
|
534
|
-
meta: { order: 2 }
|
|
535
|
-
}
|
|
536
|
-
]);
|
|
537
|
-
const result1 = matcher(new URL("/a/123", BASE_URL), BASE_URL);
|
|
538
|
-
assert.equal(result1.matches.length, 1);
|
|
539
|
-
assert.equal((_b = (_a = result1.matches[0]) == null ? void 0 : _a.meta) == null ? void 0 : _b.order, 1);
|
|
540
|
-
const result2 = matcher(new URL("/a/special", BASE_URL), BASE_URL);
|
|
541
|
-
assert.equal(result2.matches.length, 1);
|
|
542
|
-
assert.equal((_d = (_c = result2.matches[0]) == null ? void 0 : _c.meta) == null ? void 0 : _d.order, 2);
|
|
543
|
-
});
|
|
544
|
-
test("Special characters in path handling", () => {
|
|
545
|
-
const routes = [
|
|
546
|
-
{ path: "/test-path" },
|
|
547
|
-
{ path: "/test_path" },
|
|
548
|
-
{ path: "/test.path" }
|
|
549
|
-
];
|
|
550
|
-
const matcher = createMatcher(routes);
|
|
551
|
-
for (const { path } of routes) {
|
|
552
|
-
const result = matcher(new URL(path, BASE_URL), BASE_URL);
|
|
553
|
-
assert.equal(result.matches.length, 1);
|
|
554
|
-
assert.equal(result.matches[0].path, path);
|
|
555
|
-
}
|
|
556
|
-
});
|
|
557
|
-
test("Empty string path handling", () => {
|
|
558
|
-
const matcher = createMatcher([
|
|
559
|
-
{
|
|
560
|
-
path: "",
|
|
561
|
-
children: [{ path: "child" }]
|
|
562
|
-
}
|
|
563
|
-
]);
|
|
564
|
-
const result = matcher(new URL("/child", BASE_URL), BASE_URL);
|
|
565
|
-
assert.equal(result.matches.length, 2);
|
|
566
|
-
assert.equal(result.matches[0].path, "");
|
|
567
|
-
assert.equal(result.matches[1].path, "child");
|
|
568
|
-
});
|
|
569
|
-
test.todo("Route matching performance verification", () => {
|
|
570
|
-
const routes = Array.from({ length: 1e3 }, (_, i) => ({
|
|
571
|
-
path: "/route".concat(i, "/:id")
|
|
572
|
-
}));
|
|
573
|
-
routes.push({ path: "/target/:id" });
|
|
574
|
-
const matcher = createMatcher(routes);
|
|
575
|
-
const startTime = performance.now();
|
|
576
|
-
const result = matcher(new URL("/target/123", BASE_URL), BASE_URL);
|
|
577
|
-
const endTime = performance.now();
|
|
578
|
-
assert.equal(result.matches.length, 1);
|
|
579
|
-
assert.equal(result.params.id, "123");
|
|
580
|
-
assert.isTrue(endTime - startTime < 10);
|
|
581
|
-
});
|
|
582
|
-
test("Edge case: extremely long path", () => {
|
|
583
|
-
const longPath = "/very/long/path/with/many/segments/that/goes/on/and/on/and/on";
|
|
584
|
-
const matcher = createMatcher([{ path: longPath }]);
|
|
585
|
-
const result = matcher(new URL(longPath, BASE_URL), BASE_URL);
|
|
586
|
-
assert.equal(result.matches.length, 1);
|
|
587
|
-
assert.equal(result.matches[0].path, longPath);
|
|
588
|
-
});
|
|
589
|
-
test("Edge case: large number of parameters", () => {
|
|
590
|
-
const matcher = createMatcher([
|
|
591
|
-
{ path: "/:a/:b/:c/:d/:e/:f/:g/:h/:i/:j" }
|
|
592
|
-
]);
|
|
593
|
-
const result = matcher(
|
|
594
|
-
new URL("/1/2/3/4/5/6/7/8/9/10", BASE_URL),
|
|
595
|
-
BASE_URL
|
|
596
|
-
);
|
|
597
|
-
assert.equal(result.matches.length, 1);
|
|
598
|
-
assert.equal(result.params.a, "1");
|
|
599
|
-
assert.equal(result.params.j, "10");
|
|
600
|
-
assert.equal(Object.keys(result.params).length, 10);
|
|
601
|
-
});
|
|
602
|
-
test("Path rewriting and encoding", () => {
|
|
603
|
-
const matcher = createMatcher([{ path: "/api/:resource" }]);
|
|
604
|
-
const result = matcher(
|
|
605
|
-
new URL("/api/user%2Fprofile", BASE_URL),
|
|
606
|
-
BASE_URL
|
|
607
|
-
);
|
|
608
|
-
assert.equal(result.matches.length, 1);
|
|
609
|
-
assert.equal(result.params.resource, "user%2Fprofile");
|
|
610
|
-
});
|
|
611
|
-
test("Query parameters do not affect route matching", () => {
|
|
612
|
-
const matcher = createMatcher([{ path: "/search" }]);
|
|
613
|
-
const result = matcher(
|
|
614
|
-
new URL("/search?q=test&page=1", BASE_URL),
|
|
615
|
-
BASE_URL
|
|
616
|
-
);
|
|
617
|
-
assert.equal(result.matches.length, 1);
|
|
618
|
-
assert.equal(result.matches[0].path, "/search");
|
|
619
|
-
});
|
|
620
|
-
test("Hash does not affect route matching", () => {
|
|
621
|
-
const matcher = createMatcher([{ path: "/page" }]);
|
|
622
|
-
const result = matcher(new URL("/page#section1", BASE_URL), BASE_URL);
|
|
623
|
-
assert.equal(result.matches.length, 1);
|
|
624
|
-
assert.equal(result.matches[0].path, "/page");
|
|
625
|
-
});
|
|
626
|
-
test.todo("Case-sensitive matching", () => {
|
|
627
|
-
const matcher = createMatcher([{ path: "/API" }, { path: "/api" }]);
|
|
628
|
-
const result1 = matcher(new URL("/API", BASE_URL), BASE_URL);
|
|
629
|
-
const result2 = matcher(new URL("/api", BASE_URL), BASE_URL);
|
|
630
|
-
assert.equal(result1.matches.length, 1);
|
|
631
|
-
assert.equal(result1.matches[0].path, "/API");
|
|
632
|
-
assert.equal(result2.matches.length, 1);
|
|
633
|
-
assert.equal(result2.matches[0].path, "/api");
|
|
634
|
-
});
|
|
635
|
-
test("Username and password in baseURL should be ignored", () => {
|
|
636
|
-
const customBase = new URL("https://uname@pwlocalhost:3000/app/");
|
|
637
|
-
const matcher = createMatcher([{ path: "/test" }]);
|
|
638
|
-
const result = matcher(
|
|
639
|
-
new URL("https://uname2@pw2localhost:3000/app/test"),
|
|
640
|
-
customBase
|
|
641
|
-
);
|
|
642
|
-
assert.equal(result.matches.length, 1);
|
|
643
|
-
assert.equal(result.matches[0].path, "/test");
|
|
644
|
-
});
|
|
645
|
-
test("Empty string handling in nested routes", () => {
|
|
646
|
-
const matcher = createMatcher([
|
|
647
|
-
{
|
|
648
|
-
path: "/parent",
|
|
649
|
-
children: [{ path: "" }, { path: "child" }]
|
|
650
|
-
}
|
|
651
|
-
]);
|
|
652
|
-
const result1 = matcher(new URL("/parent", BASE_URL), BASE_URL);
|
|
653
|
-
assert.equal(result1.matches.length, 2);
|
|
654
|
-
assert.equal(result1.matches[0].path, "/parent");
|
|
655
|
-
assert.equal(result1.matches[1].path, "");
|
|
656
|
-
const result2 = matcher(new URL("/parent/child", BASE_URL), BASE_URL);
|
|
657
|
-
assert.equal(result2.matches.length, 2);
|
|
658
|
-
assert.equal(result2.matches[0].path, "/parent");
|
|
659
|
-
assert.equal(result2.matches[1].path, "child");
|
|
660
|
-
});
|
|
661
|
-
test("Route component configuration persistence", () => {
|
|
662
|
-
const TestComponent = () => "test";
|
|
663
|
-
const matcher = createMatcher([
|
|
664
|
-
{
|
|
665
|
-
path: "/component-test",
|
|
666
|
-
component: TestComponent
|
|
667
|
-
}
|
|
668
|
-
]);
|
|
669
|
-
const result = matcher(new URL("/component-test", BASE_URL), BASE_URL);
|
|
670
|
-
assert.equal(result.matches.length, 1);
|
|
671
|
-
assert.equal(result.matches[0].component, TestComponent);
|
|
672
|
-
});
|
|
673
|
-
test("Route redirect configuration persistence", () => {
|
|
674
|
-
const redirectTarget = "/new-path";
|
|
675
|
-
const matcher = createMatcher([
|
|
676
|
-
{
|
|
677
|
-
path: "/old-path",
|
|
678
|
-
redirect: redirectTarget
|
|
679
|
-
}
|
|
680
|
-
]);
|
|
681
|
-
const result = matcher(new URL("/old-path", BASE_URL), BASE_URL);
|
|
682
|
-
assert.equal(result.matches.length, 1);
|
|
683
|
-
assert.equal(result.matches[0].redirect, redirectTarget);
|
|
684
|
-
});
|
|
685
|
-
test("Numeric parameter parsing", () => {
|
|
686
|
-
const matcher = createMatcher([{ path: "/user/:id(\\d+)" }]);
|
|
687
|
-
const result1 = matcher(new URL("/user/123", BASE_URL), BASE_URL);
|
|
688
|
-
assert.equal(result1.matches.length, 1);
|
|
689
|
-
assert.equal(result1.params.id, "123");
|
|
690
|
-
const result2 = matcher(new URL("/user/abc", BASE_URL), BASE_URL);
|
|
691
|
-
assert.equal(result2.matches.length, 0);
|
|
692
|
-
});
|
|
693
|
-
test("Route matching depth-first strategy verification", () => {
|
|
694
|
-
var _a, _b, _c;
|
|
695
|
-
const matcher = createMatcher([
|
|
696
|
-
{
|
|
697
|
-
path: "/level1",
|
|
698
|
-
meta: { level: 1 },
|
|
699
|
-
children: [
|
|
700
|
-
{
|
|
701
|
-
path: "level2",
|
|
702
|
-
meta: { level: 2 },
|
|
703
|
-
children: [
|
|
704
|
-
{
|
|
705
|
-
path: "level3",
|
|
706
|
-
meta: { level: 3 }
|
|
707
|
-
}
|
|
708
|
-
]
|
|
709
|
-
}
|
|
710
|
-
]
|
|
711
|
-
}
|
|
712
|
-
]);
|
|
713
|
-
const result = matcher(
|
|
714
|
-
new URL("/level1/level2/level3", BASE_URL),
|
|
715
|
-
BASE_URL
|
|
716
|
-
);
|
|
717
|
-
assert.equal(result.matches.length, 3);
|
|
718
|
-
assert.equal((_a = result.matches[0].meta) == null ? void 0 : _a.level, 1);
|
|
719
|
-
assert.equal((_b = result.matches[1].meta) == null ? void 0 : _b.level, 2);
|
|
720
|
-
assert.equal((_c = result.matches[2].meta) == null ? void 0 : _c.level, 3);
|
|
721
|
-
});
|
|
722
|
-
test("Empty meta object default handling", () => {
|
|
723
|
-
const matcher = createMatcher([{ path: "/no-meta" }]);
|
|
724
|
-
const result = matcher(new URL("/no-meta", BASE_URL), BASE_URL);
|
|
725
|
-
assert.equal(result.matches.length, 1);
|
|
726
|
-
assert.isObject(result.matches[0].meta);
|
|
727
|
-
assert.deepEqual(result.matches[0].meta, {});
|
|
728
|
-
});
|
|
729
|
-
test("Path normalization handling", () => {
|
|
730
|
-
const matcher = createMatcher([{ path: "/test//double//slash" }]);
|
|
731
|
-
const result = matcher(
|
|
732
|
-
new URL("/test/double/slash", BASE_URL),
|
|
733
|
-
BASE_URL
|
|
734
|
-
);
|
|
735
|
-
assert.equal(result.matches.length, 1);
|
|
736
|
-
});
|
|
737
|
-
test("Error path configuration handling", () => {
|
|
738
|
-
const matcher1 = createMatcher([{ path: "" }]);
|
|
739
|
-
const result1 = matcher1(new URL("/", BASE_URL), BASE_URL);
|
|
740
|
-
assert.equal(result1.matches.length, 1);
|
|
741
|
-
const matcher2 = createMatcher([{ path: "/" }]);
|
|
742
|
-
const result2 = matcher2(new URL("/", BASE_URL), BASE_URL);
|
|
743
|
-
assert.equal(result2.matches.length, 1);
|
|
744
|
-
});
|
|
745
|
-
test("Empty parameter handling", () => {
|
|
746
|
-
const matcher = createMatcher([{ path: "/user/:id" }]);
|
|
747
|
-
const result = matcher(new URL("/user/", BASE_URL), BASE_URL);
|
|
748
|
-
assert.equal(result.matches.length, 0);
|
|
749
|
-
});
|
|
750
|
-
test("Route configuration completeness verification", () => {
|
|
751
|
-
var _a, _b, _c, _d, _e, _f;
|
|
752
|
-
const TestComponent = () => "test";
|
|
753
|
-
const asyncComponent = async () => TestComponent;
|
|
754
|
-
const beforeEnter = async (to, from, router) => void 0;
|
|
755
|
-
const matcher = createMatcher([
|
|
756
|
-
{
|
|
757
|
-
path: "/complete",
|
|
758
|
-
component: TestComponent,
|
|
759
|
-
asyncComponent,
|
|
760
|
-
beforeEnter,
|
|
761
|
-
meta: {
|
|
762
|
-
title: "Complete Route",
|
|
763
|
-
requiresAuth: true,
|
|
764
|
-
permissions: ["read", "write"]
|
|
765
|
-
},
|
|
766
|
-
children: [
|
|
767
|
-
{
|
|
768
|
-
path: "child",
|
|
769
|
-
component: TestComponent
|
|
770
|
-
}
|
|
771
|
-
]
|
|
772
|
-
}
|
|
773
|
-
]);
|
|
774
|
-
const result = matcher(new URL("/complete", BASE_URL), BASE_URL);
|
|
775
|
-
assert.equal(result.matches.length, 1);
|
|
776
|
-
assert.equal(result.matches[0].component, TestComponent);
|
|
777
|
-
assert.equal(result.matches[0].asyncComponent, asyncComponent);
|
|
778
|
-
assert.equal(result.matches[0].beforeEnter, beforeEnter);
|
|
779
|
-
assert.equal((_b = (_a = result.matches[0]) == null ? void 0 : _a.meta) == null ? void 0 : _b.title, "Complete Route");
|
|
780
|
-
assert.equal((_d = (_c = result.matches[0]) == null ? void 0 : _c.meta) == null ? void 0 : _d.requiresAuth, true);
|
|
781
|
-
assert.deepEqual((_f = (_e = result.matches[0]) == null ? void 0 : _e.meta) == null ? void 0 : _f.permissions, [
|
|
782
|
-
"read",
|
|
783
|
-
"write"
|
|
784
|
-
]);
|
|
785
|
-
assert.equal(result.matches[0].children.length, 1);
|
|
786
|
-
});
|
|
787
|
-
test.todo("Route conflict and priority handling", () => {
|
|
788
|
-
var _a, _b, _c, _d;
|
|
789
|
-
const matcher = createMatcher([
|
|
790
|
-
{
|
|
791
|
-
path: "/conflict/:id",
|
|
792
|
-
meta: { priority: 1 }
|
|
793
|
-
},
|
|
794
|
-
{
|
|
795
|
-
path: "/conflict/special",
|
|
796
|
-
meta: { priority: 2 }
|
|
797
|
-
},
|
|
798
|
-
{
|
|
799
|
-
path: "/conflict/:rest*",
|
|
800
|
-
meta: { priority: 3 }
|
|
801
|
-
}
|
|
802
|
-
]);
|
|
803
|
-
const result1 = matcher(
|
|
804
|
-
new URL("/conflict/special", BASE_URL),
|
|
805
|
-
BASE_URL
|
|
806
|
-
);
|
|
807
|
-
assert.equal(result1.matches.length, 1);
|
|
808
|
-
assert.equal((_b = (_a = result1.matches[0]) == null ? void 0 : _a.meta) == null ? void 0 : _b.priority, 2);
|
|
809
|
-
const result2 = matcher(new URL("/conflict/123", BASE_URL), BASE_URL);
|
|
810
|
-
assert.equal(result2.matches.length, 1);
|
|
811
|
-
assert.equal((_d = (_c = result2.matches[0]) == null ? void 0 : _c.meta) == null ? void 0 : _d.priority, 1);
|
|
812
|
-
assert.equal(result2.params.id, "123");
|
|
813
|
-
});
|
|
814
|
-
test("Multi-level nested parameter extraction", () => {
|
|
815
|
-
const matcher = createMatcher([
|
|
816
|
-
{
|
|
817
|
-
path: "/api",
|
|
818
|
-
children: [
|
|
819
|
-
{
|
|
820
|
-
path: "v:version",
|
|
821
|
-
children: [
|
|
822
|
-
{
|
|
823
|
-
path: ":resource",
|
|
824
|
-
children: [
|
|
825
|
-
{
|
|
826
|
-
path: ":id",
|
|
827
|
-
children: [{ path: ":action" }]
|
|
828
|
-
}
|
|
829
|
-
]
|
|
830
|
-
}
|
|
831
|
-
]
|
|
832
|
-
}
|
|
833
|
-
]
|
|
834
|
-
}
|
|
835
|
-
]);
|
|
836
|
-
const result = matcher(
|
|
837
|
-
new URL("/api/v1/users/123/edit", BASE_URL),
|
|
838
|
-
BASE_URL
|
|
839
|
-
);
|
|
840
|
-
assert.equal(result.matches.length, 5);
|
|
841
|
-
assert.equal(result.params.version, "1");
|
|
842
|
-
assert.equal(result.params.resource, "users");
|
|
843
|
-
assert.equal(result.params.id, "123");
|
|
844
|
-
assert.equal(result.params.action, "edit");
|
|
845
|
-
});
|
|
846
|
-
test("Route override configuration handling", () => {
|
|
847
|
-
var _a, _b;
|
|
848
|
-
const overrideHandler = (to, from, router) => {
|
|
849
|
-
return async (toRoute, fromRoute, routerInstance) => ({
|
|
850
|
-
data: "test"
|
|
851
|
-
});
|
|
852
|
-
};
|
|
853
|
-
const matcher = createMatcher([
|
|
854
|
-
{
|
|
855
|
-
path: "/override-test",
|
|
856
|
-
override: overrideHandler,
|
|
857
|
-
meta: { type: "hybrid" }
|
|
858
|
-
}
|
|
859
|
-
]);
|
|
860
|
-
const result = matcher(new URL("/override-test", BASE_URL), BASE_URL);
|
|
861
|
-
assert.equal(result.matches.length, 1);
|
|
862
|
-
assert.equal(result.matches[0].override, overrideHandler);
|
|
863
|
-
assert.equal((_b = (_a = result.matches[0]) == null ? void 0 : _a.meta) == null ? void 0 : _b.type, "hybrid");
|
|
864
|
-
});
|
|
865
|
-
test("Application configuration handling", () => {
|
|
866
|
-
const appConfig = "test-app";
|
|
867
|
-
const appCallback = () => ({ mount: () => {
|
|
868
|
-
}, unmount: () => {
|
|
869
|
-
} });
|
|
870
|
-
const matcher = createMatcher([
|
|
871
|
-
{ path: "/app1", app: appConfig },
|
|
872
|
-
{ path: "/app2", app: appCallback }
|
|
873
|
-
]);
|
|
874
|
-
const result1 = matcher(new URL("/app1", BASE_URL), BASE_URL);
|
|
875
|
-
assert.equal(result1.matches.length, 1);
|
|
876
|
-
assert.equal(result1.matches[0].app, appConfig);
|
|
877
|
-
const result2 = matcher(new URL("/app2", BASE_URL), BASE_URL);
|
|
878
|
-
assert.equal(result2.matches.length, 1);
|
|
879
|
-
assert.equal(result2.matches[0].app, appCallback);
|
|
880
|
-
});
|
|
881
|
-
test("Complex wildcard and parameter combinations", () => {
|
|
882
|
-
const matcher = createMatcher([{ path: "/files/:category/:rest*" }]);
|
|
883
|
-
const result = matcher(
|
|
884
|
-
new URL("/files/documents/folder1/folder2/view", BASE_URL),
|
|
885
|
-
BASE_URL
|
|
886
|
-
);
|
|
887
|
-
assert.equal(result.matches.length, 1);
|
|
888
|
-
assert.equal(result.params.category, "documents");
|
|
889
|
-
assert.deepEqual(result.params.rest, ["folder1", "folder2", "view"]);
|
|
890
|
-
});
|
|
891
|
-
test("Route redirect configuration verification", () => {
|
|
892
|
-
const redirectTarget = "/new-location";
|
|
893
|
-
const redirectFunction = () => "/dynamic-location";
|
|
894
|
-
const matcher = createMatcher([
|
|
895
|
-
{
|
|
896
|
-
path: "/redirect-string",
|
|
897
|
-
redirect: redirectTarget
|
|
898
|
-
},
|
|
899
|
-
{
|
|
900
|
-
path: "/redirect-function",
|
|
901
|
-
redirect: redirectFunction
|
|
902
|
-
}
|
|
903
|
-
]);
|
|
904
|
-
const result1 = matcher(
|
|
905
|
-
new URL("/redirect-string", BASE_URL),
|
|
906
|
-
BASE_URL
|
|
907
|
-
);
|
|
908
|
-
assert.equal(result1.matches.length, 1);
|
|
909
|
-
assert.equal(result1.matches[0].redirect, redirectTarget);
|
|
910
|
-
const result2 = matcher(
|
|
911
|
-
new URL("/redirect-function", BASE_URL),
|
|
912
|
-
BASE_URL
|
|
913
|
-
);
|
|
914
|
-
assert.equal(result2.matches.length, 1);
|
|
915
|
-
assert.equal(result2.matches[0].redirect, redirectFunction);
|
|
916
|
-
});
|
|
917
|
-
test("Route guard configuration verification", () => {
|
|
918
|
-
var _a, _b;
|
|
919
|
-
const beforeEnter = async (to, from, router) => void 0;
|
|
920
|
-
const beforeUpdate = async (to, from, router) => void 0;
|
|
921
|
-
const beforeLeave = async (to, from, router) => "/cancel";
|
|
922
|
-
const matcher = createMatcher([
|
|
923
|
-
{
|
|
924
|
-
path: "/guarded",
|
|
925
|
-
beforeEnter,
|
|
926
|
-
beforeUpdate,
|
|
927
|
-
beforeLeave,
|
|
928
|
-
meta: { protected: true }
|
|
929
|
-
}
|
|
930
|
-
]);
|
|
931
|
-
const result = matcher(new URL("/guarded", BASE_URL), BASE_URL);
|
|
932
|
-
assert.equal(result.matches.length, 1);
|
|
933
|
-
assert.equal(result.matches[0].beforeEnter, beforeEnter);
|
|
934
|
-
assert.equal(result.matches[0].beforeUpdate, beforeUpdate);
|
|
935
|
-
assert.equal(result.matches[0].beforeLeave, beforeLeave);
|
|
936
|
-
assert.equal((_b = (_a = result.matches[0]) == null ? void 0 : _a.meta) == null ? void 0 : _b.protected, true);
|
|
937
|
-
});
|
|
938
|
-
test.todo("matcher performance boundary test", () => {
|
|
939
|
-
const routes = [];
|
|
940
|
-
for (let i = 0; i < 500; i++) {
|
|
941
|
-
routes.push({
|
|
942
|
-
path: "/category".concat(i, "/:id"),
|
|
943
|
-
children: [
|
|
944
|
-
{
|
|
945
|
-
path: "subcategory/:subId"
|
|
946
|
-
}
|
|
947
|
-
]
|
|
948
|
-
});
|
|
949
|
-
}
|
|
950
|
-
const matcher = createMatcher(routes);
|
|
951
|
-
const startTime = performance.now();
|
|
952
|
-
const result = matcher(new URL("/nonexistent", BASE_URL), BASE_URL);
|
|
953
|
-
const endTime = performance.now();
|
|
954
|
-
assert.equal(result.matches.length, 0);
|
|
955
|
-
assert.isTrue(endTime - startTime < 50);
|
|
956
|
-
});
|
|
957
|
-
test("params type and value verification", () => {
|
|
958
|
-
const matcher = createMatcher([
|
|
959
|
-
{ path: "/typed/:stringParam/:numberParam(\\d+)/:optionalParam?" }
|
|
960
|
-
]);
|
|
961
|
-
const result = matcher(
|
|
962
|
-
new URL("/typed/hello/123/extra", BASE_URL),
|
|
963
|
-
BASE_URL
|
|
964
|
-
);
|
|
965
|
-
assert.equal(result.matches.length, 1);
|
|
966
|
-
assert.equal(typeof result.params.stringParam, "string");
|
|
967
|
-
assert.equal(result.params.stringParam, "hello");
|
|
968
|
-
assert.equal(typeof result.params.numberParam, "string");
|
|
969
|
-
assert.equal(result.params.numberParam, "123");
|
|
970
|
-
assert.equal(result.params.optionalParam, "extra");
|
|
971
|
-
});
|
|
972
|
-
test("Special URL encoding scenarios", () => {
|
|
973
|
-
const matcher = createMatcher([{ path: "/encoded/:param" }]);
|
|
974
|
-
const testCases = [
|
|
975
|
-
{ input: "/encoded/hello%20world", expected: "hello%20world" },
|
|
976
|
-
{
|
|
977
|
-
input: "/encoded/%E4%B8%AD%E6%96%87",
|
|
978
|
-
expected: "%E4%B8%AD%E6%96%87"
|
|
979
|
-
},
|
|
980
|
-
{
|
|
981
|
-
input: "/encoded/user%40domain.com",
|
|
982
|
-
expected: "user%40domain.com"
|
|
983
|
-
},
|
|
984
|
-
{ input: "/encoded/path%2Fto%2Ffile", expected: "path%2Fto%2Ffile" }
|
|
985
|
-
];
|
|
986
|
-
testCases.forEach(({ input, expected }) => {
|
|
987
|
-
const result = matcher(new URL(input, BASE_URL), BASE_URL);
|
|
988
|
-
assert.equal(result.matches.length, 1);
|
|
989
|
-
assert.equal(result.params.param, expected);
|
|
990
|
-
});
|
|
991
|
-
});
|
|
992
|
-
test("Error configuration tolerance handling", () => {
|
|
993
|
-
const emptyMatcher = createMatcher([]);
|
|
994
|
-
const emptyResult = emptyMatcher(new URL("/any", BASE_URL), BASE_URL);
|
|
995
|
-
assert.equal(emptyResult.matches.length, 0);
|
|
996
|
-
assert.deepEqual(emptyResult.params, {});
|
|
997
|
-
const matcher = createMatcher([
|
|
998
|
-
{ path: "/valid" },
|
|
999
|
-
...process.env.NODE_ENV === "test" ? [] : []
|
|
1000
|
-
]);
|
|
1001
|
-
const result = matcher(new URL("/valid", BASE_URL), BASE_URL);
|
|
1002
|
-
assert.equal(result.matches.length, 1);
|
|
1003
|
-
});
|
|
1004
|
-
test("Wildcard route matching - optional wildcard", () => {
|
|
1005
|
-
const routes = [
|
|
1006
|
-
{ path: "/files/:path*", component: "FilesPage" },
|
|
1007
|
-
{ path: "/api/:section/data", component: "ApiDataPage" },
|
|
1008
|
-
{ path: "/:rest*", component: "CatchAllPage" }
|
|
1009
|
-
];
|
|
1010
|
-
const matcher = createMatcher(routes);
|
|
1011
|
-
let result = matcher(
|
|
1012
|
-
new URL("/files/document.pdf", BASE_URL),
|
|
1013
|
-
BASE_URL
|
|
1014
|
-
);
|
|
1015
|
-
assert.equal(result.matches.length, 1);
|
|
1016
|
-
assert.equal(result.matches[0].component, "FilesPage");
|
|
1017
|
-
assert.deepEqual(result.params.path, ["document.pdf"]);
|
|
1018
|
-
result = matcher(
|
|
1019
|
-
new URL("/files/images/photo.jpg", BASE_URL),
|
|
1020
|
-
BASE_URL
|
|
1021
|
-
);
|
|
1022
|
-
assert.equal(result.matches.length, 1);
|
|
1023
|
-
assert.equal(result.matches[0].component, "FilesPage");
|
|
1024
|
-
assert.deepEqual(result.params.path, ["images", "photo.jpg"]);
|
|
1025
|
-
result = matcher(new URL("/files/", BASE_URL), BASE_URL);
|
|
1026
|
-
assert.equal(result.matches.length, 1);
|
|
1027
|
-
assert.equal(result.matches[0].component, "FilesPage");
|
|
1028
|
-
assert.equal(result.params.path, void 0);
|
|
1029
|
-
result = matcher(new URL("/files", BASE_URL), BASE_URL);
|
|
1030
|
-
assert.equal(result.matches.length, 1);
|
|
1031
|
-
assert.equal(result.matches[0].component, "FilesPage");
|
|
1032
|
-
assert.equal(result.params.path, void 0);
|
|
1033
|
-
result = matcher(new URL("/api/v1/data", BASE_URL), BASE_URL);
|
|
1034
|
-
assert.equal(result.matches.length, 1);
|
|
1035
|
-
assert.equal(result.matches[0].component, "ApiDataPage");
|
|
1036
|
-
assert.equal(result.params.section, "v1");
|
|
1037
|
-
result = matcher(new URL("/anything/else", BASE_URL), BASE_URL);
|
|
1038
|
-
assert.equal(result.matches.length, 1);
|
|
1039
|
-
assert.equal(result.matches[0].component, "CatchAllPage");
|
|
1040
|
-
assert.deepEqual(result.params.rest, ["anything", "else"]);
|
|
1041
|
-
});
|
|
1042
|
-
test("Repeatable parameter route matching - + modifier", () => {
|
|
1043
|
-
const routes = [
|
|
1044
|
-
{ path: "/chapters/:chapters+", component: "ChaptersPage" },
|
|
1045
|
-
{
|
|
1046
|
-
path: "/categories/:categories+/items",
|
|
1047
|
-
component: "CategoriesItemsPage"
|
|
1048
|
-
},
|
|
1049
|
-
{ path: "/tags/:tags+/posts/:postId", component: "TaggedPostPage" }
|
|
1050
|
-
];
|
|
1051
|
-
const matcher = createMatcher(routes);
|
|
1052
|
-
let result = matcher(new URL("/chapters/intro", BASE_URL), BASE_URL);
|
|
1053
|
-
assert.equal(result.matches.length, 1);
|
|
1054
|
-
assert.equal(result.matches[0].component, "ChaptersPage");
|
|
1055
|
-
assert.deepEqual(result.params.chapters, ["intro"]);
|
|
1056
|
-
result = matcher(
|
|
1057
|
-
new URL("/chapters/intro/basics/advanced", BASE_URL),
|
|
1058
|
-
BASE_URL
|
|
1059
|
-
);
|
|
1060
|
-
assert.equal(result.matches.length, 1);
|
|
1061
|
-
assert.equal(result.matches[0].component, "ChaptersPage");
|
|
1062
|
-
assert.deepEqual(result.params.chapters, [
|
|
1063
|
-
"intro",
|
|
1064
|
-
"basics",
|
|
1065
|
-
"advanced"
|
|
1066
|
-
]);
|
|
1067
|
-
result = matcher(
|
|
1068
|
-
new URL("/categories/tech/programming/items", BASE_URL),
|
|
1069
|
-
BASE_URL
|
|
1070
|
-
);
|
|
1071
|
-
assert.equal(result.matches.length, 1);
|
|
1072
|
-
assert.equal(result.matches[0].component, "CategoriesItemsPage");
|
|
1073
|
-
assert.deepEqual(result.params.categories, ["tech", "programming"]);
|
|
1074
|
-
result = matcher(
|
|
1075
|
-
new URL("/tags/react/typescript/hooks/posts/123", BASE_URL),
|
|
1076
|
-
BASE_URL
|
|
1077
|
-
);
|
|
1078
|
-
assert.equal(result.matches.length, 1);
|
|
1079
|
-
assert.equal(result.matches[0].component, "TaggedPostPage");
|
|
1080
|
-
assert.deepEqual(result.params.tags, ["react", "typescript", "hooks"]);
|
|
1081
|
-
assert.equal(result.params.postId, "123");
|
|
1082
|
-
});
|
|
1083
|
-
test("Repeatable parameter route matching - * modifier", () => {
|
|
1084
|
-
const routes = [
|
|
1085
|
-
{ path: "/path/:segments*", component: "DynamicPathPage" },
|
|
1086
|
-
{ path: "/files/:path*/download", component: "DownloadPage" }
|
|
1087
|
-
];
|
|
1088
|
-
const matcher = createMatcher(routes);
|
|
1089
|
-
let result = matcher(new URL("/path", BASE_URL), BASE_URL);
|
|
1090
|
-
assert.equal(result.matches.length, 1);
|
|
1091
|
-
assert.equal(result.matches[0].component, "DynamicPathPage");
|
|
1092
|
-
assert.equal(result.params.segments, void 0);
|
|
1093
|
-
result = matcher(new URL("/path/a", BASE_URL), BASE_URL);
|
|
1094
|
-
assert.equal(result.matches.length, 1);
|
|
1095
|
-
assert.equal(result.matches[0].component, "DynamicPathPage");
|
|
1096
|
-
assert.equal(result.params.segments, "a");
|
|
1097
|
-
result = matcher(new URL("/path/a/b/c/d", BASE_URL), BASE_URL);
|
|
1098
|
-
assert.equal(result.matches.length, 1);
|
|
1099
|
-
assert.equal(result.matches[0].component, "DynamicPathPage");
|
|
1100
|
-
assert.deepEqual(result.params.segments, ["a", "b", "c", "d"]);
|
|
1101
|
-
result = matcher(new URL("/files/download", BASE_URL), BASE_URL);
|
|
1102
|
-
assert.equal(result.matches.length, 1);
|
|
1103
|
-
assert.equal(result.matches[0].component, "DownloadPage");
|
|
1104
|
-
assert.equal(result.params.path, void 0);
|
|
1105
|
-
result = matcher(new URL("/files/a/download", BASE_URL), BASE_URL);
|
|
1106
|
-
assert.equal(result.matches.length, 1);
|
|
1107
|
-
assert.equal(result.matches[0].component, "DownloadPage");
|
|
1108
|
-
assert.equal(result.params.path, "a");
|
|
1109
|
-
result = matcher(
|
|
1110
|
-
new URL("/files/docs/images/download", BASE_URL),
|
|
1111
|
-
BASE_URL
|
|
1112
|
-
);
|
|
1113
|
-
assert.equal(result.matches.length, 1);
|
|
1114
|
-
assert.equal(result.matches[0].component, "DownloadPage");
|
|
1115
|
-
assert.deepEqual(result.params.path, ["docs", "images"]);
|
|
1116
|
-
});
|
|
1117
|
-
test("Custom regular expression route matching", () => {
|
|
1118
|
-
const routes = [
|
|
1119
|
-
{ path: "/order/:orderId(\\d+)", component: "OrderPage" },
|
|
1120
|
-
{ path: "/user/:username([a-zA-Z0-9_]+)", component: "UserPage" },
|
|
1121
|
-
{ path: "/product/:productName", component: "ProductPage" },
|
|
1122
|
-
{ path: "/api/v:version(\\d+)", component: "ApiPage" },
|
|
1123
|
-
{ path: "/hex/:color([0-9a-fA-F]{6})", component: "ColorPage" }
|
|
1124
|
-
];
|
|
1125
|
-
const matcher = createMatcher(routes);
|
|
1126
|
-
let result = matcher(new URL("/order/12345", BASE_URL), BASE_URL);
|
|
1127
|
-
assert.equal(result.matches.length, 1);
|
|
1128
|
-
assert.equal(result.matches[0].component, "OrderPage");
|
|
1129
|
-
assert.equal(result.params.orderId, "12345");
|
|
1130
|
-
result = matcher(new URL("/order/abc123", BASE_URL), BASE_URL);
|
|
1131
|
-
assert.equal(result.matches.length, 0);
|
|
1132
|
-
result = matcher(new URL("/user/john_doe123", BASE_URL), BASE_URL);
|
|
1133
|
-
assert.equal(result.matches.length, 1);
|
|
1134
|
-
assert.equal(result.matches[0].component, "UserPage");
|
|
1135
|
-
assert.equal(result.params.username, "john_doe123");
|
|
1136
|
-
result = matcher(new URL("/api/v2", BASE_URL), BASE_URL);
|
|
1137
|
-
assert.equal(result.matches.length, 1);
|
|
1138
|
-
assert.equal(result.matches[0].component, "ApiPage");
|
|
1139
|
-
assert.equal(result.params.version, "2");
|
|
1140
|
-
result = matcher(new URL("/hex/FF0000", BASE_URL), BASE_URL);
|
|
1141
|
-
assert.equal(result.matches.length, 1);
|
|
1142
|
-
assert.equal(result.matches[0].component, "ColorPage");
|
|
1143
|
-
assert.equal(result.params.color, "FF0000");
|
|
1144
|
-
result = matcher(new URL("/hex/GGGGGG", BASE_URL), BASE_URL);
|
|
1145
|
-
assert.equal(result.matches.length, 0);
|
|
1146
|
-
result = matcher(new URL("/product/laptop-pro", BASE_URL), BASE_URL);
|
|
1147
|
-
assert.equal(result.matches.length, 1);
|
|
1148
|
-
assert.equal(result.matches[0].component, "ProductPage");
|
|
1149
|
-
assert.equal(result.params.productName, "laptop-pro");
|
|
1150
|
-
});
|
|
1151
|
-
test("Repeatable parameter and custom regular expression route matching", () => {
|
|
1152
|
-
const routes = [
|
|
1153
|
-
{ path: "/numbers/:nums(\\d+)+", component: "NumbersPage" },
|
|
1154
|
-
{ path: "/codes/:codes([A-Z]{2,3})+", component: "CodesPage" },
|
|
1155
|
-
{ path: "/optional/:items(\\d+)*", component: "OptionalItemsPage" },
|
|
1156
|
-
{
|
|
1157
|
-
path: "/mixed/:ids(\\d+)+/info/:codes([A-Z]+)*",
|
|
1158
|
-
component: "MixedPage"
|
|
1159
|
-
}
|
|
1160
|
-
];
|
|
1161
|
-
const matcher = createMatcher(routes);
|
|
1162
|
-
let result = matcher(new URL("/numbers/123", BASE_URL), BASE_URL);
|
|
1163
|
-
assert.equal(result.matches.length, 1);
|
|
1164
|
-
assert.equal(result.matches[0].component, "NumbersPage");
|
|
1165
|
-
assert.deepEqual(result.params.nums, ["123"]);
|
|
1166
|
-
result = matcher(new URL("/numbers/123/456/789", BASE_URL), BASE_URL);
|
|
1167
|
-
assert.equal(result.matches.length, 1);
|
|
1168
|
-
assert.equal(result.matches[0].component, "NumbersPage");
|
|
1169
|
-
assert.deepEqual(result.params.nums, ["123", "456", "789"]);
|
|
1170
|
-
result = matcher(new URL("/codes/US/UK/CA", BASE_URL), BASE_URL);
|
|
1171
|
-
assert.equal(result.matches.length, 1);
|
|
1172
|
-
assert.equal(result.matches[0].component, "CodesPage");
|
|
1173
|
-
assert.deepEqual(result.params.codes, ["US", "UK", "CA"]);
|
|
1174
|
-
result = matcher(new URL("/optional", BASE_URL), BASE_URL);
|
|
1175
|
-
assert.equal(result.matches.length, 1);
|
|
1176
|
-
assert.equal(result.matches[0].component, "OptionalItemsPage");
|
|
1177
|
-
assert.equal(result.params.items, void 0);
|
|
1178
|
-
result = matcher(new URL("/optional/100/200/300", BASE_URL), BASE_URL);
|
|
1179
|
-
assert.equal(result.matches.length, 1);
|
|
1180
|
-
assert.equal(result.matches[0].component, "OptionalItemsPage");
|
|
1181
|
-
assert.deepEqual(result.params.items, ["100", "200", "300"]);
|
|
1182
|
-
result = matcher(
|
|
1183
|
-
new URL("/mixed/111/222/info/ABC/DEF", BASE_URL),
|
|
1184
|
-
BASE_URL
|
|
1185
|
-
);
|
|
1186
|
-
assert.equal(result.matches.length, 1);
|
|
1187
|
-
assert.equal(result.matches[0].component, "MixedPage");
|
|
1188
|
-
assert.deepEqual(result.params.ids, ["111", "222"]);
|
|
1189
|
-
assert.deepEqual(result.params.codes, ["ABC", "DEF"]);
|
|
1190
|
-
result = matcher(new URL("/numbers/abc/123", BASE_URL), BASE_URL);
|
|
1191
|
-
assert.equal(result.matches.length, 0);
|
|
1192
|
-
});
|
|
1193
|
-
test("Optional parameter route matching - basic usage", () => {
|
|
1194
|
-
const routes = [
|
|
1195
|
-
{ path: "/users/:userId?", component: "UsersPage" },
|
|
1196
|
-
{ path: "/posts/:postId?/comments", component: "CommentsPage" },
|
|
1197
|
-
{ path: "/search/:query?/:page?", component: "SearchPage" },
|
|
1198
|
-
{
|
|
1199
|
-
path: "/profile/:section?/:subsection?",
|
|
1200
|
-
component: "ProfilePage"
|
|
1201
|
-
}
|
|
1202
|
-
];
|
|
1203
|
-
const matcher = createMatcher(routes);
|
|
1204
|
-
let result = matcher(new URL("/users", BASE_URL), BASE_URL);
|
|
1205
|
-
assert.equal(result.matches.length, 1);
|
|
1206
|
-
assert.equal(result.matches[0].component, "UsersPage");
|
|
1207
|
-
assert.equal(result.params.userId, void 0);
|
|
1208
|
-
result = matcher(new URL("/users/123", BASE_URL), BASE_URL);
|
|
1209
|
-
assert.equal(result.matches.length, 1);
|
|
1210
|
-
assert.equal(result.matches[0].component, "UsersPage");
|
|
1211
|
-
assert.equal(result.params.userId, "123");
|
|
1212
|
-
result = matcher(new URL("/posts/comments", BASE_URL), BASE_URL);
|
|
1213
|
-
assert.equal(result.matches.length, 1);
|
|
1214
|
-
assert.equal(result.matches[0].component, "CommentsPage");
|
|
1215
|
-
assert.equal(result.params.postId, void 0);
|
|
1216
|
-
result = matcher(new URL("/posts/456/comments", BASE_URL), BASE_URL);
|
|
1217
|
-
assert.equal(result.matches.length, 1);
|
|
1218
|
-
assert.equal(result.matches[0].component, "CommentsPage");
|
|
1219
|
-
assert.equal(result.params.postId, "456");
|
|
1220
|
-
result = matcher(new URL("/search", BASE_URL), BASE_URL);
|
|
1221
|
-
assert.equal(result.matches.length, 1);
|
|
1222
|
-
assert.equal(result.matches[0].component, "SearchPage");
|
|
1223
|
-
assert.equal(result.params.query, void 0);
|
|
1224
|
-
assert.equal(result.params.page, void 0);
|
|
1225
|
-
result = matcher(new URL("/search/react", BASE_URL), BASE_URL);
|
|
1226
|
-
assert.equal(result.matches.length, 1);
|
|
1227
|
-
assert.equal(result.matches[0].component, "SearchPage");
|
|
1228
|
-
assert.equal(result.params.query, "react");
|
|
1229
|
-
assert.equal(result.params.page, void 0);
|
|
1230
|
-
result = matcher(new URL("/search/react/2", BASE_URL), BASE_URL);
|
|
1231
|
-
assert.equal(result.matches.length, 1);
|
|
1232
|
-
assert.equal(result.matches[0].component, "SearchPage");
|
|
1233
|
-
assert.equal(result.params.query, "react");
|
|
1234
|
-
assert.equal(result.params.page, "2");
|
|
1235
|
-
});
|
|
1236
|
-
test("Optional parameter and custom regular expression route matching", () => {
|
|
1237
|
-
const routes = [
|
|
1238
|
-
{ path: "/users/:userId(\\d+)?", component: "UsersPage" },
|
|
1239
|
-
{
|
|
1240
|
-
path: "/products/:category([a-z]+)?/:productId(\\d+)?",
|
|
1241
|
-
component: "ProductsPage"
|
|
1242
|
-
},
|
|
1243
|
-
{
|
|
1244
|
-
path: "/articles/:year(\\d{4})?/:month(\\d{1,2})?/:slug?",
|
|
1245
|
-
component: "ArticlesPage"
|
|
1246
|
-
},
|
|
1247
|
-
{
|
|
1248
|
-
path: "/api/:version(v\\d+)?/users/:userId(\\d+)?",
|
|
1249
|
-
component: "ApiUsersPage"
|
|
1250
|
-
}
|
|
1251
|
-
];
|
|
1252
|
-
const matcher = createMatcher(routes);
|
|
1253
|
-
let result = matcher(new URL("/users", BASE_URL), BASE_URL);
|
|
1254
|
-
assert.equal(result.matches.length, 1);
|
|
1255
|
-
assert.equal(result.matches[0].component, "UsersPage");
|
|
1256
|
-
assert.equal(result.params.userId, void 0);
|
|
1257
|
-
result = matcher(new URL("/users/123", BASE_URL), BASE_URL);
|
|
1258
|
-
assert.equal(result.matches.length, 1);
|
|
1259
|
-
assert.equal(result.matches[0].component, "UsersPage");
|
|
1260
|
-
assert.equal(result.params.userId, "123");
|
|
1261
|
-
result = matcher(new URL("/users/abc", BASE_URL), BASE_URL);
|
|
1262
|
-
assert.equal(result.matches.length, 0);
|
|
1263
|
-
result = matcher(new URL("/products", BASE_URL), BASE_URL);
|
|
1264
|
-
assert.equal(result.matches.length, 1);
|
|
1265
|
-
assert.equal(result.matches[0].component, "ProductsPage");
|
|
1266
|
-
assert.equal(result.params.category, void 0);
|
|
1267
|
-
assert.equal(result.params.productId, void 0);
|
|
1268
|
-
result = matcher(new URL("/products/electronics", BASE_URL), BASE_URL);
|
|
1269
|
-
assert.equal(result.matches.length, 1);
|
|
1270
|
-
assert.equal(result.matches[0].component, "ProductsPage");
|
|
1271
|
-
assert.equal(result.params.category, "electronics");
|
|
1272
|
-
assert.equal(result.params.productId, void 0);
|
|
1273
|
-
result = matcher(
|
|
1274
|
-
new URL("/products/electronics/456", BASE_URL),
|
|
1275
|
-
BASE_URL
|
|
1276
|
-
);
|
|
1277
|
-
assert.equal(result.matches.length, 1);
|
|
1278
|
-
assert.equal(result.matches[0].component, "ProductsPage");
|
|
1279
|
-
assert.equal(result.params.category, "electronics");
|
|
1280
|
-
assert.equal(result.params.productId, "456");
|
|
1281
|
-
result = matcher(new URL("/articles/2024", BASE_URL), BASE_URL);
|
|
1282
|
-
assert.equal(result.matches.length, 1);
|
|
1283
|
-
assert.equal(result.matches[0].component, "ArticlesPage");
|
|
1284
|
-
assert.equal(result.params.year, "2024");
|
|
1285
|
-
assert.equal(result.params.month, void 0);
|
|
1286
|
-
assert.equal(result.params.slug, void 0);
|
|
1287
|
-
result = matcher(
|
|
1288
|
-
new URL("/articles/2024/03/my-post", BASE_URL),
|
|
1289
|
-
BASE_URL
|
|
1290
|
-
);
|
|
1291
|
-
assert.equal(result.matches.length, 1);
|
|
1292
|
-
assert.equal(result.matches[0].component, "ArticlesPage");
|
|
1293
|
-
assert.equal(result.params.year, "2024");
|
|
1294
|
-
assert.equal(result.params.month, "03");
|
|
1295
|
-
assert.equal(result.params.slug, "my-post");
|
|
1296
|
-
result = matcher(new URL("/api/v2/users/789", BASE_URL), BASE_URL);
|
|
1297
|
-
assert.equal(result.matches.length, 1);
|
|
1298
|
-
assert.equal(result.matches[0].component, "ApiUsersPage");
|
|
1299
|
-
assert.equal(result.params.version, "v2");
|
|
1300
|
-
assert.equal(result.params.userId, "789");
|
|
1301
|
-
result = matcher(new URL("/api/users", BASE_URL), BASE_URL);
|
|
1302
|
-
assert.equal(result.matches.length, 1);
|
|
1303
|
-
assert.equal(result.matches[0].component, "ApiUsersPage");
|
|
1304
|
-
assert.equal(result.params.version, void 0);
|
|
1305
|
-
assert.equal(result.params.userId, void 0);
|
|
1306
|
-
});
|
|
1307
|
-
test("Complex route pattern combination matching", () => {
|
|
1308
|
-
const routes = [
|
|
1309
|
-
{
|
|
1310
|
-
path: "/api/v:version(\\d+)/users/:userId(\\d+)/posts/:postIds(\\d+)+",
|
|
1311
|
-
component: "UserPostsPage"
|
|
1312
|
-
},
|
|
1313
|
-
{
|
|
1314
|
-
path: "/files/:folders([a-zA-Z0-9_-]+)*/download/:filename+",
|
|
1315
|
-
component: "FileDownloadPage"
|
|
1316
|
-
},
|
|
1317
|
-
{
|
|
1318
|
-
path: "/shop/:categories([a-z]+)+/items/:itemId(\\d+)?/reviews/:reviewIds(\\d+)*",
|
|
1319
|
-
component: "ShopReviewsPage"
|
|
1320
|
-
},
|
|
1321
|
-
{
|
|
1322
|
-
path: "/admin/users/:userIds(\\d+)+/roles/:roleNames([a-z]+)*",
|
|
1323
|
-
component: "AdminUserRolesPage"
|
|
1324
|
-
}
|
|
1325
|
-
];
|
|
1326
|
-
const matcher = createMatcher(routes);
|
|
1327
|
-
let result = matcher(
|
|
1328
|
-
new URL("/api/v1/users/123/posts/456/789", BASE_URL),
|
|
1329
|
-
BASE_URL
|
|
1330
|
-
);
|
|
1331
|
-
assert.equal(result.matches.length, 1);
|
|
1332
|
-
assert.equal(result.matches[0].component, "UserPostsPage");
|
|
1333
|
-
assert.equal(result.params.version, "1");
|
|
1334
|
-
assert.equal(result.params.userId, "123");
|
|
1335
|
-
assert.deepEqual(result.params.postIds, ["456", "789"]);
|
|
1336
|
-
result = matcher(
|
|
1337
|
-
new URL("/files/docs/images/download/photo.jpg", BASE_URL),
|
|
1338
|
-
BASE_URL
|
|
1339
|
-
);
|
|
1340
|
-
assert.equal(result.matches.length, 1);
|
|
1341
|
-
assert.equal(result.matches[0].component, "FileDownloadPage");
|
|
1342
|
-
assert.deepEqual(result.params.folders, ["docs", "images"]);
|
|
1343
|
-
assert.deepEqual(result.params.filename, ["photo.jpg"]);
|
|
1344
|
-
result = matcher(
|
|
1345
|
-
new URL("/files/download/readme.txt", BASE_URL),
|
|
1346
|
-
BASE_URL
|
|
1347
|
-
);
|
|
1348
|
-
assert.equal(result.matches.length, 1);
|
|
1349
|
-
assert.equal(result.matches[0].component, "FileDownloadPage");
|
|
1350
|
-
assert.equal(result.params.folders, void 0);
|
|
1351
|
-
assert.deepEqual(result.params.filename, ["readme.txt"]);
|
|
1352
|
-
result = matcher(
|
|
1353
|
-
new URL("/shop/electronics/computers/items/123/reviews/", BASE_URL),
|
|
1354
|
-
BASE_URL
|
|
1355
|
-
);
|
|
1356
|
-
assert.equal(result.matches.length, 1);
|
|
1357
|
-
assert.equal(result.matches[0].component, "ShopReviewsPage");
|
|
1358
|
-
assert.deepEqual(result.params.categories, [
|
|
1359
|
-
"electronics",
|
|
1360
|
-
"computers"
|
|
1361
|
-
]);
|
|
1362
|
-
assert.equal(result.params.itemId, "123");
|
|
1363
|
-
assert.equal(result.params.reviewIds, void 0);
|
|
1364
|
-
result = matcher(
|
|
1365
|
-
new URL("/shop/books/items/reviews/101/102", BASE_URL),
|
|
1366
|
-
BASE_URL
|
|
1367
|
-
);
|
|
1368
|
-
assert.equal(result.matches.length, 1);
|
|
1369
|
-
assert.equal(result.matches[0].component, "ShopReviewsPage");
|
|
1370
|
-
assert.deepEqual(result.params.categories, ["books"]);
|
|
1371
|
-
assert.equal(result.params.itemId, void 0);
|
|
1372
|
-
assert.deepEqual(result.params.reviewIds, ["101", "102"]);
|
|
1373
|
-
result = matcher(
|
|
1374
|
-
new URL("/admin/users/100/200/300/roles/admin/moderator", BASE_URL),
|
|
1375
|
-
BASE_URL
|
|
1376
|
-
);
|
|
1377
|
-
assert.equal(result.matches.length, 1);
|
|
1378
|
-
assert.equal(result.matches[0].component, "AdminUserRolesPage");
|
|
1379
|
-
assert.deepEqual(result.params.userIds, ["100", "200", "300"]);
|
|
1380
|
-
assert.deepEqual(result.params.roleNames, ["admin", "moderator"]);
|
|
1381
|
-
result = matcher(
|
|
1382
|
-
new URL("/admin/users/100/roles/", BASE_URL),
|
|
1383
|
-
BASE_URL
|
|
1384
|
-
);
|
|
1385
|
-
assert.equal(result.matches.length, 1);
|
|
1386
|
-
assert.equal(result.matches[0].component, "AdminUserRolesPage");
|
|
1387
|
-
assert.deepEqual(result.params.userIds, ["100"]);
|
|
1388
|
-
assert.equal(result.params.roleNames, void 0);
|
|
1389
|
-
});
|
|
1390
|
-
test("Advanced route pattern edge cases", () => {
|
|
1391
|
-
const routes = [
|
|
1392
|
-
{
|
|
1393
|
-
path: "/test/:param(\\d+)?/:param2(\\d+)+",
|
|
1394
|
-
component: "TestPage"
|
|
1395
|
-
},
|
|
1396
|
-
{ path: "/empty/:empty*", component: "EmptyPage" },
|
|
1397
|
-
{ path: "/strict/:id(\\d{3})", component: "StrictPage" }
|
|
1398
|
-
];
|
|
1399
|
-
const matcher = createMatcher(routes);
|
|
1400
|
-
let result = matcher(new URL("/test/123/456", BASE_URL), BASE_URL);
|
|
1401
|
-
assert.equal(result.matches.length, 1);
|
|
1402
|
-
assert.equal(result.matches[0].component, "TestPage");
|
|
1403
|
-
assert.equal(result.params.param, "123");
|
|
1404
|
-
assert.deepEqual(result.params.param2, ["456"]);
|
|
1405
|
-
result = matcher(new URL("/test/123/456/789", BASE_URL), BASE_URL);
|
|
1406
|
-
assert.equal(result.matches.length, 1);
|
|
1407
|
-
assert.equal(result.matches[0].component, "TestPage");
|
|
1408
|
-
assert.equal(result.params.param, "123");
|
|
1409
|
-
assert.deepEqual(result.params.param2, ["456", "789"]);
|
|
1410
|
-
result = matcher(new URL("/empty/", BASE_URL), BASE_URL);
|
|
1411
|
-
assert.equal(result.matches.length, 1);
|
|
1412
|
-
assert.equal(result.matches[0].component, "EmptyPage");
|
|
1413
|
-
assert.equal(result.params.empty, void 0);
|
|
1414
|
-
result = matcher(new URL("/strict/123", BASE_URL), BASE_URL);
|
|
1415
|
-
assert.equal(result.matches.length, 1);
|
|
1416
|
-
assert.equal(result.matches[0].component, "StrictPage");
|
|
1417
|
-
assert.equal(result.params.id, "123");
|
|
1418
|
-
result = matcher(new URL("/strict/1234", BASE_URL), BASE_URL);
|
|
1419
|
-
assert.equal(result.matches.length, 0);
|
|
1420
|
-
result = matcher(new URL("/strict/12", BASE_URL), BASE_URL);
|
|
1421
|
-
assert.equal(result.matches.length, 0);
|
|
1422
|
-
});
|
|
1423
|
-
test("Empty path wildcard", () => {
|
|
1424
|
-
assert.throws(
|
|
1425
|
-
() => createMatcher([{ path: "*" }]),
|
|
1426
|
-
"Unexpected MODIFIER"
|
|
1427
|
-
);
|
|
1428
|
-
let result = createMatcher([{ path: "(.*)" }])(
|
|
1429
|
-
new URL("/users/a/b/c", BASE_URL),
|
|
1430
|
-
BASE_URL
|
|
1431
|
-
);
|
|
1432
|
-
assert.equal(result.matches.length, 1);
|
|
1433
|
-
assert.equal(result.matches[0].path, "(.*)");
|
|
1434
|
-
result = createMatcher([{ path: "(.*)*" }])(
|
|
1435
|
-
new URL("/users/a/b/c", BASE_URL),
|
|
1436
|
-
BASE_URL
|
|
1437
|
-
);
|
|
1438
|
-
assert.equal(result.matches.length, 1);
|
|
1439
|
-
assert.equal(result.matches[0].path, "(.*)*");
|
|
1440
|
-
});
|
|
1441
|
-
test.todo("Wildcard route matching - new version", () => {
|
|
1442
|
-
const routes = [
|
|
1443
|
-
{ path: "/files{/*path}/:file{.:ext}", component: "FilesPage" },
|
|
1444
|
-
// /files/:path*/:file.:ext?
|
|
1445
|
-
{ path: "/api/*section/data", component: "ApiDataPage" },
|
|
1446
|
-
// /api/:section?/data
|
|
1447
|
-
{ path: "{/*rest}", component: "CatchAllPage" }
|
|
1448
|
-
// /:rest*
|
|
1449
|
-
];
|
|
1450
|
-
const matcher = createMatcher(routes);
|
|
1451
|
-
let result = matcher(
|
|
1452
|
-
new URL("/files/document.pdf", BASE_URL),
|
|
1453
|
-
BASE_URL
|
|
1454
|
-
);
|
|
1455
|
-
assert.equal(result.matches.length, 1);
|
|
1456
|
-
assert.equal(result.matches[0].component, "FilesPage");
|
|
1457
|
-
assert.equal(result.params.path, void 0);
|
|
1458
|
-
assert.equal(result.params.file, "document");
|
|
1459
|
-
assert.equal(result.params.ext, "pdf");
|
|
1460
|
-
result = matcher(
|
|
1461
|
-
new URL("/files/images/photo.jpg", BASE_URL),
|
|
1462
|
-
BASE_URL
|
|
1463
|
-
);
|
|
1464
|
-
assert.equal(result.matches.length, 1);
|
|
1465
|
-
assert.equal(result.matches[0].component, "FilesPage");
|
|
1466
|
-
assert.equal(result.params.path, ["images"]);
|
|
1467
|
-
assert.equal(result.params.file, "photo");
|
|
1468
|
-
assert.equal(result.params.ext, "jpg");
|
|
1469
|
-
result = matcher(new URL("/files/images/photo", BASE_URL), BASE_URL);
|
|
1470
|
-
assert.equal(result.matches.length, 1);
|
|
1471
|
-
assert.equal(result.matches[0].component, "FilesPage");
|
|
1472
|
-
assert.equal(result.params.path, ["images"]);
|
|
1473
|
-
assert.equal(result.params.file, "photo");
|
|
1474
|
-
assert.equal(result.params.ext, void 0);
|
|
1475
|
-
result = matcher(new URL("/files/", BASE_URL), BASE_URL);
|
|
1476
|
-
assert.equal(result.matches.length, 1);
|
|
1477
|
-
assert.equal(result.matches[0].component, "FilesPage");
|
|
1478
|
-
assert.equal(result.params.path, void 0);
|
|
1479
|
-
result = matcher(new URL("/files", BASE_URL), BASE_URL);
|
|
1480
|
-
assert.equal(result.matches.length, 1);
|
|
1481
|
-
assert.equal(result.matches[0].component, "FilesPage");
|
|
1482
|
-
assert.equal(result.params.path, void 0);
|
|
1483
|
-
result = matcher(new URL("/api/v1/data", BASE_URL), BASE_URL);
|
|
1484
|
-
assert.equal(result.matches.length, 1);
|
|
1485
|
-
assert.equal(result.matches[0].component, "ApiDataPage");
|
|
1486
|
-
assert.equal(result.params.section, "v1");
|
|
1487
|
-
result = matcher(new URL("/anything/else", BASE_URL), BASE_URL);
|
|
1488
|
-
assert.equal(result.matches.length, 1);
|
|
1489
|
-
assert.equal(result.matches[0].component, "CatchAllPage");
|
|
1490
|
-
assert.deepEqual(result.params.rest, ["anything", "else"]);
|
|
1491
|
-
});
|
|
1492
|
-
});
|