diff --git a/src/components/ModalView/__snapshots__/index.test.jsx.snap b/src/components/ModalView/__snapshots__/index.test.jsx.snap new file mode 100644 index 0000000..317d9d4 --- /dev/null +++ b/src/components/ModalView/__snapshots__/index.test.jsx.snap @@ -0,0 +1,51 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ModalView snapshot should renders default ModalView 1`] = ` +
+ + + +

+ Thank you for your interest! +

+
+
+ +
+

+ Personalized recommendations feature is not yet available. We are working hard on bringing it to your learner home in the near future. +

+

+ Would you like to be alerted when it becomes available? +

+
+
+ + + + Skip for now + + + + +
+
+`; diff --git a/src/components/ModalView/hooks.js b/src/components/ModalView/hooks.js new file mode 100644 index 0000000..96c93ac --- /dev/null +++ b/src/components/ModalView/hooks.js @@ -0,0 +1,18 @@ +import React from 'react'; +import { StrictDict } from 'utils'; + +import * as module from './hooks'; + +export const state = StrictDict({ + isRecommendationsModalOpen: (val) => React.useState(val), // eslint-disable-line +}); + +export const useRecommendationsModal = () => { + const [isRecommendationsModalOpen, setIsRecommendationsModalOpen] = module.state.isRecommendationsModalOpen(false); + const toggleRecommendationsModal = () => setIsRecommendationsModalOpen(!isRecommendationsModalOpen); + + return { + isRecommendationsModalOpen, + toggleRecommendationsModal, + }; +}; diff --git a/src/components/ModalView/hooks.test.js b/src/components/ModalView/hooks.test.js new file mode 100644 index 0000000..0ec597d --- /dev/null +++ b/src/components/ModalView/hooks.test.js @@ -0,0 +1,21 @@ +import { MockUseState } from 'testUtils'; + +import * as hooks from './hooks'; + +const state = new MockUseState(hooks); + +const { + useRecommendationsModal, +} = hooks; + +describe('LearnerDashboardHeader hooks', () => { + describe('useRecommendationsModal', () => { + test('default state', () => { + state.mock(); + const out = useRecommendationsModal(); + state.expectInitializedWith(state.keys.isRecommendationsModalOpen, false); + out.toggleRecommendationsModal(); + expect(state.values.isRecommendationsModalOpen).toEqual(true); + }); + }); +}); diff --git a/src/components/ModalView/index.jsx b/src/components/ModalView/index.jsx new file mode 100644 index 0000000..df55850 --- /dev/null +++ b/src/components/ModalView/index.jsx @@ -0,0 +1,56 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import { ModalDialog, ActionRow, Button } from '@edx/paragon'; +import { useIntl } from '@edx/frontend-platform/i18n'; +import messages from './messages'; + +export const ModalView = ({ + isOpen, + onClose, +}) => { + const { formatMessage } = useIntl(); + + return ( +
+ + + +

{formatMessage(messages.recommendationsModalHeading)}

+
+
+ +
+

{formatMessage(messages.recommendationsFeatureText)}

+

{formatMessage(messages.recommendationsAlertText)}

+
+
+ + + + {formatMessage(messages.modalSkipButton)} + + + + +
+
+ ); +}; + +ModalView.defaultProps = { + isOpen: false, +}; + +ModalView.propTypes = { + onClose: PropTypes.func.isRequired, + isOpen: PropTypes.bool, +}; + +export default ModalView; diff --git a/src/components/ModalView/index.test.jsx b/src/components/ModalView/index.test.jsx new file mode 100644 index 0000000..101f2a7 --- /dev/null +++ b/src/components/ModalView/index.test.jsx @@ -0,0 +1,16 @@ +import { shallow } from 'enzyme'; + +import ModalView from '.'; + +describe('ModalView', () => { + const props = { + isOpen: true, + onClose: jest.fn(), + }; + describe('snapshot', () => { + test('should renders default ModalView', () => { + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); + }); + }); +}); diff --git a/src/components/ModalView/messages.js b/src/components/ModalView/messages.js new file mode 100644 index 0000000..21d8add --- /dev/null +++ b/src/components/ModalView/messages.js @@ -0,0 +1,31 @@ +import { defineMessages } from '@edx/frontend-platform/i18n'; + +const messages = defineMessages({ + recommendationsFeatureText: { + id: 'RecommendationsPanel.recommendationsFeatureText', + defaultMessage: 'Personalized recommendations feature is not yet available. We are working hard on bringing it to your learner home in the near future.', + description: 'recommendations feature text', + }, + recommendationsAlertText: { + id: 'RecommendationsPanel.recommendationsAlertText', + defaultMessage: 'Would you like to be alerted when it becomes available?', + description: 'recommendations alerted text', + }, + recommendationsModalHeading: { + id: 'RecommendationsPanel.recommendationsModalHeading', + defaultMessage: 'Thank you for your interest!', + description: 'Heading of modal', + }, + modalSkipButton: { + id: 'RecommendationsPanel.modalSkipButton', + defaultMessage: 'Skip for now', + description: 'button for Skip for now', + }, + modalCountMeButton: { + id: 'RecommendationsPanel.modalCountMeButton', + defaultMessage: 'Count me in!', + description: 'button for Count me in!', + }, +}); + +export default messages; diff --git a/src/containers/LearnerDashboardHeader/CollapsedHeader/CollapseMenuBody.jsx b/src/containers/LearnerDashboardHeader/CollapsedHeader/CollapseMenuBody.jsx index 3736c26..0034705 100644 --- a/src/containers/LearnerDashboardHeader/CollapsedHeader/CollapseMenuBody.jsx +++ b/src/containers/LearnerDashboardHeader/CollapsedHeader/CollapseMenuBody.jsx @@ -12,9 +12,10 @@ import { reduxHooks } from 'hooks'; import { findCoursesNavDropdownClicked } from '../hooks'; +import ModalView from '../../../components/ModalView'; import messages from '../messages'; -export const CollapseMenuBody = ({ isOpen }) => { +export const CollapseMenuBody = ({ isOpen, isRecommendationsModalOpen, setIsRecommendationsModalOpen }) => { const { formatMessage } = useIntl(); const { authenticatedUser } = React.useContext(AppContext); @@ -40,6 +41,12 @@ export const CollapseMenuBody = ({ isOpen }) => { > {formatMessage(messages.discoverNew)} + @@ -92,6 +99,7 @@ export const CollapseMenuBody = ({ isOpen }) => { )} + ) ); @@ -99,6 +107,13 @@ export const CollapseMenuBody = ({ isOpen }) => { CollapseMenuBody.propTypes = { isOpen: PropTypes.bool.isRequired, + isRecommendationsModalOpen: PropTypes.bool, + setIsRecommendationsModalOpen: PropTypes.func, +}; + +CollapseMenuBody.defaultProps = { + isRecommendationsModalOpen: false, + setIsRecommendationsModalOpen: () => {}, }; export default CollapseMenuBody; diff --git a/src/containers/LearnerDashboardHeader/CollapsedHeader/CollapseMenuBody.test.jsx b/src/containers/LearnerDashboardHeader/CollapsedHeader/CollapseMenuBody.test.jsx index 1f2426b..b259cfe 100644 --- a/src/containers/LearnerDashboardHeader/CollapsedHeader/CollapseMenuBody.test.jsx +++ b/src/containers/LearnerDashboardHeader/CollapsedHeader/CollapseMenuBody.test.jsx @@ -3,6 +3,13 @@ import { AppContext } from '@edx/frontend-platform/react'; import CollapseMenuBody from './CollapseMenuBody'; +jest.mock('@edx/frontend-platform', () => ({ + getConfig: jest.fn(() => ({ + ORDER_HISTORY_URL: 'http://account-profile-url.test', + CAREER_LINK_URL: 'http://account-profile-url.test', + })), +})); + jest.mock('@edx/frontend-platform/react', () => ({ AppContext: { authenticatedUser: { @@ -15,6 +22,7 @@ jest.mock('hooks', () => ({ reduxHooks: { useEnterpriseDashboardData: () => ({ url: 'url', + dashboard: false, }), usePlatformSettingsData: () => ({ courseSearchUrl: '/courseSearchUrl', diff --git a/src/containers/LearnerDashboardHeader/CollapsedHeader/__snapshots__/CollapseMenuBody.test.jsx.snap b/src/containers/LearnerDashboardHeader/CollapsedHeader/__snapshots__/CollapseMenuBody.test.jsx.snap index c7694e8..1ebed64 100644 --- a/src/containers/LearnerDashboardHeader/CollapsedHeader/__snapshots__/CollapseMenuBody.test.jsx.snap +++ b/src/containers/LearnerDashboardHeader/CollapsedHeader/__snapshots__/CollapseMenuBody.test.jsx.snap @@ -13,22 +13,27 @@ exports[`CollapseMenuBody render 1`] = ` + + + `; @@ -79,25 +94,34 @@ exports[`CollapseMenuBody render unauthenticated 1`] = ` + + `; diff --git a/src/containers/LearnerDashboardHeader/CollapsedHeader/__snapshots__/index.test.jsx.snap b/src/containers/LearnerDashboardHeader/CollapsedHeader/__snapshots__/index.test.jsx.snap index c1dd843..c9c9d32 100644 --- a/src/containers/LearnerDashboardHeader/CollapsedHeader/__snapshots__/index.test.jsx.snap +++ b/src/containers/LearnerDashboardHeader/CollapsedHeader/__snapshots__/index.test.jsx.snap @@ -20,6 +20,8 @@ exports[`CollapsedHeader renders 1`] = ` `; @@ -43,6 +45,8 @@ exports[`CollapsedHeader renders with isOpen true 1`] = ` `; diff --git a/src/containers/LearnerDashboardHeader/CollapsedHeader/index.jsx b/src/containers/LearnerDashboardHeader/CollapsedHeader/index.jsx index f8465b3..7481642 100644 --- a/src/containers/LearnerDashboardHeader/CollapsedHeader/index.jsx +++ b/src/containers/LearnerDashboardHeader/CollapsedHeader/index.jsx @@ -5,6 +5,7 @@ import { Menu, Close } from '@edx/paragon/icons'; import { IconButton, Icon } from '@edx/paragon'; import { useLearnerDashboardHeaderData, useIsCollapsed } from '../hooks'; +import { useRecommendationsModal } from '../../../components/ModalView/hooks'; import CollapseMenuBody from './CollapseMenuBody'; import BrandLogo from '../BrandLogo'; @@ -15,6 +16,7 @@ export const CollapsedHeader = () => { const { formatMessage } = useIntl(); const isCollapsed = useIsCollapsed(); const { isOpen, toggleIsOpen } = useLearnerDashboardHeaderData(); + const { isRecommendationsModalOpen, toggleRecommendationsModal } = useRecommendationsModal(); return ( isCollapsed && ( @@ -36,7 +38,11 @@ export const CollapsedHeader = () => { /> - + ) ); diff --git a/src/containers/LearnerDashboardHeader/CollapsedHeader/index.test.jsx b/src/containers/LearnerDashboardHeader/CollapsedHeader/index.test.jsx index 54a5ece..395cb6c 100644 --- a/src/containers/LearnerDashboardHeader/CollapsedHeader/index.test.jsx +++ b/src/containers/LearnerDashboardHeader/CollapsedHeader/index.test.jsx @@ -3,6 +3,7 @@ import { shallow } from 'enzyme'; import CollapsedHeader from '.'; import { useLearnerDashboardHeaderData, useIsCollapsed } from '../hooks'; +import { useRecommendationsModal } from '../../../components/ModalView/hooks'; jest.mock('../BrandLogo', () => jest.fn(() => 'BrandLogo')); jest.mock('./CollapseMenuBody', () => jest.fn(() => 'CollapseMenuBody')); @@ -15,14 +16,26 @@ jest.mock('../hooks', () => ({ })), })); +jest.mock('../../../components/ModalView/hooks', () => ({ + useRecommendationsModal: jest.fn(), +})); + describe('CollapsedHeader', () => { it('renders', () => { + useRecommendationsModal.mockReturnValueOnce({ + isRecommendationsModalOpen: false, + toggleRecommendationsModal: jest.fn(), + }); const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); it('render nothing if not collapsed', () => { useIsCollapsed.mockReturnValueOnce(false); + useRecommendationsModal.mockReturnValueOnce({ + isRecommendationsModalOpen: false, + toggleRecommendationsModal: jest.fn(), + }); const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); @@ -32,6 +45,10 @@ describe('CollapsedHeader', () => { isOpen: true, toggleIsOpen: jest.fn().mockName('toggleIsOpen'), }); + useRecommendationsModal.mockReturnValueOnce({ + isRecommendationsModalOpen: false, + toggleRecommendationsModal: jest.fn(), + }); const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); diff --git a/src/containers/LearnerDashboardHeader/ExpandedHeader/__snapshots__/index.test.jsx.snap b/src/containers/LearnerDashboardHeader/ExpandedHeader/__snapshots__/index.test.jsx.snap index 9426255..21836c6 100644 --- a/src/containers/LearnerDashboardHeader/ExpandedHeader/__snapshots__/index.test.jsx.snap +++ b/src/containers/LearnerDashboardHeader/ExpandedHeader/__snapshots__/index.test.jsx.snap @@ -33,6 +33,13 @@ exports[`ExpandedHeader render 1`] = ` > Discover New + @@ -46,6 +53,10 @@ exports[`ExpandedHeader render 1`] = ` + `; diff --git a/src/containers/LearnerDashboardHeader/ExpandedHeader/index.jsx b/src/containers/LearnerDashboardHeader/ExpandedHeader/index.jsx index dccb045..39248e7 100644 --- a/src/containers/LearnerDashboardHeader/ExpandedHeader/index.jsx +++ b/src/containers/LearnerDashboardHeader/ExpandedHeader/index.jsx @@ -10,13 +10,16 @@ import { reduxHooks } from 'hooks'; import AuthenticatedUserDropdown from './AuthenticatedUserDropdown'; import { useIsCollapsed, findCoursesNavClicked } from '../hooks'; +import { useRecommendationsModal } from '../../../components/ModalView/hooks'; import messages from '../messages'; +import ModalView from '../../../components/ModalView'; import BrandLogo from '../BrandLogo'; export const ExpandedHeader = () => { const { formatMessage } = useIntl(); const { courseSearchUrl } = reduxHooks.usePlatformSettingsData(); const isCollapsed = useIsCollapsed(); + const { isRecommendationsModalOpen, toggleRecommendationsModal } = useRecommendationsModal(); const exploreCoursesClick = findCoursesNavClicked(urls.baseAppUrl(courseSearchUrl)); @@ -51,6 +54,13 @@ export const ExpandedHeader = () => { > {formatMessage(messages.discoverNew)} + + -
- -
- + + ); }; @@ -61,6 +59,8 @@ LoadedView.propTypes = { marketingUrl: PropTypes.string, })).isRequired, isControl: PropTypes.oneOf([true, false, null]), + setIsRecommendationsModalOpen: PropTypes.func.isRequired, + isRecommendationsModalOpen: PropTypes.bool.isRequired, }; export default LoadedView; diff --git a/src/widgets/RecommendationsPanel/LoadedView.test.jsx b/src/widgets/RecommendationsPanel/LoadedView.test.jsx index 20b7a4b..555965d 100644 --- a/src/widgets/RecommendationsPanel/LoadedView.test.jsx +++ b/src/widgets/RecommendationsPanel/LoadedView.test.jsx @@ -24,6 +24,8 @@ describe('RecommendationsPanel LoadedView', () => { const props = { courses: mockData.courses, isControl: null, + setIsRecommendationsModalOpen: () => {}, + isRecommendationsModalOpen: false, }; describe('snapshot', () => { test('without personalize recommendation', () => { diff --git a/src/widgets/RecommendationsPanel/__snapshots__/LoadedView.test.jsx.snap b/src/widgets/RecommendationsPanel/__snapshots__/LoadedView.test.jsx.snap index c04c032..6676fa5 100644 --- a/src/widgets/RecommendationsPanel/__snapshots__/LoadedView.test.jsx.snap +++ b/src/widgets/RecommendationsPanel/__snapshots__/LoadedView.test.jsx.snap @@ -1,151 +1,157 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`RecommendationsPanel LoadedView snapshot with personalize recommendation 1`] = ` -
-

- Recommendations for you -

-
- - - - -
+
- + Recommendations for you + +
+ + + + +
+
+ +
-
+ + `; exports[`RecommendationsPanel LoadedView snapshot without personalize recommendation 1`] = ` -
-

- Popular courses -

-
- - - - -
+
- + Popular courses + +
+ + + + +
+
+ +
-
+ + `; diff --git a/src/widgets/RecommendationsPanel/index.jsx b/src/widgets/RecommendationsPanel/index.jsx index c091a8d..47bfb36 100644 --- a/src/widgets/RecommendationsPanel/index.jsx +++ b/src/widgets/RecommendationsPanel/index.jsx @@ -4,6 +4,7 @@ import LookingForChallengeWidget from 'widgets/LookingForChallengeWidget'; import LoadingView from './LoadingView'; import LoadedView from './LoadedView'; import hooks from './hooks'; +import { useRecommendationsModal } from '../../components/ModalView/hooks'; export const RecommendationsPanel = () => { const { @@ -14,12 +15,19 @@ export const RecommendationsPanel = () => { isLoading, } = hooks.useRecommendationPanelData(); + const { isRecommendationsModalOpen, toggleRecommendationsModal } = useRecommendationsModal(); + if (isLoading) { return (); } if (isLoaded && courses.length > 0) { return ( - + ); } if (isFailed) { diff --git a/src/widgets/RecommendationsPanel/index.test.jsx b/src/widgets/RecommendationsPanel/index.test.jsx index f87cd5c..e46d5e2 100644 --- a/src/widgets/RecommendationsPanel/index.test.jsx +++ b/src/widgets/RecommendationsPanel/index.test.jsx @@ -7,10 +7,14 @@ import mockData from './mockData'; import LoadedView from './LoadedView'; import LoadingView from './LoadingView'; import RecommendationsPanel from '.'; +import { useRecommendationsModal } from '../../components/ModalView/hooks'; jest.mock('./hooks', () => ({ useRecommendationPanelData: jest.fn(), })); +jest.mock('../../components/ModalView/hooks', () => ({ + useRecommendationsModal: jest.fn(), +})); jest.mock('widgets/LookingForChallengeWidget', () => 'LookingForChallengeWidget'); jest.mock('./LoadingView', () => 'LoadingView'); jest.mock('./LoadedView', () => 'LoadedView'); @@ -21,6 +25,8 @@ describe('RecommendationsPanel snapshot', () => { const defaultLoadedViewProps = { courses: [], isControl: false, + setIsRecommendationsModalOpen: jest.fn(), + isRecommendationsModalOpen: false, }; const defaultValues = { isFailed: false, @@ -29,6 +35,10 @@ describe('RecommendationsPanel snapshot', () => { ...defaultLoadedViewProps, }; it('displays LoadingView if request is loading', () => { + useRecommendationsModal.mockReturnValueOnce({ + isRecommendationsModalOpen: false, + toggleRecommendationsModal: jest.fn(), + }); hooks.useRecommendationPanelData.mockReturnValueOnce({ ...defaultValues, isLoading: true, @@ -36,16 +46,23 @@ describe('RecommendationsPanel snapshot', () => { expect(shallow()).toMatchObject(shallow()); }); it('displays LoadedView with courses if request is loaded', () => { + useRecommendationsModal.mockReturnValueOnce({ + isRecommendationsModalOpen: false, + toggleRecommendationsModal: jest.fn(), + }); hooks.useRecommendationPanelData.mockReturnValueOnce({ ...defaultValues, courses, isLoaded: true, }); - expect(shallow()).toMatchObject( - shallow(), - ); + expect(JSON.stringify(shallow())) + .toEqual(JSON.stringify(shallow())); }); it('displays LookingForChallengeWidget if request is failed', () => { + useRecommendationsModal.mockReturnValueOnce({ + isRecommendationsModalOpen: false, + toggleRecommendationsModal: jest.fn(), + }); hooks.useRecommendationPanelData.mockReturnValueOnce({ ...defaultValues, isFailed: true, @@ -55,6 +72,10 @@ describe('RecommendationsPanel snapshot', () => { ); }); it('defaults to LookingForChallengeWidget if no flags are true', () => { + useRecommendationsModal.mockReturnValueOnce({ + isRecommendationsModalOpen: false, + toggleRecommendationsModal: jest.fn(), + }); hooks.useRecommendationPanelData.mockReturnValueOnce({ ...defaultValues, }); diff --git a/src/widgets/RecommendationsPanel/messages.js b/src/widgets/RecommendationsPanel/messages.js index 69bedd4..6422d24 100644 --- a/src/widgets/RecommendationsPanel/messages.js +++ b/src/widgets/RecommendationsPanel/messages.js @@ -16,6 +16,11 @@ const messages = defineMessages({ defaultMessage: 'Explore courses', description: 'Button to explore more courses on recommendations panel', }, + seeAllRecommendationsButton: { + id: 'RecommendationsPanel.seeAllRecommendationsButton', + defaultMessage: 'See All Recommendations', + description: 'Button to see all recommendations', + }, }); export default messages;