feat: add __layout wrapper to default 404 page (#633)

This commit is contained in:
Valerio Ageno
2025-03-09 11:58:25 +01:00
committed by GitHub
parent 77c902a7a5
commit 8fcebae4a9
3 changed files with 129 additions and 10 deletions
@@ -0,0 +1,114 @@
import type { HTMLAttributes, JSX } from 'react'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { cleanup, render, screen } from '@testing-library/react'
import { Route } from '../route'
import type { RouteComponent, RouteProps } from '../types'
import { NotFound } from './NotFound'
function createRouteComponent(
routeType: string,
RouteComponentFn: (props: RouteProps) => JSX.Element,
): RouteComponent {
const RootComponent = RouteComponentFn as RouteComponent
RootComponent.preload = vi.fn()
RootComponent.displayName = routeType
return RootComponent
}
const root = new Route({
isRoot: true,
component: createRouteComponent('root', ({ children }) => (
<div data-testid="root">{children}</div>
)),
})
vi.mock('./Link', () => ({
Link: (props: HTMLAttributes<HTMLAnchorElement>): JSX.Element => (
<a {...props} />
),
}))
vi.mock('../hooks/useServerPayloadData.ts', () => ({
useServerPayloadData: (): { data: unknown; isLoading: boolean } => {
return {
data: undefined,
isLoading: false,
}
},
}))
const { useRouterContext } = vi.hoisted(() => {
return { useRouterContext: vi.fn() }
})
vi.mock('../components/RouterContext', () => ({
useRouterContext,
}))
describe('<NotFound />', () => {
afterEach(cleanup)
describe('when a custom 404 page exists', () => {
it('should render the custom 404 page', () => {
useRouterContext.mockReturnValue({
router: {
routesById: {
'/404': new Route({
getParentRoute: (): Route => root,
component: createRouteComponent('404', () => (
<div data-testid="404">custom 404</div>
)),
}),
__root__: root,
},
},
})
render(<NotFound />)
expect(screen.getByTestId('root')).toMatchInlineSnapshot(
`
<div
data-testid="root"
>
<div
data-testid="404"
>
custom 404
</div>
</div>
`,
)
})
})
describe('when a custom 404 page does not exist', () => {
it('should render the default 404 page, wrapped by the root __layout', () => {
useRouterContext.mockReturnValue({
router: {
routesById: {
__root__: root,
},
},
})
render(<NotFound />)
expect(screen.getByTestId('root')).toMatchInlineSnapshot(
`
<div
data-testid="root"
>
<h1>
404 Not found
</h1>
<a
href="/"
>
Return home
</a>
</div>
`,
)
})
})
})
@@ -1,11 +1,12 @@
import type { JSX } from 'react'
import { useRouterContext } from '../components/RouterContext'
import { ROOT_ROUTE_ID } from '../route'
import { RouteMatch } from './RouteMatch'
import { Link } from './Link'
export function NotFound(): JSX.Element {
export function NotFound(): JSX.Element | null {
const { router } = useRouterContext()
const custom404Route = router.routesById['/404']
@@ -15,10 +16,14 @@ export function NotFound(): JSX.Element {
return <RouteMatch route={custom404Route} serverInitialData={{}} />
}
const RootLayout = router.routesById[ROOT_ROUTE_ID]?.component
if (!RootLayout) return null
return (
<>
<RootLayout data={null} isLoading={false}>
<h1>404 Not found</h1>
<Link href="/">Return home</Link>
</>
</RootLayout>
)
}
+7 -7
View File
@@ -14,7 +14,7 @@ export function createRoute(options: RouteOptions): Route {
return new Route(options)
}
const rootRouteId = '__root__'
export const ROOT_ROUTE_ID = '__root__'
export class Route {
options: RouteOptions
@@ -47,10 +47,10 @@ export class Route {
this.parentRoute = this.options.getParentRoute?.()
if (isRoot) {
this.path = rootRouteId
this.path = ROOT_ROUTE_ID
}
let path: undefined | string = isRoot ? rootRouteId : this.options.path
let path: undefined | string = isRoot ? ROOT_ROUTE_ID : this.options.path
// If the path is anything other than an index path, trim it up
if (path && path !== '/') {
@@ -60,17 +60,17 @@ export class Route {
const customId = this.options.id || path
// Strip the parentId prefix from the first level of children
let id = isRoot ? rootRouteId : joinPaths([customId])
let id = isRoot ? ROOT_ROUTE_ID : joinPaths([customId])
if (path === rootRouteId) {
if (path === ROOT_ROUTE_ID) {
path = '/'
}
if (id !== rootRouteId) {
if (id !== ROOT_ROUTE_ID) {
id = joinPaths(['/', id])
}
const fullPath = id === rootRouteId ? '/' : path
const fullPath = id === ROOT_ROUTE_ID ? '/' : path
this.path = path
this.id = id