Wrap test element in Routes when mountedRoutes are provided

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 <Route> matching for param extraction.

Wrap the test element in <Routes> with a <Route> 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) <noreply@anthropic.com>
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
Fredrik Adelöw
2026-05-20 09:42:47 +02:00
parent a58bcdcb47
commit 95e107bc6e
2 changed files with 55 additions and 11 deletions
@@ -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<const TApiPairs extends any[] = any[]>(
element: JSX.Element,
options?: TestAppOptions<TApiPairs>,
): RenderResult {
const mountedPaths = options?.mountedRoutes
? Object.keys(options.mountedRoutes)
: [];
const extensions: Array<ExtensionDefinition> = [
createExtension({
attachTo: { id: 'app/root', input: 'children' },
output: [coreExtensionData.reactElement],
factory: () => {
if (mountedPaths.length > 0) {
return [
coreExtensionData.reactElement(
<Routes>
{mountedPaths.map(path => (
<Route
key={path}
path={path === '/' ? path : `${path.replace(/\/$/, '')}/*`}
element={element}
/>
))}
<Route path="*" element={element} />
</Routes>,
),
];
}
return [coreExtensionData.reactElement(element)];
},
}),
+34 -10
View File
@@ -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'));