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