Skip to content

Commit

Permalink
fix: stop user from unenroll after earned the certificate (#162)
Browse files Browse the repository at this point in the history
  • Loading branch information
leangseu-edx authored Jul 6, 2023
1 parent e7d9255 commit 4e47018
Show file tree
Hide file tree
Showing 7 changed files with 293 additions and 122 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`CourseCardMenu enrolled, share enabled, email setting enable snapshot 1`] = `
exports[`CourseCardMenu default snapshot 1`] = `
<Fragment>
<Dropdown>
<Dropdown
onToggle={[MockFunction mockHandleToggleDropdown]}
>
<Dropdown.Toggle
alt="Course actions dropdown"
as="IconButton"
Expand All @@ -28,7 +30,7 @@ exports[`CourseCardMenu enrolled, share enabled, email setting enable snapshot 1
</Dropdown.Item>
<FacebookShareButton
className="pgn__dropdown-item dropdown-item"
onClick={[MockFunction facebookShareClick]}
onClick={[MockFunction handleFacebookShare]}
resetButtonStyle={false}
title="I'm taking test-course-name online with facebook-social-brand. Check it out!"
url="facebook-share-url"
Expand All @@ -37,7 +39,7 @@ exports[`CourseCardMenu enrolled, share enabled, email setting enable snapshot 1
</FacebookShareButton>
<TwitterShareButton
className="pgn__dropdown-item dropdown-item"
onClick={[MockFunction twitterShareClick]}
onClick={[MockFunction handleTwitterShare]}
resetButtonStyle={false}
title="I'm taking test-course-name online with twitter-social-brand. Check it out!"
url="twitter-share-url"
Expand All @@ -61,7 +63,9 @@ exports[`CourseCardMenu enrolled, share enabled, email setting enable snapshot 1

exports[`CourseCardMenu masquerading snapshot 1`] = `
<Fragment>
<Dropdown>
<Dropdown
onToggle={[MockFunction mockHandleToggleDropdown]}
>
<Dropdown.Toggle
alt="Course actions dropdown"
as="IconButton"
Expand All @@ -87,7 +91,7 @@ exports[`CourseCardMenu masquerading snapshot 1`] = `
</Dropdown.Item>
<FacebookShareButton
className="pgn__dropdown-item dropdown-item"
onClick={[MockFunction facebookShareClick]}
onClick={[MockFunction handleFacebookShare]}
resetButtonStyle={false}
title="I'm taking test-course-name online with facebook-social-brand. Check it out!"
url="facebook-share-url"
Expand All @@ -96,7 +100,7 @@ exports[`CourseCardMenu masquerading snapshot 1`] = `
</FacebookShareButton>
<TwitterShareButton
className="pgn__dropdown-item dropdown-item"
onClick={[MockFunction twitterShareClick]}
onClick={[MockFunction handleTwitterShare]}
resetButtonStyle={false}
title="I'm taking test-course-name online with twitter-social-brand. Check it out!"
url="twitter-share-url"
Expand All @@ -118,23 +122,4 @@ exports[`CourseCardMenu masquerading snapshot 1`] = `
</Fragment>
`;

exports[`CourseCardMenu not enrolled, share disabled, email setting disabled snapshot 1`] = `
<Fragment>
<Dropdown>
<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>
<UnenrollConfirmModal
cardId="test-card-id"
closeModal={[MockFunction unenrollHide]}
show={false}
/>
</Fragment>
`;
exports[`CourseCardMenu renders null if showDropdown is false 1`] = `""`;
33 changes: 33 additions & 0 deletions src/containers/CourseCard/components/CourseCardMenu/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,36 @@ export const useHandleToggleDropdown = (cardId) => {
if (isOpen) { trackCourseEvent(); }
};
};

export const useCourseCardMenu = (cardId) => {
const { courseName } = reduxHooks.useCardCourseData(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;

return {
courseName,
isMasquerading,
isEmailEnabled,
showUnenrollItem,
showDropdown,
facebook,
twitter,
handleTwitterShare,
handleFacebookShare,
};
};
129 changes: 129 additions & 0 deletions src/containers/CourseCard/components/CourseCardMenu/hooks.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,30 @@ import * as hooks from './hooks';
jest.mock('hooks', () => ({
reduxHooks: {
useTrackCourseEvent: jest.fn(),
useCardCourseData: jest.fn(),
useCardEnrollmentData: jest.fn(),
useCardSocialSettingsData: jest.fn(),
useMasqueradeData: jest.fn(),
useCardCertificateData: 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;

Expand Down Expand Up @@ -88,4 +105,116 @@ describe('CourseCardMenu hooks', () => {
});
});
});

describe('useCourseCardMenu', () => {
const mockUseCourseCardMenu = ({
courseName,
isEnrolled,
isEmailEnabled,
isMasquerading,
facebook,
twitter,
isEarned,
} = {}) => {
reduxHooks.useCardCourseData.mockReturnValueOnce({ courseName });
reduxHooks.useCardSocialSettingsData.mockReturnValueOnce({
facebook: {
...defaultSocialShare.facebook,
...facebook,
},
twitter: {
...defaultSocialShare.twitter,
...twitter,
},
});
reduxHooks.useCardEnrollmentData.mockReturnValueOnce({
isEnrolled,
isEmailEnabled,
});
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();
});

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();
});
});

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();
});

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();
});
});

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',
);
});
});
});
36 changes: 18 additions & 18 deletions src/containers/CourseCard/components/CourseCardMenu/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,40 +6,40 @@ import { useIntl } from '@edx/frontend-platform/i18n';
import { Dropdown, Icon, IconButton } from '@edx/paragon';
import { MoreVert } from '@edx/paragon/icons';

import track from 'tracking';
import { reduxHooks } from 'hooks';
import EmailSettingsModal from 'containers/EmailSettingsModal';
import UnenrollConfirmModal from 'containers/UnenrollConfirmModal';
import {
useEmailSettings,
useUnenrollData,
useHandleToggleDropdown,
useCourseCardMenu,
} from './hooks';

import messages from './messages';

export const CourseCardMenu = ({ cardId }) => {
const { formatMessage } = useIntl();

const { courseName } = reduxHooks.useCardCourseData(cardId);
const { isEnrolled, isEmailEnabled } = reduxHooks.useCardEnrollmentData(cardId);
const { twitter, facebook } = reduxHooks.useCardSocialSettingsData(cardId);
const { isMasquerading } = reduxHooks.useMasqueradeData();
const handleTwitterShare = reduxHooks.useTrackCourseEvent(
track.socialShare,
cardId,
'twitter',
);
const handleFacebookShare = reduxHooks.useTrackCourseEvent(
track.socialShare,
cardId,
'facebook',
);

const emailSettingsModal = useEmailSettings();
const unenrollModal = useUnenrollData();
const handleToggleDropdown = useHandleToggleDropdown(cardId);

const {
courseName,
isMasquerading,
isEmailEnabled,
showUnenrollItem,
showDropdown,
facebook,
twitter,
handleTwitterShare,
handleFacebookShare,
} = useCourseCardMenu(cardId);

if (!showDropdown) {
return null;
}

return (
<>
<Dropdown onToggle={handleToggleDropdown}>
Expand All @@ -52,7 +52,7 @@ export const CourseCardMenu = ({ cardId }) => {
alt={formatMessage(messages.dropdownAlt)}
/>
<Dropdown.Menu>
{isEnrolled && (
{showUnenrollItem && (
<Dropdown.Item
disabled={isMasquerading}
onClick={unenrollModal.show}
Expand Down
Loading

0 comments on commit 4e47018

Please sign in to comment.