Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { reduxHooks } from 'hooks';

import { useIntl } from '@edx/frontend-platform/i18n';
import {
Expand All @@ -10,13 +11,17 @@ import {
import messages from './messages';

export const ConfirmPane = ({
cardId,
handleClose,
handleConfirm,
}) => {
const { formatMessage } = useIntl();
const { courseName } = reduxHooks.useCardCourseData(cardId);
const courseTitle = <span className="font-italic">“{courseName}”</span>;
return (
<>
<h4>{formatMessage(messages.confirmHeader)}</h4>
<p className="py-2">{formatMessage(messages.confirmText, { courseTitle })}</p>
<ActionRow>
<Button variant="tertiary" onClick={handleClose}>
{formatMessage(messages.confirmCancel)}
Expand All @@ -31,6 +36,7 @@ export const ConfirmPane = ({
ConfirmPane.propTypes = {
handleClose: PropTypes.func.isRequired,
handleConfirm: PropTypes.func.isRequired,
cardId: PropTypes.string.isRequired,
};

export default ConfirmPane;
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ConfirmPane } from './ConfirmPane';
import messages from './messages';

const props = {
cardId: 'cardId',
handleClose: jest.fn().mockName('props.handleClose'),
handleConfirm: jest.fn().mockName('props.handleConfirm'),
};
Expand Down
11 changes: 7 additions & 4 deletions src/containers/UnenrollConfirmModal/components/FinishedPane.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { reduxHooks } from 'hooks';

import { useIntl } from '@edx/frontend-platform/i18n';
import {
Expand All @@ -10,16 +11,18 @@ import {
import messages from './messages';

export const FinishedPane = ({
gaveReason,
cardId,
handleClose,
}) => {
const { formatMessage } = useIntl();
const { courseName } = reduxHooks.useCardCourseData(cardId);
const courseTitle = <span className="font-italic">“{courseName}”</span>;

return (
<>
<h4>{formatMessage(messages.finishHeading)}</h4>
<p>
{gaveReason && formatMessage(messages.finishThanksText)}
{formatMessage(messages.finishText)}
{formatMessage(messages.finishText, { courseTitle })}
</p>
<ActionRow>
<Button onClick={handleClose}>{formatMessage(messages.finishReturn)}</Button>
Expand All @@ -29,7 +32,7 @@ export const FinishedPane = ({
};
FinishedPane.propTypes = {
handleClose: PropTypes.func.isRequired,
gaveReason: PropTypes.bool.isRequired,
cardId: PropTypes.string.isRequired,
};

export default FinishedPane;
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { FinishedPane } from './FinishedPane';
import messages from './messages';

const props = {
gaveReason: true,
cardId: 'cardId',
handleClose: jest.fn().mockName('props.handleClose'),
};

Expand All @@ -25,22 +25,8 @@ describe('UnenrollConfirmModal FinishedPane', () => {
expect(returnButton).toBeInTheDocument();
});
it('Gave reason, display thanks message', () => {
const thanksMsg = screen.getByText((text) => text.includes('Thank you'));
expect(thanksMsg).toBeInTheDocument();
expect(thanksMsg.innerHTML).toContain(formatMessage(messages.finishThanksText));
});
});
describe('Did not give reason', () => {
it('Does not display thanks message', () => {
const customProps = {
gaveReason: false,
handleClose: jest.fn().mockName('props.handleClose'),
};
render(<IntlProvider locale="en"><FinishedPane {...customProps} /></IntlProvider>);
const thanksMsg = screen.queryByText((text) => text.includes('Thank you'));
expect(thanksMsg).toBeNull();
const finishMsg = screen.getByText(formatMessage(messages.finishText));
expect(finishMsg).toBeInTheDocument();
const finishSuccessMessage = screen.getByText((text) => text.includes('Unenrollment Successful'));
expect(finishSuccessMessage).toBeInTheDocument();
});
});
});
11 changes: 7 additions & 4 deletions src/containers/UnenrollConfirmModal/components/ReasonPane.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import messages from './messages';

export const ReasonPane = ({
reason,
handleClose,
}) => {
const { formatMessage } = useIntl();
const option = (key) => (
Expand All @@ -27,6 +28,7 @@ export const ReasonPane = ({
name="unenrollReason"
onChange={reason.selectOption}
value={reason.selected}
defaultValue={constants.reasonKeys.preferNotToSay}
>
{constants.order.map(option)}
<Form.Radio value={constants.reasonKeys.custom}>
Expand All @@ -35,12 +37,13 @@ export const ReasonPane = ({
placeholder={formatMessage(constants.messages.customPlaceholder)}
/>
</Form.Radio>
{option(constants.reasonKeys.preferNotToSay)}
</Form.RadioSet>
<ActionRow>
<Button variant="tertiary" onClick={reason.handleSkip}>
{formatMessage(messages.reasonSkip)}
<Button variant="tertiary" onClick={handleClose}>
{formatMessage(messages.confirmCancel)}
</Button>
<Button disabled={!reason.hasReason} onClick={reason.handleSubmit}>
<Button onClick={reason.handleSubmit}>
{formatMessage(messages.reasonSubmit)}
</Button>
</ActionRow>
Expand All @@ -50,7 +53,6 @@ export const ReasonPane = ({
ReasonPane.propTypes = {
reason: PropTypes.shape({
value: PropTypes.string,
handleSkip: PropTypes.func,
hasReason: PropTypes.bool,
selectOption: PropTypes.func,
customOption: PropTypes.shape({
Expand All @@ -60,6 +62,7 @@ ReasonPane.propTypes = {
selected: PropTypes.string,
handleSubmit: PropTypes.func.isRequired,
}).isRequired,
handleClose: PropTypes.func.isRequired,
};

export default ReasonPane;
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ describe('UnenrollConfirmModal ReasonPane', () => {
render(<IntlProvider locale="en"><ReasonPane {...props} /></IntlProvider>);
const radioButtons = screen.getAllByRole('radio');
expect(radioButtons).toBeDefined();
expect(radioButtons.length).toBe(10);
expect(radioButtons.length).toBe(11);
});
it('render skip button', () => {
it('render cancel button', () => {
render(<IntlProvider locale="en"><ReasonPane {...props} hasReason={false} /></IntlProvider>);
const skipButton = screen.getByRole('button', { name: formatMessage(messages.reasonSkip) });
const skipButton = screen.getByRole('button', { name: formatMessage(messages.confirmCancel) });
expect(skipButton).toBeInTheDocument();
});
it('render submit button', () => {
Expand Down
24 changes: 12 additions & 12 deletions src/containers/UnenrollConfirmModal/components/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@ const messages = defineMessages({
confirmHeader: {
id: 'learner-dash.unenrollConfirm.confirm.header',
description: 'Header for confirm unenroll modal',
defaultMessage: 'Unenroll from course?',
defaultMessage: 'Confirm Unenrollment',
},
confirmText: {
id: 'learner-dash.unenrollConfirm.confirm.text',
description: 'Text for confirm unenroll modal',
defaultMessage: 'Are you sure you want to unenroll from the course {courseTitle} ?',
},
confirmCancel: {
id: 'learner-dash.unenrollConfirm.confirm.cancel',
description: 'Cancel action for confirm unenroll modal',
defaultMessage: 'Never mind',
defaultMessage: 'Cancel',
},
confirmUnenroll: {
id: 'learner-dash.unenrollConfirm.confirm.unenroll',
Expand All @@ -20,7 +25,7 @@ const messages = defineMessages({
reasonHeading: {
id: 'learner-dash.unenrollConfirm.confirm.reason.heading',
description: 'Heading for unenroll reason modal',
defaultMessage: 'What\'s your main reason for unenrolling?',
defaultMessage: 'Why are you unenrolling?',
},
reasonSkip: {
id: 'learner-dash.unenrollConfirm.confirm.reason.skip',
Expand All @@ -30,27 +35,22 @@ const messages = defineMessages({
reasonSubmit: {
id: 'learner-dash.unenrollConfirm.confirm.reason.submit',
description: 'Submit action for unenroll reason modal',
defaultMessage: 'Submit reason',
defaultMessage: 'Unenroll',
},
finishHeading: {
id: 'learner-dash.unenrollConfirm.confirm.finish.heading',
description: 'Heading for unenroll finish modal',
defaultMessage: 'You are unenrolled',
},
finishThanksText: {
id: 'learner-dash.unenrollConfirm.confirm.finish.thanks-text',
description: 'Thank you message on unenroll modal for providing a reason',
defaultMessage: 'Thank you for sharing your reason for unenrolling. ',
defaultMessage: 'Unenrollment Successful',
},
finishText: {
id: 'learner-dash.unenrollConfirm.confirm.finish.text',
description: 'Text for unenroll finish modal',
defaultMessage: 'This course will be removed from your dashboard.',
defaultMessage: 'You have been unenrolled from the course {courseTitle}',
},
finishReturn: {
id: 'learner-dash.unenrollConfirm.confirm.finish.return',
description: 'Return action for unenroll finish modal',
defaultMessage: 'Return to dashboard',
defaultMessage: 'Ok',
},
});

Expand Down
8 changes: 7 additions & 1 deletion src/containers/UnenrollConfirmModal/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,19 @@ export const reasonKeys = StrictDict({
quality: 'quality',
easy: 'easy',
custom: 'custom',
preferNotToSay: 'prefer-not-to-say',
});

export const order = [
reasonKeys.prereqs,
reasonKeys.difficulty,
reasonKeys.easy,
reasonKeys.goals,
reasonKeys.broken,
reasonKeys.time,
reasonKeys.browse,
reasonKeys.support,
reasonKeys.quality,
reasonKeys.easy,
];

const messages = defineMessages({
Expand Down Expand Up @@ -78,6 +79,11 @@ const messages = defineMessages({
description: 'Unenroll custom reason option placeholder text',
defaultMessage: 'Other',
},
[reasonKeys.preferNotToSay]: {
id: 'learner-dash.unenrollConfirm.reasons.prefer-not-to-say',
description: 'Unenroll reason option - prefer not to say',
defaultMessage: 'I prefer not to say',
},
});

export default {
Expand Down
2 changes: 1 addition & 1 deletion src/containers/UnenrollConfirmModal/hooks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const useUnenrollData = ({ closeModal, cardId }) => {

let modalState;
if (isConfirmed) {
modalState = (reason.isSubmitted || reason.isSkipped)
modalState = (reason.isSubmitted)
? modalStates.finished : modalStates.reason;
} else {
modalState = modalStates.confirm;
Expand Down
16 changes: 4 additions & 12 deletions src/containers/UnenrollConfirmModal/hooks/reasons.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import { StrictDict } from 'utils';
import track from 'tracking';

import * as module from './reasons';
import constants from '../constants';

export const state = StrictDict({
customOption: (val) => React.useState(val), // eslint-disable-line
isSkipped: (val) => React.useState(val), // eslint-disable-line
selectedReason: (val) => React.useState(val), // eslint-disable-line
isSubmitted: (val) => React.useState(val), //eslint-disable-line
});
Expand All @@ -21,12 +21,12 @@ export const useUnenrollReasons = ({
cardId,
}) => {
// The selected option element from the menu
const [selectedReason, setSelectedReason] = module.state.selectedReason(null);
const [selectedReason, setSelectedReason] = module.state.selectedReason(
constants.reasonKeys.preferNotToSay,
);
// Custom option element entry value
const [customOption, setCustomOption] = module.state.customOption('');

// Did the user choose to skip selecting a reason?
const [isSkipped, setIsSkipped] = module.state.isSkipped(false);
// Did the user submit an unenrollment reason
const [isSubmitted, setIsSubmitted] = module.state.isSubmitted(false);

Expand All @@ -47,15 +47,9 @@ export const useUnenrollReasons = ({
const handleClear = () => {
setSelectedReason(null);
setCustomOption('');
setIsSkipped(false);
setIsSubmitted(false);
};

const handleSkip = () => {
setIsSkipped(true);
unenrollFromCourse();
};

const handleSubmit = (e) => {
handleTrackReasons(e);
setIsSubmitted(true);
Expand All @@ -68,10 +62,8 @@ export const useUnenrollReasons = ({
return {
customOption: { value: customOption, onChange: handleCustomOptionChange },
handleClear,
handleSkip,
handleSubmit,
hasReason,
isSkipped,
isSubmitted,
selectOption: handleSelectOption,
submittedReason,
Expand Down
18 changes: 2 additions & 16 deletions src/containers/UnenrollConfirmModal/hooks/reasons.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
} from 'hooks';

import * as hooks from './reasons';
import constants from '../constants';

jest.mock('hooks', () => ({
apiHooks: {
Expand Down Expand Up @@ -39,7 +40,6 @@ const loadHook = (isEntitlement = false) => {
describe('UnenrollConfirmModal reasons hooks', () => {
describe('state fields', () => {
state.testGetter(state.keys.customOption);
state.testGetter(state.keys.isSkipped);
state.testGetter(state.keys.isSubmitted);
state.testGetter(state.keys.selectedReason);
});
Expand All @@ -55,14 +55,11 @@ describe('UnenrollConfirmModal reasons hooks', () => {
describe('behavior', () => {
describe('state fields', () => {
it('initializes selectedReason with null', () => {
state.expectInitializedWith(state.keys.selectedReason, null);
state.expectInitializedWith(state.keys.selectedReason, constants.reasonKeys.preferNotToSay);
});
it('initializes customOption with empty string', () => {
state.expectInitializedWith(state.keys.customOption, '');
});
it('initializes isSkipped with false', () => {
state.expectInitializedWith(state.keys.isSkipped, false);
});
it('initializes isSubmitted with false', () => {
state.expectInitializedWith(state.keys.isSubmitted, false);
});
Expand Down Expand Up @@ -140,15 +137,9 @@ describe('UnenrollConfirmModal reasons hooks', () => {
out.handleClear();
expect(state.setState.selectedReason).toHaveBeenCalledWith(null);
expect(state.setState.customOption).toHaveBeenCalledWith('');
expect(state.setState.isSkipped).toHaveBeenCalledWith(false);
expect(state.setState.isSubmitted).toHaveBeenCalledWith(false);
});
});
test('handleSkip sets isSkipped and isSubmitted, and unenrolls w/out a reason', () => {
out.handleSkip();
expect(state.setState.isSkipped).toHaveBeenCalledWith(true);
expect(unenrollFromCourse).toHaveBeenCalledWith();
});
describe('handleSubmit', () => {
it('tracks reason event and calls unenroll action', () => {
state.mockVal(state.keys.selectedReason, testValue);
Expand All @@ -160,11 +151,6 @@ describe('UnenrollConfirmModal reasons hooks', () => {
expect(unenrollFromCourse).toHaveBeenCalledWith();
});
});
test('isSkipped returns state value', () => {
state.mockVal(state.keys.isSkipped, testValue);
loadHook();
expect(out.isSkipped).toEqual(testValue);
});
test('isSubmitted returns state value', () => {
state.mockVal(state.keys.isSubmitted, testValue);
loadHook();
Expand Down
Loading