Thunk Removal Proof-of-concept (#98)
This commit is contained in:
23
src/App.jsx
23
src/App.jsx
@@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { Helmet } from 'react-helmet';
|
||||
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
@@ -16,9 +15,8 @@ import store from 'data/store';
|
||||
import {
|
||||
selectors,
|
||||
actions,
|
||||
thunkActions,
|
||||
hooks as appHooks,
|
||||
} from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import LearnerDashboardHeader from 'containers/LearnerDashboardHeader';
|
||||
import Dashboard from 'containers/Dashboard';
|
||||
import ZendeskFab from 'components/ZendeskFab';
|
||||
@@ -32,34 +30,33 @@ import messages from './messages';
|
||||
import './App.scss';
|
||||
|
||||
export const App = () => {
|
||||
const dispatch = useDispatch();
|
||||
// TODO: made development-only
|
||||
const { authenticatedUser } = React.useContext(AppContext);
|
||||
const { formatMessage } = useIntl();
|
||||
const isFailed = {
|
||||
initialize: appHooks.useRequestIsFailed(RequestKeys.initialize),
|
||||
refreshList: appHooks.useRequestIsFailed(RequestKeys.refreshList),
|
||||
initialize: reduxHooks.useRequestIsFailed(RequestKeys.initialize),
|
||||
refreshList: reduxHooks.useRequestIsFailed(RequestKeys.refreshList),
|
||||
};
|
||||
const hasNetworkFailure = isFailed.initialize || isFailed.refreshList;
|
||||
const { supportEmail } = appHooks.usePlatformSettingsData();
|
||||
const { supportEmail } = reduxHooks.usePlatformSettingsData();
|
||||
const loadData = reduxHooks.useLoadData();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (authenticatedUser?.administrator || process.env.NODE_ENV === 'development') {
|
||||
window.loadEmptyData = () => {
|
||||
dispatch(thunkActions.app.loadData({ ...fakeData.globalData, courses: [] }));
|
||||
loadData({ ...fakeData.globalData, courses: [] });
|
||||
};
|
||||
window.loadMockData = () => {
|
||||
dispatch(thunkActions.app.loadData({
|
||||
loadData({
|
||||
...fakeData.globalData,
|
||||
courses: [
|
||||
...fakeData.courseRunData,
|
||||
...fakeData.entitlementData,
|
||||
],
|
||||
}));
|
||||
});
|
||||
};
|
||||
window.store = store;
|
||||
window.selectors = selectors;
|
||||
window.actions = actions;
|
||||
window.thunkActions = thunkActions;
|
||||
window.track = track;
|
||||
}
|
||||
if (process.env.HOTJAR_APP_ID) {
|
||||
@@ -73,7 +70,7 @@ export const App = () => {
|
||||
logError(error);
|
||||
}
|
||||
}
|
||||
}, [authenticatedUser, dispatch]);
|
||||
}, [authenticatedUser, loadData]);
|
||||
return (
|
||||
<Router>
|
||||
<Helmet>
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Alert } from '@edx/paragon';
|
||||
|
||||
import { RequestKeys } from 'data/constants/requests';
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import Dashboard from 'containers/Dashboard';
|
||||
import LearnerDashboardHeader from 'containers/LearnerDashboardHeader';
|
||||
import { App } from './App';
|
||||
@@ -25,18 +25,24 @@ jest.mock('data/redux', () => ({
|
||||
selectors: 'redux.selectors',
|
||||
actions: 'redux.actions',
|
||||
thunkActions: 'redux.thunkActions',
|
||||
hooks: {
|
||||
}));
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useRequestIsFailed: jest.fn(),
|
||||
usePlatformSettingsData: jest.fn(),
|
||||
useLoadData: jest.fn(),
|
||||
},
|
||||
}));
|
||||
jest.mock('data/store', () => 'data/store');
|
||||
|
||||
const loadData = jest.fn();
|
||||
reduxHooks.useLoadData.mockReturnValue(loadData);
|
||||
|
||||
const logo = 'fakeLogo.png';
|
||||
let el;
|
||||
|
||||
const supportEmail = 'test-support-url';
|
||||
appHooks.usePlatformSettingsData.mockReturnValue({ supportEmail });
|
||||
reduxHooks.usePlatformSettingsData.mockReturnValue({ supportEmail });
|
||||
|
||||
describe('App router component', () => {
|
||||
process.env.LOGO_POWERED_BY_OPEN_EDX_URL_SVG = logo;
|
||||
@@ -59,7 +65,7 @@ describe('App router component', () => {
|
||||
};
|
||||
describe('no network failure', () => {
|
||||
beforeAll(() => {
|
||||
appHooks.useRequestIsFailed.mockReturnValue(false);
|
||||
reduxHooks.useRequestIsFailed.mockReturnValue(false);
|
||||
el = shallow(<App />);
|
||||
});
|
||||
runBasicTests();
|
||||
@@ -71,7 +77,7 @@ describe('App router component', () => {
|
||||
});
|
||||
describe('initialize failure', () => {
|
||||
beforeAll(() => {
|
||||
appHooks.useRequestIsFailed.mockImplementation((key) => key === RequestKeys.initialize);
|
||||
reduxHooks.useRequestIsFailed.mockImplementation((key) => key === RequestKeys.initialize);
|
||||
el = shallow(<App />);
|
||||
});
|
||||
runBasicTests();
|
||||
@@ -87,7 +93,7 @@ describe('App router component', () => {
|
||||
});
|
||||
describe('refresh failure', () => {
|
||||
beforeAll(() => {
|
||||
appHooks.useRequestIsFailed.mockImplementation((key) => key === RequestKeys.refreshList);
|
||||
reduxHooks.useRequestIsFailed.mockImplementation((key) => key === RequestKeys.refreshList);
|
||||
el = shallow(<App />);
|
||||
});
|
||||
runBasicTests();
|
||||
|
||||
@@ -4,16 +4,16 @@ import PropTypes from 'prop-types';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import track from 'tracking';
|
||||
import { hooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import ActionButton from './ActionButton';
|
||||
import messages from './messages';
|
||||
|
||||
export const BeginCourseButton = ({ cardId }) => {
|
||||
const { homeUrl } = hooks.useCardCourseRunData(cardId);
|
||||
const { hasAccess } = hooks.useCardEnrollmentData(cardId);
|
||||
const { isMasquerading } = hooks.useMasqueradeData();
|
||||
const { formatMessage } = useIntl();
|
||||
const handleClick = hooks.useTrackCourseEvent(
|
||||
const { homeUrl } = reduxHooks.useCardCourseRunData(cardId);
|
||||
const { hasAccess } = reduxHooks.useCardEnrollmentData(cardId);
|
||||
const { isMasquerading } = reduxHooks.useMasqueradeData();
|
||||
const handleClick = reduxHooks.useTrackCourseEvent(
|
||||
track.course.enterCourseClicked,
|
||||
cardId,
|
||||
homeUrl,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { htmlProps } from 'data/constants/htmlKeys';
|
||||
import { hooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import track from 'tracking';
|
||||
import BeginCourseButton from './BeginCourseButton';
|
||||
|
||||
@@ -11,8 +11,8 @@ jest.mock('tracking', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('data/redux', () => ({
|
||||
hooks: {
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useCardCourseRunData: jest.fn(() => ({ homeUrl: 'home-url' })),
|
||||
useCardEnrollmentData: jest.fn(() => ({ hasAccess: true })),
|
||||
useMasqueradeData: jest.fn(() => ({ isMasquerading: false })),
|
||||
@@ -25,7 +25,7 @@ jest.mock('data/redux', () => ({
|
||||
jest.mock('./ActionButton', () => 'ActionButton');
|
||||
|
||||
let wrapper;
|
||||
const { homeUrl } = hooks.useCardCourseRunData();
|
||||
const { homeUrl } = reduxHooks.useCardCourseRunData();
|
||||
|
||||
describe('BeginCourseButton', () => {
|
||||
const props = {
|
||||
@@ -39,7 +39,7 @@ describe('BeginCourseButton', () => {
|
||||
wrapper = shallow(<BeginCourseButton {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(wrapper.prop(htmlProps.disabled)).toEqual(false);
|
||||
expect(wrapper.prop(htmlProps.onClick)).toEqual(hooks.useTrackCourseEvent(
|
||||
expect(wrapper.prop(htmlProps.onClick)).toEqual(reduxHooks.useTrackCourseEvent(
|
||||
track.course.enterCourseClicked,
|
||||
props.cardId,
|
||||
homeUrl,
|
||||
@@ -49,20 +49,20 @@ describe('BeginCourseButton', () => {
|
||||
describe('behavior', () => {
|
||||
it('initializes course run data with cardId', () => {
|
||||
wrapper = shallow(<BeginCourseButton {...props} />);
|
||||
expect(hooks.useCardCourseRunData).toHaveBeenCalledWith(props.cardId);
|
||||
expect(reduxHooks.useCardCourseRunData).toHaveBeenCalledWith(props.cardId);
|
||||
});
|
||||
it('initializes enrollment data with cardId', () => {
|
||||
wrapper = shallow(<BeginCourseButton {...props} />);
|
||||
expect(hooks.useCardEnrollmentData).toHaveBeenCalledWith(props.cardId);
|
||||
expect(reduxHooks.useCardEnrollmentData).toHaveBeenCalledWith(props.cardId);
|
||||
});
|
||||
describe('disabled states', () => {
|
||||
test('learner does not have access', () => {
|
||||
hooks.useCardEnrollmentData.mockReturnValueOnce({ hasAccess: false });
|
||||
reduxHooks.useCardEnrollmentData.mockReturnValueOnce({ hasAccess: false });
|
||||
wrapper = shallow(<BeginCourseButton {...props} />);
|
||||
expect(wrapper.prop(htmlProps.disabled)).toEqual(true);
|
||||
});
|
||||
test('masquerading', () => {
|
||||
hooks.useMasqueradeData.mockReturnValueOnce({ isMasquerading: true });
|
||||
reduxHooks.useMasqueradeData.mockReturnValueOnce({ isMasquerading: true });
|
||||
wrapper = shallow(<BeginCourseButton {...props} />);
|
||||
expect(wrapper.prop(htmlProps.disabled)).toEqual(true);
|
||||
});
|
||||
|
||||
@@ -3,17 +3,17 @@ import PropTypes from 'prop-types';
|
||||
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { hooks } from 'data/redux';
|
||||
import track from 'tracking';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import ActionButton from './ActionButton';
|
||||
import messages from './messages';
|
||||
|
||||
export const ResumeButton = ({ cardId }) => {
|
||||
const { resumeUrl } = hooks.useCardCourseRunData(cardId);
|
||||
const { hasAccess, isAudit, isAuditAccessExpired } = hooks.useCardEnrollmentData(cardId);
|
||||
const { isMasquerading } = hooks.useMasqueradeData();
|
||||
const { resumeUrl } = reduxHooks.useCardCourseRunData(cardId);
|
||||
const { hasAccess, isAudit, isAuditAccessExpired } = reduxHooks.useCardEnrollmentData(cardId);
|
||||
const { isMasquerading } = reduxHooks.useMasqueradeData();
|
||||
const { formatMessage } = useIntl();
|
||||
const handleClick = hooks.useTrackCourseEvent(
|
||||
const handleClick = reduxHooks.useTrackCourseEvent(
|
||||
track.course.enterCourseClicked,
|
||||
cardId,
|
||||
resumeUrl,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { htmlProps } from 'data/constants/htmlKeys';
|
||||
import { hooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import track from 'tracking';
|
||||
import ResumeButton from './ResumeButton';
|
||||
|
||||
jest.mock('data/redux', () => ({
|
||||
hooks: {
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useCardCourseRunData: jest.fn(() => ({ resumeUrl: 'resumeUrl' })),
|
||||
useCardEnrollmentData: jest.fn(() => ({
|
||||
hasAccess: true,
|
||||
@@ -26,7 +26,7 @@ jest.mock('tracking', () => ({
|
||||
}));
|
||||
jest.mock('./ActionButton', () => 'ActionButton');
|
||||
|
||||
const { resumeUrl } = hooks.useCardCourseRunData();
|
||||
const { resumeUrl } = reduxHooks.useCardCourseRunData();
|
||||
|
||||
describe('ResumeButton', () => {
|
||||
const props = {
|
||||
@@ -48,20 +48,20 @@ describe('ResumeButton', () => {
|
||||
describe('behavior', () => {
|
||||
it('initializes course run data based on cardId', () => {
|
||||
shallow(<ResumeButton {...props} />);
|
||||
expect(hooks.useCardCourseRunData).toHaveBeenCalledWith(props.cardId);
|
||||
expect(reduxHooks.useCardCourseRunData).toHaveBeenCalledWith(props.cardId);
|
||||
});
|
||||
it('initializes course enrollment data based on cardId', () => {
|
||||
shallow(<ResumeButton {...props} />);
|
||||
expect(hooks.useCardEnrollmentData).toHaveBeenCalledWith(props.cardId);
|
||||
expect(reduxHooks.useCardEnrollmentData).toHaveBeenCalledWith(props.cardId);
|
||||
});
|
||||
describe('disabled states', () => {
|
||||
test('masquerading', () => {
|
||||
hooks.useMasqueradeData.mockReturnValueOnce({ isMasquerading: true });
|
||||
reduxHooks.useMasqueradeData.mockReturnValueOnce({ isMasquerading: true });
|
||||
const wrapper = shallow(<ResumeButton {...props} />);
|
||||
expect(wrapper.prop(htmlProps.disabled)).toEqual(true);
|
||||
});
|
||||
test('learner does not have access', () => {
|
||||
hooks.useCardEnrollmentData.mockReturnValueOnce({
|
||||
reduxHooks.useCardEnrollmentData.mockReturnValueOnce({
|
||||
hasAccess: false,
|
||||
isAudit: true,
|
||||
isAuditAccessExpired: false,
|
||||
@@ -70,7 +70,7 @@ describe('ResumeButton', () => {
|
||||
expect(wrapper.prop(htmlProps.disabled)).toEqual(true);
|
||||
});
|
||||
test('audit access expired', () => {
|
||||
hooks.useCardEnrollmentData.mockReturnValueOnce({
|
||||
reduxHooks.useCardEnrollmentData.mockReturnValueOnce({
|
||||
hasAccess: true,
|
||||
isAudit: true,
|
||||
isAuditAccessExpired: true,
|
||||
|
||||
@@ -3,16 +3,16 @@ import PropTypes from 'prop-types';
|
||||
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { hooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import ActionButton from './ActionButton';
|
||||
import messages from './messages';
|
||||
|
||||
export const SelectSessionButton = ({ cardId }) => {
|
||||
const { hasAccess } = hooks.useCardEnrollmentData(cardId);
|
||||
const { canChange, hasSessions } = hooks.useCardEntitlementData(cardId);
|
||||
const { isMasquerading } = hooks.useMasqueradeData();
|
||||
const { formatMessage } = useIntl();
|
||||
const openSessionModal = hooks.useUpdateSelectSessionModalCallback(cardId);
|
||||
const { isMasquerading } = reduxHooks.useMasqueradeData();
|
||||
const { hasAccess } = reduxHooks.useCardEnrollmentData(cardId);
|
||||
const { canChange, hasSessions } = reduxHooks.useCardEntitlementData(cardId);
|
||||
const openSessionModal = reduxHooks.useUpdateSelectSessionModalCallback(cardId);
|
||||
return (
|
||||
<ActionButton
|
||||
disabled={isMasquerading || !hasAccess || (!canChange || !hasSessions)}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { hooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import { htmlProps } from 'data/constants/htmlKeys';
|
||||
import SelectSessionButton from './SelectSessionButton';
|
||||
|
||||
jest.mock('data/redux', () => ({
|
||||
hooks: {
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useCardEnrollmentData: jest.fn(() => ({ hasAccess: true })),
|
||||
useCardEntitlementData: jest.fn(() => ({ canChange: true, hasSessions: true })),
|
||||
useMasqueradeData: jest.fn(() => ({ isMasquerading: false })),
|
||||
@@ -24,12 +24,12 @@ describe('SelectSessionButton', () => {
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
it('renders disabled button when user does not have access to the course', () => {
|
||||
hooks.useCardEnrollmentData.mockReturnValueOnce({ hasAccess: false });
|
||||
reduxHooks.useCardEnrollmentData.mockReturnValueOnce({ hasAccess: false });
|
||||
wrapper = shallow(<SelectSessionButton {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
it('renders disabled button if masquerading', () => {
|
||||
hooks.useMasqueradeData.mockReturnValueOnce({ isMasquerading: true });
|
||||
reduxHooks.useMasqueradeData.mockReturnValueOnce({ isMasquerading: true });
|
||||
wrapper = shallow(<SelectSessionButton {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -39,26 +39,26 @@ describe('SelectSessionButton', () => {
|
||||
wrapper = shallow(<SelectSessionButton {...props} />);
|
||||
expect(wrapper.prop(htmlProps.disabled)).toEqual(false);
|
||||
expect(wrapper.prop(htmlProps.onClick).getMockName())
|
||||
.toEqual(hooks.useUpdateSelectSessionModalCallback().getMockName());
|
||||
.toEqual(reduxHooks.useUpdateSelectSessionModalCallback().getMockName());
|
||||
});
|
||||
describe('disabled states', () => {
|
||||
test('learner does not have access', () => {
|
||||
hooks.useCardEnrollmentData.mockReturnValueOnce({ hasAccess: false });
|
||||
reduxHooks.useCardEnrollmentData.mockReturnValueOnce({ hasAccess: false });
|
||||
wrapper = shallow(<SelectSessionButton {...props} />);
|
||||
expect(wrapper.prop(htmlProps.disabled)).toEqual(true);
|
||||
});
|
||||
test('learner cannot change sessions', () => {
|
||||
hooks.useCardEntitlementData.mockReturnValueOnce({ canChange: false, hasSessions: true });
|
||||
reduxHooks.useCardEntitlementData.mockReturnValueOnce({ canChange: false, hasSessions: true });
|
||||
wrapper = shallow(<SelectSessionButton {...props} />);
|
||||
expect(wrapper.prop(htmlProps.disabled)).toEqual(true);
|
||||
});
|
||||
test('entitlement does not have available sessions', () => {
|
||||
hooks.useCardEntitlementData.mockReturnValueOnce({ canChange: true, hasSessions: false });
|
||||
reduxHooks.useCardEntitlementData.mockReturnValueOnce({ canChange: true, hasSessions: false });
|
||||
wrapper = shallow(<SelectSessionButton {...props} />);
|
||||
expect(wrapper.prop(htmlProps.disabled)).toEqual(true);
|
||||
});
|
||||
test('user is masquerading', () => {
|
||||
hooks.useMasqueradeData.mockReturnValueOnce({ isMasquerading: true });
|
||||
reduxHooks.useMasqueradeData.mockReturnValueOnce({ isMasquerading: true });
|
||||
wrapper = shallow(<SelectSessionButton {...props} />);
|
||||
expect(wrapper.prop(htmlProps.disabled)).toEqual(true);
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Locked } from '@edx/paragon/icons';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import track from 'tracking';
|
||||
import { hooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
|
||||
import ActionButton from './ActionButton';
|
||||
import messages from './messages';
|
||||
@@ -13,10 +13,10 @@ import messages from './messages';
|
||||
export const UpgradeButton = ({ cardId }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
const { upgradeUrl } = hooks.useCardCourseRunData(cardId);
|
||||
const { canUpgrade } = hooks.useCardEnrollmentData(cardId);
|
||||
const { isMasquerading } = hooks.useMasqueradeData();
|
||||
const trackUpgradeClick = hooks.useTrackCourseEvent(
|
||||
const { upgradeUrl } = reduxHooks.useCardCourseRunData(cardId);
|
||||
const { canUpgrade } = reduxHooks.useCardEnrollmentData(cardId);
|
||||
const { isMasquerading } = reduxHooks.useMasqueradeData();
|
||||
const trackUpgradeClick = reduxHooks.useTrackCourseEvent(
|
||||
track.course.upgradeClicked,
|
||||
cardId,
|
||||
upgradeUrl,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import track from 'tracking';
|
||||
import { hooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import { htmlProps } from 'data/constants/htmlKeys';
|
||||
import UpgradeButton from './UpgradeButton';
|
||||
|
||||
@@ -11,8 +11,8 @@ jest.mock('tracking', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('data/redux', () => ({
|
||||
hooks: {
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useMasqueradeData: jest.fn(() => ({ isMasquerading: false })),
|
||||
useCardCourseRunData: jest.fn(),
|
||||
useCardEnrollmentData: jest.fn(() => ({ canUpgrade: true })),
|
||||
@@ -29,26 +29,26 @@ describe('UpgradeButton', () => {
|
||||
cardId: 'cardId',
|
||||
};
|
||||
const upgradeUrl = 'upgradeUrl';
|
||||
hooks.useCardCourseRunData.mockReturnValue({ upgradeUrl });
|
||||
reduxHooks.useCardCourseRunData.mockReturnValue({ upgradeUrl });
|
||||
describe('snapshot', () => {
|
||||
test('can upgrade', () => {
|
||||
const wrapper = shallow(<UpgradeButton {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(wrapper.prop(htmlProps.disabled)).toEqual(false);
|
||||
expect(wrapper.prop(htmlProps.onClick)).toEqual(hooks.useTrackCourseEvent(
|
||||
expect(wrapper.prop(htmlProps.onClick)).toEqual(reduxHooks.useTrackCourseEvent(
|
||||
track.course.upgradeClicked,
|
||||
props.cardId,
|
||||
upgradeUrl,
|
||||
));
|
||||
});
|
||||
test('cannot upgrade', () => {
|
||||
hooks.useCardEnrollmentData.mockReturnValueOnce({ canUpgrade: false });
|
||||
reduxHooks.useCardEnrollmentData.mockReturnValueOnce({ canUpgrade: false });
|
||||
const wrapper = shallow(<UpgradeButton {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(wrapper.prop(htmlProps.disabled)).toEqual(true);
|
||||
});
|
||||
test('masquerading', () => {
|
||||
hooks.useMasqueradeData.mockReturnValueOnce({ isMasquerading: true });
|
||||
reduxHooks.useMasqueradeData.mockReturnValueOnce({ isMasquerading: true });
|
||||
const wrapper = shallow(<UpgradeButton {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(wrapper.prop(htmlProps.disabled)).toEqual(true);
|
||||
|
||||
@@ -4,20 +4,19 @@ import PropTypes from 'prop-types';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import track from 'tracking';
|
||||
import { hooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import ActionButton from './ActionButton';
|
||||
import messages from './messages';
|
||||
|
||||
export const ViewCourseButton = ({ cardId }) => {
|
||||
const { homeUrl } = hooks.useCardCourseRunData(cardId);
|
||||
const { hasAccess } = hooks.useCardEnrollmentData(cardId);
|
||||
const handleClick = hooks.useTrackCourseEvent(
|
||||
const { formatMessage } = useIntl();
|
||||
const { homeUrl } = reduxHooks.useCardCourseRunData(cardId);
|
||||
const { hasAccess } = reduxHooks.useCardEnrollmentData(cardId);
|
||||
const handleClick = reduxHooks.useTrackCourseEvent(
|
||||
track.course.enterCourseClicked,
|
||||
cardId,
|
||||
homeUrl,
|
||||
);
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
<ActionButton
|
||||
disabled={!hasAccess}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { shallow } from 'enzyme';
|
||||
|
||||
import track from 'tracking';
|
||||
import { htmlProps } from 'data/constants/htmlKeys';
|
||||
import { hooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import ViewCourseButton from './ViewCourseButton';
|
||||
|
||||
jest.mock('tracking', () => ({
|
||||
@@ -11,8 +11,8 @@ jest.mock('tracking', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('data/redux', () => ({
|
||||
hooks: {
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useCardCourseRunData: jest.fn(),
|
||||
useCardEnrollmentData: jest.fn(),
|
||||
useCardEntitlementData: jest.fn(),
|
||||
@@ -33,9 +33,9 @@ const createWrapper = ({
|
||||
isExpired = false,
|
||||
propsOveride = {},
|
||||
}) => {
|
||||
hooks.useCardCourseRunData.mockReturnValue({ homeUrl });
|
||||
hooks.useCardEnrollmentData.mockReturnValueOnce({ hasAccess });
|
||||
hooks.useCardEntitlementData.mockReturnValueOnce({ isEntitlement, isExpired });
|
||||
reduxHooks.useCardCourseRunData.mockReturnValue({ homeUrl });
|
||||
reduxHooks.useCardEnrollmentData.mockReturnValueOnce({ hasAccess });
|
||||
reduxHooks.useCardEntitlementData.mockReturnValueOnce({ isEntitlement, isExpired });
|
||||
return shallow(<ViewCourseButton {...defaultProps} {...propsOveride} />);
|
||||
};
|
||||
|
||||
@@ -48,7 +48,7 @@ describe('ViewCourseButton', () => {
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
test('links to home URL', () => {
|
||||
expect(wrapper.prop(htmlProps.onClick)).toEqual(hooks.useTrackCourseEvent(
|
||||
expect(wrapper.prop(htmlProps.onClick)).toEqual(reduxHooks.useTrackCourseEvent(
|
||||
track.course.enterCourseClicked,
|
||||
defaultProps.cardId,
|
||||
homeUrl,
|
||||
@@ -66,7 +66,7 @@ describe('ViewCourseButton', () => {
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
test('links to home URL', () => {
|
||||
expect(wrapper.prop(htmlProps.onClick)).toEqual(hooks.useTrackCourseEvent(
|
||||
expect(wrapper.prop(htmlProps.onClick)).toEqual(reduxHooks.useTrackCourseEvent(
|
||||
track.course.enterCourseClicked,
|
||||
defaultProps.cardId,
|
||||
homeUrl,
|
||||
|
||||
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
||||
|
||||
import { ActionRow } from '@edx/paragon';
|
||||
|
||||
import { hooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
|
||||
import UpgradeButton from './UpgradeButton';
|
||||
import SelectSessionButton from './SelectSessionButton';
|
||||
@@ -12,9 +12,9 @@ import ResumeButton from './ResumeButton';
|
||||
import ViewCourseButton from './ViewCourseButton';
|
||||
|
||||
export const CourseCardActions = ({ cardId }) => {
|
||||
const { isEntitlement, isFulfilled } = hooks.useCardEntitlementData(cardId);
|
||||
const { isVerified, hasStarted } = hooks.useCardEnrollmentData(cardId);
|
||||
const { isArchived } = hooks.useCardCourseRunData(cardId);
|
||||
const { isEntitlement, isFulfilled } = reduxHooks.useCardEntitlementData(cardId);
|
||||
const { isVerified, hasStarted } = reduxHooks.useCardEnrollmentData(cardId);
|
||||
const { isArchived } = reduxHooks.useCardCourseRunData(cardId);
|
||||
let PrimaryButton;
|
||||
if (isEntitlement) {
|
||||
PrimaryButton = isFulfilled ? ViewCourseButton : SelectSessionButton;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { hooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
|
||||
import CourseCardActions from '.';
|
||||
|
||||
jest.mock('data/redux', () => ({
|
||||
hooks: {
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useCardCourseRunData: jest.fn(),
|
||||
useCardEnrollmentData: jest.fn(),
|
||||
useCardEntitlementData: jest.fn(),
|
||||
@@ -25,9 +25,9 @@ describe('CourseCardActions', () => {
|
||||
const createWrapper = ({
|
||||
isEntitlement, isFulfilled, isArchived, isVerified, hasStarted,
|
||||
}) => {
|
||||
hooks.useCardEntitlementData.mockReturnValueOnce({ isEntitlement, isFulfilled });
|
||||
hooks.useCardCourseRunData.mockReturnValueOnce({ isArchived });
|
||||
hooks.useCardEnrollmentData.mockReturnValueOnce({ isVerified, hasStarted });
|
||||
reduxHooks.useCardEntitlementData.mockReturnValueOnce({ isEntitlement, isFulfilled });
|
||||
reduxHooks.useCardCourseRunData.mockReturnValueOnce({ isArchived });
|
||||
reduxHooks.useCardEnrollmentData.mockReturnValueOnce({ isVerified, hasStarted });
|
||||
return shallow(<CourseCardActions {...props} />);
|
||||
};
|
||||
describe('snapshot', () => {
|
||||
|
||||
@@ -6,22 +6,23 @@ import { MailtoLink, Hyperlink } from '@edx/paragon';
|
||||
import { CheckCircle } from '@edx/paragon/icons';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { useFormatDate } from 'utils/hooks';
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { utilHooks, reduxHooks } from 'hooks';
|
||||
import Banner from 'components/Banner';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
const { useFormatDate } = utilHooks;
|
||||
|
||||
export const CertificateBanner = ({ cardId }) => {
|
||||
const certificate = appHooks.useCardCertificateData(cardId);
|
||||
const certificate = reduxHooks.useCardCertificateData(cardId);
|
||||
const {
|
||||
isAudit,
|
||||
isVerified,
|
||||
} = appHooks.useCardEnrollmentData(cardId);
|
||||
const { isPassing } = appHooks.useCardGradeData(cardId);
|
||||
const { isArchived } = appHooks.useCardCourseRunData(cardId);
|
||||
const { minPassingGrade, progressUrl } = appHooks.useCardCourseRunData(cardId);
|
||||
const { supportEmail, billingEmail } = appHooks.usePlatformSettingsData();
|
||||
} = reduxHooks.useCardEnrollmentData(cardId);
|
||||
const { isPassing } = reduxHooks.useCardGradeData(cardId);
|
||||
const { isArchived } = reduxHooks.useCardCourseRunData(cardId);
|
||||
const { minPassingGrade, progressUrl } = reduxHooks.useCardCourseRunData(cardId);
|
||||
const { supportEmail, billingEmail } = reduxHooks.usePlatformSettingsData();
|
||||
const { formatMessage } = useIntl();
|
||||
const formatDate = useFormatDate();
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { hooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import CertificateBanner from './CertificateBanner';
|
||||
import messages from './messages';
|
||||
|
||||
jest.mock('data/redux', () => ({
|
||||
hooks: {
|
||||
jest.mock('hooks', () => ({
|
||||
utilHooks: {
|
||||
useFormatDate: jest.fn(() => date => date),
|
||||
},
|
||||
reduxHooks: {
|
||||
useCardCertificateData: jest.fn(),
|
||||
useCardCourseRunData: jest.fn(),
|
||||
useCardEnrollmentData: jest.fn(),
|
||||
@@ -18,16 +21,17 @@ jest.mock('Components/Banner', () => 'Banner');
|
||||
|
||||
describe('CertificateBanner', () => {
|
||||
const props = { cardId: 'cardId' };
|
||||
hooks.usePlatformSettingsData.mockReturnValue({
|
||||
reduxHooks.usePlatformSettingsData.mockReturnValue({
|
||||
supportEmail: 'suport@email',
|
||||
billingEmail: 'billing@email',
|
||||
});
|
||||
hooks.useCardCourseRunData.mockReturnValue({
|
||||
reduxHooks.useCardCourseRunData.mockReturnValue({
|
||||
minPassingGrade: 0.8,
|
||||
progressUrl: 'progressUrl',
|
||||
});
|
||||
|
||||
const defaultCertificate = {
|
||||
availableDate: '10/20/3030',
|
||||
isRestricted: false,
|
||||
isDownloadable: false,
|
||||
isEarnedButUnavailable: false,
|
||||
@@ -44,10 +48,10 @@ describe('CertificateBanner', () => {
|
||||
grade = {},
|
||||
courseRun = {},
|
||||
}) => {
|
||||
hooks.useCardGradeData.mockReturnValueOnce({ ...defaultGrade, ...grade });
|
||||
hooks.useCardCertificateData.mockReturnValueOnce({ ...defaultCertificate, ...certificate });
|
||||
hooks.useCardEnrollmentData.mockReturnValueOnce({ ...defaultEnrollment, ...enrollment });
|
||||
hooks.useCardCourseRunData.mockReturnValueOnce({ ...defaultCourseRun, ...courseRun });
|
||||
reduxHooks.useCardGradeData.mockReturnValueOnce({ ...defaultGrade, ...grade });
|
||||
reduxHooks.useCardCertificateData.mockReturnValueOnce({ ...defaultCertificate, ...certificate });
|
||||
reduxHooks.useCardEnrollmentData.mockReturnValueOnce({ ...defaultEnrollment, ...enrollment });
|
||||
reduxHooks.useCardCourseRunData.mockReturnValueOnce({ ...defaultCourseRun, ...courseRun });
|
||||
return shallow(<CertificateBanner {...props} />);
|
||||
};
|
||||
describe('snapshot', () => {
|
||||
|
||||
@@ -4,8 +4,7 @@ import PropTypes from 'prop-types';
|
||||
import { Hyperlink } from '@edx/paragon';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { useFormatDate } from 'utils/hooks';
|
||||
import { utilHooks, reduxHooks } from 'hooks';
|
||||
import Banner from 'components/Banner';
|
||||
import messages from './messages';
|
||||
|
||||
@@ -15,10 +14,10 @@ export const CourseBanner = ({ cardId }) => {
|
||||
isAuditAccessExpired,
|
||||
canUpgrade,
|
||||
coursewareAccess = {},
|
||||
} = appHooks.useCardEnrollmentData(cardId);
|
||||
const courseRun = appHooks.useCardCourseRunData(cardId);
|
||||
} = reduxHooks.useCardEnrollmentData(cardId);
|
||||
const courseRun = reduxHooks.useCardCourseRunData(cardId);
|
||||
const { formatMessage } = useIntl();
|
||||
const formatDate = useFormatDate();
|
||||
const formatDate = utilHooks.useFormatDate();
|
||||
|
||||
const { hasUnmetPrerequisites, isStaff, isTooEarly } = coursewareAccess;
|
||||
|
||||
|
||||
@@ -2,15 +2,18 @@ import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { Hyperlink } from '@edx/paragon';
|
||||
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import { formatMessage } from 'testUtils';
|
||||
import { CourseBanner } from './CourseBanner';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
jest.mock('components/Banner', () => 'Banner');
|
||||
jest.mock('data/redux', () => ({
|
||||
hooks: {
|
||||
jest.mock('hooks', () => ({
|
||||
utilHooks: {
|
||||
useFormatDate: () => date => date,
|
||||
},
|
||||
reduxHooks: {
|
||||
useCardCourseRunData: jest.fn(),
|
||||
useCardEnrollmentData: jest.fn(),
|
||||
},
|
||||
@@ -41,11 +44,11 @@ const render = (overrides = {}) => {
|
||||
courseRun = {},
|
||||
enrollment = {},
|
||||
} = overrides;
|
||||
appHooks.useCardCourseRunData.mockReturnValueOnce({
|
||||
reduxHooks.useCardCourseRunData.mockReturnValueOnce({
|
||||
...courseRunData,
|
||||
...courseRun,
|
||||
});
|
||||
appHooks.useCardEnrollmentData.mockReturnValueOnce({
|
||||
reduxHooks.useCardEnrollmentData.mockReturnValueOnce({
|
||||
...enrollmentData,
|
||||
...enrollment,
|
||||
});
|
||||
@@ -55,8 +58,8 @@ const render = (overrides = {}) => {
|
||||
describe('CourseBanner', () => {
|
||||
test('initializes data with course number from enrollment, course and course run data', () => {
|
||||
render();
|
||||
expect(appHooks.useCardCourseRunData).toHaveBeenCalledWith(cardId);
|
||||
expect(appHooks.useCardEnrollmentData).toHaveBeenCalledWith(cardId);
|
||||
expect(reduxHooks.useCardCourseRunData).toHaveBeenCalledWith(cardId);
|
||||
expect(reduxHooks.useCardEnrollmentData).toHaveBeenCalledWith(cardId);
|
||||
});
|
||||
test('no display if learner is verified', () => {
|
||||
render({ enrollment: { isVerified: true } });
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { StrictDict } from 'utils';
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
|
||||
import { reduxHooks } from 'hooks';
|
||||
|
||||
import ApprovedContent from './views/ApprovedContent';
|
||||
import EligibleContent from './views/EligibleContent';
|
||||
@@ -14,8 +15,8 @@ export const statusComponents = StrictDict({
|
||||
});
|
||||
|
||||
export const useCreditBannerData = (cardId) => {
|
||||
const credit = appHooks.useCardCreditData(cardId);
|
||||
const { supportEmail } = appHooks.usePlatformSettingsData();
|
||||
const credit = reduxHooks.useCardCreditData(cardId);
|
||||
const { supportEmail } = reduxHooks.usePlatformSettingsData();
|
||||
if (!credit.isEligible) { return null; }
|
||||
|
||||
const { error, purchased, requestStatus } = credit;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { keyStore } from 'utils';
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
|
||||
import ApprovedContent from './views/ApprovedContent';
|
||||
import EligibleContent from './views/EligibleContent';
|
||||
@@ -9,8 +9,8 @@ import RejectedContent from './views/RejectedContent';
|
||||
|
||||
import * as hooks from './hooks';
|
||||
|
||||
jest.mock('data/redux', () => ({
|
||||
hooks: {
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useCardCreditData: jest.fn(),
|
||||
usePlatformSettingsData: jest.fn(),
|
||||
},
|
||||
@@ -34,18 +34,18 @@ const defaultProps = {
|
||||
};
|
||||
|
||||
const loadHook = (creditData = {}) => {
|
||||
appHooks.useCardCreditData.mockReturnValue({ ...defaultProps, ...creditData });
|
||||
reduxHooks.useCardCreditData.mockReturnValue({ ...defaultProps, ...creditData });
|
||||
out = hooks.useCreditBannerData(cardId);
|
||||
};
|
||||
|
||||
describe('useCreditBannerData hook', () => {
|
||||
beforeEach(() => {
|
||||
appHooks.usePlatformSettingsData.mockReturnValue({ supportEmail });
|
||||
reduxHooks.usePlatformSettingsData.mockReturnValue({ supportEmail });
|
||||
});
|
||||
it('loads card credit data with cardID and loads platform settings data', () => {
|
||||
loadHook({ isEligible: false });
|
||||
expect(appHooks.useCardCreditData).toHaveBeenCalledWith(cardId);
|
||||
expect(appHooks.usePlatformSettingsData).toHaveBeenCalledWith();
|
||||
expect(reduxHooks.useCardCreditData).toHaveBeenCalledWith(cardId);
|
||||
expect(reduxHooks.usePlatformSettingsData).toHaveBeenCalledWith();
|
||||
});
|
||||
describe('non-credit-eligible learner', () => {
|
||||
it('returns null if the learner is not credit eligible', () => {
|
||||
|
||||
@@ -3,14 +3,14 @@ import PropTypes from 'prop-types';
|
||||
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import CreditContent from './components/CreditContent';
|
||||
import ProviderLink from './components/ProviderLink';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
export const ApprovedContent = ({ cardId }) => {
|
||||
const { providerStatusUrl: href, providerName } = appHooks.useCardCreditData(cardId);
|
||||
const { providerStatusUrl: href, providerName } = reduxHooks.useCardCreditData(cardId);
|
||||
const { formatMessage } = useIntl();
|
||||
return (
|
||||
<CreditContent
|
||||
|
||||
@@ -2,13 +2,13 @@ import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { formatMessage } from 'testUtils';
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import messages from './messages';
|
||||
import ProviderLink from './components/ProviderLink';
|
||||
import ApprovedContent from './ApprovedContent';
|
||||
|
||||
jest.mock('data/redux', () => ({
|
||||
hooks: {
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useCardCreditData: jest.fn(),
|
||||
},
|
||||
}));
|
||||
@@ -21,7 +21,7 @@ const credit = {
|
||||
providerStatusUrl: 'test-credit-provider-status-url',
|
||||
providerName: 'test-credit-provider-name',
|
||||
};
|
||||
appHooks.useCardCreditData.mockReturnValue(credit);
|
||||
reduxHooks.useCardCreditData.mockReturnValue(credit);
|
||||
|
||||
describe('ApprovedContent component', () => {
|
||||
beforeEach(() => {
|
||||
@@ -29,7 +29,7 @@ describe('ApprovedContent component', () => {
|
||||
});
|
||||
describe('behavior', () => {
|
||||
it('initializes credit data with cardId', () => {
|
||||
expect(appHooks.useCardCreditData).toHaveBeenCalledWith(cardId);
|
||||
expect(reduxHooks.useCardCreditData).toHaveBeenCalledWith(cardId);
|
||||
});
|
||||
});
|
||||
describe('render', () => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
||||
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import track from 'tracking';
|
||||
|
||||
import CreditContent from './components/CreditContent';
|
||||
@@ -11,8 +11,8 @@ import messages from './messages';
|
||||
|
||||
export const EligibleContent = ({ cardId }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { providerName, creditPurchaseUrl: href } = appHooks.useCardCreditData(cardId);
|
||||
const { courseId } = appHooks.useCardCourseRunData(cardId);
|
||||
const { providerName, creditPurchaseUrl: href } = reduxHooks.useCardCreditData(cardId);
|
||||
const { courseId } = reduxHooks.useCardCourseRunData(cardId);
|
||||
|
||||
const onClick = track.credit.purchase(courseId, href);
|
||||
const getCredit = formatMessage(messages.getCredit);
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import { formatMessage } from 'testUtils';
|
||||
import track from 'tracking';
|
||||
|
||||
import messages from './messages';
|
||||
import EligibleContent from './EligibleContent';
|
||||
|
||||
jest.mock('data/redux', () => ({
|
||||
hooks: {
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useCardCreditData: jest.fn(),
|
||||
useCardCourseRunData: jest.fn(),
|
||||
},
|
||||
@@ -30,8 +30,8 @@ const credit = {
|
||||
creditPurchaseUrl: 'test-credit-purchase-url',
|
||||
providerName: 'test-credit-provider-name',
|
||||
};
|
||||
appHooks.useCardCreditData.mockReturnValue(credit);
|
||||
appHooks.useCardCourseRunData.mockReturnValue({ courseId });
|
||||
reduxHooks.useCardCreditData.mockReturnValue(credit);
|
||||
reduxHooks.useCardCourseRunData.mockReturnValue({ courseId });
|
||||
|
||||
const render = () => {
|
||||
el = shallow(<EligibleContent cardId={cardId} />);
|
||||
@@ -45,10 +45,10 @@ describe('EligibleContent component', () => {
|
||||
});
|
||||
describe('behavior', () => {
|
||||
it('initializes credit data with cardId', () => {
|
||||
expect(appHooks.useCardCreditData).toHaveBeenCalledWith(cardId);
|
||||
expect(reduxHooks.useCardCreditData).toHaveBeenCalledWith(cardId);
|
||||
});
|
||||
it('initializes course run data with cardId', () => {
|
||||
expect(appHooks.useCardCourseRunData).toHaveBeenCalledWith(cardId);
|
||||
expect(reduxHooks.useCardCourseRunData).toHaveBeenCalledWith(cardId);
|
||||
});
|
||||
});
|
||||
describe('render', () => {
|
||||
@@ -65,7 +65,7 @@ describe('EligibleContent component', () => {
|
||||
expect(component.props().action.message).toEqual(formatMessage(messages.getCredit));
|
||||
});
|
||||
test('message is formatted eligible message if no provider', () => {
|
||||
appHooks.useCardCreditData.mockReturnValueOnce({
|
||||
reduxHooks.useCardCreditData.mockReturnValueOnce({
|
||||
creditPurchaseUrl: credit.creditPurchaseUrl,
|
||||
});
|
||||
render();
|
||||
|
||||
@@ -3,13 +3,13 @@ import PropTypes from 'prop-types';
|
||||
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import CreditContent from './components/CreditContent';
|
||||
import messages from './messages';
|
||||
import hooks from './hooks';
|
||||
|
||||
export const PendingContent = ({ cardId }) => {
|
||||
const { providerName } = appHooks.useCardCreditData(cardId);
|
||||
const { providerName } = reduxHooks.useCardCreditData(cardId);
|
||||
const { formatMessage } = useIntl();
|
||||
const { requestData, createCreditRequest } = hooks.useCreditRequestData(cardId);
|
||||
return (
|
||||
|
||||
@@ -2,13 +2,13 @@ import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { formatMessage } from 'testUtils';
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
|
||||
import messages from './messages';
|
||||
import hooks from './hooks';
|
||||
import PendingContent from './PendingContent';
|
||||
|
||||
jest.mock('data/redux', () => ({ hooks: { useCardCreditData: jest.fn() } }));
|
||||
jest.mock('hooks', () => ({ reduxHooks: { useCardCreditData: jest.fn() } }));
|
||||
jest.mock('./hooks', () => ({ useCreditRequestData: jest.fn() }));
|
||||
jest.mock('./components/CreditContent', () => 'CreditContent');
|
||||
jest.mock('./components/ProviderLink', () => 'ProviderLink');
|
||||
@@ -20,7 +20,7 @@ const cardId = 'test-card-id';
|
||||
const requestData = { test: 'requestData' };
|
||||
const providerName = 'test-credit-provider-name';
|
||||
const createCreditRequest = jest.fn().mockName('createCreditRequest');
|
||||
appHooks.useCardCreditData.mockReturnValue({ providerName });
|
||||
reduxHooks.useCardCreditData.mockReturnValue({ providerName });
|
||||
hooks.useCreditRequestData.mockReturnValue({ requestData, createCreditRequest });
|
||||
|
||||
const render = () => {
|
||||
@@ -32,7 +32,7 @@ describe('PendingContent component', () => {
|
||||
});
|
||||
describe('behavior', () => {
|
||||
it('initializes card credit data with cardId', () => {
|
||||
expect(appHooks.useCardCreditData).toHaveBeenCalledWith(cardId);
|
||||
expect(reduxHooks.useCardCreditData).toHaveBeenCalledWith(cardId);
|
||||
});
|
||||
it('initializes credit request data with cardId', () => {
|
||||
expect(hooks.useCreditRequestData).toHaveBeenCalledWith(cardId);
|
||||
|
||||
@@ -3,13 +3,13 @@ import PropTypes from 'prop-types';
|
||||
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import CreditContent from './components/CreditContent';
|
||||
import ProviderLink from './components/ProviderLink';
|
||||
import messages from './messages';
|
||||
|
||||
export const RejectedContent = ({ cardId }) => {
|
||||
const credit = appHooks.useCardCreditData(cardId);
|
||||
const credit = reduxHooks.useCardCreditData(cardId);
|
||||
const { formatMessage } = useIntl();
|
||||
return (
|
||||
<CreditContent
|
||||
|
||||
@@ -2,13 +2,13 @@ import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { formatMessage } from 'testUtils';
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import messages from './messages';
|
||||
import ProviderLink from './components/ProviderLink';
|
||||
import RejectedContent from './RejectedContent';
|
||||
|
||||
jest.mock('data/redux', () => ({
|
||||
hooks: {
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useCardCreditData: jest.fn(),
|
||||
},
|
||||
}));
|
||||
@@ -20,7 +20,7 @@ const credit = {
|
||||
providerStatusUrl: 'test-credit-provider-status-url',
|
||||
providerName: 'test-credit-provider-name',
|
||||
};
|
||||
appHooks.useCardCreditData.mockReturnValue(credit);
|
||||
reduxHooks.useCardCreditData.mockReturnValue(credit);
|
||||
|
||||
let el;
|
||||
let component;
|
||||
@@ -31,7 +31,7 @@ describe('RejectedContent component', () => {
|
||||
beforeEach(render);
|
||||
describe('behavior', () => {
|
||||
it('initializes credit data with cardId', () => {
|
||||
expect(appHooks.useCardCreditData).toHaveBeenCalledWith(cardId);
|
||||
expect(reduxHooks.useCardCreditData).toHaveBeenCalledWith(cardId);
|
||||
});
|
||||
});
|
||||
describe('render', () => {
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import { Hyperlink } from '@edx/paragon';
|
||||
|
||||
export const ProviderLink = ({ cardId }) => {
|
||||
const credit = appHooks.useCardCreditData(cardId);
|
||||
const credit = reduxHooks.useCardCreditData(cardId);
|
||||
return (
|
||||
<Hyperlink
|
||||
href={credit.providerStatusUrl}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
|
||||
import ProviderLink from './ProviderLink';
|
||||
|
||||
jest.mock('data/redux', () => ({
|
||||
hooks: {
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useCardCreditData: jest.fn(),
|
||||
},
|
||||
}));
|
||||
@@ -20,12 +20,12 @@ let el;
|
||||
|
||||
describe('ProviderLink component', () => {
|
||||
beforeEach(() => {
|
||||
appHooks.useCardCreditData.mockReturnValue(credit);
|
||||
reduxHooks.useCardCreditData.mockReturnValue(credit);
|
||||
el = shallow(<ProviderLink cardId={cardId} />);
|
||||
});
|
||||
describe('behavior', () => {
|
||||
it('initializes credit hook with cardId', () => {
|
||||
expect(appHooks.useCardCreditData).toHaveBeenCalledWith(cardId);
|
||||
expect(reduxHooks.useCardCreditData).toHaveBeenCalledWith(cardId);
|
||||
});
|
||||
});
|
||||
describe('render', () => {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import { StrictDict } from 'utils';
|
||||
import { AppContext } from '@edx/frontend-platform/react';
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import api from 'data/services/lms/api';
|
||||
import { apiHooks } from 'hooks';
|
||||
|
||||
import * as module from './hooks';
|
||||
|
||||
@@ -12,17 +11,11 @@ export const state = StrictDict({
|
||||
|
||||
export const useCreditRequestData = (cardId) => {
|
||||
const [requestData, setRequestData] = module.state.creditRequestData(null);
|
||||
const { courseId } = appHooks.useCardCourseRunData(cardId);
|
||||
const { providerId } = appHooks.useCardCreditData(cardId);
|
||||
const { authenticatedUser } = React.useContext(AppContext);
|
||||
const { username } = authenticatedUser;
|
||||
|
||||
const createCreditApiRequest = apiHooks.useCreateCreditRequest(cardId);
|
||||
const createCreditRequest = (e) => {
|
||||
e.preventDefault();
|
||||
api.createCreditRequest({ providerId, courseId, username })
|
||||
.then(setRequestData);
|
||||
createCreditApiRequest().then(setRequestData);
|
||||
};
|
||||
|
||||
return { requestData, createCreditRequest };
|
||||
};
|
||||
|
||||
|
||||
@@ -1,37 +1,21 @@
|
||||
import { AppContext } from '@edx/frontend-platform/react';
|
||||
|
||||
import { MockUseState } from 'testUtils';
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import api from 'data/services/lms/api';
|
||||
import { apiHooks } from 'hooks';
|
||||
import * as hooks from './hooks';
|
||||
|
||||
jest.mock('@edx/frontend-platform/react', () => ({
|
||||
AppContext: {
|
||||
authenticatedUser: { username: 'test-username' },
|
||||
jest.mock('hooks', () => ({
|
||||
apiHooks: {
|
||||
useCreateCreditRequest: jest.fn(),
|
||||
},
|
||||
}));
|
||||
jest.mock('data/redux', () => ({
|
||||
hooks: {
|
||||
useCardCourseRunData: jest.fn(),
|
||||
useCardCreditData: jest.fn(),
|
||||
},
|
||||
}));
|
||||
jest.mock('data/services/lms/api', () => ({
|
||||
createCreditRequest: jest.fn(),
|
||||
}));
|
||||
|
||||
const state = new MockUseState(hooks);
|
||||
|
||||
const cardId = 'test-card-id';
|
||||
const testValue = 'test-value';
|
||||
const courseId = 'test-course-id';
|
||||
const providerId = 'test-credit-provider-id';
|
||||
const requestData = { test: 'request data' };
|
||||
const creditRequest = jest.fn().mockReturnValue(Promise.resolve(requestData));
|
||||
apiHooks.useCreateCreditRequest.mockReturnValue(creditRequest);
|
||||
const event = { preventDefault: jest.fn() };
|
||||
|
||||
appHooks.useCardCourseRunData.mockReturnValue({ courseId });
|
||||
appHooks.useCardCreditData.mockReturnValue({ providerId });
|
||||
api.createCreditRequest.mockReturnValue(Promise.resolve(testValue));
|
||||
|
||||
const { username } = AppContext.authenticatedUser;
|
||||
let out;
|
||||
describe('Credit Banner view hooks', () => {
|
||||
describe('state', () => {
|
||||
@@ -40,35 +24,31 @@ describe('Credit Banner view hooks', () => {
|
||||
describe('useCreditRequestData', () => {
|
||||
beforeEach(() => {
|
||||
state.mock();
|
||||
state.mockVal(state.keys.creditRequestData, testValue);
|
||||
out = hooks.useCreditRequestData(cardId);
|
||||
});
|
||||
describe('behavior', () => {
|
||||
it('initializes creditRequestData state field with null value', () => {
|
||||
state.expectInitializedWith(state.keys.creditRequestData, null);
|
||||
});
|
||||
it('calls useCardCourseRunData with passed cardID', () => {
|
||||
expect(appHooks.useCardCourseRunData).toHaveBeenCalledWith(cardId);
|
||||
});
|
||||
it('calls useCardCreditData with passed cardID', () => {
|
||||
expect(appHooks.useCardCreditData).toHaveBeenCalledWith(cardId);
|
||||
it('calls useCreateCreditRequest with passed cardID', () => {
|
||||
expect(apiHooks.useCreateCreditRequest).toHaveBeenCalledWith(cardId);
|
||||
});
|
||||
});
|
||||
describe('output', () => {
|
||||
it('returns requestData state value', () => {
|
||||
expect(out.requestData).toEqual(testValue);
|
||||
state.mockVal(state.keys.creditRequestData, requestData);
|
||||
out = hooks.useCreditRequestData(cardId);
|
||||
expect(out.requestData).toEqual(requestData);
|
||||
});
|
||||
describe('createCreditRequest', () => {
|
||||
const preventDefault = jest.fn();
|
||||
const event = { preventDefault };
|
||||
it('returns an event handler that prevents default click behavior', () => {
|
||||
out.createCreditRequest(event);
|
||||
expect(preventDefault).toHaveBeenCalled();
|
||||
expect(event.preventDefault).toHaveBeenCalled();
|
||||
});
|
||||
it('calls api.createCreditRequest and sets requestData with the response', async () => {
|
||||
await out.createCreditRequest(event);
|
||||
expect(api.createCreditRequest).toHaveBeenCalledWith({ providerId, courseId, username });
|
||||
expect(state.setState.creditRequestData).toHaveBeenCalledWith(testValue);
|
||||
expect(creditRequest).toHaveBeenCalledWith();
|
||||
expect(state.setState.creditRequestData).toHaveBeenCalledWith(requestData);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,13 +4,13 @@ import PropTypes from 'prop-types';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Button, MailtoLink } from '@edx/paragon';
|
||||
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { useFormatDate } from 'utils/hooks';
|
||||
import { utilHooks, reduxHooks } from 'hooks';
|
||||
|
||||
import Banner from 'components/Banner';
|
||||
import messages from './messages';
|
||||
|
||||
export const EntitlementBanner = ({ cardId }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const {
|
||||
isEntitlement,
|
||||
hasSessions,
|
||||
@@ -18,11 +18,10 @@ export const EntitlementBanner = ({ cardId }) => {
|
||||
changeDeadline,
|
||||
showExpirationWarning,
|
||||
isExpired,
|
||||
} = appHooks.useCardEntitlementData(cardId);
|
||||
const { supportEmail } = appHooks.usePlatformSettingsData();
|
||||
const openSessionModal = appHooks.useUpdateSelectSessionModalCallback(cardId);
|
||||
const { formatMessage } = useIntl();
|
||||
const formatDate = useFormatDate();
|
||||
} = reduxHooks.useCardEntitlementData(cardId);
|
||||
const { supportEmail } = reduxHooks.usePlatformSettingsData();
|
||||
const openSessionModal = reduxHooks.useUpdateSelectSessionModalCallback(cardId);
|
||||
const formatDate = utilHooks.useFormatDate();
|
||||
|
||||
if (!isEntitlement) {
|
||||
return null;
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import EntitlementBanner from './EntitlementBanner';
|
||||
|
||||
jest.mock('components/Banner', () => 'Banner');
|
||||
jest.mock('data/redux', () => ({
|
||||
hooks: {
|
||||
jest.mock('hooks', () => ({
|
||||
utilHooks: {
|
||||
useFormatDate: () => date => date,
|
||||
},
|
||||
reduxHooks: {
|
||||
usePlatformSettingsData: jest.fn(),
|
||||
useCardEntitlementData: jest.fn(),
|
||||
useUpdateSelectSessionModalCallback: jest.fn(
|
||||
@@ -30,16 +33,16 @@ const platformData = { supportEmail: 'test-support-email' };
|
||||
|
||||
const render = (overrides = {}) => {
|
||||
const { entitlement = {} } = overrides;
|
||||
appHooks.useCardEntitlementData.mockReturnValueOnce({ ...entitlementData, ...entitlement });
|
||||
appHooks.usePlatformSettingsData.mockReturnValueOnce(platformData);
|
||||
reduxHooks.useCardEntitlementData.mockReturnValueOnce({ ...entitlementData, ...entitlement });
|
||||
reduxHooks.usePlatformSettingsData.mockReturnValueOnce(platformData);
|
||||
el = shallow(<EntitlementBanner cardId={cardId} />);
|
||||
};
|
||||
|
||||
describe('EntitlementBanner', () => {
|
||||
test('initializes data with course number from entitlement', () => {
|
||||
render();
|
||||
expect(appHooks.useCardEntitlementData).toHaveBeenCalledWith(cardId);
|
||||
expect(appHooks.useUpdateSelectSessionModalCallback).toHaveBeenCalledWith(cardId);
|
||||
expect(reduxHooks.useCardEntitlementData).toHaveBeenCalledWith(cardId);
|
||||
expect(reduxHooks.useUpdateSelectSessionModalCallback).toHaveBeenCalledWith(cardId);
|
||||
});
|
||||
test('no display if not an entitlement', () => {
|
||||
render({ entitlement: { isEntitlement: false } });
|
||||
|
||||
@@ -15,7 +15,7 @@ exports[`CertificateBanner snapshot is passing and is downloadable 1`] = `
|
||||
|
||||
exports[`CertificateBanner snapshot is passing and is earned but unavailable 1`] = `
|
||||
<Banner>
|
||||
Your grade and certificate will be ready after Invalid Date.
|
||||
Your grade and certificate will be ready after 10/20/3030.
|
||||
</Banner>
|
||||
`;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
|
||||
import CourseBanner from './CourseBanner';
|
||||
import CertificateBanner from './CertificateBanner';
|
||||
@@ -9,7 +9,7 @@ import CreditBanner from './CreditBanner';
|
||||
import EntitlementBanner from './EntitlementBanner';
|
||||
|
||||
export const CourseCardBanners = ({ cardId }) => {
|
||||
const { isEnrolled } = appHooks.useCardEnrollmentData(cardId);
|
||||
const { isEnrolled } = reduxHooks.useCardEnrollmentData(cardId);
|
||||
return (
|
||||
<div className="course-card-banners" data-testid="CourseCardBanners">
|
||||
<CourseBanner cardId={cardId} />
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { useFormatDate } from 'utils/hooks';
|
||||
import { utilHooks, reduxHooks } from 'hooks';
|
||||
|
||||
import * as hooks from './hooks';
|
||||
import messages from './messages';
|
||||
|
||||
export const useAccessMessage = ({ cardId }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const enrollment = appHooks.useCardEnrollmentData(cardId);
|
||||
const courseRun = appHooks.useCardCourseRunData(cardId);
|
||||
const formatDate = useFormatDate();
|
||||
const enrollment = reduxHooks.useCardEnrollmentData(cardId);
|
||||
const courseRun = reduxHooks.useCardCourseRunData(cardId);
|
||||
const formatDate = utilHooks.useFormatDate();
|
||||
if (!courseRun.isStarted) {
|
||||
if (!courseRun.startDate) { return null; }
|
||||
const startDate = formatDate(courseRun.startDate);
|
||||
@@ -39,15 +38,15 @@ export const useAccessMessage = ({ cardId }) => {
|
||||
|
||||
export const useCardDetailsData = ({ cardId }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const providerName = appHooks.useCardProviderData(cardId).name;
|
||||
const { courseNumber } = appHooks.useCardCourseData(cardId);
|
||||
const providerName = reduxHooks.useCardProviderData(cardId).name;
|
||||
const { courseNumber } = reduxHooks.useCardCourseData(cardId);
|
||||
const {
|
||||
isEntitlement,
|
||||
isFulfilled,
|
||||
canChange,
|
||||
} = appHooks.useCardEntitlementData(cardId);
|
||||
} = reduxHooks.useCardEntitlementData(cardId);
|
||||
|
||||
const openSessionModal = appHooks.useUpdateSelectSessionModalCallback(cardId);
|
||||
const openSessionModal = reduxHooks.useUpdateSelectSessionModalCallback(cardId);
|
||||
|
||||
return {
|
||||
providerName: providerName || formatMessage(messages.unknownProviderName),
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { keyStore, dateFormatter } from 'utils';
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { keyStore } from 'utils';
|
||||
import { utilHooks, reduxHooks } from 'hooks';
|
||||
|
||||
import * as hooks from './hooks';
|
||||
import messages from './messages';
|
||||
|
||||
jest.mock('data/redux', () => ({
|
||||
hooks: {
|
||||
jest.mock('hooks', () => ({
|
||||
utilHooks: {
|
||||
useFormatDate: jest.fn(),
|
||||
},
|
||||
reduxHooks: {
|
||||
useCardCourseData: jest.fn(),
|
||||
useCardCourseRunData: jest.fn(),
|
||||
useCardEnrollmentData: jest.fn(),
|
||||
@@ -21,11 +24,13 @@ const cardId = 'my-test-card-id';
|
||||
const courseNumber = 'test-course-number';
|
||||
const useAccessMessage = 'test-access-message';
|
||||
const mockAccessMessage = (args) => ({ cardId: args.cardId, useAccessMessage });
|
||||
const formatDate = jest.fn(date => `formatted-${date}`);
|
||||
utilHooks.useFormatDate.mockReturnValue(formatDate);
|
||||
const hookKeys = keyStore(hooks);
|
||||
|
||||
describe('CourseCardDetails hooks', () => {
|
||||
let out;
|
||||
const { formatMessage, formatDate } = useIntl();
|
||||
const { formatMessage } = useIntl();
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
@@ -45,15 +50,15 @@ describe('CourseCardDetails hooks', () => {
|
||||
const runHook = ({ provider = {}, entitlement = {} }) => {
|
||||
jest.spyOn(hooks, hookKeys.useAccessMessage)
|
||||
.mockImplementationOnce(mockAccessMessage);
|
||||
appHooks.useCardProviderData.mockReturnValueOnce({
|
||||
reduxHooks.useCardProviderData.mockReturnValueOnce({
|
||||
...providerData,
|
||||
...provider,
|
||||
});
|
||||
appHooks.useCardEntitlementData.mockReturnValueOnce({
|
||||
reduxHooks.useCardEntitlementData.mockReturnValueOnce({
|
||||
...entitlementData,
|
||||
...entitlement,
|
||||
});
|
||||
appHooks.useCardCourseData.mockReturnValueOnce({ courseNumber });
|
||||
reduxHooks.useCardCourseData.mockReturnValueOnce({ courseNumber });
|
||||
out = hooks.useCardDetailsData({ cardId });
|
||||
};
|
||||
beforeEach(() => {
|
||||
@@ -86,11 +91,11 @@ describe('CourseCardDetails hooks', () => {
|
||||
endDate: '10/20/2000',
|
||||
};
|
||||
const runHook = ({ enrollment = {}, courseRun = {} }) => {
|
||||
appHooks.useCardCourseRunData.mockReturnValueOnce({
|
||||
reduxHooks.useCardCourseRunData.mockReturnValueOnce({
|
||||
...courseRunData,
|
||||
...courseRun,
|
||||
});
|
||||
appHooks.useCardEnrollmentData.mockReturnValueOnce({
|
||||
reduxHooks.useCardEnrollmentData.mockReturnValueOnce({
|
||||
...enrollmentData,
|
||||
...enrollment,
|
||||
});
|
||||
@@ -99,8 +104,8 @@ describe('CourseCardDetails hooks', () => {
|
||||
|
||||
it('loads data from enrollment and course run data based on course number', () => {
|
||||
runHook({});
|
||||
expect(appHooks.useCardCourseRunData).toHaveBeenCalledWith(cardId);
|
||||
expect(appHooks.useCardEnrollmentData).toHaveBeenCalledWith(cardId);
|
||||
expect(reduxHooks.useCardCourseRunData).toHaveBeenCalledWith(cardId);
|
||||
expect(reduxHooks.useCardEnrollmentData).toHaveBeenCalledWith(cardId);
|
||||
});
|
||||
|
||||
describe('if not started yet', () => {
|
||||
@@ -111,7 +116,7 @@ describe('CourseCardDetails hooks', () => {
|
||||
});
|
||||
expect(out).toEqual(formatMessage(
|
||||
messages.courseStarts,
|
||||
{ startDate: dateFormatter(formatDate, courseRunData.startDate) },
|
||||
{ startDate: formatDate(courseRunData.startDate) },
|
||||
));
|
||||
});
|
||||
});
|
||||
@@ -123,7 +128,7 @@ describe('CourseCardDetails hooks', () => {
|
||||
runHook({ enrollment: { isAudit: true, isAuditAccessExpired: true } });
|
||||
expect(out).toEqual(formatMessage(
|
||||
messages.accessExpired,
|
||||
{ accessExpirationDate: dateFormatter(formatDate, enrollmentData.accessExpirationDate) },
|
||||
{ accessExpirationDate: formatDate(enrollmentData.accessExpirationDate) },
|
||||
));
|
||||
});
|
||||
});
|
||||
@@ -133,7 +138,7 @@ describe('CourseCardDetails hooks', () => {
|
||||
runHook({ enrollment: { isAudit: true } });
|
||||
expect(out).toEqual(formatMessage(
|
||||
messages.accessExpires,
|
||||
{ accessExpirationDate: dateFormatter(formatDate, enrollmentData.accessExpirationDate) },
|
||||
{ accessExpirationDate: formatDate(enrollmentData.accessExpirationDate) },
|
||||
));
|
||||
});
|
||||
it('no endDate and no accessExpirationDate, returns null', () => {
|
||||
@@ -144,14 +149,14 @@ describe('CourseCardDetails hooks', () => {
|
||||
runHook({ enrollment: { isAudit: true, accessExpirationDate: '' } });
|
||||
expect(out).toEqual(formatMessage(
|
||||
messages.courseEnds,
|
||||
{ endDate: dateFormatter(formatDate, courseRunData.endDate) },
|
||||
{ endDate: formatDate(courseRunData.endDate) },
|
||||
));
|
||||
});
|
||||
it('no accessExpirationDate and is archived, return courseEnded with endDate', () => {
|
||||
runHook({ enrollment: { isAudit: true, accessExpirationDate: '' }, courseRun: { isArchived: true } });
|
||||
expect(out).toEqual(formatMessage(
|
||||
messages.courseEnded,
|
||||
{ endDate: dateFormatter(formatDate, courseRunData.endDate) },
|
||||
{ endDate: formatDate(courseRunData.endDate) },
|
||||
));
|
||||
});
|
||||
});
|
||||
@@ -162,7 +167,7 @@ describe('CourseCardDetails hooks', () => {
|
||||
runHook({});
|
||||
expect(out).toEqual(formatMessage(
|
||||
messages.courseEnds,
|
||||
{ endDate: dateFormatter(formatDate, courseRunData.endDate) },
|
||||
{ endDate: formatDate(courseRunData.endDate) },
|
||||
));
|
||||
});
|
||||
});
|
||||
@@ -172,7 +177,7 @@ describe('CourseCardDetails hooks', () => {
|
||||
runHook({ courseRun: { isArchived: true } });
|
||||
expect(out).toEqual(formatMessage(
|
||||
messages.courseEnded,
|
||||
{ endDate: dateFormatter(formatDate, courseRunData.endDate) },
|
||||
{ endDate: formatDate(courseRunData.endDate) },
|
||||
));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Badge } from '@edx/paragon';
|
||||
|
||||
import track from 'tracking';
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
|
||||
import verifiedRibbon from 'assets/verified-ribbon.png';
|
||||
|
||||
@@ -15,11 +15,11 @@ const { courseImageClicked } = track.course;
|
||||
|
||||
export const CourseCardImage = ({ cardId, orientation }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { bannerImgSrc } = appHooks.useCardCourseData(cardId);
|
||||
const { homeUrl } = appHooks.useCardCourseRunData(cardId);
|
||||
const { isVerified } = appHooks.useCardEnrollmentData(cardId);
|
||||
const { isEntitlement } = appHooks.useCardEntitlementData(cardId);
|
||||
const handleImageClicked = appHooks.useTrackCourseEvent(courseImageClicked, cardId, homeUrl);
|
||||
const { bannerImgSrc } = reduxHooks.useCardCourseData(cardId);
|
||||
const { homeUrl } = reduxHooks.useCardCourseRunData(cardId);
|
||||
const { isVerified } = reduxHooks.useCardEnrollmentData(cardId);
|
||||
const { isEntitlement } = reduxHooks.useCardEntitlementData(cardId);
|
||||
const handleImageClicked = reduxHooks.useTrackCourseEvent(courseImageClicked, cardId, homeUrl);
|
||||
const wrapperClassName = `pgn__card-wrapper-image-cap overflow-visible ${orientation}`;
|
||||
const image = (
|
||||
<>
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { StrictDict } from 'utils';
|
||||
|
||||
import track from 'tracking';
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
|
||||
import * as module from './hooks';
|
||||
|
||||
@@ -31,7 +31,7 @@ export const useEmailSettings = () => {
|
||||
|
||||
export const useHandleToggleDropdown = (cardId) => {
|
||||
const eventName = track.course.courseOptionsDropdownClicked;
|
||||
const trackCourseEvent = appHooks.useTrackCourseEvent(eventName, cardId);
|
||||
const trackCourseEvent = reduxHooks.useTrackCourseEvent(eventName, cardId);
|
||||
return (isOpen) => {
|
||||
if (isOpen) { trackCourseEvent(); }
|
||||
};
|
||||
|
||||
@@ -1,9 +1,22 @@
|
||||
import { MockUseState } from 'testUtils';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import track from 'tracking';
|
||||
|
||||
import * as hooks from './hooks';
|
||||
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useTrackCourseEvent: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
const trackCourseEvent = jest.fn();
|
||||
reduxHooks.useTrackCourseEvent.mockReturnValue(trackCourseEvent);
|
||||
const state = new MockUseState(hooks);
|
||||
|
||||
const cardId = 'test-card-id';
|
||||
let out;
|
||||
|
||||
describe('CourseCardMenu hooks', () => {
|
||||
describe('state values', () => {
|
||||
state.testGetter(state.keys.isUnenrollConfirmVisible);
|
||||
@@ -11,7 +24,6 @@ describe('CourseCardMenu hooks', () => {
|
||||
});
|
||||
|
||||
describe('useUnenrollData', () => {
|
||||
let out;
|
||||
beforeEach(() => {
|
||||
state.mock();
|
||||
out = hooks.useUnenrollData();
|
||||
@@ -34,7 +46,6 @@ describe('CourseCardMenu hooks', () => {
|
||||
});
|
||||
|
||||
describe('useEmailSettings', () => {
|
||||
let out;
|
||||
beforeEach(() => {
|
||||
state.mock();
|
||||
out = hooks.useEmailSettings();
|
||||
@@ -55,4 +66,26 @@ describe('CourseCardMenu hooks', () => {
|
||||
state.expectSetStateCalledWith(state.keys.isEmailSettingsVisible, false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useHandleToggleDropdown', () => {
|
||||
beforeEach(() => {
|
||||
out = hooks.useHandleToggleDropdown(cardId);
|
||||
});
|
||||
describe('behavior', () => {
|
||||
it('initializes course event tracker with event name and card ID', () => {
|
||||
expect(reduxHooks.useTrackCourseEvent).toHaveBeenCalledWith(
|
||||
track.course.courseOptionsDropdownClicked,
|
||||
cardId,
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('returned method', () => {
|
||||
it('calls trackCourseEvent iff true is passed', () => {
|
||||
out(false);
|
||||
expect(trackCourseEvent).not.toHaveBeenCalled();
|
||||
out(true);
|
||||
expect(trackCourseEvent).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Dropdown, Icon, IconButton } from '@edx/paragon';
|
||||
import { MoreVert } from '@edx/paragon/icons';
|
||||
|
||||
import track from 'tracking';
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import EmailSettingsModal from 'containers/EmailSettingsModal';
|
||||
import UnenrollConfirmModal from 'containers/UnenrollConfirmModal';
|
||||
import {
|
||||
@@ -21,16 +21,16 @@ import messages from './messages';
|
||||
export const CourseCardMenu = ({ cardId }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
const { courseName } = appHooks.useCardCourseData(cardId);
|
||||
const { isEnrolled, isEmailEnabled } = appHooks.useCardEnrollmentData(cardId);
|
||||
const { twitter, facebook } = appHooks.useCardSocialSettingsData(cardId);
|
||||
const { isMasquerading } = appHooks.useMasqueradeData();
|
||||
const handleTwitterShare = appHooks.useTrackCourseEvent(
|
||||
const { courseName } = reduxHooks.useCardCourseData(cardId);
|
||||
const { isEnrolled, isEmailEnabled } = reduxHooks.useCardEnrollmentData(cardId);
|
||||
const { twitter, facebook } = reduxHooks.useCardSocialSettingsData(cardId);
|
||||
const { isMasquerading } = reduxHooks.useMasqueradeData();
|
||||
const handleTwitterShare = reduxHooks.useTrackCourseEvent(
|
||||
track.socialShare,
|
||||
cardId,
|
||||
'twitter',
|
||||
);
|
||||
const handleFacebookShare = appHooks.useTrackCourseEvent(
|
||||
const handleFacebookShare = reduxHooks.useTrackCourseEvent(
|
||||
track.socialShare,
|
||||
cardId,
|
||||
'facebook',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import { useEmailSettings, useUnenrollData } from './hooks';
|
||||
import CourseCardMenu from '.';
|
||||
|
||||
@@ -8,8 +8,8 @@ jest.mock('react-share', () => ({
|
||||
FacebookShareButton: () => 'FacebookShareButton',
|
||||
TwitterShareButton: () => 'TwitterShareButton',
|
||||
}));
|
||||
jest.mock('data/redux', () => ({
|
||||
hooks: {
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useCardCourseData: jest.fn(),
|
||||
useCardEnrollmentData: jest.fn(),
|
||||
useCardSocialSettingsData: jest.fn(),
|
||||
@@ -56,10 +56,10 @@ describe('CourseCardMenu', () => {
|
||||
beforeEach(() => {
|
||||
useEmailSettings.mockReturnValue(defaultEmailSettingsModal);
|
||||
useUnenrollData.mockReturnValue(defaultUnenrollModal);
|
||||
appHooks.useCardSocialSettingsData.mockReturnValue(defaultSocialShare);
|
||||
appHooks.useCardCourseData.mockReturnValue({ courseName });
|
||||
appHooks.useCardEnrollmentData.mockReturnValue({ isEnrolled: true, isEmailEnabled: true });
|
||||
appHooks.useMasqueradeData.mockReturnValue({ isMasquerading: false });
|
||||
reduxHooks.useCardSocialSettingsData.mockReturnValue(defaultSocialShare);
|
||||
reduxHooks.useCardCourseData.mockReturnValue({ courseName });
|
||||
reduxHooks.useCardEnrollmentData.mockReturnValue({ isEnrolled: true, isEmailEnabled: true });
|
||||
reduxHooks.useMasqueradeData.mockReturnValue({ isMasquerading: false });
|
||||
});
|
||||
describe('enrolled, share enabled, email setting enable', () => {
|
||||
beforeEach(() => {
|
||||
@@ -91,12 +91,12 @@ describe('CourseCardMenu', () => {
|
||||
});
|
||||
describe('not enrolled, share disabled, email setting disabled', () => {
|
||||
beforeEach(() => {
|
||||
appHooks.useCardSocialSettingsData.mockReturnValueOnce({
|
||||
reduxHooks.useCardSocialSettingsData.mockReturnValueOnce({
|
||||
...defaultSocialShare,
|
||||
twitter: { ...defaultSocialShare.twitter, isEnabled: false },
|
||||
facebook: { ...defaultSocialShare.facebook, isEnabled: false },
|
||||
});
|
||||
appHooks.useCardEnrollmentData.mockReturnValueOnce({ isEnrolled: false, isEmailEnabled: false });
|
||||
reduxHooks.useCardEnrollmentData.mockReturnValueOnce({ isEnrolled: false, isEmailEnabled: false });
|
||||
wrapper = shallow(<CourseCardMenu {...props} />);
|
||||
});
|
||||
test('snapshot', () => {
|
||||
@@ -117,7 +117,7 @@ describe('CourseCardMenu', () => {
|
||||
});
|
||||
describe('masquerading', () => {
|
||||
beforeEach(() => {
|
||||
appHooks.useMasqueradeData.mockReturnValue({ isMasquerading: true });
|
||||
reduxHooks.useMasqueradeData.mockReturnValue({ isMasquerading: true });
|
||||
wrapper = shallow(<CourseCardMenu {...props} />);
|
||||
});
|
||||
test('snapshot', () => {
|
||||
|
||||
@@ -2,15 +2,15 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import track from 'tracking';
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
|
||||
const { courseTitleClicked } = track.course;
|
||||
|
||||
export const CourseCardTitle = ({ cardId }) => {
|
||||
const { courseName } = appHooks.useCardCourseData(cardId);
|
||||
const { isEntitlement, isFulfilled } = appHooks.useCardEntitlementData(cardId);
|
||||
const { homeUrl } = appHooks.useCardCourseRunData(cardId);
|
||||
const handleTitleClicked = appHooks.useTrackCourseEvent(courseTitleClicked, cardId, homeUrl);
|
||||
const { courseName } = reduxHooks.useCardCourseData(cardId);
|
||||
const { isEntitlement, isFulfilled } = reduxHooks.useCardEntitlementData(cardId);
|
||||
const { homeUrl } = reduxHooks.useCardCourseRunData(cardId);
|
||||
const handleTitleClicked = reduxHooks.useTrackCourseEvent(courseTitleClicked, cardId, homeUrl);
|
||||
return (
|
||||
<h3>
|
||||
<a
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { StrictDict } from 'utils';
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
|
||||
import messages from './messages';
|
||||
import * as module from './hooks';
|
||||
@@ -14,7 +14,7 @@ export const state = StrictDict({
|
||||
export const useRelatedProgramsBadgeData = ({ cardId }) => {
|
||||
const [isOpen, setIsOpen] = module.state.isOpen(false);
|
||||
const { formatMessage } = useIntl();
|
||||
const numPrograms = appHooks.useCardRelatedProgramsData(cardId).length;
|
||||
const numPrograms = reduxHooks.useCardRelatedProgramsData(cardId).length;
|
||||
let programsMessage = '';
|
||||
if (numPrograms) {
|
||||
programsMessage = formatMessage(
|
||||
@@ -27,8 +27,8 @@ export const useRelatedProgramsBadgeData = ({ cardId }) => {
|
||||
numPrograms,
|
||||
programsMessage,
|
||||
isOpen,
|
||||
openModal: React.useCallback(() => setIsOpen(true), [setIsOpen]),
|
||||
closeModal: React.useCallback(() => setIsOpen(false), [setIsOpen]),
|
||||
openModal: () => setIsOpen(true),
|
||||
closeModal: () => setIsOpen(false),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { MockUseState } from 'testUtils';
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
|
||||
import * as hooks from './hooks';
|
||||
import messages from './messages';
|
||||
|
||||
jest.mock('data/redux', () => ({
|
||||
hooks: {
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useCardRelatedProgramsData: jest.fn(),
|
||||
},
|
||||
}));
|
||||
@@ -30,7 +30,7 @@ describe('RelatedProgramsBadge hooks', () => {
|
||||
describe('useRelatedProgramsBadgeData', () => {
|
||||
beforeEach(() => {
|
||||
state.mock();
|
||||
appHooks.useCardRelatedProgramsData.mockReturnValueOnce({
|
||||
reduxHooks.useCardRelatedProgramsData.mockReturnValueOnce({
|
||||
length: numPrograms,
|
||||
});
|
||||
out = hooks.useRelatedProgramsBadgeData({ cardId });
|
||||
@@ -38,16 +38,12 @@ describe('RelatedProgramsBadge hooks', () => {
|
||||
afterEach(state.restore);
|
||||
|
||||
test('openModal sets isOpen to true as useCallback', () => {
|
||||
const { cb, prereqs } = out.openModal.useCallback;
|
||||
expect(prereqs).toEqual([state.setState.isOpen]);
|
||||
cb();
|
||||
out.openModal();
|
||||
expect(state.setState.isOpen).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
test('closeModal sets isOpen to false as useCallback', () => {
|
||||
const { cb, prereqs } = out.closeModal.useCallback;
|
||||
expect(prereqs).toEqual([state.setState.isOpen]);
|
||||
cb();
|
||||
out.closeModal();
|
||||
expect(state.setState.isOpen).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
@@ -59,12 +55,12 @@ describe('RelatedProgramsBadge hooks', () => {
|
||||
expect(out.numPrograms).toEqual(numPrograms);
|
||||
});
|
||||
test('returns empty programsMessage if no programs', () => {
|
||||
appHooks.useCardRelatedProgramsData.mockReturnValueOnce({ length: 0 });
|
||||
reduxHooks.useCardRelatedProgramsData.mockReturnValueOnce({ length: 0 });
|
||||
out = hooks.useRelatedProgramsBadgeData({ cardId });
|
||||
expect(out.programsMessage).toEqual('');
|
||||
});
|
||||
test('returns badgeLabelSingular programsMessage if 1 programs', () => {
|
||||
appHooks.useCardRelatedProgramsData.mockReturnValueOnce({ length: 1 });
|
||||
reduxHooks.useCardRelatedProgramsData.mockReturnValueOnce({ length: 1 });
|
||||
out = hooks.useRelatedProgramsBadgeData({ cardId });
|
||||
expect(out.programsMessage).toEqual(formatMessage(
|
||||
messages.badgeLabelSingular,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { useWindowSize, breakpoints } from '@edx/paragon';
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
|
||||
export const useIsCollapsed = () => {
|
||||
const { width } = useWindowSize();
|
||||
@@ -9,8 +9,8 @@ export const useIsCollapsed = () => {
|
||||
|
||||
export const useCardData = ({ cardId }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { title, bannerImgSrc } = appHooks.useCardCourseData(cardId);
|
||||
const { isEnrolled } = appHooks.useCardEnrollmentData(cardId);
|
||||
const { title, bannerImgSrc } = reduxHooks.useCardCourseData(cardId);
|
||||
const { isEnrolled } = reduxHooks.useCardEnrollmentData(cardId);
|
||||
|
||||
return {
|
||||
isEnrolled,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
|
||||
import * as hooks from './hooks';
|
||||
|
||||
jest.mock('data/redux', () => ({
|
||||
hooks: {
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useCardCourseData: jest.fn(),
|
||||
useCardEnrollmentData: jest.fn(),
|
||||
},
|
||||
@@ -26,11 +26,11 @@ describe('CourseCard hooks', () => {
|
||||
bannerImgSrc: 'my-banner-url',
|
||||
};
|
||||
const runHook = ({ course = {} }) => {
|
||||
appHooks.useCardCourseData.mockReturnValueOnce({
|
||||
reduxHooks.useCardCourseData.mockReturnValueOnce({
|
||||
...courseData,
|
||||
...course,
|
||||
});
|
||||
appHooks.useCardEnrollmentData.mockReturnValue({ isEnrolled: 'test-is-enrolled' });
|
||||
reduxHooks.useCardEnrollmentData.mockReturnValue({ isEnrolled: 'test-is-enrolled' });
|
||||
out = hooks.useCardData({ cardId });
|
||||
};
|
||||
beforeEach(() => {
|
||||
@@ -40,7 +40,7 @@ describe('CourseCard hooks', () => {
|
||||
expect(out.formatMessage).toEqual(formatMessage);
|
||||
});
|
||||
it('passes course title and banner URL form course data', () => {
|
||||
expect(appHooks.useCardCourseData).toHaveBeenCalledWith(cardId);
|
||||
expect(reduxHooks.useCardCourseData).toHaveBeenCalledWith(cardId);
|
||||
expect(out.title).toEqual(courseData.title);
|
||||
expect(out.bannerImgSrc).toEqual(courseData.bannerImgSrc);
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
} from '@edx/paragon';
|
||||
import { Close, Tune } from '@edx/paragon/icons';
|
||||
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
|
||||
import FilterForm from './components/FilterForm';
|
||||
import SortForm from './components/SortForm';
|
||||
@@ -30,7 +30,7 @@ export const CourseFilterControls = ({
|
||||
setFilters,
|
||||
}) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const hasCourses = appHooks.useHasCourses();
|
||||
const hasCourses = reduxHooks.useHasCourses();
|
||||
const {
|
||||
isOpen,
|
||||
open,
|
||||
|
||||
@@ -2,13 +2,13 @@ import { shallow } from 'enzyme';
|
||||
|
||||
import { breakpoints, useWindowSize } from '@edx/paragon';
|
||||
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
|
||||
import CourseFilterControls from './CourseFilterControls';
|
||||
import useCourseFilterControlsData from './hooks';
|
||||
|
||||
jest.mock('data/redux', () => ({
|
||||
hooks: { useHasCourses: jest.fn() },
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: { useHasCourses: jest.fn() },
|
||||
}));
|
||||
|
||||
jest.mock('./hooks', () => jest.fn().mockName('useCourseFilterControlsData'));
|
||||
@@ -16,7 +16,7 @@ jest.mock('./hooks', () => jest.fn().mockName('useCourseFilterControlsData'));
|
||||
jest.mock('./components/FilterForm', () => 'FilterForm');
|
||||
jest.mock('./components/SortForm', () => 'SortForm');
|
||||
|
||||
appHooks.useHasCourses.mockReturnValue(true);
|
||||
reduxHooks.useHasCourses.mockReturnValue(true);
|
||||
|
||||
describe('CourseFilterControls', () => {
|
||||
const props = {
|
||||
@@ -41,7 +41,7 @@ describe('CourseFilterControls', () => {
|
||||
|
||||
describe('no courses', () => {
|
||||
test('snapshot', () => {
|
||||
appHooks.useHasCourses.mockReturnValueOnce(false);
|
||||
reduxHooks.useHasCourses.mockReturnValueOnce(false);
|
||||
useWindowSize.mockReturnValueOnce({ width: breakpoints.small.minWidth });
|
||||
const wrapper = shallow(<CourseFilterControls {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
||||
@@ -4,15 +4,14 @@ import { Button, Image } from '@edx/paragon';
|
||||
import { Search } from '@edx/paragon/icons';
|
||||
|
||||
import emptyCourseSVG from 'assets/empty-course.svg';
|
||||
import { reduxHooks } from 'hooks';
|
||||
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import messages from './messages';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
export const NoCoursesView = () => {
|
||||
const { courseSearchUrl } = appHooks.usePlatformSettingsData();
|
||||
const { formatMessage } = useIntl();
|
||||
const { courseSearchUrl } = reduxHooks.usePlatformSettingsData();
|
||||
return (
|
||||
<div
|
||||
id="no-courses-content-view"
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import React from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { useCheckboxSetValues, useWindowSize, breakpoints } from '@edx/paragon';
|
||||
|
||||
import { StrictDict } from 'utils';
|
||||
import { actions, hooks as appHooks } from 'data/redux';
|
||||
import { ListPageSize, SortKeys } from 'data/constants/app';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import { StrictDict } from 'utils';
|
||||
|
||||
import * as module from './hooks';
|
||||
|
||||
@@ -19,18 +18,16 @@ export const state = StrictDict({
|
||||
});
|
||||
|
||||
export const useCourseListData = () => {
|
||||
const dispatch = useDispatch();
|
||||
const pageNumber = appHooks.usePageNumber();
|
||||
const [filters, setFilters] = useCheckboxSetValues([]);
|
||||
const [sortBy, setSortBy] = module.state.sortBy(SortKeys.enrolled);
|
||||
|
||||
const { numPages, visible } = appHooks.useCurrentCourseList({
|
||||
const pageNumber = reduxHooks.usePageNumber();
|
||||
const { numPages, visible } = reduxHooks.useCurrentCourseList({
|
||||
sortBy,
|
||||
filters,
|
||||
pageSize: ListPageSize,
|
||||
});
|
||||
const handleRemoveFilter = (filter) => () => setFilters.remove(filter);
|
||||
const setPageNumber = (value) => dispatch(actions.app.setPageNumber(value));
|
||||
const setPageNumber = reduxHooks.useSetPageNumber();
|
||||
|
||||
return {
|
||||
pageNumber,
|
||||
|
||||
@@ -1,27 +1,20 @@
|
||||
import { useDispatch } from 'react-redux';
|
||||
import * as paragon from '@edx/paragon';
|
||||
|
||||
import { MockUseState } from 'testUtils';
|
||||
import { actions, hooks as appHooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import { ListPageSize, SortKeys } from 'data/constants/app';
|
||||
import * as hooks from './hooks';
|
||||
|
||||
jest.mock('data/redux', () => ({
|
||||
actions: {
|
||||
app: {
|
||||
setPageNumber: (value) => ({ setPageNumber: value }),
|
||||
},
|
||||
},
|
||||
hooks: {
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useCurrentCourseList: jest.fn(),
|
||||
usePageNumber: jest.fn(() => 23),
|
||||
useSetPageNumber: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
const state = new MockUseState(hooks);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const testList = ['a', 'b'];
|
||||
const testListData = {
|
||||
numPages: 52,
|
||||
@@ -31,11 +24,13 @@ const testSortBy = 'fake sort option';
|
||||
const testFilters = ['some', 'fake', 'filters'];
|
||||
const testSetFilters = { add: jest.fn(), remove: jest.fn() };
|
||||
const testCheckboxSetValues = [testFilters, testSetFilters];
|
||||
const setPageNumber = jest.fn(val => ({ setPageNumber: val }));
|
||||
reduxHooks.useSetPageNumber.mockReturnValue(setPageNumber);
|
||||
|
||||
describe('CourseList hooks', () => {
|
||||
let out;
|
||||
|
||||
appHooks.useCurrentCourseList.mockReturnValue(testListData);
|
||||
reduxHooks.useCurrentCourseList.mockReturnValue(testListData);
|
||||
paragon.useCheckboxSetValues.mockImplementation(() => testCheckboxSetValues);
|
||||
|
||||
describe('state values', () => {
|
||||
@@ -55,7 +50,7 @@ describe('CourseList hooks', () => {
|
||||
state.expectInitializedWith(state.keys.sortBy, SortKeys.enrolled);
|
||||
});
|
||||
it('loads current course list with sortBy, filters, and page size', () => {
|
||||
expect(appHooks.useCurrentCourseList).toHaveBeenCalledWith({
|
||||
expect(reduxHooks.useCurrentCourseList).toHaveBeenCalledWith({
|
||||
sortBy: testSortBy,
|
||||
filters: testFilters,
|
||||
pageSize: ListPageSize,
|
||||
@@ -64,7 +59,7 @@ describe('CourseList hooks', () => {
|
||||
});
|
||||
describe('output', () => {
|
||||
test('pageNumber loads from usePageNumber hook', () => {
|
||||
expect(out.pageNumber).toEqual(appHooks.usePageNumber());
|
||||
expect(out.pageNumber).toEqual(reduxHooks.usePageNumber());
|
||||
});
|
||||
test('numPages and visible list load from useCurrentCourseList hook', () => {
|
||||
expect(out.numPages).toEqual(testListData.numPages);
|
||||
@@ -94,7 +89,7 @@ describe('CourseList hooks', () => {
|
||||
expect(testSetFilters.remove).toHaveBeenCalledWith(testFilters[0]);
|
||||
});
|
||||
test('setPageNumber dispatches setPageNumber action with passed value', () => {
|
||||
expect(out.setPageNumber(2)).toEqual(dispatch(actions.app.setPageNumber(2)));
|
||||
expect(out.setPageNumber(2)).toEqual(setPageNumber(2));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from 'react';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Pagination } from '@edx/paragon';
|
||||
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import {
|
||||
ActiveCourseFilters,
|
||||
CourseFilterControls,
|
||||
@@ -19,7 +19,7 @@ import './index.scss';
|
||||
|
||||
export const CourseList = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
const hasCourses = appHooks.useHasCourses();
|
||||
const hasCourses = reduxHooks.useHasCourses();
|
||||
const {
|
||||
filterOptions,
|
||||
setPageNumber,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import { useCourseListData, useIsCollapsed } from './hooks';
|
||||
import CourseList from '.';
|
||||
|
||||
jest.mock('data/redux', () => ({
|
||||
hooks: { useHasCourses: jest.fn() },
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: { useHasCourses: jest.fn() },
|
||||
}));
|
||||
|
||||
jest.mock('./hooks', () => ({
|
||||
@@ -19,7 +19,7 @@ jest.mock('containers/CourseFilterControls', () => ({
|
||||
CourseFilterControls: 'CourseFilterControls',
|
||||
}));
|
||||
|
||||
appHooks.useHasCourses.mockReturnValue(true);
|
||||
reduxHooks.useHasCourses.mockReturnValue(true);
|
||||
|
||||
describe('CourseList', () => {
|
||||
const defaultCourseListData = {
|
||||
@@ -40,7 +40,7 @@ describe('CourseList', () => {
|
||||
|
||||
describe('no courses', () => {
|
||||
test('snapshot', () => {
|
||||
appHooks.useHasCourses.mockReturnValue(true);
|
||||
reduxHooks.useHasCourses.mockReturnValue(true);
|
||||
const wrapper = createWrapper();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import React from 'react';
|
||||
import { useWindowSize, breakpoints } from '@edx/paragon';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { thunkActions } from 'data/redux';
|
||||
import { apiHooks } from 'hooks';
|
||||
|
||||
import appMessages from 'messages';
|
||||
|
||||
@@ -12,8 +11,8 @@ export const useIsDashboardCollapsed = () => {
|
||||
};
|
||||
|
||||
export const useInitializeDashboard = () => {
|
||||
const dispatch = useDispatch();
|
||||
React.useEffect(() => { dispatch(thunkActions.app.initialize()); }, [dispatch]);
|
||||
const initialize = apiHooks.useInitializeApp();
|
||||
React.useEffect(() => { initialize(); }, []); // eslint-disable-line
|
||||
};
|
||||
|
||||
export const useDashboardMessages = () => {
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import React from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { useWindowSize, breakpoints } from '@edx/paragon';
|
||||
|
||||
import { thunkActions } from 'data/redux';
|
||||
import { apiHooks } from 'hooks';
|
||||
|
||||
import appMessages from 'messages';
|
||||
import * as hooks from './hooks';
|
||||
@@ -14,14 +13,14 @@ jest.mock('@edx/paragon', () => ({
|
||||
breakpoints: {},
|
||||
}));
|
||||
|
||||
jest.mock('data/redux', () => ({
|
||||
thunkActions: {
|
||||
app: {
|
||||
initialize: jest.fn(() => 'thunkActions.app.initialize'),
|
||||
},
|
||||
jest.mock('hooks', () => ({
|
||||
apiHooks: {
|
||||
useInitializeApp: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
const initializeApp = jest.fn();
|
||||
apiHooks.useInitializeApp.mockReturnValue(initializeApp);
|
||||
describe('CourseCard hooks', () => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
@@ -42,13 +41,12 @@ describe('CourseCard hooks', () => {
|
||||
});
|
||||
describe('useInitializeDashboard', () => {
|
||||
it('dispatches initialize thunk action on component load', () => {
|
||||
const dispatch = useDispatch();
|
||||
hooks.useInitializeDashboard();
|
||||
const [cb, prereqs] = React.useEffect.mock.calls[0];
|
||||
expect(prereqs).toEqual([dispatch]);
|
||||
expect(dispatch).not.toHaveBeenCalled();
|
||||
expect(prereqs).toEqual([]);
|
||||
expect(initializeApp).not.toHaveBeenCalled();
|
||||
cb();
|
||||
expect(dispatch).toHaveBeenCalledWith(thunkActions.app.initialize());
|
||||
expect(initializeApp).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
describe('useDashboardMessages', () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import { RequestKeys } from 'data/constants/requests';
|
||||
import EnterpriseDashboardModal from 'containers/EnterpriseDashboardModal';
|
||||
import SelectSessionModal from 'containers/SelectSessionModal';
|
||||
@@ -17,10 +17,10 @@ import './index.scss';
|
||||
export const Dashboard = () => {
|
||||
hooks.useInitializeDashboard();
|
||||
const { pageTitle } = hooks.useDashboardMessages();
|
||||
const hasCourses = appHooks.useHasCourses();
|
||||
const hasAvailableDashboards = appHooks.useHasAvailableDashboards();
|
||||
const initIsPending = appHooks.useRequestIsPending(RequestKeys.initialize);
|
||||
const showSelectSessionModal = appHooks.useShowSelectSessionModal();
|
||||
const hasCourses = reduxHooks.useHasCourses();
|
||||
const hasAvailableDashboards = reduxHooks.useHasAvailableDashboards();
|
||||
const initIsPending = reduxHooks.useRequestIsPending(RequestKeys.initialize);
|
||||
const showSelectSessionModal = reduxHooks.useShowSelectSessionModal();
|
||||
return (
|
||||
<div id="dashboard-container" className="d-flex flex-column p-2 pt-0">
|
||||
<h1 className="sr-only">{pageTitle}</h1>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
|
||||
import EnterpriseDashboardModal from 'containers/EnterpriseDashboardModal';
|
||||
import SelectSessionModal from 'containers/SelectSessionModal';
|
||||
@@ -14,13 +14,8 @@ import LoadingView from './LoadingView';
|
||||
import hooks from './hooks';
|
||||
import Dashboard from '.';
|
||||
|
||||
jest.mock('data/redux', () => ({
|
||||
thunkActions: {
|
||||
app: {
|
||||
initialize: jest.fn(),
|
||||
},
|
||||
},
|
||||
hooks: {
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useHasCourses: jest.fn(),
|
||||
useHasAvailableDashboards: jest.fn(),
|
||||
useShowSelectSessionModal: jest.fn(),
|
||||
@@ -52,10 +47,10 @@ describe('Dashboard', () => {
|
||||
initIsPending,
|
||||
showSelectSessionModal,
|
||||
}) => {
|
||||
appHooks.useHasCourses.mockReturnValueOnce(hasCourses);
|
||||
appHooks.useHasAvailableDashboards.mockReturnValueOnce(hasAvailableDashboards);
|
||||
appHooks.useRequestIsPending.mockReturnValueOnce(initIsPending);
|
||||
appHooks.useShowSelectSessionModal.mockReturnValueOnce(showSelectSessionModal);
|
||||
reduxHooks.useHasCourses.mockReturnValueOnce(hasCourses);
|
||||
reduxHooks.useHasAvailableDashboards.mockReturnValueOnce(hasAvailableDashboards);
|
||||
reduxHooks.useRequestIsPending.mockReturnValueOnce(initIsPending);
|
||||
reduxHooks.useShowSelectSessionModal.mockReturnValueOnce(showSelectSessionModal);
|
||||
return shallow(<Dashboard />);
|
||||
};
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ exports[`EmailSettingsModal render snapshot: emails disabled, show: false 1`] =
|
||||
<ModalDialog
|
||||
hasCloseButton={false}
|
||||
isOpen={false}
|
||||
onClose={[MockFunction hooks.nullMethod]}
|
||||
onClose={[MockFunction utils.nullMethod]}
|
||||
title=""
|
||||
>
|
||||
<div
|
||||
@@ -48,7 +48,7 @@ exports[`EmailSettingsModal render snapshot: emails disabled, show: true 1`] = `
|
||||
<ModalDialog
|
||||
hasCloseButton={false}
|
||||
isOpen={true}
|
||||
onClose={[MockFunction hooks.nullMethod]}
|
||||
onClose={[MockFunction utils.nullMethod]}
|
||||
title=""
|
||||
>
|
||||
<div
|
||||
@@ -92,7 +92,7 @@ exports[`EmailSettingsModal render snapshot: emails enabled, show: true 1`] = `
|
||||
<ModalDialog
|
||||
hasCloseButton={false}
|
||||
isOpen={true}
|
||||
onClose={[MockFunction hooks.nullMethod]}
|
||||
onClose={[MockFunction utils.nullMethod]}
|
||||
title=""
|
||||
>
|
||||
<div
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import { StrictDict } from 'utils';
|
||||
import { hooks as appHooks, thunkActions } from 'data/redux';
|
||||
import { reduxHooks, apiHooks } from 'hooks';
|
||||
|
||||
import * as module from './hooks';
|
||||
|
||||
@@ -12,22 +12,15 @@ export const state = StrictDict({
|
||||
export const useEmailData = ({
|
||||
closeModal,
|
||||
cardId,
|
||||
dispatch,
|
||||
}) => {
|
||||
const { hasOptedOutOfEmail } = appHooks.useCardEnrollmentData(cardId);
|
||||
const { hasOptedOutOfEmail } = reduxHooks.useCardEnrollmentData(cardId);
|
||||
const [isOptedOut, setIsOptedOut] = module.state.toggle(hasOptedOutOfEmail);
|
||||
const onToggle = React.useCallback(
|
||||
() => setIsOptedOut(!isOptedOut),
|
||||
[setIsOptedOut, isOptedOut],
|
||||
);
|
||||
const save = React.useCallback(
|
||||
() => {
|
||||
// update email settings 2nd arg is true if opting in, false if opting out
|
||||
dispatch(thunkActions.app.updateEmailSettings(cardId, !isOptedOut));
|
||||
closeModal();
|
||||
},
|
||||
[cardId, closeModal, dispatch, isOptedOut],
|
||||
);
|
||||
const updateEmailSettings = apiHooks.useUpdateEmailSettings(cardId);
|
||||
const onToggle = () => setIsOptedOut(!isOptedOut);
|
||||
const save = () => {
|
||||
updateEmailSettings(!isOptedOut);
|
||||
closeModal();
|
||||
};
|
||||
|
||||
return {
|
||||
onToggle,
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
import { MockUseState } from 'testUtils';
|
||||
import { hooks as appHooks, thunkActions } from 'data/redux';
|
||||
import { reduxHooks, apiHooks } from 'hooks';
|
||||
|
||||
import * as hooks from './hooks';
|
||||
|
||||
jest.mock('data/redux', () => ({
|
||||
hooks: {
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useCardEnrollmentData: jest.fn(),
|
||||
},
|
||||
thunkActions: {
|
||||
app: {
|
||||
updateEmailSettings: jest.fn(),
|
||||
},
|
||||
apiHooks: {
|
||||
useUpdateEmailSettings: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
const cardId = 'my-test-course-number';
|
||||
const closeModal = jest.fn();
|
||||
const dispatch = jest.fn();
|
||||
const updateEmailSettings = jest.fn();
|
||||
apiHooks.useUpdateEmailSettings.mockReturnValue(updateEmailSettings);
|
||||
|
||||
const state = new MockUseState(hooks);
|
||||
|
||||
@@ -31,41 +30,40 @@ describe('EmailSettingsModal hooks', () => {
|
||||
describe('useEmailData', () => {
|
||||
beforeEach(() => {
|
||||
state.mock();
|
||||
appHooks.useCardEnrollmentData.mockReturnValueOnce({ hasOptedOutOfEmail: true });
|
||||
out = hooks.useEmailData({ closeModal, cardId, dispatch });
|
||||
reduxHooks.useCardEnrollmentData.mockReturnValueOnce({ hasOptedOutOfEmail: true });
|
||||
out = hooks.useEmailData({ closeModal, cardId });
|
||||
});
|
||||
afterEach(state.restore);
|
||||
|
||||
test('loads enrollment data based on course number', () => {
|
||||
expect(appHooks.useCardEnrollmentData).toHaveBeenCalledWith(cardId);
|
||||
it('loads enrollment data based on course number', () => {
|
||||
expect(reduxHooks.useCardEnrollmentData).toHaveBeenCalledWith(cardId);
|
||||
});
|
||||
|
||||
test('initializes toggle value to cardData.hasOptedOutOfEmail', () => {
|
||||
it('initializes toggle value to cardData.hasOptedOutOfEmail', () => {
|
||||
state.expectInitializedWith(state.keys.toggle, true);
|
||||
expect(out.isOptedOut).toEqual(true);
|
||||
|
||||
appHooks.useCardEnrollmentData.mockReturnValueOnce({ hasOptedOutOfEmail: false });
|
||||
reduxHooks.useCardEnrollmentData.mockReturnValueOnce({ hasOptedOutOfEmail: false });
|
||||
out = hooks.useEmailData({ closeModal, cardId });
|
||||
state.expectInitializedWith(state.keys.toggle, false);
|
||||
expect(out.isOptedOut).toEqual(false);
|
||||
});
|
||||
it('initializes email settings hok with cardId', () => {
|
||||
expect(apiHooks.useUpdateEmailSettings).toHaveBeenCalledWith(cardId);
|
||||
});
|
||||
describe('onToggle - returned callback', () => {
|
||||
it('is based on toggle state value', () => {
|
||||
expect(out.onToggle.useCallback.prereqs).toEqual([state.setState.toggle, out.isOptedOut]);
|
||||
});
|
||||
it('sets toggle state value to opposite current value', () => {
|
||||
out.onToggle.useCallback.cb();
|
||||
out.onToggle();
|
||||
expect(state.setState.toggle).toHaveBeenCalledWith(!out.isOptedOut);
|
||||
});
|
||||
});
|
||||
describe('save', () => {
|
||||
it('calls dispatch with thunkActions.app.updateEmailSettings', () => {
|
||||
out.save.useCallback.cb();
|
||||
expect(thunkActions.app.updateEmailSettings).toHaveBeenCalledWith(cardId, !out.isOptedOut);
|
||||
expect(dispatch).toHaveBeenCalledWith(thunkActions.app.updateEmailSettings(cardId, !out.isOptedOut));
|
||||
it('calls updateEmailSettings', () => {
|
||||
out.save();
|
||||
expect(updateEmailSettings).toHaveBeenCalledWith(!out.isOptedOut);
|
||||
});
|
||||
it('calls closeModal', () => {
|
||||
out.save.useCallback.cb();
|
||||
out.save();
|
||||
expect(closeModal).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
@@ -10,7 +9,7 @@ import {
|
||||
ModalDialog,
|
||||
} from '@edx/paragon';
|
||||
|
||||
import { nullMethod } from 'hooks';
|
||||
import { nullMethod } from 'utils';
|
||||
|
||||
import useEmailData from './hooks';
|
||||
import messages from './messages';
|
||||
@@ -20,12 +19,11 @@ export const EmailSettingsModal = ({
|
||||
show,
|
||||
cardId,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const {
|
||||
isOptedOut,
|
||||
onToggle,
|
||||
save,
|
||||
} = useEmailData({ dispatch, closeModal, cardId });
|
||||
} = useEmailData({ closeModal, cardId });
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import hooks from './hooks';
|
||||
@@ -22,8 +21,6 @@ const props = {
|
||||
cardId: 'test-course-number',
|
||||
};
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
describe('EmailSettingsModal', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
@@ -33,10 +30,9 @@ describe('EmailSettingsModal', () => {
|
||||
hooks.mockReturnValueOnce(hookProps);
|
||||
shallow(<EmailSettingsModal {...props} />);
|
||||
});
|
||||
it('calls hook w/ dispatch from redux hook, and closeModal, cardId from props', () => {
|
||||
it('calls hook w/ closeModal and cardId from props', () => {
|
||||
expect(hooks).toHaveBeenCalledWith({
|
||||
closeModal: props.closeModal,
|
||||
dispatch,
|
||||
cardId: props.cardId,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
|
||||
import { StrictDict } from 'utils';
|
||||
import track from 'tracking';
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
|
||||
import * as module from './hooks';
|
||||
|
||||
@@ -14,7 +14,7 @@ const { modalOpened, modalClosed, modalCTAClicked } = track.enterpriseDashboard;
|
||||
|
||||
export const useEnterpriseDashboardHook = () => {
|
||||
const [showModal, setShowModal] = module.state.showModal(true);
|
||||
const dashboard = appHooks.useEnterpriseDashboardData();
|
||||
const dashboard = reduxHooks.useEnterpriseDashboardData();
|
||||
|
||||
const trackOpened = modalOpened(dashboard.enterpriseUUID);
|
||||
const trackClose = modalClosed(dashboard.enterpriseUUID, 'Cancel button');
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { MockUseState } from 'testUtils';
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import track from 'tracking';
|
||||
|
||||
import * as hooks from './hooks';
|
||||
|
||||
jest.mock('data/redux', () => ({
|
||||
hooks: {
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useEnterpriseDashboardData: jest.fn(),
|
||||
},
|
||||
}));
|
||||
@@ -33,7 +33,7 @@ const state = new MockUseState(hooks);
|
||||
const enterpriseDashboardData = { label: 'edX, Inc.', url: '/edx-dashboard' };
|
||||
|
||||
describe('EnterpriseDashboard hooks', () => {
|
||||
appHooks.useEnterpriseDashboardData.mockReturnValue({ ...enterpriseDashboardData });
|
||||
reduxHooks.useEnterpriseDashboardData.mockReturnValue({ ...enterpriseDashboardData });
|
||||
|
||||
describe('state values', () => {
|
||||
state.testGetter(state.keys.showModal);
|
||||
|
||||
@@ -6,8 +6,8 @@ import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { AppContext } from '@edx/frontend-platform/react';
|
||||
import { AvatarButton, Dropdown } from '@edx/paragon';
|
||||
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import urls from 'data/services/lms/urls';
|
||||
import { reduxHooks } from 'hooks';
|
||||
|
||||
import { useIsCollapsed, findCoursesNavDropdownClicked } from './hooks';
|
||||
import messages from './messages';
|
||||
@@ -15,10 +15,10 @@ import messages from './messages';
|
||||
export const AuthenticatedUserDropdown = ({ username }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { authenticatedUser } = React.useContext(AppContext);
|
||||
const { profileImage } = authenticatedUser;
|
||||
const dashboard = appHooks.useEnterpriseDashboardData();
|
||||
const { courseSearchUrl } = appHooks.usePlatformSettingsData();
|
||||
const dashboard = reduxHooks.useEnterpriseDashboardData();
|
||||
const { courseSearchUrl } = reduxHooks.usePlatformSettingsData();
|
||||
const isCollapsed = useIsCollapsed();
|
||||
const { profileImage } = authenticatedUser;
|
||||
|
||||
return (
|
||||
<Dropdown variant={isCollapsed ? 'light' : 'dark'} className="user-dropdown ml-1">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import { AuthenticatedUserDropdown } from './AuthenticatedUserDropdown';
|
||||
import { useIsCollapsed } from './hooks';
|
||||
|
||||
@@ -11,8 +11,8 @@ jest.mock('@edx/frontend-platform/react', () => ({
|
||||
},
|
||||
},
|
||||
}));
|
||||
jest.mock('data/redux', () => ({
|
||||
hooks: {
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useEnterpriseDashboardData: jest.fn(),
|
||||
usePlatformSettingsData: jest.fn(() => ({
|
||||
courseSearchUrl: 'test-course-search-url',
|
||||
@@ -35,13 +35,13 @@ describe('AuthenticatedUserDropdown', () => {
|
||||
|
||||
describe('snapshots', () => {
|
||||
test('with enterprise dashboard', () => {
|
||||
appHooks.useEnterpriseDashboardData.mockReturnValueOnce(defaultDashboardData);
|
||||
reduxHooks.useEnterpriseDashboardData.mockReturnValueOnce(defaultDashboardData);
|
||||
useIsCollapsed.mockReturnValueOnce(true);
|
||||
const wrapper = shallow(<AuthenticatedUserDropdown {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
test('without enterprise dashboard and expanded', () => {
|
||||
appHooks.useEnterpriseDashboardData.mockReturnValueOnce(null);
|
||||
reduxHooks.useEnterpriseDashboardData.mockReturnValueOnce(null);
|
||||
useIsCollapsed.mockReturnValueOnce(false);
|
||||
const wrapper = shallow(<AuthenticatedUserDropdown {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import { StrictDict } from 'utils';
|
||||
import { hooks as appHooks, thunkActions } from 'data/redux';
|
||||
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { apiHooks, reduxHooks } from 'hooks';
|
||||
|
||||
import * as module from './hooks';
|
||||
|
||||
@@ -13,16 +11,16 @@ export const state = StrictDict({
|
||||
});
|
||||
|
||||
export const useConfirmEmailBannerData = () => {
|
||||
const dispatch = useDispatch();
|
||||
const { isNeeded } = appHooks.useEmailConfirmationData();
|
||||
const { isNeeded } = reduxHooks.useEmailConfirmationData();
|
||||
const [showPageBanner, setShowPageBanner] = module.state.showPageBanner(isNeeded);
|
||||
const [showConfirmModal, setShowConfirmModal] = module.state.showConfirmModal(false);
|
||||
const closePageBanner = () => setShowPageBanner(false);
|
||||
const closeConfirmModal = () => setShowConfirmModal(false);
|
||||
const openConfirmModal = () => setShowConfirmModal(true);
|
||||
const sendConfirmEmail = apiHooks.useSendConfirmEmail();
|
||||
|
||||
const openConfirmModalButtonClick = () => {
|
||||
dispatch(thunkActions.app.sendConfirmEmail());
|
||||
sendConfirmEmail();
|
||||
openConfirmModal();
|
||||
closePageBanner();
|
||||
};
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import { MockUseState } from 'testUtils';
|
||||
import { hooks as appHooks, thunkActions } from 'data/redux';
|
||||
import { reduxHooks, apiHooks } from 'hooks';
|
||||
|
||||
import * as hooks from './hooks';
|
||||
|
||||
jest.mock('data/redux', () => ({
|
||||
hooks: {
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useEmailConfirmationData: jest.fn(),
|
||||
},
|
||||
thunkActions: {
|
||||
app: {
|
||||
sendConfirmEmail: jest.fn(),
|
||||
},
|
||||
apiHooks: {
|
||||
useSendConfirmEmail: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
const sendConfirmEmail = jest.fn();
|
||||
apiHooks.useSendConfirmEmail.mockReturnValue(sendConfirmEmail);
|
||||
|
||||
const emailConfirmation = {
|
||||
isNeeded: true,
|
||||
};
|
||||
@@ -34,14 +35,14 @@ describe('ConfirmEmailBanner hooks', () => {
|
||||
afterEach(state.restore);
|
||||
|
||||
test('show page banner on unverified email', () => {
|
||||
appHooks.useEmailConfirmationData.mockReturnValueOnce({ ...emailConfirmation });
|
||||
reduxHooks.useEmailConfirmationData.mockReturnValueOnce({ ...emailConfirmation });
|
||||
out = hooks.useConfirmEmailBannerData();
|
||||
expect(out.isNeeded).toEqual(emailConfirmation.isNeeded);
|
||||
appHooks.useEmailConfirmationData.mockReturnValueOnce({ isNeeded: false });
|
||||
reduxHooks.useEmailConfirmationData.mockReturnValueOnce({ isNeeded: false });
|
||||
});
|
||||
|
||||
test('hide page banner on verified email', () => {
|
||||
appHooks.useEmailConfirmationData.mockReturnValueOnce({ isNeeded: false });
|
||||
reduxHooks.useEmailConfirmationData.mockReturnValueOnce({ isNeeded: false });
|
||||
out = hooks.useConfirmEmailBannerData();
|
||||
expect(out.isNeeded).toEqual(false);
|
||||
});
|
||||
@@ -50,7 +51,7 @@ describe('ConfirmEmailBanner hooks', () => {
|
||||
describe('behavior', () => {
|
||||
beforeEach(() => {
|
||||
state.mock();
|
||||
appHooks.useEmailConfirmationData.mockReturnValueOnce({ ...emailConfirmation });
|
||||
reduxHooks.useEmailConfirmationData.mockReturnValueOnce({ ...emailConfirmation });
|
||||
out = hooks.useConfirmEmailBannerData();
|
||||
});
|
||||
afterEach(state.restore);
|
||||
@@ -65,7 +66,7 @@ describe('ConfirmEmailBanner hooks', () => {
|
||||
test('openConfirmModalButtonClick', () => {
|
||||
out.openConfirmModalButtonClick();
|
||||
expect(state.values.showConfirmModal).toEqual(true);
|
||||
expect(thunkActions.app.sendConfirmEmail).toBeCalled();
|
||||
expect(sendConfirmEmail).toBeCalled();
|
||||
});
|
||||
test('userConfirmEmailButtonClick', () => {
|
||||
out.userConfirmEmailButtonClick();
|
||||
|
||||
@@ -31,6 +31,7 @@ exports[`LearnerDashboardHeader snapshots with collapsed 1`] = `
|
||||
className="my-auto ml-1 d-flex"
|
||||
>
|
||||
<IconButton
|
||||
alt="Course search"
|
||||
as="a"
|
||||
href="test-course-search-url"
|
||||
iconAs="Icon"
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
import topBanner from 'assets/top_stripe.svg';
|
||||
import MasqueradeBar from 'containers/MasqueradeBar';
|
||||
import urls from 'data/services/lms/urls';
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
|
||||
import AuthenticatedUserDropdown from './AuthenticatedUserDropdown';
|
||||
import GreetingBanner from './GreetingBanner';
|
||||
@@ -28,7 +28,7 @@ export const UserMenu = () => {
|
||||
export const LearnerDashboardHeader = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
const isCollapsed = useIsCollapsed();
|
||||
const { courseSearchUrl } = appHooks.usePlatformSettingsData();
|
||||
const { courseSearchUrl } = reduxHooks.usePlatformSettingsData();
|
||||
|
||||
const exploreCoursesClick = findCoursesNavClicked(courseSearchUrl);
|
||||
|
||||
@@ -52,6 +52,7 @@ export const LearnerDashboardHeader = () => {
|
||||
{isCollapsed ? (
|
||||
<div className="my-auto ml-1 d-flex">
|
||||
<IconButton
|
||||
alt={formatMessage(messages.courseSearchAlt)}
|
||||
as="a"
|
||||
href={courseSearchUrl}
|
||||
variant="primary"
|
||||
|
||||
@@ -16,8 +16,8 @@ jest.mock('./hooks', () => ({
|
||||
useIsCollapsed: jest.fn(),
|
||||
findCoursesNavClicked: (href) => jest.fn().mockName(`findCoursesNavClicked('${href}')`),
|
||||
}));
|
||||
jest.mock('data/redux', () => ({
|
||||
hooks: {
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
usePlatformSettingsData: jest.fn(() => ({
|
||||
courseSearchUrl: 'test-course-search-url',
|
||||
})),
|
||||
|
||||
@@ -62,6 +62,11 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Explore courses',
|
||||
description: 'Header link for switching to course page.',
|
||||
},
|
||||
courseSearchAlt: {
|
||||
id: 'leanerDashboard.courseSearchAlt',
|
||||
defaultMessage: 'Course search',
|
||||
description: 'Alt-text for course search icon button',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import React from 'react';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { thunkActions, hooks as appHooks } from 'data/redux';
|
||||
import { apiHooks, reduxHooks } from 'hooks';
|
||||
import { StrictDict } from 'utils';
|
||||
import * as module from './hooks';
|
||||
|
||||
@@ -35,28 +34,26 @@ export const getMasqueradeErrorMessage = (errorStatus) => {
|
||||
export const useMasqueradeBarData = ({
|
||||
authenticatedUser,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const { formatMessage } = useIntl();
|
||||
const canMasquerade = authenticatedUser?.administrator;
|
||||
|
||||
const handleMasqueradeSubmit = (user) => (e) => {
|
||||
dispatch(thunkActions.app.masqueradeAs(user));
|
||||
e.preventDefault();
|
||||
};
|
||||
const handleClearMasquerade = () => dispatch(thunkActions.app.clearMasquerade());
|
||||
const handleMasqueradeAs = apiHooks.useMasqueradeAs();
|
||||
const handleClearMasquerade = apiHooks.useClearMasquerade();
|
||||
|
||||
const {
|
||||
isMasquerading,
|
||||
isMasqueradingFailed,
|
||||
isMasqueradingPending,
|
||||
masqueradeErrorStatus,
|
||||
} = appHooks.useMasqueradeData();
|
||||
} = reduxHooks.useMasqueradeData();
|
||||
const { masqueradeInput, handleMasqueradeInputChange } = module.useMasqueradeInput();
|
||||
|
||||
const masqueradeErrorMessage = getMasqueradeErrorMessage(masqueradeErrorStatus);
|
||||
const handleMasqueradeSubmit = (user) => (e) => {
|
||||
handleMasqueradeAs(user);
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
return {
|
||||
canMasquerade,
|
||||
canMasquerade: authenticatedUser?.administrator,
|
||||
isMasquerading,
|
||||
isMasqueradingFailed,
|
||||
isMasqueradingPending,
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
import { MockUseState } from 'testUtils';
|
||||
import { thunkActions, hooks as appHooks } from 'data/redux';
|
||||
import { apiHooks, reduxHooks } from 'hooks';
|
||||
|
||||
import * as hooks from './hooks';
|
||||
import messages from './messages';
|
||||
|
||||
jest.mock('data/redux', () => ({
|
||||
thunkActions: {
|
||||
app: {
|
||||
masqueradeAs: jest.fn(),
|
||||
clearMasquerade: jest.fn(),
|
||||
},
|
||||
jest.mock('hooks', () => ({
|
||||
apiHooks: {
|
||||
useMasqueradeAs: jest.fn(),
|
||||
useClearMasquerade: jest.fn(),
|
||||
},
|
||||
hooks: {
|
||||
reduxHooks: {
|
||||
useMasqueradeData: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
const masqueradeAs = jest.fn();
|
||||
const clearMasquerade = jest.fn();
|
||||
apiHooks.useMasqueradeAs.mockReturnValue(masqueradeAs);
|
||||
apiHooks.useClearMasquerade.mockReturnValue(clearMasquerade);
|
||||
const state = new MockUseState(hooks);
|
||||
const testValue = 'test-value';
|
||||
|
||||
describe('MasqueradeBar hooks', () => {
|
||||
const authenticatedUser = {
|
||||
@@ -29,7 +32,7 @@ describe('MasqueradeBar hooks', () => {
|
||||
masqueradeErrorStatus: null,
|
||||
};
|
||||
const createHook = (masqueradeData = {}, user) => {
|
||||
appHooks.useMasqueradeData.mockReturnValueOnce({
|
||||
reduxHooks.useMasqueradeData.mockReturnValueOnce({
|
||||
...defaultMasqueradeData,
|
||||
...masqueradeData,
|
||||
});
|
||||
@@ -65,23 +68,23 @@ describe('MasqueradeBar hooks', () => {
|
||||
test('handleMasqueradeInputChange', () => {
|
||||
const out = createHook();
|
||||
expect(state.stateVals.masqueradeInput).toEqual('');
|
||||
out.handleMasqueradeInputChange({ target: { value: 'test' } });
|
||||
expect(state.setState.masqueradeInput).toHaveBeenCalledWith('test');
|
||||
out.handleMasqueradeInputChange({ target: { value: testValue } });
|
||||
expect(state.setState.masqueradeInput).toHaveBeenCalledWith(testValue);
|
||||
});
|
||||
test('handleMasqueradeSubmit', () => {
|
||||
const out = createHook();
|
||||
const preventDefault = jest.fn();
|
||||
// make sure submit doesn't refresh the page
|
||||
out.handleMasqueradeSubmit('test')({
|
||||
out.handleMasqueradeSubmit(testValue)({
|
||||
preventDefault,
|
||||
});
|
||||
expect(thunkActions.app.masqueradeAs).toHaveBeenCalledWith('test');
|
||||
expect(masqueradeAs).toHaveBeenCalledWith(testValue);
|
||||
expect(preventDefault).toHaveBeenCalled();
|
||||
});
|
||||
test('handleClearMasquerade', () => {
|
||||
const out = createHook();
|
||||
out.handleClearMasquerade();
|
||||
expect(thunkActions.app.clearMasquerade).toHaveBeenCalled();
|
||||
expect(clearMasquerade).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
|
||||
export const useProgramData = ({
|
||||
cardId,
|
||||
}) => ({
|
||||
courseTitle: appHooks.useCardCourseData(cardId).title,
|
||||
relatedPrograms: appHooks.useCardRelatedProgramsData(cardId).list,
|
||||
courseTitle: reduxHooks.useCardCourseData(cardId).title,
|
||||
relatedPrograms: reduxHooks.useCardRelatedProgramsData(cardId).list,
|
||||
});
|
||||
|
||||
export default useProgramData;
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
Container, Row, Col, ModalDialog,
|
||||
} from '@edx/paragon';
|
||||
|
||||
import { hooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import ProgramCard from './components/ProgramCard';
|
||||
import messages from './messages';
|
||||
import './index.scss';
|
||||
@@ -18,8 +18,8 @@ export const RelatedProgramsModal = ({
|
||||
cardId,
|
||||
}) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { courseName } = hooks.useCardCourseData(cardId);
|
||||
const relatedPrograms = hooks.useCardRelatedProgramsData(cardId).list;
|
||||
const { courseName } = reduxHooks.useCardCourseData(cardId);
|
||||
const relatedPrograms = reduxHooks.useCardRelatedProgramsData(cardId).list;
|
||||
return (
|
||||
<ModalDialog
|
||||
title={formatMessage(messages.header)}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { hooks } from 'data/redux';
|
||||
import { reduxHooks } from 'hooks';
|
||||
import RelatedProgramsModal from '.';
|
||||
|
||||
jest.mock('./components/ProgramCard', () => 'ProgramCard');
|
||||
jest.mock('data/redux', () => ({
|
||||
hooks: {
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useCardCourseData: jest.fn(),
|
||||
useCardRelatedProgramsData: jest.fn(),
|
||||
},
|
||||
@@ -41,13 +41,13 @@ const props = {
|
||||
|
||||
describe('RelatedProgramsModal', () => {
|
||||
beforeEach(() => {
|
||||
hooks.useCardCourseData.mockReturnValueOnce(courseData);
|
||||
hooks.useCardRelatedProgramsData.mockReturnValueOnce(programData);
|
||||
reduxHooks.useCardCourseData.mockReturnValueOnce(courseData);
|
||||
reduxHooks.useCardRelatedProgramsData.mockReturnValueOnce(programData);
|
||||
});
|
||||
it('initializes hooks with cardId', () => {
|
||||
shallow(<RelatedProgramsModal {...props} />);
|
||||
expect(hooks.useCardCourseData).toHaveBeenCalledWith(cardId);
|
||||
expect(hooks.useCardRelatedProgramsData).toHaveBeenCalledWith(cardId);
|
||||
expect(reduxHooks.useCardCourseData).toHaveBeenCalledWith(cardId);
|
||||
expect(reduxHooks.useCardRelatedProgramsData).toHaveBeenCalledWith(cardId);
|
||||
});
|
||||
test('snapshot: open', () => {
|
||||
expect(shallow(<RelatedProgramsModal {...props} />)).toMatchSnapshot();
|
||||
|
||||
@@ -6,7 +6,7 @@ exports[`SelectSessionModal snapshot empty modal with leave option 1`] = `
|
||||
hasCloseButton={false}
|
||||
isFullscreenOnMobile={true}
|
||||
isOpen={true}
|
||||
onClose={[MockFunction hooks.nullMethod]}
|
||||
onClose={[MockFunction utils.nullMethod]}
|
||||
size="md"
|
||||
title="test-header"
|
||||
>
|
||||
@@ -50,7 +50,7 @@ exports[`SelectSessionModal snapshot modal with leave option 1`] = `
|
||||
hasCloseButton={false}
|
||||
isFullscreenOnMobile={true}
|
||||
isOpen={true}
|
||||
onClose={[MockFunction hooks.nullMethod]}
|
||||
onClose={[MockFunction utils.nullMethod]}
|
||||
size="md"
|
||||
title="test-header"
|
||||
>
|
||||
@@ -118,7 +118,7 @@ exports[`SelectSessionModal snapshot modal without leave option 1`] = `
|
||||
hasCloseButton={false}
|
||||
isFullscreenOnMobile={true}
|
||||
isOpen={true}
|
||||
onClose={[MockFunction hooks.nullMethod]}
|
||||
onClose={[MockFunction utils.nullMethod]}
|
||||
size="md"
|
||||
title="test-header"
|
||||
>
|
||||
|
||||
@@ -1,32 +1,35 @@
|
||||
import React from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { StrictDict } from 'utils';
|
||||
|
||||
import track from 'tracking';
|
||||
import { hooks as appHooks, thunkActions } from 'data/redux';
|
||||
import * as module from './hooks';
|
||||
|
||||
import { reduxHooks, apiHooks } from 'hooks';
|
||||
|
||||
import { LEAVE_OPTION } from './constants';
|
||||
import messages from './messages';
|
||||
import * as module from './hooks';
|
||||
|
||||
export const state = StrictDict({
|
||||
selectedSession: (val) => React.useState(val), // eslint-disable-line
|
||||
});
|
||||
|
||||
export const useSelectSessionModalData = () => {
|
||||
const dispatch = useDispatch();
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
const selectedCardId = appHooks.useSelectSessionModalData().cardId;
|
||||
const selectedCardId = reduxHooks.useSelectSessionModalData().cardId;
|
||||
const {
|
||||
availableSessions,
|
||||
isFulfilled,
|
||||
} = appHooks.useCardEntitlementData(selectedCardId);
|
||||
const { title: courseTitle } = appHooks.useCardCourseData(selectedCardId);
|
||||
const { courseId } = appHooks.useCardCourseRunData(selectedCardId) || {};
|
||||
const { isEnrolled } = appHooks.useCardEnrollmentData(selectedCardId);
|
||||
} = reduxHooks.useCardEntitlementData(selectedCardId);
|
||||
const { title: courseTitle } = reduxHooks.useCardCourseData(selectedCardId);
|
||||
const { courseId } = reduxHooks.useCardCourseRunData(selectedCardId) || {};
|
||||
const { isEnrolled } = reduxHooks.useCardEnrollmentData(selectedCardId);
|
||||
const leaveEntitlementSession = apiHooks.useLeaveEntitlementSession(selectedCardId);
|
||||
const switchEntitlementEnrollment = apiHooks.useSwitchEntitlementEnrollment(selectedCardId);
|
||||
const newEntitlementEnrollment = apiHooks.useNewEntitlementEnrollment(selectedCardId);
|
||||
|
||||
const [selectedSession, setSelectedSession] = module.state.selectedSession(courseId || null);
|
||||
|
||||
@@ -39,8 +42,7 @@ export const useSelectSessionModalData = () => {
|
||||
header = formatMessage(messages.selectSessionHeader, { courseTitle });
|
||||
hint = formatMessage(messages.selectSessionHint);
|
||||
}
|
||||
const updateCardIdCallback = appHooks.useUpdateSelectSessionModalCallback;
|
||||
const closeSessionModal = updateCardIdCallback(null);
|
||||
const closeSessionModal = reduxHooks.useUpdateSelectSessionModalCallback(null);
|
||||
|
||||
const trackNewSession = track.entitlements.newSession(selectedSession);
|
||||
const trackSwitchSession = track.entitlements.switchSession(selectedCardId, selectedSession);
|
||||
@@ -50,13 +52,13 @@ export const useSelectSessionModalData = () => {
|
||||
const handleSubmit = () => {
|
||||
if (selectedSession === LEAVE_OPTION) {
|
||||
trackLeaveSession();
|
||||
dispatch(thunkActions.app.leaveEntitlementSession(selectedCardId));
|
||||
leaveEntitlementSession();
|
||||
} else if (isEnrolled) {
|
||||
trackSwitchSession();
|
||||
dispatch(thunkActions.app.switchEntitlementEnrollment(selectedCardId, selectedSession));
|
||||
switchEntitlementEnrollment(selectedSession);
|
||||
} else {
|
||||
trackNewSession();
|
||||
dispatch(thunkActions.app.newEntitlementEnrollment(selectedCardId, selectedSession));
|
||||
newEntitlementEnrollment(selectedSession);
|
||||
}
|
||||
closeSessionModal();
|
||||
};
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import track from 'tracking';
|
||||
|
||||
import { MockUseState } from 'testUtils';
|
||||
import { hooks as appHooks, thunkActions } from 'data/redux';
|
||||
import track from 'tracking';
|
||||
import { reduxHooks, apiHooks } from 'hooks';
|
||||
|
||||
import { LEAVE_OPTION } from './constants';
|
||||
import messages from './messages';
|
||||
@@ -17,31 +15,37 @@ jest.mock('tracking', () => ({
|
||||
leaveSession: jest.fn(),
|
||||
},
|
||||
}));
|
||||
jest.mock('data/redux', () => ({
|
||||
hooks: {
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
useCardCourseData: jest.fn(),
|
||||
useCardCourseRunData: jest.fn(),
|
||||
useCardEnrollmentData: jest.fn(),
|
||||
useCardEntitlementData: jest.fn(),
|
||||
useSelectSessionModalData: jest.fn(),
|
||||
useUpdateSelectSessionModalCallback: jest.fn((...args) => () => ({
|
||||
updateSelectSession: args,
|
||||
})),
|
||||
useUpdateSelectSessionModalCallback: jest.fn(),
|
||||
},
|
||||
actions: {
|
||||
app: {
|
||||
updateSelectSessionModal: jest.fn(),
|
||||
},
|
||||
},
|
||||
thunkActions: {
|
||||
app: {
|
||||
switchEntitlementEnrollment: jest.fn((...args) => ({ switchEntitlementEnrollment: args })),
|
||||
leaveEntitlementSession: jest.fn((...args) => ({ leaveEntitlementSession: args })),
|
||||
newEntitlementEnrollment: jest.fn((...args) => ({ newEntitlementEnrollment: args })),
|
||||
},
|
||||
apiHooks: {
|
||||
useSwitchEntitlementEnrollment: jest.fn((...args) => ({ switchEntitlementEnrollment: args })),
|
||||
useLeaveEntitlementSession: jest.fn((...args) => ({ leaveEntitlementSession: args })),
|
||||
useNewEntitlementEnrollment: jest.fn((...args) => ({ newEntitlementEnrollment: args })),
|
||||
},
|
||||
}));
|
||||
|
||||
const updateSelectSessionModalCallback = jest.fn();
|
||||
reduxHooks.useUpdateSelectSessionModalCallback.mockReturnValue(updateSelectSessionModalCallback);
|
||||
const newEntitlementEnrollment = jest.fn();
|
||||
apiHooks.useNewEntitlementEnrollment.mockReturnValue(newEntitlementEnrollment);
|
||||
const switchEntitlementEnrollment = jest.fn();
|
||||
apiHooks.useSwitchEntitlementEnrollment.mockReturnValue(switchEntitlementEnrollment);
|
||||
const leaveEntitlementSession = jest.fn();
|
||||
apiHooks.useLeaveEntitlementSession.mockReturnValue(leaveEntitlementSession);
|
||||
const trackNewSession = jest.fn();
|
||||
track.entitlements.newSession.mockReturnValue(trackNewSession);
|
||||
const trackLeaveSession = jest.fn();
|
||||
track.entitlements.leaveSession.mockReturnValue(trackLeaveSession);
|
||||
const trackSwitchSession = jest.fn();
|
||||
track.entitlements.switchSession.mockReturnValue(trackSwitchSession);
|
||||
|
||||
const state = new MockUseState(hooks);
|
||||
const selectedCardId = 'test-selected-card-id';
|
||||
const courseTitle = 'course-title: brown fox';
|
||||
@@ -58,19 +62,11 @@ const entitlementData = {
|
||||
};
|
||||
|
||||
const { formatMessage } = useIntl();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const testValue = 'test-value';
|
||||
|
||||
const courseId = 'test-course-id';
|
||||
appHooks.useCardCourseRunData.mockReturnValue({ courseId });
|
||||
|
||||
const newSession = jest.fn();
|
||||
const switchSession = jest.fn();
|
||||
const leaveSession = jest.fn();
|
||||
track.entitlements.newSession.mockReturnValue(newSession);
|
||||
track.entitlements.switchSession.mockReturnValue(switchSession);
|
||||
track.entitlements.leaveSession.mockReturnValue(leaveSession);
|
||||
reduxHooks.useCardCourseRunData.mockReturnValue({ courseId });
|
||||
|
||||
describe('SelectSessionModal hooks', () => {
|
||||
let out;
|
||||
@@ -89,11 +85,11 @@ describe('SelectSessionModal hooks', () => {
|
||||
entitlement = {},
|
||||
selectSession = {},
|
||||
}) => {
|
||||
appHooks.useCardCourseData.mockReturnValueOnce({ title: courseTitle, ...course });
|
||||
appHooks.useCardCourseRunData.mockReturnValueOnce({ courseId, ...courseRun });
|
||||
appHooks.useCardEnrollmentData.mockReturnValueOnce({ isEnrolled: false, ...enrollment });
|
||||
appHooks.useCardEntitlementData.mockReturnValueOnce({ ...entitlementData, ...entitlement });
|
||||
appHooks.useSelectSessionModalData.mockReturnValueOnce({ cardId: selectedCardId, ...selectSession });
|
||||
reduxHooks.useCardCourseData.mockReturnValueOnce({ title: courseTitle, ...course });
|
||||
reduxHooks.useCardCourseRunData.mockReturnValueOnce({ courseId, ...courseRun });
|
||||
reduxHooks.useCardEnrollmentData.mockReturnValueOnce({ isEnrolled: false, ...enrollment });
|
||||
reduxHooks.useCardEntitlementData.mockReturnValueOnce({ ...entitlementData, ...entitlement });
|
||||
reduxHooks.useSelectSessionModalData.mockReturnValueOnce({ cardId: selectedCardId, ...selectSession });
|
||||
out = hooks.useSelectSessionModalData();
|
||||
};
|
||||
beforeEach(() => {
|
||||
@@ -101,11 +97,30 @@ describe('SelectSessionModal hooks', () => {
|
||||
runHook({});
|
||||
});
|
||||
describe('initialization', () => {
|
||||
test('loads entitlement data based on course number', () => {
|
||||
expect(appHooks.useCardEntitlementData).toHaveBeenCalledWith(selectedCardId);
|
||||
it('loads redux data based on selected card id', () => {
|
||||
expect(reduxHooks.useCardEntitlementData).toHaveBeenCalledWith(selectedCardId);
|
||||
expect(reduxHooks.useCardCourseData).toHaveBeenCalledWith(selectedCardId);
|
||||
expect(reduxHooks.useCardEnrollmentData).toHaveBeenCalledWith(selectedCardId);
|
||||
});
|
||||
test('get course title based on course number', () => {
|
||||
expect(appHooks.useCardCourseData).toHaveBeenCalledWith(selectedCardId);
|
||||
it('initializes enrollment hooks with selected card id', () => {
|
||||
expect(apiHooks.useLeaveEntitlementSession).toHaveBeenCalledWith(selectedCardId);
|
||||
expect(apiHooks.useNewEntitlementEnrollment).toHaveBeenCalledWith(selectedCardId);
|
||||
expect(apiHooks.useSwitchEntitlementEnrollment).toHaveBeenCalledWith(selectedCardId);
|
||||
});
|
||||
it('initializes selected session with courseId if available', () => {
|
||||
state.expectInitializedWith(state.keys.selectedSession, courseId);
|
||||
});
|
||||
it('initializes selected session with null if courseId not available', () => {
|
||||
runHook({ courseRun: { courseId: undefined } });
|
||||
state.expectInitializedWith(state.keys.selectedSession, null);
|
||||
});
|
||||
it('initializes update callback with null', () => {
|
||||
expect(reduxHooks.useUpdateSelectSessionModalCallback).toHaveBeenCalledWith(null);
|
||||
});
|
||||
it('initializes tracking methods', () => {
|
||||
expect(track.entitlements.newSession).toHaveBeenCalledWith(courseId);
|
||||
expect(track.entitlements.leaveSession).toHaveBeenCalledWith(selectedCardId);
|
||||
expect(track.entitlements.switchSession).toHaveBeenCalledWith(selectedCardId, courseId);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -127,36 +142,30 @@ describe('SelectSessionModal hooks', () => {
|
||||
});
|
||||
describe('handleSubmit', () => {
|
||||
describe('if LEAVE_OPTION is selected', () => {
|
||||
it('dispatches leaveEntitlementSession', () => {
|
||||
it('calls and tracks leaveEntitlementSession', () => {
|
||||
state.mockVal(state.keys.selectedSession, LEAVE_OPTION);
|
||||
runHook({});
|
||||
out.handleSubmit();
|
||||
expect(leaveSession).toHaveBeenCalledWith();
|
||||
expect(dispatch).toHaveBeenCalledWith(
|
||||
thunkActions.app.leaveEntitlementSession(selectedCardId),
|
||||
);
|
||||
expect(leaveEntitlementSession).toHaveBeenCalledWith();
|
||||
expect(trackLeaveSession).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
describe('if not enrolled in a session yet', () => {
|
||||
it('dispatches newEntitlementEnrollment with selected card ID and session', () => {
|
||||
it('calls and tracks newEntitlementEnrollment with selected card ID and session', () => {
|
||||
state.mockVal(state.keys.selectedSession, testValue);
|
||||
runHook({});
|
||||
out.handleSubmit();
|
||||
expect(newSession).toHaveBeenCalledWith();
|
||||
expect(dispatch).toHaveBeenCalledWith(
|
||||
thunkActions.app.newEntitlementEnrollment(selectedCardId, testValue),
|
||||
);
|
||||
expect(newEntitlementEnrollment).toHaveBeenCalledWith(testValue);
|
||||
expect(trackNewSession).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
describe('if enrolled in a session already, selecting a new session', () => {
|
||||
it('dispatches swtichEntitlementEnrollment with selected card ID and session', () => {
|
||||
it('calls and tracks swtichEntitlementEnrollment w/ selected card ID and session', () => {
|
||||
state.mockVal(state.keys.selectedSession, testValue);
|
||||
runHook({ enrollment: { isEnrolled: true } });
|
||||
out.handleSubmit();
|
||||
expect(switchSession).toHaveBeenCalledWith();
|
||||
expect(dispatch).toHaveBeenCalledWith(
|
||||
thunkActions.app.switchEntitlementEnrollment(selectedCardId, testValue),
|
||||
);
|
||||
expect(switchEntitlementEnrollment).toHaveBeenCalledWith(testValue);
|
||||
expect(trackSwitchSession).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -178,7 +187,7 @@ describe('SelectSessionModal hooks', () => {
|
||||
});
|
||||
test('closeSessionModal returns update callback wth dispatch and null card id', () => {
|
||||
expect(out.closeSessionModal()).toEqual(
|
||||
appHooks.useUpdateSelectSessionModalCallback(null)(),
|
||||
reduxHooks.useUpdateSelectSessionModalCallback(null)(),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,8 +8,8 @@ import {
|
||||
ModalDialog,
|
||||
} from '@edx/paragon';
|
||||
|
||||
import { nullMethod } from 'hooks';
|
||||
import { dateFormatter } from 'utils';
|
||||
import { utilHooks } from 'hooks';
|
||||
import { nullMethod, dateFormatter } from 'utils';
|
||||
|
||||
import useSelectSessionModalData from './hooks';
|
||||
import { LEAVE_OPTION } from './constants';
|
||||
@@ -28,7 +28,8 @@ export const SelectSessionModal = () => {
|
||||
selectedSession,
|
||||
} = useSelectSessionModalData();
|
||||
|
||||
const { formatMessage, formatDate } = useIntl();
|
||||
const formatDate = utilHooks.useFormatDate();
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
<ModalDialog
|
||||
|
||||
@@ -5,7 +5,7 @@ exports[`UnenrollConfirmModal component snapshot: modalStates.confirm 1`] = `
|
||||
hasCloseButton={false}
|
||||
isFullscreenOnMobile={false}
|
||||
isOpen={true}
|
||||
onClose={[MockFunction hooks.nullMethod]}
|
||||
onClose={[MockFunction utils.nullMethod]}
|
||||
title=""
|
||||
>
|
||||
<div
|
||||
@@ -29,7 +29,7 @@ exports[`UnenrollConfirmModal component snapshot: modalStates.finished, reason g
|
||||
hasCloseButton={false}
|
||||
isFullscreenOnMobile={false}
|
||||
isOpen={true}
|
||||
onClose={[MockFunction hooks.nullMethod]}
|
||||
onClose={[MockFunction utils.nullMethod]}
|
||||
title=""
|
||||
>
|
||||
<div
|
||||
@@ -53,7 +53,7 @@ exports[`UnenrollConfirmModal component snapshot: modalStates.finished, reason s
|
||||
hasCloseButton={false}
|
||||
isFullscreenOnMobile={false}
|
||||
isOpen={true}
|
||||
onClose={[MockFunction hooks.nullMethod]}
|
||||
onClose={[MockFunction utils.nullMethod]}
|
||||
title=""
|
||||
>
|
||||
<div
|
||||
@@ -77,7 +77,7 @@ exports[`UnenrollConfirmModal component snapshot: modalStates.reason, should be
|
||||
hasCloseButton={false}
|
||||
isFullscreenOnMobile={true}
|
||||
isOpen={true}
|
||||
onClose={[MockFunction hooks.nullMethod]}
|
||||
onClose={[MockFunction utils.nullMethod]}
|
||||
title=""
|
||||
>
|
||||
<div
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import { StrictDict } from 'utils';
|
||||
import { thunkActions } from 'data/redux';
|
||||
import { apiHooks } from 'hooks';
|
||||
|
||||
import { useUnenrollReasons } from './reasons';
|
||||
import * as module from '.';
|
||||
@@ -16,10 +16,11 @@ export const modalStates = StrictDict({
|
||||
finished: 'finished',
|
||||
});
|
||||
|
||||
export const useUnenrollData = ({ closeModal, dispatch, cardId }) => {
|
||||
export const useUnenrollData = ({ closeModal, cardId }) => {
|
||||
const [isConfirmed, setIsConfirmed] = module.state.confirmed(false);
|
||||
const confirm = () => setIsConfirmed(true);
|
||||
const reason = useUnenrollReasons({ dispatch, cardId });
|
||||
const reason = useUnenrollReasons({ cardId });
|
||||
const refreshList = apiHooks.useInitializeApp();
|
||||
|
||||
let modalState;
|
||||
if (isConfirmed) {
|
||||
@@ -35,7 +36,7 @@ export const useUnenrollData = ({ closeModal, dispatch, cardId }) => {
|
||||
reason.handleClear();
|
||||
};
|
||||
const closeAndRefresh = () => {
|
||||
dispatch(thunkActions.app.refreshList());
|
||||
refreshList();
|
||||
close();
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { thunkActions } from 'data/redux';
|
||||
import { apiHooks } from 'hooks';
|
||||
import { MockUseState } from 'testUtils';
|
||||
|
||||
import * as reasons from './reasons';
|
||||
@@ -8,13 +8,16 @@ jest.mock('./reasons', () => ({
|
||||
useUnenrollReasons: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('data/redux/thunkActions/app', () => ({
|
||||
refreshList: jest.fn((args) => ({ refreshList: args })),
|
||||
unenrollFromCourse: jest.fn((...args) => ({ unenrollFromCourse: args })),
|
||||
jest.mock('hooks', () => ({
|
||||
apiHooks: {
|
||||
useInitializeApp: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
const state = new MockUseState(hooks);
|
||||
const testValue = 'test-value';
|
||||
const initializeApp = jest.fn();
|
||||
apiHooks.useInitializeApp.mockReturnValue(initializeApp);
|
||||
let out;
|
||||
|
||||
const mockReason = {
|
||||
@@ -29,11 +32,10 @@ describe('UnenrollConfirmModal hooks', () => {
|
||||
beforeEach(() => {
|
||||
reasons.useUnenrollReasons.mockImplementation(useUnenrollReasons);
|
||||
});
|
||||
const dispatch = jest.fn();
|
||||
const closeModal = jest.fn();
|
||||
const cardId = 'test-card-id';
|
||||
|
||||
const createUseUnenrollData = () => hooks.useUnenrollData({ closeModal, dispatch, cardId });
|
||||
const createUseUnenrollData = () => hooks.useUnenrollData({ closeModal, cardId });
|
||||
|
||||
describe('state fields', () => {
|
||||
state.testGetter(state.keys.confirmed);
|
||||
@@ -72,9 +74,9 @@ describe('UnenrollConfirmModal hooks', () => {
|
||||
expect(state.setState.confirmed).toHaveBeenCalledWith(false);
|
||||
expect(mockReason.handleClear).toHaveBeenCalled();
|
||||
});
|
||||
it('dispatches refreshList thunkAction', () => {
|
||||
it('calls initializeApp api method', () => {
|
||||
out.closeAndRefresh();
|
||||
expect(dispatch).toHaveBeenCalledWith(thunkActions.app.refreshList());
|
||||
expect(initializeApp).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
describe('modalState', () => {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import React from 'react';
|
||||
|
||||
import { thunkActions, hooks as appHooks } from 'data/redux';
|
||||
import { useValueCallback } from 'hooks';
|
||||
import {
|
||||
apiHooks,
|
||||
reduxHooks,
|
||||
utilHooks,
|
||||
} from 'hooks';
|
||||
import { StrictDict } from 'utils';
|
||||
import track from 'tracking';
|
||||
|
||||
@@ -14,18 +17,7 @@ export const state = StrictDict({
|
||||
isSubmitted: (val) => React.useState(val), //eslint-disable-line
|
||||
});
|
||||
|
||||
export const useTrackUnenrollReasons = ({ cardId, submittedReason }) => {
|
||||
const { isEntitlement } = appHooks.useCardEntitlementData(cardId);
|
||||
return appHooks.useTrackCourseEvent(
|
||||
track.engagement.unenrollReason,
|
||||
cardId,
|
||||
submittedReason,
|
||||
isEntitlement,
|
||||
);
|
||||
};
|
||||
|
||||
export const useUnenrollReasons = ({
|
||||
dispatch,
|
||||
cardId,
|
||||
}) => {
|
||||
// The selected option element from the menu
|
||||
@@ -38,10 +30,19 @@ export const useUnenrollReasons = ({
|
||||
// Did the user submit an unenrollment reason
|
||||
const [isSubmitted, setIsSubmitted] = module.state.isSubmitted(false);
|
||||
|
||||
const { isEntitlement } = reduxHooks.useCardEntitlementData(cardId);
|
||||
|
||||
const submittedReason = selectedReason === 'custom' ? customOption : selectedReason;
|
||||
const hasReason = ![null, ''].includes(submittedReason);
|
||||
|
||||
const handleTrackReasons = module.useTrackUnenrollReasons({ cardId, submittedReason });
|
||||
const handleTrackReasons = reduxHooks.useTrackCourseEvent(
|
||||
track.engagement.unenrollReason,
|
||||
cardId,
|
||||
submittedReason,
|
||||
isEntitlement,
|
||||
);
|
||||
|
||||
const unenrollFromCourse = apiHooks.useUnenrollFromCourse(cardId);
|
||||
|
||||
const handleClear = () => {
|
||||
setSelectedReason(null);
|
||||
@@ -52,24 +53,27 @@ export const useUnenrollReasons = ({
|
||||
|
||||
const handleSkip = () => {
|
||||
setIsSkipped(true);
|
||||
dispatch(thunkActions.app.unenrollFromCourse(cardId));
|
||||
unenrollFromCourse();
|
||||
};
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
handleTrackReasons(e);
|
||||
setIsSubmitted(true);
|
||||
dispatch(thunkActions.app.unenrollFromCourse(cardId, submittedReason));
|
||||
unenrollFromCourse();
|
||||
};
|
||||
|
||||
const handleCustomOptionChange = utilHooks.useValueCallback(setCustomOption);
|
||||
const handleSelectOption = utilHooks.useValueCallback(setSelectedReason);
|
||||
|
||||
return {
|
||||
customOption: { value: customOption, onChange: useValueCallback(setCustomOption) },
|
||||
customOption: { value: customOption, onChange: handleCustomOptionChange },
|
||||
handleClear,
|
||||
handleSkip,
|
||||
handleSubmit,
|
||||
hasReason,
|
||||
isSkipped,
|
||||
isSubmitted,
|
||||
selectOption: useValueCallback(setSelectedReason),
|
||||
selectOption: handleSelectOption,
|
||||
submittedReason,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,154 +1,191 @@
|
||||
import { keyStore } from 'utils';
|
||||
import { thunkActions, hooks as appHooks } from 'data/redux';
|
||||
import { useValueCallback } from 'hooks';
|
||||
import { MockUseState } from 'testUtils';
|
||||
import track from 'tracking';
|
||||
import {
|
||||
apiHooks,
|
||||
reduxHooks,
|
||||
utilHooks,
|
||||
} from 'hooks';
|
||||
|
||||
import * as hooks from './reasons';
|
||||
|
||||
jest.mock('data/redux', () => ({
|
||||
thunkActions: {
|
||||
app: {
|
||||
refreshList: jest.fn((args) => ({ refreshList: args })),
|
||||
unenrollFromCourse: jest.fn((...args) => ({ unenrollFromCourse: args })),
|
||||
},
|
||||
jest.mock('hooks', () => ({
|
||||
apiHooks: {
|
||||
useUnenrollFromCourse: jest.fn((...args) => ({ unenrollFromCourse: args })),
|
||||
},
|
||||
hooks: {
|
||||
reduxHooks: {
|
||||
useCardEntitlementData: jest.fn(),
|
||||
useTrackCourseEvent: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('hooks', () => ({
|
||||
useValueCallback: jest.fn((cb, prereqs) => ({ useValueCallback: { cb, prereqs } })),
|
||||
utilHooks: {
|
||||
useValueCallback: jest.fn((cb, prereqs) => ({ useValueCallback: { cb, prereqs } })),
|
||||
},
|
||||
}));
|
||||
|
||||
const state = new MockUseState(hooks);
|
||||
const testValue = 'test-value';
|
||||
const testValue2 = 'test-value2';
|
||||
const moduleKeys = keyStore(hooks);
|
||||
const unenrollFromCourse = jest.fn((...args) => ({ unenrollFromCourse: args }));
|
||||
const trackCourseEvent = jest.fn((e) => ({ courseEvent: e }));
|
||||
apiHooks.useUnenrollFromCourse.mockReturnValue(unenrollFromCourse);
|
||||
reduxHooks.useTrackCourseEvent.mockReturnValue(trackCourseEvent);
|
||||
let out;
|
||||
|
||||
const cardId = 'test-card-id';
|
||||
const loadHook = (isEntitlement = false) => {
|
||||
reduxHooks.useCardEntitlementData.mockReturnValue({ isEntitlement });
|
||||
out = hooks.useUnenrollReasons({ cardId });
|
||||
};
|
||||
|
||||
describe('UnenrollConfirmModal reasons hooks', () => {
|
||||
const dispatch = jest.fn();
|
||||
const cardId = 'test-card-id';
|
||||
|
||||
const createUseUnenrollReasons = () => hooks.useUnenrollReasons({ dispatch, cardId });
|
||||
|
||||
describe('state fields', () => {
|
||||
state.testGetter(state.keys.customOption);
|
||||
state.testGetter(state.keys.isSkipped);
|
||||
state.testGetter(state.keys.isSubmitted);
|
||||
state.testGetter(state.keys.selectedReason);
|
||||
});
|
||||
describe('useTrackUnenrollReasons', () => {
|
||||
it('returns trackCourseEvent for unenroll with submitted reason and isEntitlement', () => {
|
||||
appHooks.useCardEntitlementData.mockReturnValue({ isEntitlement: false });
|
||||
const args = { cardId, submittedReason: testValue };
|
||||
expect(hooks.useTrackUnenrollReasons(args)).toEqual(appHooks.useTrackCourseEvent(
|
||||
track.engagement.unenrollReason,
|
||||
args.cardId,
|
||||
args.submittedReason,
|
||||
false,
|
||||
));
|
||||
});
|
||||
});
|
||||
describe('useUnenrollReasons', () => {
|
||||
const trackReasonsEvent = jest.fn((e) => ({ trackReasonsEvent: e }));
|
||||
let useTrackUnenrollReasons;
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
state.mock();
|
||||
appHooks.useCardEntitlementData.mockReturnValue({ isEntitlement: false });
|
||||
useTrackUnenrollReasons = jest.spyOn(hooks, moduleKeys.useTrackUnenrollReasons)
|
||||
.mockImplementation(() => trackReasonsEvent);
|
||||
out = createUseUnenrollReasons();
|
||||
loadHook();
|
||||
});
|
||||
afterEach(() => {
|
||||
state.restore();
|
||||
});
|
||||
describe('customOption', () => {
|
||||
test('customOption.value returns custom option', () => {
|
||||
state.mockVal(state.keys.customOption, testValue);
|
||||
expect(createUseUnenrollReasons().customOption.value).toEqual(testValue);
|
||||
});
|
||||
test('customOption.onChange returns valueCallback for setCustomOption', () => {
|
||||
expect(out.customOption.onChange).toEqual(useValueCallback(state.setState.customOption));
|
||||
});
|
||||
});
|
||||
describe('handleClear method', () => {
|
||||
it('resets selected and submitted reasons, custom option and isSkipped', () => {
|
||||
out.handleClear();
|
||||
expect(state.setState.selectedReason).toHaveBeenCalledWith(null);
|
||||
expect(state.setState.customOption).toHaveBeenCalledWith('');
|
||||
expect(state.setState.isSkipped).toHaveBeenCalledWith(false);
|
||||
expect(state.setState.isSubmitted).toHaveBeenCalledWith(false);
|
||||
});
|
||||
});
|
||||
test('handleSkip sets isSkipped and isSubmitted, and unenrolls w/out a reason', () => {
|
||||
out.handleSkip();
|
||||
expect(state.setState.isSkipped).toHaveBeenCalledWith(true);
|
||||
expect(dispatch).toHaveBeenCalledWith(thunkActions.app.unenrollFromCourse(cardId));
|
||||
});
|
||||
describe('handleSubmit', () => {
|
||||
it('tracks reason event and dispatches unenroll thunk action', () => {
|
||||
state.mockVal(state.keys.selectedReason, testValue);
|
||||
out = createUseUnenrollReasons();
|
||||
expect(useTrackUnenrollReasons).toHaveBeenCalledWith({
|
||||
cardId,
|
||||
submittedReason: testValue,
|
||||
describe('behavior', () => {
|
||||
describe('state fields', () => {
|
||||
it('initializes selectedReason with null', () => {
|
||||
state.expectInitializedWith(state.keys.selectedReason, null);
|
||||
});
|
||||
expect(trackReasonsEvent).not.toHaveBeenCalled();
|
||||
const event = { test: 'event' };
|
||||
out.handleSubmit(event);
|
||||
expect(trackReasonsEvent).toHaveBeenCalledWith(event);
|
||||
expect(dispatch).toHaveBeenCalledWith(thunkActions.app.unenrollFromCourse(cardId));
|
||||
it('initializes customOption with empty string', () => {
|
||||
state.expectInitializedWith(state.keys.customOption, '');
|
||||
});
|
||||
it('initializes isSkipped with false', () => {
|
||||
state.expectInitializedWith(state.keys.isSkipped, false);
|
||||
});
|
||||
it('initializes isSubmitted with false', () => {
|
||||
state.expectInitializedWith(state.keys.isSubmitted, false);
|
||||
});
|
||||
});
|
||||
describe('useTrackCourseEvent inititalization', () => {
|
||||
it('passes custom option if selectedReason is custom', () => {
|
||||
state.mockVal(state.keys.selectedReason, 'custom');
|
||||
state.mockVal(state.keys.customOption, testValue);
|
||||
loadHook();
|
||||
expect(reduxHooks.useTrackCourseEvent).toHaveBeenCalledWith(
|
||||
track.engagement.unenrollReason,
|
||||
cardId,
|
||||
testValue,
|
||||
false, // isEntitlement
|
||||
);
|
||||
});
|
||||
it('passes selected reason if not custom', () => {
|
||||
state.mockVal(state.keys.selectedReason, testValue2);
|
||||
state.mockVal(state.keys.customOption, testValue);
|
||||
loadHook(true);
|
||||
expect(reduxHooks.useTrackCourseEvent).toHaveBeenCalledWith(
|
||||
track.engagement.unenrollReason,
|
||||
cardId,
|
||||
testValue2,
|
||||
true, // isEntitlement
|
||||
);
|
||||
});
|
||||
});
|
||||
it('initializes card entitlement data with cardId', () => {
|
||||
expect(reduxHooks.useCardEntitlementData).toHaveBeenCalledWith(cardId);
|
||||
});
|
||||
it('initializes unenerollFromCourse event with cardId', () => {
|
||||
expect(apiHooks.useUnenrollFromCourse).toHaveBeenCalledWith(cardId);
|
||||
});
|
||||
});
|
||||
describe('hasReason', () => {
|
||||
it('returns true if an option is selected other than custom', () => {
|
||||
state.mockVal(state.keys.selectedReason, testValue);
|
||||
out = createUseUnenrollReasons();
|
||||
expect(out.hasReason).toEqual(true);
|
||||
describe('output', () => {
|
||||
describe('customOption', () => {
|
||||
test('customOption.value returns custom option', () => {
|
||||
state.mockVal(state.keys.customOption, testValue);
|
||||
loadHook();
|
||||
expect(out.customOption.value).toEqual(testValue);
|
||||
});
|
||||
test('customOption.onChange returns valueCallback for setCustomOption', () => {
|
||||
expect(out.customOption.onChange).toEqual(
|
||||
utilHooks.useValueCallback(state.setState.customOption),
|
||||
);
|
||||
});
|
||||
});
|
||||
it('returns true if custom option is selected and provided', () => {
|
||||
state.mockVal(state.keys.selectedReason, 'custom');
|
||||
state.mockVal(state.keys.customOption, testValue2);
|
||||
out = createUseUnenrollReasons();
|
||||
expect(out.hasReason).toEqual(true);
|
||||
describe('hasReason', () => {
|
||||
it('returns true if an option is selected other than custom', () => {
|
||||
state.mockVal(state.keys.selectedReason, testValue);
|
||||
loadHook();
|
||||
expect(out.hasReason).toEqual(true);
|
||||
});
|
||||
it('returns true if custom option is selected and provided', () => {
|
||||
state.mockVal(state.keys.selectedReason, 'custom');
|
||||
state.mockVal(state.keys.customOption, testValue2);
|
||||
loadHook();
|
||||
expect(out.hasReason).toEqual(true);
|
||||
});
|
||||
it('returns false if no option is selected', () => {
|
||||
state.mockVal(state.keys.selectedReason, null);
|
||||
loadHook();
|
||||
expect(out.hasReason).toEqual(false);
|
||||
});
|
||||
it('returns false if custom option is selcted but not provided', () => {
|
||||
state.mockVal(state.keys.selectedReason, 'custom');
|
||||
state.mockVal(state.keys.customOption, '');
|
||||
loadHook();
|
||||
expect(out.hasReason).toEqual(false);
|
||||
});
|
||||
});
|
||||
it('returns false if no option is selected', () => {
|
||||
state.mockVal(state.keys.selectedReason, null);
|
||||
out = createUseUnenrollReasons();
|
||||
expect(out.hasReason).toEqual(false);
|
||||
describe('handleClear method', () => {
|
||||
it('resets selected and submitted reasons, custom option and isSkipped', () => {
|
||||
out.handleClear();
|
||||
expect(state.setState.selectedReason).toHaveBeenCalledWith(null);
|
||||
expect(state.setState.customOption).toHaveBeenCalledWith('');
|
||||
expect(state.setState.isSkipped).toHaveBeenCalledWith(false);
|
||||
expect(state.setState.isSubmitted).toHaveBeenCalledWith(false);
|
||||
});
|
||||
});
|
||||
it('returns false if custom option is selcted but not provided', () => {
|
||||
state.mockVal(state.keys.selectedReason, 'custom');
|
||||
state.mockVal(state.keys.customOption, '');
|
||||
out = createUseUnenrollReasons();
|
||||
expect(out.hasReason).toEqual(false);
|
||||
test('handleSkip sets isSkipped and isSubmitted, and unenrolls w/out a reason', () => {
|
||||
out.handleSkip();
|
||||
expect(state.setState.isSkipped).toHaveBeenCalledWith(true);
|
||||
expect(unenrollFromCourse).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
test('isSkipped returns state value', () => {
|
||||
state.mockVal(state.keys.isSkipped, testValue);
|
||||
expect(createUseUnenrollReasons().isSkipped).toEqual(testValue);
|
||||
});
|
||||
test('isSubmitted returns state value', () => {
|
||||
state.mockVal(state.keys.isSubmitted, testValue);
|
||||
expect(createUseUnenrollReasons().isSubmitted).toEqual(testValue);
|
||||
});
|
||||
test('selectedOption returns valueCallback for setSelectedReason', () => {
|
||||
expect(out.selectOption).toEqual(useValueCallback(state.setState.selectedReason));
|
||||
});
|
||||
describe('submittedReason', () => {
|
||||
it('returns the selected reason unless custom is selcted, then shows custom option', () => {
|
||||
state.mockVal(state.keys.selectedReason, testValue);
|
||||
state.mockVal(state.keys.customOption, testValue2);
|
||||
out = createUseUnenrollReasons();
|
||||
expect(out.submittedReason).toEqual(testValue);
|
||||
state.mockVal(state.keys.selectedReason, 'custom');
|
||||
state.mockVal(state.keys.customOption, testValue2);
|
||||
out = createUseUnenrollReasons();
|
||||
expect(out.submittedReason).toEqual(testValue2);
|
||||
describe('handleSubmit', () => {
|
||||
it('tracks reason event and calls unenroll action', () => {
|
||||
state.mockVal(state.keys.selectedReason, testValue);
|
||||
loadHook();
|
||||
expect(trackCourseEvent).not.toHaveBeenCalled();
|
||||
const event = { test: 'event' };
|
||||
out.handleSubmit(event);
|
||||
expect(trackCourseEvent).toHaveBeenCalledWith(event);
|
||||
expect(unenrollFromCourse).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
test('isSkipped returns state value', () => {
|
||||
state.mockVal(state.keys.isSkipped, testValue);
|
||||
loadHook();
|
||||
expect(out.isSkipped).toEqual(testValue);
|
||||
});
|
||||
test('isSubmitted returns state value', () => {
|
||||
state.mockVal(state.keys.isSubmitted, testValue);
|
||||
loadHook();
|
||||
expect(out.isSubmitted).toEqual(testValue);
|
||||
});
|
||||
test('selectedOption returns valueCallback for setSelectedReason', () => {
|
||||
expect(out.selectOption).toEqual(
|
||||
utilHooks.useValueCallback(state.setState.selectedReason),
|
||||
);
|
||||
});
|
||||
describe('submittedReason', () => {
|
||||
it('returns the selected reason unless is custom, then shows custom option', () => {
|
||||
state.mockVal(state.keys.selectedReason, testValue);
|
||||
state.mockVal(state.keys.customOption, testValue2);
|
||||
loadHook();
|
||||
expect(out.submittedReason).toEqual(testValue);
|
||||
state.mockVal(state.keys.selectedReason, 'custom');
|
||||
state.mockVal(state.keys.customOption, testValue2);
|
||||
loadHook();
|
||||
expect(out.submittedReason).toEqual(testValue2);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {
|
||||
ModalDialog,
|
||||
} from '@edx/paragon';
|
||||
import { ModalDialog } from '@edx/paragon';
|
||||
|
||||
import { nullMethod } from 'hooks';
|
||||
import { nullMethod } from 'utils';
|
||||
|
||||
import ConfirmPane from './components/ConfirmPane';
|
||||
import ReasonPane from './components/ReasonPane';
|
||||
@@ -20,14 +17,13 @@ export const UnenrollConfirmModal = ({
|
||||
show,
|
||||
cardId,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const {
|
||||
confirm,
|
||||
reason,
|
||||
closeAndRefresh,
|
||||
close,
|
||||
modalState,
|
||||
} = useUnenrollData({ dispatch, closeModal, cardId });
|
||||
} = useUnenrollData({ closeModal, cardId });
|
||||
const showFullscreen = modalState === modalStates.reason;
|
||||
return (
|
||||
<ModalDialog
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { UnenrollConfirmModal } from '.';
|
||||
|
||||
@@ -17,7 +16,6 @@ jest.mock('./hooks', () => ({
|
||||
}));
|
||||
|
||||
describe('UnenrollConfirmModal component', () => {
|
||||
const dispatch = useDispatch();
|
||||
const hookProps = {
|
||||
confirm: jest.fn().mockName('hooks.confirm'),
|
||||
reason: {
|
||||
@@ -35,10 +33,10 @@ describe('UnenrollConfirmModal component', () => {
|
||||
show: true,
|
||||
cardId,
|
||||
};
|
||||
test('hooks called with dispatch and closeModal props', () => {
|
||||
test('hooks called with closeModal and cardId', () => {
|
||||
hooks.useUnenrollData.mockReturnValueOnce(hookProps);
|
||||
shallow(<UnenrollConfirmModal {...props} />);
|
||||
expect(hooks.useUnenrollData).toHaveBeenCalledWith({ dispatch, closeModal, cardId });
|
||||
expect(hooks.useUnenrollData).toHaveBeenCalledWith({ closeModal, cardId });
|
||||
});
|
||||
test('snapshot: modalStates.confirm', () => {
|
||||
hooks.useUnenrollData.mockReturnValueOnce(hookProps);
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
|
||||
import { actions as appActions } from './app/reducer';
|
||||
import appSelectors from './app/selectors';
|
||||
import requestSelectors from './requests/selectors';
|
||||
import * as module from './hooks';
|
||||
|
||||
const { courseCard } = appSelectors;
|
||||
|
||||
export const usePageNumber = () => useSelector(appSelectors.pageNumber);
|
||||
export const useEmailConfirmationData = () => useSelector(appSelectors.emailConfirmation);
|
||||
export const useEnterpriseDashboardData = () => useSelector(appSelectors.enterpriseDashboard);
|
||||
export const usePlatformSettingsData = () => useSelector(appSelectors.platformSettings);
|
||||
export const useSelectSessionModalData = () => useSelector(appSelectors.selectSessionModal);
|
||||
export const useSocialSettingsData = () => useSelector(appSelectors.socialSettingsData);
|
||||
|
||||
export const useHasCourses = () => useSelector(appSelectors.hasCourses);
|
||||
export const useHasAvailableDashboards = () => useSelector(appSelectors.hasAvailableDashboards);
|
||||
export const useCurrentCourseList = (opts) => useSelector(
|
||||
state => appSelectors.currentList(state, opts),
|
||||
);
|
||||
export const useShowSelectSessionModal = () => useSelector(appSelectors.showSelectSessionModal);
|
||||
|
||||
// eslint-disable-next-line
|
||||
export const useCourseCardData = (selector) => (cardId) => useSelector(
|
||||
(state) => selector(state, cardId),
|
||||
);
|
||||
|
||||
export const useCardCertificateData = useCourseCardData(courseCard.certificate);
|
||||
export const useCardCourseData = useCourseCardData(courseCard.course);
|
||||
export const useCardCourseRunData = useCourseCardData(courseCard.courseRun);
|
||||
export const useCardCreditData = useCourseCardData(courseCard.credit);
|
||||
export const useCardEnrollmentData = useCourseCardData(courseCard.enrollment);
|
||||
export const useCardEntitlementData = useCourseCardData(courseCard.entitlement);
|
||||
export const useCardGradeData = useCourseCardData(courseCard.gradeData);
|
||||
export const useCardProviderData = useCourseCardData(courseCard.courseProvider);
|
||||
export const useCardRelatedProgramsData = useCourseCardData(courseCard.relatedPrograms);
|
||||
|
||||
export const useCardSocialSettingsData = (cardId) => {
|
||||
const { socialShareUrl } = module.useCardCourseData(cardId);
|
||||
const socialShareSettings = useSelector(appSelectors.socialShareSettings);
|
||||
if (!socialShareSettings) {
|
||||
return {
|
||||
facebook: { isEnabled: false, shareUrl: '' },
|
||||
twitter: { isEnabled: false, shareUrl: '' },
|
||||
};
|
||||
}
|
||||
return {
|
||||
facebook: {
|
||||
isEnabled: socialShareSettings.facebook.isEnabled,
|
||||
shareUrl: `${socialShareUrl}?${socialShareSettings.facebook.utmParams}`,
|
||||
},
|
||||
twitter: {
|
||||
isEnabled: socialShareSettings.twitter.isEnabled,
|
||||
shareUrl: `${socialShareUrl}?${socialShareSettings.twitter.utmParams}`,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const useUpdateSelectSessionModalCallback = (cardId) => {
|
||||
const dispatch = useDispatch();
|
||||
return () => dispatch(appActions.updateSelectSessionModal(cardId));
|
||||
};
|
||||
|
||||
export const useMasqueradeData = () => useSelector(requestSelectors.masquerade);
|
||||
|
||||
export const useRequestIsPending = (requestName) => useSelector(requestSelectors.isPending(requestName));
|
||||
export const useRequestIsFailed = (requestName) => useSelector(requestSelectors.isFailed(requestName));
|
||||
|
||||
export const useTrackCourseEvent = (tracker, cardId, ...args) => {
|
||||
const { courseId } = module.useCardCourseRunData(cardId);
|
||||
return (e) => tracker(courseId, ...args)(e);
|
||||
};
|
||||
80
src/data/redux/hooks/app.js
Normal file
80
src/data/redux/hooks/app.js
Normal file
@@ -0,0 +1,80 @@
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
|
||||
import * as redux from 'data/redux';
|
||||
import * as module from './app';
|
||||
|
||||
const selectors = redux.selectors.app;
|
||||
const actions = redux.actions.app;
|
||||
|
||||
/** Simple Selectors **/
|
||||
export const usePageNumber = () => useSelector(selectors.pageNumber);
|
||||
export const useEmailConfirmationData = () => useSelector(selectors.emailConfirmation);
|
||||
export const useEnterpriseDashboardData = () => useSelector(selectors.enterpriseDashboard);
|
||||
export const usePlatformSettingsData = () => useSelector(selectors.platformSettings);
|
||||
export const useSelectSessionModalData = () => useSelector(selectors.selectSessionModal);
|
||||
export const useSocialShareSettings = () => useSelector(selectors.socialShareSettings);
|
||||
|
||||
/** global-level meta-selectors **/
|
||||
export const useHasCourses = () => useSelector(selectors.hasCourses);
|
||||
export const useHasAvailableDashboards = () => useSelector(selectors.hasAvailableDashboards);
|
||||
export const useCurrentCourseList = (opts) => useSelector(
|
||||
state => selectors.currentList(state, opts),
|
||||
);
|
||||
export const useShowSelectSessionModal = () => useSelector(selectors.showSelectSessionModal);
|
||||
|
||||
// eslint-disable-next-line
|
||||
export const useCourseCardData = (selector) => (cardId) => useSelector(
|
||||
(state) => selector(state, cardId),
|
||||
);
|
||||
/** Course Card selectors **/
|
||||
const { courseCard } = selectors;
|
||||
export const useCardCertificateData = useCourseCardData(courseCard.certificate);
|
||||
export const useCardCourseData = useCourseCardData(courseCard.course);
|
||||
export const useCardCourseRunData = useCourseCardData(courseCard.courseRun);
|
||||
export const useCardCreditData = useCourseCardData(courseCard.credit);
|
||||
export const useCardEnrollmentData = useCourseCardData(courseCard.enrollment);
|
||||
export const useCardEntitlementData = useCourseCardData(courseCard.entitlement);
|
||||
export const useCardGradeData = useCourseCardData(courseCard.gradeData);
|
||||
export const useCardProviderData = useCourseCardData(courseCard.courseProvider);
|
||||
export const useCardRelatedProgramsData = useCourseCardData(courseCard.relatedPrograms);
|
||||
|
||||
export const useCardSocialSettingsData = (cardId) => {
|
||||
const socialShareSettings = module.useSocialShareSettings();
|
||||
const { socialShareUrl } = module.useCardCourseData(cardId);
|
||||
const defaultSettings = { isEnabled: false, shareUrl: '' };
|
||||
|
||||
if (!socialShareSettings) {
|
||||
return { facebook: defaultSettings, twitter: defaultSettings };
|
||||
}
|
||||
const { facebook, twitter } = socialShareSettings;
|
||||
const loadSettings = (target) => ({
|
||||
isEnabled: target.isEnabled,
|
||||
shareUrl: `${socialShareUrl}?${target.utmParams}`,
|
||||
});
|
||||
return { facebook: loadSettings(facebook), twitter: loadSettings(twitter) };
|
||||
};
|
||||
|
||||
/** Events **/
|
||||
export const useUpdateSelectSessionModalCallback = (cardId) => {
|
||||
const dispatch = useDispatch();
|
||||
return () => dispatch(actions.updateSelectSessionModal(cardId));
|
||||
};
|
||||
|
||||
export const useTrackCourseEvent = (tracker, cardId, ...args) => {
|
||||
const { courseId } = module.useCardCourseRunData(cardId);
|
||||
return (e) => tracker(courseId, ...args)(e);
|
||||
};
|
||||
|
||||
export const useSetPageNumber = () => {
|
||||
const dispatch = useDispatch();
|
||||
return (value) => dispatch(actions.setPageNumber(value));
|
||||
};
|
||||
|
||||
export const useLoadData = () => {
|
||||
const dispatch = useDispatch();
|
||||
return ({ courses, ...globalData }) => {
|
||||
dispatch(actions.setPageNumber(1));
|
||||
dispatch(actions.loadGlobalData(globalData));
|
||||
dispatch(actions.loadCourses({ courses }));
|
||||
};
|
||||
};
|
||||
2
src/data/redux/hooks/index.js
Normal file
2
src/data/redux/hooks/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './app';
|
||||
export * from './requests';
|
||||
45
src/data/redux/hooks/requests.js
Normal file
45
src/data/redux/hooks/requests.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
|
||||
import * as redux from 'data/redux';
|
||||
import * as module from './requests';
|
||||
|
||||
const selectors = redux.selectors.requests;
|
||||
const actions = redux.actions.requests;
|
||||
|
||||
export const useMasqueradeData = () => useSelector(selectors.masquerade);
|
||||
|
||||
export const statusSelector = selector => (requestName) => useSelector(selector(requestName));
|
||||
export const useRequestIsPending = module.statusSelector(selectors.isPending);
|
||||
export const useRequestIsFailed = module.statusSelector(selectors.isFailed);
|
||||
export const useRequestIsCompleted = module.statusSelector(selectors.isCompleted);
|
||||
export const useRequestIsInactive = module.statusSelector(selectors.isInactive);
|
||||
export const useRequestError = module.statusSelector(selectors.error);
|
||||
export const useRequestErrorCode = module.statusSelector(selectors.errorCode);
|
||||
export const useRequestErrorStatus = module.statusSelector(selectors.errorStatus);
|
||||
export const useRequestData = module.statusSelector(selectors.data);
|
||||
|
||||
export const useMakeNetworkRequest = () => {
|
||||
const dispatch = useDispatch();
|
||||
return ({
|
||||
requestKey,
|
||||
promise,
|
||||
onSuccess,
|
||||
onFailure,
|
||||
}) => {
|
||||
dispatch(actions.startRequest({ requestKey }));
|
||||
return promise.then((response) => {
|
||||
if (onSuccess) { onSuccess(response); }
|
||||
dispatch(actions.completeRequest({ requestKey, response }));
|
||||
}).catch((error) => {
|
||||
if (onFailure) { onFailure(error); }
|
||||
dispatch(actions.failRequest({ requestKey, error }));
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export const useClearRequest = () => {
|
||||
const dispatch = useDispatch();
|
||||
return (requestKey) => {
|
||||
dispatch(actions.clearRequest({ requestKey }));
|
||||
};
|
||||
};
|
||||
@@ -5,10 +5,6 @@ import { StrictDict } from 'utils';
|
||||
import * as app from './app';
|
||||
import * as requests from './requests';
|
||||
|
||||
import * as hooks from './hooks';
|
||||
|
||||
export { default as thunkActions } from './thunkActions';
|
||||
|
||||
const modules = {
|
||||
app,
|
||||
requests,
|
||||
@@ -28,6 +24,6 @@ const actions = StrictDict(moduleProps('actions'));
|
||||
|
||||
const selectors = StrictDict(moduleProps('selectors'));
|
||||
|
||||
export { actions, selectors, hooks };
|
||||
export { actions, selectors };
|
||||
|
||||
export default rootReducer;
|
||||
|
||||
@@ -19,7 +19,7 @@ const requests = createSlice({
|
||||
reducers: {
|
||||
startRequest: (state, { payload }) => ({
|
||||
...state,
|
||||
[payload]: {
|
||||
[payload.requestKey]: {
|
||||
status: RequestStates.pending,
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -18,7 +18,7 @@ describe('requests reducer', () => {
|
||||
it('adds a pending status for the given key', () => {
|
||||
expect(reducer(
|
||||
testingState,
|
||||
actions.startRequest(testKey),
|
||||
actions.startRequest({ requestKey: testKey }),
|
||||
)).toEqual({
|
||||
...testingState,
|
||||
[testKey]: { status: RequestStates.pending },
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
import { StrictDict } from 'utils';
|
||||
import { actions, selectors } from 'data/redux';
|
||||
import { post } from 'data/services/lms/utils';
|
||||
|
||||
import requests from './requests';
|
||||
|
||||
// import { locationId } from 'data/constants/app';
|
||||
|
||||
// import { } from './requests';
|
||||
import * as module from './app';
|
||||
|
||||
export const loadData = ({ courses, ...globalData }) => dispatch => {
|
||||
dispatch(actions.app.setPageNumber(1));
|
||||
dispatch(actions.app.loadGlobalData(globalData));
|
||||
dispatch(actions.app.loadCourses({ courses }));
|
||||
};
|
||||
|
||||
/**
|
||||
* initialize the app, loading ora and course metadata from the api, and loading the initial
|
||||
* submission list data.
|
||||
*/
|
||||
export const initialize = () => (dispatch) => (
|
||||
dispatch(requests.initializeList({
|
||||
onSuccess: ({ data }) => dispatch(module.loadData(data)),
|
||||
}))
|
||||
);
|
||||
|
||||
// TODO: connect hook to actual api later
|
||||
export const sendConfirmEmail = () => (dispatch, getState) => post(
|
||||
selectors.app.emailConfirmation(getState()).sendEmailUrl,
|
||||
);
|
||||
|
||||
export const newEntitlementEnrollment = (cardId, selection) => (dispatch, getState) => {
|
||||
const { uuid } = selectors.app.courseCard.entitlement(getState(), cardId);
|
||||
dispatch(requests.newEntitlementEnrollment({
|
||||
uuid,
|
||||
courseId: selection,
|
||||
onSuccess: () => dispatch(module.initialize()),
|
||||
}));
|
||||
};
|
||||
|
||||
export const switchEntitlementEnrollment = (cardId, selection) => (dispatch, getState) => {
|
||||
const { uuid } = selectors.app.courseCard.entitlement(getState(), cardId);
|
||||
dispatch(requests.switchEntitlementEnrollment({
|
||||
uuid,
|
||||
courseId: selection,
|
||||
onSuccess: () => dispatch(module.initialize()),
|
||||
}));
|
||||
};
|
||||
|
||||
export const leaveEntitlementSession = (cardId) => (dispatch, getState) => {
|
||||
const { uuid, isRefundable } = selectors.app.courseCard.entitlement(getState(), cardId);
|
||||
dispatch(requests.leaveEntitlementSession({
|
||||
uuid,
|
||||
isRefundable,
|
||||
onSuccess: () => dispatch(module.initialize()),
|
||||
}));
|
||||
};
|
||||
|
||||
export const unenrollFromCourse = (cardId) => (dispatch, getState) => {
|
||||
const { courseId } = selectors.app.courseCard.courseRun(getState(), cardId);
|
||||
dispatch(requests.unenrollFromCourse({ courseId }));
|
||||
};
|
||||
|
||||
export const masqueradeAs = (user) => (dispatch) => (
|
||||
dispatch(requests.masqueradeAs({
|
||||
user,
|
||||
onSuccess: ({ data }) => dispatch(module.loadData(data)),
|
||||
}))
|
||||
);
|
||||
|
||||
export const clearMasquerade = () => (dispatch) => {
|
||||
dispatch(requests.clearMasquerade());
|
||||
dispatch(module.initialize());
|
||||
};
|
||||
|
||||
export const updateEmailSettings = (cardId, enable) => (dispatch, getState) => {
|
||||
const { courseId } = selectors.app.courseCard.courseRun(getState(), cardId);
|
||||
dispatch(requests.updateEmailSettings({
|
||||
courseId,
|
||||
enable,
|
||||
}));
|
||||
};
|
||||
|
||||
export default StrictDict({
|
||||
loadData,
|
||||
initialize,
|
||||
refreshList: initialize,
|
||||
sendConfirmEmail,
|
||||
newEntitlementEnrollment,
|
||||
switchEntitlementEnrollment,
|
||||
leaveEntitlementSession,
|
||||
unenrollFromCourse,
|
||||
masqueradeAs,
|
||||
clearMasquerade,
|
||||
updateEmailSettings,
|
||||
});
|
||||
@@ -1,181 +0,0 @@
|
||||
import { keyStore } from 'utils';
|
||||
import { actions, selectors } from 'data/redux';
|
||||
import { post } from 'data/services/lms/utils';
|
||||
|
||||
import requests from './requests';
|
||||
|
||||
import * as module from './app';
|
||||
|
||||
jest.mock('data/services/lms/utils', () => ({
|
||||
post: jest.fn(),
|
||||
}));
|
||||
jest.mock('data/redux', () => ({
|
||||
actions: {
|
||||
app: {
|
||||
setPageNumber: jest.fn(v => ({ setPageNumber: v })),
|
||||
loadGlobalData: jest.fn(v => ({ loadGlobalData: v })),
|
||||
loadCourses: jest.fn(v => ({ loadCourses: v })),
|
||||
loadRecommendedCourses: jest.fn(v => ({ loadRecommendedCourses: v })),
|
||||
},
|
||||
},
|
||||
selectors: {
|
||||
app: {
|
||||
emailConfirmation: jest.fn(),
|
||||
courseCard: {
|
||||
courseRun: jest.fn(),
|
||||
entitlement: jest.fn(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
jest.mock('./requests', () => ({
|
||||
initializeList: jest.fn((args) => ({ initializeList: args })),
|
||||
newEntitlementEnrollment: jest.fn((args) => ({ newEntitlementEnrollment: args })),
|
||||
switchEntitlementEnrollment: jest.fn((args) => ({ switchEntitlementEnrollment: args })),
|
||||
leaveEntitlementSession: jest.fn((args) => ({ leaveEntitlementSession: args })),
|
||||
unenrollFromCourse: jest.fn((args) => ({ unenrollFromCourse: args })),
|
||||
masqueradeAs: jest.fn((args) => ({ masqueradeAs: args })),
|
||||
clearMasquerade: jest.fn((args) => ({ clearMasquerade: args })),
|
||||
updateEmailSettings: jest.fn((args) => ({ updateEmailSettings: args })),
|
||||
}));
|
||||
|
||||
const dispatch = jest.fn(action => action);
|
||||
|
||||
const checkDispatch = (call) => { expect(dispatch).toHaveBeenCalledWith(call); };
|
||||
|
||||
const moduleKeys = keyStore(module);
|
||||
|
||||
const testString = 'TEST-string';
|
||||
const uuid = 'test-UUID';
|
||||
const cardId = 'test-card-id';
|
||||
const selection = 'test-selection';
|
||||
const courseId = 'test-COURSE-id';
|
||||
const isRefundable = 'test-is-refundable';
|
||||
|
||||
const loadDataSpy = jest.spyOn(module, moduleKeys.loadData);
|
||||
const mockLoadData = data => ({ loadData: data });
|
||||
|
||||
const initializeSpy = jest.spyOn(module, moduleKeys.initialize);
|
||||
const mockInitialize = () => 'mock-initialize';
|
||||
|
||||
const testState = { test: 'state' };
|
||||
const getState = () => testState;
|
||||
|
||||
describe('app thunk actions', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
selectors.app.emailConfirmation.mockReturnValueOnce({ sendEmailUrl: testString });
|
||||
selectors.app.courseCard.entitlement.mockReturnValueOnce({ uuid, isRefundable });
|
||||
selectors.app.courseCard.courseRun.mockReturnValueOnce({ courseId });
|
||||
});
|
||||
describe('loadData', () => {
|
||||
const courses = 'test-courses';
|
||||
const globalData = { some: 'global', data: 'fields' };
|
||||
beforeEach(() => {
|
||||
module.loadData({ courses, ...globalData })(dispatch);
|
||||
});
|
||||
it('initializes pageNumber to 1', () => {
|
||||
checkDispatch(actions.app.setPageNumber(1));
|
||||
});
|
||||
it('loads courses', () => {
|
||||
checkDispatch(actions.app.loadCourses({ courses }));
|
||||
});
|
||||
it('loads remaining passed args as global data', () => {
|
||||
checkDispatch(actions.app.loadGlobalData(globalData));
|
||||
});
|
||||
});
|
||||
describe('initialize', () => {
|
||||
beforeEach(() => {
|
||||
loadDataSpy.mockImplementationOnce(mockLoadData);
|
||||
module.initialize()(dispatch);
|
||||
});
|
||||
it('dispatches initializeList event, calling loadData on response', () => {
|
||||
const { onSuccess } = dispatch.mock.calls[0][0].initializeList;
|
||||
onSuccess({ data: testString });
|
||||
checkDispatch(mockLoadData(testString));
|
||||
});
|
||||
});
|
||||
describe('sendConfirmEmail', () => {
|
||||
it('sends post request fo sendEmailUrl', () => {
|
||||
expect(module.sendConfirmEmail()(dispatch, getState)).toEqual(post(testString));
|
||||
expect(selectors.app.emailConfirmation).toHaveBeenCalledWith(testState);
|
||||
});
|
||||
});
|
||||
describe('newEntitlementEnrollment', () => {
|
||||
beforeEach(() => {
|
||||
module.newEntitlementEnrollment(cardId, selection)(dispatch, getState);
|
||||
});
|
||||
it('dispatches newEntitlementEnrollment request then re-init on success', () => {
|
||||
const request = dispatch.mock.calls[0][0];
|
||||
expect(request.newEntitlementEnrollment.uuid).toEqual(uuid);
|
||||
expect(request.newEntitlementEnrollment.courseId).toEqual(selection);
|
||||
expect(request.newEntitlementEnrollment.onSuccess).toBeDefined();
|
||||
expect(initializeSpy).not.toHaveBeenCalled();
|
||||
request.newEntitlementEnrollment.onSuccess();
|
||||
expect(initializeSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
describe('switchEntitlementEnrollmnent', () => {
|
||||
beforeEach(() => {
|
||||
module.switchEntitlementEnrollment(cardId, selection)(dispatch, getState);
|
||||
});
|
||||
it('dispatches switchEntitlementEnrollment request then re-init on success', () => {
|
||||
const request = dispatch.mock.calls[0][0];
|
||||
expect(request.switchEntitlementEnrollment.uuid).toEqual(uuid);
|
||||
expect(request.switchEntitlementEnrollment.courseId).toEqual(selection);
|
||||
expect(request.switchEntitlementEnrollment.onSuccess).toBeDefined();
|
||||
expect(initializeSpy).not.toHaveBeenCalled();
|
||||
request.switchEntitlementEnrollment.onSuccess();
|
||||
expect(initializeSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
describe('leaveEntitlementSession', () => {
|
||||
beforeEach(() => {
|
||||
module.leaveEntitlementSession(cardId)(dispatch, getState);
|
||||
});
|
||||
it('dispatches leaveEntitlementEnrollment request then re-init on success', () => {
|
||||
const request = dispatch.mock.calls[0][0];
|
||||
expect(request.leaveEntitlementSession.uuid).toEqual(uuid);
|
||||
expect(request.leaveEntitlementSession.isRefundable).toEqual(isRefundable);
|
||||
expect(request.leaveEntitlementSession.onSuccess).toBeDefined();
|
||||
expect(initializeSpy).not.toHaveBeenCalled();
|
||||
request.leaveEntitlementSession.onSuccess();
|
||||
expect(initializeSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
describe('unenrollFromCourse', () => {
|
||||
const reason = 'test-reason';
|
||||
it('dispatches unenrollFromCourse request action', () => {
|
||||
module.unenrollFromCourse(cardId, reason)(dispatch, getState);
|
||||
const request = dispatch.mock.calls[0][0];
|
||||
expect(request.unenrollFromCourse.courseId).toEqual(courseId);
|
||||
});
|
||||
});
|
||||
describe('masqueradeAs', () => {
|
||||
it('dispatches masqueradeAS request action, loading data on success', () => {
|
||||
loadDataSpy.mockImplementationOnce(mockLoadData);
|
||||
module.masqueradeAs(testString)(dispatch);
|
||||
const request = dispatch.mock.calls[0][0];
|
||||
request.masqueradeAs.onSuccess({ data: testString });
|
||||
expect(dispatch).toHaveBeenCalledWith(mockLoadData(testString));
|
||||
});
|
||||
});
|
||||
describe('clearMasquerade', () => {
|
||||
it('dispatches clearMasquerade action and re-initializes', () => {
|
||||
initializeSpy.mockImplementationOnce(mockInitialize);
|
||||
module.clearMasquerade()(dispatch);
|
||||
expect(dispatch).toHaveBeenCalledWith(requests.clearMasquerade());
|
||||
expect(dispatch).toHaveBeenCalledWith(mockInitialize());
|
||||
});
|
||||
});
|
||||
describe('update email settings', () => {
|
||||
it('dispatches updateEmailSettings request action', () => {
|
||||
module.updateEmailSettings(cardId, testString)(dispatch, getState);
|
||||
expect(selectors.app.courseCard.courseRun).toHaveBeenCalledWith(testState, cardId);
|
||||
expect(dispatch).toHaveBeenCalledWith(requests.updateEmailSettings({
|
||||
courseId,
|
||||
enable: testString,
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,7 +0,0 @@
|
||||
import { StrictDict } from 'utils';
|
||||
|
||||
import app from './app';
|
||||
|
||||
export default StrictDict({
|
||||
app,
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user