diff --git a/packages/frontend-app-api/src/tree/resolveAppTree.test.ts b/packages/frontend-app-api/src/tree/resolveAppTree.test.ts index 740f05d5f7..ac12d6284b 100644 --- a/packages/frontend-app-api/src/tree/resolveAppTree.test.ts +++ b/packages/frontend-app-api/src/tree/resolveAppTree.test.ts @@ -14,7 +14,12 @@ * limitations under the License. */ -import { createExtension, Extension } from '@backstage/frontend-plugin-api'; +import { + coreExtensionData, + createExtension, + createExtensionInput, + Extension, +} 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'; @@ -165,4 +170,99 @@ describe('buildAppTree', () => { ]), ).toThrow("Unexpected duplicate extension id 'a'"); }); + + describe('redirects', () => { + it('should throw an error when theres a duplicate redirect target', () => { + const e1 = resolveExtensionDefinition( + createExtension({ + name: 'test', + attachTo: { id: 'nonexistent', input: 'nonexistent' }, + inputs: { + test: createExtensionInput([coreExtensionData.reactElement], { + replaces: [{ id: 'a', input: 'test' }], + }), + }, + output: [], + factory: () => [], + }), + ) as Extension; + + const e2 = resolveExtensionDefinition( + createExtension({ + name: 'test-2', + attachTo: { id: 'nonexistent', input: 'nonexistent' }, + inputs: { + test: createExtensionInput([coreExtensionData.reactElement], { + replaces: [{ id: 'a', input: 'test' }], + }), + }, + output: [], + factory: () => [], + }), + ) as Extension; + + expect(() => + resolveAppTree('a', [ + { ...baseSpec, id: 'a', extension: e1 }, + { ...baseSpec, id: 'b', extension: e2 }, + ]), + ).toThrow("Duplicate redirect target for input 'test' in extension 'b'"); + }); + }); + + it('should set the correct attachment point for a redirect', () => { + const e1 = resolveExtensionDefinition( + createExtension({ + name: 'test', + attachTo: { id: 'nonexistent', input: 'nonexistent' }, + inputs: { + test: createExtensionInput([coreExtensionData.reactElement], { + replaces: [{ id: 'replace', input: 'me' }], + }), + }, + output: [], + factory: () => [], + }), + ) as Extension; + + const e2 = resolveExtensionDefinition( + createExtension({ + name: 'test-2', + attachTo: { id: 'replace', input: 'me' }, + output: [], + factory: () => [], + }), + ) as Extension; + + const tree = resolveAppTree('a', [ + { attachTo: e1.attachTo, id: 'a', extension: e1, disabled: false }, + { attachTo: e2.attachTo, id: 'b', extension: e2, disabled: false }, + ]); + + expect(tree.root).toMatchInlineSnapshot(` + { + "attachments": { + "test": [ + { + "attachments": undefined, + "id": "b", + "output": undefined, + }, + ], + }, + "id": "a", + "output": undefined, + } + `); + + expect(tree.orphans).toMatchInlineSnapshot(`[]`); + + expect(String(tree.root)).toMatchInlineSnapshot(` + " + test [ + + ] + " + `); + }); }); diff --git a/packages/frontend-app-api/src/tree/resolveAppTree.ts b/packages/frontend-app-api/src/tree/resolveAppTree.ts index 1e6af123b1..054ad26f4f 100644 --- a/packages/frontend-app-api/src/tree/resolveAppTree.ts +++ b/packages/frontend-app-api/src/tree/resolveAppTree.ts @@ -119,6 +119,11 @@ export function resolveAppTree( if (input.replaces) { for (const replace of input.replaces) { const key = makeRedirectKey(replace); + if (redirectTargetsByKey.has(key)) { + throw new Error( + `Duplicate redirect target for input '${inputName}' in extension '${spec.id}'`, + ); + } redirectTargetsByKey.set(key, { id: spec.id, input: inputName }); } }