Skip to content
366 changes: 366 additions & 0 deletions packages/router-core/tests/load.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,366 @@
import { describe, expect, test, vi } from 'vitest'
import { createMemoryHistory } from '@tanstack/history'
import {
BaseRootRoute,
BaseRoute,
RouterCore,
notFound,
redirect,
} from '../src'
import type { RouteOptions } from '../src'

type AnyRouteOptions = RouteOptions<any>
type BeforeLoad = NonNullable<AnyRouteOptions['beforeLoad']>
type Loader = NonNullable<AnyRouteOptions['loader']>

describe('beforeLoad skip or exec', () => {
const setup = ({ beforeLoad }: { beforeLoad?: BeforeLoad }) => {
const rootRoute = new BaseRootRoute({})

const fooRoute = new BaseRoute({
getParentRoute: () => rootRoute,
path: '/foo',
beforeLoad,
})

const barRoute = new BaseRoute({
getParentRoute: () => rootRoute,
path: '/bar',
})

const routeTree = rootRoute.addChildren([fooRoute, barRoute])

const router = new RouterCore({
routeTree,
history: createMemoryHistory(),
})

return router
}

test('baseline', async () => {
const beforeLoad = vi.fn()
const router = setup({ beforeLoad })
await router.load()
expect(beforeLoad).toHaveBeenCalledTimes(0)
})

test('exec on regular nav', async () => {
const beforeLoad = vi.fn(() => Promise.resolve({ hello: 'world' }))
const router = setup({ beforeLoad })
const navigation = router.navigate({ to: '/foo' })
expect(beforeLoad).toHaveBeenCalledTimes(1)
expect(router.state.pendingMatches).toEqual(
expect.arrayContaining([expect.objectContaining({ id: '/foo' })]),
)
await navigation
expect(router.state.location.pathname).toBe('/foo')
expect(router.state.matches).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: '/foo',
context: {
hello: 'world',
},
}),
]),
)
expect(beforeLoad).toHaveBeenCalledTimes(1)
})

test('exec if resolved preload (success)', async () => {
const beforeLoad = vi.fn()
const router = setup({ beforeLoad })
await router.preloadRoute({ to: '/foo' })
expect(router.state.cachedMatches).toEqual(
expect.arrayContaining([expect.objectContaining({ id: '/foo' })]),
)
await sleep(10)
await router.navigate({ to: '/foo' })

expect(beforeLoad).toHaveBeenCalledTimes(2)
})

test('exec if pending preload (success)', async () => {
const beforeLoad = vi.fn(() => sleep(100))
const router = setup({ beforeLoad })
router.preloadRoute({ to: '/foo' })
await Promise.resolve()
expect(router.state.cachedMatches).toEqual(
expect.arrayContaining([expect.objectContaining({ id: '/foo' })]),
)
await router.navigate({ to: '/foo' })

expect(beforeLoad).toHaveBeenCalledTimes(2)
})

test('exec if rejected preload (notFound)', async () => {
const beforeLoad = vi.fn<BeforeLoad>(async ({ preload }) => {
if (preload) throw notFound()
await Promise.resolve()
})
const router = setup({
beforeLoad,
})
await router.preloadRoute({ to: '/foo' })
await sleep(10)
await router.navigate({ to: '/foo' })

expect(beforeLoad).toHaveBeenCalledTimes(2)
})

test('exec if pending preload (notFound)', async () => {
const beforeLoad = vi.fn<BeforeLoad>(async ({ preload }) => {
await sleep(100)
if (preload) throw notFound()
})
const router = setup({
beforeLoad,
})
router.preloadRoute({ to: '/foo' })
await Promise.resolve()
await router.navigate({ to: '/foo' })

expect(beforeLoad).toHaveBeenCalledTimes(2)
})

test('exec if rejected preload (redirect)', async () => {
const beforeLoad = vi.fn<BeforeLoad>(async ({ preload }) => {
if (preload) throw redirect({ to: '/bar' })
await Promise.resolve()
})
const router = setup({
beforeLoad,
})
await router.preloadRoute({ to: '/foo' })
await sleep(10)
await router.navigate({ to: '/foo' })

expect(router.state.location.pathname).toBe('/foo')
expect(beforeLoad).toHaveBeenCalledTimes(2)
})

test('exec if pending preload (redirect)', async () => {
const beforeLoad = vi.fn<BeforeLoad>(async ({ preload }) => {
await sleep(100)
if (preload) throw redirect({ to: '/bar' })
})
const router = setup({
beforeLoad,
})
router.preloadRoute({ to: '/foo' })
await Promise.resolve()
await router.navigate({ to: '/foo' })

expect(router.state.location.pathname).toBe('/foo')
expect(beforeLoad).toHaveBeenCalledTimes(2)
})

test('exec if rejected preload (error)', async () => {
const beforeLoad = vi.fn<BeforeLoad>(async ({ preload }) => {
if (preload) throw new Error('error')
await Promise.resolve()
})
const router = setup({
beforeLoad,
})
await router.preloadRoute({ to: '/foo' })
await sleep(10)
await router.navigate({ to: '/foo' })

expect(beforeLoad).toHaveBeenCalledTimes(2)
})

