diff --git a/README.rst b/README.rst
index 7b6208bc..ca392666 100644
--- a/README.rst
+++ b/README.rst
@@ -251,15 +251,16 @@ Wrap
''''
Unlike Modify, the Wrap operation adds a React component around the widget, and a single widget can receive more than
-one wrap operation. Each wrapper function takes in a ``component`` and ``id`` prop.
+one wrap operation. Each wrapper function takes in a ``component``, ``id`` and ``pluginProps`` prop.
.. code-block::
- const wrapWidget = ({ component, idx }) => (
+ const wrapWidget = ({ component, idx, pluginProps }) => (
This is a wrapper component that is placed around the widget.
{component}
With this wrapper, you can add anything before or after the widget.
+
You can use the pluginProps to pass in any additional props to the wrapper: {pluginProps.prop1}
);
@@ -272,6 +273,9 @@ one wrap operation. Each wrapper function takes in a ``component`` and ``id`` pr
{
op: PLUGIN_OPERATIONS.Wrap,
widgetId: 'default_contents',
+ pluginProps: {
+ prop1: 'prop1',
+ },
wrapper: wrapWidget,
}
diff --git a/src/plugins/PluginSlot.jsx b/src/plugins/PluginSlot.jsx
index 99e53d8e..44bf6120 100644
--- a/src/plugins/PluginSlot.jsx
+++ b/src/plugins/PluginSlot.jsx
@@ -93,6 +93,7 @@ function BasePluginSlot({
wrapComponent(
() => container,
pluginConfig.wrappers,
+ pluginProps,
),
);
} else {
diff --git a/src/plugins/PluginSlot.test.jsx b/src/plugins/PluginSlot.test.jsx
index e18244ac..567b9800 100644
--- a/src/plugins/PluginSlot.test.jsx
+++ b/src/plugins/PluginSlot.test.jsx
@@ -3,7 +3,7 @@
import React from 'react';
import '@testing-library/jest-dom';
import classNames from 'classnames';
-import { render, fireEvent } from '@testing-library/react';
+import { render, fireEvent, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { logError } from '@edx/frontend-platform/logging';
import { IntlProvider } from '@edx/frontend-platform/i18n';
@@ -78,7 +78,7 @@ function DefaultContents({ className, onClick, ...rest }) {
);
}
-function PluginSlotWrapper({ slotOptions, children }) {
+function PluginSlotWrapper({ slotOptions, children, pluginProps }) {
return (
{children}
@@ -182,9 +183,39 @@ describe('PluginSlot', () => {
{
op: PLUGIN_OPERATIONS.Wrap,
widgetId: 'default_contents',
- wrapper: ({ component }) => (
+ wrapper: ({ component, pluginProps }) => (
{component}
+
+ {pluginProps?.prop1 && `This is a wrapper with ${pluginProps?.prop1}.`}
+
+
+ ),
+ },
+ ],
+ keepDefault: true,
+ });
+
+ const { getByTestId } = render();
+ const customWrapper = getByTestId('custom-wrapper');
+ const defaultContent = getByTestId('default_contents');
+ expect(customWrapper).toContainElement(defaultContent);
+ const pluginProps = within(customWrapper).getByTestId('custom-wrapper-props');
+ expect(pluginProps).toHaveTextContent('This is a wrapper with prop1.');
+ });
+
+ it('should wrap a Plugin when using the "wrap" operation without passing props', () => {
+ usePluginSlot.mockReturnValueOnce({
+ plugins: [
+ {
+ op: PLUGIN_OPERATIONS.Wrap,
+ widgetId: 'default_contents',
+ wrapper: ({ component, pluginProps }) => (
+
+ {component}
+
+ {`This is a wrapper without props: ${JSON.stringify(pluginProps)}`}
+
),
},
@@ -196,6 +227,8 @@ describe('PluginSlot', () => {
const customWrapper = getByTestId('custom-wrapper');
const defaultContent = getByTestId('default_contents');
expect(customWrapper).toContainElement(defaultContent);
+ const pluginProps = within(customWrapper).getByTestId('custom-wrapper-no-props');
+ expect(pluginProps).toHaveTextContent('This is a wrapper without props: {}');
});
it('should not render a widget if the Hide operation is applied to it', () => {
diff --git a/src/plugins/data/utils.jsx b/src/plugins/data/utils.jsx
index 68e4e3be..c79d0d71 100644
--- a/src/plugins/data/utils.jsx
+++ b/src/plugins/data/utils.jsx
@@ -82,13 +82,14 @@ export const organizePlugins = (defaultContents, plugins) => {
*
* @param {Function} renderComponent - Function that returns JSX (i.e. React Component)
* @param {Array} wrappers - Array of components that each use a "component" prop to render the wrapped contents
+ * @params {object} pluginProps - Props defined in the PluginSlot
* @returns {React.ReactElement} - The plugin component wrapped by any number of wrappers provided.
*/
-export const wrapComponent = (renderComponent, wrappers) => wrappers.reduce(
+export const wrapComponent = (renderComponent, wrappers, pluginProps) => wrappers.reduce(
// Disabled lint because currently we don't have a unique identifier for this
// The "component" and "wrapper" are both functions
// eslint-disable-next-line react/no-array-index-key
- (component, wrapper, idx) => React.createElement(wrapper, { component, key: idx }),
+ (component, wrapper, idx) => React.createElement(wrapper, { component, key: idx, pluginProps }),
renderComponent(),
);
diff --git a/src/plugins/data/utils.test.jsx b/src/plugins/data/utils.test.jsx
index b93c4163..602cebd5 100644
--- a/src/plugins/data/utils.test.jsx
+++ b/src/plugins/data/utils.test.jsx
@@ -22,10 +22,10 @@ const mockIsAdminWrapper = ({ widget }) => {
return isAdmin ? widget : null;
};
-const makeMockElementWrapper = (testId = 0) => function MockElementWrapper({ component }) {
+const makeMockElementWrapper = (testId = 0) => function MockElementWrapper({ component, pluginProps }) {
return (
- This is a wrapper.
+ {pluginProps?.prop1 && `This is a wrapper with ${pluginProps?.prop1}.`}
{component}
);
@@ -181,6 +181,18 @@ describe('organizePlugins', () => {
describe('wrapComponent', () => {
describe('when provided with a single wrapper in an array', () => {
it('should wrap the provided component', () => {
+ const wrappedComponent = wrapComponent(mockRenderWidget, [makeMockElementWrapper()], { prop1: 'prop1' });
+
+ const { getByTestId } = render(wrappedComponent);
+
+ const wrapper = getByTestId('wrapper0');
+ const widget = getByTestId('widget');
+
+ expect(wrapper).toContainElement(widget);
+ expect(wrapper).toHaveTextContent('This is a wrapper with prop1.');
+ });
+
+ it('should wrap the provided component without passing props', () => {
const wrappedComponent = wrapComponent(mockRenderWidget, [makeMockElementWrapper()]);
const { getByTestId } = render(wrappedComponent);
@@ -189,6 +201,7 @@ describe('wrapComponent', () => {
const widget = getByTestId('widget');
expect(wrapper).toContainElement(widget);
+ expect(wrapper).not.toHaveTextContent('This is a wrapper with prop1.');
});
});
describe('when provided with multiple wrappers in an array', () => {