diff --git a/packages/frontend-app-api/src/graph/createAppGraph.test.ts b/packages/frontend-app-api/src/graph/createAppGraph.test.ts new file mode 100644 index 0000000000..2c9f062199 --- /dev/null +++ b/packages/frontend-app-api/src/graph/createAppGraph.test.ts @@ -0,0 +1,123 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + createExtension, + createExtensionOverrides, + createPlugin, +} from '@backstage/frontend-plugin-api'; +import { MockConfigApi } from '@backstage/test-utils'; +import { createAppGraph } from './createAppGraph'; + +const extBase = { + id: 'test', + attachTo: { id: 'core', input: 'root' }, + output: {}, + factory() {}, +}; + +describe('createAppGraph', () => { + it('throws an error when a core extension is parametrized', () => { + const config = new MockConfigApi({ + app: { + extensions: [ + { + core: {}, + }, + ], + }, + }); + const features = [ + createPlugin({ + id: 'plugin', + extensions: [], + }), + ]; + expect(() => + createAppGraph({ features, config, builtinExtensions: [] }), + ).toThrow("Configuration of the 'core' extension is forbidden"); + }); + + it('throws an error when a core extension is overridden', () => { + const config = new MockConfigApi({}); + const features = [ + createPlugin({ + id: 'plugin', + extensions: [ + createExtension({ + id: 'core', + attachTo: { id: 'core.routes', input: 'route' }, + inputs: {}, + output: {}, + factory() {}, + }), + ], + }), + ]; + expect(() => + createAppGraph({ 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(() => + createAppGraph({ 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('throws an error when duplicated extension overrides are detected', () => { + expect(() => + createAppGraph({ + features: [ + createExtensionOverrides({ + extensions: [ + createExtension({ ...extBase, id: 'a' }), + createExtension({ ...extBase, id: 'a' }), + createExtension({ ...extBase, id: 'b' }), + ], + }), + createExtensionOverrides({ + extensions: [createExtension({ ...extBase, id: 'b' })], + }), + ], + config: new MockConfigApi({}), + builtinExtensions: [], + }), + ).toThrow('The following extensions had duplicate overrides: a, b'); + }); +}); diff --git a/packages/frontend-app-api/src/wiring/createApp.test.tsx b/packages/frontend-app-api/src/wiring/createApp.test.tsx index de574bc6a9..e9fb48f957 100644 --- a/packages/frontend-app-api/src/wiring/createApp.test.tsx +++ b/packages/frontend-app-api/src/wiring/createApp.test.tsx @@ -15,123 +15,15 @@ */ import { - createExtension, - createExtensionOverrides, createPageExtension, createPlugin, - createRouteRef, createThemeExtension, } from '@backstage/frontend-plugin-api'; -import { createApp, createInstances } from './createApp'; import { screen, waitFor } from '@testing-library/react'; +import { createApp } from './createApp'; import { MockConfigApi, renderWithEffects } from '@backstage/test-utils'; import React from 'react'; -const extBaseConfig = { - id: 'test', - attachTo: { id: 'root', input: 'default' }, - output: {}, - factory() {}, -}; - -describe('createInstances', () => { - it('throws an error when a core extension is parametrized', () => { - const config = new MockConfigApi({ - app: { - extensions: [ - { - core: {}, - }, - ], - }, - }); - const features = [ - createPlugin({ - id: 'plugin', - extensions: [], - }), - ]; - expect(() => createInstances({ config, features })).toThrow( - "A 'core' extension configuration was detected, but the core extension is not configurable", - ); - }); - - it('throws an error when a core extension is overridden', () => { - const config = new MockConfigApi({}); - const features = [ - createPlugin({ - id: 'plugin', - extensions: [ - createExtension({ - id: 'core', - attachTo: { id: 'core.routes', input: 'route' }, - inputs: {}, - output: {}, - factory() {}, - }), - ], - }), - ]; - expect(() => createInstances({ config, features })).toThrow( - "The following plugin(s) are overriding the 'core' extension which is forbidden: plugin", - ); - }); - - it('throws an error when duplicated extensions are detected', () => { - const config = new MockConfigApi({}); - - const ExtensionA = createPageExtension({ - id: 'A', - defaultPath: '/', - routeRef: createRouteRef(), - loader: async () =>
Extension A
, - }); - - const ExtensionB = createPageExtension({ - id: 'B', - defaultPath: '/', - routeRef: createRouteRef(), - loader: async () =>
Extension B
, - }); - - const PluginA = createPlugin({ - id: 'A', - extensions: [ExtensionA, ExtensionA], - }); - - const PluginB = createPlugin({ - id: 'B', - extensions: [ExtensionA, ExtensionB, ExtensionB], - }); - - const features = [PluginA, PluginB]; - - expect(() => createInstances({ config, features })).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('throws an error when duplicated extension overrides are detected', () => { - expect(() => - createInstances({ - config: new MockConfigApi({}), - features: [ - createExtensionOverrides({ - extensions: [ - createExtension({ ...extBaseConfig, id: 'a' }), - createExtension({ ...extBaseConfig, id: 'a' }), - createExtension({ ...extBaseConfig, id: 'b' }), - ], - }), - createExtensionOverrides({ - extensions: [createExtension({ ...extBaseConfig, id: 'b' })], - }), - ], - }), - ).toThrow('The following extensions had duplicate overrides: a, b'); - }); -}); - describe('createApp', () => { it('should allow themes to be installed', async () => { const app = createApp({ @@ -161,90 +53,6 @@ describe('createApp', () => { await expect(screen.findByText('Derp')).resolves.toBeInTheDocument(); }); - it('should log an app', () => { - const { coreInstance } = createInstances({ - config: new MockConfigApi({}), - features: [], - }); - - expect(String(coreInstance)).toMatchInlineSnapshot(` - " - root [ - - content [ - - ] - nav [ - - ] - - ] - themes [ - - - ] - " - `); - }); - - it('should serialize an app as JSON', () => { - const { coreInstance } = createInstances({ - config: new MockConfigApi({}), - features: [], - }); - - expect(JSON.parse(JSON.stringify(coreInstance))).toMatchInlineSnapshot(` - { - "attachments": { - "root": [ - { - "attachments": { - "content": [ - { - "id": "core.routes", - "output": [ - "core.reactElement", - ], - }, - ], - "nav": [ - { - "id": "core.nav", - "output": [ - "core.reactElement", - ], - }, - ], - }, - "id": "core.layout", - "output": [ - "core.reactElement", - ], - }, - ], - "themes": [ - { - "id": "themes.light", - "output": [ - "core.theme", - ], - }, - { - "id": "themes.dark", - "output": [ - "core.theme", - ], - }, - ], - }, - "id": "core", - "output": [ - "core.reactElement", - ], - } - `); - }); - it('should deduplicate features keeping the last received one', async () => { const duplicatedFeatureId = 'test'; const app = createApp({