mirror of
https://github.com/tuono-labs/tuono
synced 2026-07-25 12:52:47 -07:00
feat: add __layout wrapper to default 404 page (#633)
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user