Skip to content

Commit d2ed3e5

Browse files
authored
refactor: replaced injectIntl with useIntl (#1239)
* refactor: replaced injectIntl with useIntl * test: update tests for useIntl hook implementation * fix: add missing trailing comma * test: fix failing component tests and remove deprecated defaultProps - Fix SwitchContent component defaultProps warning with default parameters - Fix Visibility component formatMessage errors and remove defaultProps - Fix FormControls component intl provider issues with useIntl mock - Fix EditButton component defaultProps and message formatting - Update EditableItemHeader, EmptyContent components - Replace React defaultProps with ES6 default parameters across components - Update test mocking to properly handle useIntl hook - All 82 tests now pass (previously 4 failed, 78 passed) Resolves React deprecation warnings and modernizes component patterns. * fix: add missing trailing comma
1 parent c73d1f9 commit d2ed3e5

File tree

10 files changed

+176
-122
lines changed

10 files changed

+176
-122
lines changed

src/head/Head.jsx

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,26 @@
11
import React from 'react';
22
import { Helmet } from 'react-helmet';
3-
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
3+
import { useIntl } from '@edx/frontend-platform/i18n';
44
import { getConfig } from '@edx/frontend-platform';
55

66
import messages from './messages';
77

8-
const Head = ({ intl }) => (
9-
<Helmet>
10-
<title>
11-
{intl.formatMessage(messages['profile.page.title'], { siteName: getConfig().SITE_NAME })}
12-
</title>
13-
<link rel="shortcut icon" href={getConfig().FAVICON_URL} type="image/x-icon" />
14-
</Helmet>
15-
);
16-
17-
Head.propTypes = {
18-
intl: intlShape.isRequired,
8+
const Head = () => {
9+
const intl = useIntl();
10+
return (
11+
<Helmet>
12+
<title>
13+
{intl.formatMessage(messages['profile.page.title'], {
14+
siteName: getConfig().SITE_NAME,
15+
})}
16+
</title>
17+
<link
18+
rel="shortcut icon"
19+
href={getConfig().FAVICON_URL}
20+
type="image/x-icon"
21+
/>
22+
</Helmet>
23+
);
1924
};
2025

21-
export default injectIntl(Head);
26+
export default Head;
Lines changed: 27 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,41 @@
11
import React from 'react';
22
import PropTypes from 'prop-types';
33
import { EditOutline } from '@openedx/paragon/icons';
4-
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
4+
import { useIntl } from '@edx/frontend-platform/i18n';
55
import { Button, OverlayTrigger, Tooltip } from '@openedx/paragon';
66
import messages from './EditButton.messages';
77

8-
const EditButton = ({
9-
onClick, className, style, intl,
10-
}) => (
11-
<OverlayTrigger
12-
key="top"
13-
placement="top"
14-
overlay={(
15-
<Tooltip variant="light" id="tooltip-top">
16-
<p className="h5 font-weight-normal m-0 p-0">
17-
{intl.formatMessage(messages['profile.editbutton.edit'])}
18-
</p>
19-
</Tooltip>
20-
)}
21-
>
22-
<Button
23-
variant="link"
24-
size="sm"
25-
className={className}
26-
onClick={onClick}
27-
style={style}
8+
const EditButton = ({ onClick, className = null, style = null }) => {
9+
const intl = useIntl();
10+
return (
11+
<OverlayTrigger
12+
key="top"
13+
placement="top"
14+
overlay={(
15+
<Tooltip variant="light" id="tooltip-top">
16+
<p className="h5 font-weight-normal m-0 p-0">
17+
{intl.formatMessage(messages['profile.editbutton.edit'])}
18+
</p>
19+
</Tooltip>
20+
)}
2821
>
29-
<EditOutline className="text-gray-700" />
30-
</Button>
31-
</OverlayTrigger>
32-
);
22+
<Button
23+
variant="link"
24+
size="sm"
25+
className={className}
26+
onClick={onClick}
27+
style={style}
28+
>
29+
<EditOutline className="text-gray-700" />
30+
</Button>
31+
</OverlayTrigger>
32+
);
33+
};
3334

34-
export default injectIntl(EditButton);
35+
export default EditButton;
3536

3637
EditButton.propTypes = {
3738
onClick: PropTypes.func.isRequired,
3839
className: PropTypes.string,
3940
style: PropTypes.object, // eslint-disable-line
40-
intl: intlShape.isRequired,
41-
};
42-
43-
EditButton.defaultProps = {
44-
className: null,
45-
style: null,
4641
};
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import React from 'react';
2+
import { render, fireEvent } from '@testing-library/react';
3+
import { IntlProvider } from '@edx/frontend-platform/i18n';
4+
import EditButton from './EditButton';
5+
6+
const messages = {
7+
'profile.editbutton.edit': 'Edit',
8+
};
9+
10+
describe('EditButton', () => {
11+
it('renders and calls onClick when clicked', () => {
12+
const onClick = jest.fn();
13+
const { getByRole } = render(
14+
<IntlProvider locale="en" messages={messages}>
15+
<EditButton onClick={onClick} />
16+
</IntlProvider>,
17+
);
18+
const button = getByRole('button');
19+
fireEvent.click(button);
20+
expect(onClick).toHaveBeenCalled();
21+
});
22+
});

src/profile/forms/elements/EditableItemHeader.jsx

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ import { Visibility } from './Visibility';
77
import { useIsOnMobileScreen } from '../../data/hooks';
88

99
const EditableItemHeader = ({
10-
content,
11-
showVisibility,
12-
visibility,
13-
showEditButton,
14-
onClickEdit,
15-
headingId,
10+
content = '',
11+
showVisibility = false,
12+
visibility = 'private',
13+
showEditButton = false,
14+
onClickEdit = () => {},
15+
headingId = null,
1616
}) => {
1717
const isMobileView = useIsOnMobileScreen();
1818
return (
@@ -57,13 +57,3 @@ EditableItemHeader.propTypes = {
5757
visibility: PropTypes.oneOf(['private', 'all_users']),
5858
headingId: PropTypes.string,
5959
};
60-
61-
EditableItemHeader.defaultProps = {
62-
onClickEdit: () => {
63-
},
64-
showVisibility: false,
65-
showEditButton: false,
66-
content: '',
67-
visibility: 'private',
68-
headingId: null,
69-
};

src/profile/forms/elements/EmptyContent.jsx

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
33
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
44
import { faPlus } from '@fortawesome/free-solid-svg-icons';
55

6-
const EmptyContent = ({ children, onClick, showPlusIcon }) => (
6+
const EmptyContent = ({ children = null, onClick = null, showPlusIcon = true }) => (
77
<div className="p-0 m-0">
88
{onClick ? (
99
<button
@@ -27,9 +27,3 @@ EmptyContent.propTypes = {
2727
children: PropTypes.node,
2828
showPlusIcon: PropTypes.bool,
2929
};
30-
31-
EmptyContent.defaultProps = {
32-
onClick: null,
33-
children: null,
34-
showPlusIcon: true,
35-
};

src/profile/forms/elements/FormControls.jsx

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
import React from 'react';
22
import PropTypes from 'prop-types';
33
import { Button, StatefulButton } from '@openedx/paragon';
4-
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
4+
import { useIntl } from '@edx/frontend-platform/i18n';
55

66
import messages from './FormControls.messages';
77

88
import { VisibilitySelect } from './Visibility';
99
import { useIsVisibilityEnabled } from '../../data/hooks';
1010

1111
const FormControls = ({
12-
cancelHandler, changeHandler, visibility, visibilityId, saveState, intl,
12+
cancelHandler,
13+
changeHandler,
14+
visibility = 'private',
15+
visibilityId,
16+
saveState = null,
1317
}) => {
18+
const intl = useIntl();
1419
const buttonState = saveState === 'error' ? null : saveState;
1520
const isVisibilityEnabled = useIsVisibilityEnabled();
1621

@@ -42,18 +47,24 @@ const FormControls = ({
4247
type="submit"
4348
state={buttonState}
4449
labels={{
45-
default: intl.formatMessage(messages['profile.formcontrols.button.save']),
46-
pending: intl.formatMessage(messages['profile.formcontrols.button.saving']),
47-
complete: intl.formatMessage(messages['profile.formcontrols.button.saved']),
50+
default: intl.formatMessage(
51+
messages['profile.formcontrols.button.save'],
52+
),
53+
pending: intl.formatMessage(
54+
messages['profile.formcontrols.button.saving'],
55+
),
56+
complete: intl.formatMessage(
57+
messages['profile.formcontrols.button.saved'],
58+
),
4859
}}
4960
onClick={(e) => {
50-
// Swallow clicks if the state is pending.
51-
// We do this instead of disabling the button to prevent
52-
// it from losing focus (disabled elements cannot have focus).
53-
// Disabling it would causes upstream issues in focus management.
54-
// Swallowing the onSubmit event on the form would be better, but
55-
// we would have to add that logic for every field given our
56-
// current structure of the application.
61+
// Swallow clicks if the state is pending.
62+
// We do this instead of disabling the button to prevent
63+
// it from losing focus (disabled elements cannot have focus).
64+
// Disabling it would causes upstream issues in focus management.
65+
// Swallowing the onSubmit event on the form would be better, but
66+
// we would have to add that logic for every field given our
67+
// current structure of the application.
5768
if (buttonState === 'pending') {
5869
e.preventDefault();
5970
}
@@ -66,19 +77,12 @@ const FormControls = ({
6677
);
6778
};
6879

69-
export default injectIntl(FormControls);
80+
export default FormControls;
7081

7182
FormControls.propTypes = {
7283
saveState: PropTypes.oneOf([null, 'pending', 'complete', 'error']),
7384
visibility: PropTypes.oneOf(['private', 'all_users']),
7485
visibilityId: PropTypes.string.isRequired,
7586
cancelHandler: PropTypes.func.isRequired,
7687
changeHandler: PropTypes.func.isRequired,
77-
78-
intl: intlShape.isRequired,
79-
};
80-
81-
FormControls.defaultProps = {
82-
visibility: 'private',
83-
saveState: null,
8488
};

src/profile/forms/elements/FormControls.test.jsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ jest.mock('@edx/frontend-platform/i18n', () => {
1515
const actual = jest.requireActual('@edx/frontend-platform/i18n');
1616
return {
1717
...actual,
18+
useIntl: () => ({
19+
formatMessage: (msg) => msg.id, // returns id so we can assert on it
20+
}),
1821
injectIntl: (Component) => (props) => (
1922
<Component
2023
{...props}
@@ -27,6 +30,10 @@ jest.mock('@edx/frontend-platform/i18n', () => {
2730
};
2831
});
2932

33+
jest.mock('../../data/hooks', () => ({
34+
useIsVisibilityEnabled: () => true,
35+
}));
36+
3037
describe('FormControls', () => {
3138
it('renders Save button label when saveState is null', () => {
3239
render(<FormControls {...defaultProps} />);

src/profile/forms/elements/SwitchContent.jsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const onChildExit = (htmlNode) => {
1717
}
1818
};
1919

20-
const SwitchContent = ({ expression, cases, className }) => {
20+
const SwitchContent = ({ expression = null, cases, className = null }) => {
2121
const getContent = (caseKey) => {
2222
if (cases[caseKey]) {
2323
if (typeof cases[caseKey] === 'string') {
@@ -51,9 +51,4 @@ SwitchContent.propTypes = {
5151
className: PropTypes.string,
5252
};
5353

54-
SwitchContent.defaultProps = {
55-
expression: null,
56-
className: null,
57-
};
58-
5954
export default SwitchContent;

0 commit comments

Comments
 (0)