From 24b00f1611fd67478138154af37f52c461a086ec Mon Sep 17 00:00:00 2001 From: Alban Bailly Date: Wed, 25 Sep 2024 10:00:38 -0400 Subject: [PATCH] testing + utils --- packages/manager/src/MainContentV2.tsx | 8 - packages/manager/src/routes/account.tsx | 41 +++--- packages/manager/src/routes/domains.tsx | 35 ++--- packages/manager/src/routes/images.tsx | 41 ++++++ packages/manager/src/routes/linodes.tsx | 30 ++-- packages/manager/src/routes/nodeBalancers.tsx | 51 +++---- .../manager/src/routes/placementGroups.tsx | 79 +++++----- packages/manager/src/routes/utils.test.tsx | 139 ++++++++++++++++++ packages/manager/src/routes/utils.ts | 53 +++++++ packages/manager/src/routes/volumes.tsx | 11 +- 10 files changed, 341 insertions(+), 147 deletions(-) create mode 100644 packages/manager/src/routes/images.tsx create mode 100644 packages/manager/src/routes/utils.test.tsx create mode 100644 packages/manager/src/routes/utils.ts diff --git a/packages/manager/src/MainContentV2.tsx b/packages/manager/src/MainContentV2.tsx index 185640b878a..bcfa46399b9 100644 --- a/packages/manager/src/MainContentV2.tsx +++ b/packages/manager/src/MainContentV2.tsx @@ -11,7 +11,6 @@ import { useAccountSettings } from './queries/account/settings'; import { router } from './routes'; import { rootRoute } from './routes/root'; -const Images = React.lazy(() => import('src/features/Images')); const Kubernetes = React.lazy(() => import('src/features/Kubernetes').then((module) => ({ default: module.Kubernetes, @@ -77,12 +76,6 @@ const longviewRoute = createRoute({ path: 'longview', }); -const imagesRoute = createRoute({ - component: Images, - getParentRoute: () => rootRoute, - path: 'images', -}); - const stackScriptsRoute = createRoute({ component: StackScripts, getParentRoute: () => rootRoute, @@ -186,7 +179,6 @@ const notFoundRoute = createRoute({ const routeTree = rootRoute.addChildren([ managedRoute, longviewRoute, - imagesRoute, stackScriptsRoute, objectStorageRoute, kubernetesRoute, diff --git a/packages/manager/src/routes/account.tsx b/packages/manager/src/routes/account.tsx index a31e23d6e82..507c2201b0d 100644 --- a/packages/manager/src/routes/account.tsx +++ b/packages/manager/src/routes/account.tsx @@ -1,14 +1,11 @@ -import { - Outlet, - createRoute, - lazyRouteComponent, -} from '@tanstack/react-router'; +import { Outlet, createRoute } from '@tanstack/react-router'; import React from 'react'; import { ProductInformationBanner } from 'src/components/ProductInformationBanner/ProductInformationBanner'; import { SuspenseLoader } from 'src/components/SuspenseLoader'; import { rootRoute } from './root'; +import { strictLazyRouteComponent } from './utils'; export const AccountRoutes = () => { return ( @@ -26,7 +23,7 @@ const accountRoute = createRoute({ }); const accountIndexRoute = createRoute({ - component: lazyRouteComponent( + component: strictLazyRouteComponent( () => import('src/features/Account/AccountLanding') ), getParentRoute: () => accountRoute, @@ -34,17 +31,16 @@ const accountIndexRoute = createRoute({ }); const accountUsersUsernameRoute = createRoute({ - component: lazyRouteComponent(() => - import('src/features/Users/UserDetail').then((module) => ({ - default: module.UserDetail, - })) + component: strictLazyRouteComponent( + () => import('src/features/Users/UserDetail'), + 'UserDetail' ), getParentRoute: () => accountRoute, path: 'users/$username', }); const accountBillingRoute = createRoute({ - component: lazyRouteComponent( + component: strictLazyRouteComponent( () => import('src/features/Account/AccountLanding') ), getParentRoute: () => accountRoute, @@ -52,7 +48,7 @@ const accountBillingRoute = createRoute({ }); const accountBillingEditRoute = createRoute({ - component: lazyRouteComponent( + component: strictLazyRouteComponent( () => import('src/features/Account/AccountLanding') ), getParentRoute: () => accountRoute, @@ -60,12 +56,9 @@ const accountBillingEditRoute = createRoute({ }); const accountInvoicesInvoiceIdRoute = createRoute({ - component: lazyRouteComponent(() => - import('src/features/Billing/InvoiceDetail/InvoiceDetail').then( - (module) => ({ - default: module.InvoiceDetail, - }) - ) + component: strictLazyRouteComponent( + () => import('src/features/Billing/InvoiceDetail/InvoiceDetail'), + 'InvoiceDetail' ), getParentRoute: () => accountRoute, parseParams: (params) => ({ @@ -75,12 +68,12 @@ const accountInvoicesInvoiceIdRoute = createRoute({ }); const accountEntityTransfersCreateRoute = createRoute({ - component: lazyRouteComponent(() => - import( - 'src/features/EntityTransfers/EntityTransfersCreate/EntityTransfersCreate' - ).then((module) => ({ - default: module.EntityTransfersCreate, - })) + component: strictLazyRouteComponent( + () => + import( + 'src/features/EntityTransfers/EntityTransfersCreate/EntityTransfersCreate' + ), + 'EntityTransfersCreate' ), getParentRoute: () => accountRoute, path: 'service-transfers/create', diff --git a/packages/manager/src/routes/domains.tsx b/packages/manager/src/routes/domains.tsx index 979a12dfe97..f67a729c899 100644 --- a/packages/manager/src/routes/domains.tsx +++ b/packages/manager/src/routes/domains.tsx @@ -1,14 +1,11 @@ -import { - Outlet, - createRoute, - lazyRouteComponent, -} from '@tanstack/react-router'; +import { Outlet, createRoute } from '@tanstack/react-router'; import React from 'react'; import { ProductInformationBanner } from 'src/components/ProductInformationBanner/ProductInformationBanner'; import { SuspenseLoader } from 'src/components/SuspenseLoader'; import { rootRoute } from './root'; +import { strictLazyRouteComponent } from './utils'; export const DomainsRoutes = () => { return ( @@ -26,30 +23,27 @@ const domainsRoute = createRoute({ }); const domainsIndexRoute = createRoute({ - component: lazyRouteComponent(() => - import('src/features/Domains/DomainsLanding').then((module) => ({ - default: module.DomainsLanding, - })) + component: strictLazyRouteComponent( + () => import('src/features/Domains/DomainsLanding'), + 'DomainsLanding' ), getParentRoute: () => domainsRoute, path: '/', }); const domainCreateRoute = createRoute({ - component: lazyRouteComponent(() => - import('src/features/Domains/CreateDomain/CreateDomain').then((module) => ({ - default: module.CreateDomain, - })) + component: strictLazyRouteComponent( + () => import('src/features/Domains/CreateDomain/CreateDomain'), + 'CreateDomain' ), getParentRoute: () => domainsRoute, path: 'create', }); const domainDetailRoute = createRoute({ - component: lazyRouteComponent(() => - import('src/features/Domains/DomainDetail').then((module) => ({ - default: module.DomainDetailRouting, - })) + component: strictLazyRouteComponent( + () => import('src/features/Domains/DomainDetail'), + 'DomainDetailRouting' ), getParentRoute: () => domainsRoute, parseParams: (params) => ({ @@ -59,10 +53,9 @@ const domainDetailRoute = createRoute({ }); const domainDetailRecordsRoute = createRoute({ - component: lazyRouteComponent(() => - import('src/features/Domains/DomainDetail').then((module) => ({ - default: module.DomainDetailRouting, - })) + component: strictLazyRouteComponent( + () => import('src/features/Domains/DomainDetail'), + 'DomainDetailRouting' ), getParentRoute: () => domainDetailRoute, path: 'records', diff --git a/packages/manager/src/routes/images.tsx b/packages/manager/src/routes/images.tsx new file mode 100644 index 00000000000..9dea962e15e --- /dev/null +++ b/packages/manager/src/routes/images.tsx @@ -0,0 +1,41 @@ +import { Outlet, createRoute } from '@tanstack/react-router'; +import React from 'react'; + +import { ProductInformationBanner } from 'src/components/ProductInformationBanner/ProductInformationBanner'; +import { SuspenseLoader } from 'src/components/SuspenseLoader'; + +import { rootRoute } from './root'; +import { strictLazyRouteComponent } from './utils'; + +export const ImagesRoutes = () => { + return ( + }> + + + + ); +}; + +export const imagesRoute = createRoute({ + component: ImagesRoutes, + getParentRoute: () => rootRoute, + path: 'images', +}); + +export const imagesIndexRoute = createRoute({ + component: strictLazyRouteComponent( + () => import('src/features/Images/ImagesLanding/ImagesLanding'), + 'ImagesLanding' + ), + getParentRoute: () => imagesRoute, + path: '/', +}); + +export const imagesCreateRoute = createRoute({ + component: strictLazyRouteComponent( + () => import('src/features/Images/ImagesCreate/ImageCreate'), + 'ImageCreate' + ), + getParentRoute: () => imagesRoute, + path: 'create', +}); diff --git a/packages/manager/src/routes/linodes.tsx b/packages/manager/src/routes/linodes.tsx index bf8e174c6c6..81d69969a5a 100644 --- a/packages/manager/src/routes/linodes.tsx +++ b/packages/manager/src/routes/linodes.tsx @@ -1,13 +1,10 @@ -import { - Outlet, - createRoute, - lazyRouteComponent, -} from '@tanstack/react-router'; +import { Outlet, createRoute } from '@tanstack/react-router'; import React from 'react'; import { SuspenseLoader } from 'src/components/SuspenseLoader'; import { rootRoute } from './root'; +import { strictLazyRouteComponent } from './utils'; export const LinodesRoutes = () => { return ( @@ -24,32 +21,27 @@ export const linodesRoute = createRoute({ }); const linodesIndexRoute = createRoute({ - component: lazyRouteComponent(() => - import('src/features/Linodes').then((module) => ({ - default: module.LinodesLandingWrapper, - })) + component: strictLazyRouteComponent( + () => import('src/features/Linodes'), + 'LinodesLandingWrapper' ), getParentRoute: () => linodesRoute, path: '/', }); const linodesCreateRoute = createRoute({ - component: lazyRouteComponent(() => - import('src/features/Linodes/LinodeCreatev2').then((module) => ({ - default: module.LinodeCreatev2, - })) + component: strictLazyRouteComponent( + () => import('src/features/Linodes/LinodeCreatev2'), + 'LinodeCreatev2' ), getParentRoute: () => linodesRoute, path: 'create', }); const linodesDetailRoute = createRoute({ - component: lazyRouteComponent(() => - import('src/features/Linodes/LinodesDetail/LinodesDetail').then( - (module) => ({ - default: module.LinodeDetail, - }) - ) + component: strictLazyRouteComponent( + () => import('src/features/Linodes/LinodesDetail/LinodesDetail'), + 'LinodeDetail' ), getParentRoute: () => linodesRoute, parseParams: (params) => ({ diff --git a/packages/manager/src/routes/nodeBalancers.tsx b/packages/manager/src/routes/nodeBalancers.tsx index 955462640e6..6ace1b86173 100644 --- a/packages/manager/src/routes/nodeBalancers.tsx +++ b/packages/manager/src/routes/nodeBalancers.tsx @@ -1,14 +1,11 @@ -import { - Outlet, - createRoute, - lazyRouteComponent, -} from '@tanstack/react-router'; +import { Outlet, createRoute } from '@tanstack/react-router'; import React from 'react'; import { ProductInformationBanner } from 'src/components/ProductInformationBanner/ProductInformationBanner'; import { SuspenseLoader } from 'src/components/SuspenseLoader'; import { rootRoute } from './root'; +import { strictLazyRouteComponent } from './utils'; export const NodeBalancersRoutes = () => { return ( @@ -26,7 +23,7 @@ const nodeBalancersRoute = createRoute({ }); const nodeBalancersIndexRoute = createRoute({ - component: lazyRouteComponent( + component: strictLazyRouteComponent( () => import( 'src/features/NodeBalancers/NodeBalancersLanding/NodeBalancersLanding' @@ -37,20 +34,20 @@ const nodeBalancersIndexRoute = createRoute({ }); const nodeBalancersCreateRoute = createRoute({ - component: lazyRouteComponent( - () => import('src/features/NodeBalancers/NodeBalancerCreate') + component: strictLazyRouteComponent( + () => import('src/features/NodeBalancers/NodeBalancerCreate'), ), getParentRoute: () => nodeBalancersRoute, path: 'create', }); const nodeBalancerDetailRoute = createRoute({ - component: lazyRouteComponent(() => - import( - 'src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerDetail' - ).then((module) => ({ - default: module.NodeBalancerDetail, - })) + component: strictLazyRouteComponent( + () => + import( + 'src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerDetail' + ), + 'NodeBalancerDetail' ), getParentRoute: () => nodeBalancersRoute, parseParams: (params) => ({ @@ -60,19 +57,19 @@ const nodeBalancerDetailRoute = createRoute({ }); const nodeBalancerDetailSummaryRoute = createRoute({ - component: lazyRouteComponent(() => - import( - 'src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerSummary/NodeBalancerSummary' - ).then((module) => ({ - default: module.NodeBalancerSummary, - })) + component: strictLazyRouteComponent( + () => + import( + 'src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerSummary/NodeBalancerSummary' + ), + 'NodeBalancerSummary' ), getParentRoute: () => nodeBalancerDetailRoute, path: 'summary', }); const nodeBalancerDetailConfigurationsRoute = createRoute({ - component: lazyRouteComponent( + component: strictLazyRouteComponent( () => import( 'src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerConfigurations' @@ -83,12 +80,12 @@ const nodeBalancerDetailConfigurationsRoute = createRoute({ }); const nodeBalancerDetailSettingsRoute = createRoute({ - component: lazyRouteComponent(() => - import( - 'src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerSettings' - ).then((module) => ({ - default: module.NodeBalancerSettings, - })) + component: strictLazyRouteComponent( + () => + import( + 'src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerSettings' + ), + 'NodeBalancerSettings' ), getParentRoute: () => nodeBalancerDetailRoute, path: 'settings', diff --git a/packages/manager/src/routes/placementGroups.tsx b/packages/manager/src/routes/placementGroups.tsx index 50cb7743836..2ec06b59fe7 100644 --- a/packages/manager/src/routes/placementGroups.tsx +++ b/packages/manager/src/routes/placementGroups.tsx @@ -1,8 +1,4 @@ -import { - Outlet, - createRoute, - lazyRouteComponent, -} from '@tanstack/react-router'; +import { Outlet, createRoute } from '@tanstack/react-router'; import React from 'react'; import { DocumentTitleSegment } from 'src/components/DocumentTitle'; @@ -10,6 +6,7 @@ import { ProductInformationBanner } from 'src/components/ProductInformationBanne import { SuspenseLoader } from 'src/components/SuspenseLoader'; import { rootRoute } from './root'; +import { strictLazyRouteComponent } from './utils'; export const AccountRoutes = () => { return ( @@ -28,36 +25,36 @@ export const placementGroupsRoute = createRoute({ }); const placementGroupsIndexRoute = createRoute({ - component: lazyRouteComponent(() => - import( - 'src/features/PlacementGroups/PlacementGroupsLanding/PlacementGroupsLanding' - ).then((module) => ({ - default: module.PlacementGroupsLanding, - })) + component: strictLazyRouteComponent( + () => + import( + 'src/features/PlacementGroups/PlacementGroupsLanding/PlacementGroupsLanding' + ), + 'PlacementGroupsLanding' ), getParentRoute: () => placementGroupsRoute, path: '/', }); const placementGroupsCreateRoute = createRoute({ - component: lazyRouteComponent(() => - import( - 'src/features/PlacementGroups/PlacementGroupsLanding/PlacementGroupsLanding' - ).then((module) => ({ - default: module.PlacementGroupsLanding, - })) + component: strictLazyRouteComponent( + () => + import( + 'src/features/PlacementGroups/PlacementGroupsLanding/PlacementGroupsLanding' + ), + 'PlacementGroupsLanding' ), getParentRoute: () => placementGroupsRoute, path: 'create', }); const placementGroupsEditRoute = createRoute({ - component: lazyRouteComponent(() => - import( - 'src/features/PlacementGroups/PlacementGroupsLanding/PlacementGroupsLanding' - ).then((module) => ({ - default: module.PlacementGroupsLanding, - })) + component: strictLazyRouteComponent( + () => + import( + 'src/features/PlacementGroups/PlacementGroupsLanding/PlacementGroupsLanding' + ), + 'PlacementGroupsLanding' ), getParentRoute: () => placementGroupsRoute, parseParams: (params) => ({ @@ -67,12 +64,12 @@ const placementGroupsEditRoute = createRoute({ }); const placementGroupsDeleteRoute = createRoute({ - component: lazyRouteComponent(() => - import( - 'src/features/PlacementGroups/PlacementGroupsLanding/PlacementGroupsLanding' - ).then((module) => ({ - default: module.PlacementGroupsLanding, - })) + component: strictLazyRouteComponent( + () => + import( + 'src/features/PlacementGroups/PlacementGroupsLanding/PlacementGroupsLanding' + ), + 'PlacementGroupsLanding' ), getParentRoute: () => placementGroupsRoute, parseParams: (params) => ({ @@ -82,12 +79,12 @@ const placementGroupsDeleteRoute = createRoute({ }); const placementGroupsUnassignRoute = createRoute({ - component: lazyRouteComponent(() => - import( - 'src/features/PlacementGroups/PlacementGroupsDetail/PlacementGroupsDetail' - ).then((module) => ({ - default: module.PlacementGroupsDetail, - })) + component: strictLazyRouteComponent( + () => + import( + 'src/features/PlacementGroups/PlacementGroupsDetail/PlacementGroupsDetail' + ), + 'PlacementGroupsDetail' ), getParentRoute: () => placementGroupsRoute, parseParams: (params) => ({ @@ -98,12 +95,12 @@ const placementGroupsUnassignRoute = createRoute({ }); const placementGroupsDetailRoute = createRoute({ - component: lazyRouteComponent(() => - import( - 'src/features/PlacementGroups/PlacementGroupsDetail/PlacementGroupsDetail' - ).then((module) => ({ - default: module.PlacementGroupsDetail, - })) + component: strictLazyRouteComponent( + () => + import( + 'src/features/PlacementGroups/PlacementGroupsDetail/PlacementGroupsDetail' + ), + 'PlacementGroupsDetail' ), getParentRoute: () => placementGroupsRoute, parseParams: (params) => ({ diff --git a/packages/manager/src/routes/utils.test.tsx b/packages/manager/src/routes/utils.test.tsx new file mode 100644 index 00000000000..27f16d1a16c --- /dev/null +++ b/packages/manager/src/routes/utils.test.tsx @@ -0,0 +1,139 @@ +import { lazyRouteComponent } from '@tanstack/react-router'; +import React from 'react'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { strictLazyRouteComponent } from './utils'; + +vi.mock('@tanstack/react-router', () => ({ + lazyRouteComponent: vi.fn((loader) => loader), +})); + +describe('strictLazyRouteComponent', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should handle default exports correctly', async () => { + const DefaultComponent = () =>
Default Component
; + const mockImporter = vi.fn(() => + Promise.resolve({ default: DefaultComponent }) + ); + + const result = strictLazyRouteComponent(mockImporter); + expect(lazyRouteComponent).toHaveBeenCalledWith(expect.any(Function)); + + const loadedComponent = await (result as any)(); + expect(loadedComponent).toEqual({ default: DefaultComponent }); + }); + + it('should handle named exports correctly', async () => { + const NamedComponent = () =>
Named Component
; + const mockImporter = vi.fn(() => Promise.resolve({ NamedComponent })); + + const result = strictLazyRouteComponent(mockImporter, 'NamedComponent'); + expect(lazyRouteComponent).toHaveBeenCalledWith(expect.any(Function)); + + const loadedComponent = await (result as any)(); + expect(loadedComponent).toEqual({ default: NamedComponent }); + }); + + it('should throw an error for non-existent named exports', async () => { + const mockImporter = vi.fn(() => + Promise.resolve({ SomeOtherComponent: () => null }) + ); + + const result = strictLazyRouteComponent( + mockImporter, + // @ts-expect-error - forcing the wrong type + 'NonExistentComponent' + ); + await expect((result as any)()).rejects.toThrow( + 'Export "NonExistentComponent" not found in module' + ); + }); + + it('should throw an error for modules without default export when no name is provided', async () => { + const mockImporter = vi.fn(() => + Promise.resolve({ SomeComponent: () => null }) + ); + + // @ts-expect-error - forcing the wrong type + const result = strictLazyRouteComponent(mockImporter); + await expect((result as any)()).rejects.toThrow( + 'No default export found in module' + ); + }); + + it('should handle React.forwardRef components', async () => { + const ForwardRefComponent = React.forwardRef(() => ( +
ForwardRef Component
+ )); + const mockImporter = vi.fn(() => + Promise.resolve({ default: ForwardRefComponent }) + ); + + const result = strictLazyRouteComponent(mockImporter); + const loadedComponent = await (result as any)(); + expect(loadedComponent).toEqual({ default: ForwardRefComponent }); + }); + + it('should handle React.memo components', async () => { + const MemoComponent = React.memo(() =>
Memo Component
); + const mockImporter = vi.fn(() => Promise.resolve({ MemoComponent })); + + const result = strictLazyRouteComponent(mockImporter, 'MemoComponent'); + const loadedComponent = await (result as any)(); + expect(loadedComponent).toEqual({ default: MemoComponent }); + }); + + it('should throw an error for non-component exports', async () => { + const mockImporter = vi.fn(() => + Promise.resolve({ notAComponent: 'string' }) + ); + + const result = strictLazyRouteComponent(mockImporter, 'notAComponent'); + await expect((result as any)()).rejects.toThrow( + 'Export "notAComponent" is not a valid React component' + ); + }); + + it('should handle async imports correctly', async () => { + const AsyncComponent = () =>
Async Component
; + const mockImporter = vi.fn(() => Promise.resolve({ AsyncComponent })); + + const result = strictLazyRouteComponent(mockImporter, 'AsyncComponent'); + expect(lazyRouteComponent).toHaveBeenCalledWith(expect.any(Function)); + + const loadedComponent = await (result as any)(); + expect(loadedComponent).toEqual({ default: AsyncComponent }); + expect(mockImporter).toHaveBeenCalledTimes(1); + }); + + it('should work with TypeScript generics', async () => { + interface Props { + name: string; + } + const GenericComponent: React.FC = ({ name }) => ( +
Hello, {name}
+ ); + const mockImporter = vi.fn(() => Promise.resolve({ GenericComponent })); + + const result = strictLazyRouteComponent(mockImporter, 'GenericComponent'); + const loadedComponent = await (result as any)(); + expect(loadedComponent).toEqual({ default: GenericComponent }); + }); + + it('should throw a type error when no export name is provided for named exports', () => { + const mockImporter = () => Promise.resolve({ NamedComponent: () => null }); + + // @ts-expect-error - forcing the wrong type + strictLazyRouteComponent(mockImporter); + }); + + it('should not throw a type error when no export name is provided for default exports', () => { + const mockImporter = () => Promise.resolve({ default: () => null }); + + // This should not throw a TypeScript error + strictLazyRouteComponent(mockImporter); + }); +}); diff --git a/packages/manager/src/routes/utils.ts b/packages/manager/src/routes/utils.ts new file mode 100644 index 00000000000..8441670b8e3 --- /dev/null +++ b/packages/manager/src/routes/utils.ts @@ -0,0 +1,53 @@ +import { lazyRouteComponent } from '@tanstack/react-router'; + +import type { ComponentType } from 'react'; + +type AnyModule = { [key: string]: any }; + +/** + * This function is a wrapper around lazyRouteComponent that ensures the + * component is a valid React component. + * + * lazyRouteComponent does not provide the type safety we need to ensure handle both default and named exports. + * By using a function overload we do just that. + */ +export function strictLazyRouteComponent< + T extends { default: ComponentType } +>(importer: () => Promise): ReturnType; +export function strictLazyRouteComponent< + T extends AnyModule, + K extends keyof T +>( + importer: () => Promise, + exportName: K +): ReturnType; +export function strictLazyRouteComponent( + importer: () => Promise, + exportName?: string +): ReturnType { + return lazyRouteComponent(() => + importer().then((module) => { + let component: ComponentType; + + if (exportName) { + if (exportName in module) { + component = module[exportName] as ComponentType; + } else { + throw new Error(`Export "${exportName}" not found in module`); + } + } else if ('default' in module) { + component = module.default; + } else { + throw new Error('No default export found in module'); + } + + if (typeof component !== 'function' && typeof component !== 'object') { + throw new Error( + `Export "${exportName || 'default'}" is not a valid React component` + ); + } + + return { default: component }; + }) + ); +} diff --git a/packages/manager/src/routes/volumes.tsx b/packages/manager/src/routes/volumes.tsx index 7365522c275..8c1a8922162 100644 --- a/packages/manager/src/routes/volumes.tsx +++ b/packages/manager/src/routes/volumes.tsx @@ -1,14 +1,11 @@ -import { - Outlet, - createRoute, - lazyRouteComponent, -} from '@tanstack/react-router'; +import { Outlet, createRoute } from '@tanstack/react-router'; import React from 'react'; import { ProductInformationBanner } from 'src/components/ProductInformationBanner/ProductInformationBanner'; import { SuspenseLoader } from 'src/components/SuspenseLoader'; import { rootRoute } from './root'; +import { strictLazyRouteComponent } from './utils'; export const VolumesRoutes = () => { return ( @@ -26,7 +23,7 @@ export const volumesRoute = createRoute({ }); export const volumesIndexRoute = createRoute({ - component: lazyRouteComponent(() => + component: strictLazyRouteComponent(() => import('src/features/Volumes/VolumesLanding').then((module) => ({ default: module.VolumesLanding, })) @@ -36,7 +33,7 @@ export const volumesIndexRoute = createRoute({ }); export const volumesCreateRoute = createRoute({ - component: lazyRouteComponent(() => + component: strictLazyRouteComponent(() => import('src/features/Volumes/VolumeCreate').then((module) => ({ default: module.VolumeCreate, }))