diff --git a/.changeset/fix-test-route-matching.md b/.changeset/fix-test-route-matching.md index 4f69688f86..359fe19164 100644 --- a/.changeset/fix-test-route-matching.md +++ b/.changeset/fix-test-route-matching.md @@ -2,4 +2,4 @@ '@backstage/frontend-test-utils': patch --- -Fixed `renderInTestApp` to set up React Router route matching when `mountedRoutes` are provided. Previously, mounted routes only fed the route resolution API for link generation (`useRouteRef`), but did not create `` elements for param extraction (`useParams`). This meant that `initialRouteEntries` had no effect on route param availability. The test element is now wrapped in `` with a `` for each mounted path, matching the behavior of the real `AppRoutes` extension. +Added a `mountPath` option to `renderInTestApp` that controls the route path pattern the test element is rendered at. When set, the element is wrapped in a `` with the given path, enabling `useParams()` to extract route parameters from the URL. When `mountPath` is set and `initialRouteEntries` is not, the initial route entry defaults to the mount path. This is useful for testing page components that depend on URL parameters, such as entity pages that use `useRouteRefParams`. diff --git a/packages/frontend-test-utils/src/app/renderInTestApp.tsx b/packages/frontend-test-utils/src/app/renderInTestApp.tsx index 07dccd9eee..1344fc612e 100644 --- a/packages/frontend-test-utils/src/app/renderInTestApp.tsx +++ b/packages/frontend-test-utils/src/app/renderInTestApp.tsx @@ -80,7 +80,28 @@ export type TestAppOptions = { features?: FrontendFeature[]; /** - * Initial route entries to use for the router. + * The route path pattern that the test element is rendered at. When set, + * the element is wrapped in a `` with this path, enabling + * `useParams()` to extract parameters from the URL. Defaults to `'/'`. + * + * When `mountPath` is set and `initialRouteEntries` is not, the + * initial route entry defaults to `mountPath` (which works for paths + * without parameters). For parameterized paths you must also set + * `initialRouteEntries` to a concrete URL that matches the pattern. + * + * @example + * ```ts + * renderInTestApp(, { + * mountPath: '/catalog/:namespace/:kind/:name', + * initialRouteEntries: ['/catalog/default/component/my-entity'], + * }) + * ``` + */ + mountPath?: string; + + /** + * Initial route entries to use for the router. When `mountPath` is set + * and this is not, defaults to `[mountPath]`. */ initialRouteEntries?: string[]; @@ -125,9 +146,7 @@ export function renderInTestApp( element: JSX.Element, options?: TestAppOptions, ): RenderResult { - const mountedPaths = options?.mountedRoutes - ? Object.keys(options.mountedRoutes) - : []; + const mountPath = options?.mountPath; const extensions: Array = [ createExtension({ @@ -136,16 +155,12 @@ export function renderInTestApp( factory: () => { let content: JSX.Element = element; - if (mountedPaths.length > 0) { + if (mountPath) { + const routePath = + mountPath === '/' ? mountPath : `${mountPath.replace(/\/$/, '')}/*`; content = ( - {mountedPaths.map(path => ( - - ))} + ); @@ -198,7 +213,10 @@ export function renderInTestApp( params: { component: ({ children }) => ( { await renderInTestApp(tester.reactElement(), { apis: [mockCatalogApi, [starredEntitiesApiRef, mockStarredEntitiesApi]], + mountPath: '/catalog/:namespace/:kind/:name', initialRouteEntries: ['/catalog/default/component/artist-lookup'], config: { app: { @@ -201,6 +202,7 @@ describe('Entity page', () => { await renderInTestApp(tester.reactElement(), { apis: [mockCatalogApi, [starredEntitiesApiRef, mockStarredEntitiesApi]], + mountPath: '/catalog/:namespace/:kind/:name', initialRouteEntries: ['/catalog/default/component/artist-lookup'], config: { app: { @@ -245,6 +247,7 @@ describe('Entity page', () => { await renderInTestApp(tester.reactElement(), { apis: [mockCatalogApi, [starredEntitiesApiRef, mockStarredEntitiesApi]], + mountPath: '/catalog/:namespace/:kind/:name', initialRouteEntries: ['/catalog/default/component/artist-lookup'], config: { app: { @@ -296,6 +299,7 @@ describe('Entity page', () => { await renderInTestApp(tester.reactElement(), { apis: [mockCatalogApi, [starredEntitiesApiRef, mockStarredEntitiesApi]], + mountPath: '/catalog/:namespace/:kind/:name', initialRouteEntries: ['/catalog/default/component/artist-lookup'], config: { app: { @@ -341,6 +345,7 @@ describe('Entity page', () => { await renderInTestApp(tester.reactElement(), { apis: [mockCatalogApi, [starredEntitiesApiRef, mockStarredEntitiesApi]], + mountPath: '/catalog/:namespace/:kind/:name', initialRouteEntries: ['/catalog/default/component/artist-lookup'], config: { app: { @@ -373,6 +378,7 @@ describe('Entity page', () => { await renderInTestApp(tester.reactElement(), { apis: [mockCatalogApi, [starredEntitiesApiRef, mockStarredEntitiesApi]], + mountPath: '/catalog/:namespace/:kind/:name', initialRouteEntries: ['/catalog/default/component/artist-lookup'], config: { app: { @@ -417,6 +423,7 @@ describe('Entity page', () => { await renderInTestApp(tester.reactElement(), { apis: [mockCatalogApi, [starredEntitiesApiRef, mockStarredEntitiesApi]], + mountPath: '/catalog/:namespace/:kind/:name', initialRouteEntries: ['/catalog/default/component/artist-lookup'], config: { app: { @@ -457,6 +464,7 @@ describe('Entity page', () => { await renderInTestApp(tester.reactElement(), { apis: [mockCatalogApi, [starredEntitiesApiRef, mockStarredEntitiesApi]], + mountPath: '/catalog/:namespace/:kind/:name', initialRouteEntries: ['/catalog/default/component/artist-lookup'], config: { app: { @@ -496,6 +504,7 @@ describe('Entity page', () => { await renderInTestApp(tester.reactElement(), { apis: [mockCatalogApi, [starredEntitiesApiRef, mockStarredEntitiesApi]], + mountPath: '/catalog/:namespace/:kind/:name', initialRouteEntries: ['/catalog/default/component/artist-lookup'], config: { app: { @@ -543,6 +552,7 @@ describe('Entity page', () => { await renderInTestApp(tester.reactElement(), { apis: [mockCatalogApi, [starredEntitiesApiRef, mockStarredEntitiesApi]], + mountPath: '/catalog/:namespace/:kind/:name', initialRouteEntries: ['/catalog/default/component/artist-lookup'], config: { app: { @@ -590,6 +600,7 @@ describe('Entity page', () => { await renderInTestApp(tester.reactElement(), { apis: [mockCatalogApi, [starredEntitiesApiRef, mockStarredEntitiesApi]], + mountPath: '/catalog/:namespace/:kind/:name', initialRouteEntries: ['/catalog/default/component/artist-lookup'], config: { app: { @@ -625,6 +636,7 @@ describe('Entity page', () => { await renderInTestApp(tester.reactElement(), { apis: [mockCatalogApi, [starredEntitiesApiRef, mockStarredEntitiesApi]], + mountPath: '/catalog/:namespace/:kind/:name', initialRouteEntries: ['/catalog/default/component/artist-lookup'], config: { app: { @@ -662,6 +674,7 @@ describe('Entity page', () => { await renderInTestApp(tester.reactElement(), { apis: [mockCatalogApi, [starredEntitiesApiRef, mockStarredEntitiesApi]], + mountPath: '/catalog/:namespace/:kind/:name', initialRouteEntries: ['/catalog/default/component/artist-lookup'], config: { app: { @@ -719,6 +732,7 @@ describe('Entity page', () => { await renderInTestApp(tester.reactElement(), { apis: [mockCatalogApi, [starredEntitiesApiRef, mockStarredEntitiesApi]], + mountPath: '/catalog/:namespace/:kind/:name', initialRouteEntries: ['/catalog/default/component/artist-lookup'], config: { app: { @@ -772,6 +786,7 @@ describe('Entity page', () => { await renderInTestApp(tester.reactElement(), { apis: [mockCatalogApi, [starredEntitiesApiRef, mockStarredEntitiesApi]], + mountPath: '/catalog/:namespace/:kind/:name', initialRouteEntries: ['/catalog/default/component/artist-lookup'], config: { app: { @@ -861,6 +876,7 @@ describe('Entity page', () => { .add(filteredMenuItem); await renderInTestApp(tester.reactElement(), { + mountPath: '/catalog/:namespace/:kind/:name', initialRouteEntries: ['/catalog/default/component/artist-lookup'], config: { app: {