From 95e107bc6ee58767c814fd2f812747a640e79b7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 20 May 2026 09:42:47 +0200 Subject: [PATCH] Wrap test element in Routes when mountedRoutes are provided MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit renderInTestApp disables app/routes, so the test element was rendered without any Route matching. This meant useParams() always returned {} even when initialRouteEntries and mountedRoutes were set — the mounted routes only fed the route resolution API for link generation, never created actual React Router matching for param extraction. Wrap the test element in with a for each mounted path (plus a path="*" fallback), mirroring what the real AppRoutes extension does via useRoutes(). This makes useParams() return real params when initialRouteEntries matches a mounted route path. Update entity page test href expectations to match the now-correct production behavior where sub-route links resolve relative to the matched parent Route, and replace synchronous getByTestId/getByText with async findBy queries for lazy-loaded content. Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Fredrik Adelöw --- .../src/app/renderInTestApp.tsx | 22 +++++++++- plugins/catalog/src/alpha/pages.test.tsx | 44 ++++++++++++++----- 2 files changed, 55 insertions(+), 11 deletions(-) diff --git a/packages/frontend-test-utils/src/app/renderInTestApp.tsx b/packages/frontend-test-utils/src/app/renderInTestApp.tsx index d599bdca01..e45bd9a8ca 100644 --- a/packages/frontend-test-utils/src/app/renderInTestApp.tsx +++ b/packages/frontend-test-utils/src/app/renderInTestApp.tsx @@ -15,7 +15,7 @@ */ import { Fragment } from 'react'; -import { MemoryRouter } from 'react-router-dom'; +import { MemoryRouter, Route, Routes } from 'react-router-dom'; import { prepareSpecializedApp } from '@backstage/frontend-app-api'; import { RenderResult, render } from '@testing-library/react'; import { ConfigReader } from '@backstage/config'; @@ -125,11 +125,31 @@ export function renderInTestApp( element: JSX.Element, options?: TestAppOptions, ): RenderResult { + const mountedPaths = options?.mountedRoutes + ? Object.keys(options.mountedRoutes) + : []; + const extensions: Array = [ createExtension({ attachTo: { id: 'app/root', input: 'children' }, output: [coreExtensionData.reactElement], factory: () => { + if (mountedPaths.length > 0) { + return [ + coreExtensionData.reactElement( + + {mountedPaths.map(path => ( + + ))} + + , + ), + ]; + } return [coreExtensionData.reactElement(element)]; }, }), diff --git a/plugins/catalog/src/alpha/pages.test.tsx b/plugins/catalog/src/alpha/pages.test.tsx index 3cd14cb510..6ed586abd0 100644 --- a/plugins/catalog/src/alpha/pages.test.tsx +++ b/plugins/catalog/src/alpha/pages.test.tsx @@ -176,11 +176,17 @@ describe('Entity page', () => { await expect( screen.findByRole('button', { name: /TechDocs/ }), - ).resolves.toHaveAttribute('href', '/techdocs'); + ).resolves.toHaveAttribute( + 'href', + '/catalog/default/component/artist-lookup/techdocs', + ); await expect( screen.findByRole('button', { name: /ApiDocs/ }), - ).resolves.toHaveAttribute('href', '/apidocs'); + ).resolves.toHaveAttribute( + 'href', + '/catalog/default/component/artist-lookup/apidocs', + ); }); it('Should rename a default group', async () => { @@ -222,11 +228,17 @@ describe('Entity page', () => { await expect( screen.findByRole('button', { name: /TechDocs/ }), - ).resolves.toHaveAttribute('href', '/techdocs'); + ).resolves.toHaveAttribute( + 'href', + '/catalog/default/component/artist-lookup/techdocs', + ); await expect( screen.findByRole('button', { name: /ApiDocs/ }), - ).resolves.toHaveAttribute('href', '/apidocs'); + ).resolves.toHaveAttribute( + 'href', + '/catalog/default/component/artist-lookup/apidocs', + ); }); it('Should disassociate a content with a default group', async () => { @@ -317,11 +329,17 @@ describe('Entity page', () => { await expect( screen.findByRole('button', { name: /TechDocs/ }), - ).resolves.toHaveAttribute('href', '/techdocs'); + ).resolves.toHaveAttribute( + 'href', + '/catalog/default/component/artist-lookup/techdocs', + ); await expect( screen.findByRole('button', { name: /ApiDocs/ }), - ).resolves.toHaveAttribute('href', '/apidocs'); + ).resolves.toHaveAttribute( + 'href', + '/catalog/default/component/artist-lookup/apidocs', + ); }); it('Should render a single-content groups as a normal tab', async () => { @@ -441,11 +459,17 @@ describe('Entity page', () => { await expect( screen.findByRole('button', { name: /TechDocs/ }), - ).resolves.toHaveAttribute('href', '/techdocs'); + ).resolves.toHaveAttribute( + 'href', + '/catalog/default/component/artist-lookup/techdocs', + ); await expect( screen.findByRole('button', { name: /ApiDocs/ }), - ).resolves.toHaveAttribute('href', '/apidocs'); + ).resolves.toHaveAttribute( + 'href', + '/catalog/default/component/artist-lookup/apidocs', + ); }); it('Should sort content by title by default', async () => { @@ -816,14 +840,14 @@ describe('Entity page', () => { screen.findByText(/artist-lookup/), ).resolves.toBeInTheDocument(); - await userEvent.click(screen.getByTestId('menu-button')); + await userEvent.click(await screen.findByTestId('menu-button')); await expect( screen.findByText('Test Title'), ).resolves.toBeInTheDocument(); await expect(screen.findByText('Test Icon')).resolves.toBeInTheDocument(); - const listItem = screen.getByText('Test Title').closest('li'); + const listItem = (await screen.findByText('Test Title')).closest('li'); expect(listItem).toHaveAttribute('aria-disabled', disabled.toString()); if (!disabled) { await userEvent.click(screen.getByText('Test Title'));