test('exec if pending preload (error)', async () => {
const beforeLoad = vi.fn<BeforeLoad>(async ({ preload }) => {
await sleep(100)
if (preload) throw new Error('error')
})
const router = setup({
beforeLoad,
})
router.preloadRoute({ to: '/foo' })
await Promise.resolve()
await router.navigate({ to: '/foo' })

expect(beforeLoad).toHaveBeenCalledTimes(2)
})
})

describe('loader skip or exec', () => {
const setup = ({ loader }: { loader?: Loader }) => {
const rootRoute = new BaseRootRoute({})

const fooRoute = new BaseRoute({
getParentRoute: () => rootRoute,
path: '/foo',
loader,
})

const barRoute = new BaseRoute({
getParentRoute: () => rootRoute,
path: '/bar',
})

const routeTree = rootRoute.addChildren([fooRoute, barRoute])

const router = new RouterCore({
routeTree,
history: createMemoryHistory(),
})

return router
}

test('baseline', async () => {
const loader = vi.fn()
const router = setup({ loader })
await router.load()
expect(loader).toHaveBeenCalledTimes(0)
})

test('exec on regular nav', async () => {
const loader = vi.fn(() => Promise.resolve({ hello: 'world' }))
const router = setup({ loader })
const navigation = router.navigate({ to: '/foo' })
expect(loader).toHaveBeenCalledTimes(1)
expect(router.state.pendingMatches).toEqual(
expect.arrayContaining([expect.objectContaining({ id: '/foo' })]),
)
await navigation
expect(router.state.location.pathname).toBe('/foo')
expect(router.state.matches).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: '/foo',
loaderData: {
hello: 'world',
},
}),
]),
)
expect(loader).toHaveBeenCalledTimes(1)
})

test('exec if resolved preload (success)', async () => {
const loader = vi.fn()
const router = setup({ loader })
await router.preloadRoute({ to: '/foo' })
expect(router.state.cachedMatches).toEqual(
expect.arrayContaining([expect.objectContaining({ id: '/foo' })]),
)
await sleep(10)
await router.navigate({ to: '/foo' })

expect(loader).toHaveBeenCalledTimes(2)
})

test('skip if pending preload (success)', async () => {
const loader = vi.fn(() => sleep(100))
const router = setup({ loader })
router.preloadRoute({ to: '/foo' })
await Promise.resolve()
expect(router.state.cachedMatches).toEqual(
expect.arrayContaining([expect.objectContaining({ id: '/foo' })]),
)
await router.navigate({ to: '/foo' })

expect(loader).toHaveBeenCalledTimes(1)
})

test('exec if rejected preload (notFound)', async () => {
const loader = vi.fn<Loader>(async ({ preload }) => {
if (preload) throw notFound()
await Promise.resolve()
})
const router = setup({
loader,
})
await router.preloadRoute({ to: '/foo' })
await sleep(10)
await router.navigate({ to: '/foo' })

expect(loader).toHaveBeenCalledTimes(2)
})

test('skip if pending preload (notFound)', async () => {
const loader = vi.fn<Loader>(async ({ preload }) => {
await sleep(100)
if (preload) throw notFound()
})
const router = setup({
loader,
})
router.preloadRoute({ to: '/foo' })
await Promise.resolve()
await router.navigate({ to: '/foo' })

expect(loader).toHaveBeenCalledTimes(1)
})

test('exec if rejected preload (redirect)', async () => {
const loader = vi.fn<Loader>(async ({ preload }) => {
if (preload) throw redirect({ to: '/bar' })
await Promise.resolve()
})
const router = setup({
loader,
})
await router.preloadRoute({ to: '/foo' })
await sleep(10)
await router.navigate({ to: '/foo' })

expect(router.state.location.pathname).toBe('/foo')
expect(loader).toHaveBeenCalledTimes(2)
})

test('skip if pending preload (redirect)', async () => {
const loader = vi.fn<Loader>(async ({ preload }) => {
await sleep(100)
if (preload) throw redirect({ to: '/bar' })
})
const router = setup({
loader,
})
router.preloadRoute({ to: '/foo' })
await Promise.resolve()
await router.navigate({ to: '/foo' })

expect(router.state.location.pathname).toBe('/bar')
expect(loader).toHaveBeenCalledTimes(1)
})

test('exec if rejected preload (error)', async () => {
const loader = vi.fn<Loader>(async ({ preload }) => {
if (preload) throw new Error('error')
await Promise.resolve()
})
const router = setup({
loader,
})
await router.preloadRoute({ to: '/foo' })
await sleep(10)
await router.navigate({ to: '/foo' })

expect(loader).toHaveBeenCalledTimes(2)
})

test('skip if pending preload (error)', async () => {
const loader = vi.fn<Loader>(async ({ preload }) => {
await sleep(100)
if (preload) throw new Error('error')
})
const router = setup({
loader,
})
router.preloadRoute({ to: '/foo' })
await Promise.resolve()
await router.navigate({ to: '/foo' })

expect(loader).toHaveBeenCalledTimes(1)
})
})

function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms))
}