Replace mountedRoutes-based wrapping with mountPath option
Address review feedback: mountedRoutes are for abstract route targets the test subject links to, not for the subject itself. Add a dedicated mountPath option that wraps the test element in a single Route with the given path pattern, enabling useParams(). When mountPath is set and initialRouteEntries is not, the initial entry defaults to mountPath. 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:
@@ -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 `<Route>` elements for param extraction (`useParams`). This meant that `initialRouteEntries` had no effect on route param availability. The test element is now wrapped in `<Routes>` with a `<Route>` 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 `<Route>` 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`.
|
||||
|
||||
@@ -80,7 +80,28 @@ export type TestAppOptions<TApiPairs extends any[] = any[]> = {
|
||||
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 `<Route>` 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(<EntityPage />, {
|
||||
* 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<const TApiPairs extends any[] = any[]>(
|
||||
element: JSX.Element,
|
||||
options?: TestAppOptions<TApiPairs>,
|
||||
): RenderResult {
|
||||
const mountedPaths = options?.mountedRoutes
|
||||
? Object.keys(options.mountedRoutes)
|
||||
: [];
|
||||
const mountPath = options?.mountPath;
|
||||
|
||||
const extensions: Array<ExtensionDefinition> = [
|
||||
createExtension({
|
||||
@@ -136,16 +155,12 @@ export function renderInTestApp<const TApiPairs extends any[] = any[]>(
|
||||
factory: () => {
|
||||
let content: JSX.Element = element;
|
||||
|
||||
if (mountedPaths.length > 0) {
|
||||
if (mountPath) {
|
||||
const routePath =
|
||||
mountPath === '/' ? mountPath : `${mountPath.replace(/\/$/, '')}/*`;
|
||||
content = (
|
||||
<Routes>
|
||||
{mountedPaths.map(path => (
|
||||
<Route
|
||||
key={path}
|
||||
path={path === '/' ? path : `${path.replace(/\/$/, '')}/*`}
|
||||
element={content}
|
||||
/>
|
||||
))}
|
||||
<Route path={routePath} element={content} />
|
||||
<Route path="*" element={content} />
|
||||
</Routes>
|
||||
);
|
||||
@@ -198,7 +213,10 @@ export function renderInTestApp<const TApiPairs extends any[] = any[]>(
|
||||
params: {
|
||||
component: ({ children }) => (
|
||||
<MemoryRouter
|
||||
initialEntries={options?.initialRouteEntries}
|
||||
initialEntries={
|
||||
options?.initialRouteEntries ??
|
||||
(mountPath ? [mountPath] : undefined)
|
||||
}
|
||||
future={{
|
||||
v7_relativeSplatPath: false,
|
||||
v7_startTransition: false,
|
||||
|
||||
@@ -150,6 +150,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: {
|
||||
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user