diff --git a/packages/test-utils/src/testUtils/appWrappers.test.tsx b/packages/test-utils/src/testUtils/appWrappers.test.tsx
index e46b6a95ce..a30e8f9823 100644
--- a/packages/test-utils/src/testUtils/appWrappers.test.tsx
+++ b/packages/test-utils/src/testUtils/appWrappers.test.tsx
@@ -14,22 +14,59 @@
* limitations under the License.
*/
-import React from 'react';
+import React, { FC } from 'react';
import { render } from '@testing-library/react';
-import { wrapInTestApp } from './appWrappers';
+import { wrapInTestApp, renderInTestApp } from './appWrappers';
import { Route } from 'react-router';
+import { withLogCollector } from '@backstage/test-utils-core';
describe('wrapInTestApp', () => {
- it('should provide routing', () => {
- const rendered = render(
- wrapInTestApp(
- <>
- Route 1
- Route 2
- >,
- { routeEntries: ['/route2'] },
+ it('should provide routing and warn about missing act()', async () => {
+ const { error } = await withLogCollector(['error'], async () => {
+ const rendered = render(
+ wrapInTestApp(
+ <>
+ Route 1
+ Route 2
+ >,
+ { routeEntries: ['/route2'] },
+ ),
+ );
+ expect(rendered.getByText('Route 2')).toBeInTheDocument();
+
+ // Wait for async actions to trigger the act() warnings that we assert below
+ await Promise.resolve();
+ });
+
+ expect(error).toEqual([
+ expect.stringMatching(
+ /^Warning: An update to %s inside a test was not wrapped in act\(...\)/,
),
- );
- expect(rendered.getByText('Route 2')).toBeInTheDocument();
+ expect.stringMatching(
+ /^Warning: An update to %s inside a test was not wrapped in act\(...\)/,
+ ),
+ ]);
+ });
+
+ it('should render a component in a test app without warning about missing act()', async () => {
+ const { error } = await withLogCollector(['error'], async () => {
+ const Foo: FC<{}> = () => {
+ return
foo
;
+ };
+
+ const rendered = await renderInTestApp(Foo);
+ expect(rendered.getByText('foo')).toBeInTheDocument();
+ });
+
+ expect(error).toEqual([]);
+ });
+
+ it('should render a node in a test app', async () => {
+ const Foo: FC<{}> = () => {
+ return foo
;
+ };
+
+ const rendered = await renderInTestApp();
+ expect(rendered.getByText('foo')).toBeInTheDocument();
});
});
diff --git a/packages/test-utils/src/testUtils/appWrappers.tsx b/packages/test-utils/src/testUtils/appWrappers.tsx
index 2450604976..6d48082b7c 100644
--- a/packages/test-utils/src/testUtils/appWrappers.tsx
+++ b/packages/test-utils/src/testUtils/appWrappers.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import React, { ComponentType, ReactNode, FunctionComponent, FC } from 'react';
+import React, { ComponentType, ReactNode, FC, ReactElement } from 'react';
import { MemoryRouter } from 'react-router';
import { Route } from 'react-router-dom';
import { lightTheme } from '@backstage/theme';
@@ -23,6 +23,8 @@ import privateExports, {
ApiTestRegistry,
BootErrorPageProps,
} from '@backstage/core-api';
+import { RenderResult } from '@testing-library/react';
+import { renderWithEffects } from '@backstage/test-utils-core';
const { PrivateAppImpl } = privateExports;
const NotFoundErrorPage = () => {
@@ -43,10 +45,17 @@ type TestAppOptions = {
routeEntries?: string[];
};
+/**
+ * Wraps a component inside a Backstage test app, providing a mocked theme
+ * and app context, along with mocked APIs.
+ *
+ * @param Component - A component or react node to render inside the test app.
+ * @param options - Additional options for the rendering.
+ */
export function wrapInTestApp(
Component: ComponentType | ReactNode,
options: TestAppOptions = {},
-) {
+): ReactElement {
const { routeEntries = ['/'] } = options;
const app = new PrivateAppImpl({
@@ -72,7 +81,7 @@ export function wrapInTestApp(
if (Component instanceof Function) {
Wrapper = Component;
} else {
- Wrapper = (() => Component) as FunctionComponent;
+ Wrapper = (() => Component) as FC;
}
const AppProvider = app.getProvider();
@@ -85,3 +94,20 @@ export function wrapInTestApp(
);
}
+
+/**
+ * Renders a component inside a Backstage test app, providing a mocked theme
+ * and app context, along with mocked APIs.
+ *
+ * The render executes async effects similar to `renderWithEffects`. To avoid this
+ * behavior, use a regular `render()` + `wrapInTestApp()` instead.
+ *
+ * @param Component - A component or react node to render inside the test app.
+ * @param options - Additional options for the rendering.
+ */
+export async function renderInTestApp(
+ Component: ComponentType | ReactNode,
+ options: TestAppOptions = {},
+): Promise {
+ return renderWithEffects(wrapInTestApp(Component, options));
+}
diff --git a/packages/test-utils/src/testUtils/index.tsx b/packages/test-utils/src/testUtils/index.tsx
index ea33de2f31..302bdb405f 100644
--- a/packages/test-utils/src/testUtils/index.tsx
+++ b/packages/test-utils/src/testUtils/index.tsx
@@ -15,4 +15,4 @@
*/
export { default as mockBreakpoint } from './mockBreakpoint';
-export * from './appWrappers';
+export { wrapInTestApp, renderInTestApp } from './appWrappers';