feat: Submit states (#37)

* feat: enable teams support

* feat: submit grade pending behavior

* feat: submit error behavior

* fix: Update src/data/services/lms/fakeData/testUtils.js

Co-authored-by: leangseu-edx <83240113+leangseu-edx@users.noreply.github.com>

Co-authored-by: leangseu-edx <83240113+leangseu-edx@users.noreply.github.com>
This commit is contained in:
Ben Warzeski
2021-12-06 11:02:43 -05:00
committed by GitHub
parent 909516dbc7
commit 91c874e20d
45 changed files with 1148 additions and 230 deletions

View File

@@ -5,29 +5,40 @@ import { connect } from 'react-redux';
import { Col, Row } from '@edx/paragon';
import { selectors } from 'data/redux';
import { RequestKeys } from 'data/constants/requests';
import ResponseDisplay from 'containers/ResponseDisplay';
import Rubric from 'containers/Rubric';
import ReviewErrors from './ReviewErrors';
/**
* <ReviewContent />
*/
export const ReviewContent = ({ showRubric }) => (
export const ReviewContent = ({ isFailed, isLoaded, showRubric }) => (isLoaded || isFailed) && (
<div className="content-block">
<Row className="flex-nowrap">
<Col><ResponseDisplay /></Col>
{ showRubric && <Rubric /> }
</Row>
<ReviewErrors />
{ isLoaded && (
<Row className="flex-nowrap">
<Col><ResponseDisplay /></Col>
{ showRubric && <Rubric /> }
</Row>
)}
</div>
);
ReviewContent.defaultProps = {
isFailed: false,
isLoaded: false,
showRubric: false,
};
ReviewContent.propTypes = {
isFailed: PropTypes.bool,
isLoaded: PropTypes.bool,
showRubric: PropTypes.bool,
};
export const mapStateToProps = (state) => ({
isFailed: selectors.requests.isFailed(state, { requestKey: RequestKeys.fetchSubmission }),
isLoaded: selectors.requests.isCompleted(state, { requestKey: RequestKeys.fetchSubmission }),
showRubric: selectors.app.showRubric(state),
});

View File

@@ -2,6 +2,7 @@ import React from 'react';
import { shallow } from 'enzyme';
import { selectors } from 'data/redux';
import { RequestKeys } from 'data/constants/requests';
import {
ReviewContent,
mapStateToProps,
@@ -12,21 +13,30 @@ jest.mock('data/redux', () => ({
app: {
showRubric: (...args) => ({ showRubric: args }),
},
requests: {
isCompleted: (...args) => ({ isCompleted: args }),
isFailed: (...args) => ({ isFailed: args }),
},
},
}));
jest.mock('containers/ResponseDisplay', () => 'ResponseDisplay');
jest.mock('containers/Rubric', () => 'Rubric');
jest.useFakeTimers('modern');
jest.mock('./ReviewErrors', () => 'ReviewErrors');
describe('ReviewContent component', () => {
describe('component', () => {
describe('render tests', () => {
test('snapshot (show rubric)', () => {
expect(shallow(<ReviewContent />)).toMatchSnapshot();
test('snapshot: not loaded, no error', () => {
expect(shallow(<ReviewContent />).isEmptyRender()).toEqual(true);
});
test('snapshot (hide rubric)', () => {
expect(shallow(<ReviewContent showRubric />)).toMatchSnapshot();
test('snapshot: show rubric', () => {
expect(shallow(<ReviewContent isLoaded />)).toMatchSnapshot();
});
test('snapshot: hide rubric', () => {
expect(shallow(<ReviewContent isLoaded showRubric />)).toMatchSnapshot();
});
test('snapshot: failed, showRubric (errors only)', () => {
expect(shallow(<ReviewContent showRubric isFailed />)).toMatchSnapshot();
});
});
});
@@ -36,8 +46,15 @@ describe('ReviewContent component', () => {
beforeEach(() => {
mapped = mapStateToProps(testState);
});
const requestKey = RequestKeys.fetchSubmission;
test('showRubric loads from app.showRubric', () => {
expect(mapped.showRubric).toEqual(selectors.app.showRubric(testState));
});
test('isFailed loads from requests.isFailed(fetchSubmission)', () => {
expect(mapped.isFailed).toEqual(selectors.requests.isFailed(testState, { requestKey }));
});
test('isLoadeed loads from requests.isCompleted(fetchSubmission)', () => {
expect(mapped.isLoaded).toEqual(selectors.requests.isCompleted(testState, { requestKey }));
});
});
});

View File

@@ -1,51 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import {
Alert,
Button,
} from '@edx/paragon';
import { Info } from '@edx/paragon/icons';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { thunkActions } from 'data/redux';
import messages from './messages';
/**
* <ReviewError />
*/
export const ReviewError = ({ reload }) => (
<Alert
variant="danger"
icon={Info}
actions={[
<Button onClick={reload}>
<FormattedMessage {...messages.reloadSubmission} />
</Button>,
]}
>
<Alert.Heading>
<FormattedMessage {...messages.loadErrorHeading} />
</Alert.Heading>
<p>
<FormattedMessage {...messages.loadErrorMessage} />
</p>
</Alert>
);
ReviewError.defaultProps = {
};
ReviewError.propTypes = {
// redux
reload: PropTypes.func.isRequired,
};
export const mapStateToProps = () => ({
});
export const mapDispatchToProps = {
reload: thunkActions.grading.loadSubmission,
};
export default connect(mapStateToProps, mapDispatchToProps)(ReviewError);

View File

@@ -1,34 +0,0 @@
import React from 'react';
import { shallow } from 'enzyme';
import { thunkActions } from 'data/redux';
import {
ReviewError,
mapDispatchToProps,
} from './ReviewError';
let el;
jest.useFakeTimers('modern');
describe('ReviewError component', () => {
const props = {};
describe('component', () => {
beforeEach(() => {
props.reload = jest.fn();
});
describe('render tests', () => {
beforeEach(() => {
el = shallow(<ReviewError {...props} />);
});
test('snapshot', () => {
expect(el).toMatchSnapshot();
});
});
});
describe('mapDispatchToProps', () => {
it('loads reload from thunkActions.grading.reloadSubmission', () => {
expect(mapDispatchToProps.reload).toEqual(thunkActions.grading.loadSubmission);
});
});
});

View File

@@ -0,0 +1,50 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { selectors, thunkActions } from 'data/redux';
import { RequestKeys } from 'data/constants/requests';
import messages from '../messages';
import ReviewError from './ReviewError';
/**
* <FetchErrors />
*/
export const FetchErrors = ({
isFailed,
reload,
}) => isFailed && (
<ReviewError
key="loadFailed"
actions={{
confirm: {
onClick: reload,
message: messages.reloadSubmission,
},
}}
headingMessage={messages.loadErrorHeading}
>
<FormattedMessage {...messages.loadErrorMessage} />
</ReviewError>
);
FetchErrors.defaultProps = {
};
FetchErrors.propTypes = {
// redux
isFailed: PropTypes.bool.isRequired,
reload: PropTypes.func.isRequired,
};
export const mapStateToProps = (state) => ({
isFailed: selectors.requests.isFailed(state, { requestKey: RequestKeys.fetchSubmission }),
});
export const mapDispatchToProps = {
reload: thunkActions.grading.loadSubmission,
};
export default connect(mapStateToProps, mapDispatchToProps)(FetchErrors);

View File

@@ -0,0 +1,61 @@
import React from 'react';
import { selectors, thunkActions } from 'data/redux';
import { RequestKeys } from 'data/constants/requests';
import {
FetchErrors,
mapStateToProps,
mapDispatchToProps,
} from './FetchErrors';
jest.mock('data/redux', () => ({
selectors: {
requests: {
isFailed: (...args) => ({ isFailed: args }),
},
},
thunkActions: {
grading: {
loadSubmission: jest.fn(),
},
},
}));
jest.mock('./ReviewError', () => 'ReviewError');
const requestKey = RequestKeys.fetchSubmission;
describe('FetchErrors component', () => {
const props = {
isFailed: false,
};
describe('component', () => {
beforeEach(() => {
props.reload = jest.fn();
});
describe('snapshots', () => {
test('snapshot: no failure', () => {
expect(<FetchErrors {...props} />).toMatchSnapshot();
});
test('snapshot: with failure', () => {
expect(<FetchErrors {...props} isFailed={false} />).toMatchSnapshot();
});
});
});
describe('mapStateToProps', () => {
let mapped;
const testState = { some: 'test-state' };
beforeEach(() => {
mapped = mapStateToProps(testState);
});
test('isFailed loads from requests.isFailed(fetchSubmission)', () => {
expect(mapped.isFailed).toEqual(selectors.requests.isFailed(testState, { requestKey }));
});
});
describe('mapDispatchToProps', () => {
it('loads reload from thunkActions.grading.loadSubmission', () => {
expect(mapDispatchToProps.reload).toEqual(thunkActions.grading.loadSubmission);
});
});
});

View File

@@ -0,0 +1,62 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Alert, Button } from '@edx/paragon';
import { Info } from '@edx/paragon/icons';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
const messageShape = PropTypes.shape({
id: PropTypes.string,
defaultMessage: PropTypes.string,
});
const ReviewError = ({
actions: {
cancel,
confirm,
},
headingMessage,
children,
}) => {
const actions = [];
if (cancel) {
actions.push((
<Button key="cancel" onClick={cancel.onClick} variant="outline-primary">
<FormattedMessage {...cancel.message} />
</Button>
));
}
if (confirm) {
actions.push((
<Button key="confirm" onClick={confirm.onClick}>
<FormattedMessage {...confirm.message} />
</Button>
));
}
return (
<Alert
variant="danger"
icon={Info}
actions={actions}
>
<Alert.Heading><FormattedMessage {...headingMessage} /></Alert.Heading>
<p>{children}</p>
</Alert>
);
};
ReviewError.propTypes = {
actions: PropTypes.shape({
cancel: PropTypes.shape({
onClick: PropTypes.func,
message: messageShape,
}),
confirm: PropTypes.shape({
onClick: PropTypes.func,
message: messageShape,
}),
}).isRequired,
headingMessage: messageShape.isRequired,
children: PropTypes.node.isRequired,
};
export default ReviewError;

View File

@@ -0,0 +1,92 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { actions, selectors, thunkActions } from 'data/redux';
import { RequestKeys, ErrorStatuses } from 'data/constants/requests';
import messages from './messages';
import ReviewError from './ReviewError';
/**
* <SubmitErrors />
*/
export class SubmitErrors extends React.Component {
constructor(props) {
super(props);
this.dismissError = this.dismissError.bind(this);
}
get gradeNotSubmitted() {
return {
confirm: { onClick: this.props.resubmit, message: messages.resubmitGrade },
headingMessage: messages.gradeNotSubmittedHeading,
contentMessage: messages.gradeNotSubmittedContent,
};
}
get errorSubmittingGrade() {
return {
headingMessage: messages.errorSubmittingGradeHeading,
contentMessage: messages.errorSubmittingGradeContent,
};
}
get errorProps() {
if (this.props.errorStatus === ErrorStatuses.badRequest) {
return this.gradeNotSubmitted;
}
if (this.props.errorStatus === ErrorStatuses.conflict) {
return this.errorSubmittingGrade;
}
// TODO: Network-Log an error here for unhandled error type
return this.gradeNotSubmitted;
}
dismissError() {
this.props.clearRequest({ requestKey: RequestKeys.submitGrade });
}
render() {
if (!this.props.errorStatus) {
return null;
}
const props = this.errorProps;
return (
<ReviewError
actions={{
cancel: { onClick: this.dismissError, message: messages.dismiss },
confirm: props.confirm,
}}
headingMessage={props.headingMessage}
>
<FormattedMessage {...props.contentMessage} />
</ReviewError>
);
}
}
SubmitErrors.defaultProps = {
errorStatus: undefined,
};
SubmitErrors.propTypes = {
// redux
clearRequest: PropTypes.func.isRequired,
errorStatus: PropTypes.number,
resubmit: PropTypes.func.isRequired,
};
const requestKey = RequestKeys.submitGrade;
export const mapStateToProps = (state) => ({
errorStatus: selectors.requests.errorStatus(state, { requestKey }),
});
export const mapDispatchToProps = {
clearRequest: actions.requests.clearRequest,
resubmit: thunkActions.grading.submitGrade,
};
export default connect(mapStateToProps, mapDispatchToProps)(SubmitErrors);

View File

@@ -0,0 +1,79 @@
import React from 'react';
import { shallow } from 'enzyme';
import { actions, selectors, thunkActions } from 'data/redux';
import { ErrorStatuses, RequestKeys } from 'data/constants/requests';
import {
SubmitErrors,
mapStateToProps,
mapDispatchToProps,
} from './SubmitErrors';
jest.mock('data/redux', () => ({
actions: {
requests: {
clearRequest: jest.fn().mockName('actions.requests.clearRequest'),
},
},
selectors: {
requests: {
errorStatus: (...args) => ({ errorStatus: args }),
},
},
thunkActions: {
grading: {
submitGrade: jest.fn().mockName('thunkActions.grading.submitGrade'),
},
},
}));
let el;
jest.mock('./ReviewError', () => 'ReviewError');
const requestKey = RequestKeys.submitGrade;
describe('SubmitErrors component', () => {
const props = {};
describe('component', () => {
beforeEach(() => {
props.resubmit = jest.fn();
props.clearRequest = jest.fn();
el = shallow(<SubmitErrors {...props} />);
el.instance().dismissError = jest.fn().mockName('this.dismissError');
});
describe('snapshots', () => {
test('snapshot: no failure', () => {
expect(el.instance().render()).toMatchSnapshot();
});
test('snapshot: with network failure', () => {
el.setProps({ errorStatus: ErrorStatuses.badRequest });
expect(el.instance().render()).toMatchSnapshot();
});
test('snapshot: with conflict failure', () => {
el.setProps({ errorStatus: ErrorStatuses.conflict });
expect(el.instance().render()).toMatchSnapshot();
});
});
});
describe('mapStateToProps', () => {
let mapped;
const testState = { some: 'test-state' };
beforeEach(() => {
mapped = mapStateToProps(testState);
});
test('errorStatus loads from requests.errorStatus(fetchSubmission)', () => {
expect(mapped.errorStatus).toEqual(
selectors.requests.errorStatus(testState, { requestKey }),
);
});
});
describe('mapDispatchToProps', () => {
it('loads clearRequest from actions.requests.clearRequest', () => {
expect(mapDispatchToProps.clearRequest).toEqual(actions.requests.clearRequest);
});
it('loads resubmit from thunkActions.grading.submitGrade', () => {
expect(mapDispatchToProps.resubmit).toEqual(thunkActions.grading.submitGrade);
});
});
});

View File

@@ -0,0 +1,15 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`FetchErrors component component snapshots snapshot: no failure 1`] = `
<FetchErrors
isFailed={false}
reload={[MockFunction]}
/>
`;
exports[`FetchErrors component component snapshots snapshot: with failure 1`] = `
<FetchErrors
isFailed={false}
reload={[MockFunction]}
/>
`;

View File

@@ -0,0 +1,72 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SubmitErrors component component snapshots snapshot: no failure 1`] = `null`;
exports[`SubmitErrors component component snapshots snapshot: with conflict failure 1`] = `
<ReviewError
actions={
Object {
"cancel": Object {
"message": Object {
"defaultMessage": "Dismiss",
"description": "Dismiss error action button text",
"id": "ora-grading.ReviewModal.dismiss",
},
"onClick": [MockFunction this.dismissError],
},
"confirm": undefined,
}
}
headingMessage={
Object {
"defaultMessage": "Error submitting grade",
"description": "Error Submitting Grade heading text",
"id": "ora-grading.ReviewModal.errorSubmittingGrade.Heading",
}
}
>
<FormattedMessage
defaultMessage="It looks like someone else got here first! Your grade submission has been rejected"
description="Error Submitting Grade content"
id="ora-grading.ReviewModal.errorSubmittingGrade.Content"
/>
</ReviewError>
`;
exports[`SubmitErrors component component snapshots snapshot: with network failure 1`] = `
<ReviewError
actions={
Object {
"cancel": Object {
"message": Object {
"defaultMessage": "Dismiss",
"description": "Dismiss error action button text",
"id": "ora-grading.ReviewModal.dismiss",
},
"onClick": [MockFunction this.dismissError],
},
"confirm": Object {
"message": Object {
"defaultMessage": "Resubmit grate",
"description": "Resubmit grade button after network failure",
"id": "ora-grading.ReviewModal.resubmitGrade",
},
"onClick": [MockFunction],
},
}
}
headingMessage={
Object {
"defaultMessage": "Grade not submitted",
"description": "Grade submission network error heading",
"id": "ora-grading.ReviewModal.gradeNotSubmitted.heading",
}
}
>
<FormattedMessage
defaultMessage="We're sorry, something went wrong when we tried to submit this grade. Please try again."
description="Grade submission network error message"
id="ora-grading.ReviewModal.gradeNotSubmitted.heading"
/>
</ReviewError>
`;

View File

@@ -0,0 +1,8 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ReviewErrors component component snapshot: no failure 1`] = `
<Fragment>
<FetchErrors />
<SubmitErrors />
</Fragment>
`;

View File

@@ -0,0 +1,20 @@
import React from 'react';
import FetchErrors from './FetchErrors';
import SubmitErrors from './SubmitErrors';
/**
* <ReviewErrors />
*/
export const ReviewErrors = () => (
<>
<FetchErrors />
<SubmitErrors />
</>
);
ReviewErrors.defaultProps = {
};
ReviewErrors.propTypes = {
};
export default ReviewErrors;

View File

@@ -0,0 +1,15 @@
import React from 'react';
import { shallow } from 'enzyme';
import { ReviewErrors } from '.';
jest.mock('./FetchErrors', () => 'FetchErrors');
jest.mock('./SubmitErrors', () => 'SubmitErrors');
describe('ReviewErrors component', () => {
describe('component', () => {
test('snapshot: no failure', () => {
expect(shallow(<ReviewErrors />)).toMatchSnapshot();
});
});
});

View File

@@ -0,0 +1,53 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
import { StrictDict } from 'utils';
const messages = defineMessages({
loadErrorHeading: {
id: 'ora-grading.ReviewModal.loadErrorHeading',
defaultMessage: 'Error loading submissions',
description: 'Submission response load failure alert header',
},
loadErrorMessage: {
id: 'ora-grading.ReviewModal.loadErrorMessage1',
defaultMessage: 'An error occurred while loading this submission. Try reloading this submission.',
description: 'Submission response load failure alert message',
},
reloadSubmission: {
id: 'ora-grading.ReviewModal.reloadSubmission',
defaultMessage: 'Reload submission',
description: 'Reload button text in case of network failure',
},
gradeNotSubmittedHeading: {
id: 'ora-grading.ReviewModal.gradeNotSubmitted.heading',
defaultMessage: 'Grade not submitted',
description: 'Grade submission network error heading',
},
gradeNotSubmittedContent: {
id: 'ora-grading.ReviewModal.gradeNotSubmitted.heading',
defaultMessage: "We're sorry, something went wrong when we tried to submit this grade. Please try again.",
description: 'Grade submission network error message',
},
resubmitGrade: {
id: 'ora-grading.ReviewModal.resubmitGrade',
defaultMessage: 'Resubmit grate',
description: 'Resubmit grade button after network failure',
},
dismiss: {
id: 'ora-grading.ReviewModal.dismiss',
defaultMessage: 'Dismiss',
description: 'Dismiss error action button text',
},
errorSubmittingGradeHeading: {
id: 'ora-grading.ReviewModal.errorSubmittingGrade.Heading',
defaultMessage: 'Error submitting grade',
description: 'Error Submitting Grade heading text',
},
errorSubmittingGradeContent: {
id: 'ora-grading.ReviewModal.errorSubmittingGrade.Content',
defaultMessage: 'It looks like someone else got here first! Your grade submission has been rejected',
description: 'Error Submitting Grade content',
},
});
export default StrictDict(messages);

View File

@@ -1,9 +1,18 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ReviewContent component component render tests snapshot (hide rubric) 1`] = `
exports[`ReviewContent component component render tests snapshot: failed, showRubric (errors only) 1`] = `
<div
className="content-block"
>
<ReviewErrors />
</div>
`;
exports[`ReviewContent component component render tests snapshot: hide rubric 1`] = `
<div
className="content-block"
>
<ReviewErrors />
<Row
className="flex-nowrap"
>
@@ -15,10 +24,11 @@ exports[`ReviewContent component component render tests snapshot (hide rubric) 1
</div>
`;
exports[`ReviewContent component component render tests snapshot (show rubric) 1`] = `
exports[`ReviewContent component component render tests snapshot: show rubric 1`] = `
<div
className="content-block"
>
<ReviewErrors />
<Row
className="flex-nowrap"
>

View File

@@ -1,35 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ReviewError component component render tests snapshot 1`] = `
<Alert
actions={
Array [
<Button
onClick={[MockFunction]}
>
<FormattedMessage
defaultMessage="Reload submission"
description="Reload button text in case of network failure"
id="ora-grading.ReviewModal.reloadSubmission"
/>
</Button>,
]
}
variant="danger"
>
<Alert.Heading>
<FormattedMessage
defaultMessage="Error loading submissions"
description="Submission response load failure alert header"
id="ora-grading.ReviewModal.loadErrorHeading"
/>
</Alert.Heading>
<p>
<FormattedMessage
defaultMessage="An error occurred while loading this submission. Try reloading this submission."
description="Submission response load failure alert message"
id="ora-grading.ReviewModal.loadErrorMessage1"
/>
</p>
</Alert>
`;

View File

@@ -30,9 +30,7 @@ exports[`ReviewModal component component snapshots error 1`] = `
onClose={[MockFunction this.onClose]}
title="test-ora-name"
>
<React.Fragment>
<ReviewError />
</React.Fragment>
<ReviewContent />
</FullscreenModal>
`;
@@ -45,7 +43,7 @@ exports[`ReviewModal component component snapshots loading 1`] = `
onClose={[MockFunction this.onClose]}
title="test-ora-name"
>
<React.Fragment />
<ReviewContent />
<LoadingMessage
message={
Object {
@@ -67,8 +65,6 @@ exports[`ReviewModal component component snapshots success 1`] = `
onClose={[MockFunction this.onClose]}
title="test-ora-name"
>
<React.Fragment>
<ReviewContent />
</React.Fragment>
<ReviewContent />
</FullscreenModal>
`;

View File

@@ -9,7 +9,6 @@ import { RequestKeys } from 'data/constants/requests';
import LoadingMessage from 'components/LoadingMessage';
import ReviewActions from 'containers/ReviewActions';
import ReviewError from './ReviewError';
import ReviewContent from './ReviewContent';
import messages from './messages';
@@ -29,11 +28,11 @@ export class ReviewModal extends React.Component {
}
get isLoading() {
return !(this.props.hasError || this.props.isLoaded);
return !(this.props.errorStatus || this.props.isLoaded);
}
render() {
const { isOpen, isLoaded, hasError } = this.props;
const { isOpen, isLoaded, errorStatus } = this.props;
return (
<FullscreenModal
title={this.props.oraName}
@@ -43,19 +42,15 @@ export class ReviewModal extends React.Component {
className="review-modal"
modalBodyClassName="review-modal-body"
>
{isOpen && (
<>
{isLoaded && <ReviewContent />}
{hasError && <ReviewError />}
</>
)}
{isOpen && <ReviewContent />}
{/* even if the modal is closed, in case we want to add transitions later */}
{!(isLoaded || hasError) && <LoadingMessage message={messages.loadingResponse} />}
{!(isLoaded || errorStatus) && <LoadingMessage message={messages.loadingResponse} />}
</FullscreenModal>
);
}
}
ReviewModal.defaultProps = {
errorStatus: null,
response: null,
};
ReviewModal.propTypes = {
@@ -66,7 +61,7 @@ ReviewModal.propTypes = {
}),
setShowReview: PropTypes.func.isRequired,
isLoaded: PropTypes.bool.isRequired,
hasError: PropTypes.bool.isRequired,
errorStatus: PropTypes.number,
};
export const mapStateToProps = (state) => ({
@@ -74,7 +69,7 @@ export const mapStateToProps = (state) => ({
oraName: selectors.app.ora.name(state),
response: selectors.grading.selected.response(state),
isLoaded: selectors.requests.isCompleted(state, { requestKey: RequestKeys.fetchSubmission }),
hasError: selectors.requests.isFailed(state, { requestKey: RequestKeys.fetchSubmission }),
errorStatus: selectors.requests.errorStatus(state, { requestKey: RequestKeys.fetchSubmission }),
});
export const mapDispatchToProps = {

View File

@@ -24,7 +24,7 @@ jest.mock('data/redux', () => ({
},
requests: {
isCompleted: (...args) => ({ isCompleted: args }),
isFailed: (...args) => ({ isFailed: args }),
errorStatus: (...args) => ({ errorStatus: args }),
},
},
actions: {
@@ -35,7 +35,6 @@ jest.mock('data/redux', () => ({
}));
jest.mock('containers/ReviewActions', () => 'ReviewActions');
jest.mock('./ReviewError', () => 'ReviewError');
jest.mock('./ReviewContent', () => 'ReviewContent');
jest.mock('components/LoadingMessage', () => 'LoadingMessage');
@@ -48,7 +47,6 @@ describe('ReviewModal component', () => {
response: { text: (<div>some text</div>) },
showRubric: false,
isLoaded: false,
hasError: false,
};
describe('component', () => {
beforeEach(() => {
@@ -69,7 +67,7 @@ describe('ReviewModal component', () => {
expect(render()).toMatchSnapshot();
});
test('error', () => {
el.setProps({ isOpen: true, hasError: true });
el.setProps({ isOpen: true, errorStatus: 200 });
expect(render()).toMatchSnapshot();
});
test('success', () => {
@@ -96,8 +94,8 @@ describe('ReviewModal component', () => {
test('isLoaded loads from requests.isCompleted(fetchSubmission)', () => {
expect(mapped.isLoaded).toEqual(selectors.requests.isCompleted(testState, { requestKey }));
});
test('hasError loads from requests.isFailed(fetchSubmission)', () => {
expect(mapped.hasError).toEqual(selectors.requests.isFailed(testState, { requestKey }));
test('errorStatus loads from requests.errorStatus(fetchSubmission)', () => {
expect(mapped.errorStatus).toEqual(selectors.requests.errorStatus(testState, { requestKey }));
});
});
describe('mapDispatchToProps', () => {

View File

@@ -1,4 +1,5 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
import { StrictDict } from 'utils';
const messages = defineMessages({
loadErrorHeading: {
@@ -21,6 +22,26 @@ const messages = defineMessages({
defaultMessage: 'Loading response',
description: 'loading text for submission response review screen',
},
gradeNotSubmittedHeading: {
id: 'ora-grading.ReviewModal.gradeNotSubmitted.heading',
defaultMessage: 'Grade not submitted',
description: 'Grade submission network error heading',
},
gradeNotSubmittedContent: {
id: 'ora-grading.ReviewModal.gradeNotSubmitted.heading',
defaultMessage: "We're sorry, something went wrong when we tried to submit this grade. Please try again.",
description: 'Grade submission network error message',
},
resubmitGrade: {
id: 'ora-grading.ReviewModal.resubmitGrade',
defaultMessage: 'Resubmit grate',
description: 'Resubmit grade button after network failure',
},
dismiss: {
id: 'ora-grading.ReviewModal.dismiss',
defaultMessage: 'Dismiss',
description: 'Dismiss error action button text',
},
});
export default messages;
export default StrictDict(messages);