Skip to content

Commit

Permalink
fix(react-router): redirect with relative path (#1746)
Browse files Browse the repository at this point in the history
* fix: redirect with relative path

* tests(react-router): add test for redirect with `from` and fix existing test

* tests: add beforeLoad test with redirect with `from`

* tests: remove leading slashes

---------

Co-authored-by: chorobin <chrishorobin@hotmail.com>
  • Loading branch information
schiller-manuel and chorobin authored Jun 15, 2024
1 parent d017403 commit 3b9a6fe
Show file tree
Hide file tree
Showing 3 changed files with 213 additions and 1 deletion.
2 changes: 1 addition & 1 deletion packages/react-router/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1677,7 +1677,7 @@ export class Router<

if (isRedirect(err)) {
rendered = true
err = this.resolveRedirect(err)
err = this.resolveRedirect({ ...err, _fromLocation: location })
throw err
} else if (isNotFound(err)) {
this.handleNotFound(matches, err)
Expand Down
14 changes: 14 additions & 0 deletions packages/react-router/tests/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,13 @@ describe('ssr redirects', async () => {
await router.load()

expect(router.state.redirect).toEqual({
_fromLocation: expect.objectContaining({
hash: '',
href: '/',
pathname: '/',
search: {},
searchStr: '',
}),
to: '/about',
headers: {},
href: '/about',
Expand Down Expand Up @@ -635,6 +642,13 @@ describe('ssr redirects', async () => {
await router.load()

expect(router.state.redirect).toEqual({
_fromLocation: expect.objectContaining({
hash: '',
href: '/',
pathname: '/',
search: {},
searchStr: '',
}),
to: '/about',
headers: {},
href: '/about',
Expand Down
198 changes: 198 additions & 0 deletions packages/react-router/tests/link.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2528,6 +2528,204 @@ describe('Link', () => {
expect(ErrorComponent).not.toHaveBeenCalled()
})

test('when navigating to /post/$postId with a redirect from /post/$postId to ../../login in a loader', async () => {
const ErrorComponent = vi.fn(() => <div>Something went wrong!</div>)

const rootRoute = createRootRoute({
errorComponent: ErrorComponent,
})

const indexRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
component: () => {
return (
<React.Fragment>
<h1>Index</h1>
<Link to="/posts/$postId" params={{ postId: 'id1' }}>
To first post
</Link>
</React.Fragment>
)
},
})

const PostsComponent = () => {
return (
<React.Fragment>
<h1>Posts</h1>
<Outlet />
</React.Fragment>
)
}

const postsRoute = createRoute({
getParentRoute: () => rootRoute,
path: 'posts',
component: PostsComponent,
})

const PostComponent = () => {
const params = useParams({ strict: false })
return (
<React.Fragment>
<span>Params: {params.postId}</span>
<Outlet />
</React.Fragment>
)
}

const search = vi.fn((prev) => ({ page: prev.postPage }))

const postRoute = createRoute({
getParentRoute: () => postsRoute,
path: '$postId',
component: PostComponent,
validateSearch: () => ({ postPage: 0 }),
loader: () => {
throw redirect({
from: postRoute.fullPath,
to: '../../login',
search,
})
},
})

const LoginComponent = () => {
return <React.Fragment>Login!</React.Fragment>
}

const loginRoute = createRoute({
getParentRoute: () => rootRoute,
path: 'login',
component: LoginComponent,
validateSearch: () => ({ page: 0 }),
})

const routeTree = rootRoute.addChildren([
indexRoute,
loginRoute,
postsRoute.addChildren([postRoute]),
])

const router = createRouter({
routeTree,
})

render(<RouterProvider router={router} />)

const postLink = await screen.findByRole('link', {
name: 'To first post',
})

expect(postLink).toHaveAttribute('href', '/posts/id1')

fireEvent.click(postLink)

expect(await screen.findByText('Login!')).toBeInTheDocument()

expect(ErrorComponent).not.toHaveBeenCalled()
})

test('when navigating to /post/$postId with a redirect from /post/$postId to ../../login in beforeLoad', async () => {
const ErrorComponent = vi.fn(() => <div>Something went wrong!</div>)

const rootRoute = createRootRoute({
errorComponent: ErrorComponent,
})

const indexRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
component: () => {
return (
<React.Fragment>
<h1>Index</h1>
<Link to="/posts/$postId" params={{ postId: 'id1' }}>
To first post
</Link>
</React.Fragment>
)
},
})

const PostsComponent = () => {
return (
<React.Fragment>
<h1>Posts</h1>
<Outlet />
</React.Fragment>
)
}

const postsRoute = createRoute({
getParentRoute: () => rootRoute,
path: 'posts',
component: PostsComponent,
})

const PostComponent = () => {
const params = useParams({ strict: false })
return (
<React.Fragment>
<span>Params: {params.postId}</span>
<Outlet />
</React.Fragment>
)
}

const search = vi.fn((prev) => ({ page: prev.postPage }))

const postRoute = createRoute({
getParentRoute: () => postsRoute,
path: '$postId',
component: PostComponent,
validateSearch: () => ({ postPage: 0 }),
beforeLoad: () => {
throw redirect({
from: postRoute.fullPath,
to: '../../login',
search,
})
},
})

const LoginComponent = () => {
return <React.Fragment>Login!</React.Fragment>
}

const loginRoute = createRoute({
getParentRoute: () => rootRoute,
path: 'login',
component: LoginComponent,
validateSearch: () => ({ page: 0 }),
})

const routeTree = rootRoute.addChildren([
indexRoute,
loginRoute,
postsRoute.addChildren([postRoute]),
])

const router = createRouter({
routeTree,
})

render(<RouterProvider router={router} />)

const postLink = await screen.findByRole('link', {
name: 'To first post',
})

expect(postLink).toHaveAttribute('href', '/posts/id1')

fireEvent.click(postLink)

expect(await screen.findByText('Login!')).toBeInTheDocument()

expect(ErrorComponent).not.toHaveBeenCalled()
})

test('when preloading /post/$postId with a beforeLoad that navigates to /login', async () => {
const ErrorComponent = vi.fn(() => <div>Something went wrong!</div>)

Expand Down

0 comments on commit 3b9a6fe

Please sign in to comment.