Skip to content

Commit 5f013fc

Browse files
committed
refactor: change plugins hook to a component
1 parent 4d7a58d commit 5f013fc

File tree

3 files changed

+81
-81
lines changed

3 files changed

+81
-81
lines changed

src/components/PluggableComponent/hooks.jsx renamed to src/components/PluggableComponent/MultiplePlugins.jsx

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,17 @@
11
import React, { useState, useEffect, useRef } from 'react';
22
import loadable from '@loadable/component';
33
import useDeepCompareEffect from 'use-deep-compare-effect';
4+
import PropTypes from 'prop-types';
45

56
import { isPluginAvailable, getPluginsByPrefix } from './utils';
67

7-
/**
8-
* Component that dynamically loads and displays a list of plugins.
9-
*
10-
* @param {Object} props - The component props.
11-
* @param {React.ReactNode} props.children - Children components to be passed to each plugin.
12-
* @param {Array} props.plugins - An array of plugin objects to be loaded.
13-
* @param {React.ReactNode} props.loadingComponent - Component to display while each plugin is loading.
14-
* @param {Object} props.pluggableComponentProps - Additional props to be passed to each dynamically loaded plugin.
15-
* @returns {React.ReactNode} The rendered component.
16-
*/
17-
export const usePlugins = (plugins, pluggableComponentProps, prefix, loadingComponent) => {
8+
const MultiplePlugins = ({
9+
plugins,
10+
pluggableComponentProps,
11+
prefix,
12+
loadingComponent,
13+
containerPluginsProps,
14+
}) => {
1815
const [pluginComponents, setPluginComponents] = useState({});
1916
const loadedAllPluginsRef = useRef(null);
2017

@@ -89,5 +86,34 @@ export const usePlugins = (plugins, pluggableComponentProps, prefix, loadingComp
8986
}
9087
}, [pluggableComponentProps]);
9188

92-
return pluginComponents;
89+
return (
90+
<div {...containerPluginsProps}>
91+
{Object.entries(pluginComponents).map(([pluginKey, Component]) => (
92+
<React.Fragment key={pluginKey}>
93+
{Component}
94+
</React.Fragment>
95+
))}
96+
</div>
97+
);
9398
};
99+
100+
MultiplePlugins.defaultProps = {
101+
plugins: [],
102+
pluggableComponentProps: {},
103+
prefix: '',
104+
loadingComponent: null,
105+
containerPluginsProps: {},
106+
};
107+
108+
MultiplePlugins.propTypes = {
109+
plugins: PropTypes.arrayOf(PropTypes.shape({
110+
name: PropTypes.string.isRequired,
111+
id: PropTypes.string.isRequired,
112+
})),
113+
pluggableComponentProps: PropTypes.shape({}),
114+
prefix: PropTypes.string,
115+
loadingComponent: PropTypes.node,
116+
containerPluginsProps: PropTypes.shape({}),
117+
};
118+
119+
export default MultiplePlugins;

src/components/PluggableComponent/hooks.test.jsx renamed to src/components/PluggableComponent/MultiplePlugins.test.jsx

