refactor: replacing injectIntl with useIntl() part 2 (#459)

This commit is contained in:
Jacobo Dominguez
2025-08-22 10:24:39 -06:00
committed by GitHub
parent 3115fc275c
commit 57022ed294
10 changed files with 206 additions and 226 deletions

View File

@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Form } from '@openedx/paragon';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { useIntl } from '@edx/frontend-platform/i18n';
import { feedbackRequirement } from 'data/services/lms/constants';
import { actions, selectors } from 'data/redux';
@@ -12,60 +12,56 @@ import messages from './messages';
/**
* <CriterionFeedback />
*/
export class CriterionFeedback extends React.Component {
constructor(props) {
super(props);
this.onChange = this.onChange.bind(this);
}
export const CriterionFeedback = ({
orderNum,
isGrading,
config,
setValue,
value,
isInvalid,
}) => {
const intl = useIntl();
onChange(event) {
this.props.setValue({
const onChange = (event) => {
setValue({
value: event.target.value,
orderNum: this.props.orderNum,
orderNum,
});
}
};
get commentMessage() {
const { config, isGrading } = this.props;
let commentMessage = this.translate(isGrading ? messages.addComments : messages.comments);
const translate = (msg) => intl.formatMessage(msg);
const getCommentMessage = () => {
let commentMessage = translate(isGrading ? messages.addComments : messages.comments);
if (config === feedbackRequirement.optional) {
commentMessage += ` ${this.translate(messages.optional)}`;
commentMessage += ` ${translate(messages.optional)}`;
}
return commentMessage;
};
if (config === feedbackRequirement.disabled) {
return null;
}
translate = (msg) => this.props.intl.formatMessage(msg);
render() {
const {
config,
isGrading,
value,
isInvalid,
} = this.props;
if (config === feedbackRequirement.disabled) {
return null;
}
return (
<Form.Group isInvalid={this.feedbackIsInvalid}>
<Form.Control
as="textarea"
className="criterion-feedback feedback-input"
data-testid="criterion-feedback-input"
floatingLabel={this.commentMessage}
value={value}
onChange={this.onChange}
disabled={!isGrading}
/>
{isInvalid && (
<Form.Control.Feedback type="invalid" className="feedback-error-msg" data-testid="criterion-feedback-error-msg">
{this.translate(messages.criterionFeedbackError)}
</Form.Control.Feedback>
)}
</Form.Group>
);
}
}
return (
<Form.Group isInvalid={isInvalid}>
<Form.Control
as="textarea"
className="criterion-feedback feedback-input"
data-testid="criterion-feedback-input"
floatingLabel={getCommentMessage()}
value={value}
onChange={onChange}
disabled={!isGrading}
/>
{isInvalid && (
<Form.Control.Feedback type="invalid" className="feedback-error-msg" data-testid="criterion-feedback-error-msg">
{translate(messages.criterionFeedbackError)}
</Form.Control.Feedback>
)}
</Form.Group>
);
};
CriterionFeedback.defaultProps = {
value: '',
@@ -74,8 +70,6 @@ CriterionFeedback.defaultProps = {
CriterionFeedback.propTypes = {
orderNum: PropTypes.number.isRequired,
isGrading: PropTypes.bool.isRequired,
// injected
intl: intlShape.isRequired,
// redux
config: PropTypes.string.isRequired,
setValue: PropTypes.func.isRequired,
@@ -93,6 +87,4 @@ export const mapDispatchToProps = {
setValue: actions.grading.setCriterionFeedback,
};
export default injectIntl(
connect(mapStateToProps, mapDispatchToProps)(CriterionFeedback),
);
export default connect(mapStateToProps, mapDispatchToProps)(CriterionFeedback);

View File

@@ -6,7 +6,6 @@ import {
feedbackRequirement,
gradeStatuses,
} from 'data/services/lms/constants';
import { formatMessage } from 'testUtils';
import {
CriterionFeedback,
mapStateToProps,
@@ -38,7 +37,6 @@ jest.unmock('react');
describe('Criterion Feedback', () => {
const props = {
intl: { formatMessage },
orderNum: 1,
config: 'config string',
isGrading: true,

View File

@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Form } from '@openedx/paragon';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { useIntl } from '@edx/frontend-platform/i18n';
import { actions, selectors } from 'data/redux';
import messages from './messages';
@@ -11,51 +11,46 @@ import messages from './messages';
/**
* <RadioCriterion />
*/
export class RadioCriterion extends React.Component {
constructor(props) {
super(props);
this.onChange = this.onChange.bind(this);
}
export const RadioCriterion = ({
orderNum,
isGrading,
config,
data,
setCriterionOption,
isInvalid,
}) => {
const intl = useIntl();
onChange(event) {
this.props.setCriterionOption({
orderNum: this.props.orderNum,
const onChange = (event) => {
setCriterionOption({
orderNum,
value: event.target.value,
});
}
};
render() {
const {
config,
data,
intl,
isGrading,
isInvalid,
} = this.props;
return (
<Form.RadioSet name={config.name} value={data}>
{config.options.map((option) => (
<Form.Radio
className="criteria-option align-items-center"
key={option.name}
value={option.name}
description={intl.formatMessage(messages.optionPoints, { points: option.points })}
onChange={this.onChange}
disabled={!isGrading}
style={{ flexShrink: 0 }}
>
{option.label}
</Form.Radio>
))}
{isInvalid && (
<Form.Control.Feedback type="invalid" className="feedback-error-msg">
{intl.formatMessage(messages.rubricSelectedError)}
</Form.Control.Feedback>
)}
</Form.RadioSet>
);
}
}
return (
<Form.RadioSet name={config.name} value={data}>
{config.options.map((option) => (
<Form.Radio
className="criteria-option align-items-center"
key={option.name}
value={option.name}
description={intl.formatMessage(messages.optionPoints, { points: option.points })}
onChange={onChange}
disabled={!isGrading}
style={{ flexShrink: 0 }}
>
{option.label}
</Form.Radio>
))}
{isInvalid && (
<Form.Control.Feedback type="invalid" className="feedback-error-msg">
{intl.formatMessage(messages.rubricSelectedError)}
</Form.Control.Feedback>
)}
</Form.RadioSet>
);
};
RadioCriterion.defaultProps = {
data: {
@@ -67,8 +62,6 @@ RadioCriterion.defaultProps = {
RadioCriterion.propTypes = {
orderNum: PropTypes.number.isRequired,
isGrading: PropTypes.bool.isRequired,
// injected
intl: intlShape.isRequired,
// redux
config: PropTypes.shape({
prompt: PropTypes.string,
@@ -99,4 +92,4 @@ export const mapDispatchToProps = {
setCriterionOption: actions.grading.setCriterionOption,
};
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(RadioCriterion));
export default connect(mapStateToProps, mapDispatchToProps)(RadioCriterion);

View File

@@ -1,7 +1,6 @@
import { render } from '@testing-library/react';
import { actions, selectors } from 'data/redux';
import { formatMessage } from 'testUtils';
import {
RadioCriterion,
mapDispatchToProps,
@@ -33,7 +32,6 @@ jest.unmock('react');
describe('Radio Criterion Container', () => {
const props = {
intl: { formatMessage },
orderNum: 1,
isGrading: true,
config: {

View File

@@ -5,7 +5,7 @@ import {
Card, Collapsible, Icon, DataTable, Button,
} from '@openedx/paragon';
import { ArrowDropDown, ArrowDropUp, WarningFilled } from '@openedx/paragon/icons';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { useIntl } from '@edx/frontend-platform/i18n';
import { downloadAllLimit, downloadSingleLimit } from 'data/constants/files';
@@ -19,93 +19,91 @@ import messages from './messages';
/**
* <SubmissionFiles />
*/
export class SubmissionFiles extends React.Component {
get title() {
return `${this.props.intl.formatMessage(messages.submissionFiles)} (${this.props.files.length})`;
}
export const SubmissionFiles = ({ files }) => {
const intl = useIntl();
get canDownload() {
const getTitle = () => `${intl.formatMessage(messages.submissionFiles)} (${files.length})`;
const getCanDownload = () => {
let totalFileSize = 0;
const exceedFileSize = this.props.files.some(file => {
const exceedFileSize = files.some(file => {
totalFileSize += file.size;
return file.size > downloadSingleLimit;
});
return !exceedFileSize && totalFileSize < downloadAllLimit;
}
};
render() {
const { files, intl } = this.props;
return (
<Card className="submission-files">
{files.length ? (
<>
<Collapsible.Advanced defaultOpen>
<Collapsible.Trigger className="submission-files-title">
<h3 data-testid="submission-files-title">{this.title}</h3>
<Collapsible.Visible whenClosed>
<Icon src={ArrowDropDown} />
</Collapsible.Visible>
<Collapsible.Visible whenOpen>
<Icon src={ArrowDropUp} />
</Collapsible.Visible>
</Collapsible.Trigger>
<Collapsible.Body className="submission-files-body">
<div className="submission-files-table">
<DataTable
columns={[
{
Header: intl.formatMessage(messages.tableNameHeader),
accessor: 'name',
Cell: FileNameCell,
},
{
Header: intl.formatMessage(
messages.tableExtensionHeader,
),
accessor: 'name',
id: 'extension',
Cell: FileExtensionCell,
},
{
Header: intl.formatMessage(messages.tablePopoverHeader),
accessor: '',
Cell: FilePopoverCell,
},
]}
data={files}
itemCount={files.length}
>
<DataTable.Table />
</DataTable>
return (
<Card className="submission-files">
{files.length ? (
<>
<Collapsible.Advanced defaultOpen>
<Collapsible.Trigger className="submission-files-title">
<h3 data-testid="submission-files-title">{getTitle()}</h3>
<Collapsible.Visible whenClosed>
<Icon src={ArrowDropDown} />
</Collapsible.Visible>
<Collapsible.Visible whenOpen>
<Icon src={ArrowDropUp} />
</Collapsible.Visible>
</Collapsible.Trigger>
<Collapsible.Body className="submission-files-body">
<div className="submission-files-table">
<DataTable
columns={[
{
Header: intl.formatMessage(messages.tableNameHeader),
accessor: 'name',
Cell: FileNameCell,
},
{
Header: intl.formatMessage(
messages.tableExtensionHeader,
),
accessor: 'name',
id: 'extension',
Cell: FileExtensionCell,
},
{
Header: intl.formatMessage(messages.tablePopoverHeader),
accessor: '',
Cell: FilePopoverCell,
},
]}
data={files}
itemCount={files.length}
>
<DataTable.Table />
</DataTable>
</div>
</Collapsible.Body>
</Collapsible.Advanced>
<Card.Footer className="text-right">
{
getCanDownload() ? <FileDownload files={files} data-testid="file-download" /> : (
<div>
<Icon className="d-inline-block align-middle" src={WarningFilled} />
<span className="exceed-download-text" data-testid="exceed-download-text"> {intl.formatMessage(messages.exceedFileSize)} </span>
<Button disabled>{intl.formatMessage(messages.downloadFiles)}</Button>
</div>
</Collapsible.Body>
</Collapsible.Advanced>
<Card.Footer className="text-right">
{
this.canDownload ? <FileDownload files={files} data-testid="file-download" /> : (
<div>
<Icon className="d-inline-block align-middle" src={WarningFilled} />
<span className="exceed-download-text" data-testid="exceed-download-text"> {intl.formatMessage(messages.exceedFileSize)} </span>
<Button disabled>{intl.formatMessage(messages.downloadFiles)}</Button>
</div>
)
}
</Card.Footer>
</>
) : (
<div className="submission-files-title no-submissions">
<h3>{this.title}</h3>
</div>
)}
</Card>
);
}
}
)
}
</Card.Footer>
</>
) : (
<div className="submission-files-title no-submissions">
<h3>{getTitle()}</h3>
</div>
)}
</Card>
);
};
SubmissionFiles.defaultProps = {
files: [],
};
SubmissionFiles.propTypes = {
files: PropTypes.arrayOf(
PropTypes.shape({
@@ -114,7 +112,6 @@ SubmissionFiles.propTypes = {
downloadURL: PropTypes.string,
}),
),
intl: intlShape.isRequired,
};
export default injectIntl(SubmissionFiles);
export default SubmissionFiles;

View File

@@ -32,7 +32,7 @@ describe('SubmissionFiles', () => {
};
let el;
beforeEach(() => {
el = shallow(<SubmissionFiles intl={{ formatMessage }} {...props} />);
el = shallow(<SubmissionFiles {...props} />);
});
describe('snapshot', () => {
@@ -41,13 +41,13 @@ describe('SubmissionFiles', () => {
});
test('files does not exist', () => {
el = shallow(<SubmissionFiles intl={{ formatMessage }} {...props} files={[]} />);
el = shallow(<SubmissionFiles {...props} files={[]} />);
expect(el.snapshot).toMatchSnapshot();
});
test('files size exceed', () => {
const files = props.files.map(file => ({ ...file, size: downloadSingleLimit + 1 }));
el = shallow(<SubmissionFiles intl={{ formatMessage }} {...props} files={files} />);
el = shallow(<SubmissionFiles {...props} files={files} />);
expect(el.snapshot).toMatchSnapshot();
});
});
@@ -70,7 +70,7 @@ describe('SubmissionFiles', () => {
oneFileExceed.forEach(file => expect(file.size < downloadAllLimit).toEqual(true));
el = shallow(<SubmissionFiles intl={{ formatMessage }} {...props} files={oneFileExceed} />);
el = shallow(<SubmissionFiles {...props} files={oneFileExceed} />);
expect(el.instance.findByTestId('file-download')).toHaveLength(0);
const warningEl = el.instance.findByTestId('exceed-download-text')[0];
@@ -90,7 +90,7 @@ describe('SubmissionFiles', () => {
expect(file.size < downloadSingleLimit).toEqual(true);
});
el = shallow(<SubmissionFiles intl={{ formatMessage }} {...props} files={totalFilesExceed} />);
el = shallow(<SubmissionFiles {...props} files={totalFilesExceed} />);
expect(el.instance.findByTestId('file-download')).toHaveLength(0);
});
});

View File

@@ -1,33 +1,36 @@
import React from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { useIntl } from '@edx/frontend-platform/i18n';
import ConfirmModal from 'components/ConfirmModal';
import messages from './messages';
export const OverrideGradeConfirmModal = ({
intl,
isOpen,
onCancel,
onConfirm,
}) => (
<ConfirmModal
title={intl.formatMessage(messages.overrideConfirmTitle)}
content={intl.formatMessage(messages.overrideConfirmWarning)}
cancelText={intl.formatMessage(messages.goBack)}
confirmText={intl.formatMessage(messages.overrideConfirmContinue)}
onCancel={onCancel}
onConfirm={onConfirm}
isOpen={isOpen}
/>
);
export const OverrideGradeConfirmModal = (
{
isOpen,
onCancel,
onConfirm,
},
) => {
const intl = useIntl();
return (
<ConfirmModal
title={intl.formatMessage(messages.overrideConfirmTitle)}
content={intl.formatMessage(messages.overrideConfirmWarning)}
cancelText={intl.formatMessage(messages.goBack)}
confirmText={intl.formatMessage(messages.overrideConfirmContinue)}
onCancel={onCancel}
onConfirm={onConfirm}
isOpen={isOpen}
/>
);
};
OverrideGradeConfirmModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
onCancel: PropTypes.func.isRequired,
onConfirm: PropTypes.func.isRequired,
// injected
intl: intlShape.isRequired,
};
export default injectIntl(OverrideGradeConfirmModal);
export default OverrideGradeConfirmModal;

View File

@@ -1,13 +1,11 @@
import { shallow } from '@edx/react-unit-test-utils';
import { formatMessage } from 'testUtils';
import { OverrideGradeConfirmModal } from './OverrideGradeConfirmModal';
jest.mock('components/ConfirmModal', () => 'ConfirmModal');
describe('OverrideGradeConfirmModal', () => {
const props = {
intl: { formatMessage },
isOpen: false,
onCancel: jest.fn().mockName('this.props.onCancel'),
onConfirm: jest.fn().mockName('this.props.onConfirm'),

View File

@@ -1,33 +1,36 @@
import React from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { useIntl } from '@edx/frontend-platform/i18n';
import ConfirmModal from 'components/ConfirmModal';
import messages from './messages';
export const CloseReviewConfirmModal = ({
intl,
isOpen,
onCancel,
onConfirm,
}) => (
<ConfirmModal
title={intl.formatMessage(messages.closeReviewConfirmTitle)}
content={intl.formatMessage(messages.closeReviewConfirmWarning)}
cancelText={intl.formatMessage(messages.goBack)}
confirmText={intl.formatMessage(messages.confirmCloseModalAction)}
onCancel={onCancel}
onConfirm={onConfirm}
isOpen={isOpen}
/>
);
export const CloseReviewConfirmModal = (
{
isOpen,
onCancel,
onConfirm,
},
) => {
const intl = useIntl();
return (
<ConfirmModal
title={intl.formatMessage(messages.closeReviewConfirmTitle)}
content={intl.formatMessage(messages.closeReviewConfirmWarning)}
cancelText={intl.formatMessage(messages.goBack)}
confirmText={intl.formatMessage(messages.confirmCloseModalAction)}
onCancel={onCancel}
onConfirm={onConfirm}
isOpen={isOpen}
/>
);
};
CloseReviewConfirmModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
onCancel: PropTypes.func.isRequired,
onConfirm: PropTypes.func.isRequired,
// injected
intl: intlShape.isRequired,
};
export default injectIntl(CloseReviewConfirmModal);
export default CloseReviewConfirmModal;

View File

@@ -1,13 +1,11 @@
import { shallow } from '@edx/react-unit-test-utils';
import { formatMessage } from 'testUtils';
import { CloseReviewConfirmModal } from './CloseReviewConfirmModal';
jest.mock('components/ConfirmModal', () => 'ConfirmModal');
describe('CloseReviewConfirmModal', () => {
const props = {
intl: { formatMessage },
isOpen: false,
onCancel: jest.fn().mockName('this.props.onCancel'),
onConfirm: jest.fn().mockName('this.props.onConfirm'),