feat: add returnUrl prop to editors (#341)

This commit is contained in:
Kristin Aoki
2023-06-08 13:32:53 -04:00
committed by GitHub
parent f3c4669604
commit 82b0f67f11
17 changed files with 79 additions and 7 deletions

View File

@@ -15,6 +15,7 @@ export const Editor = ({
lmsEndpointUrl,
studioEndpointUrl,
onClose,
returnFunction,
}) => {
const dispatch = useDispatch();
hooks.initializeApp({
@@ -37,7 +38,7 @@ export const Editor = ({
aria-label={blockType}
>
{(EditorComponent !== undefined)
? <EditorComponent onClose={onClose} />
? <EditorComponent {...{ onClose, returnFunction }} />
: <FormattedMessage {...messages.couldNotFindEditor} />}
</div>
</div>
@@ -48,6 +49,7 @@ Editor.defaultProps = {
learningContextId: null,
lmsEndpointUrl: null,
onClose: null,
returnFunction: null,
studioEndpointUrl: null,
};
@@ -57,6 +59,7 @@ Editor.propTypes = {
learningContextId: PropTypes.string,
lmsEndpointUrl: PropTypes.string,
onClose: PropTypes.func,
returnFunction: PropTypes.func,
studioEndpointUrl: PropTypes.string,
};

View File

@@ -13,6 +13,7 @@ export const EditorPage = ({
lmsEndpointUrl,
studioEndpointUrl,
onClose,
returnFunction,
}) => (
<Provider store={store}>
<ErrorBoundary
@@ -29,6 +30,7 @@ export const EditorPage = ({
blockId,
lmsEndpointUrl,
studioEndpointUrl,
returnFunction,
}}
/>
</ErrorBoundary>
@@ -39,6 +41,7 @@ EditorPage.defaultProps = {
courseId: null,
lmsEndpointUrl: null,
onClose: null,
returnFunction: null,
studioEndpointUrl: null,
};
@@ -48,6 +51,7 @@ EditorPage.propTypes = {
courseId: PropTypes.string,
lmsEndpointUrl: PropTypes.string,
onClose: PropTypes.func,
returnFunction: PropTypes.func,
studioEndpointUrl: PropTypes.string,
};

View File

@@ -29,6 +29,7 @@ exports[`Editor render snapshot: renders correct editor given blockType (html ->
>
<TextEditor
onClose={[MockFunction props.onClose]}
returnFunction={null}
/>
</div>
</div>

View File

@@ -22,6 +22,7 @@ exports[`Editor Page snapshots props besides blockType default to null 1`] = `
learningContextId={null}
lmsEndpointUrl={null}
onClose={null}
returnFunction={null}
studioEndpointUrl={null}
/>
</ErrorBoundary>
@@ -50,6 +51,7 @@ exports[`Editor Page snapshots rendering correctly with expected Input 1`] = `
learningContextId="course-v1:edX+DemoX+Demo_Course"
lmsEndpointUrl="evenfakerurl.com"
onClose={[MockFunction props.onClose]}
returnFunction={null}
studioEndpointUrl="fakeurl.com"
/>
</ErrorBoundary>

View File

@@ -13,6 +13,7 @@ exports[`EditorContainer component render snapshot: initialized. enable save and
Object {
"handleCancel": Object {
"onClose": [MockFunction props.onClose],
"returnFunction": [MockFunction props.returnFunction],
},
}
}
@@ -73,6 +74,7 @@ exports[`EditorContainer component render snapshot: initialized. enable save and
"handleSaveClicked": Object {
"dispatch": [MockFunction react-redux.dispatch],
"getContent": [MockFunction props.getContent],
"returnFunction": [MockFunction props.returnFunction],
"validateEntry": [MockFunction props.validateEntry],
},
}
@@ -94,6 +96,7 @@ exports[`EditorContainer component render snapshot: not initialized. disable sav
Object {
"handleCancel": Object {
"onClose": [MockFunction props.onClose],
"returnFunction": [MockFunction props.returnFunction],
},
}
}
@@ -150,6 +153,7 @@ exports[`EditorContainer component render snapshot: not initialized. disable sav
"handleSaveClicked": Object {
"dispatch": [MockFunction react-redux.dispatch],
"getContent": [MockFunction props.getContent],
"returnFunction": [MockFunction props.returnFunction],
"validateEntry": [MockFunction props.validateEntry],
},
}

View File

@@ -19,7 +19,12 @@ export const state = StrictDict({
isCancelConfirmModalOpen: (val) => useState(val),
});
export const handleSaveClicked = ({ dispatch, getContent, validateEntry }) => {
export const handleSaveClicked = ({
dispatch,
getContent,
validateEntry,
returnFunction,
}) => {
const destination = useSelector(selectors.app.returnUrl);
const analytics = useSelector(selectors.app.analytics);
@@ -28,6 +33,7 @@ export const handleSaveClicked = ({ dispatch, getContent, validateEntry }) => {
content: getContent({ dispatch }),
destination,
dispatch,
returnFunction,
validateEntry,
});
};
@@ -41,11 +47,12 @@ export const cancelConfirmModalToggle = () => {
};
};
export const handleCancel = ({ onClose }) => {
export const handleCancel = ({ onClose, returnFunction }) => {
if (onClose) {
return onClose;
}
return navigateCallback({
returnFunction,
destination: useSelector(selectors.app.returnUrl),
analyticsEvent: analyticsEvt.editorCancelClick,
analytics: useSelector(selectors.app.analytics),

View File

@@ -19,13 +19,14 @@ export const EditorContainer = ({
getContent,
onClose,
validateEntry,
returnFunction,
// injected
intl,
}) => {
const dispatch = useDispatch();
const isInitialized = hooks.isInitialized();
const { isCancelConfirmOpen, openCancelConfirmModal, closeCancelConfirmModal } = hooks.cancelConfirmModalToggle();
const handleCancel = hooks.handleCancel({ onClose });
const handleCancel = hooks.handleCancel({ onClose, returnFunction });
return (
<div
className="position-relative zindex-0"
@@ -65,7 +66,12 @@ export const EditorContainer = ({
clearSaveFailed={hooks.clearSaveError({ dispatch })}
disableSave={!isInitialized}
onCancel={openCancelConfirmModal}
onSave={hooks.handleSaveClicked({ dispatch, getContent, validateEntry })}
onSave={hooks.handleSaveClicked({
dispatch,
getContent,
validateEntry,
returnFunction,
})}
saveFailed={hooks.saveFailed()}
/>
</div>
@@ -73,12 +79,14 @@ export const EditorContainer = ({
};
EditorContainer.defaultProps = {
onClose: null,
returnFunction: null,
validateEntry: null,
};
EditorContainer.propTypes = {
children: PropTypes.node.isRequired,
getContent: PropTypes.func.isRequired,
onClose: PropTypes.func,
returnFunction: PropTypes.func,
validateEntry: PropTypes.func,
// injected
intl: intlShape.isRequired,

View File

@@ -9,6 +9,7 @@ const props = {
getContent: jest.fn().mockName('props.getContent'),
onClose: jest.fn().mockName('props.onClose'),
validateEntry: jest.fn().mockName('props.validateEntry'),
returnFunction: jest.fn().mockName('props.returnFunction'),
// inject
intl: { formatMessage },
};
@@ -51,6 +52,7 @@ describe('EditorContainer component', () => {
dispatch: useDispatch(),
getContent: props.getContent,
validateEntry: props.validateEntry,
returnFunction: props.returnFunction,
});
expect(el.children().at(3)
.props().onSave).toEqual(expected);

View File

@@ -3,6 +3,7 @@
exports[`EditorProblemView component renders raw editor 1`] = `
<injectIntl(ShimmedIntlComponent)
getContent={[Function]}
returnFunction={null}
>
<Component
footerNode={
@@ -71,6 +72,7 @@ exports[`EditorProblemView component renders raw editor 1`] = `
exports[`EditorProblemView component renders simple view 1`] = `
<injectIntl(ShimmedIntlComponent)
getContent={[Function]}
returnFunction={null}
>
<Component
footerNode={

View File

@@ -25,6 +25,7 @@ import ExplanationWidget from './ExplanationWidget';
import { saveBlock } from '../../../../hooks';
export const EditProblemView = ({
returnFunction,
// redux
problemType,
problemState,
@@ -50,6 +51,7 @@ export const EditProblemView = ({
assets,
lmsEndpointUrl,
})}
returnFunction={returnFunction}
>
<AlertModal
title={isAdvancedProblemType ? (
@@ -71,6 +73,7 @@ export const EditProblemView = ({
assets,
lmsEndpointUrl,
})(),
returnFunction,
destination: returnUrl,
dispatch,
analytics,
@@ -117,10 +120,12 @@ export const EditProblemView = ({
EditProblemView.defaultProps = {
assets: null,
lmsEndpointUrl: null,
returnFunction: null,
};
EditProblemView.propTypes = {
problemType: PropTypes.string.isRequired,
returnFunction: PropTypes.func,
// eslint-disable-next-line
problemState: PropTypes.any.isRequired,
assets: PropTypes.shape({}),

View File

@@ -11,6 +11,7 @@ import messages from './messages';
export const ProblemEditor = ({
onClose,
returnFunction,
// Redux
problemType,
blockFinished,
@@ -48,16 +49,18 @@ export const ProblemEditor = ({
}
if (problemType === null) {
return (<SelectTypeModal onClose={onClose} />);
return (<SelectTypeModal {...{ onClose }} />);
}
return (<EditProblemView onClose={onClose} />);
return (<EditProblemView {...{ onClose, returnFunction }} />);
};
ProblemEditor.defaultProps = {
assetsFinished: null,
returnFunction: null,
};
ProblemEditor.propTypes = {
onClose: PropTypes.func.isRequired,
returnFunction: PropTypes.func,
// redux
assetsFinished: PropTypes.bool,
advancedSettingsFinished: PropTypes.bool.isRequired,

View File

@@ -20,6 +20,7 @@ exports[`TextEditor snapshots block failed to load, Toast is shown 1`] = `
}
}
onClose={[MockFunction props.onClose]}
returnFunction={null}
>
<div
className="editor-body h-75 overflow-auto"
@@ -73,6 +74,7 @@ exports[`TextEditor snapshots loaded, raw editor 1`] = `
}
}
onClose={[MockFunction props.onClose]}
returnFunction={null}
>
<div
className="editor-body h-75 overflow-auto"
@@ -128,6 +130,7 @@ exports[`TextEditor snapshots not yet loaded, Spinner appears 1`] = `
}
}
onClose={[MockFunction props.onClose]}
returnFunction={null}
>
<div
className="editor-body h-75 overflow-auto"
@@ -175,6 +178,7 @@ exports[`TextEditor snapshots renders as expected with default behavior 1`] = `
}
}
onClose={[MockFunction props.onClose]}
returnFunction={null}
>
<div
className="editor-body h-75 overflow-auto"

View File

@@ -20,6 +20,7 @@ import { prepareEditorRef } from '../../sharedComponents/TinyMceWidget/hooks';
export const TextEditor = ({
onClose,
returnFunction,
// redux
isRaw,
blockValue,
@@ -60,6 +61,7 @@ export const TextEditor = ({
<EditorContainer
getContent={hooks.getContent({ editorRef, isRaw, assets })}
onClose={onClose}
returnFunction={returnFunction}
>
<div className="editor-body h-75 overflow-auto">
<Toast show={blockFailed} onClose={hooks.nullMethod}>
@@ -85,9 +87,11 @@ TextEditor.defaultProps = {
isRaw: null,
assetsFinished: null,
assets: null,
returnFunction: null,
};
TextEditor.propTypes = {
onClose: PropTypes.func.isRequired,
returnFunction: PropTypes.func,
// redux
blockValue: PropTypes.shape({
data: PropTypes.shape({ data: PropTypes.string }),

View File

@@ -6,6 +6,7 @@ exports[`VideoEditor snapshots renders as expected with default behavior 1`] = `
>
<EditorContainer
onClose={[MockFunction props.onClose]}
returnFunction={null}
validateEntry={[MockFunction validateEntry]}
>
<div
@@ -34,6 +35,7 @@ exports[`VideoEditor snapshots renders as expected with default behavior 2`] = `
>
<EditorContainer
onClose={[MockFunction props.onClose]}
returnFunction={null}
validateEntry={[MockFunction validateEntry]}
>
<div

View File

@@ -17,6 +17,7 @@ import messages from './messages';
export const VideoEditor = ({
onClose,
returnFunction,
// injected
intl,
// redux
@@ -31,6 +32,7 @@ export const VideoEditor = ({
<EditorContainer
getContent={fetchVideoContent()}
onClose={onClose}
returnFunction={returnFunction}
validateEntry={validateEntry}
>
{studioViewFinished ? (
@@ -59,9 +61,11 @@ export const VideoEditor = ({
VideoEditor.defaultProps = {
onClose: null,
returnFunction: null,
};
VideoEditor.propTypes = {
onClose: PropTypes.func,
returnFunction: PropTypes.func,
// injected
intl: intlShape.isRequired,
// redux

View File

@@ -17,6 +17,7 @@ export const navigateTo = (destination) => {
};
export const navigateCallback = ({
returnFunction,
destination,
analyticsEvent,
analytics,
@@ -24,6 +25,10 @@ export const navigateCallback = ({
if (process.env.NODE_ENV !== 'development' && analyticsEvent && analytics) {
sendTrackEvent(analyticsEvent, analytics);
}
if (returnFunction) {
returnFunction();
return;
}
module.navigateTo(destination);
};
@@ -34,6 +39,7 @@ export const saveBlock = ({
content,
destination,
dispatch,
returnFunction,
validateEntry,
}) => {
if (!content) {
@@ -53,6 +59,7 @@ export const saveBlock = ({
destination,
analyticsEvent: analyticsEvt.editorSaveClick,
analytics,
returnFunction,
}),
content,
}));

View File

@@ -74,6 +74,7 @@ describe('hooks', () => {
let output;
const SAVED_ENV = process.env;
const destination = 'hOmE';
const returnFunction = jest.fn();
beforeEach(() => {
jest.resetModules();
process.env = { ...SAVED_ENV };
@@ -100,6 +101,15 @@ describe('hooks', () => {
output();
expect(spy).toHaveBeenCalledWith(destination);
});
it('should call returnFunction and return null', () => {
output = hooks.navigateCallback({
destination,
returnFunction,
});
const returnedOutput = output();
expect(returnFunction).toHaveBeenCalled();
expect(returnedOutput).toEqual(undefined);
});
});
describe('nullMethod', () => {