Lines changed: 33 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,51 @@
11
import React from 'react';
22
import { render, waitFor } from '@testing-library/react';
3-
import { usePlugins } from './hooks';
4-
5-
describe('usePlugins', () => {
6-
beforeEach(() => {
7-
jest.resetModules();
8-
jest.clearAllMocks();
9-
});
3+
import MultiplePlugins from './MultiplePlugins';
104

5+
describe('MultiplePlugins', () => {
116
const mockPlugins = [
127
{ id: 'plugin1', name: 'Plugin1' },
138
{ id: 'plugin2', name: 'Plugin2' },
149
];
1510

16-
/* eslint-disable react/prop-types */
17-
const ComponentsContainer = ({
18-
plugins = [],
19-
pluggableComponentProps = {},
20-
prefix = '',
21-
loadingComponent,
22-
}) => {
23-
const pluginComponents = usePlugins(
24-
plugins,
25-
pluggableComponentProps,
26-
prefix,
27-
loadingComponent,
28-
);
11+
beforeEach(() => {
12+
jest.resetModules();
13+
jest.clearAllMocks();
14+
});
2915

30-
return (
31-
<>
32-
{Object.entries(pluginComponents).map(([pluginKey, Component]) => (
33-
<div key={pluginKey} data-testid={pluginKey}>
34-
{Component}
35-
</div>
36-
))}
37-
</>
16+
/* eslint-disable react/prop-types */
17+
test('initializes with loading components for each plugin', async () => {
18+
const { getAllByText } = render(
19+
<MultiplePlugins
20+
plugins={mockPlugins}
21+
pluggableComponentProps={{}}
22+
loadingComponent={<div>Loading...</div>}
23+
/>,
3824
);
39-
};
40-
41-
const renderComponent = (overrideProps) => render(<ComponentsContainer {...overrideProps} />);
4225

43-
test('initializes with loading components for each plugin', async () => {
44-
const { getByTestId } = renderComponent({
45-
plugins: mockPlugins,
46-
pluggableComponentProps: {},
47-
loadingComponent: <div>Loading...</div>,
48-
});
26+
const [pluginLoading1, pluginLoading2] = getAllByText('Loading...');
4927

50-
expect(getByTestId('plugin1')).toHaveTextContent('Loading...');
51-
expect(getByTestId('plugin2')).toHaveTextContent('Loading...');
28+
expect(pluginLoading1).toBeInTheDocument();
29+
expect(pluginLoading1).toHaveTextContent('Loading...');
30+
expect(pluginLoading2).toBeInTheDocument();
31+
expect(pluginLoading2).toHaveTextContent('Loading...');
5232
});
5333

5434
test('loads a plugins list successfully', async () => {
5535
const mockValidPlugins = [
5636
{ id: 'plugin1', name: 'communications-app-test-component' },
5737
];
5838

59-
const MockPluginComponent = () => <div>Mocked Plugin Component</div>;
39+
const MockPluginComponent = () => <div data-testid="plugin1">Mocked Plugin Component</div>;
6040

6141
jest.mock(
6242
'@node_modules/@openedx-plugins/communications-app-test-component',
6343
() => MockPluginComponent,
6444
);
6545

66-
const { getByTestId } = renderComponent({
67-
plugins: mockValidPlugins,
68-
pluggableComponentProps: {},
69-
});
46+
const { getByTestId } = render(
47+
<MultiplePlugins plugins={mockValidPlugins} pluggableComponentProps={{}} />,
48+
);
7049

7150
await waitFor(() => {
7251
const pluginComponent = getByTestId('plugin1');
@@ -76,17 +55,19 @@ describe('usePlugins', () => {
7655
});
7756

7857
test('loads a plugin successfully with prefix', async () => {
79-
const MockPluginComponent = () => <div>Mocked Plugin Component</div>;
58+
const MockPluginComponent = () => <div data-testid="communications-app-test-component">Mocked Plugin Component</div>;
8059

8160
jest.mock(
8261
'@node_modules/@openedx-plugins/communications-app-test-component',
8362
() => MockPluginComponent,
8463
);
8564

86-
const { getByTestId } = renderComponent({
87-
pluggableComponentProps: {},
88-
prefix: 'communications-app-test',
89-
});
65+
const { getByTestId } = render(
66+
<MultiplePlugins
67+
pluggableComponentProps={{}}
68+
prefix="communications-app-test"
69+
/>,
70+
);
9071

9172
await waitFor(() => {
9273
const pluginComponent = getByTestId('communications-app-test-component');
@@ -96,23 +77,15 @@ describe('usePlugins', () => {
9677
});
9778

9879
test('loads a plugin successfully with prefix changing component props', async () => {
99-
// eslint-disable-next-line react/prop-types
100-
const MockPluginComponent = (props) => {
101-
console.log('props', props);
102-
return <div data-testid="mock-plugin-props">{props.title}</div>;
103-
};
80+
const MockPluginComponent = (props) => <div data-testid="mock-plugin-props">{props.title}</div>;
10481

10582
jest.mock(
10683
'@node_modules/@openedx-plugins/communications-app-test-component',
10784
() => MockPluginComponent,
10885
);
10986

110-
const ComponentsContainerUpdater = (overrideProps) => (
111-
<ComponentsContainer {...overrideProps} />
112-
);
113-
11487
const { getByTestId, rerender } = render(
115-
<ComponentsContainerUpdater
88+
<MultiplePlugins
11689
prefix="communications-app-test"
11790
pluggableComponentProps={{ title: 'Initial Title' }}
11891
/>,
@@ -126,7 +99,7 @@ describe('usePlugins', () => {
12699
});
127100

128101
rerender(
129-
<ComponentsContainerUpdater
102+
<MultiplePlugins
130103
prefix="communications-app-test"
131104
pluggableComponentProps={{ title: 'Title updated' }}
132105
/>,

src/components/PluggableComponent/index.jsx

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
44
import useDeepCompareEffect from 'use-deep-compare-effect';
55

66
import { isPluginAvailable } from './utils';
7-
import { usePlugins } from './hooks';
7+
import MultiplePlugins from './MultiplePlugins';
88

99
/**
1010
* PluggableComponent - A component that allows dynamic loading and replacement of child components.
@@ -37,7 +37,6 @@ const PluggableComponent = ({
3737
const [newComponent, setNewComponent] = useState(children || null);
3838
const loadedComponentRef = useRef(null);
3939
const [isLoadingComponent, setIsLoadingComponent] = useState(false);
40-
const pluginComponents = usePlugins(plugins, pluggableComponentProps, pluginsPrefix, loadingComponent);
4140
const hasConfigForMultiplePlugins = pluginsPrefix || plugins.length;
4241

4342
useEffect(() => {
@@ -93,13 +92,15 @@ const PluggableComponent = ({
9392

9493
if (hasConfigForMultiplePlugins) {
9594
return (
96-
<div {...containerPluginsProps}>
97-
{Object.entries(pluginComponents).map(([pluginKey, Component]) => (
98-
<React.Fragment key={pluginKey}>
99-
{Component}
100-
</React.Fragment>
101-
))}
102-
</div>
95+
<MultiplePlugins
96+
prefix={pluginsPrefix}
97+
{...{
98+
plugins,
99+
pluggableComponentProps,
100+
loadingComponent,
101+
containerPluginsProps,
102+
}}
103+
/>
103104
);
104105
}
105106

0 commit comments

Comments
 (0)