feat: Exec Education flag around course card menu and actions (#188)
Co-authored-by: jajjibhai008 <ejazofficial122@gmail.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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),
|
||||
));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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`] = `""`;
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
},
|
||||
),
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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([
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user