Skip to content

Commit c6c8c3b

Browse files
authored
Merge pull request #9684 from remix-run/pedro/explode-optional-segments
fix: explode optional segments for relative paths in nested routes
2 parents 09024c8 + 6f4c824 commit c6c8c3b

File tree

2 files changed

+400
-38
lines changed

2 files changed

+400
-38
lines changed

packages/react-router/__tests__/path-matching-test.tsx

Lines changed: 332 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ function pickPathsAndParams(routes: RouteObject[], pathname: string) {
1010
let matches = matchRoutes(routes, pathname);
1111
return (
1212
matches &&
13-
matches.map((match) => ({ path: match.route.path, params: match.params }))
13+
matches.map((match) => ({
14+
...(match.route.index ? { index: match.route.index } : {}),
15+
...(match.route.path ? { path: match.route.path } : {}),
16+
params: match.params,
17+
}))
1418
);
1519
}
1620

@@ -329,7 +333,7 @@ describe("path matching with splats", () => {
329333
});
330334
});
331335

332-
describe("path matchine with optional segments", () => {
336+
describe("path matching with optional segments", () => {
333337
test("optional static segment at the start of the path", () => {
334338
let routes = [
335339
{
@@ -409,6 +413,24 @@ describe("path matchine with optional segments", () => {
409413
},
410414
]);
411415
});
416+
417+
test("optional static segment in nested routes", () => {
418+
let nested = [
419+
{
420+
path: "/en?",
421+
children: [
422+
{
423+
path: "abc",
424+
},
425+
],
426+
},
427+
];
428+
429+
expect(pickPathsAndParams(nested, "/en/abc")).toEqual([
430+
{ path: "/en?", params: {} },
431+
{ path: "abc", params: {} },
432+
]);
433+
});
412434
});
413435

414436
describe("path matching with optional dynamic segments", () => {
@@ -491,4 +513,312 @@ describe("path matching with optional dynamic segments", () => {
491513
},
492514
]);
493515
});
516+
517+
test("consecutive optional dynamic segments in nested routes", () => {
518+
let nested = [
519+
{
520+
path: "/one/:two?",
521+
children: [
522+
{
523+
path: "three/:four?",
524+
children: [
525+
{
526+
path: ":five?",
527+
},
528+
],
529+
},
530+
],
531+
},
532+
];
533+
expect(pickPathsAndParams(nested, "/one/dos/three/cuatro/cinco")).toEqual([
534+
{
535+
path: "/one/:two?",
536+
params: { two: "dos", four: "cuatro", five: "cinco" },
537+
},
538+
{
539+
path: "three/:four?",
540+
params: { two: "dos", four: "cuatro", five: "cinco" },
541+
},
542+
{ path: ":five?", params: { two: "dos", four: "cuatro", five: "cinco" } },
543+
]);
544+
expect(pickPathsAndParams(nested, "/one/dos/three/cuatro")).toEqual([
545+
{
546+
path: "/one/:two?",
547+
params: { two: "dos", four: "cuatro" },
548+
},
549+
{
550+
path: "three/:four?",
551+
params: { two: "dos", four: "cuatro" },
552+
},
553+
{
554+
path: ":five?",
555+
params: { two: "dos", four: "cuatro" },
556+
},
557+
]);
558+
expect(pickPathsAndParams(nested, "/one/dos/three")).toEqual([
559+
{
560+
path: "/one/:two?",
561+
params: { two: "dos" },
562+
},
563+
{
564+
path: "three/:four?",
565+
params: { two: "dos" },
566+
},
567+
// Matches into 5 because it's just like if we did path=""
568+
{
569+
path: ":five?",
570+
params: { two: "dos" },
571+
},
572+
]);
573+
expect(pickPathsAndParams(nested, "/one/dos")).toEqual([
574+
{
575+
path: "/one/:two?",
576+
params: { two: "dos" },
577+
},
578+
]);
579+
expect(pickPathsAndParams(nested, "/one")).toEqual([
580+
{
581+
path: "/one/:two?",
582+
params: {},
583+
},
584+
]);
585+
expect(pickPathsAndParams(nested, "/one/three/cuatro/cinco")).toEqual([
586+
{
587+
path: "/one/:two?",
588+
params: { four: "cuatro", five: "cinco" },
589+
},
590+
{
591+
path: "three/:four?",
592+
params: { four: "cuatro", five: "cinco" },
593+
},
594+
{ path: ":five?", params: { four: "cuatro", five: "cinco" } },
595+
]);
596+
expect(pickPathsAndParams(nested, "/one/three/cuatro")).toEqual([
597+
{
598+
path: "/one/:two?",
599+
params: { four: "cuatro" },
600+
},
601+
{
602+
path: "three/:four?",
603+
params: { four: "cuatro" },
604+
},
605+
{
606+
path: ":five?",
607+
params: { four: "cuatro" },
608+
},
609+
]);
610+
expect(pickPathsAndParams(nested, "/one/three")).toEqual([
611+
{
612+
path: "/one/:two?",
613+
params: {},
614+
},
615+
{
616+
path: "three/:four?",
617+
params: {},
618+
},
619+
// Matches into 5 because it's just like if we did path=""
620+
{
621+
path: ":five?",
622+
params: {},
623+
},
624+
]);
625+
expect(pickPathsAndParams(nested, "/one")).toEqual([
626+
{
627+
path: "/one/:two?",
628+
params: {},
629+
},
630+
]);
631+
});
632+
633+
test("prefers optional static over optional dynamic segments", () => {
634+
let nested = [
635+
{
636+
path: "/one",
637+
children: [
638+
{
639+
path: ":param?",
640+
children: [
641+
{
642+
path: "three",
643+
},
644+
],
645+
},
646+
{
647+
path: "two?",
648+
children: [
649+
{
650+
path: "three",
651+
},
652+
],
653+
},
654+
],
655+
},
656+
];
657+
658+
// static `two` segment should win
659+
expect(pickPathsAndParams(nested, "/one/two/three")).toEqual([
660+
{
661+
params: {},
662+
path: "/one",
663+
},
664+
{
665+
params: {},
666+
path: "two?",
667+
},
668+
{
669+
params: {},
670+
path: "three",
671+
},
672+
]);
673+
674+
// fall back to param when no static match
675+
expect(pickPathsAndParams(nested, "/one/not-two/three")).toEqual([
676+
{
677+
params: {
678+
param: "not-two",
679+
},
680+
path: "/one",
681+
},
682+
{
683+
params: {
684+
param: "not-two",
685+
},
686+
path: ":param?",
687+
},
688+
{
689+
params: {
690+
param: "not-two",
691+
},
692+
path: "three",
693+
},
694+
]);
695+
696+
// No optional segment provided - earlier "dup" route should win
697+
expect(pickPathsAndParams(nested, "/one/three")).toEqual([
698+
{
699+
params: {},
700+
path: "/one",
701+
},
702+
{
703+
params: {},
704+
path: ":param?",
705+
},
706+
{
707+
params: {},
708+
path: "three",
709+
},
710+
]);
711+
});
712+
713+
test("prefers index routes over optional static segments", () => {
714+
let nested = [
715+
{
716+
path: "/one",
717+
children: [
718+
{
719+
path: ":param?",
720+
children: [
721+
{
722+
path: "three?",
723+
},
724+
{
725+
index: true,
726+
},
727+
],
728+
},
729+
],
730+
},
731+
];
732+
733+
expect(pickPathsAndParams(nested, "/one/two")).toEqual([
734+
{
735+
params: {
736+
param: "two",
737+
},
738+
path: "/one",
739+
},
740+
{
741+
params: {
742+
param: "two",
743+
},
744+
path: ":param?",
745+
},
746+
{
747+
index: true,
748+
params: {
749+
param: "two",
750+
},
751+
},
752+
]);
753+
expect(pickPathsAndParams(nested, "/one")).toEqual([
754+
{
755+
params: {},
756+
path: "/one",
757+
},
758+
{
759+
params: {},
760+
path: ":param?",
761+
},
762+
{
763+
index: true,
764+
params: {},
765+
},
766+
]);
767+
});
768+
769+
test("prefers index routes over optional dynamic segments", () => {
770+
let nested = [
771+
{
772+
path: "/one",
773+
children: [
774+
{
775+
path: ":param?",
776+
children: [
777+
{
778+
path: ":three?",
779+
},
780+
{
781+
index: true,
782+
},
783+
],
784+
},
785+
],
786+
},
787+
];
788+
789+
expect(pickPathsAndParams(nested, "/one/two")).toEqual([
790+
{
791+
params: {
792+
param: "two",
793+
},
794+
path: "/one",
795+
},
796+
{
797+
params: {
798+
param: "two",
799+
},
800+
path: ":param?",
801+
},
802+
{
803+
index: true,
804+
params: {
805+
param: "two",
806+
},
807+
},
808+
]);
809+
expect(pickPathsAndParams(nested, "/one")).toEqual([
810+
{
811+
params: {},
812+
path: "/one",
813+
},
814+
{
815+
params: {},
816+
path: ":param?",
817+
},
818+
{
819+
index: true,
820+
params: {},
821+
},
822+
]);
823+
});
494824
});

0 commit comments

Comments
 (0)