diff --git a/src/containers/CriterionContainer/CriterionFeedback.jsx b/src/containers/CriterionContainer/CriterionFeedback.jsx
index b8b7e7e..d3f50f1 100644
--- a/src/containers/CriterionContainer/CriterionFeedback.jsx
+++ b/src/containers/CriterionContainer/CriterionFeedback.jsx
@@ -28,19 +28,30 @@ export class CriterionFeedback extends React.Component {
translate = (msg) => this.props.intl.formatMessage(msg);
render() {
- const { config, isGrading, value } = this.props;
+ const {
+ config, isGrading, value, valueIsInvalid,
+ } = this.props;
if (config === feedbackRequirement.disabled) {
return null;
}
return (
-
+
+
+ {valueIsInvalid && (
+
+ {this.translate(messages.criterionFeedbackError)}
+
+ )}
+
);
}
}
@@ -58,15 +69,19 @@ CriterionFeedback.propTypes = {
config: PropTypes.string.isRequired,
setValue: PropTypes.func.isRequired,
value: PropTypes.string,
+ valueIsInvalid: PropTypes.bool.isRequired,
};
export const mapStateToProps = (state, { orderNum }) => ({
config: selectors.app.rubric.criterionFeedbackConfig(state, { orderNum }),
value: selectors.grading.selected.criterionFeedback(state, { orderNum }),
+ valueIsInvalid: selectors.grading.selected.criterionFeedbackIsInvalid(state, { orderNum }),
});
export const mapDispatchToProps = {
setValue: actions.grading.setCriterionFeedback,
};
-export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(CriterionFeedback));
+export default injectIntl(
+ connect(mapStateToProps, mapDispatchToProps)(CriterionFeedback),
+);
diff --git a/src/containers/CriterionContainer/CriterionFeedback.test.jsx b/src/containers/CriterionContainer/CriterionFeedback.test.jsx
index b1c3f63..3b4c537 100644
--- a/src/containers/CriterionContainer/CriterionFeedback.test.jsx
+++ b/src/containers/CriterionContainer/CriterionFeedback.test.jsx
@@ -25,7 +25,7 @@ jest.mock('data/redux/grading/selectors', () => ({
criterionFeedback: jest.fn((...args) => ({
selectedCriterionFeedback: args,
})),
- gradeStatus: jest.fn((...args) => ({ selectedGradeStatus: args })),
+ criterionFeedbackIsInvalid: jest.fn((...args) => ({ selectedFeedbackIsInvalid: args })),
},
}));
@@ -38,6 +38,7 @@ describe('Criterion Feedback', () => {
value: 'some value',
gradeStatus: gradeStatuses.ungraded,
setValue: jest.fn().mockName('this.props.setValue'),
+ valueIsInvalid: false,
};
let el;
beforeEach(() => {
@@ -57,6 +58,13 @@ describe('Criterion Feedback', () => {
expect(el.instance().render()).toMatchSnapshot();
});
+ test('feedback value is invalid', () => {
+ el.setProps({
+ valueIsInvalid: true,
+ });
+ expect(el.instance().render()).toMatchSnapshot();
+ });
+
test('is configure to disabled', () => {
el.setProps({
config: feedbackRequirement.disabled,
@@ -69,16 +77,28 @@ describe('Criterion Feedback', () => {
describe('render', () => {
test('is grading (the feedback input is not disabled)', () => {
expect(el.isEmptyRender()).toEqual(false);
- expect(el.prop('value')).toEqual(props.value);
- expect(el.prop('disabled')).toEqual(false);
+ expect(el.instance().props.value).toEqual(props.value);
+ const controlEl = el.find('.feedback-input');
+ expect(controlEl.prop('disabled')).toEqual(false);
+ expect(controlEl.prop('value')).toEqual(props.value);
});
test('is graded (the input is disabled)', () => {
el.setProps({
isGrading: false,
gradeStatus: gradeStatuses.graded,
});
- expect(el.prop('value')).toEqual(props.value);
- expect(el.prop('disabled')).toEqual(true);
+ expect(el.instance().props.value).toEqual(props.value);
+ const controlEl = el.find('.feedback-input');
+ expect(controlEl.prop('disabled')).toEqual(true);
+ expect(controlEl.prop('value')).toEqual(props.value);
+ });
+ test('is having invalid feedback (feedback get render)', () => {
+ el.setProps({
+ valueIsInvalid: true,
+ });
+ const feedbackErrorEl = el.find('.feedback-error-msg');
+ expect(el.instance().props.valueIsInvalid).toEqual(true);
+ expect(feedbackErrorEl).toBeDefined();
});
test('is configure to disabled (the input does not get render)', () => {
el.setProps({
@@ -108,18 +128,21 @@ describe('Criterion Feedback', () => {
beforeEach(() => {
mapped = mapStateToProps(testState, ownProps);
});
-
test('selectors.app.rubric.criterionFeedbackConfig', () => {
expect(mapped.config).toEqual(
selectors.app.rubric.criterionFeedbackConfig(testState, ownProps),
);
});
-
test('selector.grading.selected.criterionFeedback', () => {
expect(mapped.value).toEqual(
selectors.grading.selected.criterionFeedback(testState, ownProps),
);
});
+ test('selector.grading.selected.criterionFeedbackIsInvalid', () => {
+ expect(mapped.valueIsInvalid).toEqual(
+ selectors.grading.selected.criterionFeedbackIsInvalid(testState, ownProps),
+ );
+ });
});
describe('mapDispatchToProps', () => {
diff --git a/src/containers/CriterionContainer/RadioCriterion.jsx b/src/containers/CriterionContainer/RadioCriterion.jsx
index 746a226..43db992 100644
--- a/src/containers/CriterionContainer/RadioCriterion.jsx
+++ b/src/containers/CriterionContainer/RadioCriterion.jsx
@@ -30,6 +30,7 @@ export class RadioCriterion extends React.Component {
data,
intl,
isGrading,
+ radioIsInvalid,
} = this.props;
return (
<>
@@ -46,6 +47,11 @@ export class RadioCriterion extends React.Component {
{option.label}
))}
+ {radioIsInvalid && (
+
+ {intl.formatMessage(messages.rubricSelectedError)}
+
+ )}
>
);
@@ -84,11 +90,13 @@ RadioCriterion.propTypes = {
feedback: PropTypes.string,
}),
setCriterionOption: PropTypes.func.isRequired,
+ radioIsInvalid: PropTypes.bool.isRequired,
};
export const mapStateToProps = (state, { orderNum }) => ({
config: selectors.app.rubric.criterionConfig(state, { orderNum }),
data: selectors.grading.selected.criterionGradeData(state, { orderNum }),
+ radioIsInvalid: selectors.grading.selected.criterionSelectedIsInvalid(state, { orderNum }),
});
export const mapDispatchToProps = {
diff --git a/src/containers/CriterionContainer/RadioCriterion.test.jsx b/src/containers/CriterionContainer/RadioCriterion.test.jsx
index eee58ad..13abfad 100644
--- a/src/containers/CriterionContainer/RadioCriterion.test.jsx
+++ b/src/containers/CriterionContainer/RadioCriterion.test.jsx
@@ -21,6 +21,9 @@ jest.mock('data/redux/grading/selectors', () => ({
criterionGradeData: jest.fn((...args) => ({
selectedCriterionGradeData: args,
})),
+ criterionSelectedIsInvalid: jest.fn((...args) => ({
+ selectedCriterionSelectedIsInvalid: args,
+ })),
},
}));
@@ -55,6 +58,7 @@ describe('Radio Criterion Container', () => {
feedback: 'data feedback',
},
setCriterionOption: jest.fn().mockName('this.props.setCriterionOption'),
+ radioIsInvalid: false,
};
let el;
@@ -73,6 +77,13 @@ describe('Radio Criterion Container', () => {
});
expect(el.instance().render()).toMatchSnapshot();
});
+
+ test('radio contain invalid response', () => {
+ el.setProps({
+ radioIsInvalid: true,
+ });
+ expect(el.instance().render()).toMatchSnapshot();
+ });
});
describe('component', () => {
@@ -93,6 +104,16 @@ describe('Radio Criterion Container', () => {
expect(optionsEl.length).toEqual(props.config.options.length);
optionsEl.forEach((optionEl) => expect(optionEl.prop('disabled')).toEqual(true));
});
+
+ test('radio contain invalid response (error response get render)', () => {
+ el.setProps({
+ radioIsInvalid: true,
+ });
+ expect(el.isEmptyRender()).toEqual(false);
+ const radioErrorEl = el.find('.feedback-error-msg');
+ expect(el.instance().props.radioIsInvalid).toEqual(true);
+ expect(radioErrorEl).toBeDefined();
+ });
});
describe('behavior', () => {
@@ -126,6 +147,11 @@ describe('Radio Criterion Container', () => {
selectors.grading.selected.criterionGradeData(testState, ownProps),
);
});
+ test('selectors.grading.selected.criterionSelectedIsInvalid', () => {
+ expect(mapped.radioIsInvalid).toEqual(
+ selectors.grading.selected.criterionSelectedIsInvalid(testState, ownProps),
+ );
+ });
});
describe('mapDispatchToProps', () => {
diff --git a/src/containers/CriterionContainer/__snapshots__/CriterionFeedback.test.jsx.snap b/src/containers/CriterionContainer/__snapshots__/CriterionFeedback.test.jsx.snap
index 4ba98df..30332df 100644
--- a/src/containers/CriterionContainer/__snapshots__/CriterionFeedback.test.jsx.snap
+++ b/src/containers/CriterionContainer/__snapshots__/CriterionFeedback.test.jsx.snap
@@ -1,25 +1,48 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`Criterion Feedback snapshot feedback value is invalid 1`] = `
+
+
+
+ The feedback is required
+
+
+`;
+
exports[`Criterion Feedback snapshot is configure to disabled 1`] = `null`;
exports[`Criterion Feedback snapshot is graded 1`] = `
-
+
+
+
`;
exports[`Criterion Feedback snapshot is grading 1`] = `
-
+
+
+
`;
diff --git a/src/containers/CriterionContainer/__snapshots__/RadioCriterion.test.jsx.snap b/src/containers/CriterionContainer/__snapshots__/RadioCriterion.test.jsx.snap
index b16e6c9..1d6ad07 100644
--- a/src/containers/CriterionContainer/__snapshots__/RadioCriterion.test.jsx.snap
+++ b/src/containers/CriterionContainer/__snapshots__/RadioCriterion.test.jsx.snap
@@ -55,3 +55,37 @@ exports[`Radio Criterion Container snapshot is not grading 1`] = `
`;
+
+exports[`Radio Criterion Container snapshot radio contain invalid response 1`] = `
+
+
+
+ this label
+
+
+ this label 2
+
+
+ Rubric selection is required
+
+
+
+`;
diff --git a/src/containers/CriterionContainer/messages.js b/src/containers/CriterionContainer/messages.js
index 22263cc..d44e7aa 100644
--- a/src/containers/CriterionContainer/messages.js
+++ b/src/containers/CriterionContainer/messages.js
@@ -16,6 +16,16 @@ const messages = defineMessages({
defaultMessage: '{points} points',
description: 'criterion option point value display',
},
+ rubricSelectedError: {
+ id: 'ora-grading.RadioCriterion.rubricSelectedError',
+ defaultMessage: 'Rubric selection is required',
+ description: 'Error message when rubric radio did not get selected',
+ },
+ criterionFeedbackError: {
+ id: 'ora-grading.CriterionFeedback.criterionFeedbackError',
+ defaultMessage: 'The feedback is required',
+ description: 'Error message when feedback is required',
+ },
});
export default messages;
diff --git a/src/containers/Rubric/RubricFeedback.jsx b/src/containers/Rubric/RubricFeedback.jsx
index b580934..dc351b4 100644
--- a/src/containers/Rubric/RubricFeedback.jsx
+++ b/src/containers/Rubric/RubricFeedback.jsx
@@ -3,7 +3,11 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Form } from '@edx/paragon';
-import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
+import {
+ FormattedMessage,
+ injectIntl,
+ intlShape,
+} from '@edx/frontend-platform/i18n';
import { feedbackRequirement } from 'data/services/lms/constants';
import { actions, selectors } from 'data/redux';
@@ -32,10 +36,7 @@ export class RubricFeedback extends React.Component {
render() {
const {
- isGrading,
- value,
- feedbackPrompt,
- config,
+ isGrading, value, feedbackPrompt, config, valueIsInvalid,
} = this.props;
if (config === feedbackRequirement.disabled) {
@@ -59,6 +60,11 @@ export class RubricFeedback extends React.Component {
onChange={this.onChange}
disabled={!isGrading}
/>
+ {valueIsInvalid && (
+
+
+
+ )}
);
}
@@ -76,12 +82,14 @@ RubricFeedback.propTypes = {
isGrading: PropTypes.bool.isRequired,
setValue: PropTypes.func.isRequired,
value: PropTypes.string,
+ valueIsInvalid: PropTypes.bool.isRequired,
feedbackPrompt: PropTypes.string.isRequired,
};
export const mapStateToProps = (state) => ({
isGrading: selectors.app.isGrading(state),
value: selectors.grading.selected.overallFeedback(state),
+ valueIsInvalid: selectors.grading.selected.overallFeedbackIsInvalid(state),
config: selectors.app.rubric.feedbackConfig(state),
feedbackPrompt: selectors.app.rubric.feedbackPrompt(state),
});
@@ -90,4 +98,6 @@ export const mapDispatchToProps = {
setValue: actions.grading.setRubricFeedback,
};
-export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(RubricFeedback));
+export default injectIntl(
+ connect(mapStateToProps, mapDispatchToProps)(RubricFeedback),
+);
diff --git a/src/containers/Rubric/RubricFeedback.test.jsx b/src/containers/Rubric/RubricFeedback.test.jsx
index c7feddc..0e6a656 100644
--- a/src/containers/Rubric/RubricFeedback.test.jsx
+++ b/src/containers/Rubric/RubricFeedback.test.jsx
@@ -33,6 +33,9 @@ jest.mock('data/redux/grading/selectors', () => ({
overallFeedback: jest.fn((...args) => ({
selectedOverallFeedback: args,
})),
+ overallFeedbackIsInvalid: jest.fn((...args) => ({
+ selectedOverallFeedbackIsInvalid: args,
+ })),
},
}));
@@ -42,6 +45,7 @@ describe('Rubric Feedback component', () => {
config: 'config stirng',
isGrading: true,
value: 'some value',
+ valueIsInvalid: false,
feedbackPrompt: 'feedback prompt',
gradeStatus: gradeStatuses.ungraded,
setValue: jest.fn().mockName('this.props.setValue'),
@@ -64,6 +68,13 @@ describe('Rubric Feedback component', () => {
expect(el.instance().render()).toMatchSnapshot();
});
+ test('feedback value is invalid', () => {
+ el.setProps({
+ valueIsInvalid: true,
+ });
+ expect(el.instance().render()).toMatchSnapshot();
+ });
+
test('is configure to disabled', () => {
el.setProps({
config: feedbackRequirement.disabled,
@@ -91,6 +102,16 @@ describe('Rubric Feedback component', () => {
expect(input.prop('disabled')).toEqual(true);
expect(input.prop('value')).toEqual(props.value);
});
+
+ test('is having invalid feedback (feedback get render)', () => {
+ el.setProps({
+ valueIsInvalid: true,
+ });
+ const feedbackErrorEl = el.find('.feedback-error-msg');
+ expect(el.instance().props.valueIsInvalid).toEqual(true);
+ expect(feedbackErrorEl).toBeDefined();
+ });
+
test('is configure to disabled (this input does not get render)', () => {
el.setProps({
config: feedbackRequirement.disabled,
@@ -133,6 +154,12 @@ describe('Rubric Feedback component', () => {
);
});
+ test('selectors.grading.selected.overallFeedbackIsInvalid', () => {
+ expect(mapped.valueIsInvalid).toEqual(
+ selectors.grading.selected.overallFeedbackIsInvalid(testState),
+ );
+ });
+
test('selectors.app.rubric.feedbackPrompt', () => {
expect(mapped.feedbackPrompt).toEqual(
selectors.app.rubric.feedbackPrompt(testState),
diff --git a/src/containers/Rubric/__snapshots__/RubricFeedback.test.jsx.snap b/src/containers/Rubric/__snapshots__/RubricFeedback.test.jsx.snap
index b25f2ee..f698267 100644
--- a/src/containers/Rubric/__snapshots__/RubricFeedback.test.jsx.snap
+++ b/src/containers/Rubric/__snapshots__/RubricFeedback.test.jsx.snap
@@ -1,5 +1,46 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`Rubric Feedback component snapshot feedback value is invalid 1`] = `
+
+
+
+
+
+
+
+ feedback prompt
+
+
+
+
+
+
+
+
+`;
+
exports[`Rubric Feedback component snapshot is configure to disabled 1`] = `null`;
exports[`Rubric Feedback component snapshot is graded 1`] = `
diff --git a/src/containers/Rubric/__snapshots__/index.test.jsx.snap b/src/containers/Rubric/__snapshots__/index.test.jsx.snap
index 7fc53d4..db589fc 100644
--- a/src/containers/Rubric/__snapshots__/index.test.jsx.snap
+++ b/src/containers/Rubric/__snapshots__/index.test.jsx.snap
@@ -48,7 +48,9 @@ exports[`Rubric Container snapshot is grading 1`] = `
-