test-utils: add rerender support for renderInTestApp
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/test-utils': patch
|
||||
---
|
||||
|
||||
Fixed `renderInTestApp` so that it is able to re-render the result without removing the app wrapping.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/test-utils': minor
|
||||
---
|
||||
|
||||
Added the options parameter to `renderWithEffects`, which if forwarded to the `render` function from `@testling-library/react`. Initially only the `wrapper` option is supported.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/test-utils': minor
|
||||
---
|
||||
|
||||
Added `createTestAppWrapper`, which returns a component that can be used as the `wrapper` option for `render` or `renderWithEffects`.
|
||||
@@ -27,6 +27,7 @@ import { Observable } from '@backstage/types';
|
||||
import { PermissionApi } from '@backstage/plugin-permission-react';
|
||||
import { ReactElement } from 'react';
|
||||
import { ReactNode } from 'react';
|
||||
import { RenderOptions } from '@testing-library/react';
|
||||
import { RenderResult } from '@testing-library/react';
|
||||
import { RouteRef } from '@backstage/core-plugin-api';
|
||||
import { StorageApi } from '@backstage/core-plugin-api';
|
||||
@@ -40,6 +41,11 @@ export type CollectedLogs<T extends LogFuncs> = {
|
||||
[key in T]: string[];
|
||||
};
|
||||
|
||||
// @public
|
||||
export function createTestAppWrapper(
|
||||
options?: TestAppOptions,
|
||||
): (props: { children: ReactNode }) => JSX.Element;
|
||||
|
||||
// @public
|
||||
export type ErrorWithContext = {
|
||||
error: ErrorApiError;
|
||||
@@ -187,7 +193,10 @@ export function renderInTestApp(
|
||||
): Promise<RenderResult>;
|
||||
|
||||
// @public
|
||||
export function renderWithEffects(nodes: ReactElement): Promise<RenderResult>;
|
||||
export function renderWithEffects(
|
||||
nodes: ReactElement,
|
||||
options?: Pick<RenderOptions, 'wrapper'>,
|
||||
): Promise<RenderResult>;
|
||||
|
||||
// @public
|
||||
export function setupRequestMockHandlers(worker: {
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
createSubRouteRef,
|
||||
errorApiRef,
|
||||
useApi,
|
||||
useApp,
|
||||
useRouteRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { withLogCollector } from './logCollector';
|
||||
@@ -172,4 +173,18 @@ describe('wrapInTestApp', () => {
|
||||
expect(root.children.length).toBe(1);
|
||||
expect(root.children[0].textContent).toBe('foo');
|
||||
});
|
||||
|
||||
it('should support rerenders', async () => {
|
||||
const MyComponent = () => {
|
||||
const app = useApp();
|
||||
const { Progress } = app.getComponents();
|
||||
return <Progress />;
|
||||
};
|
||||
|
||||
const rendered = await renderInTestApp(<MyComponent />);
|
||||
expect(rendered.getByTestId('progress')).toBeInTheDocument();
|
||||
|
||||
rendered.rerender(<MyComponent />);
|
||||
expect(rendered.getByTestId('progress')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -107,17 +107,15 @@ function isExternalRouteRef(
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a component inside a Backstage test app, providing a mocked theme
|
||||
* and app context, along with mocked APIs.
|
||||
* Creates a Wrapper component that 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.
|
||||
* @public
|
||||
*/
|
||||
export function wrapInTestApp(
|
||||
Component: ComponentType | ReactNode,
|
||||
export function createTestAppWrapper(
|
||||
options: TestAppOptions = {},
|
||||
): ReactElement {
|
||||
): (props: { children: ReactNode }) => JSX.Element {
|
||||
const { routeEntries = ['/'] } = options;
|
||||
const boundRoutes = new Map<ExternalRouteRef, RouteRef>();
|
||||
|
||||
@@ -162,13 +160,6 @@ export function wrapInTestApp(
|
||||
},
|
||||
});
|
||||
|
||||
let wrappedElement: React.ReactElement;
|
||||
if (Component instanceof Function) {
|
||||
wrappedElement = <Component />;
|
||||
} else {
|
||||
wrappedElement = Component as React.ReactElement;
|
||||
}
|
||||
|
||||
const routeElements = Object.entries(options.mountedRoutes ?? {}).map(
|
||||
([path, routeRef]) => {
|
||||
const Page = () => <div>Mounted at {path}</div>;
|
||||
@@ -189,18 +180,44 @@ export function wrapInTestApp(
|
||||
const AppProvider = app.getProvider();
|
||||
const AppRouter = app.getRouter();
|
||||
|
||||
return (
|
||||
const TestAppWrapper = ({ children }: { children: ReactNode }) => (
|
||||
<AppProvider>
|
||||
<AppRouter>
|
||||
<NoRender>{routeElements}</NoRender>
|
||||
{/* The path of * here is needed to be set as a catch all, so it will render the wrapper element
|
||||
* and work with nested routes if they exist too */}
|
||||
<Routes>
|
||||
<Route path="/*" element={wrappedElement} />
|
||||
<Route path="/*" element={<>{children}</>} />
|
||||
</Routes>
|
||||
</AppRouter>
|
||||
</AppProvider>
|
||||
);
|
||||
|
||||
return TestAppWrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @public
|
||||
*/
|
||||
export function wrapInTestApp(
|
||||
Component: ComponentType | ReactNode,
|
||||
options: TestAppOptions = {},
|
||||
): ReactElement {
|
||||
const TestAppWrapper = createTestAppWrapper(options);
|
||||
|
||||
let wrappedElement: React.ReactElement;
|
||||
if (Component instanceof Function) {
|
||||
wrappedElement = <Component />;
|
||||
} else {
|
||||
wrappedElement = Component as React.ReactElement;
|
||||
}
|
||||
|
||||
return <TestAppWrapper>{wrappedElement}</TestAppWrapper>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -218,5 +235,14 @@ export async function renderInTestApp(
|
||||
Component: ComponentType | ReactNode,
|
||||
options: TestAppOptions = {},
|
||||
): Promise<RenderResult> {
|
||||
return renderWithEffects(wrapInTestApp(Component, options));
|
||||
let wrappedElement: React.ReactElement;
|
||||
if (Component instanceof Function) {
|
||||
wrappedElement = <Component />;
|
||||
} else {
|
||||
wrappedElement = Component as React.ReactElement;
|
||||
}
|
||||
|
||||
return renderWithEffects(wrappedElement, {
|
||||
wrapper: createTestAppWrapper(options),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -16,7 +16,11 @@
|
||||
|
||||
export * from './apis';
|
||||
export { default as mockBreakpoint } from './mockBreakpoint';
|
||||
export { wrapInTestApp, renderInTestApp } from './appWrappers';
|
||||
export {
|
||||
wrapInTestApp,
|
||||
renderInTestApp,
|
||||
createTestAppWrapper,
|
||||
} from './appWrappers';
|
||||
export type { TestAppOptions } from './appWrappers';
|
||||
export * from './msw';
|
||||
export * from './logCollector';
|
||||
|
||||
@@ -15,7 +15,12 @@
|
||||
*/
|
||||
|
||||
import { ReactElement } from 'react';
|
||||
import { act, render, RenderResult } from '@testing-library/react';
|
||||
import {
|
||||
act,
|
||||
render,
|
||||
RenderOptions,
|
||||
RenderResult,
|
||||
} from '@testing-library/react';
|
||||
|
||||
/**
|
||||
* @public
|
||||
@@ -31,10 +36,11 @@ import { act, render, RenderResult } from '@testing-library/react';
|
||||
*/
|
||||
export async function renderWithEffects(
|
||||
nodes: ReactElement,
|
||||
options?: Pick<RenderOptions, 'wrapper'>,
|
||||
): Promise<RenderResult> {
|
||||
let value: RenderResult;
|
||||
await act(async () => {
|
||||
value = render(nodes);
|
||||
value = render(nodes, options);
|
||||
});
|
||||
return value!;
|
||||
}
|
||||
|
||||
@@ -19,11 +19,7 @@ import { screen, waitFor } from '@testing-library/react';
|
||||
import { ShortcutItem } from './ShortcutItem';
|
||||
import { Shortcut } from './types';
|
||||
import { LocalStoredShortcuts } from './api';
|
||||
import {
|
||||
MockStorageApi,
|
||||
renderInTestApp,
|
||||
wrapInTestApp,
|
||||
} from '@backstage/test-utils';
|
||||
import { MockStorageApi, renderInTestApp } from '@backstage/test-utils';
|
||||
import { SidebarContext } from '@backstage/core-components';
|
||||
|
||||
describe('ShortcutItem', () => {
|
||||
@@ -66,12 +62,12 @@ describe('ShortcutItem', () => {
|
||||
);
|
||||
expect(screen.getByText('On')).toBeInTheDocument();
|
||||
|
||||
rerender(wrapInTestApp(<ShortcutItem api={api} shortcut={shortcut2} />));
|
||||
rerender(<ShortcutItem api={api} shortcut={shortcut2} />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('TT')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
rerender(wrapInTestApp(<ShortcutItem api={api} shortcut={shortcut3} />));
|
||||
rerender(<ShortcutItem api={api} shortcut={shortcut3} />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('MT')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user