test(frontend-app-api): cover more levels of cyclical dependencies

Signed-off-by: Camila Belo <camilaibs@gmail.com>
This commit is contained in:
Camila Belo
2023-09-20 15:11:05 +02:00
parent cc94fbd3b3
commit cfd94f29ed
@@ -14,10 +14,18 @@
* limitations under the License.
*/
import { createExtension, createPlugin } from '@backstage/frontend-plugin-api';
import {
createExtension,
createExtensionDataRef,
createExtensionInput,
createPageExtension,
createPlugin,
} from '@backstage/frontend-plugin-api';
import { createInstances } from './createApp';
import { MockConfigApi } from '@backstage/test-utils';
import React from 'react';
import { createRouteRef } from '@backstage/core-plugin-api';
describe('createInstances', () => {
it('throws an error when a root extension is parametrized', () => {
@@ -63,4 +71,170 @@ describe('createInstances', () => {
'The following plugins are overriding root extensions and root extensions cannot be overridden: plugin',
);
});
describe('throws an error when immediate cyclical dependencies are found', () => {
it('in an immediate level (e.g., A(plugin.page) -> A(plugin.page))', () => {
const config = new MockConfigApi({});
const addonExtensionData =
createExtensionDataRef<JSX.Element>('plugin.page.addon');
const addon = createExtension({
id: 'plugin.page', // cyclical
at: 'plugin.page/addons',
inputs: {},
output: {
element: addonExtensionData,
},
factory({ bind }) {
bind({
element: <div key="addon">Addon</div>,
});
},
});
const page = createPageExtension({
id: 'plugin.page',
defaultPath: '/',
routeRef: createRouteRef({ id: 'plugins.page.addon' }),
inputs: {
addons: createExtensionInput({
element: addonExtensionData,
}),
},
loader: async ({ inputs }) => (
<div>Page {inputs.addons.map(({ element }) => element)}</div>
),
});
const plugins = [
createPlugin({
id: 'plugin',
extensions: [page, addon],
}),
];
expect(() => createInstances({ config, plugins })).toThrow(
'There is a cyclical dependency with the extension "plugin.page": core.layout → core.routes → plugin.page → plugin.page',
);
});
it('in an intermediate level (e.g., A(core.routes) -> B(plugin.page) -> A(core.routes))', () => {
const config = new MockConfigApi({});
const addonExtensionData =
createExtensionDataRef<JSX.Element>('plugin.page.addon');
const addon = createExtension({
id: 'core.routes', // cyclical
at: 'plugin.page/addons',
inputs: {},
output: {
element: addonExtensionData,
},
factory({ bind }) {
bind({
element: <div key="addon">Addon</div>,
});
},
});
const page = createPageExtension({
id: 'plugin.page',
defaultPath: '/',
routeRef: createRouteRef({ id: 'plugins.page.addon' }),
inputs: {
addons: createExtensionInput({
element: addonExtensionData,
}),
},
loader: async ({ inputs }) => (
<div>Page {inputs.addons.map(({ element }) => element)}</div>
),
});
const plugins = [
createPlugin({
id: 'plugin',
extensions: [page, addon],
}),
];
expect(() => createInstances({ config, plugins })).toThrow(
'There is a cyclical dependency with the extension "core.routes": core.layout → core.routes → plugin.page → core.routes',
);
});
it('in an deep level (e.g., A(core.layout) -> B(core.routes) -> C(plugin.page) -> D(plugin.page.addon) -> B(core.routes))', () => {
const config = new MockConfigApi({});
const addonRendererInput = createExtensionDataRef<() => JSX.Element>(
'plugin.page.addon.renderer',
);
const renderer = createExtension({
id: 'core.routes', // cyclical
at: 'plugin.page.addon/renderer',
inputs: {},
output: {
renderer: addonRendererInput,
},
factory({ bind }) {
bind({
renderer: () => <div key="addon">Addon</div>,
});
},
});
const addonElementInput = createExtensionDataRef<JSX.Element>(
'plugin.page.addon.element',
);
const addon = createExtension({
id: 'plugin.page.addon',
at: 'plugin.page/addons',
inputs: {
renderer: createExtensionInput(
{
element: addonRendererInput,
},
{ singleton: true, required: false },
),
},
output: {
element: addonElementInput,
},
factory({ bind, inputs }) {
bind({
element: inputs.renderer?.element() ?? <div key="addon">Addon</div>,
});
},
});
const page = createPageExtension({
id: 'plugin.page',
defaultPath: '/',
routeRef: createRouteRef({ id: 'plugins.page.addon' }),
inputs: {
addons: createExtensionInput({
element: addonElementInput,
}),
},
loader: async ({ inputs }) => (
<div>Page {inputs.addons.map(({ element }) => element)}</div>
),
});
const plugins = [
createPlugin({
id: 'plugin',
extensions: [page, addon, renderer],
}),
];
expect(() => createInstances({ config, plugins })).toThrow(
'There is a cyclical dependency with the extension "core.routes": core.layout → core.routes → plugin.page → plugin.page.addon → core.routes',
);
});
});
});