@@ -5,7 +5,14 @@ import React, { StrictMode } from 'react';
5
5
import { defaultRegistry } from 'react-sweet-state' ;
6
6
7
7
import { isServerEnvironment } from '../common/utils/is-server-environment' ;
8
- import { Route , RouteComponent , Router , type Plugin } from '../index' ;
8
+ import {
9
+ Route ,
10
+ RouteComponent ,
11
+ Router ,
12
+ type Plugin ,
13
+ usePathParam ,
14
+ useQueryParam ,
15
+ } from '../index' ;
9
16
10
17
jest . mock ( '../common/utils/is-server-environment' ) ;
11
18
@@ -318,5 +325,209 @@ describe('<Router /> client-side integration tests', () => {
318
325
expect ( screen . getByText ( 'route component' ) ) . toBeInTheDocument ( ) ;
319
326
} ) ;
320
327
} ) ;
328
+
329
+ describe ( `path matching integration tests: strict mode ${ strictModeState } ` , ( ) => {
330
+ it ( 'matches dynamic route with optional parameter' , ( ) => {
331
+ const MigrationComponent = ( ) => {
332
+ const [ step ] = usePathParam ( 'step' ) ;
333
+ const [ migrationId ] = usePathParam ( 'migrationId' ) ;
334
+
335
+ return (
336
+ < div >
337
+ Step: { step } , Migration ID: { migrationId || 'N/A' }
338
+ </ div >
339
+ ) ;
340
+ } ;
341
+
342
+ const route = {
343
+ name : 'migration' ,
344
+ path : '/settings/system/migration/:step/:migrationId?' ,
345
+ component : MigrationComponent ,
346
+ } ;
347
+ const { history } = mountRouter ( { routes : [ route ] , strictMode : true } ) ;
348
+
349
+ act ( ( ) => {
350
+ history . push ( '/settings/system/migration/plan-configuration/123' ) ;
351
+ } ) ;
352
+
353
+ expect (
354
+ screen . getByText ( 'Step: plan-configuration, Migration ID: 123' )
355
+ ) . toBeInTheDocument ( ) ;
356
+ } ) ;
357
+
358
+ it ( 'matches route with regex constraint on path parameter' , ( ) => {
359
+ const PlanComponent = ( ) => {
360
+ const [ planId ] = usePathParam ( 'planId' ) ;
361
+
362
+ return < div > Plan ID: { planId } </ div > ;
363
+ } ;
364
+
365
+ const route = {
366
+ name : 'plans' ,
367
+ path : '/plans/:planId(\\d+)' ,
368
+ component : PlanComponent ,
369
+ } ;
370
+ const { history } = mountRouter ( { routes : [ route ] , strictMode : true } ) ;
371
+
372
+ act ( ( ) => {
373
+ history . push ( '/plans/456' ) ;
374
+ } ) ;
375
+
376
+ expect ( screen . getByText ( 'Plan ID: 456' ) ) . toBeInTheDocument ( ) ;
377
+ } ) ;
378
+
379
+ it ( 'matches route with multiple dynamic parameters' , ( ) => {
380
+ const ProjectAppComponent = ( ) => {
381
+ const [ projectType ] = usePathParam ( 'projectType' ) ;
382
+ const [ projectKey ] = usePathParam ( 'projectKey' ) ;
383
+ const [ appId ] = usePathParam ( 'appId' ) ;
384
+
385
+ return (
386
+ < div >
387
+ Project Type: { projectType } , Project Key: { projectKey } , App ID:{ ' ' }
388
+ { appId }
389
+ </ div >
390
+ ) ;
391
+ } ;
392
+
393
+ const route = {
394
+ name : 'project-app' ,
395
+ path : '/app/:projectType(software|servicedesk)/projects/:projectKey/apps/:appId' ,
396
+ component : ProjectAppComponent ,
397
+ } ;
398
+ const { history } = mountRouter ( { routes : [ route ] , strictMode : true } ) ;
399
+
400
+ act ( ( ) => {
401
+ history . push ( '/app/software/projects/PROJ123/apps/456' ) ;
402
+ } ) ;
403
+
404
+ expect (
405
+ screen . getByText (
406
+ 'Project Type: software, Project Key: PROJ123, App ID: 456'
407
+ )
408
+ ) . toBeInTheDocument ( ) ;
409
+ } ) ;
410
+
411
+ it ( 'matches route with dynamic and query parameters' , ( ) => {
412
+ const IssueComponent = ( ) => {
413
+ const [ issueKey ] = usePathParam ( 'issueKey' ) ;
414
+ const [ queryParam ] = useQueryParam ( 'query' ) ;
415
+
416
+ return (
417
+ < div >
418
+ Issue Key: { issueKey } , Query: { queryParam || 'None' }
419
+ </ div >
420
+ ) ;
421
+ } ;
422
+
423
+ const route = {
424
+ name : 'browse' ,
425
+ path : '/browse/:issueKey(\\w+-\\d+)' ,
426
+ component : IssueComponent ,
427
+ } ;
428
+ const { history } = mountRouter ( { routes : [ route ] , strictMode : true } ) ;
429
+
430
+ act ( ( ) => {
431
+ history . push ( '/browse/ISSUE-123?query=details' ) ;
432
+ } ) ;
433
+
434
+ expect (
435
+ screen . getByText ( 'Issue Key: ISSUE-123, Query: details' )
436
+ ) . toBeInTheDocument ( ) ;
437
+ } ) ;
438
+
439
+ it ( 'matches route with complex regex constraint on path parameter and wildcard' , ( ) => {
440
+ const IssueComponent = ( ) => {
441
+ const [ issueKey ] = usePathParam ( 'issueKey' ) ;
442
+
443
+ return < div > Issue Key: { issueKey } </ div > ;
444
+ } ;
445
+
446
+ const route = {
447
+ name : 'browse' ,
448
+ path : '/browse/:issueKey(\\w+-\\d+)(.*)?' ,
449
+ component : IssueComponent ,
450
+ } ;
451
+ const { history } = mountRouter ( { routes : [ route ] , strictMode : true } ) ;
452
+
453
+ act ( ( ) => {
454
+ history . push ( '/browse/ISSUE-123/details' ) ;
455
+ } ) ;
456
+
457
+ expect ( screen . getByText ( 'Issue Key: ISSUE-123' ) ) . toBeInTheDocument ( ) ;
458
+ } ) ;
459
+
460
+ it ( 'matches route with multiple dynamic segments and regex constraints' , ( ) => {
461
+ const SettingsComponent = ( ) => {
462
+ const [ settingsType ] = usePathParam ( 'settingsType' ) ;
463
+ const [ appId ] = usePathParam ( 'appId' ) ;
464
+ const [ envId ] = usePathParam ( 'envId' ) ;
465
+ const [ route ] = usePathParam ( 'route' ) ;
466
+
467
+ return (
468
+ < div >
469
+ Settings Type: { settingsType } , App ID: { appId } , Environment ID:{ ' ' }
470
+ { envId } , Route: { route || 'None' }
471
+ </ div >
472
+ ) ;
473
+ } ;
474
+
475
+ const route = {
476
+ name : 'settings' ,
477
+ path : '/settings/apps/:settingsType(configure|get-started)/:appId/:envId/:route?' ,
478
+ component : SettingsComponent ,
479
+ } ;
480
+ const { history } = mountRouter ( { routes : [ route ] , strictMode : true } ) ;
481
+
482
+ act ( ( ) => {
483
+ history . push ( '/settings/apps/configure/app123/env456/setup' ) ;
484
+ } ) ;
485
+
486
+ expect (
487
+ screen . getByText (
488
+ 'Settings Type: configure, App ID: app123, Environment ID: env456, Route: setup'
489
+ )
490
+ ) . toBeInTheDocument ( ) ;
491
+ } ) ;
492
+
493
+ it ( 'matches route with regex constraint and renders wildcard route for invalid paths' , ( ) => {
494
+ const IssueComponent = ( ) => {
495
+ const [ issueKey ] = usePathParam ( 'issueKey' ) ;
496
+
497
+ return < div > Issue Key: { issueKey } </ div > ;
498
+ } ;
499
+
500
+ const NotFoundComponent = ( ) => < div > Not Found</ div > ;
501
+
502
+ const routes = [
503
+ {
504
+ name : 'issue' ,
505
+ path : '/browse/:issueKey(\\w+-\\d+)(.*)?' ,
506
+ component : IssueComponent ,
507
+ } ,
508
+ {
509
+ name : 'wildcard' ,
510
+ path : '/' ,
511
+ component : NotFoundComponent ,
512
+ } ,
513
+ ] ;
514
+ const { history } = mountRouter ( { routes, strictMode : true } ) ;
515
+
516
+ act ( ( ) => {
517
+ history . push ( '/browse/TEST-1' ) ;
518
+ } ) ;
519
+ expect ( screen . getByText ( 'Issue Key: TEST-1' ) ) . toBeInTheDocument ( ) ;
520
+
521
+ act ( ( ) => {
522
+ history . push ( '/browse/1' ) ;
523
+ } ) ;
524
+ expect ( screen . getByText ( 'Not Found' ) ) . toBeInTheDocument ( ) ;
525
+
526
+ act ( ( ) => {
527
+ history . push ( '/browse/TEST' ) ;
528
+ } ) ;
529
+ expect ( screen . getByText ( 'Not Found' ) ) . toBeInTheDocument ( ) ;
530
+ } ) ;
531
+ } ) ;
321
532
}
322
533
} ) ;
0 commit comments