diff --git a/packages/react-router/tests/errorComponent.test.tsx b/packages/react-router/tests/errorComponent.test.tsx index 2c0062a9ac..7835ca9e3a 100644 --- a/packages/react-router/tests/errorComponent.test.tsx +++ b/packages/react-router/tests/errorComponent.test.tsx @@ -5,6 +5,7 @@ import { Link, RouterProvider, createBrowserHistory, + createLazyRoute, createRootRoute, createRoute, createRouter, @@ -38,110 +39,135 @@ afterEach(() => { cleanup() }) -describe.each([{ preload: false }, { preload: 'intent' }] as const)( - 'errorComponent is rendered when the preload=$preload', - (options) => { - describe.each([true, false])('with async=%s', (isAsync) => { - const throwableFn = isAsync ? asyncToThrowFn : throwFn - - const callers = [ - { caller: 'beforeLoad', testFn: throwableFn }, - { caller: 'loader', testFn: throwableFn }, - ] - - test.each(callers)( - 'an Error is thrown on navigate in the route $caller function', - async ({ caller, testFn }) => { - const rootRoute = createRootRoute() - const indexRoute = createRoute({ - getParentRoute: () => rootRoute, - path: '/', - component: function Home() { - return ( -
- link to about -
+describe.each([true, false])( + 'with lazy errorComponent=%s', + (isUsingLazyError) => { + describe.each([{ preload: false }, { preload: 'intent' }] as const)( + 'errorComponent is rendered when the preload=$preload', + (options) => { + describe.each([true, false])('with async=%s', (isAsync) => { + const throwableFn = isAsync ? asyncToThrowFn : throwFn + + const callers = [ + { caller: 'beforeLoad', testFn: throwableFn }, + { caller: 'loader', testFn: throwableFn }, + ] + + test.each(callers)( + 'an Error is thrown on navigate in the route $caller function', + async ({ caller, testFn }) => { + const rootRoute = createRootRoute() + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + component: function Home() { + return ( +
+ link to about +
+ ) + }, + }) + const aboutRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/about', + beforeLoad: caller === 'beforeLoad' ? testFn : undefined, + loader: caller === 'loader' ? testFn : undefined, + component: function Home() { + return
About route content
+ }, + errorComponent: isUsingLazyError ? undefined : MyErrorComponent, + }) + + if (isUsingLazyError) { + aboutRoute.lazy(() => + Promise.resolve( + createLazyRoute('/about')({ + errorComponent: MyErrorComponent, + }), + ), + ) + } + + const routeTree = rootRoute.addChildren([indexRoute, aboutRoute]) + + const router = createRouter({ + routeTree, + defaultPreload: options.preload, + history, + }) + + render() + + const linkToAbout = await screen.findByRole('link', { + name: 'link to about', + }) + + expect(linkToAbout).toBeInTheDocument() + fireEvent.mouseOver(linkToAbout) + fireEvent.focus(linkToAbout) + fireEvent.click(linkToAbout) + + const errorComponent = await screen.findByText( + `Error: error thrown`, + undefined, + { timeout: 1500 }, ) + await expect( + screen.findByText('About route content'), + ).rejects.toThrow() + expect(errorComponent).toBeInTheDocument() }, - }) - const aboutRoute = createRoute({ - getParentRoute: () => rootRoute, - path: '/about', - beforeLoad: caller === 'beforeLoad' ? testFn : undefined, - loader: caller === 'loader' ? testFn : undefined, - component: function Home() { - return
About route content
- }, - errorComponent: MyErrorComponent, - }) - - const routeTree = rootRoute.addChildren([indexRoute, aboutRoute]) - - const router = createRouter({ - routeTree, - defaultPreload: options.preload, - history, - }) - - render() - - const linkToAbout = await screen.findByRole('link', { - name: 'link to about', - }) - - expect(linkToAbout).toBeInTheDocument() - fireEvent.mouseOver(linkToAbout) - fireEvent.focus(linkToAbout) - fireEvent.click(linkToAbout) - - const errorComponent = await screen.findByText( - `Error: error thrown`, - undefined, - { timeout: 1500 }, ) - await expect( - screen.findByText('About route content'), - ).rejects.toThrow() - expect(errorComponent).toBeInTheDocument() - }, - ) - - test.each(callers)( - 'an Error is thrown on first load in the route $caller function', - async ({ caller, testFn }) => { - const rootRoute = createRootRoute() - const indexRoute = createRoute({ - getParentRoute: () => rootRoute, - path: '/', - beforeLoad: caller === 'beforeLoad' ? testFn : undefined, - loader: caller === 'loader' ? testFn : undefined, - component: function Home() { - return
Index route content
- }, - errorComponent: MyErrorComponent, - }) - - const routeTree = rootRoute.addChildren([indexRoute]) - - const router = createRouter({ - routeTree, - defaultPreload: options.preload, - history, - }) - render() - - const errorComponent = await screen.findByText( - `Error: error thrown`, - undefined, - { timeout: 750 }, + test.each(callers)( + 'an Error is thrown on first load in the route $caller function', + async ({ caller, testFn }) => { + const rootRoute = createRootRoute() + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + beforeLoad: caller === 'beforeLoad' ? testFn : undefined, + loader: caller === 'loader' ? testFn : undefined, + component: function Home() { + return
Index route content
+ }, + errorComponent: isUsingLazyError ? undefined : MyErrorComponent, + }) + + if (isUsingLazyError) { + indexRoute.lazy(() => + Promise.resolve( + createLazyRoute('/')({ + errorComponent: MyErrorComponent, + }), + ), + ) + } + + const routeTree = rootRoute.addChildren([indexRoute]) + + const router = createRouter({ + routeTree, + defaultPreload: options.preload, + history, + }) + + render() + + const errorComponent = await screen.findByText( + `Error: error thrown`, + undefined, + { timeout: 750 }, + ) + await expect( + screen.findByText('Index route content'), + ).rejects.toThrow() + expect(errorComponent).toBeInTheDocument() + }, ) - await expect( - screen.findByText('Index route content'), - ).rejects.toThrow() - expect(errorComponent).toBeInTheDocument() - }, - ) - }) + }) + }, + ) }, ) diff --git a/packages/react-router/tests/store-updates-during-navigation.test.tsx b/packages/react-router/tests/store-updates-during-navigation.test.tsx index d68373ff4e..f7323a4ce0 100644 --- a/packages/react-router/tests/store-updates-during-navigation.test.tsx +++ b/packages/react-router/tests/store-updates-during-navigation.test.tsx @@ -182,7 +182,7 @@ describe("Store doesn't update *too many* times during navigation", () => { // that needs to be done during a navigation. // Any change that increases this number should be investigated. expect(updates).toBeGreaterThanOrEqual(6) // WARN: this is flaky, and sometimes (rarely) is 7 - expect(updates).toBeLessThanOrEqual(7) + expect(updates).toBeLessThanOrEqual(8) }) test('not found in beforeLoad', async () => { @@ -197,7 +197,7 @@ describe("Store doesn't update *too many* times during navigation", () => { // This number should be as small as possible to minimize the amount of work // that needs to be done during a navigation. // Any change that increases this number should be investigated. - expect(updates).toBe(7) + expect(updates).toBe(8) }) test('hover preload, then navigate, w/ async loaders', async () => { diff --git a/packages/router-core/src/load-matches.ts b/packages/router-core/src/load-matches.ts index 0ea5068ad0..5c943523c0 100644 --- a/packages/router-core/src/load-matches.ts +++ b/packages/router-core/src/load-matches.ts @@ -466,13 +466,22 @@ const executeBeforeLoad = ( if (isPromise(beforeLoadContext)) { pending() return beforeLoadContext - .catch((err) => { + .catch(async (err) => { + if (!isRedirect(err)) { + await loadRouteChunk(route) + } handleSerialError(inner, index, err, 'BEFORE_LOAD') }) .then(updateContext) } } catch (err) { pending() + if (!isRedirect(err)) { + return loadRouteChunk(route).then(() => { + handleSerialError(inner, index, err, 'BEFORE_LOAD') + updateContext(undefined) + }) + } handleSerialError(inner, index, err, 'BEFORE_LOAD') } diff --git a/packages/solid-router/tests/errorComponent.test.tsx b/packages/solid-router/tests/errorComponent.test.tsx index b9ccd941a6..4ee1abf3f1 100644 --- a/packages/solid-router/tests/errorComponent.test.tsx +++ b/packages/solid-router/tests/errorComponent.test.tsx @@ -4,6 +4,7 @@ import { cleanup, fireEvent, render, screen } from '@solidjs/testing-library' import { Link, RouterProvider, + createLazyRoute, createRootRoute, createRoute, createRouter, @@ -29,108 +30,133 @@ afterEach(() => { cleanup() }) -describe.each([{ preload: false }, { preload: 'intent' }] as const)( - 'errorComponent is rendered when the preload=$preload', - (options) => { - describe.each([true, false])('with async=%s', (isAsync) => { - const throwableFn = isAsync ? asyncToThrowFn : throwFn - - const callers = [ - { caller: 'beforeLoad', testFn: throwableFn }, - { caller: 'loader', testFn: throwableFn }, - ] - - test.each(callers)( - 'an Error is thrown on navigate in the route $caller function', - async ({ caller, testFn }) => { - const rootRoute = createRootRoute() - const indexRoute = createRoute({ - getParentRoute: () => rootRoute, - path: '/', - component: function Home() { - return ( -
- link to about -
+describe.each([true, false])( + 'with lazy errorComponent=%s', + (isUsingLazyError) => { + describe.each([{ preload: false }, { preload: 'intent' }] as const)( + 'errorComponent is rendered when the preload=$preload', + (options) => { + describe.each([true, false])('with async=%s', (isAsync) => { + const throwableFn = isAsync ? asyncToThrowFn : throwFn + + const callers = [ + { caller: 'beforeLoad', testFn: throwableFn }, + { caller: 'loader', testFn: throwableFn }, + ] + + test.each(callers)( + 'an Error is thrown on navigate in the route $caller function', + async ({ caller, testFn }) => { + const rootRoute = createRootRoute() + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + component: function Home() { + return ( +
+ link to about +
+ ) + }, + }) + const aboutRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/about', + beforeLoad: caller === 'beforeLoad' ? testFn : undefined, + loader: caller === 'loader' ? testFn : undefined, + component: function Home() { + return
About route content
+ }, + errorComponent: isUsingLazyError ? undefined : MyErrorComponent, + }) + + if (isUsingLazyError) { + aboutRoute.lazy(() => + Promise.resolve( + createLazyRoute('/about')({ + errorComponent: MyErrorComponent, + }), + ), + ) + } + + const routeTree = rootRoute.addChildren([indexRoute, aboutRoute]) + + const router = createRouter({ + routeTree, + defaultPreload: options.preload, + }) + + render(() => ) + + const linkToAbout = await screen.findByRole('link', { + name: 'link to about', + }) + + expect(linkToAbout).toBeInTheDocument() + fireEvent.mouseOver(linkToAbout) + fireEvent.focus(linkToAbout) + fireEvent.click(linkToAbout) + + const errorComponent = await screen.findByText( + `Error: error thrown`, + undefined, + { timeout: 1500 }, ) + await expect( + screen.findByText('About route content'), + ).rejects.toThrow() + expect(errorComponent).toBeInTheDocument() }, - }) - const aboutRoute = createRoute({ - getParentRoute: () => rootRoute, - path: '/about', - beforeLoad: caller === 'beforeLoad' ? testFn : undefined, - loader: caller === 'loader' ? testFn : undefined, - component: function Home() { - return
About route content
- }, - errorComponent: MyErrorComponent, - }) - - const routeTree = rootRoute.addChildren([indexRoute, aboutRoute]) - - const router = createRouter({ - routeTree, - defaultPreload: options.preload, - }) - - render(() => ) - - const linkToAbout = await screen.findByRole('link', { - name: 'link to about', - }) - - expect(linkToAbout).toBeInTheDocument() - fireEvent.mouseOver(linkToAbout) - fireEvent.focus(linkToAbout) - fireEvent.click(linkToAbout) - - const errorComponent = await screen.findByText( - `Error: error thrown`, - undefined, - { timeout: 1500 }, ) - await expect( - screen.findByText('About route content'), - ).rejects.toThrow() - expect(errorComponent).toBeInTheDocument() - }, - ) - - test.each(callers)( - 'an Error is thrown on first load in the route $caller function', - async ({ caller, testFn }) => { - const rootRoute = createRootRoute() - const indexRoute = createRoute({ - getParentRoute: () => rootRoute, - path: '/', - beforeLoad: caller === 'beforeLoad' ? testFn : undefined, - loader: caller === 'loader' ? testFn : undefined, - component: function Home() { - return
Index route content
- }, - errorComponent: MyErrorComponent, - }) - - const routeTree = rootRoute.addChildren([indexRoute]) - - const router = createRouter({ - routeTree, - defaultPreload: options.preload, - }) - render(() => ) - - const errorComponent = await screen.findByText( - `Error: error thrown`, - undefined, - { timeout: 750 }, + test.each(callers)( + 'an Error is thrown on first load in the route $caller function', + async ({ caller, testFn }) => { + const rootRoute = createRootRoute() + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + beforeLoad: caller === 'beforeLoad' ? testFn : undefined, + loader: caller === 'loader' ? testFn : undefined, + component: function Home() { + return
Index route content
+ }, + errorComponent: isUsingLazyError ? undefined : MyErrorComponent, + }) + + if (isUsingLazyError) { + indexRoute.lazy(() => + Promise.resolve( + createLazyRoute('/')({ + errorComponent: MyErrorComponent, + }), + ), + ) + } + + const routeTree = rootRoute.addChildren([indexRoute]) + + const router = createRouter({ + routeTree, + defaultPreload: options.preload, + }) + + render(() => ) + + const errorComponent = await screen.findByText( + `Error: error thrown`, + undefined, + { timeout: 750 }, + ) + await expect( + screen.findByText('Index route content'), + ).rejects.toThrow() + expect(errorComponent).toBeInTheDocument() + }, ) - await expect( - screen.findByText('Index route content'), - ).rejects.toThrow() - expect(errorComponent).toBeInTheDocument() - }, - ) - }) + }) + }, + ) }, )