@@ -57,7 +57,7 @@ describe('generateRouteRecordPath', () => {
57
57
generateRouteRecordPath ( { importsMap, node, paramParsersMap : new Map ( ) } )
58
58
) . toMatchInlineSnapshot ( `
59
59
"path: new MatcherPatternPathCustomParams(
60
- /^\\/a\\/([^/]+)$/i,
60
+ /^\\/a\\/([^/]+? )$/i,
61
61
{
62
62
b: {},
63
63
},
@@ -76,7 +76,7 @@ describe('generateRouteRecordPath', () => {
76
76
} )
77
77
) . toMatchInlineSnapshot ( `
78
78
"path: new MatcherPatternPathCustomParams(
79
- /^\\/a\\/([^/]+)\\/([^/]+)$/i,
79
+ /^\\/a\\/([^/]+? )\\/([^/]+? )$/i,
80
80
{
81
81
b: {},
82
82
c: {},
@@ -92,7 +92,7 @@ describe('generateRouteRecordPath', () => {
92
92
generateRouteRecordPath ( { importsMap, node, paramParsersMap : new Map ( ) } )
93
93
) . toMatchInlineSnapshot ( `
94
94
"path: new MatcherPatternPathCustomParams(
95
- /^\\/a\\/([^/]+)?$/i,
95
+ /^\\/a\\/([^/]+? )?$/i,
96
96
{
97
97
b: {},
98
98
},
@@ -140,7 +140,7 @@ describe('generateRouteRecordPath', () => {
140
140
generateRouteRecordPath ( { importsMap, node, paramParsersMap : new Map ( ) } )
141
141
) . toMatchInlineSnapshot ( `
142
142
"path: new MatcherPatternPathCustomParams(
143
- /^\\/a\\/a-([^/]+)-c-([^/]+)$/i,
143
+ /^\\/a\\/a-([^/]+? )-c-([^/]+? )$/i,
144
144
{
145
145
b: {},
146
146
d: {},
@@ -253,10 +253,10 @@ describe('generateRouteResolver', () => {
253
253
})
254
254
255
255
export const resolver = createStaticResolver([
256
- r_0, // /a
257
- r_2, // /b/c
258
256
r_3, // /b/c/d
259
257
r_5, // /b/e/f
258
+ r_2, // /b/c
259
+ r_0, // /a
260
260
])
261
261
"
262
262
` )
@@ -285,22 +285,140 @@ describe('generateRouteResolver', () => {
285
285
286
286
expect ( resolver . replace ( / ^ .* ?c r e a t e S t a t i c R e s o l v e r / s, '' ) )
287
287
. toMatchInlineSnapshot ( `
288
- "([
289
- r_1, // /a
290
- r_11 , // /b/a-b
291
- r_7 , // /b/a- :a
292
- r_8, // /b/a-:a?
293
- r_10 , // /b/a-:a+
294
- r_9, // /b/a-:a*
295
- r_3 , // /b/:a
296
- r_4 , // /b/:a?
297
- r_6 , // /b/:a+
298
- r_5 , // /b/:a*
299
- r_0, // /:all(.*)
300
- ])
301
- "
302
- ` )
288
+ "([
289
+ r_11, // /b/a-b
290
+ r_7 , // /b/a-:a
291
+ r_3 , // /b/:a
292
+ r_8, // /b/a-:a?
293
+ r_4 , // /b/:a?
294
+ r_10, // /b/a-:a+
295
+ r_6 , // /b/:a+
296
+ r_9 , // /b/a-:a*
297
+ r_5 , // /b/:a*
298
+ r_1 , // /a
299
+ r_0, // /:all(.*)
300
+ ])
301
+ "
302
+ ` )
303
303
} )
304
304
305
305
it . todo ( 'strips off empty parent records' )
306
306
} )
307
+
308
+ describe ( 'route prioritization in resolver' , ( ) => {
309
+ function getRouteOrderFromResolver ( tree : PrefixTree ) : string [ ] {
310
+ const resolver = generateRouteResolver (
311
+ tree ,
312
+ DEFAULT_OPTIONS ,
313
+ new ImportsMap ( ) ,
314
+ new Map ( )
315
+ )
316
+
317
+ // Extract the order from the resolver output
318
+ const lines = resolver . split ( '\n' ) . filter ( ( line ) => line . includes ( '// /' ) )
319
+ return lines . map ( ( line ) => line . split ( '// ' ) [ 1 ] || '' )
320
+ }
321
+
322
+ it ( 'prioritizes routes correctly in resolver output' , ( ) => {
323
+ const tree = new PrefixTree ( DEFAULT_OPTIONS )
324
+
325
+ // Create routes with different specificity levels
326
+ tree . insert ( 'static' , 'static.vue' )
327
+ tree . insert ( '[id]' , '[id].vue' )
328
+ tree . insert ( '[[optional]]' , '[[optional]].vue' )
329
+ tree . insert ( 'prefix-[id]-suffix' , 'prefix-[id]-suffix.vue' )
330
+ tree . insert ( '[...all]' , '[...all].vue' )
331
+
332
+ // Routes should be ordered from most specific to least specific
333
+ expect ( getRouteOrderFromResolver ( tree ) ) . toEqual ( [
334
+ '/static' , // static routes first
335
+ '/prefix-:id-suffix' , // mixed routes with static content
336
+ '/:id' , // pure parameter routes
337
+ '/:optional?' , // optional parameter routes
338
+ '/:all(.*)' , // catch-all routes last
339
+ ] )
340
+ } )
341
+
342
+ it ( 'handles nested route prioritization correctly' , ( ) => {
343
+ const tree = new PrefixTree ( DEFAULT_OPTIONS )
344
+
345
+ // Create nested routes with different patterns
346
+ tree . insert ( 'api/users' , 'api/users.vue' )
347
+ tree . insert ( 'api/[resource]' , 'api/[resource].vue' )
348
+ tree . insert ( 'prefix-[param]/static' , 'prefix-[param]/static.vue' )
349
+ tree . insert ( '[dynamic]/static' , '[dynamic]/static.vue' )
350
+ tree . insert ( '[x]/[y]' , '[x]/[y].vue' )
351
+
352
+ // Routes with more static content should come first
353
+ expect ( getRouteOrderFromResolver ( tree ) ) . toEqual ( [
354
+ '/api/users' , // all static segments
355
+ '/api/:resource' , // static root, param child
356
+ '/prefix-:param/static' , // mixed root, static child
357
+ '/:dynamic/static' , // param root, static child
358
+ '/:x/:y' , // all param segments
359
+ ] )
360
+ } )
361
+
362
+ it ( 'orders complex mixed routes appropriately' , ( ) => {
363
+ const tree = new PrefixTree ( DEFAULT_OPTIONS )
364
+
365
+ // Create routes with various subsegment complexity
366
+ tree . insert ( 'users' , 'users.vue' ) // pure static
367
+ tree . insert ( 'prefix-[id]' , 'prefix-[id].vue' ) // prefix + param
368
+ tree . insert ( '[id]-suffix' , '[id]-suffix.vue' ) // param + suffix
369
+ tree . insert ( 'pre-[a]-mid-[b]-end' , 'complex.vue' ) // complex mixed
370
+ tree . insert ( '[a]-[b]' , '[a]-[b].vue' ) // params with separator
371
+ tree . insert ( '[param]' , '[param].vue' ) // pure param
372
+
373
+ expect ( getRouteOrderFromResolver ( tree ) ) . toEqual ( [
374
+ '/users' , // pure static wins
375
+ '/pre-:a-mid-:b-end' , // most static content in mixed
376
+ '/:id-suffix' , // static suffix
377
+ '/prefix-:id' , // static prefix
378
+ '/:a-:b' , // params with static separator
379
+ '/:param' , // pure param last
380
+ ] )
381
+ } )
382
+
383
+ it ( 'handles optional and repeatable params in nested contexts' , ( ) => {
384
+ const tree = new PrefixTree ( DEFAULT_OPTIONS )
385
+
386
+ // Create nested routes with optional and repeatable params
387
+ tree . insert ( 'api/static' , 'api/static.vue' )
388
+ tree . insert ( 'api/[[optional]]' , 'api/[[optional]].vue' )
389
+ tree . insert ( 'api/[required]' , 'api/[required].vue' )
390
+ tree . insert ( 'api/[repeatable]+' , 'api/[repeatable]+.vue' )
391
+ tree . insert ( 'api/[[optional]]+' , 'api/[[optional]]+.vue' )
392
+ tree . insert ( 'api/[...catchall]' , 'api/[...catchall].vue' )
393
+
394
+ expect ( getRouteOrderFromResolver ( tree ) ) . toEqual ( [
395
+ '/api/static' , // static segment wins
396
+ '/api/:required' , // required param
397
+ '/api/:optional?' , // optional param
398
+ '/api/:repeatable+' , // repeatable param
399
+ '/api/:optional*' , // optional repeatable param
400
+ '/api/:catchall(.*)' , // catch-all last
401
+ ] )
402
+ } )
403
+
404
+ it ( 'handles complex subsegments in deeply nested routes' , ( ) => {
405
+ const tree = new PrefixTree ( DEFAULT_OPTIONS )
406
+
407
+ // Create deeply nested routes with complex subsegment patterns
408
+ tree . insert ( 'api/v1/users' , 'api/v1/users.vue' )
409
+ tree . insert ( 'api/v1/user-[id]' , 'api/v1/user-[id].vue' )
410
+ tree . insert ( 'api/v1/[type]/list' , 'api/v1/[type]/list.vue' )
411
+ tree . insert ( 'api/v1/[type]/[id]' , 'api/v1/[type]/[id].vue' )
412
+ tree . insert ( 'api/v1/prefix-[a]-mid-[b]' , 'api/v1/prefix-[a]-mid-[b].vue' )
413
+ tree . insert ( 'api/v1/[x]-[y]-[z]' , 'api/v1/[x]-[y]-[z].vue' )
414
+
415
+ expect ( getRouteOrderFromResolver ( tree ) ) . toEqual ( [
416
+ '/api/v1/users' , // all static segments
417
+ '/api/v1/user-:id' , // mixed with static prefix
418
+ '/api/v1/prefix-:a-mid-:b' , // complex mixed pattern
419
+ '/api/v1/:x-:y-:z' , // multiple params with separators (mixed subsegments rank higher)
420
+ '/api/v1/:type/list' , // param + static child
421
+ '/api/v1/:type/:id' , // all param segments
422
+ ] )
423
+ } )
424
+ } )
0 commit comments