frontend-test-utils: remove render method
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/frontend-test-utils': minor
|
||||
---
|
||||
|
||||
**BREAKING**: The deprecated `.render()` method has been removed from the extension tester.
|
||||
@@ -42,32 +42,36 @@ import { renderInTestApp as renderInOldTestApp } from '@backstage/test-utils';
|
||||
describe('BackwardsCompatProvider', () => {
|
||||
it('should convert the app context', () => {
|
||||
// TODO(Rugvip): Replace with the new renderInTestApp once it's available, and have some plugins
|
||||
createExtensionTester(
|
||||
createExtension({
|
||||
attachTo: { id: 'ignored', input: 'ignored' },
|
||||
output: [coreExtensionData.reactElement],
|
||||
factory() {
|
||||
function Component() {
|
||||
const app = useApp();
|
||||
return (
|
||||
<div data-testid="ctx">
|
||||
plugins:
|
||||
{app
|
||||
.getPlugins()
|
||||
.map(p => p.getId())
|
||||
.join(', ')}
|
||||
{'\n'}
|
||||
components: {Object.keys(app.getComponents()).join(', ')}
|
||||
{'\n'}
|
||||
icons: {Object.keys(app.getSystemIcons()).join(', ')}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
renderInNewTestApp(
|
||||
createExtensionTester(
|
||||
createExtension({
|
||||
attachTo: { id: 'ignored', input: 'ignored' },
|
||||
output: [coreExtensionData.reactElement],
|
||||
factory() {
|
||||
function Component() {
|
||||
const app = useApp();
|
||||
return (
|
||||
<div data-testid="ctx">
|
||||
plugins:
|
||||
{app
|
||||
.getPlugins()
|
||||
.map(p => p.getId())
|
||||
.join(', ')}
|
||||
{'\n'}
|
||||
components: {Object.keys(app.getComponents()).join(', ')}
|
||||
{'\n'}
|
||||
icons: {Object.keys(app.getSystemIcons()).join(', ')}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return [coreExtensionData.reactElement(compatWrapper(<Component />))];
|
||||
},
|
||||
}),
|
||||
).render();
|
||||
return [
|
||||
coreExtensionData.reactElement(compatWrapper(<Component />)),
|
||||
];
|
||||
},
|
||||
}),
|
||||
).reactElement(),
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('ctx').textContent).toMatchInlineSnapshot(`
|
||||
"plugins:
|
||||
|
||||
@@ -16,14 +16,15 @@
|
||||
|
||||
import React from 'react';
|
||||
import { AppRootWrapperBlueprint } from './AppRootWrapperBlueprint';
|
||||
import { createExtensionTester } from '@backstage/frontend-test-utils';
|
||||
import { PageBlueprint } from './PageBlueprint';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import {
|
||||
coreExtensionData,
|
||||
createExtension,
|
||||
createExtensionInput,
|
||||
createFrontendPlugin,
|
||||
} from '../wiring';
|
||||
import { createSpecializedApp } from '@backstage/frontend-app-api';
|
||||
import { MockConfigApi } from '@backstage/test-utils';
|
||||
|
||||
describe('AppRootWrapperBlueprint', () => {
|
||||
it('should return an extension with sensible defaults', () => {
|
||||
@@ -64,24 +65,22 @@ describe('AppRootWrapperBlueprint', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { getByText } = createExtensionTester(
|
||||
PageBlueprint.make({
|
||||
params: {
|
||||
defaultPath: '/',
|
||||
loader: async () => <div />,
|
||||
},
|
||||
}),
|
||||
)
|
||||
.add(extension)
|
||||
.render();
|
||||
const app = createSpecializedApp({
|
||||
features: [
|
||||
createFrontendPlugin({
|
||||
id: 'test',
|
||||
extensions: [extension],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
await waitFor(() => expect(getByText('Hello')).toBeInTheDocument());
|
||||
render(app.createRoot());
|
||||
|
||||
await waitFor(() => expect(screen.getByText('Hello')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('should render the complex component wrapper', async () => {
|
||||
const extension = AppRootWrapperBlueprint.makeWithOverrides({
|
||||
namespace: 'ns',
|
||||
name: 'test',
|
||||
config: {
|
||||
schema: {
|
||||
name: z => z.string(),
|
||||
@@ -104,28 +103,39 @@ describe('AppRootWrapperBlueprint', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { getByText, getByTestId } = createExtensionTester(
|
||||
PageBlueprint.make({
|
||||
params: {
|
||||
defaultPath: '/',
|
||||
loader: async () => <div>Hi</div>,
|
||||
const app = createSpecializedApp({
|
||||
features: [
|
||||
createFrontendPlugin({
|
||||
id: 'test',
|
||||
extensions: [
|
||||
extension,
|
||||
createExtension({
|
||||
name: 'test-child',
|
||||
attachTo: { id: 'app-root-wrapper:test', input: 'children' },
|
||||
output: [coreExtensionData.reactElement],
|
||||
factory: () => [
|
||||
coreExtensionData.reactElement(<div>Its Me</div>),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
config: new MockConfigApi({
|
||||
app: {
|
||||
extensions: [
|
||||
{
|
||||
'app-root-wrapper:test': { config: { name: 'Robin' } },
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
)
|
||||
.add(extension, { config: { name: 'Robin' } })
|
||||
.add(
|
||||
createExtension({
|
||||
attachTo: { id: 'app-root-wrapper:ns/test', input: 'children' },
|
||||
output: [coreExtensionData.reactElement],
|
||||
factory: () => [coreExtensionData.reactElement(<div>Its Me</div>)],
|
||||
}),
|
||||
)
|
||||
.render();
|
||||
});
|
||||
|
||||
render(app.createRoot());
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByText('Hi')).toBeInTheDocument();
|
||||
expect(getByTestId('Robin-1')).toBeInTheDocument();
|
||||
expect(getByText('Its Me')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('Robin-1')).toBeInTheDocument();
|
||||
expect(screen.getByText('Its Me')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -103,7 +103,7 @@ describe('PageBlueprint', () => {
|
||||
|
||||
expect(tester.get(coreExtensionData.routeRef)).toBe(mockRouteRef);
|
||||
|
||||
const { getByTestId } = tester.render();
|
||||
const { getByTestId } = renderInTestApp(tester.reactElement());
|
||||
|
||||
await waitFor(() => expect(getByTestId('test')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
@@ -23,14 +23,12 @@ import {
|
||||
} from '@backstage/test-utils';
|
||||
import { ExtensionBoundary } from './ExtensionBoundary';
|
||||
import { coreExtensionData, createExtension } from '../wiring';
|
||||
import {
|
||||
analyticsApiRef,
|
||||
createApiFactory,
|
||||
useAnalytics,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { analyticsApiRef, useAnalytics } from '@backstage/core-plugin-api';
|
||||
import { createRouteRef } from '../routing';
|
||||
import { createExtensionTester } from '@backstage/frontend-test-utils';
|
||||
import { ApiBlueprint } from '../blueprints';
|
||||
import {
|
||||
createExtensionTester,
|
||||
renderInTestApp,
|
||||
} from '@backstage/frontend-test-utils';
|
||||
|
||||
const wrapInBoundaryExtension = (element?: JSX.Element) => {
|
||||
const routeRef = createRouteRef();
|
||||
@@ -60,7 +58,11 @@ describe('ExtensionBoundary', () => {
|
||||
const TextComponent = () => {
|
||||
return <p>{text}</p>;
|
||||
};
|
||||
createExtensionTester(wrapInBoundaryExtension(<TextComponent />)).render();
|
||||
renderInTestApp(
|
||||
createExtensionTester(
|
||||
wrapInBoundaryExtension(<TextComponent />),
|
||||
).reactElement(),
|
||||
);
|
||||
await waitFor(() => expect(screen.getByText(text)).toBeInTheDocument());
|
||||
});
|
||||
|
||||
@@ -70,9 +72,11 @@ describe('ExtensionBoundary', () => {
|
||||
throw new Error(errorMsg);
|
||||
};
|
||||
const { error } = await withLogCollector(['error'], async () => {
|
||||
createExtensionTester(
|
||||
wrapInBoundaryExtension(<ErrorComponent />),
|
||||
).render();
|
||||
renderInTestApp(
|
||||
createExtensionTester(
|
||||
wrapInBoundaryExtension(<ErrorComponent />),
|
||||
).reactElement(),
|
||||
);
|
||||
await waitFor(() =>
|
||||
expect(screen.getByText(errorMsg)).toBeInTheDocument(),
|
||||
);
|
||||
@@ -99,13 +103,13 @@ describe('ExtensionBoundary', () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
createExtensionTester(
|
||||
wrapInBoundaryExtension(
|
||||
<TestApiProvider apis={[[analyticsApiRef, analyticsApiMock]]}>
|
||||
<AnalyticsComponent />
|
||||
</TestApiProvider>,
|
||||
),
|
||||
).render();
|
||||
renderInTestApp(
|
||||
<TestApiProvider apis={[[analyticsApiRef, analyticsApiMock]]}>
|
||||
{createExtensionTester(
|
||||
wrapInBoundaryExtension(<AnalyticsComponent />),
|
||||
).reactElement()}
|
||||
</TestApiProvider>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
const event = analyticsApiMock
|
||||
@@ -122,8 +126,9 @@ describe('ExtensionBoundary', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// TODO(Rugvip): It's annoying to test the inverse of this currently, because the extension tester overrides the subject to always output a path
|
||||
it('should emit analytics events if routable', async () => {
|
||||
// TODO(Rugvip): Need a way to be able to override APIs in the app to be able to test this properly
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
it.skip('should emit analytics events if routable', async () => {
|
||||
const Emitter = () => {
|
||||
const analytics = useAnalytics();
|
||||
useEffect(() => {
|
||||
@@ -134,16 +139,12 @@ describe('ExtensionBoundary', () => {
|
||||
const analyticsApiMock = new MockAnalyticsApi();
|
||||
|
||||
await act(async () => {
|
||||
createExtensionTester(wrapInBoundaryExtension(<Emitter />))
|
||||
.add(
|
||||
ApiBlueprint.make({
|
||||
namespace: analyticsApiRef.id,
|
||||
params: {
|
||||
factory: createApiFactory(analyticsApiRef, analyticsApiMock),
|
||||
},
|
||||
}),
|
||||
)
|
||||
.render();
|
||||
renderInTestApp(
|
||||
createExtensionTester(
|
||||
wrapInBoundaryExtension(<Emitter />),
|
||||
).reactElement(),
|
||||
// { apis: [[analyticsApiRef, analyticsApiMock]] },
|
||||
);
|
||||
});
|
||||
|
||||
expect(analyticsApiMock.getEvents()).toEqual([
|
||||
|
||||
@@ -17,7 +17,10 @@
|
||||
import React from 'react';
|
||||
import { coreExtensionData } from './coreExtensionData';
|
||||
import { createExtensionBlueprint } from './createExtensionBlueprint';
|
||||
import { createExtensionTester } from '@backstage/frontend-test-utils';
|
||||
import {
|
||||
createExtensionTester,
|
||||
renderInTestApp,
|
||||
} from '@backstage/frontend-test-utils';
|
||||
import {
|
||||
ExtensionDataValue,
|
||||
createExtensionDataRef,
|
||||
@@ -80,7 +83,9 @@ describe('createExtensionBlueprint', () => {
|
||||
version: 'v2',
|
||||
});
|
||||
|
||||
const { container } = createExtensionTester(extension).render();
|
||||
const { container } = renderInTestApp(
|
||||
createExtensionTester(extension).reactElement(),
|
||||
);
|
||||
expect(container.querySelector('h1')).toHaveTextContent('Hello, world!');
|
||||
});
|
||||
|
||||
@@ -120,7 +125,9 @@ describe('createExtensionBlueprint', () => {
|
||||
version: 'v2',
|
||||
});
|
||||
|
||||
const { container } = createExtensionTester(extension).render();
|
||||
const { container } = renderInTestApp(
|
||||
createExtensionTester(extension).reactElement(),
|
||||
);
|
||||
expect(container.querySelector('h1')).toHaveTextContent('Hello, world!');
|
||||
});
|
||||
|
||||
@@ -145,7 +152,9 @@ describe('createExtensionBlueprint', () => {
|
||||
|
||||
expect(extension).toBeDefined();
|
||||
|
||||
const { container } = createExtensionTester(extension).render();
|
||||
const { container } = renderInTestApp(
|
||||
createExtensionTester(extension).reactElement(),
|
||||
);
|
||||
expect(container.querySelector('h1')).toHaveTextContent('Hello, world!');
|
||||
});
|
||||
|
||||
@@ -216,13 +225,15 @@ describe('createExtensionBlueprint', () => {
|
||||
|
||||
expect.assertions(4);
|
||||
|
||||
createExtensionTester(extension, {
|
||||
config: {
|
||||
something: 'something new!',
|
||||
text: 'Hello, world!',
|
||||
defaulted: 'lolz',
|
||||
},
|
||||
}).render();
|
||||
renderInTestApp(
|
||||
createExtensionTester(extension, {
|
||||
config: {
|
||||
something: 'something new!',
|
||||
text: 'Hello, world!',
|
||||
defaulted: 'lolz',
|
||||
},
|
||||
}).reactElement(),
|
||||
);
|
||||
});
|
||||
|
||||
it('should not allow overlapping config keys', () => {
|
||||
@@ -291,12 +302,14 @@ describe('createExtensionBlueprint', () => {
|
||||
|
||||
expect.assertions(2);
|
||||
|
||||
createExtensionTester(extension, {
|
||||
config: {
|
||||
something: 'something new!',
|
||||
defaulted: 'lolz',
|
||||
},
|
||||
}).render();
|
||||
renderInTestApp(
|
||||
createExtensionTester(extension, {
|
||||
config: {
|
||||
something: 'something new!',
|
||||
defaulted: 'lolz',
|
||||
},
|
||||
}).reactElement(),
|
||||
);
|
||||
});
|
||||
|
||||
it('should allow getting inputs properly', () => {
|
||||
|
||||
@@ -84,8 +84,6 @@ export class ExtensionTester<UOutput extends AnyExtensionDataRef> {
|
||||
): ExtensionQuery<UQueryExtensionOutput>;
|
||||
// (undocumented)
|
||||
reactElement(): JSX.Element;
|
||||
// @deprecated (undocumented)
|
||||
render(options?: { config?: JsonObject }): RenderResult;
|
||||
}
|
||||
|
||||
// @public
|
||||
|
||||
@@ -14,22 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import {
|
||||
ApiBlueprint,
|
||||
analyticsApiRef,
|
||||
configApiRef,
|
||||
coreExtensionData,
|
||||
createApiFactory,
|
||||
createExtension,
|
||||
createExtensionDataRef,
|
||||
createExtensionInput,
|
||||
useAnalytics,
|
||||
useApi,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { MockAnalyticsApi } from '../apis';
|
||||
import { createExtensionTester } from './createExtensionTester';
|
||||
|
||||
const stringDataRef = createExtensionDataRef<string>().with({
|
||||
@@ -37,206 +28,6 @@ const stringDataRef = createExtensionDataRef<string>().with({
|
||||
});
|
||||
|
||||
describe('createExtensionTester', () => {
|
||||
const defaultDefinition = {
|
||||
namespace: 'test',
|
||||
attachTo: { id: 'ignored', input: 'ignored' },
|
||||
output: [coreExtensionData.reactElement],
|
||||
factory: () => [coreExtensionData.reactElement(<div>test</div>)],
|
||||
};
|
||||
|
||||
it('should render a simple extension', async () => {
|
||||
const extension = createExtension(defaultDefinition);
|
||||
const tester = createExtensionTester(extension);
|
||||
tester.render();
|
||||
await expect(screen.findByText('test')).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render an extension even if disabled by default', async () => {
|
||||
const extension = createExtension({
|
||||
...defaultDefinition,
|
||||
disabled: true,
|
||||
});
|
||||
const tester = createExtensionTester(extension);
|
||||
tester.render();
|
||||
await expect(screen.findByText('test')).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should fail to render an extension that doesn't output a react element", async () => {
|
||||
const extension = createExtension({
|
||||
...defaultDefinition,
|
||||
output: [coreExtensionData.routePath],
|
||||
factory: () => [coreExtensionData.routePath('/foo')],
|
||||
});
|
||||
const tester = createExtensionTester(extension);
|
||||
expect(() => tester.render()).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Failed to instantiate extension 'app/routes', extension 'test' could not be attached because its output data ('core.routing.path') does not match what the input 'routes' requires ('core.routing.path', 'core.reactElement')"`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should render multiple extensions', async () => {
|
||||
const indexPageExtension = createExtension({
|
||||
...defaultDefinition,
|
||||
factory: () => [
|
||||
coreExtensionData.reactElement(
|
||||
<div>
|
||||
Index page <Link to="/details">See details</Link>
|
||||
</div>,
|
||||
),
|
||||
],
|
||||
});
|
||||
const detailsPageExtension = createExtension({
|
||||
...defaultDefinition,
|
||||
name: 'details',
|
||||
attachTo: { id: 'app/routes', input: 'routes' },
|
||||
output: [coreExtensionData.routePath, coreExtensionData.reactElement],
|
||||
factory: () => [
|
||||
coreExtensionData.routePath('/details'),
|
||||
coreExtensionData.reactElement(<div>Details page</div>),
|
||||
],
|
||||
});
|
||||
|
||||
const tester = createExtensionTester(indexPageExtension);
|
||||
tester.add(detailsPageExtension);
|
||||
tester.render();
|
||||
|
||||
await expect(screen.findByText('Index page')).resolves.toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByRole('link', { name: 'See details' }));
|
||||
|
||||
await expect(
|
||||
screen.findByText('Details page'),
|
||||
).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should accepts a custom config', async () => {
|
||||
const indexPageExtension = createExtension({
|
||||
...defaultDefinition,
|
||||
config: {
|
||||
schema: {
|
||||
title: z => z.string().optional(),
|
||||
},
|
||||
},
|
||||
factory: ({ config }) => {
|
||||
const Component = () => {
|
||||
const configApi = useApi(configApiRef);
|
||||
const appTitle = configApi.getOptionalString('app.title');
|
||||
return (
|
||||
<div>
|
||||
<h2>{appTitle ?? 'Backstage app'}</h2>
|
||||
<h3>{config.title ?? 'Index page'}</h3>
|
||||
<Link to="/details">See details</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return [coreExtensionData.reactElement(<Component />)];
|
||||
},
|
||||
});
|
||||
|
||||
const detailsPageExtension = createExtension({
|
||||
...defaultDefinition,
|
||||
name: 'details',
|
||||
attachTo: { id: 'app/routes', input: 'routes' },
|
||||
config: {
|
||||
schema: {
|
||||
title: z => z.string().optional(),
|
||||
},
|
||||
},
|
||||
output: [coreExtensionData.routePath, coreExtensionData.reactElement],
|
||||
factory: ({ config }) => [
|
||||
coreExtensionData.routePath('/details'),
|
||||
coreExtensionData.reactElement(
|
||||
<div>{config.title ?? 'Details page'}</div>,
|
||||
),
|
||||
],
|
||||
});
|
||||
|
||||
const tester = createExtensionTester(indexPageExtension, {
|
||||
config: { title: 'Custom index' },
|
||||
});
|
||||
|
||||
tester.add(detailsPageExtension, {
|
||||
config: { title: 'Custom details' },
|
||||
});
|
||||
|
||||
tester.render({
|
||||
config: {
|
||||
app: {
|
||||
title: 'Custom app',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await expect(
|
||||
screen.findByRole('heading', { name: 'Custom app' }),
|
||||
).resolves.toBeInTheDocument();
|
||||
|
||||
await expect(
|
||||
screen.findByRole('heading', { name: 'Custom index' }),
|
||||
).resolves.toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByRole('link', { name: 'See details' }));
|
||||
|
||||
await expect(
|
||||
screen.findByText('Custom details'),
|
||||
).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should capture click events in analytics', async () => {
|
||||
// Mocking the analytics api implementation
|
||||
const analyticsApiMock = new MockAnalyticsApi();
|
||||
|
||||
const analyticsApiOverride = ApiBlueprint.make({
|
||||
params: {
|
||||
factory: createApiFactory({
|
||||
api: analyticsApiRef,
|
||||
deps: {},
|
||||
factory: () => analyticsApiMock,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
const indexPageExtension = createExtension({
|
||||
...defaultDefinition,
|
||||
factory: () => {
|
||||
const Component = () => {
|
||||
const analyticsApi = useAnalytics();
|
||||
const handleClick = useCallback(() => {
|
||||
analyticsApi.captureEvent('click', 'See details');
|
||||
}, [analyticsApi]);
|
||||
return (
|
||||
<div>
|
||||
Index Page
|
||||
<button onClick={handleClick}>See details</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return [coreExtensionData.reactElement(<Component />)];
|
||||
},
|
||||
});
|
||||
|
||||
const tester = createExtensionTester(indexPageExtension);
|
||||
|
||||
// Overriding the analytics api extension
|
||||
tester.add(analyticsApiOverride);
|
||||
|
||||
tester.render();
|
||||
|
||||
fireEvent.click(await screen.findByRole('button', { name: 'See details' }));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(analyticsApiMock.getEvents()).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
action: 'click',
|
||||
subject: 'See details',
|
||||
}),
|
||||
]),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('should return the correct dataRef when called', () => {
|
||||
const extension = createExtension({
|
||||
namespace: 'test',
|
||||
|
||||
@@ -14,10 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { MemoryRouter, Link } from 'react-router-dom';
|
||||
import { RenderResult, render } from '@testing-library/react';
|
||||
import { createSpecializedApp } from '@backstage/frontend-app-api';
|
||||
import {
|
||||
AnyExtensionDataRef,
|
||||
AppNode,
|
||||
@@ -25,23 +21,13 @@ import {
|
||||
Extension,
|
||||
ExtensionDataRef,
|
||||
ExtensionDefinition,
|
||||
IconComponent,
|
||||
NavItemBlueprint,
|
||||
RouteRef,
|
||||
RouterBlueprint,
|
||||
coreExtensionData,
|
||||
createExtension,
|
||||
createExtensionInput,
|
||||
createExtensionOverrides,
|
||||
useRouteRef,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { Config, ConfigReader } from '@backstage/config';
|
||||
import { JsonArray, JsonObject, JsonValue } from '@backstage/types';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { resolveExtensionDefinition } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { toInternalExtensionDefinition } from '../../../frontend-plugin-api/src/wiring/createExtension';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { resolveAppTree } from '../../../frontend-app-api/src/tree/resolveAppTree';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { resolveAppNodeSpecs } from '../../../frontend-app-api/src/tree/resolveAppNodeSpecs';
|
||||
@@ -51,56 +37,6 @@ import { instantiateAppNodeTree } from '../../../frontend-app-api/src/tree/insta
|
||||
import { readAppExtensionsConfig } from '../../../frontend-app-api/src/tree/readAppExtensionsConfig';
|
||||
import { TestApiRegistry } from '@backstage/test-utils';
|
||||
|
||||
const NavItem = (props: {
|
||||
routeRef: RouteRef<undefined>;
|
||||
title: string;
|
||||
icon: IconComponent;
|
||||
}) => {
|
||||
const { routeRef, title, icon: Icon } = props;
|
||||
const link = useRouteRef(routeRef);
|
||||
if (!link) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<li>
|
||||
<Link to={link()}>
|
||||
<Icon /> {title}
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
const TestAppNavExtension = createExtension({
|
||||
namespace: 'app',
|
||||
name: 'nav',
|
||||
attachTo: { id: 'app/layout', input: 'nav' },
|
||||
inputs: {
|
||||
items: createExtensionInput([NavItemBlueprint.dataRefs.target]),
|
||||
},
|
||||
output: [coreExtensionData.reactElement],
|
||||
factory({ inputs }) {
|
||||
return [
|
||||
coreExtensionData.reactElement(
|
||||
<nav>
|
||||
<ul>
|
||||
{inputs.items.map((item, index) => {
|
||||
const target = item.get(NavItemBlueprint.dataRefs.target);
|
||||
return (
|
||||
<NavItem
|
||||
key={index}
|
||||
icon={target.icon}
|
||||
title={target.title}
|
||||
routeRef={target.routeRef}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</nav>,
|
||||
),
|
||||
];
|
||||
},
|
||||
});
|
||||
|
||||
/** @public */
|
||||
export class ExtensionQuery<UOutput extends AnyExtensionDataRef> {
|
||||
#node: AppNode;
|
||||
@@ -236,70 +172,6 @@ export class ExtensionTester<UOutput extends AnyExtensionDataRef> {
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Switch to using `renderInTestApp` directly and using `.reactElement()` or `.get(...)` to get the component you w
|
||||
*/
|
||||
render(options?: { config?: JsonObject }): RenderResult {
|
||||
const { config = {} } = options ?? {};
|
||||
|
||||
const [subject] = this.#extensions;
|
||||
if (!subject) {
|
||||
throw new Error(
|
||||
'No subject found. At least one extension should be added to the tester.',
|
||||
);
|
||||
}
|
||||
|
||||
const subjectInternal = toInternalExtensionDefinition(subject.definition);
|
||||
let subjectOverride;
|
||||
// attaching to app/routes to render as index route
|
||||
if (subjectInternal.version === 'v1') {
|
||||
throw new Error('The extension tester does not support v1 extensions');
|
||||
} else if (subjectInternal.version === 'v2') {
|
||||
subjectOverride = createExtension({
|
||||
...subjectInternal,
|
||||
attachTo: { id: 'app/routes', input: 'routes' },
|
||||
output: subjectInternal.output.find(
|
||||
ref => ref.id === coreExtensionData.routePath.id,
|
||||
)
|
||||
? subjectInternal.output
|
||||
: [...subjectInternal.output, coreExtensionData.routePath],
|
||||
factory: params => {
|
||||
const parentOutput = Array.from(
|
||||
subjectInternal.factory(params as any),
|
||||
).filter(val => val.id !== coreExtensionData.routePath.id);
|
||||
|
||||
return [...parentOutput, coreExtensionData.routePath('/')];
|
||||
},
|
||||
});
|
||||
(subjectOverride as any).configSchema = subjectInternal.configSchema;
|
||||
} else {
|
||||
throw new Error('Unsupported extension version');
|
||||
}
|
||||
|
||||
const app = createSpecializedApp({
|
||||
features: [
|
||||
createExtensionOverrides({
|
||||
extensions: [
|
||||
subjectOverride,
|
||||
...this.#extensions.slice(1).map(extension => extension.definition),
|
||||
TestAppNavExtension,
|
||||
RouterBlueprint.make({
|
||||
namespace: 'test',
|
||||
params: {
|
||||
Component: ({ children }) => (
|
||||
<MemoryRouter>{children}</MemoryRouter>
|
||||
),
|
||||
},
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
config: this.#getConfig(config),
|
||||
});
|
||||
|
||||
return render(app.createRoot());
|
||||
}
|
||||
|
||||
#resolveTree() {
|
||||
if (this.#tree) {
|
||||
return this.#tree;
|
||||
|
||||
@@ -20,7 +20,10 @@ import {
|
||||
createExtension,
|
||||
createExtensionInput,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { createExtensionTester } from '@backstage/frontend-test-utils';
|
||||
import {
|
||||
createExtensionTester,
|
||||
renderInTestApp,
|
||||
} from '@backstage/frontend-test-utils';
|
||||
import { waitFor, screen } from '@testing-library/react';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
|
||||
@@ -163,9 +166,11 @@ describe('EntityCardBlueprint', () => {
|
||||
},
|
||||
});
|
||||
|
||||
createExtensionTester(extension, { config: { mock: 'mock test config' } })
|
||||
.add(mockExtension)
|
||||
.render();
|
||||
renderInTestApp(
|
||||
createExtensionTester(extension, { config: { mock: 'mock test config' } })
|
||||
.add(mockExtension)
|
||||
.reactElement(),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('test')).toBeInTheDocument();
|
||||
|
||||
@@ -15,7 +15,10 @@
|
||||
*/
|
||||
import React from 'react';
|
||||
import { EntityContentBlueprint } from './EntityContentBlueprint';
|
||||
import { createExtensionTester } from '@backstage/frontend-test-utils';
|
||||
import {
|
||||
createExtensionTester,
|
||||
renderInTestApp,
|
||||
} from '@backstage/frontend-test-utils';
|
||||
import {
|
||||
coreExtensionData,
|
||||
createExtension,
|
||||
@@ -211,9 +214,11 @@ describe('EntityContentBlueprint', () => {
|
||||
},
|
||||
});
|
||||
|
||||
createExtensionTester(extension, { config: { mock: 'mock test config' } })
|
||||
.add(mockExtension)
|
||||
.render();
|
||||
renderInTestApp(
|
||||
createExtensionTester(extension, { config: { mock: 'mock test config' } })
|
||||
.add(mockExtension)
|
||||
.reactElement(),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('test')).toBeInTheDocument();
|
||||
|
||||
@@ -20,7 +20,10 @@ import {
|
||||
createExtension,
|
||||
createExtensionInput,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { createExtensionTester } from '@backstage/frontend-test-utils';
|
||||
import {
|
||||
createExtensionTester,
|
||||
renderInTestApp,
|
||||
} from '@backstage/frontend-test-utils';
|
||||
import { waitFor, screen } from '@testing-library/react';
|
||||
|
||||
describe('CatalogFilterBlueprint', () => {
|
||||
@@ -89,9 +92,11 @@ describe('CatalogFilterBlueprint', () => {
|
||||
},
|
||||
});
|
||||
|
||||
createExtensionTester(extension, { config: { test: 'mock test config' } })
|
||||
.add(mockExtension)
|
||||
.render();
|
||||
renderInTestApp(
|
||||
createExtensionTester(extension, { config: { test: 'mock test config' } })
|
||||
.add(mockExtension)
|
||||
.reactElement(),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('test')).toBeInTheDocument();
|
||||
|
||||
@@ -16,7 +16,10 @@
|
||||
|
||||
import React from 'react';
|
||||
import { SearchResultListItemBlueprint } from './SearchResultListItemBlueprint';
|
||||
import { createExtensionTester } from '@backstage/frontend-test-utils';
|
||||
import {
|
||||
createExtensionTester,
|
||||
renderInTestApp,
|
||||
} from '@backstage/frontend-test-utils';
|
||||
import {
|
||||
PageBlueprint,
|
||||
createExtensionInput,
|
||||
@@ -107,17 +110,17 @@ describe('SearchResultListItemBlueprint', () => {
|
||||
});
|
||||
|
||||
await expect(
|
||||
createExtensionTester(mockSearchPage)
|
||||
.add(extension)
|
||||
.render()
|
||||
.findByText('noTrack: false'),
|
||||
renderInTestApp(
|
||||
createExtensionTester(mockSearchPage).add(extension).reactElement(),
|
||||
).findByText('noTrack: false'),
|
||||
).resolves.toBeInTheDocument();
|
||||
|
||||
await expect(
|
||||
createExtensionTester(mockSearchPage)
|
||||
.add(extension, { config: { noTrack: true } })
|
||||
.render()
|
||||
.findByText('noTrack: true'),
|
||||
renderInTestApp(
|
||||
createExtensionTester(mockSearchPage)
|
||||
.add(extension, { config: { noTrack: true } })
|
||||
.reactElement(),
|
||||
).findByText('noTrack: true'),
|
||||
).resolves.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user