diff --git a/package-lock.json b/package-lock.json index 18b05366c..bff3cae32 100644 --- a/package-lock.json +++ b/package-lock.json @@ -62,6 +62,14 @@ "react-redux": "^7.1.1 || ^8.1.1", "react-router-dom": "^6.0.0", "redux": "^4.0.4" + }, + "peerDependenciesMeta": { + "react-redux": { + "optional": true + }, + "redux": { + "optional": true + } } }, "node_modules/@adobe/css-tools": { diff --git a/package.json b/package.json index b1c391f58..f454a943f 100644 --- a/package.json +++ b/package.json @@ -82,5 +82,13 @@ "react-redux": "^7.1.1 || ^8.1.1", "react-router-dom": "^6.0.0", "redux": "^4.0.4" + }, + "peerDependenciesMeta": { + "redux": { + "optional": true + }, + "react-redux": { + "optional": true + } } } diff --git a/src/react/AppProvider.test.jsx b/src/react/AppProvider.test.jsx index 9dd05c2c3..f27fd4476 100644 --- a/src/react/AppProvider.test.jsx +++ b/src/react/AppProvider.test.jsx @@ -1,6 +1,6 @@ import React from 'react'; import { createStore } from 'redux'; -import { render, waitFor } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import AppProvider from './AppProvider'; import { initialize } from '../initialize'; @@ -58,10 +58,8 @@ describe('AppProvider', () => { const wrapper = render(component); await waitFor(() => { - const list = wrapper.container.querySelectorAll('div.child'); - expect(list.length).toEqual(2); - expect(list[0].textContent).toEqual('Child One'); - expect(list[1].textContent).toEqual('Child Two'); + expect(screen.getByText('Child One')).toBeInTheDocument(); + expect(screen.getByText('Child Two')).toBeInTheDocument(); }); expect(wrapper.getByTestId('browser-router')).toBeInTheDocument(); const reduxProvider = wrapper.getByTestId('redux-provider'); @@ -78,10 +76,8 @@ describe('AppProvider', () => { const wrapper = render(component); await waitFor(() => { - const list = wrapper.container.querySelectorAll('div.child'); - expect(list.length).toEqual(2); - expect(list[0].textContent).toEqual('Child One'); - expect(list[1].textContent).toEqual('Child Two'); + expect(screen.getByText('Child One')).toBeInTheDocument(); + expect(screen.getByText('Child Two')).toBeInTheDocument(); }); expect(wrapper.queryByTestId('browser-router')).not.toBeInTheDocument(); const reduxProvider = wrapper.getByTestId('redux-provider'); @@ -97,13 +93,8 @@ describe('AppProvider', () => { ); const wrapper = render(component); - await waitFor(() => { - const list = wrapper.container.querySelectorAll('div.child'); - expect(list.length).toEqual(2); - expect(list[0].textContent).toEqual('Child One'); - expect(list[1].textContent).toEqual('Child Two'); - }); - + expect(screen.getByText('Child One')).toBeInTheDocument(); + expect(screen.getByText('Child Two')).toBeInTheDocument(); const reduxProvider = wrapper.queryByTestId('redux-provider'); expect(reduxProvider).not.toBeInTheDocument(); }); diff --git a/src/react/OptionalReduxProvider.jsx b/src/react/OptionalReduxProvider.jsx index 8ea0625b8..792111d39 100644 --- a/src/react/OptionalReduxProvider.jsx +++ b/src/react/OptionalReduxProvider.jsx @@ -1,13 +1,44 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; -import { Provider } from 'react-redux'; +/* eslint-disable import/no-extraneous-dependencies */ +import { logError } from '@edx/frontend-platform/logging'; +/* eslint-enable import/no-extraneous-dependencies */ + +function useProvider(store) { + const [Provider, setProvider] = useState(null); // Initially null to prevent render children that expect a Provider + + useEffect(() => { + if (!store) { + setProvider(() => ({ children }) => children); // Ensure fallback if no store + return; + } + const loadProvider = async () => { + try { + const { Provider: ReactReduxProvider } = await import('react-redux'); + // Set the Provider from react-redux + setProvider(() => ReactReduxProvider); + } catch (error) { + logError('Failed to load react-redux', error); + } + }; + loadProvider(); + }, [store]); + + return Provider; +} /** * @memberof module:React * @param {Object} props */ export default function OptionalReduxProvider({ store = null, children }) { - if (store === null) { + const Provider = useProvider(store); + + if (!Provider) { + return null; + } + + if (!store) { return children; } diff --git a/src/react/OptionalReduxProvider.test.jsx b/src/react/OptionalReduxProvider.test.jsx new file mode 100644 index 000000000..f10169281 --- /dev/null +++ b/src/react/OptionalReduxProvider.test.jsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import OptionalReduxProvider from './OptionalReduxProvider'; // Adjust the import path as needed + +describe('OptionalReduxProvider', () => { + it('should handle error when react-redux import fails', async () => { + // Simulate the failed import of 'react-redux' + jest.mock('react-redux', () => { + throw new Error('Failed to load react-redux'); + }); + + const mockStore = {}; // Mock store object + render( + + Test Children + , + ); + + // Check that the children are still rendered even when react-redux fails to load + const childrenElement = await screen.findByText('Test Children'); + expect(childrenElement).toBeInTheDocument(); + }); +});