feat: Exec Education flag around course card menu and actions (#188)

Co-authored-by: jajjibhai008 <ejazofficial122@gmail.com>
This commit is contained in:
Ben Warzeski
2023-08-15 16:27:32 -04:00
committed by GitHub
parent 86a4573405
commit ce269e8c8f
25 changed files with 1813 additions and 1677 deletions

View File

@@ -8,20 +8,17 @@ import { reduxHooks } from 'hooks';
import useActionDisabledState from '../hooks';
import ActionButton from './ActionButton';
import messages from './messages';
import { useEnterpriseDashboardData } from '../../../../data/redux/hooks/app';
export const BeginCourseButton = ({ cardId }) => {
const { formatMessage } = useIntl();
const { homeUrl } = reduxHooks.useCardCourseRunData(cardId);
const execEdTrackingParam = reduxHooks.useCardExecEdTrackingParam(cardId);
const { disableBeginCourse } = useActionDisabledState(cardId);
const { authOrgId } = useEnterpriseDashboardData();
const { isExecutiveEd2uCourse } = useActionDisabledState(cardId);
const execEdURLParam = `?org_id=${authOrgId}`;
const handleClick = reduxHooks.useTrackCourseEvent(
track.course.enterCourseClicked,
cardId,
homeUrl + ((isExecutiveEd2uCourse && authOrgId) ? execEdURLParam : ''),
homeUrl + execEdTrackingParam,
);
return (
<ActionButton

View File

@@ -14,17 +14,22 @@ jest.mock('tracking', () => ({
jest.mock('hooks', () => ({
reduxHooks: {
useCardCourseRunData: jest.fn(() => ({ homeUrl: 'home-url' })),
useTrackCourseEvent: jest.fn(
(eventName, cardId, upgradeUrl) => ({ trackCourseEvent: { eventName, cardId, upgradeUrl } }),
),
useCardCourseRunData: jest.fn(),
useCardExecEdTrackingParam: jest.fn(),
useTrackCourseEvent: jest.fn(),
},
}));
jest.mock('../hooks', () => jest.fn(() => ({ disableBeginCourse: false })));
jest.mock('./ActionButton', () => 'ActionButton');
let wrapper;
const { homeUrl } = reduxHooks.useCardCourseRunData();
const homeUrl = 'home-url';
reduxHooks.useCardCourseRunData.mockReturnValue({ homeUrl });
const execEdPath = (cardId) => `exec-ed-tracking-path=${cardId}`;
reduxHooks.useCardExecEdTrackingParam.mockImplementation(execEdPath);
reduxHooks.useTrackCourseEvent.mockImplementation(
(eventName, cardId, upgradeUrl) => ({ trackCourseEvent: { eventName, cardId, upgradeUrl } }),
);
describe('BeginCourseButton', () => {
const props = {
@@ -33,27 +38,50 @@ describe('BeginCourseButton', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('snapshot', () => {
test('renders default button when learner has access to the course', () => {
wrapper = shallow(<BeginCourseButton {...props} />);
expect(wrapper).toMatchSnapshot();
expect(wrapper.prop(htmlProps.disabled)).toEqual(false);
expect(wrapper.prop(htmlProps.onClick)).toEqual(reduxHooks.useTrackCourseEvent(
track.course.enterCourseClicked,
props.cardId,
homeUrl,
));
});
});
describe('behavior', () => {
it('initializes course run data with cardId', () => {
wrapper = shallow(<BeginCourseButton {...props} />);
expect(reduxHooks.useCardCourseRunData).toHaveBeenCalledWith(props.cardId);
});
test('disabled states', () => {
useActionDisabledState.mockReturnValueOnce({ disableBeginCourse: true });
it('loads exec education path param', () => {
wrapper = shallow(<BeginCourseButton {...props} />);
expect(wrapper.prop(htmlProps.disabled)).toEqual(true);
expect(reduxHooks.useCardExecEdTrackingParam).toHaveBeenCalledWith(props.cardId);
});
it('loads disabled states for begin action from action hooks', () => {
wrapper = shallow(<BeginCourseButton {...props} />);
expect(useActionDisabledState).toHaveBeenCalledWith(props.cardId);
});
});
describe('snapshot', () => {
describe('disabled', () => {
beforeEach(() => {
useActionDisabledState.mockReturnValueOnce({ disableBeginCourse: true });
wrapper = shallow(<BeginCourseButton {...props} />);
});
test('snapshot', () => {
expect(wrapper).toMatchSnapshot();
});
it('should be disabled', () => {
expect(wrapper.prop(htmlProps.disabled)).toEqual(true);
});
});
describe('enabled', () => {
beforeEach(() => {
wrapper = shallow(<BeginCourseButton {...props} />);
});
test('snapshot', () => {
expect(wrapper).toMatchSnapshot();
});
it('should be enabled', () => {
expect(wrapper.prop(htmlProps.disabled)).toEqual(false);
});
it('should track enter course clicked event on click, with exec ed param', () => {
expect(wrapper.prop(htmlProps.onClick)).toEqual(reduxHooks.useTrackCourseEvent(
track.course.enterCourseClicked,
props.cardId,
homeUrl + execEdPath(props.cardId),
));
});
});
});
});

View File

@@ -8,20 +8,17 @@ import { reduxHooks } from 'hooks';
import useActionDisabledState from '../hooks';
import ActionButton from './ActionButton';
import messages from './messages';
import { useEnterpriseDashboardData } from '../../../../data/redux/hooks/app';
export const ResumeButton = ({ cardId }) => {
const { formatMessage } = useIntl();
const { resumeUrl } = reduxHooks.useCardCourseRunData(cardId);
const execEdTrackingParam = reduxHooks.useCardExecEdTrackingParam(cardId);
const { disableResumeCourse } = useActionDisabledState(cardId);
const { authOrgId } = useEnterpriseDashboardData();
const { isExecutiveEd2uCourse } = useActionDisabledState(cardId);
const execEdURLParam = `?org_id=${authOrgId}`;
const handleClick = reduxHooks.useTrackCourseEvent(
track.course.enterCourseClicked,
cardId,
resumeUrl + ((isExecutiveEd2uCourse && authOrgId) ? execEdURLParam : ''),
resumeUrl + execEdTrackingParam,
);
return (
<ActionButton

View File

@@ -6,52 +6,80 @@ import track from 'tracking';
import useActionDisabledState from '../hooks';
import ResumeButton from './ResumeButton';
jest.mock('tracking', () => ({
course: {
enterCourseClicked: jest.fn().mockName('segment.enterCourseClicked'),
},
}));
jest.mock('hooks', () => ({
reduxHooks: {
useCardCourseRunData: jest.fn(() => ({ resumeUrl: 'resumeUrl' })),
useTrackCourseEvent: (eventName, cardId, url) => jest
.fn()
.mockName(`useTrackCourseEvent('${eventName}', '${cardId}', '${url}')`),
useCardCourseRunData: jest.fn(),
useCardExecEdTrackingParam: jest.fn(),
useTrackCourseEvent: jest.fn(),
},
}));
jest.mock('../hooks', () => jest.fn(() => ({ disableResumeCourse: false })));
jest.mock('tracking', () => ({
course: {
enterCourseClicked: 'enterCourseClicked',
},
}));
jest.mock('./ActionButton', () => 'ActionButton');
const { resumeUrl } = reduxHooks.useCardCourseRunData();
const resumeUrl = 'resume-url';
reduxHooks.useCardCourseRunData.mockReturnValue({ resumeUrl });
const execEdPath = (cardId) => `exec-ed-tracking-path=${cardId}`;
reduxHooks.useCardExecEdTrackingParam.mockImplementation(execEdPath);
reduxHooks.useTrackCourseEvent.mockImplementation(
(eventName, cardId, upgradeUrl) => ({ trackCourseEvent: { eventName, cardId, upgradeUrl } }),
);
let wrapper;
describe('ResumeButton', () => {
const props = {
cardId: 'cardId',
};
describe('snapshot', () => {
test('renders default button when learner has access to the course', () => {
const wrapper = shallow(<ResumeButton {...props} />);
expect(wrapper).toMatchSnapshot();
expect(wrapper.prop(htmlProps.disabled)).toEqual(false);
expect(wrapper.prop(htmlProps.onClick).getMockName()).toContain(
'useTrackCourseEvent',
track.course.enterCourseClicked,
props.cardId,
resumeUrl,
);
describe('behavior', () => {
it('initializes course run data with cardId', () => {
wrapper = shallow(<ResumeButton {...props} />);
expect(reduxHooks.useCardCourseRunData).toHaveBeenCalledWith(props.cardId);
});
it('loads exec education path param', () => {
wrapper = shallow(<ResumeButton {...props} />);
expect(reduxHooks.useCardExecEdTrackingParam).toHaveBeenCalledWith(props.cardId);
});
it('loads disabled states for resume action from action hooks', () => {
wrapper = shallow(<ResumeButton {...props} />);
expect(useActionDisabledState).toHaveBeenCalledWith(props.cardId);
});
});
describe('behavior', () => {
it('initializes course run data based on cardId', () => {
shallow(<ResumeButton {...props} />);
expect(reduxHooks.useCardCourseRunData).toHaveBeenCalledWith(
props.cardId,
);
describe('snapshot', () => {
describe('disabled', () => {
beforeEach(() => {
useActionDisabledState.mockReturnValueOnce({ disableResumeCourse: true });
wrapper = shallow(<ResumeButton {...props} />);
});
test('snapshot', () => {
expect(wrapper).toMatchSnapshot();
});
it('should be disabled', () => {
expect(wrapper.prop(htmlProps.disabled)).toEqual(true);
});
});
test('disabled states', () => {
useActionDisabledState.mockReturnValueOnce({ disableResumeCourse: true });
const wrapper = shallow(<ResumeButton {...props} />);
expect(wrapper.prop(htmlProps.disabled)).toEqual(true);
describe('enabled', () => {
beforeEach(() => {
wrapper = shallow(<ResumeButton {...props} />);
});
test('snapshot', () => {
expect(wrapper).toMatchSnapshot();
});
it('should be enabled', () => {
expect(wrapper.prop(htmlProps.disabled)).toEqual(false);
});
it('should track enter course clicked event on click, with exec ed param', () => {
expect(wrapper.prop(htmlProps.onClick)).toEqual(reduxHooks.useTrackCourseEvent(
track.course.enterCourseClicked,
props.cardId,
resumeUrl + execEdPath(props.cardId),
));
});
});
});
});

View File

@@ -1,6 +1,25 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`BeginCourseButton snapshot renders default button when learner has access to the course 1`] = `
exports[`BeginCourseButton snapshot disabled snapshot 1`] = `
<ActionButton
as="a"
disabled={true}
href="#"
onClick={
Object {
"trackCourseEvent": Object {
"cardId": "cardId",
"eventName": [MockFunction segment.enterCourseClicked],
"upgradeUrl": "home-urlexec-ed-tracking-path=cardId",
},
}
}
>
Begin Course
</ActionButton>
`;
exports[`BeginCourseButton snapshot enabled snapshot 1`] = `
<ActionButton
as="a"
disabled={false}
@@ -10,7 +29,7 @@ exports[`BeginCourseButton snapshot renders default button when learner has acce
"trackCourseEvent": Object {
"cardId": "cardId",
"eventName": [MockFunction segment.enterCourseClicked],
"upgradeUrl": "home-url",
"upgradeUrl": "home-urlexec-ed-tracking-path=cardId",
},
}
}

View File

@@ -1,11 +1,38 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ResumeButton snapshot renders default button when learner has access to the course 1`] = `
exports[`ResumeButton snapshot disabled snapshot 1`] = `
<ActionButton
as="a"
disabled={false}
disabled={true}
href="#"
onClick={[MockFunction useTrackCourseEvent('enterCourseClicked', 'cardId', 'resumeUrl')]}
onClick={
Object {
"trackCourseEvent": Object {
"cardId": "cardId",
"eventName": [MockFunction segment.enterCourseClicked],
"upgradeUrl": "resume-urlexec-ed-tracking-path=cardId",
},
}
}
>
Resume
</ActionButton>
`;
exports[`ResumeButton snapshot enabled snapshot 1`] = `
<ActionButton
as="a"
disabled={false}
href="#"
onClick={
Object {
"trackCourseEvent": Object {
"cardId": "cardId",
"eventName": [MockFunction segment.enterCourseClicked],
"upgradeUrl": "resume-urlexec-ed-tracking-path=cardId",
},
}
}
>
Resume
</ActionButton>

View File

@@ -1,54 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CourseCardActions snapshot show begin course button when verified and not entitlement and has started 1`] = `
<ActionRow
data-test-id="CourseCardActions"
>
<BeginCourseButton
cardId="cardId"
/>
</ActionRow>
`;
exports[`CourseCardActions snapshot show resume button when verified and not entitlement and has started 1`] = `
<ActionRow
data-test-id="CourseCardActions"
>
<ResumeButton
cardId="cardId"
/>
</ActionRow>
`;
exports[`CourseCardActions snapshot show select session button when not verified and entitlement 1`] = `
<ActionRow
data-test-id="CourseCardActions"
>
<SelectSessionButton
cardId="cardId"
/>
</ActionRow>
`;
exports[`CourseCardActions snapshot show upgrade button when not verified and not entitlement 1`] = `
<ActionRow
data-test-id="CourseCardActions"
>
<UpgradeButton
cardId="cardId"
/>
<BeginCourseButton
cardId="cardId"
/>
</ActionRow>
`;
exports[`CourseCardActions snapshot show view course button when not verified and entitlement and fulfilled 1`] = `
<ActionRow
data-test-id="CourseCardActions"
>
<ViewCourseButton
cardId="cardId"
/>
</ActionRow>
`;

View File

@@ -10,27 +10,30 @@ import SelectSessionButton from './SelectSessionButton';
import BeginCourseButton from './BeginCourseButton';
import ResumeButton from './ResumeButton';
import ViewCourseButton from './ViewCourseButton';
import useActionDisabledState from '../hooks';
export const CourseCardActions = ({ cardId }) => {
const { isEntitlement, isFulfilled } = reduxHooks.useCardEntitlementData(cardId);
const { isVerified, hasStarted } = reduxHooks.useCardEnrollmentData(cardId);
const {
isVerified,
hasStarted,
isExecEd2UCourse,
} = reduxHooks.useCardEnrollmentData(cardId);
const { isArchived } = reduxHooks.useCardCourseRunData(cardId);
const { isExecutiveEd2uCourse } = useActionDisabledState(cardId);
let PrimaryButton;
if (isEntitlement) {
PrimaryButton = isFulfilled ? ViewCourseButton : SelectSessionButton;
} else if (isArchived) {
PrimaryButton = ViewCourseButton;
} else {
PrimaryButton = hasStarted ? ResumeButton : BeginCourseButton;
}
return (
<ActionRow data-test-id="CourseCardActions">
{(!(isEntitlement || isVerified) && !isExecutiveEd2uCourse) && <UpgradeButton cardId={cardId} />}
<PrimaryButton cardId={cardId} />
{!(isEntitlement || isVerified || isExecEd2UCourse) && <UpgradeButton cardId={cardId} />}
{isEntitlement && (isFulfilled
? <ViewCourseButton cardId={cardId} />
: <SelectSessionButton cardId={cardId} />
)}
{(isArchived && !isEntitlement) && (
<ViewCourseButton cardId={cardId} />
)}
{!(isArchived || isEntitlement) && (hasStarted
? <ResumeButton cardId={cardId} />
: <BeginCourseButton cardId={cardId} />
)}
</ActionRow>
);
};

View File

@@ -1,7 +1,13 @@
import { shallow } from 'enzyme';
import { shallow } from '@edx/react-unit-test-utils';
import { reduxHooks } from 'hooks';
import UpgradeButton from './UpgradeButton';
import SelectSessionButton from './SelectSessionButton';
import BeginCourseButton from './BeginCourseButton';
import ResumeButton from './ResumeButton';
import ViewCourseButton from './ViewCourseButton';
import CourseCardActions from '.';
jest.mock('hooks', () => ({
@@ -13,96 +19,98 @@ jest.mock('hooks', () => ({
},
}));
jest.mock('../hooks', () => jest.fn(() => ({ isExecutiveEd2uCourse: false })));
jest.mock('./UpgradeButton', () => 'UpgradeButton');
jest.mock('./SelectSessionButton', () => 'SelectSessionButton');
jest.mock('./ViewCourseButton', () => 'ViewCourseButton');
jest.mock('./BeginCourseButton', () => 'BeginCourseButton');
jest.mock('./ResumeButton', () => 'ResumeButton');
const cardId = 'test-card-id';
const props = { cardId };
let el;
describe('CourseCardActions', () => {
const props = {
cardId: 'cardId',
};
const createWrapper = ({
isEntitlement, isFulfilled, isArchived, isVerified, hasStarted, isMasquerading,
}) => {
const mockHooks = ({
isEntitlement = false,
isExecEd2UCourse = false,
isFulfilled = false,
isArchived = false,
isVerified = false,
hasStarted = false,
isMasquerading = false,
} = {}) => {
reduxHooks.useCardEntitlementData.mockReturnValueOnce({ isEntitlement, isFulfilled });
reduxHooks.useCardCourseRunData.mockReturnValueOnce({ isArchived });
reduxHooks.useCardEnrollmentData.mockReturnValueOnce({ isVerified, hasStarted });
reduxHooks.useCardEnrollmentData.mockReturnValueOnce({ isExecEd2UCourse, isVerified, hasStarted });
reduxHooks.useMasqueradeData.mockReturnValueOnce({ isMasquerading });
return shallow(<CourseCardActions {...props} />);
};
describe('snapshot', () => {
test('show upgrade button when not verified and not entitlement', () => {
const wrapper = createWrapper({
isEntitlement: false, isFulfilled: false, isArchived: false, isVerified: false, hasStarted: false,
});
expect(wrapper).toMatchSnapshot();
});
test('show select session button when not verified and entitlement', () => {
const wrapper = createWrapper({
isEntitlement: true, isFulfilled: false, isArchived: false, isVerified: false, hasStarted: false,
});
expect(wrapper).toMatchSnapshot();
});
test('show begin course button when verified and not entitlement and has started', () => {
const wrapper = createWrapper({
isEntitlement: false, isFulfilled: false, isArchived: false, isVerified: true, hasStarted: false,
});
expect(wrapper).toMatchSnapshot();
});
test('show resume button when verified and not entitlement and has started', () => {
const wrapper = createWrapper({
isEntitlement: false, isFulfilled: false, isArchived: false, isVerified: true, hasStarted: true,
});
expect(wrapper).toMatchSnapshot();
});
test('show view course button when not verified and entitlement and fulfilled', () => {
const wrapper = createWrapper({
isEntitlement: true, isFulfilled: true, isArchived: false, isVerified: false, hasStarted: false,
});
expect(wrapper).toMatchSnapshot();
const render = () => {
el = shallow(<CourseCardActions {...props} />);
};
describe('behavior', () => {
it('initializes redux hooks', () => {
mockHooks();
render();
expect(reduxHooks.useCardEntitlementData).toHaveBeenCalledWith(cardId);
expect(reduxHooks.useCardEnrollmentData).toHaveBeenCalledWith(cardId);
expect(reduxHooks.useCardCourseRunData).toHaveBeenCalledWith(cardId);
});
});
describe('behavior', () => {
it('show upgrade button when not verified and not entitlement', () => {
const wrapper = createWrapper({
isEntitlement: false, isFulfilled: false, isArchived: false, isVerified: false, hasStarted: false,
describe('output', () => {
describe('Exec Ed course', () => {
it('does not render upgrade button', () => {
mockHooks({ isExecEd2UCourse: true });
render();
expect(el.instance.findByType(UpgradeButton).length).toEqual(0);
});
expect(wrapper.find('UpgradeButton')).toHaveLength(1);
});
it('show select session button when not verified and entitlement', () => {
const wrapper = createWrapper({
isEntitlement: true, isFulfilled: false, isArchived: false, isVerified: false, hasStarted: false,
describe('entitlement course', () => {
it('does not render upgrade button', () => {
mockHooks({ isEntitlement: true });
render();
expect(el.instance.findByType(UpgradeButton).length).toEqual(0);
});
it('renders ViewCourseButton if fulfilled', () => {
mockHooks({ isEntitlement: true, isFulfilled: true });
render();
expect(el.instance.findByType(ViewCourseButton)[0].props.cardId).toEqual(cardId);
});
it('renders SelectSessionButton if not fulfilled', () => {
mockHooks({ isEntitlement: true });
render();
expect(el.instance.findByType(SelectSessionButton)[0].props.cardId).toEqual(cardId);
});
expect(wrapper.find('SelectSessionButton')).toHaveLength(1);
});
it('show begin course button when verified and not entitlement and has started', () => {
const wrapper = createWrapper({
isEntitlement: false, isFulfilled: false, isArchived: false, isVerified: true, hasStarted: false,
describe('verified course', () => {
it('does not render upgrade button', () => {
mockHooks({ isVerified: true });
render();
expect(el.instance.findByType(UpgradeButton).length).toEqual(0);
});
expect(wrapper.find('BeginCourseButton')).toHaveLength(1);
});
it('show resume button when verified and not entitlement and has started', () => {
const wrapper = createWrapper({
isEntitlement: false, isFulfilled: false, isArchived: false, isVerified: true, hasStarted: true,
describe('not entielement, verified, or exec ed', () => {
it('renders UpgradeButton and ViewCourseButton for archived courses', () => {
mockHooks({ isArchived: true });
render();
expect(el.instance.findByType(UpgradeButton)[0].props.cardId).toEqual(cardId);
expect(el.instance.findByType(ViewCourseButton)[0].props.cardId).toEqual(cardId);
});
expect(wrapper.find('ResumeButton')).toHaveLength(1);
});
it('show view course button when not verified and entitlement and fulfilled', () => {
const wrapper = createWrapper({
isEntitlement: true, isFulfilled: true, isArchived: false, isVerified: false, hasStarted: false,
describe('unstarted courses', () => {
it('renders UpgradeButton and BeginCourseButton', () => {
mockHooks();
render();
expect(el.instance.findByType(UpgradeButton)[0].props.cardId).toEqual(cardId);
expect(el.instance.findByType(BeginCourseButton)[0].props.cardId).toEqual(cardId);
});
});
expect(wrapper.find('ViewCourseButton')).toHaveLength(1);
});
it('show view course button when not verified and entitlement and fulfilled and archived', () => {
const wrapper = createWrapper({
isEntitlement: true, isFulfilled: true, isArchived: true, isVerified: false, hasStarted: false,
describe('active courses (started, and not archived)', () => {
it('renders UpgradeButton and ResumeButton', () => {
mockHooks({ hasStarted: true });
render();
expect(el.instance.findByType(UpgradeButton)[0].props.cardId).toEqual(cardId);
expect(el.instance.findByType(ResumeButton)[0].props.cardId).toEqual(cardId);
});
});
expect(wrapper.find('ViewCourseButton')).toHaveLength(1);
});
});
});

View File

@@ -0,0 +1,82 @@
import React from 'react';
import PropTypes from 'prop-types';
import * as ReactShare from 'react-share';
import { StrictDict } from '@edx/react-unit-test-utils';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Dropdown } from '@edx/paragon';
import track from 'tracking';
import { reduxHooks } from 'hooks';
import { useEmailSettings } from './hooks';
import messages from './messages';
export const testIds = StrictDict({
emailSettingsModalToggle: 'emailSettingsModalToggle',
});
export const SocialShareMenu = ({ cardId }) => {
const { formatMessage } = useIntl();
const emailSettingsModal = useEmailSettings();
const { courseName } = reduxHooks.useCardCourseData(cardId);
const { isEmailEnabled, isExecEd2UCourse } = reduxHooks.useCardEnrollmentData(cardId);
const { twitter, facebook } = reduxHooks.useCardSocialSettingsData(cardId);
const { isMasquerading } = reduxHooks.useMasqueradeData();
const handleTwitterShare = reduxHooks.useTrackCourseEvent(track.socialShare, cardId, 'twitter');
const handleFacebookShare = reduxHooks.useTrackCourseEvent(track.socialShare, cardId, 'facebook');
if (isExecEd2UCourse) {
return null;
}
return (
<>
{isEmailEnabled && (
<Dropdown.Item
disabled={isMasquerading}
onClick={emailSettingsModal.show}
data-testid={testIds.emailSettingsModalToggle}
>
{formatMessage(messages.emailSettings)}
</Dropdown.Item>
)}
{facebook.isEnabled && (
<ReactShare.FacebookShareButton
url={facebook.shareUrl}
onClick={handleFacebookShare}
title={formatMessage(messages.shareQuote, {
courseName,
socialBrand: facebook.socialBrand,
})}
resetButtonStyle={false}
className="pgn__dropdown-item dropdown-item"
>
{formatMessage(messages.shareToFacebook)}
</ReactShare.FacebookShareButton>
)}
{twitter.isEnabled && (
<ReactShare.TwitterShareButton
url={twitter.shareUrl}
onClick={handleTwitterShare}
title={formatMessage(messages.shareQuote, {
courseName,
socialBrand: twitter.socialBrand,
})}
resetButtonStyle={false}
className="pgn__dropdown-item dropdown-item"
>
{formatMessage(messages.shareToTwitter)}
</ReactShare.TwitterShareButton>
)}
</>
);
};
SocialShareMenu.propTypes = {
cardId: PropTypes.string.isRequired,
};
export default SocialShareMenu;

View File

@@ -0,0 +1,239 @@
import { when } from 'jest-when';
import * as ReactShare from 'react-share';
import { useIntl } from '@edx/frontend-platform/i18n';
import { formatMessage, shallow } from '@edx/react-unit-test-utils';
import track from 'tracking';
import { reduxHooks } from 'hooks';
import { useEmailSettings } from './hooks';
import SocialShareMenu, { testIds } from './SocialShareMenu';
import messages from './messages';
jest.mock('react-share', () => ({
FacebookShareButton: () => 'FacebookShareButton',
TwitterShareButton: () => 'TwitterShareButton',
}));
jest.mock('tracking', () => ({
socialShare: 'test-social-share-key',
}));
jest.mock('@edx/frontend-platform/i18n', () => ({
useIntl: jest.fn().mockReturnValue({
formatMessage: jest.requireActual('@edx/react-unit-test-utils').formatMessage,
}),
}));
jest.mock('hooks', () => ({
reduxHooks: {
useMasqueradeData: jest.fn(),
useCardCourseData: jest.fn(),
useCardEnrollmentData: jest.fn(),
useCardSocialSettingsData: jest.fn(),
useTrackCourseEvent: jest.fn((...args) => ({ trackCourseEvent: args })),
},
}));
jest.mock('./hooks', () => ({
useEmailSettings: jest.fn(),
}));
const emailSettings = {
isVisible: false,
show: jest.fn().mockName('emailSettingShow'),
hide: jest.fn().mockName('emailSettingHide'),
};
const props = { cardId: 'test-card-id' };
const mockHook = (fn, returnValue, options = {}) => {
if (options.isCardHook) {
when(fn).calledWith(props.cardId).mockReturnValueOnce(returnValue);
} else {
when(fn).calledWith().mockReturnValueOnce(returnValue);
}
};
const courseName = 'test-course-name';
const socialShare = {
facebook: {
isEnabled: true,
shareUrl: 'facebook-share-url',
socialBrand: 'facebook-social-brand',
},
twitter: {
isEnabled: true,
shareUrl: 'twitter-share-url',
socialBrand: 'twitter-social-brand',
},
};
const mockHooks = (returnVals = {}) => {
mockHook(useEmailSettings, emailSettings);
mockHook(
reduxHooks.useCardEnrollmentData,
{
isEmailEnabled: !!returnVals.isEmailEnabled,
isExecEd2UCourse: !!returnVals.isExecEd2UCourse,
},
{ isCardHook: true },
);
mockHook(reduxHooks.useCardCourseData, { courseName }, { isCardHook: true });
mockHook(reduxHooks.useMasqueradeData, { isMasquerading: !!returnVals.isMasquerading });
mockHook(
reduxHooks.useCardSocialSettingsData,
{
facebook: { ...socialShare.facebook, isEnabled: !!returnVals.facebook?.isEnabled },
twitter: { ...socialShare.twitter, isEnabled: !!returnVals.twitter?.isEnabled },
},
{ isCardHook: true },
);
};
let el;
const render = () => {
el = shallow(<SocialShareMenu {...props} />);
};
describe('SocialShareMenu', () => {
describe('behavior', () => {
beforeEach(() => {
mockHooks();
render();
});
it('initializes intl hook', () => {
expect(useIntl).toHaveBeenCalledWith();
});
it('initializes local hooks', () => {
when(useEmailSettings).expectCalledWith();
});
it('initializes redux hook data ', () => {
when(reduxHooks.useCardEnrollmentData).expectCalledWith(props.cardId);
when(reduxHooks.useCardCourseData).expectCalledWith(props.cardId);
when(reduxHooks.useCardSocialSettingsData).expectCalledWith(props.cardId);
when(reduxHooks.useMasqueradeData).expectCalledWith();
when(reduxHooks.useTrackCourseEvent).expectCalledWith(track.socialShare, props.cardId, 'twitter');
when(reduxHooks.useTrackCourseEvent).expectCalledWith(track.socialShare, props.cardId, 'facebook');
});
});
describe('render', () => {
it('renders null if exec ed course', () => {
mockHooks({ isExecEd2UCourse: true });
render();
expect(el.isEmptyRender()).toEqual(true);
});
const testEmailSettingsDropdown = (isMasquerading = false) => {
describe('email settings dropdown', () => {
const loadToggle = () => el.instance.findByTestId(testIds.emailSettingsModalToggle)[0];
it('renders', () => {
expect(el.instance.findByTestId(testIds.emailSettingsModalToggle).length).toEqual(1);
});
if (isMasquerading) {
it('is disabled', () => {
expect(loadToggle().props.disabled).toEqual(true);
});
} else {
it('is enabled', () => {
expect(loadToggle().props.disabled).toEqual(false);
});
}
test('show email settings modal on click', () => {
expect(loadToggle().props.onClick).toEqual(emailSettings.show);
});
});
};
const testFacebookShareButton = () => {
test('renders facebook share button with courseName and brand', () => {
const button = el.instance.findByType(ReactShare.FacebookShareButton)[0];
expect(button.props.url).toEqual(socialShare.facebook.shareUrl);
expect(button.props.onClick).toEqual(
reduxHooks.useTrackCourseEvent(track.socialShare, props.cardId, 'facebook'),
);
expect(button.props.title).toEqual(formatMessage(messages.shareQuote, {
courseName,
socialBrand: socialShare.facebook.socialBrand,
}));
});
};
const testTwitterShareButton = () => {
test('renders twitter share button with courseName and brand', () => {
const button = el.instance.findByType(ReactShare.TwitterShareButton)[0];
expect(button.props.url).toEqual(socialShare.twitter.shareUrl);
expect(button.props.onClick).toEqual(
reduxHooks.useTrackCourseEvent(track.socialShare, props.cardId, 'twitter'),
);
expect(button.props.title).toEqual(formatMessage(messages.shareQuote, {
courseName,
socialBrand: socialShare.twitter.socialBrand,
}));
});
};
describe('all enabled', () => {
beforeEach(() => {
mockHooks({
facebook: { isEnabled: true },
twitter: { isEnabled: true },
isEmailEnabled: true,
});
render();
});
describe('email settings dropdown', () => {
const loadToggle = () => el.instance.findByTestId(testIds.emailSettingsModalToggle)[0];
it('renders', () => {
expect(el.instance.findByTestId(testIds.emailSettingsModalToggle).length).toEqual(1);
});
it('is enabled', () => {
expect(loadToggle().props.disabled).toEqual(false);
});
test('show email settings modal on click', () => {
expect(loadToggle().props.onClick).toEqual(emailSettings.show);
});
});
testEmailSettingsDropdown();
testFacebookShareButton();
testTwitterShareButton();
});
describe('only email enabled', () => {
beforeEach(() => {
mockHooks({ isEmailEnabled: true });
render();
});
testEmailSettingsDropdown();
it('does not render facebook or twitter controls', () => {
expect(el.instance.findByType(ReactShare.FacebookShareButton).length).toEqual(0);
expect(el.instance.findByType(ReactShare.TwitterShareButton).length).toEqual(0);
});
describe('masquerading', () => {
beforeEach(() => {
mockHooks({ isEmailEnabled: true, isMasquerading: true });
render();
});
testEmailSettingsDropdown(true);
});
});
describe('only facebook enabled', () => {
beforeEach(() => {
mockHooks({ facebook: { isEnabled: true } });
render();
});
testFacebookShareButton();
it('does not render email or twitter controls', () => {
expect(el.instance.findByTestId(testIds.emailSettingsModalToggle).length).toEqual(0);
expect(el.instance.findByType(ReactShare.TwitterShareButton).length).toEqual(0);
});
});
describe('only twitter enabled', () => {
beforeEach(() => {
mockHooks({ twitter: { isEnabled: true } });
render();
});
testTwitterShareButton();
it('does not render email or facebook controls', () => {
expect(el.instance.findByTestId(testIds.emailSettingsModalToggle).length).toEqual(0);
expect(el.instance.findByType(ReactShare.FacebookShareButton).length).toEqual(0);
});
});
});
});

View File

@@ -1,9 +1,36 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CourseCardMenu default snapshot 1`] = `
exports[`CourseCardMenu render show dropdown hide unenroll item and disable email snapshot 1`] = `
<Fragment>
<Dropdown
onToggle={[MockFunction mockHandleToggleDropdown]}
onToggle={[MockFunction hooks.handleToggleDropdown]}
>
<Dropdown.Toggle
alt="Course actions dropdown"
as="IconButton"
iconAs="Icon"
id="course-actions-dropdown-test-card-id"
src={[MockFunction icons.MoreVert]}
variant="primary"
/>
<Dropdown.Menu>
<SocialShareMenu
cardId="test-card-id"
/>
</Dropdown.Menu>
</Dropdown>
<UnenrollConfirmModal
cardId="test-card-id"
closeModal={[MockFunction unenrollHide]}
show={false}
/>
</Fragment>
`;
exports[`CourseCardMenu render show dropdown show unenroll and enable email snapshot 1`] = `
<Fragment>
<Dropdown
onToggle={[MockFunction hooks.handleToggleDropdown]}
>
<Dropdown.Toggle
alt="Course actions dropdown"
@@ -21,31 +48,9 @@ exports[`CourseCardMenu default snapshot 1`] = `
>
Unenroll
</Dropdown.Item>
<Dropdown.Item
data-testid="emailSettingsModalToggle"
disabled={false}
onClick={[MockFunction emailSettingShow]}
>
Email settings
</Dropdown.Item>
<FacebookShareButton
className="pgn__dropdown-item dropdown-item"
onClick={[MockFunction handleFacebookShare]}
resetButtonStyle={false}
title="I'm taking test-course-name online with facebook-social-brand. Check it out!"
url="facebook-share-url"
>
Share to Facebook
</FacebookShareButton>
<TwitterShareButton
className="pgn__dropdown-item dropdown-item"
onClick={[MockFunction handleTwitterShare]}
resetButtonStyle={false}
title="I'm taking test-course-name online with twitter-social-brand. Check it out!"
url="twitter-share-url"
>
Share to Twitter
</TwitterShareButton>
<SocialShareMenu
cardId="test-card-id"
/>
</Dropdown.Menu>
</Dropdown>
<UnenrollConfirmModal
@@ -60,66 +65,3 @@ exports[`CourseCardMenu default snapshot 1`] = `
/>
</Fragment>
`;
exports[`CourseCardMenu masquerading snapshot 1`] = `
<Fragment>
<Dropdown
onToggle={[MockFunction mockHandleToggleDropdown]}
>
<Dropdown.Toggle
alt="Course actions dropdown"
as="IconButton"
iconAs="Icon"
id="course-actions-dropdown-test-card-id"
src={[MockFunction icons.MoreVert]}
variant="primary"
/>
<Dropdown.Menu>
<Dropdown.Item
data-testid="unenrollModalToggle"
disabled={true}
onClick={[MockFunction unenrollShow]}
>
Unenroll
</Dropdown.Item>
<Dropdown.Item
data-testid="emailSettingsModalToggle"
disabled={true}
onClick={[MockFunction emailSettingShow]}
>
Email settings
</Dropdown.Item>
<FacebookShareButton
className="pgn__dropdown-item dropdown-item"
onClick={[MockFunction handleFacebookShare]}
resetButtonStyle={false}
title="I'm taking test-course-name online with facebook-social-brand. Check it out!"
url="facebook-share-url"
>
Share to Facebook
</FacebookShareButton>
<TwitterShareButton
className="pgn__dropdown-item dropdown-item"
onClick={[MockFunction handleTwitterShare]}
resetButtonStyle={false}
title="I'm taking test-course-name online with twitter-social-brand. Check it out!"
url="twitter-share-url"
>
Share to Twitter
</TwitterShareButton>
</Dropdown.Menu>
</Dropdown>
<UnenrollConfirmModal
cardId="test-card-id"
closeModal={[MockFunction unenrollHide]}
show={false}
/>
<EmailSettingsModal
cardId="test-card-id"
closeModal={[MockFunction emailSettingHide]}
show={false}
/>
</Fragment>
`;
exports[`CourseCardMenu renders null if showDropdown is false 1`] = `""`;

View File

@@ -1,18 +1,15 @@
import React from 'react';
import { StrictDict } from 'utils';
import { useKeyedState, StrictDict } from '@edx/react-unit-test-utils';
import track from 'tracking';
import { reduxHooks } from 'hooks';
import * as module from './hooks';
export const state = StrictDict({
isUnenrollConfirmVisible: (val) => React.useState(val), // eslint-disable-line
isEmailSettingsVisible: (val) => React.useState(val), // eslint-disable-line
export const stateKeys = StrictDict({
isUnenrollConfirmVisible: 'isUnenrollConfirmVisible',
isEmailSettingsVisible: 'isEmailSettingsVisible',
});
export const useUnenrollData = () => {
const [isVisible, setIsVisible] = module.state.isUnenrollConfirmVisible(false);
const [isVisible, setIsVisible] = useKeyedState(stateKeys.isUnenrollConfirmVisible, false);
return {
show: () => setIsVisible(true),
hide: () => setIsVisible(false),
@@ -21,7 +18,7 @@ export const useUnenrollData = () => {
};
export const useEmailSettings = () => {
const [isVisible, setIsVisible] = module.state.isEmailSettingsVisible(false);
const [isVisible, setIsVisible] = useKeyedState(stateKeys.isEmailSettingsVisible, false);
return {
show: () => setIsVisible(true),
hide: () => setIsVisible(false),
@@ -30,42 +27,30 @@ export const useEmailSettings = () => {
};
export const useHandleToggleDropdown = (cardId) => {
const eventName = track.course.courseOptionsDropdownClicked;
const trackCourseEvent = reduxHooks.useTrackCourseEvent(eventName, cardId);
const trackCourseEvent = reduxHooks.useTrackCourseEvent(
track.course.courseOptionsDropdownClicked,
cardId,
);
return (isOpen) => {
if (isOpen) { trackCourseEvent(); }
};
};
export const useCourseCardMenu = (cardId) => {
const { courseName } = reduxHooks.useCardCourseData(cardId);
export const useOptionVisibility = (cardId) => {
const { isEnrolled, isEmailEnabled } = reduxHooks.useCardEnrollmentData(cardId);
const { twitter, facebook } = reduxHooks.useCardSocialSettingsData(cardId);
const { isMasquerading } = reduxHooks.useMasqueradeData();
const { isEarned } = reduxHooks.useCardCertificateData(cardId);
const handleTwitterShare = reduxHooks.useTrackCourseEvent(
track.socialShare,
cardId,
'twitter',
);
const handleFacebookShare = reduxHooks.useTrackCourseEvent(
track.socialShare,
cardId,
'facebook',
);
const showUnenrollItem = isEnrolled && !isEarned;
const showDropdown = showUnenrollItem || isEmailEnabled || facebook.isEnabled || twitter.isEnabled;
const shouldShowUnenrollItem = isEnrolled && !isEarned;
const shouldShowDropdown = (
shouldShowUnenrollItem
|| isEmailEnabled
|| facebook.isEnabled
|| twitter.isEnabled
);
return {
courseName,
isMasquerading,
isEmailEnabled,
showUnenrollItem,
showDropdown,
facebook,
twitter,
handleTwitterShare,
handleFacebookShare,
shouldShowUnenrollItem,
shouldShowDropdown,
};
};

View File

@@ -1,4 +1,5 @@
import { MockUseState } from 'testUtils';
import { mockUseKeyedState } from '@edx/react-unit-test-utils';
import { reduxHooks } from 'hooks';
import track from 'tracking';
@@ -6,88 +7,77 @@ import * as hooks from './hooks';
jest.mock('hooks', () => ({
reduxHooks: {
useTrackCourseEvent: jest.fn(),
useCardCourseData: jest.fn(),
useCardCertificateData: jest.fn(),
useCardEnrollmentData: jest.fn(),
useCardSocialSettingsData: jest.fn(),
useMasqueradeData: jest.fn(),
useCardCertificateData: jest.fn(),
useTrackCourseEvent: jest.fn(),
},
}));
const trackCourseEvent = jest.fn();
reduxHooks.useTrackCourseEvent.mockReturnValue(trackCourseEvent);
const state = new MockUseState(hooks);
const defaultSocialShare = {
facebook: {
isEnabled: true,
shareUrl: 'facebook-share-url',
socialBrand: 'facebook-social-brand',
},
twitter: {
isEnabled: true,
shareUrl: 'twitter-share-url',
socialBrand: 'twitter-social-brand',
},
};
const cardId = 'test-card-id';
let out;
describe('CourseCardMenu hooks', () => {
describe('state values', () => {
state.testGetter(state.keys.isUnenrollConfirmVisible);
state.testGetter(state.keys.isEmailSettingsVisible);
});
const state = mockUseKeyedState(hooks.stateKeys);
describe('CourseCardMenu hooks', () => {
beforeEach(() => {
jest.clearAllMocks();
state.mock();
});
describe('useUnenrollData', () => {
beforeEach(() => {
state.mock();
state.mockVals({ isUnenrollConfirmVisible: true });
out = hooks.useUnenrollData();
});
afterEach(state.restore);
test('default state', () => {
expect(out.isVisible).toEqual(state.stateVals.isUnenrollConfirmVisible);
describe('behavior', () => {
it('initializes isUnenrollConfirmVisible state to false', () => {
state.expectInitializedWith(state.keys.isUnenrollConfirmVisible, false);
});
});
test('show', () => {
out.show();
state.expectSetStateCalledWith(state.keys.isUnenrollConfirmVisible, true);
});
test('hide', () => {
out.hide();
state.expectSetStateCalledWith(state.keys.isUnenrollConfirmVisible, false);
describe('output', () => {
test('state is loaded from current state value', () => {
expect(out.isVisible).toEqual(true);
});
test('show sets state value to true', () => {
out.show();
expect(state.setState.isUnenrollConfirmVisible).toHaveBeenCalledWith(true);
});
test('hide sets state value to false', () => {
out.hide();
expect(state.setState.isUnenrollConfirmVisible).toHaveBeenCalledWith(false);
});
});
});
describe('useEmailSettings', () => {
beforeEach(() => {
state.mock();
state.mockVals({ isEmailSettingsVisible: true });
out = hooks.useEmailSettings();
});
afterEach(state.restore);
test('default state', () => {
expect(out.isVisible).toEqual(state.stateVals.isEmailSettingsVisible);
describe('behavior', () => {
it('initializes isEmailSettingsVisible state to false', () => {
state.expectInitializedWith(state.keys.isEmailSettingsVisible, false);
});
});
test('show', () => {
out.show();
state.expectSetStateCalledWith(state.keys.isEmailSettingsVisible, true);
});
test('hide', () => {
out.hide();
state.expectSetStateCalledWith(state.keys.isEmailSettingsVisible, false);
describe('output', () => {
test('state is loaded from current state value', () => {
expect(out.isVisible).toEqual(state.values.isEmailSettingsVisible);
});
test('show sets state value to true', () => {
out.show();
expect(state.setState.isEmailSettingsVisible).toHaveBeenCalledWith(true);
});
test('hide sets state value to false', () => {
out.hide();
expect(state.setState.isEmailSettingsVisible).toHaveBeenCalledWith(false);
});
});
});
describe('useHandleToggleDropdown', () => {
beforeEach(() => {
out = hooks.useHandleToggleDropdown(cardId);
});
beforeEach(() => { out = hooks.useHandleToggleDropdown(cardId); });
describe('behavior', () => {
it('initializes course event tracker with event name and card ID', () => {
expect(reduxHooks.useTrackCourseEvent).toHaveBeenCalledWith(
@@ -106,115 +96,58 @@ describe('CourseCardMenu hooks', () => {
});
});
describe('useCourseCardMenu', () => {
const mockUseCourseCardMenu = ({
courseName,
isEnrolled,
isEmailEnabled,
isMasquerading,
facebook,
twitter,
isEarned,
} = {}) => {
reduxHooks.useCardCourseData.mockReturnValueOnce({ courseName });
describe('useOptionVisibility', () => {
const mockReduxHooks = (returnVals = {}) => {
reduxHooks.useCardSocialSettingsData.mockReturnValueOnce({
facebook: {
...defaultSocialShare.facebook,
...facebook,
},
twitter: {
...defaultSocialShare.twitter,
...twitter,
},
facebook: { isEnabled: !!returnVals.facebook?.isEnabled },
twitter: { isEnabled: !!returnVals.twitter?.isEnabled },
});
reduxHooks.useCardEnrollmentData.mockReturnValueOnce({
isEnrolled,
isEmailEnabled,
isEnrolled: !!returnVals.isEnrolled,
isEmailEnabled: !!returnVals.isEmailEnabled,
});
reduxHooks.useCardCertificateData.mockReturnValueOnce({
isEarned: !!returnVals.isEarned,
});
reduxHooks.useMasqueradeData.mockReturnValueOnce({ isMasquerading });
reduxHooks.useCardCertificateData.mockReturnValueOnce({ isEarned });
};
afterEach(() => jest.resetAllMocks());
describe('showUnenrollItem', () => {
test('return true', () => {
mockUseCourseCardMenu({ isEnrolled: true, isEarned: false });
out = hooks.useCourseCardMenu(cardId);
expect(out.showUnenrollItem).toBeTruthy();
describe('shouldShowUnenrollItem', () => {
it('returns true if enrolled and not earned', () => {
mockReduxHooks({ isEnrolled: true });
expect(hooks.useOptionVisibility(cardId).shouldShowUnenrollItem).toEqual(true);
});
test('return false', () => {
mockUseCourseCardMenu({ isEnrolled: true, isEarned: true });
out = hooks.useCourseCardMenu(cardId);
expect(out.showUnenrollItem).toBeFalsy();
mockUseCourseCardMenu({ isEnrolled: false, isEarned: false });
out = hooks.useCourseCardMenu(cardId);
expect(out.showUnenrollItem).toBeFalsy();
mockUseCourseCardMenu({ isEnrolled: false, isEarned: true });
out = hooks.useCourseCardMenu(cardId);
expect(out.showUnenrollItem).toBeFalsy();
it('returns false if not enrolled', () => {
mockReduxHooks();
expect(hooks.useOptionVisibility(cardId).shouldShowUnenrollItem).toEqual(false);
});
it('returns false if enrolled but also earned', () => {
mockReduxHooks({ isEarned: true });
expect(hooks.useOptionVisibility(cardId).shouldShowUnenrollItem).toEqual(false);
});
});
describe('showDropdown', () => {
test('return false iif everything is false', () => {
mockUseCourseCardMenu({
isEnrolled: false,
isEarned: false,
isEmailEnabled: false,
facebook: { isEnabled: false },
twitter: { isEnabled: false },
});
out = hooks.useCourseCardMenu(cardId);
expect(out.showDropdown).toBeFalsy();
describe('shouldShowDropdown', () => {
it('returns false if not enrolled and both email and socials are disabled', () => {
mockReduxHooks();
expect(hooks.useOptionVisibility(cardId).shouldShowDropdown).toEqual(false);
});
test('return true iif at least one is true', () => {
mockUseCourseCardMenu({
isEnrolled: true,
isEarned: false,
isEmailEnabled: false,
facebook: { isEnabled: false },
twitter: { isEnabled: false },
});
out = hooks.useCourseCardMenu(cardId);
expect(out.showDropdown).toBeTruthy();
it('returns false if enrolled but already earned, and both email and socials are disabled', () => {
mockReduxHooks({ isEnrolled: true, isEarned: true });
expect(hooks.useOptionVisibility(cardId).shouldShowDropdown).toEqual(false);
});
it('returns true if either social is enabled', () => {
mockReduxHooks({ facebook: { isEnabled: true } });
expect(hooks.useOptionVisibility(cardId).shouldShowDropdown).toEqual(true);
mockReduxHooks({ twitter: { isEnabled: true } });
expect(hooks.useOptionVisibility(cardId).shouldShowDropdown).toEqual(true);
});
it('returns true if email is enabled', () => {
mockReduxHooks({ isEmailEnabled: true });
expect(hooks.useOptionVisibility(cardId).shouldShowDropdown).toEqual(true);
});
it('returns true if enrolled and not earned', () => {
mockReduxHooks({ isEnrolled: true });
expect(hooks.useOptionVisibility(cardId).shouldShowDropdown).toEqual(true);
});
});
test('return correct values', () => {
const expected = {
courseName: 'abitrary-course-name',
isMasquerading: 'abitrary-masquerading-value',
isEmailEnabled: 'abitrary-email-enabled-value',
facebook: { isEnabled: 'abitrary-facebook-value' },
twitter: { isEnabled: 'abitrary-twitter-value' },
};
mockUseCourseCardMenu(expected);
out = hooks.useCourseCardMenu(cardId);
expect(out.courseName).toEqual(expected.courseName);
expect(out.isMasquerading).toEqual(expected.isMasquerading);
expect(out.isEmailEnabled).toEqual(expected.isEmailEnabled);
expect(out.facebook.isEnabled).toEqual(expected.facebook.isEnabled);
expect(out.twitter.isEnabled).toEqual(expected.twitter.isEnabled);
});
test('handleSocialShareClick', () => {
mockUseCourseCardMenu();
out = hooks.useCourseCardMenu(cardId);
expect(reduxHooks.useTrackCourseEvent).toHaveBeenCalledTimes(2);
expect(reduxHooks.useTrackCourseEvent).toHaveBeenCalledWith(
track.socialShare,
cardId,
'facebook',
);
expect(reduxHooks.useTrackCourseEvent).toHaveBeenCalledWith(
track.socialShare,
cardId,
'twitter',
);
});
});
});

View File

@@ -1,22 +1,27 @@
import React from 'react';
import PropTypes from 'prop-types';
import * as ReactShare from 'react-share';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Dropdown, Icon, IconButton } from '@edx/paragon';
import { MoreVert } from '@edx/paragon/icons';
import { StrictDict } from '@edx/react-unit-test-utils';
import EmailSettingsModal from 'containers/EmailSettingsModal';
import UnenrollConfirmModal from 'containers/UnenrollConfirmModal';
import { reduxHooks } from 'hooks';
import SocialShareMenu from './SocialShareMenu';
import {
useEmailSettings,
useUnenrollData,
useHandleToggleDropdown,
useCourseCardMenu,
useOptionVisibility,
} from './hooks';
import messages from './messages';
import useActionDisabledState from '../hooks';
export const testIds = StrictDict({
unenrollModalToggle: 'unenrollModalToggle',
});
export const CourseCardMenu = ({ cardId }) => {
const { formatMessage } = useIntl();
@@ -24,21 +29,11 @@ export const CourseCardMenu = ({ cardId }) => {
const emailSettingsModal = useEmailSettings();
const unenrollModal = useUnenrollData();
const handleToggleDropdown = useHandleToggleDropdown(cardId);
const { isExecutiveEd2uCourse } = useActionDisabledState(cardId);
const { shouldShowUnenrollItem, shouldShowDropdown } = useOptionVisibility(cardId);
const { isMasquerading } = reduxHooks.useMasqueradeData();
const { isEmailEnabled } = reduxHooks.useCardEnrollmentData(cardId);
const {
courseName,
isMasquerading,
isEmailEnabled,
showUnenrollItem,
showDropdown,
facebook,
twitter,
handleTwitterShare,
handleFacebookShare,
} = useCourseCardMenu(cardId);
if (!showDropdown) {
if (!shouldShowDropdown) {
return null;
}
@@ -54,52 +49,16 @@ export const CourseCardMenu = ({ cardId }) => {
alt={formatMessage(messages.dropdownAlt)}
/>
<Dropdown.Menu>
{showUnenrollItem && (
{shouldShowUnenrollItem && (
<Dropdown.Item
disabled={isMasquerading}
onClick={unenrollModal.show}
data-testid="unenrollModalToggle"
data-testid={testIds.unenrollModalToggle}
>
{formatMessage(messages.unenroll)}
</Dropdown.Item>
)}
{(isEmailEnabled && !isExecutiveEd2uCourse) && (
<Dropdown.Item
disabled={isMasquerading}
onClick={emailSettingsModal.show}
data-testid="emailSettingsModalToggle"
>
{formatMessage(messages.emailSettings)}
</Dropdown.Item>
)}
{(facebook.isEnabled && !isExecutiveEd2uCourse) && (
<ReactShare.FacebookShareButton
url={facebook.shareUrl}
onClick={handleFacebookShare}
title={formatMessage(messages.shareQuote, {
courseName,
socialBrand: facebook.socialBrand,
})}
resetButtonStyle={false}
className="pgn__dropdown-item dropdown-item"
>
{formatMessage(messages.shareToFacebook)}
</ReactShare.FacebookShareButton>
)}
{(twitter.isEnabled && !isExecutiveEd2uCourse) && (
<ReactShare.TwitterShareButton
url={twitter.shareUrl}
onClick={handleTwitterShare}
title={formatMessage(messages.shareQuote, {
courseName,
socialBrand: twitter.socialBrand,
})}
resetButtonStyle={false}
className="pgn__dropdown-item dropdown-item"
>
{formatMessage(messages.shareToTwitter)}
</ReactShare.TwitterShareButton>
)}
<SocialShareMenu cardId={cardId} />
</Dropdown.Menu>
</Dropdown>
<UnenrollConfirmModal

View File

@@ -1,162 +1,202 @@
import { shallow } from 'enzyme';
import { when } from 'jest-when';
import {
useEmailSettings, useUnenrollData, useCourseCardMenu,
} from './hooks';
import CourseCardMenu from '.';
import { Dropdown } from '@edx/paragon';
import { shallow } from '@edx/react-unit-test-utils';
import { useIntl } from '@edx/frontend-platform/i18n';
jest.mock('react-share', () => ({
FacebookShareButton: () => 'FacebookShareButton',
TwitterShareButton: () => 'TwitterShareButton',
import EmailSettingsModal from 'containers/EmailSettingsModal';
import UnenrollConfirmModal from 'containers/UnenrollConfirmModal';
import { reduxHooks } from 'hooks';
import * as hooks from './hooks';
import CourseCardMenu, { testIds } from '.';
jest.mock('@edx/frontend-platform/i18n', () => ({
useIntl: jest.fn().mockReturnValue({
formatMessage: jest.requireActual('@edx/react-unit-test-utils').formatMessage,
}),
}));
jest.mock('hooks', () => ({
reduxHooks: { useMasqueradeData: jest.fn(), useCardEnrollmentData: jest.fn() },
}));
jest.mock('./hooks', () => ({
useEmailSettings: jest.fn(),
useUnenrollData: jest.fn(),
useCourseCardMenu: jest.fn(),
useHandleToggleDropdown: () => jest.fn().mockName('mockHandleToggleDropdown'),
useHandleToggleDropdown: jest.fn(),
useOptionVisibility: jest.fn(),
}));
const props = {
cardId: 'test-card-id',
};
const defaultEmailSettingsModal = {
const emailSettings = {
isVisible: false,
show: jest.fn().mockName('emailSettingShow'),
hide: jest.fn().mockName('emailSettingHide'),
};
const defaultUnenrollModal = {
const unenrollData = {
isVisible: false,
show: jest.fn().mockName('unenrollShow'),
hide: jest.fn().mockName('unenrollHide'),
};
const defaultSocialShare = {
facebook: {
isEnabled: true,
shareUrl: 'facebook-share-url',
socialBrand: 'facebook-social-brand',
},
twitter: {
isEnabled: true,
shareUrl: 'twitter-share-url',
socialBrand: 'twitter-social-brand',
},
};
const defaultUseCourseCardMenu = {
courseName: 'test-course-name',
isMasquerading: false,
isEmailEnabled: true,
showUnenrollItem: true,
showDropdown: true,
handleTwitterShare: jest.fn().mockName('handleTwitterShare'),
handleFacebookShare: jest.fn().mockName('handleFacebookShare'),
};
let wrapper;
let el;
const mockHook = (fn, returnValue, options = {}) => {
if (options.isCardHook) {
when(fn).calledWith(props.cardId).mockReturnValueOnce(returnValue);
} else {
when(fn).calledWith().mockReturnValueOnce(returnValue);
}
};
const handleToggleDropdown = jest.fn().mockName('hooks.handleToggleDropdown');
const mockHooks = (returnVals = {}) => {
mockHook(
hooks.useEmailSettings,
returnVals.emailSettings ? returnVals.emailSettings : emailSettings,
);
mockHook(
hooks.useUnenrollData,
returnVals.unenrollData ? returnVals.unenrollData : unenrollData,
);
mockHook(hooks.useHandleToggleDropdown, handleToggleDropdown, { isCardHook: true });
mockHook(
hooks.useOptionVisibility,
{
shouldShowUnenrollItem: !!returnVals.shouldShowUnenrollItem,
shouldShowDropdown: !!returnVals.shouldShowDropdown,
},
{ isCardHook: true },
);
mockHook(reduxHooks.useMasqueradeData, { isMasquerading: !!returnVals.isMasquerading });
mockHook(
reduxHooks.useCardEnrollmentData,
{ isEmailEnabled: !!returnVals.isEmailEnabled },
{ isCardHook: true },
);
};
const render = () => {
el = shallow(<CourseCardMenu {...props} />);
};
describe('CourseCardMenu', () => {
useEmailSettings.mockReturnValue(defaultEmailSettingsModal);
useUnenrollData.mockReturnValue(defaultUnenrollModal);
const mockUseCourseCardMenu = ({
isMasquerading,
isEmailEnabled,
showUnenrollItem,
showDropdown,
facebook,
twitter,
}) => {
useCourseCardMenu.mockReturnValueOnce({
...defaultUseCourseCardMenu,
isMasquerading,
isEmailEnabled,
showUnenrollItem,
showDropdown,
facebook,
twitter,
});
return shallow(<CourseCardMenu {...props} />);
};
test('default snapshot', () => {
wrapper = mockUseCourseCardMenu({
isMasquerading: false,
isEmailEnabled: true,
showUnenrollItem: true,
showDropdown: true,
...defaultSocialShare,
});
expect(wrapper).toMatchSnapshot();
});
test('renders null if showDropdown is false', () => {
wrapper = mockUseCourseCardMenu({
isMasquerading: true,
isEmailEnabled: true,
showUnenrollItem: true,
showDropdown: false,
...defaultSocialShare,
});
expect(wrapper).toMatchSnapshot();
expect(wrapper.isEmptyRender()).toEqual(true);
});
describe('disable state options', () => {
beforeAll(() => {
wrapper = mockUseCourseCardMenu({
isMasquerading: false,
isEmailEnabled: false,
showUnenrollItem: false,
showDropdown: true, // set to true for testing
facebook: {
isEnabled: false,
},
twitter: {
isEnabled: false,
},
});
});
// to make sure it try to render the dropdown
it('render dropdown base on showDropdown', () => {
expect(wrapper.isEmptyRender()).toEqual(false);
expect(wrapper.find('Dropdown').length).toEqual(1);
});
it('not renders email settings modal toggle', () => {
el = wrapper.find({ 'data-testid': 'emailSettingsModalToggle' });
expect(el.length).toEqual(0);
});
it('not renders unenroll modal toggle', () => {
el = wrapper.find({ 'data-testid': 'unenrollModalToggle' });
expect(el.length).toEqual(0);
});
it('not renders share buttons', () => {
expect(wrapper.find('FacebookShareButton').length).toEqual(0);
expect(wrapper.find('TwitterShareButton').length).toEqual(0);
});
});
describe('masquerading', () => {
describe('behavior', () => {
beforeEach(() => {
wrapper = mockUseCourseCardMenu({
isMasquerading: true,
isEmailEnabled: true,
showUnenrollItem: true,
showDropdown: true,
...defaultSocialShare,
mockHooks();
render();
});
it('initializes intl hook', () => {
expect(useIntl).toHaveBeenCalledWith();
});
it('initializes local hooks', () => {
when(hooks.useEmailSettings).expectCalledWith();
when(hooks.useUnenrollData).expectCalledWith();
when(hooks.useHandleToggleDropdown).expectCalledWith(props.cardId);
when(hooks.useOptionVisibility).expectCalledWith(props.cardId);
});
it('initializes redux hook data ', () => {
when(reduxHooks.useMasqueradeData).expectCalledWith();
when(reduxHooks.useCardEnrollmentData).expectCalledWith(props.cardId);
});
});
describe('render', () => {
it('renders null if showDropdown is false', () => {
mockHooks();
render();
expect(el.isEmptyRender()).toEqual(true);
});
const testHandleToggle = () => {
it('displays Dropdown with onToggle=handleToggleDropdown', () => {
expect(el.instance.findByType(Dropdown)[0].props.onToggle).toEqual(handleToggleDropdown);
});
};
const testUnenrollConfirmModal = () => {
it('displays UnenrollConfirmModal with cardId and unenrollModal data', () => {
const modal = el.instance.findByType(UnenrollConfirmModal)[0];
expect(modal.props.show).toEqual(unenrollData.isVisible);
expect(modal.props.closeModal).toEqual(unenrollData.hide);
expect(modal.props.cardId).toEqual(props.cardId);
});
};
describe('show dropdown', () => {
describe('hide unenroll item and disable email', () => {
beforeEach(() => {
mockHooks({ shouldShowDropdown: true });
render();
});
test('snapshot', () => {
expect(el.snapshot).toMatchSnapshot();
});
testHandleToggle();
it('does not render unenroll modal toggle', () => {
expect(el.instance.findByTestId(testIds.unenrollModalToggle).length).toEqual(0);
});
it('does not render EmailSettingsModal', () => {
expect(el.instance.findByType(EmailSettingsModal).length).toEqual(0);
});
testUnenrollConfirmModal();
});
describe('show unenroll and enable email', () => {
const hookProps = {
shouldShowDropdown: true,
isEmailEnabled: true,
shouldShowUnenrollItem: true,
};
beforeEach(() => {
mockHooks(hookProps);
render();
});
test('snapshot', () => {
expect(el.snapshot).toMatchSnapshot();
});
testHandleToggle();
describe('unenroll modal toggle', () => {
let toggle;
describe('not masquerading', () => {
beforeEach(() => {
mockHooks(hookProps);
render();
[toggle] = el.instance.findByTestId(testIds.unenrollModalToggle);
});
it('renders unenroll modal toggle', () => {
expect(el.instance.findByTestId(testIds.unenrollModalToggle).length).toEqual(1);
});
test('onClick from unenroll modal hook', () => {
expect(toggle.props.onClick).toEqual(unenrollData.show);
});
test('disabled', () => {
expect(toggle.props.disabled).toEqual(false);
});
});
describe('masquerading', () => {
beforeEach(() => {
mockHooks({ ...hookProps, isMasquerading: true });
render();
[toggle] = el.instance.findByTestId(testIds.unenrollModalToggle);
});
it('renders', () => {
expect(el.instance.findByTestId(testIds.unenrollModalToggle).length).toEqual(1);
});
test('onClick from unenroll modal hook', () => {
expect(toggle.props.onClick).toEqual(unenrollData.show);
});
test('disabled', () => {
expect(toggle.props.disabled).toEqual(true);
});
});
});
testUnenrollConfirmModal();
it('displays EmaiSettingsModal with cardId and emailSettingsModal data', () => {
const modal = el.instance.findByType(EmailSettingsModal)[0];
expect(modal.props.show).toEqual(emailSettings.isVisible);
expect(modal.props.closeModal).toEqual(emailSettings.hide);
expect(modal.props.cardId).toEqual(props.cardId);
});
});
});
test('snapshot', () => {
expect(wrapper).toMatchSnapshot();
});
it('renders share buttons', () => {
expect(wrapper.find('FacebookShareButton').length).toEqual(1);
el = wrapper.find('TwitterShareButton');
expect(el.length).toEqual(1);
expect(el.prop('url')).toEqual('twitter-share-url');
});
it('renders disabled unenroll modal toggle', () => {
el = wrapper.find({ 'data-testid': 'unenrollModalToggle' });
expect(el.props().disabled).toEqual(true);
});
it('renders disabled email settings modal toggle', () => {
el = wrapper.find({ 'data-testid': 'emailSettingsModalToggle' });
expect(el.props().disabled).toEqual(true);
});
});
});

View File

@@ -1,10 +1,9 @@
import { reduxHooks } from 'hooks';
import { EXECUTIVE_EDUCATION_COURSE_MODES } from '../../../data/constants/course';
export const useActionDisabledState = (cardId) => {
const { isMasquerading } = reduxHooks.useMasqueradeData();
const {
canUpgrade, hasAccess, isAudit, isAuditAccessExpired, mode,
canUpgrade, hasAccess, isAudit, isAuditAccessExpired,
} = reduxHooks.useCardEnrollmentData(cardId);
const {
isEntitlement, isFulfilled, canChange, hasSessions,
@@ -20,8 +19,6 @@ export const useActionDisabledState = (cardId) => {
const disableCourseTitle = (isEntitlement && !isFulfilled) || disableViewCourse;
const isExecutiveEd2uCourse = (EXECUTIVE_EDUCATION_COURSE_MODES.includes(mode));
return {
disableBeginCourse,
disableResumeCourse,
@@ -29,7 +26,6 @@ export const useActionDisabledState = (cardId) => {
disableUpgradeCourse,
disableSelectSession,
disableCourseTitle,
isExecutiveEd2uCourse,
};
};

View File

@@ -63,143 +63,124 @@ describe('useActionDisabledState', () => {
});
};
const runHook = () => hooks.useActionDisabledState(cardId);
describe('disableBeginCourse', () => {
const testDisabled = (data, expected) => {
mockHooksData(data);
expect(runHook().disableBeginCourse).toBe(expected);
};
it('disable when homeUrl is invalid', () => {
mockHooksData({ homeUrl: null });
const { disableBeginCourse } = hooks.useActionDisabledState(cardId);
expect(disableBeginCourse).toBe(true);
testDisabled({ homeUrl: null }, true);
});
it('disable when isMasquerading is true', () => {
mockHooksData({ isMasquerading: true });
const { disableBeginCourse } = hooks.useActionDisabledState(cardId);
expect(disableBeginCourse).toBe(true);
testDisabled({ isMasquerading: true }, true);
});
it('disable when hasAccess is false', () => {
mockHooksData({ hasAccess: false });
const { disableBeginCourse } = hooks.useActionDisabledState(cardId);
expect(disableBeginCourse).toBe(true);
testDisabled({ hasAccess: false }, true);
});
it('disable when isAudit is true and isAuditAccessExpired is true', () => {
mockHooksData({ isAudit: true, isAuditAccessExpired: true });
const { disableBeginCourse } = hooks.useActionDisabledState(cardId);
expect(disableBeginCourse).toBe(true);
testDisabled({ isAudit: true, isAuditAccessExpired: true }, true);
});
it('enable when all conditions are met', () => {
mockHooksData({ hasAccess: true });
const { disableBeginCourse } = hooks.useActionDisabledState(cardId);
expect(disableBeginCourse).toBe(false);
testDisabled({ hasAccess: true }, false);
});
});
describe('disableResumeCourse', () => {
const testDisabled = (data, expected) => {
mockHooksData(data);
expect(runHook().disableResumeCourse).toBe(expected);
};
it('disable when resumeUrl is invalid', () => {
mockHooksData({ resumeUrl: null });
const { disableResumeCourse } = hooks.useActionDisabledState(cardId);
expect(disableResumeCourse).toBe(true);
testDisabled({ resumeUrl: null }, true);
});
it('disable when isMasquerading is true', () => {
mockHooksData({ isMasquerading: true });
const { disableResumeCourse } = hooks.useActionDisabledState(cardId);
expect(disableResumeCourse).toBe(true);
testDisabled({ isMasquerading: true }, true);
});
it('disable when hasAccess is false', () => {
mockHooksData({ hasAccess: false });
const { disableResumeCourse } = hooks.useActionDisabledState(cardId);
expect(disableResumeCourse).toBe(true);
testDisabled({ hasAccess: false }, true);
});
it('disable when isAudit is true and isAuditAccessExpired is true', () => {
mockHooksData({ isAudit: true, isAuditAccessExpired: true });
const { disableResumeCourse } = hooks.useActionDisabledState(cardId);
expect(disableResumeCourse).toBe(true);
testDisabled({ isAudit: true, isAuditAccessExpired: true }, true);
});
it('enable when all conditions are met', () => {
mockHooksData({ hasAccess: true });
const { disableResumeCourse } = hooks.useActionDisabledState(cardId);
expect(disableResumeCourse).toBe(false);
testDisabled({ hasAccess: true }, false);
});
});
describe('disableViewCourse', () => {
const testDisabled = (data, expected) => {
mockHooksData(data);
expect(runHook().disableViewCourse).toBe(expected);
};
it('disable when hasAccess is false', () => {
mockHooksData({ hasAccess: false });
const { disableViewCourse } = hooks.useActionDisabledState(cardId);
expect(disableViewCourse).toBe(true);
testDisabled({ hasAccess: false }, true);
});
it('disable when isAudit is true and isAuditAccessExpired is true', () => {
mockHooksData({ isAudit: true, isAuditAccessExpired: true });
const { disableViewCourse } = hooks.useActionDisabledState(cardId);
expect(disableViewCourse).toBe(true);
testDisabled({ isAudit: true, isAuditAccessExpired: true }, true);
});
it('enable when all conditions are met', () => {
mockHooksData({ hasAccess: true });
const { disableViewCourse } = hooks.useActionDisabledState(cardId);
expect(disableViewCourse).toBe(false);
testDisabled({ hasAccess: true }, false);
});
});
describe('disableUpgradeCourse', () => {
const testDisabled = (data, expected) => {
mockHooksData(data);
expect(runHook().disableUpgradeCourse).toBe(expected);
};
it('disable when upgradeUrl is invalid', () => {
mockHooksData({ upgradeUrl: null });
const { disableUpgradeCourse } = hooks.useActionDisabledState(cardId);
expect(disableUpgradeCourse).toBe(true);
testDisabled({ upgradeUrl: null }, true);
});
it('disable when isMasquerading is true and canUpgrade is false', () => {
mockHooksData({ isMasquerading: true, canUpgrade: false });
const { disableUpgradeCourse } = hooks.useActionDisabledState(cardId);
expect(disableUpgradeCourse).toBe(true);
testDisabled({ isMasquerading: true, canUpgrade: false }, true);
});
it('enable when all conditions are met', () => {
mockHooksData({ canUpgrade: true });
const { disableUpgradeCourse } = hooks.useActionDisabledState(cardId);
expect(disableUpgradeCourse).toBe(false);
testDisabled({ canUpgrade: true }, false);
});
});
describe('disableSelectSession', () => {
const testDisabled = (data, expected) => {
mockHooksData(data);
expect(runHook().disableSelectSession).toBe(expected);
};
it('disable when isEntitlement is false', () => {
mockHooksData({ isEntitlement: false });
const { disableSelectSession } = hooks.useActionDisabledState(cardId);
expect(disableSelectSession).toBe(true);
testDisabled({ isEntitlement: false }, true);
});
it('disable when isMasquerading is true', () => {
mockHooksData({ isMasquerading: true });
const { disableSelectSession } = hooks.useActionDisabledState(cardId);
expect(disableSelectSession).toBe(true);
testDisabled({ isMasquerading: true }, true);
});
it('disable when hasAccess is false', () => {
mockHooksData({ hasAccess: false });
const { disableSelectSession } = hooks.useActionDisabledState(cardId);
expect(disableSelectSession).toBe(true);
testDisabled({ hasAccess: false }, true);
});
it('disable when canChange is false', () => {
mockHooksData({ canChange: false });
const { disableSelectSession } = hooks.useActionDisabledState(cardId);
expect(disableSelectSession).toBe(true);
testDisabled({ canChange: false }, true);
});
it('disable when hasSessions is false', () => {
mockHooksData({ hasSessions: false });
const { disableSelectSession } = hooks.useActionDisabledState(cardId);
expect(disableSelectSession).toBe(true);
testDisabled({ hasSessions: false }, true);
});
it('enable when all conditions are met', () => {
mockHooksData({
isEntitlement: true, hasAccess: true, canChange: true, hasSessions: true,
});
const { disableSelectSession } = hooks.useActionDisabledState(cardId);
expect(disableSelectSession).toBe(false);
testDisabled(
{
isEntitlement: true,
hasAccess: true,
canChange: true,
hasSessions: true,
},
false,
);
});
});
describe('disableCourseTitle', () => {
const testDisabled = (data, expected) => {
mockHooksData(data);
expect(runHook().disableCourseTitle).toBe(expected);
};
it('disable when isEntitlement is true and isFulfilled is false', () => {
mockHooksData({ isEntitlement: true, isFulfilled: false });
const { disableCourseTitle } = hooks.useActionDisabledState(cardId);
expect(disableCourseTitle).toBe(true);
testDisabled({ isEntitlement: true, isFulfilled: false }, true);
});
it('disable when disableViewCourse is true', () => {
mockHooksData({ hasAccess: false });
const { disableCourseTitle } = hooks.useActionDisabledState(cardId);
expect(disableCourseTitle).toBe(true);
testDisabled({ hasAccess: false }, true);
});
it('enable when all conditions are met', () => {
mockHooksData({ isEntitlement: true, isFulfilled: true, hasAccess: true });
const { disableCourseTitle } = hooks.useActionDisabledState(cardId);
expect(disableCourseTitle).toBe(false);
testDisabled({ isEntitlement: true, isFulfilled: true, hasAccess: true }, false);
});
});
});

View File

@@ -1,5 +1,6 @@
import { StrictDict } from 'utils';
import { baseAppUrl } from 'data/services/lms/urls';
import { EXECUTIVE_EDUCATION_COURSE_MODES } from 'data/constants/course';
import * as module from './courseCard';
import * as simpleSelectors from './simpleSelectors';
@@ -98,6 +99,7 @@ export const courseCard = StrictDict({
isEmailEnabled: enrollment.isEmailEnabled,
hasOptedOutOfEmail: enrollment.hasOptedOutOfEmail,
mode: enrollment.mode,
isExecEd2UCourse: EXECUTIVE_EDUCATION_COURSE_MODES.includes(enrollment.mode),
};
},
),

View File

@@ -1,5 +1,6 @@
import { keyStore } from 'utils';
import { baseAppUrl } from 'data/services/lms/urls';
import { EXECUTIVE_EDUCATION_COURSE_MODES } from 'data/constants/course';
import simpleSelectors from './simpleSelectors';
import * as module from './courseCard';
@@ -228,23 +229,25 @@ describe('courseCard selectors module', () => {
});
});
describe('enrollment selector', () => {
const defaultData = {
coursewareAccess: {
isStaff: false,
hasUnmetPrereqs: false,
isTooEarly: false,
},
isEnrolled: 'test-is-enrolled',
lastEnrolled: 'test-last-enrolled',
hasStarted: 'test-has-started',
accessExpirationDate: '3000-10-20',
canUpgrade: 'test-can-upgrade',
isAudit: 'test-is-audit',
isAuditAccessExpired: 'test-is-audit-access-expired',
isVerified: 'test-is-verified',
isEmailEnabled: 'test-is-email-enabled',
mode: 'default',
};
beforeEach(() => {
loadSelector(courseCard.enrollment, {
coursewareAccess: {
isStaff: false,
hasUnmetPrereqs: false,
isTooEarly: false,
},
isEnrolled: 'test-is-enrolled',
lastEnrolled: 'test-last-enrolled',
hasStarted: 'test-has-started',
accessExpirationDate: '3000-10-20',
canUpgrade: 'test-can-upgrade',
isAudit: 'test-is-audit',
isAuditAccessExpired: 'test-is-audit-access-expired',
isVerified: 'test-is-verified',
isEmailEnabled: 'test-is-email-enabled',
});
loadSelector(courseCard.enrollment, defaultData);
});
it('returns a card selector based on enrollment cardSimpleSelector', () => {
expect(simpleSelector).toEqual(cardSimpleSelectors.enrollment);
@@ -280,6 +283,13 @@ describe('courseCard selectors module', () => {
it('passes isEmailEnabled', () => {
expect(selected.isEmailEnabled).toEqual(testData.isEmailEnabled);
});
it('returns isExecEd2UCourse: false if mode is not in EXECUTIVE_EDUCATION_COURSE_MODES', () => {
expect(selected.isExecEd2UCourse).toEqual(false);
});
it('returns isExecEd2UCourse: true if mode is in EXECUTIVE_EDUCATION_COURSE_MODES', () => {
loadSelector(courseCard.enrollment, { ...defaultData, mode: EXECUTIVE_EDUCATION_COURSE_MODES[0] });
expect(selected.isExecEd2UCourse).toEqual(true);
});
});
describe('entitlement selector', () => {
beforeEach(() => {

View File

@@ -12,7 +12,7 @@ export const simpleSelectors = StrictDict({
platformSettings: mkSimpleSelector(app => app.platformSettings),
suggestedCourses: mkSimpleSelector(app => app.suggestedCourses),
emailConfirmation: mkSimpleSelector(app => app.emailConfirmation),
enterpriseDashboard: mkSimpleSelector(app => app.enterpriseDashboard),
enterpriseDashboard: mkSimpleSelector(app => app.enterpriseDashboard || {}),
selectSessionModal: mkSimpleSelector(app => app.selectSessionModal),
pageNumber: mkSimpleSelector(app => app.pageNumber),
socialShareSettings: mkSimpleSelector(app => app.socialShareSettings),

View File

@@ -35,6 +35,12 @@ describe('app simple selectors', () => {
expect(preSelectors).toEqual([appSelector]);
expect(cb(testState.app)).toEqual(testString);
});
test('enterpriseDashboard returns empty object if data returns null', () => {
testState = { app: { enterpriseDashboard: null } };
const { preSelectors, cb } = simpleSelectors.enterpriseDashboard;
expect(preSelectors).toEqual([appSelector]);
expect(cb(testState.app)).toEqual({});
});
describe('cardSimpleSelectors', () => {
keys = keyStore(cardSimpleSelectors);
test.each([

View File

@@ -55,6 +55,12 @@ export const useCardSocialSettingsData = (cardId) => {
return { facebook: loadSettings(facebook), twitter: loadSettings(twitter) };
};
export const useCardExecEdTrackingParam = (cardId) => {
const { isExecEd2UCourse } = module.useCardEnrollmentData(cardId);
const { authOrgId } = module.useEnterpriseDashboardData(cardId);
return isExecEd2UCourse ? `?org_id=${authOrgId}` : '';
};
/** Events **/
export const useUpdateSelectSessionModalCallback = (cardId) => {
const dispatch = useDispatch();