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:
@@ -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),
|
||||
});
|
||||
|
||||
|
||||
@@ -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 }));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
50
src/containers/ReviewModal/ReviewErrors/FetchErrors.jsx
Normal file
50
src/containers/ReviewModal/ReviewErrors/FetchErrors.jsx
Normal 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);
|
||||
61
src/containers/ReviewModal/ReviewErrors/FetchErrors.test.jsx
Normal file
61
src/containers/ReviewModal/ReviewErrors/FetchErrors.test.jsx
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
62
src/containers/ReviewModal/ReviewErrors/ReviewError.jsx
Normal file
62
src/containers/ReviewModal/ReviewErrors/ReviewError.jsx
Normal 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;
|
||||
92
src/containers/ReviewModal/ReviewErrors/SubmitErrors.jsx
Normal file
92
src/containers/ReviewModal/ReviewErrors/SubmitErrors.jsx
Normal 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);
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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]}
|
||||
/>
|
||||
`;
|
||||
@@ -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>
|
||||
`;
|
||||
@@ -0,0 +1,8 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ReviewErrors component component snapshot: no failure 1`] = `
|
||||
<Fragment>
|
||||
<FetchErrors />
|
||||
<SubmitErrors />
|
||||
</Fragment>
|
||||
`;
|
||||
20
src/containers/ReviewModal/ReviewErrors/index.jsx
Normal file
20
src/containers/ReviewModal/ReviewErrors/index.jsx
Normal 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;
|
||||
15
src/containers/ReviewModal/ReviewErrors/index.test.jsx
Normal file
15
src/containers/ReviewModal/ReviewErrors/index.test.jsx
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
53
src/containers/ReviewModal/ReviewErrors/messages.js
Normal file
53
src/containers/ReviewModal/ReviewErrors/messages.js
Normal 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);
|
||||
@@ -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"
|
||||
>
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
@@ -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>
|
||||
`;
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user