feat: add no answer confirmation alert (#310)
This commit is contained in:
@@ -4,6 +4,49 @@ exports[`EditorProblemView component renders raw editor 1`] = `
|
||||
<injectIntl(ShimmedIntlComponent)
|
||||
getContent={[Function]}
|
||||
>
|
||||
<Component
|
||||
footerNode={
|
||||
<ActionRow>
|
||||
<Button
|
||||
onClick={[Function]}
|
||||
variant="tertiary"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Cancel"
|
||||
description="Label for cancel button in the no answer modal"
|
||||
id="authoring.problemEditor.editProblemView.noAnswerModal.cancelButton.label"
|
||||
/>
|
||||
</Button>
|
||||
<Button
|
||||
onClick={[Function]}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Ok"
|
||||
description="Label for save button in the no answer modal"
|
||||
id="authoring.problemEditor.editProblemView.noAnswerModal.saveButton.label"
|
||||
/>
|
||||
</Button>
|
||||
</ActionRow>
|
||||
}
|
||||
isOpen={false}
|
||||
onClose={[Function]}
|
||||
title="No answer specified"
|
||||
>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
defaultMessage="Are you sure you want to exit the editor?"
|
||||
description="Question in body of no answer modal"
|
||||
id="authoring.problemEditor.editProblemView.noAnswerModal.body.question"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
defaultMessage="No correct answer has been specified."
|
||||
description="Explanation in body of no answer modal"
|
||||
id="authoring.problemEditor.editProblemView.noAnswerModal.body.explanation"
|
||||
/>
|
||||
</div>
|
||||
</Component>
|
||||
<div
|
||||
className="editProblemView d-flex flex-row flex-nowrap justify-content-end"
|
||||
>
|
||||
@@ -36,6 +79,49 @@ exports[`EditorProblemView component renders simple view 1`] = `
|
||||
<injectIntl(ShimmedIntlComponent)
|
||||
getContent={[Function]}
|
||||
>
|
||||
<Component
|
||||
footerNode={
|
||||
<ActionRow>
|
||||
<Button
|
||||
onClick={[Function]}
|
||||
variant="tertiary"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Cancel"
|
||||
description="Label for cancel button in the no answer modal"
|
||||
id="authoring.problemEditor.editProblemView.noAnswerModal.cancelButton.label"
|
||||
/>
|
||||
</Button>
|
||||
<Button
|
||||
onClick={[Function]}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Ok"
|
||||
description="Label for save button in the no answer modal"
|
||||
id="authoring.problemEditor.editProblemView.noAnswerModal.saveButton.label"
|
||||
/>
|
||||
</Button>
|
||||
</ActionRow>
|
||||
}
|
||||
isOpen={false}
|
||||
onClose={[Function]}
|
||||
title="No answer specified"
|
||||
>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
defaultMessage="Are you sure you want to exit the editor?"
|
||||
description="Question in body of no answer modal"
|
||||
id="authoring.problemEditor.editProblemView.noAnswerModal.body.question"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
defaultMessage="No correct answer has been specified."
|
||||
description="Explanation in body of no answer modal"
|
||||
id="authoring.problemEditor.editProblemView.noAnswerModal.body.explanation"
|
||||
/>
|
||||
</div>
|
||||
</Component>
|
||||
<div
|
||||
className="editProblemView d-flex flex-row flex-nowrap justify-content-end"
|
||||
>
|
||||
|
||||
@@ -1,7 +1,23 @@
|
||||
import { useState } from 'react';
|
||||
import 'tinymce';
|
||||
import { StrictDict } from '../../../../utils';
|
||||
import ReactStateSettingsParser from '../../data/ReactStateSettingsParser';
|
||||
import ReactStateOLXParser from '../../data/ReactStateOLXParser';
|
||||
import { setAssetToStaticUrl } from '../../../../sharedComponents/TinyMceWidget/hooks';
|
||||
import { ProblemTypeKeys } from '../../../../data/constants/problem';
|
||||
|
||||
export const state = StrictDict({
|
||||
isNoAnswerModalOpen: (val) => useState(val),
|
||||
});
|
||||
|
||||
export const noAnswerModalToggle = () => {
|
||||
const [isNoAnswerModalOpen, setIsOpen] = state.isNoAnswerModalOpen(false);
|
||||
return {
|
||||
isNoAnswerModalOpen,
|
||||
openNoAnswerModal: () => setIsOpen(true),
|
||||
closeNoAnswerModal: () => setIsOpen(false),
|
||||
};
|
||||
};
|
||||
|
||||
export const fetchEditorContent = ({ format }) => {
|
||||
const editorObject = { hints: [] };
|
||||
@@ -52,3 +68,77 @@ export const parseState = ({
|
||||
olx: isAdvanced ? rawOLX : reactBuiltOlx,
|
||||
};
|
||||
};
|
||||
|
||||
export const checkForNoAnswers = ({ openNoAnswerModal, problem }) => {
|
||||
const simpleTextAreaProblems = [ProblemTypeKeys.DROPDOWN, ProblemTypeKeys.NUMERIC, ProblemTypeKeys.TEXTINPUT];
|
||||
const editorObject = fetchEditorContent({ format: '' });
|
||||
const { problemType } = problem;
|
||||
const { answers } = problem;
|
||||
const answerTitles = simpleTextAreaProblems.includes(problemType) ? {} : editorObject.answers;
|
||||
|
||||
const hasTitle = () => {
|
||||
const titles = [];
|
||||
answers.forEach(answer => {
|
||||
const title = simpleTextAreaProblems.includes(problemType) ? answer.title : answerTitles[answer.id];
|
||||
if (title.length > 0) {
|
||||
titles.push(title);
|
||||
}
|
||||
});
|
||||
if (titles.length > 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const hasNoCorrectAnswer = () => {
|
||||
let correctAnswer;
|
||||
answers.forEach(answer => {
|
||||
if (answer.correct) {
|
||||
const title = simpleTextAreaProblems.includes(problemType) ? answer.title : answerTitles[answer.id];
|
||||
if (title.length > 0) {
|
||||
correctAnswer = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (correctAnswer) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if (problemType === ProblemTypeKeys.NUMERIC && !hasTitle()) {
|
||||
openNoAnswerModal();
|
||||
return true;
|
||||
}
|
||||
if (!hasNoCorrectAnswer()) {
|
||||
openNoAnswerModal();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const getContent = ({
|
||||
problemState,
|
||||
openNoAnswerModal,
|
||||
isAdvancedProblemType,
|
||||
editorRef,
|
||||
assets,
|
||||
lmsEndpointUrl,
|
||||
}) => {
|
||||
const problem = problemState;
|
||||
const hasNoAnswers = checkForNoAnswers({
|
||||
problem,
|
||||
openNoAnswerModal,
|
||||
});
|
||||
if (!hasNoAnswers) {
|
||||
const data = parseState({
|
||||
isAdvanced: isAdvancedProblemType,
|
||||
ref: editorRef,
|
||||
problem,
|
||||
assets,
|
||||
lmsEndpointUrl,
|
||||
})();
|
||||
return data;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import { ProblemTypeKeys } from '../../../../data/constants/problem';
|
||||
import * as hooks from './hooks';
|
||||
import { MockUseState } from '../../../../../testUtils';
|
||||
|
||||
const mockRawOLX = 'rawOLX';
|
||||
const mockBuiltOLX = 'builtOLX';
|
||||
|
||||
const toStringMock = () => mockRawOLX;
|
||||
const refMock = { current: { state: { doc: { toString: toStringMock } } } };
|
||||
|
||||
jest.mock('../../data/ReactStateOLXParser', () => (
|
||||
jest.fn().mockImplementation(() => ({
|
||||
buildOLX: () => mockBuiltOLX,
|
||||
@@ -10,6 +15,44 @@ jest.mock('../../data/ReactStateOLXParser', () => (
|
||||
));
|
||||
jest.mock('../../data/ReactStateSettingsParser');
|
||||
|
||||
const hookState = new MockUseState(hooks);
|
||||
|
||||
describe('noAnswerModalToggle', () => {
|
||||
const hookKey = hookState.keys.isNoAnswerModalOpen;
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
describe('state hook', () => {
|
||||
hookState.testGetter(hookKey);
|
||||
});
|
||||
describe('using state', () => {
|
||||
beforeEach(() => {
|
||||
hookState.mock();
|
||||
});
|
||||
afterEach(() => {
|
||||
hookState.restore();
|
||||
});
|
||||
|
||||
describe('noAnswerModalToggle', () => {
|
||||
let hook;
|
||||
beforeEach(() => {
|
||||
hook = hooks.noAnswerModalToggle();
|
||||
});
|
||||
test('isNoAnswerModalOpen: state value', () => {
|
||||
expect(hook.isNoAnswerModalOpen).toEqual(hookState.stateVals[hookKey]);
|
||||
});
|
||||
test('openCancelConfirmModal: calls setter with true', () => {
|
||||
hook.openNoAnswerModal();
|
||||
expect(hookState.setState[hookKey]).toHaveBeenCalledWith(true);
|
||||
});
|
||||
test('closeCancelConfirmModal: calls setter with false', () => {
|
||||
hook.closeNoAnswerModal();
|
||||
expect(hookState.setState[hookKey]).toHaveBeenCalledWith(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('EditProblemView hooks parseState', () => {
|
||||
describe('fetchEditorContent', () => {
|
||||
const getContent = () => '<p>testString</p>';
|
||||
@@ -50,9 +93,6 @@ describe('EditProblemView hooks parseState', () => {
|
||||
});
|
||||
});
|
||||
describe('parseState', () => {
|
||||
const toStringMock = () => mockRawOLX;
|
||||
const refMock = { current: { state: { doc: { toString: toStringMock } } } };
|
||||
|
||||
test('default problem', () => {
|
||||
const res = hooks.parseState({
|
||||
problem: 'problem',
|
||||
@@ -72,4 +112,117 @@ describe('EditProblemView hooks parseState', () => {
|
||||
expect(res.olx).toBe(mockRawOLX);
|
||||
});
|
||||
});
|
||||
describe('checkNoAnswers', () => {
|
||||
const openNoAnswerModal = jest.fn();
|
||||
describe('hasNoTitle', () => {
|
||||
const problem = {
|
||||
problemType: ProblemTypeKeys.NUMERIC,
|
||||
};
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
it('returns true for numerical problem with empty title', () => {
|
||||
const expected = hooks.checkForNoAnswers({
|
||||
openNoAnswerModal,
|
||||
problem: {
|
||||
...problem,
|
||||
answers: [{ id: 'A', title: '', correct: true }],
|
||||
},
|
||||
});
|
||||
expect(openNoAnswerModal).toHaveBeenCalled();
|
||||
expect(expected).toEqual(true);
|
||||
});
|
||||
it('returns false for numerical problem with title', () => {
|
||||
const expected = hooks.checkForNoAnswers({
|
||||
openNoAnswerModal,
|
||||
problem: {
|
||||
...problem,
|
||||
answers: [{ id: 'A', title: 'sOmevALUe', correct: true }],
|
||||
},
|
||||
});
|
||||
expect(openNoAnswerModal).not.toHaveBeenCalled();
|
||||
expect(expected).toEqual(false);
|
||||
});
|
||||
});
|
||||
describe('hasNoCorrectAnswer', () => {
|
||||
const problem = {
|
||||
problemType: ProblemTypeKeys.SINGLESELECT,
|
||||
};
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
it('returns true for single select problem with empty title', () => {
|
||||
window.tinymce.editors = { 'answer-A': { getContent: () => '' }, 'answer-B': { getContent: () => 'sOmevALUe' } };
|
||||
const expected = hooks.checkForNoAnswers({
|
||||
openNoAnswerModal,
|
||||
problem: {
|
||||
...problem,
|
||||
answers: [{ id: 'A', title: '', correct: true }, { id: 'B', title: 'sOmevALUe', correct: false }],
|
||||
},
|
||||
});
|
||||
expect(openNoAnswerModal).toHaveBeenCalled();
|
||||
expect(expected).toEqual(true);
|
||||
});
|
||||
it('returns true for single select with title but no correct answer', () => {
|
||||
window.tinymce.editors = { 'answer-A': { getContent: () => 'sOmevALUe' } };
|
||||
const expected = hooks.checkForNoAnswers({
|
||||
openNoAnswerModal,
|
||||
problem: {
|
||||
...problem,
|
||||
answers: [{ id: 'A', title: 'sOmevALUe', correct: false }, { id: 'B', title: '', correct: false }],
|
||||
},
|
||||
});
|
||||
expect(openNoAnswerModal).toHaveBeenCalled();
|
||||
expect(expected).toEqual(true);
|
||||
});
|
||||
it('returns true for single select with title and correct answer', () => {
|
||||
window.tinymce.editors = { 'answer-A': { getContent: () => 'sOmevALUe' } };
|
||||
const expected = hooks.checkForNoAnswers({
|
||||
openNoAnswerModal,
|
||||
problem: {
|
||||
...problem,
|
||||
answers: [{ id: 'A', title: 'sOmevALUe', correct: true }],
|
||||
},
|
||||
});
|
||||
expect(openNoAnswerModal).not.toHaveBeenCalled();
|
||||
expect(expected).toEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('getContent', () => {
|
||||
const problemState = { problemType: ProblemTypeKeys.NUMERIC, answers: [{ id: 'A', title: 'problem', correct: true }] };
|
||||
const isAdvancedProblem = false;
|
||||
const assets = {};
|
||||
const lmsEndpointUrl = 'someUrl';
|
||||
const editorRef = refMock;
|
||||
const openNoAnswerModal = jest.fn();
|
||||
|
||||
test('default save and returns parseState data', () => {
|
||||
const content = hooks.getContent({
|
||||
problemState,
|
||||
isAdvancedProblem,
|
||||
editorRef,
|
||||
assets,
|
||||
lmsEndpointUrl,
|
||||
openNoAnswerModal,
|
||||
});
|
||||
expect(content).toEqual({
|
||||
olx: 'builtOLX',
|
||||
settings: undefined,
|
||||
});
|
||||
});
|
||||
test('returns null', () => {
|
||||
const problem = { ...problemState, answers: [{ id: 'A', title: '', correct: true }] };
|
||||
const content = hooks.getContent({
|
||||
problemState: problem,
|
||||
isAdvancedProblem,
|
||||
editorRef,
|
||||
assets,
|
||||
lmsEndpointUrl,
|
||||
openNoAnswerModal,
|
||||
});
|
||||
expect(openNoAnswerModal).toHaveBeenCalled();
|
||||
expect(content).toEqual(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import React, { useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { Container } from '@edx/paragon';
|
||||
import { connect, useDispatch } from 'react-redux';
|
||||
import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import {
|
||||
Container,
|
||||
Button,
|
||||
AlertModal,
|
||||
ActionRow,
|
||||
} from '@edx/paragon';
|
||||
import AnswerWidget from './AnswerWidget';
|
||||
import SettingsWidget from './SettingsWidget';
|
||||
import QuestionWidget from './QuestionWidget';
|
||||
@@ -10,9 +17,12 @@ import { selectors } from '../../../../data/redux';
|
||||
import RawEditor from '../../../../sharedComponents/RawEditor';
|
||||
import { ProblemTypeKeys } from '../../../../data/constants/problem';
|
||||
|
||||
import { parseState } from './hooks';
|
||||
import { parseState, noAnswerModalToggle, getContent } from './hooks';
|
||||
import './index.scss';
|
||||
import messages from './messages';
|
||||
|
||||
import ExplanationWidget from './ExplanationWidget';
|
||||
import { saveBlock } from '../../../../hooks';
|
||||
|
||||
export const EditProblemView = ({
|
||||
// redux
|
||||
@@ -20,20 +30,62 @@ export const EditProblemView = ({
|
||||
problemState,
|
||||
assets,
|
||||
lmsEndpointUrl,
|
||||
returnUrl,
|
||||
analytics,
|
||||
// injected
|
||||
intl,
|
||||
}) => {
|
||||
const editorRef = useRef(null);
|
||||
const isAdvancedProblemType = problemType === ProblemTypeKeys.ADVANCED;
|
||||
|
||||
const getContent = parseState({
|
||||
problem: problemState,
|
||||
isAdvanced: isAdvancedProblemType,
|
||||
ref: editorRef,
|
||||
assets,
|
||||
lmsEndpointUrl,
|
||||
});
|
||||
const { isNoAnswerModalOpen, openNoAnswerModal, closeNoAnswerModal } = noAnswerModalToggle();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
return (
|
||||
<EditorContainer getContent={getContent}>
|
||||
<EditorContainer
|
||||
getContent={() => getContent({
|
||||
problemState,
|
||||
openNoAnswerModal,
|
||||
isAdvancedProblemType,
|
||||
editorRef,
|
||||
assets,
|
||||
lmsEndpointUrl,
|
||||
})}
|
||||
>
|
||||
<AlertModal
|
||||
title={intl.formatMessage(messages.noAnswerModalTitle)}
|
||||
isOpen={isNoAnswerModalOpen}
|
||||
onClose={closeNoAnswerModal}
|
||||
footerNode={(
|
||||
<ActionRow>
|
||||
<Button variant="tertiary" onClick={closeNoAnswerModal}>
|
||||
<FormattedMessage {...messages.noAnswerCancelButtonLabel} />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => saveBlock({
|
||||
content: parseState({
|
||||
problem: problemState,
|
||||
isAdvanced: isAdvancedProblemType,
|
||||
ref: editorRef,
|
||||
assets,
|
||||
lmsEndpointUrl,
|
||||
})(),
|
||||
destination: returnUrl,
|
||||
dispatch,
|
||||
analytics,
|
||||
})}
|
||||
>
|
||||
<FormattedMessage {...messages.noAnswerSaveButtonLabel} />
|
||||
</Button>
|
||||
</ActionRow>
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
<FormattedMessage {...messages.noAnswerModalBodyQuestion} />
|
||||
</div>
|
||||
<div>
|
||||
<FormattedMessage {...messages.noAnswerModalBodyExplanation} />
|
||||
</div>
|
||||
</AlertModal>
|
||||
<div className="editProblemView d-flex flex-row flex-nowrap justify-content-end">
|
||||
{isAdvancedProblemType ? (
|
||||
<Container fluid className="advancedEditorTopMargin p-0">
|
||||
@@ -64,14 +116,20 @@ EditProblemView.propTypes = {
|
||||
// eslint-disable-next-line
|
||||
problemState: PropTypes.any.isRequired,
|
||||
assets: PropTypes.shape({}),
|
||||
analytics: PropTypes.shape({}).isRequired,
|
||||
lmsEndpointUrl: PropTypes.string,
|
||||
returnUrl: PropTypes.string.isRequired,
|
||||
// injected
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export const mapStateToProps = (state) => ({
|
||||
assets: selectors.app.assets(state),
|
||||
analytics: selectors.app.analytics(state),
|
||||
lmsEndpointUrl: selectors.app.lmsEndpointUrl(state),
|
||||
returnUrl: selectors.app.returnUrl(state),
|
||||
problemType: selectors.problem.problemType(state),
|
||||
problemState: selectors.problem.completeState(state),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(EditProblemView);
|
||||
export default injectIntl(connect(mapStateToProps)(EditProblemView));
|
||||
|
||||
@@ -4,6 +4,7 @@ import { EditProblemView } from '.';
|
||||
import AnswerWidget from './AnswerWidget';
|
||||
import { ProblemTypeKeys } from '../../../../data/constants/problem';
|
||||
import RawEditor from '../../../../sharedComponents/RawEditor';
|
||||
import { formatMessage } from '../../../../../testUtils';
|
||||
|
||||
describe('EditorProblemView component', () => {
|
||||
test('renders simple view', () => {
|
||||
@@ -11,6 +12,7 @@ describe('EditorProblemView component', () => {
|
||||
problemType={ProblemTypeKeys.SINGLESELECT}
|
||||
problemState={{}}
|
||||
assets={{}}
|
||||
intl={{ formatMessage }}
|
||||
/>);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(wrapper.find(AnswerWidget).length).toBe(1);
|
||||
@@ -18,7 +20,12 @@ describe('EditorProblemView component', () => {
|
||||
});
|
||||
|
||||
test('renders raw editor', () => {
|
||||
const wrapper = shallow(<EditProblemView problemType={ProblemTypeKeys.ADVANCED} problemState={{}} assets={{}} />);
|
||||
const wrapper = shallow(<EditProblemView
|
||||
problemType={ProblemTypeKeys.ADVANCED}
|
||||
problemState={{}}
|
||||
assets={{}}
|
||||
intl={{ formatMessage }}
|
||||
/>);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(wrapper.find(AnswerWidget).length).toBe(0);
|
||||
expect(wrapper.find(RawEditor).length).toBe(1);
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
noAnswerCancelButtonLabel: {
|
||||
id: 'authoring.problemEditor.editProblemView.noAnswerModal.cancelButton.label',
|
||||
defaultMessage: 'Cancel',
|
||||
description: 'Label for cancel button in the no answer modal',
|
||||
},
|
||||
noAnswerSaveButtonLabel: {
|
||||
id: 'authoring.problemEditor.editProblemView.noAnswerModal.saveButton.label',
|
||||
defaultMessage: 'Ok',
|
||||
description: 'Label for save button in the no answer modal',
|
||||
},
|
||||
noAnswerModalTitle: {
|
||||
id: 'authoring.problemEditor.editProblemView.noAnswerModal.title',
|
||||
defaultMessage: 'No answer specified',
|
||||
description: 'Title for no answer modal',
|
||||
},
|
||||
noAnswerModalBodyQuestion: {
|
||||
id: 'authoring.problemEditor.editProblemView.noAnswerModal.body.question',
|
||||
defaultMessage: 'Are you sure you want to exit the editor?',
|
||||
description: 'Question in body of no answer modal',
|
||||
},
|
||||
noAnswerModalBodyExplanation: {
|
||||
id: 'authoring.problemEditor.editProblemView.noAnswerModal.body.explanation',
|
||||
defaultMessage: 'No correct answer has been specified.',
|
||||
description: 'Explanation in body of no answer modal',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
@@ -36,6 +36,9 @@ export const saveBlock = ({
|
||||
dispatch,
|
||||
validateEntry,
|
||||
}) => {
|
||||
if (!content) {
|
||||
return;
|
||||
}
|
||||
let attemptSave = false;
|
||||
if (validateEntry) {
|
||||
if (validateEntry()) {
|
||||
|
||||
@@ -109,13 +109,27 @@ describe('hooks', () => {
|
||||
});
|
||||
|
||||
describe('saveBlock', () => {
|
||||
it('dispatches thunkActions.app.saveBlock with navigateCallback, and passed content', () => {
|
||||
const navigateCallback = (args) => ({ navigateCallback: args });
|
||||
const dispatch = jest.fn();
|
||||
const destination = 'uRLwhENsAved';
|
||||
const analytics = 'dATAonEveNT';
|
||||
const content = 'myContent';
|
||||
const navigateCallback = (args) => ({ navigateCallback: args });
|
||||
const dispatch = jest.fn();
|
||||
const destination = 'uRLwhENsAved';
|
||||
const analytics = 'dATAonEveNT';
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.spyOn(hooks, hookKeys.navigateCallback).mockImplementationOnce(navigateCallback);
|
||||
});
|
||||
it('returns null when content is null', () => {
|
||||
const content = null;
|
||||
const expected = hooks.saveBlock({
|
||||
content,
|
||||
destination,
|
||||
analytics,
|
||||
dispatch,
|
||||
});
|
||||
expect(expected).toEqual(undefined);
|
||||
});
|
||||
it('dispatches thunkActions.app.saveBlock with navigateCallback, and passed content', () => {
|
||||
const content = 'myContent';
|
||||
hooks.saveBlock({
|
||||
content,
|
||||
destination,
|
||||
|
||||
Reference in New Issue
Block a user