@@ -14,6 +14,7 @@ import {
14
14
Link ,
15
15
Outlet ,
16
16
RouterProvider ,
17
+ createBrowserHistory ,
17
18
createLink ,
18
19
createMemoryHistory ,
19
20
createRootRoute ,
@@ -35,20 +36,25 @@ import {
35
36
getSearchParamsFromURI ,
36
37
sleep ,
37
38
} from './utils'
39
+ import type { RouterHistory } from '../src'
38
40
39
41
const ioObserveMock = vi . fn ( )
40
42
const ioDisconnectMock = vi . fn ( )
43
+ let history : RouterHistory
41
44
42
45
beforeEach ( ( ) => {
43
46
const io = getIntersectionObserverMock ( {
44
47
observe : ioObserveMock ,
45
48
disconnect : ioDisconnectMock ,
46
49
} )
47
50
vi . stubGlobal ( 'IntersectionObserver' , io )
48
- window . history . replaceState ( null , 'root' , '/' )
51
+ history = createBrowserHistory ( )
52
+ expect ( window . location . pathname ) . toBe ( '/' )
49
53
} )
50
54
51
55
afterEach ( ( ) => {
56
+ history . destroy ?.( )
57
+ window . history . replaceState ( null , 'root' , '/' )
52
58
vi . resetAllMocks ( )
53
59
cleanup ( )
54
60
} )
@@ -666,6 +672,119 @@ describe('Link', () => {
666
672
expect ( pageZero ) . toBeInTheDocument ( )
667
673
} )
668
674
675
+ test ( 'when navigation to . from /posts while updating search from / and using base path' , async ( ) => {
676
+ const RootComponent = ( ) => {
677
+ return (
678
+ < >
679
+ < div data-testid = "root-nav" >
680
+ < Link
681
+ to = "."
682
+ search = { { page : 2 , filter : 'inactive' } }
683
+ data-testid = "update-search"
684
+ >
685
+ Update Search
686
+ </ Link >
687
+ </ div >
688
+ < Outlet />
689
+ </ >
690
+ )
691
+ }
692
+
693
+ const rootRoute = createRootRoute ( {
694
+ component : RootComponent ,
695
+ } )
696
+
697
+ const indexRoute = createRoute ( {
698
+ getParentRoute : ( ) => rootRoute ,
699
+ path : '/' ,
700
+ component : ( ) => {
701
+ return (
702
+ < >
703
+ < h1 > Index</ h1 >
704
+ < Link
705
+ to = "/posts"
706
+ search = { { page : 1 , filter : 'active' } }
707
+ data-testid = "to-posts"
708
+ >
709
+ Go to Posts
710
+ </ Link >
711
+ </ >
712
+ )
713
+ } ,
714
+ } )
715
+
716
+ const PostsComponent = ( ) => {
717
+ const search = useSearch ( { strict : false } )
718
+ return (
719
+ < >
720
+ < h1 > Posts</ h1 >
721
+ < span data-testid = "current-page" > Page: { search ( ) . page } </ span >
722
+ < span data-testid = "current-filter" > Filter: { search ( ) . filter } </ span >
723
+ </ >
724
+ )
725
+ }
726
+
727
+ const postsRoute = createRoute ( {
728
+ getParentRoute : ( ) => rootRoute ,
729
+ path : 'posts' ,
730
+ validateSearch : ( input : Record < string , unknown > ) => {
731
+ return {
732
+ page : input . page ? Number ( input . page ) : 1 ,
733
+ filter : ( input . filter as string ) || 'all' ,
734
+ }
735
+ } ,
736
+ component : PostsComponent ,
737
+ } )
738
+
739
+ const router = createRouter ( {
740
+ routeTree : rootRoute . addChildren ( [ indexRoute , postsRoute ] ) ,
741
+ history,
742
+ } )
743
+
744
+ render ( ( ) => < RouterProvider router = { router } basepath = { '/Dashboard' } /> )
745
+
746
+ // Start at index page
747
+ const toPostsLink = await screen . findByTestId ( 'to-posts' )
748
+ expect ( toPostsLink ) . toHaveAttribute (
749
+ 'href' ,
750
+ '/Dashboard/posts?page=1&filter=active' ,
751
+ )
752
+
753
+ // Navigate to posts with initial search params
754
+ fireEvent . click ( toPostsLink )
755
+
756
+ // Verify we're on posts with initial search
757
+ const postsHeading = await screen . findByRole ( 'heading' , { name : 'Posts' } )
758
+ expect ( postsHeading ) . toBeInTheDocument ( )
759
+ expect ( window . location . pathname ) . toBe ( '/Dashboard/posts' )
760
+ expect ( window . location . search ) . toBe ( '?page=1&filter=active' )
761
+
762
+ const currentPage = await screen . findByTestId ( 'current-page' )
763
+ const currentFilter = await screen . findByTestId ( 'current-filter' )
764
+ expect ( currentPage ) . toHaveTextContent ( 'Page: 1' )
765
+ expect ( currentFilter ) . toHaveTextContent ( 'Filter: active' )
766
+
767
+ // Navigate to current route (.) with updated search
768
+ const updateSearchLink = await screen . findByTestId ( 'update-search' )
769
+
770
+ expect ( updateSearchLink ) . toHaveAttribute (
771
+ 'href' ,
772
+ '/Dashboard/posts?page=2&filter=inactive' ,
773
+ )
774
+
775
+ fireEvent . click ( updateSearchLink )
776
+
777
+ await screen . findByTestId ( 'current-page' )
778
+ // Verify search was updated
779
+ expect ( window . location . pathname ) . toBe ( '/Dashboard/posts' )
780
+ expect ( window . location . search ) . toBe ( '?page=2&filter=inactive' )
781
+
782
+ const updatedPage = await screen . findByTestId ( 'current-page' )
783
+ const updatedFilter = await screen . findByTestId ( 'current-filter' )
784
+ expect ( updatedPage ) . toHaveTextContent ( 'Page: 2' )
785
+ expect ( updatedFilter ) . toHaveTextContent ( 'Filter: inactive' )
786
+ } )
787
+
669
788
test ( 'when navigating to /posts with invalid search' , async ( ) => {
670
789
const rootRoute = createRootRoute ( )
671
790
const onError = vi . fn ( )
0 commit comments