diff --git a/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.test.tsx
index f97798a64c..61b23e56f9 100644
--- a/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.test.tsx
+++ b/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.test.tsx
@@ -16,9 +16,11 @@
import React from 'react';
import { SignInPageBlueprint } from './SignInPageBlueprint';
-import { createExtensionTester } from '@backstage/frontend-test-utils';
+import {
+ createExtensionTester,
+ renderInTestApp,
+} from '@backstage/frontend-test-utils';
import { screen, waitFor } from '@testing-library/react';
-import { coreExtensionData, createExtension } from '../wiring';
describe('SignInPageBlueprint', () => {
it('should create an extension with sensible defaults', () => {
@@ -60,20 +62,11 @@ describe('SignInPageBlueprint', () => {
const tester = createExtensionTester(extension);
- expect(tester.data(SignInPageBlueprint.dataRefs.component)).toBeDefined();
+ const Element = tester.data(SignInPageBlueprint.dataRefs.component)!;
- createExtensionTester(
- createExtension({
- name: 'dummy',
- attachTo: { id: 'ignored', input: 'ignored' },
- output: {
- element: coreExtensionData.reactElement,
- },
- factory: () => ({ element:
}),
- }),
- )
- .add(extension)
- .render();
+ expect(Element).toBeDefined();
+
+ renderInTestApp( {}} />);
await waitFor(() => {
expect(screen.getByTestId('mock-sign-in')).toBeInTheDocument();
diff --git a/packages/frontend-test-utils/src/app/createExtensionTester.tsx b/packages/frontend-test-utils/src/app/createExtensionTester.tsx
index 4bfc8392cb..2d842062fc 100644
--- a/packages/frontend-test-utils/src/app/createExtensionTester.tsx
+++ b/packages/frontend-test-utils/src/app/createExtensionTester.tsx
@@ -49,57 +49,6 @@ import { instantiateAppNodeTree } from '../../../frontend-app-api/src/tree/insta
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { readAppExtensionsConfig } from '../../../frontend-app-api/src/tree/readAppExtensionsConfig';
-const NavItem = (props: {
- routeRef: RouteRef;
- title: string;
- icon: IconComponent;
-}) => {
- const { routeRef, title, icon: Icon } = props;
- const link = useRouteRef(routeRef);
- if (!link) {
- return null;
- }
- return (
-
-
- {title}
-
-
- );
-};
-
-const TestAppNavExtension = createExtension({
- namespace: 'app',
- name: 'nav',
- attachTo: { id: 'app/layout', input: 'nav' },
- inputs: {
- items: createExtensionInput({
- target: createNavItemExtension.targetDataRef,
- }),
- },
- output: {
- element: coreExtensionData.reactElement,
- },
- factory({ inputs }) {
- return {
- element: (
-
- ),
- };
- },
-});
-
/** @public */
export class ExtensionQuery {
#node: AppNode;
@@ -245,6 +194,25 @@ export class ExtensionTester {
return new ExtensionQuery(node);
}
+ element(): JSX.Element {
+ const tree = this.#resolveTree();
+
+ const element = new ExtensionQuery(tree.root).data(
+ coreExtensionData.reactElement,
+ );
+
+ if (!element) {
+ throw new Error(
+ 'No element found. Make sure the extension has a `coreExtensionData.reactElement` output, or use the `.get(myComponentDataRef)` method to get the component',
+ );
+ }
+
+ return element;
+ }
+
+ /**
+ * @deprecated Switch to using `renderInTestApp` directly and using `.element()` or `.get(myComponentDataRef)` to get the component you would like to wrap up
+ */
render(options?: { config?: JsonObject }): RenderResult {
const { config = {} } = options ?? {};
@@ -260,7 +228,6 @@ export class ExtensionTester {
createExtensionOverrides({
extensions: [
...this.#extensions.map(extension => extension.definition),
- TestAppNavExtension,
createRouterExtension({
namespace: 'test',
Component: ({ children }) => (
diff --git a/packages/frontend-test-utils/src/app/renderInTestApp.tsx b/packages/frontend-test-utils/src/app/renderInTestApp.tsx
index e0483f68d8..d828c89f12 100644
--- a/packages/frontend-test-utils/src/app/renderInTestApp.tsx
+++ b/packages/frontend-test-utils/src/app/renderInTestApp.tsx
@@ -16,11 +16,24 @@
import React from 'react';
import {
+ ExtensionDefinition,
+ IconComponent,
RouteRef,
coreExtensionData,
createExtension,
+ createExtensionInput,
+ createExtensionOverrides,
+ createNavItemExtension,
+ createRouterExtension,
+ useRouteRef,
} from '@backstage/frontend-plugin-api';
-import { createExtensionTester } from './createExtensionTester';
+import { Link, MemoryRouter } from 'react-router-dom';
+import { createSpecializedApp } from '@backstage/frontend-app-api';
+import { render } from '@testing-library/react';
+import { resolveExtensionDefinition } from '@backstage/frontend-plugin-api/src/wiring/resolveExtensionDefinition';
+import { resolve } from 'path';
+import { ConfigReader } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
/**
* Options to customize the behavior of the test app.
@@ -44,8 +57,64 @@ export type TestAppOptions = {
* ```
*/
mountedRoutes?: { [path: string]: RouteRef };
+
+ /**
+ * Additional configuration passed to the app when rendering elements inside it.
+ */
+ config?: JsonObject;
};
+const NavItem = (props: {
+ routeRef: RouteRef;
+ title: string;
+ icon: IconComponent;
+}) => {
+ const { routeRef, title, icon: Icon } = props;
+ const link = useRouteRef(routeRef);
+ if (!link) {
+ return null;
+ }
+ return (
+
+
+ {title}
+
+
+ );
+};
+
+const TestAppNavExtension = createExtension({
+ namespace: 'app',
+ name: 'nav',
+ attachTo: { id: 'app/layout', input: 'nav' },
+ inputs: {
+ items: createExtensionInput({
+ target: createNavItemExtension.targetDataRef,
+ }),
+ },
+ output: {
+ element: coreExtensionData.reactElement,
+ },
+ factory({ inputs }) {
+ return {
+ element: (
+
+ ),
+ };
+ },
+});
+
/**
* @public
* Renders the given element in a test app, for use in unit tests.
@@ -54,20 +123,28 @@ export function renderInTestApp(
element: JSX.Element,
options?: TestAppOptions,
) {
- const extension = createExtension({
- namespace: 'test',
- attachTo: { id: 'app', input: 'root' },
- output: {
- element: coreExtensionData.reactElement,
- },
- factory: () => ({ element }),
- });
- const tester = createExtensionTester(extension);
+ const extensions: Array> = [
+ createExtension({
+ namespace: 'test',
+ attachTo: { id: 'app/routes', input: 'routes' },
+ output: [coreExtensionData.reactElement, coreExtensionData.routePath],
+ factory: () => {
+ return [
+ coreExtensionData.reactElement(element),
+ coreExtensionData.routePath('/'),
+ ];
+ },
+ }),
+ createRouterExtension({
+ namespace: 'test',
+ Component: ({ children }) => {children},
+ }),
+ ];
if (options?.mountedRoutes) {
for (const [path, routeRef] of Object.entries(options.mountedRoutes)) {
// TODO(Rugvip): add support for external route refs
- tester.add(
+ extensions.push(
createExtension({
kind: 'test-route',
name: path,
@@ -84,5 +161,17 @@ export function renderInTestApp(
);
}
}
- return tester.render();
+
+ const app = createSpecializedApp({
+ features: [
+ createExtensionOverrides({
+ extensions,
+ }),
+ ],
+ config: ConfigReader.fromConfigs([
+ { context: 'render-config', data: options?.config ?? {} },
+ ]),
+ });
+
+ return render(app.createRoot());
}