From e7bf831061532baa8d06f9776c1eaa4a67427e7c Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 26 Nov 2020 16:48:50 +0100 Subject: [PATCH] core-api: add plugin instance collection utility Co-authored-by: blam --- .../core-api/src/plugin/collection.test.tsx | 85 +++++++++++++++++++ packages/core-api/src/plugin/collection.ts | 72 ++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 packages/core-api/src/plugin/collection.test.tsx create mode 100644 packages/core-api/src/plugin/collection.ts diff --git a/packages/core-api/src/plugin/collection.test.tsx b/packages/core-api/src/plugin/collection.test.tsx new file mode 100644 index 0000000000..cb123cd2b1 --- /dev/null +++ b/packages/core-api/src/plugin/collection.test.tsx @@ -0,0 +1,85 @@ +/* + * Copyright 2020 Spotify AB + * + * 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, { PropsWithChildren } from 'react'; +import { createRouteRef } from '../routing'; +import { createPlugin } from '../plugin'; +import { + createRoutableExtension, + createComponentExtension, +} from '../lib/extensions'; +import { MemoryRouter, Routes, Route } from 'react-router-dom'; +import { collectPlugins } from './collection'; + +const mockConfig = () => ({ path: '/foo', title: 'Foo' }); +const MockComponent = ({ children }: PropsWithChildren<{}>) => <>{children}; + +const pluginA = createPlugin({ id: 'my-plugin-a' }); +const pluginB = createPlugin({ id: 'my-plugin-b' }); +const pluginC = createPlugin({ id: 'my-plugin-c' }); + +const ref1 = createRouteRef(mockConfig()); +const ref2 = createRouteRef(mockConfig()); + +const Extension1 = pluginA.provide( + createRoutableExtension({ component: MockComponent, mountPoint: ref1 }), +); +const Extension2 = pluginB.provide( + createRoutableExtension({ component: MockComponent, mountPoint: ref2 }), +); +const Extension3 = pluginA.provide( + createComponentExtension({ component: MockComponent }), +); +const Extension4 = pluginB.provide( + createComponentExtension({ component: MockComponent }), +); +const Extension5 = pluginC.provide( + createComponentExtension({ component: MockComponent }), +); + +describe('collection', () => { + it('should collect the plugins', () => { + const root = ( + + + +
+ +
+
+ {[]} + Some text here shouldn't be a problem +
+ {null} +
+ +
+ + {false} + {true} + {0} +
+ +
+ } /> +
+ + + ); + + expect(collectPlugins(root)).toEqual(new Set([pluginA, pluginB, pluginC])); + }); +}); diff --git a/packages/core-api/src/plugin/collection.ts b/packages/core-api/src/plugin/collection.ts new file mode 100644 index 0000000000..23139deac4 --- /dev/null +++ b/packages/core-api/src/plugin/collection.ts @@ -0,0 +1,72 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ +/* + * Copyright 2020 Spotify AB + * + * 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, { isValidElement, ReactNode } from 'react'; +import { BackstagePlugin } from './types'; +import { getComponentData } from '../lib/componentData'; + +export const collectPlugins = (tree: ReactNode) => { + const plugins = new Set(); + + const nodes = [tree]; + + while (nodes.length !== 0) { + const node = nodes.shift(); + if (!isIterableElement(node)) { + continue; + } + + React.Children.forEach(node, child => { + if (!isIterableElement(child)) { + return; + } + + const { element, children } = child.props as { + element?: ReactNode; + children?: ReactNode; + }; + const plugin = getComponentData(child, 'core.plugin'); + if (plugin) { + plugins.add(plugin); + } + if (isIterableElement(element)) { + nodes.push(element); + } + nodes.push(children); + }); + } + + return plugins; +}; + +function isIterableElement(node: ReactNode): node is JSX.Element { + return isValidElement(node) || Array.isArray(node); +}