diff --git a/packages/frontend-plugin-api/src/extensions/AppRootWrapperBlueprint.test.tsx b/packages/frontend-plugin-api/src/extensions/AppRootWrapperBlueprint.test.tsx new file mode 100644 index 0000000000..b67fc3664d --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/AppRootWrapperBlueprint.test.tsx @@ -0,0 +1,127 @@ +/* + * Copyright 2024 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 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 { + coreExtensionData, + createExtension, + createExtensionInput, +} from '../wiring'; +import { each } from 'lodash'; + +describe('AppRootWrapperBlueprint', () => { + it('should return an extension with sensible defaults', () => { + const extension = AppRootWrapperBlueprint.make({ + params: { + Component: () =>
Hello
, + }, + }); + + expect(extension).toMatchInlineSnapshot(` + { + "$$type": "@backstage/ExtensionDefinition", + "attachTo": { + "id": "app/root", + "input": "elements", + }, + "configSchema": undefined, + "disabled": false, + "factory": [Function], + "inputs": {}, + "kind": "app-root-wrapper", + "name": undefined, + "namespace": undefined, + "output": [ + [Function], + ], + "toString": [Function], + "version": "v2", + } + `); + }); + + it('should render the simple component wrapper', async () => { + const extension = AppRootWrapperBlueprint.make({ + params: { + Component: () =>
Hello
, + }, + }); + + const { getByText } = createExtensionTester( + PageBlueprint.make({ + params: { + defaultPath: '/', + loader: async () =>
, + }, + }), + ) + .add(extension) + .render(); + + await waitFor(() => expect(getByText('Hello')).toBeInTheDocument()); + }); + + it('should render the complex component wrapper', async () => { + const extension = AppRootWrapperBlueprint.make({ + namespace: 'ns', + name: 'test', + config: { + schema: { + name: z => z.string(), + }, + }, + inputs: { + children: createExtensionInput([coreExtensionData.reactElement]), + }, + *factory(originalFactory, { inputs, config }) { + yield* originalFactory({ + Component: ({ children }) => ( +
+ {children} +
+ ), + }); + }, + }); + + const { getByText, getByTestId } = createExtensionTester( + PageBlueprint.make({ + params: { + defaultPath: '/', + loader: async () =>
Hi
, + }, + }), + ) + .add(extension, { config: { name: 'Robin' } }) + .add( + createExtension({ + attachTo: { id: 'app-root-wrapper:ns/test', input: 'children' }, + output: [coreExtensionData.reactElement], + factory: () => [coreExtensionData.reactElement(
Its Me
)], + }), + ) + .render(); + + await waitFor(() => { + expect(getByText('Its Me')).toBeInTheDocument(); + expect(getByText('Hi')).toBeInTheDocument(); + expect(getByTestId('Robin-1')).toBeInTheDocument(); + }); + }); +}); diff --git a/packages/frontend-plugin-api/src/extensions/AppRootWrapperBlueprint.tsx b/packages/frontend-plugin-api/src/extensions/AppRootWrapperBlueprint.tsx new file mode 100644 index 0000000000..5bcbaa49bd --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/AppRootWrapperBlueprint.tsx @@ -0,0 +1,36 @@ +/* + * Copyright 2024 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 React from 'react'; +import { ComponentType, PropsWithChildren } from 'react'; +import { createExtensionBlueprint } from '../wiring'; +import { createAppRootWrapperExtension } from './createAppRootWrapperExtension'; + +export const AppRootWrapperBlueprint = createExtensionBlueprint({ + kind: 'app-root-wrapper', + attachTo: { id: 'app/root', input: 'elements' }, + output: [createAppRootWrapperExtension.componentDataRef], + dataRefs: { + component: createAppRootWrapperExtension.componentDataRef, + }, + *factory(params: { Component: ComponentType> }) { + // todo(blam): not sure that this wrapping is even necessary anymore. + const Component = (props: PropsWithChildren<{}>) => { + return {props.children}; + }; + + yield createAppRootWrapperExtension.componentDataRef(Component); + }, +});