diff --git a/src/editors/containers/EditorContainer/__snapshots__/index.test.jsx.snap b/src/editors/containers/EditorContainer/__snapshots__/index.test.jsx.snap
index abc5ee84b..368526098 100644
--- a/src/editors/containers/EditorContainer/__snapshots__/index.test.jsx.snap
+++ b/src/editors/containers/EditorContainer/__snapshots__/index.test.jsx.snap
@@ -1,7 +1,40 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`EditorContainer component render snapshot: initialized. enable save and pass to header 1`] = `
-
+
+
+
+
+ }
+ footerAction={null}
+ isOpen={false}
+ size="md"
+ title="Exit the editor?"
+ >
+
+
@@ -23,13 +56,7 @@ exports[`EditorContainer component render snapshot: initialized. enable save and
>
@@ -40,13 +67,7 @@ exports[`EditorContainer component render snapshot: initialized. enable save and
+
+
+
+
+ }
+ footerAction={null}
+ isOpen={false}
+ size="md"
+ title="Exit the editor?"
+ >
+
+
@@ -83,13 +137,7 @@ exports[`EditorContainer component render snapshot: not initialized. disable sav
>
@@ -97,13 +145,7 @@ exports[`EditorContainer component render snapshot: not initialized. disable sav
useState(val),
+});
+
export const handleSaveClicked = ({ dispatch, getContent, validateEntry }) => {
const destination = useSelector(selectors.app.returnUrl);
const analytics = useSelector(selectors.app.analytics);
@@ -23,7 +30,16 @@ export const handleSaveClicked = ({ dispatch, getContent, validateEntry }) => {
validateEntry,
});
};
-export const handleCancelClicked = ({ onClose }) => {
+export const cancelConfirmModalToggle = () => {
+ const [isCancelConfirmOpen, setIsOpen] = module.state.isCancelConfirmModalOpen(false);
+ return {
+ isCancelConfirmOpen,
+ openCancelConfirmModal: () => setIsOpen(true),
+ closeCancelConfirmModal: () => setIsOpen(false),
+ };
+};
+
+export const handleCancel = ({ onClose }) => {
if (onClose) {
return onClose;
}
@@ -34,6 +50,6 @@ export const handleCancelClicked = ({ onClose }) => {
});
};
export const isInitialized = () => useSelector(selectors.app.isInitialized);
-export const saveFailed = () => useSelector((state) => (
- selectors.requests.isFailed(state, { requestKey: RequestKeys.saveBlock })
+export const saveFailed = () => useSelector((rootState) => (
+ selectors.requests.isFailed(rootState, { requestKey: RequestKeys.saveBlock })
));
diff --git a/src/editors/containers/EditorContainer/hooks.test.jsx b/src/editors/containers/EditorContainer/hooks.test.jsx
index 098302fba..01b9412a7 100644
--- a/src/editors/containers/EditorContainer/hooks.test.jsx
+++ b/src/editors/containers/EditorContainer/hooks.test.jsx
@@ -1,4 +1,5 @@
import * as reactRedux from 'react-redux';
+import { MockUseState } from '../../../testUtils';
import { RequestKeys } from '../../data/constants/requests';
import { selectors } from '../../data/redux';
@@ -7,6 +8,8 @@ import * as appHooks from '../../hooks';
import * as hooks from './hooks';
import analyticsEvt from '../../data/constants/analyticsEvt';
+const hookState = new MockUseState(hooks);
+
jest.mock('../../data/redux', () => ({
selectors: {
app: {
@@ -67,9 +70,46 @@ describe('EditorContainer hooks', () => {
});
});
});
- describe('handleCancelClicked', () => {
+
+ describe('cancelConfirmModalToggle', () => {
+ const hookKey = hookState.keys.isCancelConfirmModalOpen;
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+ describe('state hook', () => {
+ hookState.testGetter(hookKey);
+ });
+ describe('using state', () => {
+ beforeEach(() => {
+ hookState.mock();
+ });
+ afterEach(() => {
+ hookState.restore();
+ });
+
+ describe('cancelConfirmModalToggle', () => {
+ let hook;
+ beforeEach(() => {
+ hook = hooks.cancelConfirmModalToggle();
+ });
+ test('isCancelConfirmOpen: state value', () => {
+ expect(hook.isCancelConfirmOpen).toEqual(hookState.stateVals[hookKey]);
+ });
+ test('openCancelConfirmModal: calls setter with true', () => {
+ hook.openCancelConfirmModal();
+ expect(hookState.setState[hookKey]).toHaveBeenCalledWith(true);
+ });
+ test('closeCancelConfirmModal: calls setter with false', () => {
+ hook.closeCancelConfirmModal();
+ expect(hookState.setState[hookKey]).toHaveBeenCalledWith(false);
+ });
+ });
+ });
+ });
+
+ describe('handleCancel', () => {
it('calls navigateCallback to returnUrl if onClose is not passed', () => {
- expect(hooks.handleCancelClicked({})).toEqual(
+ expect(hooks.handleCancel({})).toEqual(
appHooks.navigateCallback({
destination: reactRedux.useSelector(selectors.app.returnUrl),
analyticsEvent: analyticsEvt.editorCancelClick,
@@ -79,7 +119,7 @@ describe('EditorContainer hooks', () => {
});
it('calls onClose and not navigateCallback if onClose is passed', () => {
const onClose = () => 'my close value';
- expect(hooks.handleCancelClicked({ onClose })).toEqual(onClose);
+ expect(hooks.handleCancel({ onClose })).toEqual(onClose);
expect(appHooks.navigateCallback).not.toHaveBeenCalled();
});
});
diff --git a/src/editors/containers/EditorContainer/index.jsx b/src/editors/containers/EditorContainer/index.jsx
index b8e5fc9f4..21d7edd2b 100644
--- a/src/editors/containers/EditorContainer/index.jsx
+++ b/src/editors/containers/EditorContainer/index.jsx
@@ -2,24 +2,50 @@ import React from 'react';
import { useDispatch } from 'react-redux';
import PropTypes from 'prop-types';
-import { Icon, ModalDialog, IconButton } from '@edx/paragon';
+import {
+ Icon, ModalDialog, IconButton, Button,
+} from '@edx/paragon';
import { Close } from '@edx/paragon/icons';
+import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n';
import EditorFooter from './components/EditorFooter';
import TitleHeader from './components/TitleHeader';
import * as hooks from './hooks';
+import BaseModal from '../TextEditor/components/BaseModal';
+import messages from './messages';
export const EditorContainer = ({
children,
getContent,
onClose,
validateEntry,
+ // injected
+ intl,
}) => {
const dispatch = useDispatch();
const isInitialized = hooks.isInitialized();
- const handleCancelClicked = hooks.handleCancelClicked({ onClose });
+ const { isCancelConfirmOpen, openCancelConfirmModal, closeCancelConfirmModal } = hooks.cancelConfirmModalToggle();
+ const handleCancel = hooks.handleCancel({ onClose });
return (
-
+
+
+
+
+ )}
+ isOpen={isCancelConfirmOpen}
+ close={closeCancelConfirmModal}
+ title={intl.formatMessage(messages.cancelConfirmTitle)}
+ >
+
+
{isInitialized && children}
({
isInitialized: jest.fn().mockReturnValue(true),
- handleCancelClicked: (args) => ({ handleCancelClicked: args }),
+ handleCancel: (args) => ({ handleCancel: args }),
handleSaveClicked: (args) => ({ handleSaveClicked: args }),
saveFailed: jest.fn().mockName('hooks.saveFailed'),
+ cancelConfirmModalToggle: jest.fn(() => ({
+ isCancelConfirmOpen: false,
+ openCancelConfirmModal: jest.fn().mockName('openCancelConfirmModal'),
+ closeCancelConfirmModal: jest.fn().mockName('closeCancelConfirmModal'),
+ })),
}));
let el;
@@ -39,23 +46,13 @@ describe('EditorContainer component', () => {
el = shallow({testContent});
});
- test('close behavior is linked to modal onClose', () => {
- const expected = hooks.handleCancelClicked({ onClose: props.onClose });
- expect(el.find(IconButton)
- .props().onClick).toEqual(expected);
- });
- test('close behavior is linked to footer onCancel', () => {
- const expected = hooks.handleCancelClicked({ onClose: props.onClose });
- expect(el.children().at(2)
- .props().onCancel).toEqual(expected);
- });
test('save behavior is linked to footer onSave', () => {
const expected = hooks.handleSaveClicked({
dispatch: useDispatch(),
getContent: props.getContent,
validateEntry: props.validateEntry,
});
- expect(el.children().at(2)
+ expect(el.children().at(3)
.props().onSave).toEqual(expected);
});
});
diff --git a/src/editors/containers/EditorContainer/messages.js b/src/editors/containers/EditorContainer/messages.js
new file mode 100644
index 000000000..c9eb65dce
--- /dev/null
+++ b/src/editors/containers/EditorContainer/messages.js
@@ -0,0 +1,19 @@
+export const messages = {
+ cancelConfirmTitle: {
+ id: 'authoring.editorContainer.cancelConfirm.title',
+ defaultMessage: 'Exit the editor?',
+ description: 'Label for modal confirming cancellation',
+ },
+ cancelConfirmDescription: {
+ id: 'authoring.editorContainer.cancelConfirm.description',
+ defaultMessage: 'Are you sure you want to exit the editor? Any unsaved changes will be lost.',
+ description: 'Description text for modal confirming cancellation',
+ },
+ okButtonLabel: {
+ id: 'authoring.editorContainer.okButton.label',
+ defaultMessage: 'OK',
+ description: 'Label for OK button',
+ },
+};
+
+export default messages;
diff --git a/src/editors/containers/ProblemEditor/components/SelectTypeModal/SelectTypeWrapper/index.jsx b/src/editors/containers/ProblemEditor/components/SelectTypeModal/SelectTypeWrapper/index.jsx
index b8c63786d..edc54d6c4 100644
--- a/src/editors/containers/ProblemEditor/components/SelectTypeModal/SelectTypeWrapper/index.jsx
+++ b/src/editors/containers/ProblemEditor/components/SelectTypeModal/SelectTypeWrapper/index.jsx
@@ -12,7 +12,7 @@ export const SelectTypeWrapper = ({
onClose,
selected,
}) => {
- const handleCancelClicked = hooks.handleCancelClicked({ onClose });
+ const handleCancel = hooks.handleCancel({ onClose });
return (
@@ -23,7 +23,7 @@ export const SelectTypeWrapper = ({
@@ -33,7 +33,7 @@ export const SelectTypeWrapper = ({
);
diff --git a/src/editors/containers/ProblemEditor/components/SelectTypeModal/SelectTypeWrapper/index.test.jsx b/src/editors/containers/ProblemEditor/components/SelectTypeModal/SelectTypeWrapper/index.test.jsx
index 342d06ced..e5e6c2c49 100644
--- a/src/editors/containers/ProblemEditor/components/SelectTypeModal/SelectTypeWrapper/index.test.jsx
+++ b/src/editors/containers/ProblemEditor/components/SelectTypeModal/SelectTypeWrapper/index.test.jsx
@@ -2,10 +2,10 @@ import React from 'react';
import { shallow } from 'enzyme';
import { IconButton } from '@edx/paragon';
import * as module from '.';
-import { handleCancelClicked } from '../../../../EditorContainer/hooks';
+import { handleCancel } from '../../../../EditorContainer/hooks';
jest.mock('../../../../EditorContainer/hooks', () => ({
- handleCancelClicked: jest.fn().mockName('handleCancelClicked'),
+ handleCancel: jest.fn().mockName('handleCancel'),
}));
describe('SelectTypeWrapper', () => {
@@ -25,7 +25,7 @@ describe('SelectTypeWrapper', () => {
el = shallow();
});
test('close behavior is linked to modal onClose', () => {
- const expected = handleCancelClicked({ onClose: props.onClose });
+ const expected = handleCancel({ onClose: props.onClose });
expect(el.find(IconButton).props().onClick)
.toEqual(expected);
});