From 5ee8b756695ba4691ff3c75b8d37f91985aa7d39 Mon Sep 17 00:00:00 2001 From: Ben Warzeski Date: Fri, 29 Oct 2021 10:23:58 -0400 Subject: [PATCH 1/8] fix: setupTest intlShape mock --- src/setupTest.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/setupTest.js b/src/setupTest.js index 89fdeab..16ce713 100755 --- a/src/setupTest.js +++ b/src/setupTest.js @@ -9,10 +9,11 @@ Enzyme.configure({ adapter: new Adapter() }); jest.mock('@edx/frontend-platform/i18n', () => { const i18n = jest.requireActual('@edx/frontend-platform/i18n'); + const PropTypes = jest.requireActual('prop-types'); return { ...i18n, - intlShape: jest.requireActual('prop-types').shape({ - formatMessage: jest.fn(msg => msg.defaultMessage), + intlShape: PropTypes.shape({ + formatMessage: PropTypes.func, }), defineMessages: m => m, FormattedMessage: () => 'FormattedMessage', From 149fb5fad72f75df8768b11bac66891895e8bd58 Mon Sep 17 00:00:00 2001 From: Ben Warzeski Date: Fri, 29 Oct 2021 10:24:11 -0400 Subject: [PATCH 2/8] feat: add formatMessage testUtil for testing intl usage --- src/testUtils.js | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/testUtils.js diff --git a/src/testUtils.js b/src/testUtils.js new file mode 100644 index 0000000..6bed340 --- /dev/null +++ b/src/testUtils.js @@ -0,0 +1,10 @@ +// eslint-disable-next-line import/prefer-default-export +export const formatMessage = (msg, values) => { + let message = msg.defaultMessage; + if (values === undefined) { return message; } + Object.keys(values).forEach(key => { + // eslint-disable-next-line + message = message.replace(`{${key}}`, values[key]); + }); + return message; +}; From 3f497295f4d1fc5bffe34b9f7bba5bdad1511fd9 Mon Sep 17 00:00:00 2001 From: Ben Warzeski Date: Fri, 29 Oct 2021 10:24:49 -0400 Subject: [PATCH 3/8] feat: i18n InfoPopover --- .../__snapshots__/index.test.jsx.snap} | 4 ++-- .../{InfoPopover.jsx => InfoPopover/index.jsx} | 13 ++++++++----- .../index.test.jsx} | 9 +++++---- src/components/InfoPopover/messages.js | 11 +++++++++++ 4 files changed, 26 insertions(+), 11 deletions(-) rename src/components/{__snapshots__/InfoPopover.test.jsx.snap => InfoPopover/__snapshots__/index.test.jsx.snap} (89%) rename src/components/{InfoPopover.jsx => InfoPopover/index.jsx} (70%) rename src/components/{InfoPopover.test.jsx => InfoPopover/index.test.jsx} (68%) create mode 100644 src/components/InfoPopover/messages.js diff --git a/src/components/__snapshots__/InfoPopover.test.jsx.snap b/src/components/InfoPopover/__snapshots__/index.test.jsx.snap similarity index 89% rename from src/components/__snapshots__/InfoPopover.test.jsx.snap rename to src/components/InfoPopover/__snapshots__/index.test.jsx.snap index 50e8a37..57524ce 100644 --- a/src/components/__snapshots__/InfoPopover.test.jsx.snap +++ b/src/components/InfoPopover/__snapshots__/index.test.jsx.snap @@ -18,8 +18,8 @@ exports[`Info Popover Component snapshot 1`] = ` trigger="focus" > */ -export const InfoPopover = ({ children }) => ( +export const InfoPopover = ({ children, intl }) => ( ( )} > {}} /> @@ -35,12 +38,12 @@ export const InfoPopover = ({ children }) => ( ); InfoPopover.defaultProps = {}; - InfoPopover.propTypes = { children: PropTypes.oneOfType([ PropTypes.arrayOf(PropTypes.node), PropTypes.node, ]).isRequired, + intl: intlShape.isRequired, }; -export default InfoPopover; +export default injectIntl(InfoPopover); diff --git a/src/components/InfoPopover.test.jsx b/src/components/InfoPopover/index.test.jsx similarity index 68% rename from src/components/InfoPopover.test.jsx rename to src/components/InfoPopover/index.test.jsx index 02b6958..4159700 100644 --- a/src/components/InfoPopover.test.jsx +++ b/src/components/InfoPopover/index.test.jsx @@ -1,7 +1,8 @@ import React from 'react'; import { shallow } from 'enzyme'; -import InfoPopover from './InfoPopover'; +import { formatMessage } from 'testUtils'; +import { InfoPopover } from '.'; jest.mock('@edx/paragon', () => ({ OverlayTrigger: () => 'OverlayTrigger', @@ -18,17 +19,17 @@ jest.mock('@edx/paragon/icons', () => ({ describe('Info Popover Component', () => { const child =
Children component
; test('snapshot', () => { - expect(shallow({child})).toMatchSnapshot(); + expect(shallow({child})).toMatchSnapshot(); }); describe('Component', () => { let el; beforeEach(() => { - el = shallow({child}); + el = shallow({child}); }); test('Test component render', () => { expect(el.length).toEqual(1); - expect(el.find('.criteria-help-icon').length).toEqual(1); + expect(el.find('.esg-help-icon').length).toEqual(1); }); }); }); diff --git a/src/components/InfoPopover/messages.js b/src/components/InfoPopover/messages.js new file mode 100644 index 0000000..3989e7a --- /dev/null +++ b/src/components/InfoPopover/messages.js @@ -0,0 +1,11 @@ +import { defineMessages } from '@edx/frontend-platform/i18n'; + +const messages = defineMessages({ + altText: { + id: 'ora-grading.InfoPopover.alt-text', + defaultMessage: 'Display more info', + description: 'Info popover trigger element alt-text', + }, +}); + +export default messages; From b31d554be9b4a9baeb088b235c9f4a337e440f85 Mon Sep 17 00:00:00 2001 From: Ben Warzeski Date: Fri, 29 Oct 2021 10:25:10 -0400 Subject: [PATCH 4/8] feat: i18n CriterionContainer --- .../CriterionContainer/CriterionFeedback.jsx | 10 +++++++-- .../CriterionFeedback.test.jsx | 2 ++ .../CriterionContainer/RadioCriterion.jsx | 15 ++++++++++--- .../RadioCriterion.test.jsx | 4 +++- .../CriterionContainer/ReviewCriterion.jsx | 4 +++- .../ReviewCriterion.test.jsx | 8 ++++--- .../RadioCriterion.test.jsx.snap | 4 ++-- .../ReviewCriterion.test.jsx.snap | 22 +++++++++++++++++-- src/containers/CriterionContainer/messages.js | 21 ++++++++++++++++++ 9 files changed, 76 insertions(+), 14 deletions(-) create mode 100644 src/containers/CriterionContainer/messages.js diff --git a/src/containers/CriterionContainer/CriterionFeedback.jsx b/src/containers/CriterionContainer/CriterionFeedback.jsx index 2deb4b9..1fa5bfa 100644 --- a/src/containers/CriterionContainer/CriterionFeedback.jsx +++ b/src/containers/CriterionContainer/CriterionFeedback.jsx @@ -3,10 +3,12 @@ import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { Form } from '@edx/paragon'; +import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { feedbackRequirement } from 'data/services/lms/constants'; import actions from 'data/actions'; import selectors from 'data/selectors'; +import messages from './messages'; /** * @@ -24,6 +26,8 @@ export class CriterionFeedback extends React.Component { }); } + translate = (msg) => this.props.intl.formatMessage(msg); + render() { const { config, isGrading, value } = this.props; if (config === feedbackRequirement.disabled) { @@ -33,7 +37,7 @@ export class CriterionFeedback extends React.Component { ({ describe('Criterion Feedback', () => { const props = { + intl: { formatMessage }, orderNum: 1, config: 'config string', isGrading: true, diff --git a/src/containers/CriterionContainer/RadioCriterion.jsx b/src/containers/CriterionContainer/RadioCriterion.jsx index cea8c33..53c65aa 100644 --- a/src/containers/CriterionContainer/RadioCriterion.jsx +++ b/src/containers/CriterionContainer/RadioCriterion.jsx @@ -3,9 +3,11 @@ import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { Form } from '@edx/paragon'; +import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import actions from 'data/actions'; import selectors from 'data/selectors'; +import messages from './messages'; /** * @@ -24,7 +26,12 @@ export class RadioCriterion extends React.Component { } render() { - const { config, data, isGrading } = this.props; + const { + config, + data, + intl, + isGrading, + } = this.props; return ( <> @@ -33,7 +40,7 @@ export class RadioCriterion extends React.Component { className="criteria-option" key={option.name} value={option.name} - description={`${option.points} points`} + description={intl.formatMessage(messages.optionPoints, { points: option.points })} onChange={this.onChange} disabled={!isGrading} > @@ -56,6 +63,8 @@ RadioCriterion.defaultProps = { RadioCriterion.propTypes = { orderNum: PropTypes.number.isRequired, isGrading: PropTypes.bool.isRequired, + // injected + intl: intlShape.isRequired, // redux config: PropTypes.shape({ prompt: PropTypes.string, @@ -87,4 +96,4 @@ export const mapDispatchToProps = { setCriterionOption: actions.grading.setCriterionOption, }; -export default connect(mapStateToProps, mapDispatchToProps)(RadioCriterion); +export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(RadioCriterion)); diff --git a/src/containers/CriterionContainer/RadioCriterion.test.jsx b/src/containers/CriterionContainer/RadioCriterion.test.jsx index fd3965e..116c789 100644 --- a/src/containers/CriterionContainer/RadioCriterion.test.jsx +++ b/src/containers/CriterionContainer/RadioCriterion.test.jsx @@ -3,6 +3,7 @@ import { shallow } from 'enzyme'; import actions from 'data/actions'; import selectors from 'data/selectors'; +import { formatMessage } from 'testUtils'; import { RadioCriterion, mapDispatchToProps, @@ -36,8 +37,9 @@ jest.mock('data/selectors', () => ({ }, })); -describe('Radio Crition Container', () => { +describe('Radio Criterion Container', () => { const props = { + intl: { formatMessage }, orderNum: 1, isGrading: true, config: { diff --git a/src/containers/CriterionContainer/ReviewCriterion.jsx b/src/containers/CriterionContainer/ReviewCriterion.jsx index 5721867..3272c4f 100644 --- a/src/containers/CriterionContainer/ReviewCriterion.jsx +++ b/src/containers/CriterionContainer/ReviewCriterion.jsx @@ -3,8 +3,10 @@ import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { Form, FormControlFeedback } from '@edx/paragon'; +import { FormattedMessage } from '@edx/frontend-platform/i18n'; import selectors from 'data/selectors'; +import messages from './messages'; /** * @@ -16,7 +18,7 @@ export const ReviewCriterion = ({ config }) => (
{option.label} - {`${option.points} points`} +
diff --git a/src/containers/CriterionContainer/ReviewCriterion.test.jsx b/src/containers/CriterionContainer/ReviewCriterion.test.jsx index 19b3fdd..170b0e1 100644 --- a/src/containers/CriterionContainer/ReviewCriterion.test.jsx +++ b/src/containers/CriterionContainer/ReviewCriterion.test.jsx @@ -3,6 +3,7 @@ import { shallow } from 'enzyme'; import selectors from 'data/selectors'; import { ReviewCriterion, mapStateToProps } from './ReviewCriterion'; +import messages from './messages'; jest.mock('@edx/paragon', () => ({ Form: { @@ -80,9 +81,10 @@ describe('Review Crition Container', () => { expect(optionEl.find('.option-label').childAt(0).text()).toEqual( option.label, ); - expect(optionEl.find('.option-points').childAt(0).text()).toContain( - String(option.points), - ); + expect(optionEl.find('.option-points').childAt(0).props()).toEqual({ + ...messages.optionPoints, + values: { points: option.points }, + }); }); }); }); diff --git a/src/containers/CriterionContainer/__snapshots__/RadioCriterion.test.jsx.snap b/src/containers/CriterionContainer/__snapshots__/RadioCriterion.test.jsx.snap index eae402b..d8c2128 100644 --- a/src/containers/CriterionContainer/__snapshots__/RadioCriterion.test.jsx.snap +++ b/src/containers/CriterionContainer/__snapshots__/RadioCriterion.test.jsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Radio Crition Container snapshot is grading 1`] = ` +exports[`Radio Criterion Container snapshot is grading 1`] = ` `; -exports[`Radio Crition Container snapshot is not grading 1`] = ` +exports[`Radio Criterion Container snapshot is not grading 1`] = ` - 1 points + @@ -34,7 +43,16 @@ exports[`Review Crition Container snapshot 1`] = ` - 2 points + diff --git a/src/containers/CriterionContainer/messages.js b/src/containers/CriterionContainer/messages.js new file mode 100644 index 0000000..22263cc --- /dev/null +++ b/src/containers/CriterionContainer/messages.js @@ -0,0 +1,21 @@ +import { defineMessages } from '@edx/frontend-platform/i18n'; + +const messages = defineMessages({ + addComments: { + id: 'ora-grading.CriterionFeedback.addCommentsLabel', + defaultMessage: 'Add comments', + description: 'label for editable feedback field', + }, + comments: { + id: 'ora-grading.CriterionFeedback.commentsLabel', + defaultMessage: 'Comments', + description: 'label for read-only feedback field', + }, + optionPoints: { + id: 'ora-grading.RadioCriterion.optionPoints', + defaultMessage: '{points} points', + description: 'criterion option point value display', + }, +}); + +export default messages; From e6247f458e6e7c1748ec5ddf013c78b73317fd78 Mon Sep 17 00:00:00 2001 From: Ben Warzeski Date: Fri, 29 Oct 2021 10:25:20 -0400 Subject: [PATCH 5/8] feat: i18n Rubric --- src/containers/Rubric/Rubric.scss | 12 +++--- src/containers/Rubric/RubricFeedback.jsx | 19 ++++++++-- src/containers/Rubric/RubricFeedback.test.jsx | 20 ++++++---- .../RubricFeedback.test.jsx.snap | 38 +++++++++++-------- .../Rubric/__snapshots__/index.test.jsx.snap | 18 +++++++-- src/containers/Rubric/index.jsx | 6 ++- src/containers/Rubric/messages.js | 31 +++++++++++++++ 7 files changed, 108 insertions(+), 36 deletions(-) create mode 100644 src/containers/Rubric/messages.js diff --git a/src/containers/Rubric/Rubric.scss b/src/containers/Rubric/Rubric.scss index a0360b5..451c2d5 100644 --- a/src/containers/Rubric/Rubric.scss +++ b/src/containers/Rubric/Rubric.scss @@ -6,14 +6,16 @@ width: 100%; .criteria-title { display: inline-block; - max-width: calc(100% - 36px); + max-width: calc(100% - 44px); color: $primary-500; - font-size: 1.5rem; font-weight: bold; + vertical-align: top; } - .criteria-help-icon { + .esg-help-icon { float: right; - margin-right: -12px; + margin-top: (map-get($spacers, 2) * -1); + margin-right: (map-get($spacers, 2\.5) * -1); + vertical-align: top; } } .criteria-option { @@ -39,7 +41,7 @@ .popover.overlay-help-popover { z-index: 4000; .help-popover-option { - margin-bottom: 1rem; + margin-bottom: map-get($spacers, 1); } } diff --git a/src/containers/Rubric/RubricFeedback.jsx b/src/containers/Rubric/RubricFeedback.jsx index fcacfac..e5ccbeb 100644 --- a/src/containers/Rubric/RubricFeedback.jsx +++ b/src/containers/Rubric/RubricFeedback.jsx @@ -3,12 +3,15 @@ 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 { feedbackRequirement } from 'data/services/lms/constants'; import actions from 'data/actions'; import selectors from 'data/selectors'; import InfoPopover from 'components/InfoPopover'; +import messages from './messages'; + /** * */ @@ -22,6 +25,12 @@ export class RubricFeedback extends React.Component { this.props.setValue(event.target.value); } + get inputLabel() { + return this.props.intl.formatMessage( + this.props.isGrading ? messages.addComments : messages.comments, + ); + } + render() { const { isGrading, @@ -36,7 +45,9 @@ export class RubricFeedback extends React.Component { return ( - Overall comments + + +
{feedbackPrompt}
@@ -44,7 +55,7 @@ export class RubricFeedback extends React.Component { 'InfoPopover'); -jest.mock('@edx/paragon', () => ({ - Form: { - Group: () => 'Form.Group', - Label: () => 'Form.Label', - Control: () => 'Form.Control', - }, -})); +jest.mock('@edx/paragon', () => { + const Form = () => 'Form'; + Object.defineProperty(Form, 'name', { value: 'Form' }); + Form.Group = 'Form.Group'; + Form.Label = 'Form.Label'; + Form.Control = 'Form.Control'; + return { Form }; +}); jest.mock('data/selectors', () => ({ __esModule: true, @@ -48,8 +51,9 @@ jest.mock('data/selectors', () => ({ }, })); -describe('Review Feedback component', () => { +describe('Rubric Feedback component', () => { const props = { + intl: { formatMessage }, config: 'config stirng', isGrading: true, value: 'some value', diff --git a/src/containers/Rubric/__snapshots__/RubricFeedback.test.jsx.snap b/src/containers/Rubric/__snapshots__/RubricFeedback.test.jsx.snap index c021a2b..b25f2ee 100644 --- a/src/containers/Rubric/__snapshots__/RubricFeedback.test.jsx.snap +++ b/src/containers/Rubric/__snapshots__/RubricFeedback.test.jsx.snap @@ -1,24 +1,28 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Review Feedback component snapshot is configure to disabled 1`] = `null`; +exports[`Rubric Feedback component snapshot is configure to disabled 1`] = `null`; -exports[`Review Feedback component snapshot is graded 1`] = ` - - - + - +
`; -exports[`Review Feedback component snapshot is grading 1`] = ` - - - + - + `; diff --git a/src/containers/Rubric/__snapshots__/index.test.jsx.snap b/src/containers/Rubric/__snapshots__/index.test.jsx.snap index 16b1da3..fc0a4e4 100644 --- a/src/containers/Rubric/__snapshots__/index.test.jsx.snap +++ b/src/containers/Rubric/__snapshots__/index.test.jsx.snap @@ -8,7 +8,11 @@ exports[`Rubric Container snapshot is grading 1`] = ` className="grading-rubric-body" >

- Rubric +


@@ -57,7 +65,11 @@ exports[`Rubric Container snapshot is not grading 1`] = ` className="grading-rubric-body" >

- Rubric +


( -

Rubric

+


{criteriaIndices.map((index) => ( (
{isGrading && (
- +
)}
diff --git a/src/containers/Rubric/messages.js b/src/containers/Rubric/messages.js new file mode 100644 index 0000000..9b56321 --- /dev/null +++ b/src/containers/Rubric/messages.js @@ -0,0 +1,31 @@ +import { defineMessages } from '@edx/frontend-platform/i18n'; + +const messages = defineMessages({ + rubric: { + id: 'ora-grading.Rubric.rubric', + defaultMessage: 'Rubric', + description: 'Rubric interface label', + }, + submitGrade: { + id: 'ora-grading.Rubric.submitGrade', + defaultMessage: 'Submit grade', + description: 'Submit Grade button text', + }, + overallComments: { + id: 'ora-grading.Rubric.overallComments', + defaultMessage: 'Overall comments', + description: 'Rubric overall commnents label', + }, + addComments: { + id: 'ora-grading.Rubric.addComments', + defaultMessage: 'Add comments', + description: 'Rubric comments input label', + }, + comments: { + id: 'ora-grading.Rubric.comments', + defaultMessage: 'Comments', + description: 'Rubric comments display label', + }, +}); + +export default messages; From 85c4df23045eef6d01fa1f16489602cf660ab134 Mon Sep 17 00:00:00 2001 From: Ben Warzeski Date: Fri, 29 Oct 2021 10:25:33 -0400 Subject: [PATCH 6/8] feat: i18n ReviewActions --- .../__snapshots__/index.test.jsx.snap | 26 ++++-- .../components/OverrideGradeConfirmModal.jsx | 16 ++-- .../OverrideGradeConfirmModal.test.jsx | 2 + .../components/StartGradingButton.jsx | 14 ++-- .../components/StartGradingButton.test.jsx | 2 + .../components/StopGradingConfirmModal.jsx | 26 +++--- .../StopGradingConfirmModal.test.jsx | 2 + .../components/SubmissionNavigation.jsx | 21 ++++- .../components/SubmissionNavigation.test.jsx | 3 + .../OverrideGradeConfirmModal.test.jsx.snap | 4 +- .../StartGradingButton.test.jsx.snap | 18 ++++- .../SubmissionNavigation.test.jsx.snap | 40 +++++++-- .../ReviewActions/components/messages.js | 81 +++++++++++++++++++ src/containers/ReviewActions/index.jsx | 16 ++-- src/containers/ReviewActions/messages.js | 21 +++++ 15 files changed, 241 insertions(+), 51 deletions(-) create mode 100644 src/containers/ReviewActions/components/messages.js create mode 100644 src/containers/ReviewActions/messages.js diff --git a/src/containers/ReviewActions/__snapshots__/index.test.jsx.snap b/src/containers/ReviewActions/__snapshots__/index.test.jsx.snap index 8152820..bf3264c 100644 --- a/src/containers/ReviewActions/__snapshots__/index.test.jsx.snap +++ b/src/containers/ReviewActions/__snapshots__/index.test.jsx.snap @@ -20,7 +20,17 @@ exports[`ReviewActions component component snapshot: do not show rubric 1`] = ` - Score: 3/10 +
- Show - Rubric + @@ -68,8 +81,11 @@ exports[`ReviewActions component component snapshot: show rubric, no score 1`] = onClick={[MockFunction this.props.toggleShowRubric]} variant="outline-primary" > - Hide - Rubric + diff --git a/src/containers/ReviewActions/components/OverrideGradeConfirmModal.jsx b/src/containers/ReviewActions/components/OverrideGradeConfirmModal.jsx index 43a7a2f..27cbd52 100644 --- a/src/containers/ReviewActions/components/OverrideGradeConfirmModal.jsx +++ b/src/containers/ReviewActions/components/OverrideGradeConfirmModal.jsx @@ -1,18 +1,22 @@ import React from 'react'; import PropTypes from 'prop-types'; +import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; + import ConfirmModal from 'components/ConfirmModal'; +import messages from './messages'; export const OverrideGradeConfirmModal = ({ + intl, isOpen, onCancel, onConfirm, }) => ( 'ConfirmModal'); describe('OverrideGradeConfirmModal', () => { const props = { + intl: { formatMessage }, isOpen: false, onCancel: jest.fn().mockName('this.props.onCancel'), onConfirm: jest.fn().mockName('this.props.onConfirm'), diff --git a/src/containers/ReviewActions/components/StartGradingButton.jsx b/src/containers/ReviewActions/components/StartGradingButton.jsx index a34bca9..22105e5 100644 --- a/src/containers/ReviewActions/components/StartGradingButton.jsx +++ b/src/containers/ReviewActions/components/StartGradingButton.jsx @@ -2,10 +2,9 @@ import React from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; -import { - Button, -} from '@edx/paragon'; +import { Button } from '@edx/paragon'; import { Cancel, Highlight } from '@edx/paragon/icons'; +import { FormattedMessage } from '@edx/frontend-platform/i18n'; import selectors from 'data/selectors'; import thunkActions from 'data/thunkActions'; @@ -13,18 +12,19 @@ import { gradingStatuses as statuses } from 'data/services/lms/constants'; import StopGradingConfirmModal from './StopGradingConfirmModal'; import OverrideGradeConfirmModal from './OverrideGradeConfirmModal'; +import messages from './messages'; export const buttonArgs = { [statuses.ungraded]: { - label: 'Start Grading', + label: messages.startGrading, iconAfter: Highlight, }, [statuses.graded]: { - label: 'Override grade', + label: messages.overrideGrade, iconAfter: Highlight, }, [statuses.inProgress]: { - label: 'Stop grading this response', + label: messages.stopGrading, iconAfter: Cancel, }, }; @@ -95,7 +95,7 @@ export class StartGradingButton extends React.Component { iconAfter={args.iconAfter} onClick={this.handleClick} > - {args.label} + ({ }, }, })); +jest.mock('./OverrideGradeConfirmModal', () => 'OverrideGradeConfirmModal'); +jest.mock('./StopGradingConfirmModal', () => 'StopGradingConfirmModal'); let el; diff --git a/src/containers/ReviewActions/components/StopGradingConfirmModal.jsx b/src/containers/ReviewActions/components/StopGradingConfirmModal.jsx index 1e75cfb..edce7c8 100644 --- a/src/containers/ReviewActions/components/StopGradingConfirmModal.jsx +++ b/src/containers/ReviewActions/components/StopGradingConfirmModal.jsx @@ -1,25 +1,27 @@ import React from 'react'; import PropTypes from 'prop-types'; +import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; + import ConfirmModal from 'components/ConfirmModal'; +import messages from './messages'; export const StopGradingConfirmModal = ({ + intl, isOpen, isOverride, onCancel, onConfirm, }) => ( 'ConfirmModal'); describe('StopGradingConfirmModal', () => { const props = { + intl: { formatMessage }, isOpen: false, isOverride: false, onCancel: jest.fn().mockName('this.props.onCancel'), diff --git a/src/containers/ReviewActions/components/SubmissionNavigation.jsx b/src/containers/ReviewActions/components/SubmissionNavigation.jsx index 8cbf6b7..f755737 100644 --- a/src/containers/ReviewActions/components/SubmissionNavigation.jsx +++ b/src/containers/ReviewActions/components/SubmissionNavigation.jsx @@ -4,14 +4,17 @@ import { connect } from 'react-redux'; import { Icon, IconButton } from '@edx/paragon'; import { ChevronLeft, ChevronRight } from '@edx/paragon/icons'; +import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import selectors from 'data/selectors'; import thunkActions from 'data/thunkActions'; +import messages from './messages'; /** * */ export const SubmissionNavigation = ({ + intl, hasPrevSubmission, hasNextSubmission, loadPrev, @@ -21,18 +24,25 @@ export const SubmissionNavigation = ({ }) => ( - {activeIndex + 1} of {selectionLength} + + + ({ describe('SubmissionNavigation component', () => { describe('component', () => { const props = { + intl: { formatMessage }, activeIndex: 4, selectionLength: 5, }; diff --git a/src/containers/ReviewActions/components/__snapshots__/OverrideGradeConfirmModal.test.jsx.snap b/src/containers/ReviewActions/components/__snapshots__/OverrideGradeConfirmModal.test.jsx.snap index a62f2f9..5cc823a 100644 --- a/src/containers/ReviewActions/components/__snapshots__/OverrideGradeConfirmModal.test.jsx.snap +++ b/src/containers/ReviewActions/components/__snapshots__/OverrideGradeConfirmModal.test.jsx.snap @@ -4,7 +4,7 @@ exports[`OverrideGradeConfirmModal snapshot: closed 1`] = ` - Override grade + - Stop grading this response + - Start Grading + - - 5 - of - 5 + + - - 1 - of - 5 + + {username} - {pointsPossible && `Score: ${pointsEarned}/${pointsPossible}`} + {pointsEarned && ( + + )}
diff --git a/src/containers/ReviewActions/messages.js b/src/containers/ReviewActions/messages.js new file mode 100644 index 0000000..16b03ab --- /dev/null +++ b/src/containers/ReviewActions/messages.js @@ -0,0 +1,21 @@ +import { defineMessages } from '@edx/frontend-platform/i18n'; + +const messages = defineMessages({ + scoreDisplay: { + id: 'ora-grading.ReviewActions.scoreDisplay', + defaultMessage: 'Score: {pointsEarned}/{pointsPossible}', + description: 'Review pane action bar score display', + }, + hideRubric: { + id: 'ora-grading.ReviewActions.hideRubric', + defaultMessage: 'Hide Rubric', + description: 'Review pane action bar Hide Rubric button text', + }, + showRubric: { + id: 'ora-grading.ReviewActions.showRubric', + defaultMessage: 'Show Rubric', + description: 'Review pane action bar Show Rubric button text', + }, +}); + +export default messages; From 7d0b281dd031627f2c2ef8c5d70956457cacc94e Mon Sep 17 00:00:00 2001 From: Ben Warzeski Date: Fri, 29 Oct 2021 10:26:06 -0400 Subject: [PATCH 7/8] feat: i18n ListView and StatusBadge labels --- src/components/StatusBadge.jsx | 9 ++- .../ListView/ListViewBreadcrumb.jsx | 4 +- src/containers/ListView/TableControls.jsx | 20 ------- .../ListView/TableControls.test.jsx | 21 ------- .../ListViewBreadcrumb.test.jsx.snap | 6 +- .../__snapshots__/TableControls.test.jsx.snap | 12 ---- .../__snapshots__/index.test.jsx.snap | 17 ++++-- src/containers/ListView/index.jsx | 52 ++++++++++------- src/containers/ListView/index.test.jsx | 37 +++++++----- src/containers/ListView/messages.js | 56 +++++++++++++++++++ src/data/services/lms/constants.js | 7 --- src/data/services/lms/messages.js | 34 +++++++++++ 12 files changed, 169 insertions(+), 106 deletions(-) delete mode 100644 src/containers/ListView/TableControls.jsx delete mode 100644 src/containers/ListView/TableControls.test.jsx delete mode 100644 src/containers/ListView/__snapshots__/TableControls.test.jsx.snap create mode 100644 src/containers/ListView/messages.js create mode 100644 src/data/services/lms/messages.js diff --git a/src/components/StatusBadge.jsx b/src/components/StatusBadge.jsx index 0bff078..9ab4da6 100644 --- a/src/components/StatusBadge.jsx +++ b/src/components/StatusBadge.jsx @@ -2,11 +2,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Badge } from '@edx/paragon'; +import { FormattedMessage } from '@edx/frontend-platform/i18n'; -import { - gradingStatuses as statuses, - gradingStatusDisplay as statusDisplay, -} from 'data/services/lms/constants'; +import { gradingStatuses as statuses } from 'data/services/lms/constants'; +import messages from 'data/services/lms/messages'; export const statusVariants = { [statuses.ungraded]: 'primary', @@ -27,7 +26,7 @@ export const StatusBadge = ({ className, status }) => { className={className} variant={statusVariants[status]} > - {statusDisplay[status]} + ); }; diff --git a/src/containers/ListView/ListViewBreadcrumb.jsx b/src/containers/ListView/ListViewBreadcrumb.jsx index 707a0fa..2e7d30c 100644 --- a/src/containers/ListView/ListViewBreadcrumb.jsx +++ b/src/containers/ListView/ListViewBreadcrumb.jsx @@ -4,10 +4,12 @@ import { connect } from 'react-redux'; import { ArrowBack, Launch } from '@edx/paragon/icons'; import { Hyperlink, Icon } from '@edx/paragon'; +import { FormattedMessage } from '@edx/frontend-platform/i18n'; import selectors from 'data/selectors'; import { locationId } from 'data/constants/app'; import urls from 'data/services/lms/urls'; +import messages from './messages'; /** * @@ -16,7 +18,7 @@ export const ListViewBreadcrumb = ({ courseId, oraName }) => ( <> - Back to all open responses +

{oraName} diff --git a/src/containers/ListView/TableControls.jsx b/src/containers/ListView/TableControls.jsx deleted file mode 100644 index 8ce2179..0000000 --- a/src/containers/ListView/TableControls.jsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; - -import { - DataTable, -} from '@edx/paragon'; - -/** - * - */ -export const TableControls = () => ( - <> - - - - - -); -TableControls.propTypes = {}; - -export default TableControls; diff --git a/src/containers/ListView/TableControls.test.jsx b/src/containers/ListView/TableControls.test.jsx deleted file mode 100644 index dd552e4..0000000 --- a/src/containers/ListView/TableControls.test.jsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react'; -import { shallow } from 'enzyme'; - -import TableControls from './TableControls'; - -jest.mock('@edx/paragon', () => ({ - DataTable: { - TableControlBar: () => 'DataTable.TableControlBar', - Table: () => 'DataTable.Table', - EmptyTable: () => 'DataTable.EmptyTable', - TableFooter: () => 'DataTable.TableFooter', - }, -})); - -describe('ListView TableControls component', () => { - describe('component', () => { - test('snapshot', () => { - expect(shallow()).toMatchSnapshot(); - }); - }); -}); diff --git a/src/containers/ListView/__snapshots__/ListViewBreadcrumb.test.jsx.snap b/src/containers/ListView/__snapshots__/ListViewBreadcrumb.test.jsx.snap index 3030053..fdd2b8f 100644 --- a/src/containers/ListView/__snapshots__/ListViewBreadcrumb.test.jsx.snap +++ b/src/containers/ListView/__snapshots__/ListViewBreadcrumb.test.jsx.snap @@ -10,7 +10,11 @@ exports[`ListViewBreadcrumb component component snapshot: empty (no list data) 1 className="mr-3" icon="icons.ArrowBack" /> - Back to all open responses +

- - - - - -`; diff --git a/src/containers/ListView/__snapshots__/index.test.jsx.snap b/src/containers/ListView/__snapshots__/index.test.jsx.snap index c14a914..8c1b0a2 100644 --- a/src/containers/ListView/__snapshots__/index.test.jsx.snap +++ b/src/containers/ListView/__snapshots__/index.test.jsx.snap @@ -34,7 +34,7 @@ exports[`ListView component component render tests snapshots snapshot: happy pat Object { "Cell": [MockFunction this.formatStatus], "Filter": "MultiSelectDropdownFilter", - "Header": "Grading Status", + "Header": "Grading status", "accessor": "gradingStatus", "filter": "includesValue", "filterChoices": Array [ @@ -43,12 +43,12 @@ exports[`ListView component component render tests snapshots snapshot: happy pat "value": "ungraded", }, Object { - "name": "Currently being graded by someone else", - "value": "locked", + "name": "Grading Completed", + "value": "graded", }, Object { - "name": "Grading Complete", - "value": "graded", + "name": "Currently being graded by someone else", + "value": "locked", }, Object { "name": "You are currently grading this response", @@ -117,7 +117,12 @@ exports[`ListView component component render tests snapshots snapshot: happy pat ] } > - + + + + diff --git a/src/containers/ListView/index.jsx b/src/containers/ListView/index.jsx index 29a65fa..b78466e 100644 --- a/src/containers/ListView/index.jsx +++ b/src/containers/ListView/index.jsx @@ -8,24 +8,20 @@ import { MultiSelectDropdownFilter, Container, } from '@edx/paragon'; +import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import { - gradingStatusDisplay, -} from 'data/services/lms/constants'; +import { gradingStatuses } from 'data/services/lms/constants'; import selectors from 'data/selectors'; import thunkActions from 'data/thunkActions'; +import lmsMessages from 'data/services/lms/messages'; import StatusBadge from 'components/StatusBadge'; import ReviewModal from 'containers/ReviewModal'; -import ListViewBreadcrumb from './ListViewBreadcrumb'; -import TableControls from './TableControls'; -import './ListView.scss'; -export const gradeStatusOptions = Object.keys(gradingStatusDisplay).map(key => ({ - name: gradingStatusDisplay[key], - value: key, -})); +import ListViewBreadcrumb from './ListViewBreadcrumb'; +import messages from './messages'; +import './ListView.scss'; /** * @@ -38,6 +34,13 @@ export class ListView extends React.Component { this.selectedBulkAction = this.selectedBulkAction.bind(this); } + get gradeStatusOptions() { + return Object.keys(gradingStatuses).map(statusKey => ({ + name: this.translate(lmsMessages[gradingStatuses[statusKey]]), + value: gradingStatuses[statusKey], + })); + } + formatDate = ({ value }) => { const date = new Date(value); return date.toLocaleString(); @@ -49,6 +52,8 @@ export class ListView extends React.Component { formatStatus = ({ value }) => (); + translate = (...args) => this.props.intl.formatMessage(...args); + handleViewAllResponsesClick(data) { const getSubmissionId = (row) => row.original.submissionId; const rows = data.selectedRows.length ? data.selectedRows : data.tableInstance.rows; @@ -57,7 +62,10 @@ export class ListView extends React.Component { selectedBulkAction(selectedFlatRows) { return { - buttonText: `View selected responses (${selectedFlatRows.length})`, + buttonText: this.translate( + messages.viewSelectedResponses, + { value: selectedFlatRows.length }, + ), className: 'view-selected-responses-btn', handleClick: this.handleViewAllResponsesClick, variant: 'primary', @@ -85,7 +93,7 @@ export class ListView extends React.Component { data={this.props.listData} tableActions={[ { - buttonText: 'View all responses', + buttonText: this.translate(messages.viewAllResponses), handleClick: this.handleViewAllResponsesClick, className: 'view-all-responses-btn', variant: 'primary', @@ -96,32 +104,35 @@ export class ListView extends React.Component { ]} columns={[ { - Header: 'Username', + Header: this.translate(messages.username), accessor: 'username', }, { - Header: 'Learner submission date', + Header: this.translate(messages.learnerSubmissionDate), accessor: 'dateSubmitted', Cell: this.formatDate, disableFilters: true, }, { - Header: 'Grade', + Header: this.translate(messages.grade), accessor: 'score', Cell: this.formatGrade, disableFilters: true, }, { - Header: 'Grading Status', + Header: this.translate(messages.gradingStatus), accessor: 'gradingStatus', Cell: this.formatStatus, Filter: MultiSelectDropdownFilter, filter: 'includesValue', - filterChoices: gradeStatusOptions, + filterChoices: this.gradeStatusOptions, }, ]} > - + + + + @@ -132,6 +143,9 @@ ListView.defaultProps = { listData: [], }; ListView.propTypes = { + // injected + intl: intlShape.isRequired, + // redux initializeApp: PropTypes.func.isRequired, listData: PropTypes.arrayOf(PropTypes.shape({ username: PropTypes.string, @@ -154,4 +168,4 @@ export const mapDispatchToProps = { loadSelectionForReview: thunkActions.grading.loadSelectionForReview, }; -export default connect(mapStateToProps, mapDispatchToProps)(ListView); +export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(ListView)); diff --git a/src/containers/ListView/index.test.jsx b/src/containers/ListView/index.test.jsx index 58c3496..a6236ee 100644 --- a/src/containers/ListView/index.test.jsx +++ b/src/containers/ListView/index.test.jsx @@ -12,23 +12,31 @@ import thunkActions from 'data/thunkActions'; import { gradingStatuses as statuses } from 'data/services/lms/constants'; import StatusBadge from 'components/StatusBadge'; +import { formatMessage } from 'testUtils'; +import messages from './messages'; import { ListView, mapStateToProps, mapDispatchToProps, - gradeStatusOptions, } from '.'; -jest.mock('@edx/paragon', () => ({ - DataTable: () => 'DataTable', - TextFilter: 'TextFilter', - MultiSelectDropdownFilter: 'MultiSelectDropdownFilter', - Container: () => 'Container', -})); +jest.mock('@edx/paragon', () => { + const mockDataTable = () => 'DataTable'; + Object.defineProperty(mockDataTable, 'name', { value: 'DataTable' }); + mockDataTable.TableControlBar = 'DataTable.TableControlBar'; + mockDataTable.Table = 'DataTable.Table'; + mockDataTable.EmptyTable = 'DataTable.EmptyTable'; + mockDataTable.TableFooter = 'DataTable.TableFooter'; + return { + DataTable: mockDataTable, + TextFilter: 'TextFilter', + MultiSelectDropdownFilter: 'MultiSelectDropdownFilter', + Container: () => 'Container', + }; +}); jest.mock('components/StatusBadge', () => 'StatusBadge'); jest.mock('containers/ReviewModal', () => 'ReviewModal'); jest.mock('./ListViewBreadcrumb', () => 'ListViewBreadcrumb'); -jest.mock('./TableControls', () => 'TableControls'); jest.mock('data/selectors', () => ({ __esModule: true, @@ -78,6 +86,7 @@ describe('ListView component', () => { beforeEach(() => { props.initializeApp = jest.fn(); props.loadSelectionForReview = jest.fn(); + props.intl = { formatMessage }; }); describe('render tests', () => { const mockMethod = (methodName) => { @@ -132,13 +141,13 @@ describe('ListView component', () => { }); test('username column', () => { expect(columns[0]).toEqual({ - Header: 'Username', + Header: messages.username.defaultMessage, accessor: 'username', }); }); test('submission date column', () => { expect(columns[1]).toEqual({ - Header: 'Learner submission date', + Header: messages.learnerSubmissionDate.defaultMessage, accessor: 'dateSubmitted', Cell: el.instance().formatDate, disableFilters: true, @@ -146,7 +155,7 @@ describe('ListView component', () => { }); test('grade column', () => { expect(columns[2]).toEqual({ - Header: 'Grade', + Header: messages.grade.defaultMessage, accessor: 'score', Cell: el.instance().formatGrade, disableFilters: true, @@ -154,12 +163,12 @@ describe('ListView component', () => { }); test('grading status column', () => { expect(columns[3]).toEqual({ - Header: 'Grading Status', + Header: messages.gradingStatus.defaultMessage, accessor: 'gradingStatus', Cell: el.instance().formatStatus, Filter: MultiSelectDropdownFilter, filter: 'includesValue', - filterChoices: gradeStatusOptions, + filterChoices: el.instance().gradeStatusOptions, }); }); }); @@ -227,7 +236,7 @@ describe('ListView component', () => { it('includes selection length and triggers handleViewAllResponsesClick', () => { const rows = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; const action = el.instance().selectedBulkAction(rows); - expect(action.buttonText.includes(rows.length)).toEqual(true); + expect(action.buttonText).toEqual(expect.stringContaining(rows.length.toString())); expect(action.handleClick).toEqual(el.instance().handleViewAllResponsesClick); }); }); diff --git a/src/containers/ListView/messages.js b/src/containers/ListView/messages.js new file mode 100644 index 0000000..953306e --- /dev/null +++ b/src/containers/ListView/messages.js @@ -0,0 +1,56 @@ +import { defineMessages } from '@edx/frontend-platform/i18n'; + +const messages = defineMessages({ + backToResponses: { + id: 'ora-grading.ListView.ListViewBreadcrumbs.backToResponses', + defaultMessage: 'Back to all open responses', + description: 'Breadcrumbs link text to return to ORA list in LMS.', + }, + noResultsFound: { + id: 'ora-grading.ListView.noResultsFound', + defaultMessage: 'No results found', + description: 'Empty table content for submissions list', + }, + viewAllResponses: { + id: 'ora-grading.ListView.viewAllResponses', + defaultMessage: 'View all responses', + description: 'Button text to load all responses for review/grading', + }, + viewSelectedResponses: { + id: 'ora-grading.ListView.viewSelectedResponses', + defaultMessage: 'View selected responses ({value})', + description: 'Button text to load selected responses for review/grading', + }, + username: { + id: 'ora-grading.ListView.tableHeaders.username', + defaultMessage: 'Username', + description: 'Username table column header for submission list view', + }, + teamName: { + id: 'ora-grading.ListView.tableHeaders.teamName', + defaultMessage: 'Team name', + description: 'Team name table column header for submission list view', + }, + learnerSubmissionDate: { + id: 'ora-grading.ListView.tableHeaders.learnerSubmissionDate', + defaultMessage: 'Learner submission date', + description: 'Learner submission date table column header for submission list view', + }, + teamSubmissionDate: { + id: 'ora-grading.ListView.tableHeaders.teamSubmissionDate', + defaultMessage: 'Team submission date', + description: 'Team submission date table column header for submission list view', + }, + grade: { + id: 'ora-grading.ListView.tableHeaders.grade', + defaultMessage: 'Grade', + description: 'Grade table column header for submission list view', + }, + gradingStatus: { + id: 'ora-grading.ListView.tableHeaders.gradingStatus', + defaultMessage: 'Grading status', + description: 'Grading status table column header for submission list view', + }, +}); + +export default messages; diff --git a/src/data/services/lms/constants.js b/src/data/services/lms/constants.js index 10e3b44..528d36d 100644 --- a/src/data/services/lms/constants.js +++ b/src/data/services/lms/constants.js @@ -18,13 +18,6 @@ export const gradingStatuses = StrictDict({ inProgress: lockStatuses.inProgress, }); -export const gradingStatusDisplay = StrictDict({ - [gradingStatuses.ungraded]: 'Ungraded', - [gradingStatuses.locked]: 'Currently being graded by someone else', - [gradingStatuses.graded]: 'Grading Complete', - [gradingStatuses.inProgress]: 'You are currently grading this response', -}); - export const feedbackRequirement = StrictDict({ disabled: 'disabled', required: 'required', diff --git a/src/data/services/lms/messages.js b/src/data/services/lms/messages.js new file mode 100644 index 0000000..e050137 --- /dev/null +++ b/src/data/services/lms/messages.js @@ -0,0 +1,34 @@ +import { defineMessages } from '@edx/frontend-platform/i18n'; +import { gradingStatuses } from './constants'; + +const messages = defineMessages({ + ungraded: { + id: 'ora-grading.lms-api.gradingStatusDisplay.ungraded', + defaultMessage: 'Ungraded', + description: 'Grading status label for ungraded submission', + }, + locked: { + id: 'ora-grading.lms-api.gradingStatusDisplay.locked', + defaultMessage: 'Currently being graded by someone else', + description: 'Grading status label for locked submission', + }, + graded: { + id: 'ora-grading.lms-api.gradingStatusDisplay.graded', + defaultMessage: 'Grading Completed', + description: 'Grading status label for graded submission', + }, + inProgress: { + id: 'ora-grading.lms-api.gradingStatusDisplay.inProgress', + defaultMessage: 'You are currently grading this response', + description: 'Grading status label for in-progress submission', + }, +}); + +// re-keying the messages to ensure that the api can link to them even if the passed +// status keys change. +export default { + [gradingStatuses.ungraded]: messages.ungraded, + [gradingStatuses.locked]: messages.locked, + [gradingStatuses.graded]: messages.graded, + [gradingStatuses.inProgress]: messages.inProgress, +}; From b05333538055de2efcde89cb0989fd30dc144929 Mon Sep 17 00:00:00 2001 From: Ben Warzeski Date: Fri, 29 Oct 2021 10:26:16 -0400 Subject: [PATCH 8/8] chore: update integration test --- src/test/app.test.jsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/test/app.test.jsx b/src/test/app.test.jsx index d9a75ce..d41285a 100644 --- a/src/test/app.test.jsx +++ b/src/test/app.test.jsx @@ -17,9 +17,12 @@ import api from 'data/services/lms/api'; import reducers from 'data/reducers'; import { gradingStatuses } from 'data/services/lms/constants'; import messages from 'i18n'; +import reviewActionsMessages from 'containers/ReviewActions/messages'; import App from 'App'; +jest.mock('@edx/frontend-platform/i18n', () => jest.requireActual('@edx/frontend-platform/i18n')); + jest.mock('@edx/frontend-platform/auth', () => ({ getAuthenticatedHttpClient: jest.fn(), getLoginRedirectUrl: jest.fn(), @@ -115,7 +118,7 @@ const makeTableSelections = async () => { const clickIndex = (index) => userEvent.click(checkbox(index)); [0, 1, 2, 3, 4].forEach(clickIndex); userEvent.click(el.container.querySelector('.view-selected-responses-btn')); - await act(() => el.findByText('Show Rubric')); + await act(() => el.findByText(reviewActionsMessages.showRubric.defaultMessage)); getState(); };