frontend-*-api: test updates for extension ID change
Co-authored-by: Camila Belo <camilaibs@gmail.com> Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -20,7 +20,7 @@ import { extractRouteInfoFromAppNode } from './extractRouteInfoFromAppNode';
|
||||
import {
|
||||
AnyRouteRefParams,
|
||||
AppNode,
|
||||
Extension,
|
||||
ExtensionDefinition,
|
||||
RouteRef,
|
||||
coreExtensionData,
|
||||
createExtension,
|
||||
@@ -40,16 +40,16 @@ const ref5 = createRouteRef();
|
||||
const refOrder: RouteRef<AnyRouteRefParams>[] = [ref1, ref2, ref3, ref4, ref5];
|
||||
|
||||
function createTestExtension(options: {
|
||||
id: string;
|
||||
name: string;
|
||||
parent?: string;
|
||||
path?: string;
|
||||
routeRef?: RouteRef;
|
||||
}) {
|
||||
return createExtension({
|
||||
id: options.id,
|
||||
name: options.name,
|
||||
attachTo: options.parent
|
||||
? { id: options.parent, input: 'children' }
|
||||
: { id: 'core.routes', input: 'routes' },
|
||||
? { id: `test/${options.parent}`, input: 'children' }
|
||||
: { id: 'core/routes', input: 'routes' },
|
||||
output: {
|
||||
element: coreExtensionData.reactElement,
|
||||
path: coreExtensionData.routePath.optional(),
|
||||
@@ -70,14 +70,14 @@ function createTestExtension(options: {
|
||||
});
|
||||
}
|
||||
|
||||
function routeInfoFromExtensions(extensions: Extension<unknown>[]) {
|
||||
function routeInfoFromExtensions(extensions: ExtensionDefinition<unknown>[]) {
|
||||
const plugin = createPlugin({
|
||||
id: 'test',
|
||||
extensions,
|
||||
});
|
||||
const tree = createAppTree({
|
||||
builtinExtensions,
|
||||
config: new MockConfigApi({}),
|
||||
builtinExtensions,
|
||||
features: [plugin],
|
||||
});
|
||||
|
||||
@@ -122,33 +122,33 @@ describe('discovery', () => {
|
||||
it('should collect routes', () => {
|
||||
const info = routeInfoFromExtensions([
|
||||
createTestExtension({
|
||||
id: 'nothing',
|
||||
name: 'nothing',
|
||||
path: 'nothing',
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page1',
|
||||
name: 'page1',
|
||||
path: 'foo',
|
||||
routeRef: ref1,
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page2',
|
||||
name: 'page2',
|
||||
parent: 'page1',
|
||||
path: 'bar/:id',
|
||||
routeRef: ref2,
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page3',
|
||||
name: 'page3',
|
||||
parent: 'page2',
|
||||
path: 'baz',
|
||||
routeRef: ref3,
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page4',
|
||||
name: 'page4',
|
||||
path: 'divsoup',
|
||||
routeRef: ref4,
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page5',
|
||||
name: 'page5',
|
||||
parent: 'page1',
|
||||
path: 'blop',
|
||||
routeRef: ref5,
|
||||
@@ -226,29 +226,29 @@ describe('discovery', () => {
|
||||
it('should handle all react router Route patterns', () => {
|
||||
const info = routeInfoFromExtensions([
|
||||
createTestExtension({
|
||||
id: 'page1',
|
||||
name: 'page1',
|
||||
path: 'foo',
|
||||
routeRef: ref1,
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page2',
|
||||
name: 'page2',
|
||||
parent: 'page1',
|
||||
path: 'bar/:id',
|
||||
routeRef: ref2,
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page3',
|
||||
name: 'page3',
|
||||
path: 'baz',
|
||||
routeRef: ref3,
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page4',
|
||||
name: 'page4',
|
||||
parent: 'page3',
|
||||
path: 'divsoup',
|
||||
routeRef: ref4,
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page5',
|
||||
name: 'page5',
|
||||
parent: 'page3',
|
||||
path: 'blop',
|
||||
routeRef: ref5,
|
||||
@@ -274,29 +274,29 @@ describe('discovery', () => {
|
||||
it('should strip leading slashes in route paths', () => {
|
||||
const info = routeInfoFromExtensions([
|
||||
createTestExtension({
|
||||
id: 'page1',
|
||||
name: 'page1',
|
||||
path: '/foo',
|
||||
routeRef: ref1,
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page2',
|
||||
name: 'page2',
|
||||
parent: 'page1',
|
||||
path: '/bar/:id',
|
||||
routeRef: ref2,
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page3',
|
||||
name: 'page3',
|
||||
path: '/baz',
|
||||
routeRef: ref3,
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page4',
|
||||
name: 'page4',
|
||||
parent: 'page3',
|
||||
path: '/divsoup',
|
||||
routeRef: ref4,
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page5',
|
||||
name: 'page5',
|
||||
parent: 'page3',
|
||||
path: '/blop',
|
||||
routeRef: ref5,
|
||||
@@ -322,44 +322,44 @@ describe('discovery', () => {
|
||||
it('should use the route aggregator key to bind child routes to the same path', () => {
|
||||
const info = routeInfoFromExtensions([
|
||||
createTestExtension({
|
||||
id: 'foo',
|
||||
name: 'foo',
|
||||
path: 'foo',
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page1',
|
||||
name: 'page1',
|
||||
parent: 'foo',
|
||||
routeRef: ref1,
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'fooChild',
|
||||
name: 'fooChild',
|
||||
parent: 'foo',
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page2',
|
||||
name: 'page2',
|
||||
parent: 'fooChild',
|
||||
routeRef: ref2,
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'fooEmpty',
|
||||
name: 'fooEmpty',
|
||||
parent: 'foo',
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page3',
|
||||
name: 'page3',
|
||||
path: 'bar',
|
||||
routeRef: ref3,
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page3Child',
|
||||
name: 'page3Child',
|
||||
parent: 'page3',
|
||||
path: '',
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page4',
|
||||
name: 'page4',
|
||||
parent: 'page3Child',
|
||||
routeRef: ref4,
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page5',
|
||||
name: 'page5',
|
||||
parent: 'page4',
|
||||
routeRef: ref5,
|
||||
}),
|
||||
@@ -411,34 +411,34 @@ describe('discovery', () => {
|
||||
it('should use the route aggregator but stop when encountering explicit path', () => {
|
||||
const info = routeInfoFromExtensions([
|
||||
createTestExtension({
|
||||
id: 'page1',
|
||||
name: 'page1',
|
||||
path: 'foo',
|
||||
routeRef: ref1,
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page1Child',
|
||||
name: 'page1Child',
|
||||
parent: 'page1',
|
||||
path: 'bar',
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page2',
|
||||
name: 'page2',
|
||||
parent: 'page1Child',
|
||||
routeRef: ref2,
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page3',
|
||||
name: 'page3',
|
||||
parent: 'page2',
|
||||
path: 'baz',
|
||||
routeRef: ref3,
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page4',
|
||||
name: 'page4',
|
||||
parent: 'page3',
|
||||
path: '/blop',
|
||||
routeRef: ref4,
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page5',
|
||||
name: 'page5',
|
||||
parent: 'page2',
|
||||
routeRef: ref5,
|
||||
}),
|
||||
@@ -500,34 +500,34 @@ describe('discovery', () => {
|
||||
it('should account for loose route paths', () => {
|
||||
const info = routeInfoFromExtensions([
|
||||
createTestExtension({
|
||||
id: 'r',
|
||||
name: 'r',
|
||||
path: 'r',
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page1',
|
||||
name: 'page1',
|
||||
parent: 'r',
|
||||
path: 'x',
|
||||
routeRef: ref1,
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'y',
|
||||
name: 'y',
|
||||
path: 'y',
|
||||
parent: 'r',
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page2',
|
||||
name: 'page2',
|
||||
parent: 'y',
|
||||
path: '1',
|
||||
routeRef: ref2,
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page3',
|
||||
name: 'page3',
|
||||
parent: 'page2',
|
||||
path: 'a',
|
||||
routeRef: ref3,
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page4',
|
||||
name: 'page4',
|
||||
parent: 'page2',
|
||||
path: 'b',
|
||||
routeRef: ref4,
|
||||
|
||||
@@ -54,12 +54,11 @@ describe('createAppTree', () => {
|
||||
it('throws an error when a core extension is overridden', () => {
|
||||
const config = new MockConfigApi({});
|
||||
const features = [
|
||||
createPlugin({
|
||||
id: 'plugin',
|
||||
createExtensionOverrides({
|
||||
extensions: [
|
||||
createExtension({
|
||||
id: 'core',
|
||||
attachTo: { id: 'core.routes', input: 'route' },
|
||||
name: 'core',
|
||||
attachTo: { id: 'core/routes', input: 'route' },
|
||||
inputs: {},
|
||||
output: {},
|
||||
factory: () => ({}),
|
||||
@@ -70,33 +69,7 @@ describe('createAppTree', () => {
|
||||
expect(() =>
|
||||
createAppTree({ features, config, builtinExtensions: [] }),
|
||||
).toThrow(
|
||||
"It is forbidden to override the following extension(s): 'core', which is done by the following plugin(s): 'plugin'",
|
||||
);
|
||||
});
|
||||
|
||||
it('throws an error when duplicated extensions are detected', () => {
|
||||
const config = new MockConfigApi({});
|
||||
|
||||
const ExtensionA = createExtension({ ...extBase, id: 'A' });
|
||||
|
||||
const ExtensionB = createExtension({ ...extBase, id: 'B' });
|
||||
|
||||
const PluginA = createPlugin({
|
||||
id: 'A',
|
||||
extensions: [ExtensionA, ExtensionA],
|
||||
});
|
||||
|
||||
const PluginB = createPlugin({
|
||||
id: 'B',
|
||||
extensions: [ExtensionA, ExtensionB, ExtensionB],
|
||||
});
|
||||
|
||||
const features = [PluginA, PluginB];
|
||||
|
||||
expect(() =>
|
||||
createAppTree({ features, config, builtinExtensions: [] }),
|
||||
).toThrow(
|
||||
"The following extensions are duplicated: The extension 'A' was provided 2 time(s) by the plugin 'A' and 1 time(s) by the plugin 'B', The extension 'B' was provided 2 time(s) by the plugin 'B'",
|
||||
"It is forbidden to override the following extension(s): 'core', which is done by one or more extension overrides",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -106,13 +79,13 @@ describe('createAppTree', () => {
|
||||
features: [
|
||||
createExtensionOverrides({
|
||||
extensions: [
|
||||
createExtension({ ...extBase, id: 'a' }),
|
||||
createExtension({ ...extBase, id: 'a' }),
|
||||
createExtension({ ...extBase, id: 'b' }),
|
||||
createExtension({ ...extBase, name: 'a' }),
|
||||
createExtension({ ...extBase, name: 'a' }),
|
||||
createExtension({ ...extBase, name: 'b' }),
|
||||
],
|
||||
}),
|
||||
createExtensionOverrides({
|
||||
extensions: [createExtension({ ...extBase, id: 'b' })],
|
||||
extensions: [createExtension({ ...extBase, name: 'b' })],
|
||||
}),
|
||||
],
|
||||
config: new MockConfigApi({}),
|
||||
|
||||
@@ -25,18 +25,18 @@ describe('readAppExtensionsConfig', () => {
|
||||
it('should disable extension with shorthand notation', () => {
|
||||
expect(
|
||||
readAppExtensionsConfig(
|
||||
new ConfigReader({ app: { extensions: [{ 'core.router': false }] } }),
|
||||
new ConfigReader({ app: { extensions: [{ 'core/router': false }] } }),
|
||||
),
|
||||
).toEqual([
|
||||
{
|
||||
id: 'core.router',
|
||||
id: 'core/router',
|
||||
disabled: true,
|
||||
},
|
||||
]);
|
||||
expect(
|
||||
readAppExtensionsConfig(
|
||||
new ConfigReader({
|
||||
app: { extensions: [{ 'core.router': { disabled: true } }] },
|
||||
app: { extensions: [{ 'core/router': { disabled: true } }] },
|
||||
}),
|
||||
),
|
||||
).toEqual([
|
||||
@@ -44,7 +44,7 @@ describe('readAppExtensionsConfig', () => {
|
||||
at: undefined,
|
||||
config: undefined,
|
||||
disabled: true,
|
||||
id: 'core.router',
|
||||
id: 'core/router',
|
||||
},
|
||||
]);
|
||||
});
|
||||
@@ -52,33 +52,33 @@ describe('readAppExtensionsConfig', () => {
|
||||
it('should enable extension with shorthand notation', () => {
|
||||
expect(
|
||||
readAppExtensionsConfig(
|
||||
new ConfigReader({ app: { extensions: ['core.router'] } }),
|
||||
new ConfigReader({ app: { extensions: ['core/router'] } }),
|
||||
),
|
||||
).toEqual([
|
||||
{
|
||||
id: 'core.router',
|
||||
id: 'core/router',
|
||||
disabled: false,
|
||||
},
|
||||
]);
|
||||
expect(
|
||||
readAppExtensionsConfig(
|
||||
new ConfigReader({ app: { extensions: [{ 'core.router': true }] } }),
|
||||
new ConfigReader({ app: { extensions: [{ 'core/router': true }] } }),
|
||||
),
|
||||
).toEqual([
|
||||
{
|
||||
id: 'core.router',
|
||||
id: 'core/router',
|
||||
disabled: false,
|
||||
},
|
||||
]);
|
||||
expect(
|
||||
readAppExtensionsConfig(
|
||||
new ConfigReader({
|
||||
app: { extensions: [{ 'core.router': { disabled: false } }] },
|
||||
app: { extensions: [{ 'core/router': { disabled: false } }] },
|
||||
}),
|
||||
),
|
||||
).toEqual([
|
||||
{
|
||||
id: 'core.router',
|
||||
id: 'core/router',
|
||||
disabled: false,
|
||||
},
|
||||
]);
|
||||
@@ -89,12 +89,12 @@ describe('readAppExtensionsConfig', () => {
|
||||
readAppExtensionsConfig(
|
||||
new ConfigReader({
|
||||
app: {
|
||||
extensions: [{ 'core.router': 'some-string' }],
|
||||
extensions: [{ 'core/router': 'some-string' }],
|
||||
},
|
||||
}),
|
||||
),
|
||||
).toThrow(
|
||||
'Invalid extension configuration at app.extensions[0][core.router], value must be a boolean or object',
|
||||
'Invalid extension configuration at app.extensions[0][core/router], value must be a boolean or object',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -156,8 +156,8 @@ describe('expandShorthandExtensionParameters', () => {
|
||||
});
|
||||
|
||||
it('supports string key', () => {
|
||||
expect(run('core.router')).toEqual({
|
||||
id: 'core.router',
|
||||
expect(run('core/router')).toEqual({
|
||||
id: 'core/router',
|
||||
disabled: false,
|
||||
});
|
||||
expect(() => run('')).toThrowErrorMatchingInlineSnapshot(
|
||||
@@ -170,96 +170,96 @@ describe('expandShorthandExtensionParameters', () => {
|
||||
|
||||
it('supports null value', () => {
|
||||
// this is the result of typing:
|
||||
// - core.router:
|
||||
// - core/router:
|
||||
// The missing value is interpreted as null by the yaml parser so we deal with that
|
||||
expect(run({ 'core.router': null })).toEqual({
|
||||
id: 'core.router',
|
||||
expect(run({ 'core/router': null })).toEqual({
|
||||
id: 'core/router',
|
||||
disabled: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('supports boolean value', () => {
|
||||
expect(run({ 'core.router': true })).toEqual({
|
||||
id: 'core.router',
|
||||
expect(run({ 'core/router': true })).toEqual({
|
||||
id: 'core/router',
|
||||
disabled: false,
|
||||
});
|
||||
expect(run({ 'core.router': false })).toEqual({
|
||||
id: 'core.router',
|
||||
expect(run({ 'core/router': false })).toEqual({
|
||||
id: 'core/router',
|
||||
disabled: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should not support string values', () => {
|
||||
expect(() =>
|
||||
run({ 'core.router': 'example-package#MyRouter' }),
|
||||
run({ 'core/router': 'example-package#MyRouter' }),
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Invalid extension configuration at app.extensions[1][core.router], value must be a boolean or object"`,
|
||||
`"Invalid extension configuration at app.extensions[1][core/router], value must be a boolean or object"`,
|
||||
);
|
||||
});
|
||||
|
||||
it('supports object id only in the key', () => {
|
||||
expect(() =>
|
||||
run({ 'core.router': { id: 'some.id' } }),
|
||||
run({ 'core/router': { id: 'some.id' } }),
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Invalid extension configuration at app.extensions[1][core.router].id, unknown parameter; expected one of 'attachTo', 'disabled', 'config'"`,
|
||||
`"Invalid extension configuration at app.extensions[1][core/router].id, unknown parameter; expected one of 'attachTo', 'disabled', 'config'"`,
|
||||
);
|
||||
});
|
||||
|
||||
it('supports object attachTo', () => {
|
||||
expect(
|
||||
run({
|
||||
'core.router': { attachTo: { id: 'other.root', input: 'inputs' } },
|
||||
'core/router': { attachTo: { id: 'other.root', input: 'inputs' } },
|
||||
}),
|
||||
).toEqual({
|
||||
id: 'core.router',
|
||||
id: 'core/router',
|
||||
attachTo: { id: 'other.root', input: 'inputs' },
|
||||
});
|
||||
expect(() =>
|
||||
run({
|
||||
'core.router': {
|
||||
'core/router': {
|
||||
id: 'other-id',
|
||||
},
|
||||
}),
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Invalid extension configuration at app.extensions[1][core.router].id, unknown parameter; expected one of 'attachTo', 'disabled', 'config'"`,
|
||||
`"Invalid extension configuration at app.extensions[1][core/router].id, unknown parameter; expected one of 'attachTo', 'disabled', 'config'"`,
|
||||
);
|
||||
});
|
||||
|
||||
it('supports object disabled', () => {
|
||||
expect(run({ 'core.router': { disabled: true } })).toEqual({
|
||||
id: 'core.router',
|
||||
expect(run({ 'core/router': { disabled: true } })).toEqual({
|
||||
id: 'core/router',
|
||||
disabled: true,
|
||||
});
|
||||
expect(run({ 'core.router': { disabled: false } })).toEqual({
|
||||
id: 'core.router',
|
||||
expect(run({ 'core/router': { disabled: false } })).toEqual({
|
||||
id: 'core/router',
|
||||
disabled: false,
|
||||
});
|
||||
expect(() =>
|
||||
run({ 'core.router': { disabled: 0 } }),
|
||||
run({ 'core/router': { disabled: 0 } }),
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Invalid extension configuration at app.extensions[1][core.router].disabled, must be a boolean"`,
|
||||
`"Invalid extension configuration at app.extensions[1][core/router].disabled, must be a boolean"`,
|
||||
);
|
||||
});
|
||||
|
||||
it('supports object config', () => {
|
||||
expect(
|
||||
run({ 'core.router': { config: { disableRedirects: true } } }),
|
||||
run({ 'core/router': { config: { disableRedirects: true } } }),
|
||||
).toEqual({
|
||||
id: 'core.router',
|
||||
id: 'core/router',
|
||||
config: { disableRedirects: true },
|
||||
});
|
||||
expect(() =>
|
||||
run({ 'core.router': { config: 0 } }),
|
||||
run({ 'core/router': { config: 0 } }),
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Invalid extension configuration at app.extensions[1][core.router].config, must be an object"`,
|
||||
`"Invalid extension configuration at app.extensions[1][core/router].config, must be an object"`,
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects unknown object keys', () => {
|
||||
expect(() =>
|
||||
run({ 'core.router': { foo: { settings: true } } }),
|
||||
run({ 'core/router': { foo: { settings: true } } }),
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Invalid extension configuration at app.extensions[1][core.router].foo, unknown parameter; expected one of 'attachTo', 'disabled', 'config'"`,
|
||||
`"Invalid extension configuration at app.extensions[1][core/router].foo, unknown parameter; expected one of 'attachTo', 'disabled', 'config'"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
createExtensionOverrides,
|
||||
createPlugin,
|
||||
Extension,
|
||||
ExtensionDefinition,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { resolveAppNodeSpecs } from './resolveAppNodeSpecs';
|
||||
|
||||
@@ -27,12 +28,26 @@ function makeExt(
|
||||
attachId: string = 'root',
|
||||
) {
|
||||
return {
|
||||
$$type: '@backstage/Extension',
|
||||
id,
|
||||
attachTo: { id: attachId, input: 'default' },
|
||||
disabled: status === 'disabled',
|
||||
} as Extension<unknown>;
|
||||
}
|
||||
|
||||
function makeExtDef(
|
||||
name: string,
|
||||
status: 'disabled' | 'enabled' = 'enabled',
|
||||
attachId: string = 'root',
|
||||
) {
|
||||
return {
|
||||
$$type: '@backstage/ExtensionDefinition',
|
||||
name,
|
||||
attachTo: { id: attachId, input: 'default' },
|
||||
disabled: status === 'disabled',
|
||||
} as ExtensionDefinition<unknown>;
|
||||
}
|
||||
|
||||
describe('resolveAppNodeSpecs', () => {
|
||||
it('should not filter out disabled extension instances', () => {
|
||||
const a = makeExt('a', 'disabled');
|
||||
@@ -78,9 +93,8 @@ describe('resolveAppNodeSpecs', () => {
|
||||
});
|
||||
|
||||
it('should override attachment points', () => {
|
||||
const a = makeExt('a');
|
||||
const b = makeExt('b');
|
||||
const pluginA = createPlugin({ id: 'test', extensions: [a] });
|
||||
const pluginA = createPlugin({ id: 'test', extensions: [makeExtDef('a')] });
|
||||
expect(
|
||||
resolveAppNodeSpecs({
|
||||
features: [pluginA],
|
||||
@@ -94,8 +108,8 @@ describe('resolveAppNodeSpecs', () => {
|
||||
}),
|
||||
).toEqual([
|
||||
{
|
||||
id: 'a',
|
||||
extension: a,
|
||||
id: 'test/a',
|
||||
extension: makeExt('test/a'),
|
||||
attachTo: { id: 'root', input: 'default' },
|
||||
source: pluginA,
|
||||
disabled: false,
|
||||
@@ -110,31 +124,34 @@ describe('resolveAppNodeSpecs', () => {
|
||||
});
|
||||
|
||||
it('should fully override configuration and duplicate', () => {
|
||||
const a = makeExt('a');
|
||||
const b = makeExt('b');
|
||||
const plugin = createPlugin({ id: 'test', extensions: [a, b] });
|
||||
const a = makeExt('test/a');
|
||||
const b = makeExt('test/b');
|
||||
const plugin = createPlugin({
|
||||
id: 'test',
|
||||
extensions: [makeExtDef('a'), makeExtDef('b')],
|
||||
});
|
||||
expect(
|
||||
resolveAppNodeSpecs({
|
||||
features: [plugin],
|
||||
builtinExtensions: [],
|
||||
parameters: [
|
||||
{
|
||||
id: 'a',
|
||||
id: 'test/a',
|
||||
config: { foo: { bar: 1 } },
|
||||
},
|
||||
{
|
||||
id: 'b',
|
||||
id: 'test/b',
|
||||
config: { foo: { bar: 2 } },
|
||||
},
|
||||
{
|
||||
id: 'b',
|
||||
id: 'test/b',
|
||||
config: { foo: { qux: 3 } },
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toEqual([
|
||||
{
|
||||
id: 'a',
|
||||
id: 'test/a',
|
||||
extension: a,
|
||||
attachTo: { id: 'root', input: 'default' },
|
||||
source: plugin,
|
||||
@@ -142,7 +159,7 @@ describe('resolveAppNodeSpecs', () => {
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
id: 'b',
|
||||
id: 'test/b',
|
||||
extension: b,
|
||||
attachTo: { id: 'root', input: 'default' },
|
||||
source: plugin,
|
||||
@@ -187,11 +204,12 @@ describe('resolveAppNodeSpecs', () => {
|
||||
});
|
||||
|
||||
it('should apply extension overrides', () => {
|
||||
const a = makeExt('a');
|
||||
const b = makeExt('b');
|
||||
const plugin = createPlugin({ id: 'test', extensions: [a, b] });
|
||||
const aOverride = makeExt('a', 'enabled', 'other');
|
||||
const bOverride = makeExt('b', 'disabled', 'other');
|
||||
const plugin = createPlugin({
|
||||
id: 'test',
|
||||
extensions: [makeExtDef('a'), makeExtDef('b')],
|
||||
});
|
||||
const aOverride = makeExt('test/a', 'enabled', 'other');
|
||||
const bOverride = makeExt('test/b', 'disabled', 'other');
|
||||
const cOverride = makeExt('c');
|
||||
|
||||
expect(
|
||||
@@ -199,7 +217,11 @@ describe('resolveAppNodeSpecs', () => {
|
||||
features: [
|
||||
plugin,
|
||||
createExtensionOverrides({
|
||||
extensions: [aOverride, bOverride, cOverride],
|
||||
extensions: [
|
||||
makeExtDef('test/a', 'enabled', 'other'),
|
||||
makeExtDef('test/b', 'disabled', 'other'),
|
||||
makeExtDef('c'),
|
||||
],
|
||||
}),
|
||||
],
|
||||
builtinExtensions: [],
|
||||
@@ -207,14 +229,14 @@ describe('resolveAppNodeSpecs', () => {
|
||||
}),
|
||||
).toEqual([
|
||||
{
|
||||
id: 'a',
|
||||
id: 'test/a',
|
||||
extension: aOverride,
|
||||
attachTo: { id: 'other', input: 'default' },
|
||||
source: plugin,
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
id: 'b',
|
||||
id: 'test/b',
|
||||
extension: bOverride,
|
||||
attachTo: { id: 'other', input: 'default' },
|
||||
source: plugin,
|
||||
@@ -231,39 +253,50 @@ describe('resolveAppNodeSpecs', () => {
|
||||
});
|
||||
|
||||
it('should use order from configuration when rather than overrides', () => {
|
||||
const a = makeExt('a', 'disabled');
|
||||
const b = makeExt('b', 'disabled');
|
||||
const c = makeExt('c', 'disabled');
|
||||
const aOverride = makeExt('c', 'disabled');
|
||||
const bOverride = makeExt('b', 'disabled');
|
||||
const cOverride = makeExt('a', 'disabled');
|
||||
|
||||
const result = resolveAppNodeSpecs({
|
||||
features: [
|
||||
createPlugin({ id: 'test', extensions: [a, b, c] }),
|
||||
createPlugin({
|
||||
id: 'test',
|
||||
extensions: [
|
||||
makeExtDef('a', 'disabled'),
|
||||
makeExtDef('b', 'disabled'),
|
||||
makeExtDef('c', 'disabled'),
|
||||
],
|
||||
}),
|
||||
createExtensionOverrides({
|
||||
extensions: [cOverride, bOverride, aOverride],
|
||||
extensions: [
|
||||
makeExtDef('test/c', 'disabled'),
|
||||
makeExtDef('test/b', 'disabled'),
|
||||
makeExtDef('test/a', 'disabled'),
|
||||
],
|
||||
}),
|
||||
],
|
||||
builtinExtensions: [],
|
||||
parameters: ['b', 'c', 'a'].map(id => ({ id, disabled: false })),
|
||||
parameters: ['test/b', 'test/c', 'test/a'].map(id => ({
|
||||
id,
|
||||
disabled: false,
|
||||
})),
|
||||
});
|
||||
|
||||
expect(result.map(r => r.extension.id)).toEqual(['b', 'c', 'a']);
|
||||
expect(result.map(r => r.extension.id)).toEqual([
|
||||
'test/b',
|
||||
'test/c',
|
||||
'test/a',
|
||||
]);
|
||||
});
|
||||
|
||||
it('throws an error when a forbidden extension is overridden by a plugin', () => {
|
||||
expect(() =>
|
||||
resolveAppNodeSpecs({
|
||||
features: [
|
||||
createPlugin({ id: 'test', extensions: [makeExt('forbidden')] }),
|
||||
createPlugin({ id: 'test', extensions: [makeExtDef('forbidden')] }),
|
||||
],
|
||||
builtinExtensions: [],
|
||||
parameters: [],
|
||||
forbidden: new Set(['forbidden']),
|
||||
forbidden: new Set(['test/forbidden']),
|
||||
}),
|
||||
).toThrow(
|
||||
"It is forbidden to override the following extension(s): 'forbidden', which is done by the following plugin(s): 'test'",
|
||||
"It is forbidden to override the following extension(s): 'test/forbidden', which is done by the following plugin(s): 'test'",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -271,7 +304,7 @@ describe('resolveAppNodeSpecs', () => {
|
||||
expect(() =>
|
||||
resolveAppNodeSpecs({
|
||||
features: [
|
||||
createExtensionOverrides({ extensions: [makeExt('forbidden')] }),
|
||||
createExtensionOverrides({ extensions: [makeExtDef('forbidden')] }),
|
||||
],
|
||||
builtinExtensions: [],
|
||||
parameters: [],
|
||||
|
||||
@@ -16,15 +16,17 @@
|
||||
|
||||
import { createExtension } from '@backstage/frontend-plugin-api';
|
||||
import { resolveAppTree } from './resolveAppTree';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { resolveExtensionDefinition } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition';
|
||||
|
||||
const extBaseConfig = {
|
||||
id: 'test',
|
||||
attachTo: { id: 'nonexistent', input: 'nonexistent' },
|
||||
output: {},
|
||||
factory: () => ({}),
|
||||
};
|
||||
|
||||
const extension = createExtension(extBaseConfig);
|
||||
const extension = resolveExtensionDefinition(
|
||||
createExtension({
|
||||
name: 'test',
|
||||
attachTo: { id: 'nonexistent', input: 'nonexistent' },
|
||||
output: {},
|
||||
factory: () => ({}),
|
||||
}),
|
||||
);
|
||||
|
||||
const baseSpec = {
|
||||
extension,
|
||||
|
||||
@@ -36,7 +36,10 @@ describe('createApp', () => {
|
||||
configLoader: async () =>
|
||||
new MockConfigApi({
|
||||
app: {
|
||||
extensions: [{ 'themes.light': false }, { 'themes.dark': false }],
|
||||
extensions: [
|
||||
{ 'theme:app/light': false },
|
||||
{ 'theme:app/dark': false },
|
||||
],
|
||||
},
|
||||
}),
|
||||
features: [
|
||||
@@ -68,7 +71,6 @@ describe('createApp', () => {
|
||||
id: duplicatedFeatureId,
|
||||
extensions: [
|
||||
createPageExtension({
|
||||
id: 'test.page.first',
|
||||
defaultPath: '/',
|
||||
loader: async () => <div>First Page</div>,
|
||||
}),
|
||||
@@ -78,7 +80,6 @@ describe('createApp', () => {
|
||||
id: duplicatedFeatureId,
|
||||
extensions: [
|
||||
createPageExtension({
|
||||
id: 'test.page.last',
|
||||
defaultPath: '/',
|
||||
loader: async () => <div>Last Page</div>,
|
||||
}),
|
||||
@@ -159,7 +160,6 @@ describe('createApp', () => {
|
||||
id: 'my-plugin',
|
||||
extensions: [
|
||||
createPageExtension({
|
||||
id: 'plugin.my-plugin.page',
|
||||
defaultPath: '/',
|
||||
loader: async () => {
|
||||
const Component = () => {
|
||||
@@ -182,22 +182,22 @@ describe('createApp', () => {
|
||||
expect(String(tree.root)).toMatchInlineSnapshot(`
|
||||
"<core out=[core.reactElement]>
|
||||
root [
|
||||
<core.router out=[core.reactElement]>
|
||||
<core/router out=[core.reactElement]>
|
||||
children [
|
||||
<core.layout out=[core.reactElement]>
|
||||
<core/layout out=[core.reactElement]>
|
||||
content [
|
||||
<core.routes out=[core.reactElement]>
|
||||
<core/routes out=[core.reactElement]>
|
||||
routes [
|
||||
<plugin.my-plugin.page out=[core.routing.path, core.routing.ref, core.reactElement] />
|
||||
<page:my-plugin out=[core.routing.path, core.routing.ref, core.reactElement] />
|
||||
]
|
||||
</core.routes>
|
||||
</core/routes>
|
||||
]
|
||||
nav [
|
||||
<core.nav out=[core.reactElement] />
|
||||
<core/nav out=[core.reactElement] />
|
||||
]
|
||||
</core.layout>
|
||||
</core/layout>
|
||||
]
|
||||
</core.router>
|
||||
</core/router>
|
||||
]
|
||||
components [
|
||||
<core.components.progress out=[component.ref] />
|
||||
@@ -206,8 +206,8 @@ describe('createApp', () => {
|
||||
<core.components.notFoundErrorPage out=[component.ref] />
|
||||
]
|
||||
themes [
|
||||
<themes.light out=[core.theme] />
|
||||
<themes.dark out=[core.theme] />
|
||||
<theme:app/light out=[core.theme] />
|
||||
<theme:app/dark out=[core.theme] />
|
||||
]
|
||||
</core>"
|
||||
`);
|
||||
|
||||
@@ -176,7 +176,7 @@ export function createExtensionTree(options: {
|
||||
);
|
||||
},
|
||||
getRootRoutes(): JSX.Element[] {
|
||||
return this.getExtensionAttachments('core.routes', 'routes').map(node => {
|
||||
return this.getExtensionAttachments('core/routes', 'routes').map(node => {
|
||||
const path = node.getData(coreExtensionData.routePath);
|
||||
const element = node.getData(coreExtensionData.reactElement);
|
||||
const routeRef = node.getData(coreExtensionData.routeRef);
|
||||
@@ -203,7 +203,7 @@ export function createExtensionTree(options: {
|
||||
);
|
||||
};
|
||||
|
||||
return this.getExtensionAttachments('core.nav', 'items')
|
||||
return this.getExtensionAttachments('core/nav', 'items')
|
||||
.map((node, index) => {
|
||||
const target = node.getData(coreExtensionData.navTarget);
|
||||
if (!target) {
|
||||
|
||||
@@ -24,11 +24,10 @@ import { createRouteRef } from '../routing';
|
||||
import { createExtensionTester } from '@backstage/frontend-test-utils';
|
||||
|
||||
const wrapInBoundaryExtension = (element: JSX.Element) => {
|
||||
const id = 'plugin.extension';
|
||||
const routeRef = createRouteRef();
|
||||
return createExtension({
|
||||
id,
|
||||
attachTo: { id: 'core.routes', input: 'routes' },
|
||||
name: 'test',
|
||||
attachTo: { id: 'core/routes', input: 'routes' },
|
||||
output: {
|
||||
element: coreExtensionData.reactElement,
|
||||
path: coreExtensionData.routePath,
|
||||
@@ -89,7 +88,7 @@ describe('ExtensionBoundary', () => {
|
||||
action,
|
||||
subject,
|
||||
context: {
|
||||
extension: 'plugin.extension',
|
||||
extension: 'test',
|
||||
routeRef: 'unknown',
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -37,7 +37,21 @@ describe('createExtensionOverrides', () => {
|
||||
createExtensionOverrides({
|
||||
extensions: [
|
||||
createExtension({
|
||||
id: 'a',
|
||||
name: 'a',
|
||||
attachTo: { id: 'core', input: 'apis' },
|
||||
output: {},
|
||||
factory: () => ({}),
|
||||
}),
|
||||
createExtension({
|
||||
namespace: 'b',
|
||||
attachTo: { id: 'core', input: 'apis' },
|
||||
output: {},
|
||||
factory: () => ({}),
|
||||
}),
|
||||
createExtension({
|
||||
kind: 'k',
|
||||
namespace: 'c',
|
||||
name: 'n',
|
||||
attachTo: { id: 'core', input: 'apis' },
|
||||
output: {},
|
||||
factory: () => ({}),
|
||||
@@ -54,12 +68,39 @@ describe('createExtensionOverrides', () => {
|
||||
"id": "core",
|
||||
"input": "apis",
|
||||
},
|
||||
"configSchema": undefined,
|
||||
"disabled": false,
|
||||
"factory": [Function],
|
||||
"id": "a",
|
||||
"inputs": {},
|
||||
"output": {},
|
||||
},
|
||||
{
|
||||
"$$type": "@backstage/Extension",
|
||||
"attachTo": {
|
||||
"id": "core",
|
||||
"input": "apis",
|
||||
},
|
||||
"configSchema": undefined,
|
||||
"disabled": false,
|
||||
"factory": [Function],
|
||||
"id": "b",
|
||||
"inputs": {},
|
||||
"output": {},
|
||||
},
|
||||
{
|
||||
"$$type": "@backstage/Extension",
|
||||
"attachTo": {
|
||||
"id": "core",
|
||||
"input": "apis",
|
||||
},
|
||||
"configSchema": undefined,
|
||||
"disabled": false,
|
||||
"factory": [Function],
|
||||
"id": "k:c/n",
|
||||
"inputs": {},
|
||||
"output": {},
|
||||
},
|
||||
],
|
||||
"featureFlags": [],
|
||||
"version": "v1",
|
||||
@@ -71,7 +112,7 @@ describe('createExtensionOverrides', () => {
|
||||
const overrides = createExtensionOverrides({
|
||||
extensions: [
|
||||
createExtension({
|
||||
id: 'a',
|
||||
namespace: 'a',
|
||||
attachTo: { id: 'core', input: 'apis' },
|
||||
output: {},
|
||||
factory: () => ({}),
|
||||
|
||||
Reference in New Issue
Block a user