@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.
Files changed (158) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +70 -0
  3. package/README.zh-CN.md +70 -0
  4. package/dist/error.d.ts +23 -0
  5. package/dist/error.mjs +61 -0
  6. package/dist/increment-id.d.ts +7 -0
  7. package/dist/increment-id.mjs +11 -0
  8. package/dist/index.d.ts +5 -3
  9. package/dist/index.mjs +14 -3
  10. package/dist/index.test.mjs +8 -0
  11. package/dist/location.d.ts +15 -0
  12. package/dist/location.mjs +53 -0
  13. package/dist/location.test.d.ts +8 -0
  14. package/dist/location.test.mjs +370 -0
  15. package/dist/matcher.d.ts +3 -0
  16. package/dist/matcher.mjs +44 -0
  17. package/dist/matcher.test.mjs +1492 -0
  18. package/dist/micro-app.d.ts +18 -0
  19. package/dist/micro-app.dom.test.d.ts +1 -0
  20. package/dist/micro-app.dom.test.mjs +532 -0
  21. package/dist/micro-app.mjs +80 -0
  22. package/dist/navigation.d.ts +43 -0
  23. package/dist/navigation.mjs +143 -0
  24. package/dist/navigation.test.d.ts +1 -0
  25. package/dist/navigation.test.mjs +681 -0
  26. package/dist/options.d.ts +4 -0
  27. package/dist/options.mjs +88 -0
  28. package/dist/route-task.d.ts +40 -0
  29. package/dist/route-task.mjs +75 -0
  30. package/dist/route-task.test.d.ts +1 -0
  31. package/dist/route-task.test.mjs +673 -0
  32. package/dist/route-transition.d.ts +53 -0
  33. package/dist/route-transition.mjs +307 -0
  34. package/dist/route-transition.test.d.ts +1 -0
  35. package/dist/route-transition.test.mjs +146 -0
  36. package/dist/route.d.ts +72 -0
  37. package/dist/route.mjs +194 -0
  38. package/dist/route.test.d.ts +1 -0
  39. package/dist/route.test.mjs +1664 -0
  40. package/dist/router-back.test.d.ts +1 -0
  41. package/dist/router-back.test.mjs +361 -0
  42. package/dist/router-forward.test.d.ts +1 -0
  43. package/dist/router-forward.test.mjs +376 -0
  44. package/dist/router-go.test.d.ts +1 -0
  45. package/dist/router-go.test.mjs +73 -0
  46. package/dist/router-guards-cleanup.test.d.ts +1 -0
  47. package/dist/router-guards-cleanup.test.mjs +437 -0
  48. package/dist/router-link.d.ts +10 -0
  49. package/dist/router-link.mjs +126 -0
  50. package/dist/router-push.test.d.ts +1 -0
  51. package/dist/router-push.test.mjs +115 -0
  52. package/dist/router-replace.test.d.ts +1 -0
  53. package/dist/router-replace.test.mjs +114 -0
  54. package/dist/router-resolve.test.d.ts +1 -0
  55. package/dist/router-resolve.test.mjs +393 -0
  56. package/dist/router-restart-app.dom.test.d.ts +1 -0
  57. package/dist/router-restart-app.dom.test.mjs +616 -0
  58. package/dist/router-window-navigation.test.d.ts +1 -0
  59. package/dist/router-window-navigation.test.mjs +359 -0
  60. package/dist/router.d.ts +109 -102
  61. package/dist/router.mjs +260 -361
  62. package/dist/types.d.ts +246 -0
  63. package/dist/types.mjs +18 -0
  64. package/dist/util.d.ts +26 -0
  65. package/dist/util.mjs +53 -0
  66. package/dist/util.test.d.ts +1 -0
  67. package/dist/util.test.mjs +1020 -0
  68. package/package.json +10 -13
  69. package/src/error.ts +84 -0
  70. package/src/increment-id.ts +12 -0
  71. package/src/index.test.ts +9 -0
  72. package/src/index.ts +54 -3
  73. package/src/location.test.ts +406 -0
  74. package/src/location.ts +96 -0
  75. package/src/matcher.test.ts +1685 -0
  76. package/src/matcher.ts +59 -0
  77. package/src/micro-app.dom.test.ts +708 -0
  78. package/src/micro-app.ts +101 -0
  79. package/src/navigation.test.ts +858 -0
  80. package/src/navigation.ts +195 -0
  81. package/src/options.ts +131 -0
  82. package/src/route-task.test.ts +901 -0
  83. package/src/route-task.ts +105 -0
  84. package/src/route-transition.test.ts +178 -0
  85. package/src/route-transition.ts +425 -0
  86. package/src/route.test.ts +2014 -0
  87. package/src/route.ts +308 -0
  88. package/src/router-back.test.ts +487 -0
  89. package/src/router-forward.test.ts +506 -0
  90. package/src/router-go.test.ts +91 -0
  91. package/src/router-guards-cleanup.test.ts +595 -0
  92. package/src/router-link.ts +235 -0
  93. package/src/router-push.test.ts +140 -0
  94. package/src/router-replace.test.ts +139 -0
  95. package/src/router-resolve.test.ts +475 -0
  96. package/src/router-restart-app.dom.test.ts +783 -0
  97. package/src/router-window-navigation.test.ts +457 -0
  98. package/src/router.ts +289 -470
  99. package/src/types.ts +341 -0
  100. package/src/util.test.ts +1262 -0
  101. package/src/util.ts +116 -0
  102. package/dist/history/abstract.d.ts +0 -29
  103. package/dist/history/abstract.mjs +0 -107
  104. package/dist/history/base.d.ts +0 -79
  105. package/dist/history/base.mjs +0 -275
  106. package/dist/history/html.d.ts +0 -30
  107. package/dist/history/html.mjs +0 -183
  108. package/dist/history/index.d.ts +0 -7
  109. package/dist/history/index.mjs +0 -16
  110. package/dist/matcher/create-matcher.d.ts +0 -5
  111. package/dist/matcher/create-matcher.mjs +0 -218
  112. package/dist/matcher/create-matcher.spec.mjs +0 -0
  113. package/dist/matcher/index.d.ts +0 -1
  114. package/dist/matcher/index.mjs +0 -1
  115. package/dist/task-pipe/index.d.ts +0 -1
  116. package/dist/task-pipe/index.mjs +0 -1
  117. package/dist/task-pipe/task.d.ts +0 -30
  118. package/dist/task-pipe/task.mjs +0 -66
  119. package/dist/types/index.d.ts +0 -694
  120. package/dist/types/index.mjs +0 -6
  121. package/dist/utils/bom.d.ts +0 -5
  122. package/dist/utils/bom.mjs +0 -10
  123. package/dist/utils/encoding.d.ts +0 -48
  124. package/dist/utils/encoding.mjs +0 -44
  125. package/dist/utils/guards.d.ts +0 -9
  126. package/dist/utils/guards.mjs +0 -12
  127. package/dist/utils/index.d.ts +0 -7
  128. package/dist/utils/index.mjs +0 -27
  129. package/dist/utils/path.d.ts +0 -60
  130. package/dist/utils/path.mjs +0 -282
  131. package/dist/utils/path.spec.mjs +0 -27
  132. package/dist/utils/scroll.d.ts +0 -25
  133. package/dist/utils/scroll.mjs +0 -59
  134. package/dist/utils/utils.d.ts +0 -16
  135. package/dist/utils/utils.mjs +0 -11
  136. package/dist/utils/warn.d.ts +0 -2
  137. package/dist/utils/warn.mjs +0 -12
  138. package/src/history/abstract.ts +0 -149
  139. package/src/history/base.ts +0 -408
  140. package/src/history/html.ts +0 -228
  141. package/src/history/index.ts +0 -20
  142. package/src/matcher/create-matcher.spec.ts +0 -3
  143. package/src/matcher/create-matcher.ts +0 -292
  144. package/src/matcher/index.ts +0 -1
  145. package/src/task-pipe/index.ts +0 -1
  146. package/src/task-pipe/task.ts +0 -97
  147. package/src/types/index.ts +0 -858
  148. package/src/utils/bom.ts +0 -14
  149. package/src/utils/encoding.ts +0 -153
  150. package/src/utils/guards.ts +0 -25
  151. package/src/utils/index.ts +0 -27
  152. package/src/utils/path.spec.ts +0 -32
  153. package/src/utils/path.ts +0 -418
  154. package/src/utils/scroll.ts +0 -120
  155. package/src/utils/utils.ts +0 -30
  156. package/src/utils/warn.ts +0 -13
  157. /package/dist/{matcher/create-matcher.spec.d.ts → index.test.d.ts} +0 -0
  158. /package/dist/{utils/path.spec.d.ts → matcher.test.d.ts} +0 -0
@@ -0,0 +1,1492 @@
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 = `/${base}/${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: `/${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${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${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
+ });