feat: clear save failed status when closing error (#266)

This commit is contained in:
Raymond Zhou
2023-03-07 10:27:55 -08:00
committed by GitHub
parent b84c9c006e
commit b0c1e4d754
8 changed files with 50 additions and 21 deletions

View File

@@ -112,7 +112,6 @@ exports[`EditorFooter render snapshot: save failed. Show error message 1`] = `
className="editor-footer fixed-bottom"
>
<Toast
onClose={[MockFunction hooks.nullMethod]}
show={true}
>
<FormattedMessage

View File

@@ -1,4 +1,5 @@
import React from 'react';
import { useSelector } from 'react-redux';
import PropTypes from 'prop-types';
import {
@@ -10,14 +11,13 @@ import {
Hyperlink,
} from '@edx/paragon';
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { useSelector } from 'react-redux';
import { selectors } from '../../../../data/redux';
import { blockTypes } from '../../../../data/constants/app';
import { nullMethod } from '../../hooks';
import messages from './messages';
export const EditorFooter = ({
clearSaveFailed,
disableSave,
onCancel,
onSave,
@@ -30,9 +30,9 @@ export const EditorFooter = ({
return (
<div className="editor-footer fixed-bottom">
{saveFailed && (
<Toast show onClose={nullMethod}>
<FormattedMessage {...messages.contentSaveFailed} />
</Toast>
<Toast show onClose={clearSaveFailed}>
<FormattedMessage {...messages.contentSaveFailed} />
</Toast>
)}
<ModalDialog.Footer className="shadow-sm">
@@ -69,6 +69,7 @@ export const EditorFooter = ({
);
};
EditorFooter.propTypes = {
clearSaveFailed: PropTypes.func.isRequired,
disableSave: PropTypes.bool.isRequired,
onCancel: PropTypes.func.isRequired,
onSave: PropTypes.func.isRequired,

View File

@@ -1,14 +1,15 @@
import { useState } from 'react';
import { useSelector } from 'react-redux';
import { useState } from 'react';
import analyticsEvt from '../../data/constants/analyticsEvt';
import { RequestKeys } from '../../data/constants/requests';
import { selectors } from '../../data/redux';
import { StrictDict } from '../../utils';
import * as appHooks from '../../hooks';
import * as module from './hooks';
import analyticsEvt from '../../data/constants/analyticsEvt';
import { StrictDict } from '../../utils';
export const {
clearSaveError,
navigateCallback,
nullMethod,
saveBlock,
@@ -30,6 +31,7 @@ export const handleSaveClicked = ({ dispatch, getContent, validateEntry }) => {
validateEntry,
});
};
export const cancelConfirmModalToggle = () => {
const [isCancelConfirmOpen, setIsOpen] = module.state.isCancelConfirmModalOpen(false);
return {
@@ -49,7 +51,9 @@ export const handleCancel = ({ onClose }) => {
analytics: useSelector(selectors.app.analytics),
});
};
export const isInitialized = () => useSelector(selectors.app.isInitialized);
export const saveFailed = () => useSelector((rootState) => (
selectors.requests.isFailed(rootState, { requestKey: RequestKeys.saveBlock })
));

View File

@@ -30,6 +30,9 @@ jest.mock('../../hooks', () => ({
const dispatch = jest.fn();
describe('EditorContainer hooks', () => {
describe('forwarded hooks', () => {
it('forwards clearSaveError from app hooks', () => {
expect(hooks.clearSaveError).toEqual(appHooks.clearSaveError);
});
it('forwards navigateCallback from app hooks', () => {
expect(hooks.navigateCallback).toEqual(appHooks.navigateCallback);
});

View File

@@ -6,12 +6,12 @@ import {
Icon, ModalDialog, IconButton, Button,
} from '@edx/paragon';
import { Close } from '@edx/paragon/icons';
import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n';
import BaseModal from '../../sharedComponents/BaseModal';
import EditorFooter from './components/EditorFooter';
import TitleHeader from './components/TitleHeader';
import * as hooks from './hooks';
import BaseModal from '../../sharedComponents/BaseModal';
import messages from './messages';
export const EditorContainer = ({
@@ -65,9 +65,10 @@ export const EditorContainer = ({
{isInitialized && children}
</ModalDialog.Body>
<EditorFooter
clearSaveFailed={hooks.clearSaveError({ dispatch })}
disableSave={!isInitialized}
onCancel={openCancelConfirmModal}
onSave={hooks.handleSaveClicked({ dispatch, getContent, validateEntry })}
disableSave={!isInitialized}
saveFailed={hooks.saveFailed()}
/>
</div>

View File

@@ -14,6 +14,7 @@ const props = {
};
jest.mock('./hooks', () => ({
clearSaveError: jest.fn().mockName('hooks.clearSaveError'),
isInitialized: jest.fn().mockReturnValue(true),
handleCancel: (args) => ({ handleCancel: args }),
handleSaveClicked: (args) => ({ handleSaveClicked: args }),
@@ -45,7 +46,6 @@ describe('EditorContainer component', () => {
beforeEach(() => {
el = shallow(<EditorContainer {...props}>{testContent}</EditorContainer>);
});
test('save behavior is linked to footer onSave', () => {
const expected = hooks.handleSaveClicked({
dispatch: useDispatch(),
@@ -55,6 +55,11 @@ describe('EditorContainer component', () => {
expect(el.children().at(3)
.props().onSave).toEqual(expected);
});
test('behavior is linked to clearSaveError', () => {
const expected = hooks.clearSaveError({ dispatch: useDispatch() });
expect(el.children().at(3)
.props().clearSaveFailed).toEqual(expected);
});
});
});
});

View File

@@ -3,8 +3,9 @@ import { useEffect } from 'react';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import analyticsEvt from './data/constants/analyticsEvt';
import { thunkActions } from './data/redux';
import { actions, thunkActions } from './data/redux';
import * as module from './hooks';
import { RequestKeys } from './data/constants/requests';
export const initializeApp = ({ dispatch, data }) => useEffect(
() => dispatch(thunkActions.app.initialize(data)),
@@ -54,3 +55,7 @@ export const saveBlock = ({
}));
}
};
export const clearSaveError = ({
dispatch,
}) => () => dispatch(actions.requests.clearRequest({ requestKey: RequestKeys.saveBlock }));

View File

@@ -1,10 +1,11 @@
import { useEffect } from 'react';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import analyticsEvt from './data/constants/analyticsEvt';
import analyticsEvt from './data/constants/analyticsEvt';
import { RequestKeys } from './data/constants/requests';
import { actions, thunkActions } from './data/redux';
import { keyStore } from './utils';
import { thunkActions } from './data/redux';
import * as hooks from './hooks';
jest.mock('react', () => ({
@@ -15,17 +16,17 @@ jest.mock('react', () => ({
}));
jest.mock('./data/redux', () => ({
actions: {
requests: {
clearRequest: (args) => ({ clearRequest: args }),
},
},
thunkActions: {
app: {
initialize: (args) => ({ initializeApp: args }),
saveBlock: (args) => ({ saveBlock: args }),
},
},
selectors: {
app: {
returnUrl: jest.fn(),
},
},
}));
jest.mock('@edx/frontend-platform/analytics', () => ({
@@ -131,4 +132,14 @@ describe('hooks', () => {
}));
});
});
describe('clearSaveError', () => {
it('dispatches actions.requests.clearRequest with saveBlock requestKey', () => {
const dispatch = jest.fn();
hooks.clearSaveError({ dispatch })();
expect(dispatch).toHaveBeenCalledWith(actions.requests.clearRequest({
requestKey: RequestKeys.saveBlock,
}));
});
});
});