@@ -981,4 +981,101 @@ describe("matchRoutes", () => {
981981 expect ( result ) . toBeNull ( ) ;
982982 } ) ;
983983 } ) ;
984+
985+ describe ( "trailing slash handling" , ( ) => {
986+ describe ( 'default mode ("ignore")' , ( ) => {
987+ it ( "matches a pathname with trailing slash against an exact route" , ( ) => {
988+ const routes = internalRoutes ( [ { path : "/users" , component : ( ) => null } ] ) ;
989+
990+ const result = matchRoutes ( routes , "/users/" ) ;
991+ expect ( result ) . toHaveLength ( 1 ) ;
992+ expect ( result ! [ 0 ] . pathname ) . toBe ( "/users" ) ;
993+ } ) ;
994+
995+ it ( "matches a pathname with trailing slash against a param route" , ( ) => {
996+ const routes = internalRoutes ( [ { path : "/users/:id" , component : ( ) => null } ] ) ;
997+
998+ const result = matchRoutes ( routes , "/users/123/" ) ;
999+ expect ( result ) . toHaveLength ( 1 ) ;
1000+ expect ( result ! [ 0 ] . params ) . toEqual ( { id : "123" } ) ;
1001+ expect ( result ! [ 0 ] . pathname ) . toBe ( "/users/123" ) ;
1002+ } ) ;
1003+
1004+ it ( "matches a pathname with trailing slash against nested routes" , ( ) => {
1005+ const routes = internalRoutes ( [
1006+ {
1007+ path : "/users" ,
1008+ component : ( ) => null ,
1009+ children : [ { path : ":id" , component : ( ) => null } ] ,
1010+ } ,
1011+ ] ) ;
1012+
1013+ const result = matchRoutes ( routes , "/users/123/" ) ;
1014+ expect ( result ) . toHaveLength ( 2 ) ;
1015+ expect ( result ! [ 1 ] . params ) . toEqual ( { id : "123" } ) ;
1016+ } ) ;
1017+
1018+ it ( "ignores a trailing slash in the route pattern" , ( ) => {
1019+ const routes = internalRoutes ( [ { path : "/users/" , component : ( ) => null } ] ) ;
1020+
1021+ expect ( matchRoutes ( routes , "/users" ) ) . toHaveLength ( 1 ) ;
1022+ expect ( matchRoutes ( routes , "/users/" ) ) . toHaveLength ( 1 ) ;
1023+ } ) ;
1024+
1025+ it ( "does not strip the root pathname" , ( ) => {
1026+ const routes = internalRoutes ( [ { path : "/" , component : ( ) => null } ] ) ;
1027+
1028+ const result = matchRoutes ( routes , "/" ) ;
1029+ expect ( result ) . toHaveLength ( 1 ) ;
1030+ } ) ;
1031+
1032+ it ( "excludes the trailing slash from wildcard-like captures" , ( ) => {
1033+ const routes = internalRoutes ( [ { path : "/files/:path+" , component : ( ) => null } ] ) ;
1034+
1035+ const result = matchRoutes ( routes , "/files/a/b/" ) ;
1036+ expect ( result ) . toHaveLength ( 1 ) ;
1037+ expect ( result ! [ 0 ] . params ) . toEqual ( { path : "a/b" } ) ;
1038+ } ) ;
1039+
1040+ it ( "strips only a single trailing slash" , ( ) => {
1041+ const routes = internalRoutes ( [ { path : "/users" , component : ( ) => null } ] ) ;
1042+
1043+ // "/users//" contains an empty segment and is not repaired
1044+ expect ( matchRoutes ( routes , "/users//" ) ) . toBeNull ( ) ;
1045+ } ) ;
1046+
1047+ it ( "does not fall through to a catch-all for a trailing-slash URL" , ( ) => {
1048+ const routes = internalRoutes ( [
1049+ { path : "/users" , component : ( ) => null } ,
1050+ { path : "/:rest*" , component : ( ) => null } ,
1051+ ] ) ;
1052+
1053+ const result = matchRoutes ( routes , "/users/" ) ;
1054+ expect ( result ) . toHaveLength ( 1 ) ;
1055+ expect ( result ! [ 0 ] . route . path ) . toBe ( "/users" ) ;
1056+ } ) ;
1057+ } ) ;
1058+
1059+ describe ( '"strict" mode' , ( ) => {
1060+ it ( "does not match a pathname with trailing slash against an exact route" , ( ) => {
1061+ const routes = internalRoutes ( [ { path : "/users" , component : ( ) => null } ] ) ;
1062+
1063+ expect ( matchRoutes ( routes , "/users/" , { trailingSlash : "strict" } ) ) . toBeNull ( ) ;
1064+ expect ( matchRoutes ( routes , "/users" , { trailingSlash : "strict" } ) ) . toHaveLength ( 1 ) ;
1065+ } ) ;
1066+
1067+ it ( "keeps a trailing slash in the route pattern significant" , ( ) => {
1068+ const routes = internalRoutes ( [ { path : "/users/" , component : ( ) => null } ] ) ;
1069+
1070+ expect ( matchRoutes ( routes , "/users" , { trailingSlash : "strict" } ) ) . toBeNull ( ) ;
1071+ expect ( matchRoutes ( routes , "/users/" , { trailingSlash : "strict" } ) ) . toHaveLength ( 1 ) ;
1072+ } ) ;
1073+
1074+ it ( "still matches the root pathname" , ( ) => {
1075+ const routes = internalRoutes ( [ { path : "/" , component : ( ) => null } ] ) ;
1076+
1077+ expect ( matchRoutes ( routes , "/" , { trailingSlash : "strict" } ) ) . toHaveLength ( 1 ) ;
1078+ } ) ;
1079+ } ) ;
1080+ } ) ;
9841081} ) ;
0 commit comments