@@ -603,6 +603,164 @@ describe("special character tests", () => {
603603 ) ;
604604 }
605605 } ) ;
606+
607+ it ( "handles encoded question marks in ancestor splat route segments" , async ( ) => {
608+ let ctx = render (
609+ < BrowserRouter window = { getWindow ( "/parent/child/question-%3F-mark" ) } >
610+ < App />
611+ </ BrowserRouter > ,
612+ ) ;
613+
614+ expect ( getHtml ( ctx . container ) ) . toMatchInlineSnapshot ( `
615+ "<div>
616+ <a
617+ data-discover="true"
618+ href="/parent/child/question-%3F-mark/grandchild"
619+ >
620+ Link to grandchild
621+ </a>
622+ </div>"
623+ ` ) ;
624+
625+ await fireEvent . click ( screen . getByText ( "Link to grandchild" ) ) ;
626+ await waitFor ( ( ) => screen . getByText ( "Grandchild" ) ) ;
627+
628+ expect ( getHtml ( ctx . container ) ) . toMatchInlineSnapshot ( `
629+ "<div>
630+ <a
631+ data-discover="true"
632+ href="/parent/child/question-%3F-mark/grandchild"
633+ >
634+ Link to grandchild
635+ </a>
636+ <h1>
637+ Grandchild
638+ </h1>
639+ <pre>
640+ {"*":"grandchild","param":"question-?-mark"}
641+ </pre>
642+ </div>"
643+ ` ) ;
644+
645+ function App ( ) {
646+ return (
647+ < Routes >
648+ < Route path = "/parent/*" element = { < Parent /> } />
649+ </ Routes >
650+ ) ;
651+ }
652+
653+ function Parent ( ) {
654+ return (
655+ < Routes >
656+ < Route path = "child/:param/*" element = { < Child /> } />
657+ </ Routes >
658+ ) ;
659+ }
660+
661+ function Child ( ) {
662+ let location = useLocation ( ) ;
663+ let to = location . pathname . endsWith ( "grandchild" )
664+ ? "."
665+ : "./grandchild" ;
666+ return (
667+ < >
668+ < Link to = { to } > Link to grandchild</ Link >
669+ < Routes >
670+ < Route path = "grandchild" element = { < Grandchild /> } />
671+ </ Routes >
672+ </ >
673+ ) ;
674+ }
675+
676+ function Grandchild ( ) {
677+ return (
678+ < >
679+ < h1 > Grandchild</ h1 >
680+ < pre > { JSON . stringify ( useParams ( ) ) } </ pre >
681+ </ >
682+ ) ;
683+ }
684+ } ) ;
685+
686+ it ( "handles encoded hashes in ancestor splat route segments" , async ( ) => {
687+ let ctx = render (
688+ < BrowserRouter window = { getWindow ( "/parent/child/hash-%23-char" ) } >
689+ < App />
690+ </ BrowserRouter > ,
691+ ) ;
692+
693+ expect ( getHtml ( ctx . container ) ) . toMatchInlineSnapshot ( `
694+ "<div>
695+ <a
696+ data-discover="true"
697+ href="/parent/child/hash-%23-char/grandchild"
698+ >
699+ Link to grandchild
700+ </a>
701+ </div>"
702+ ` ) ;
703+
704+ await fireEvent . click ( screen . getByText ( "Link to grandchild" ) ) ;
705+ await waitFor ( ( ) => screen . getByText ( "Grandchild" ) ) ;
706+
707+ expect ( getHtml ( ctx . container ) ) . toMatchInlineSnapshot ( `
708+ "<div>
709+ <a
710+ data-discover="true"
711+ href="/parent/child/hash-%23-char/grandchild"
712+ >
713+ Link to grandchild
714+ </a>
715+ <h1>
716+ Grandchild
717+ </h1>
718+ <pre>
719+ {"*":"grandchild","param":"hash-#-char"}
720+ </pre>
721+ </div>"
722+ ` ) ;
723+
724+ function App ( ) {
725+ return (
726+ < Routes >
727+ < Route path = "/parent/*" element = { < Parent /> } />
728+ </ Routes >
729+ ) ;
730+ }
731+
732+ function Parent ( ) {
733+ return (
734+ < Routes >
735+ < Route path = "child/:param/*" element = { < Child /> } />
736+ </ Routes >
737+ ) ;
738+ }
739+
740+ function Child ( ) {
741+ let location = useLocation ( ) ;
742+ let to = location . pathname . endsWith ( "grandchild" )
743+ ? "."
744+ : "./grandchild" ;
745+ return (
746+ < >
747+ < Link to = { to } > Link to grandchild</ Link >
748+ < Routes >
749+ < Route path = "grandchild" element = { < Grandchild /> } />
750+ </ Routes >
751+ </ >
752+ ) ;
753+ }
754+
755+ function Grandchild ( ) {
756+ return (
757+ < >
758+ < h1 > Grandchild</ h1 >
759+ < pre > { JSON . stringify ( useParams ( ) ) } </ pre >
760+ </ >
761+ ) ;
762+ }
763+ } ) ;
606764 } ) ;
607765
608766 describe ( "when matching as part of the defined route path" , ( ) => {
0 commit comments