Skip to content

Commit 2610def

Browse files
committed
refactor: replaced injectIntl with useIntl
1 parent 58e1401 commit 2610def

File tree

7 files changed

+164
-71
lines changed

7 files changed

+164
-71
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;

src/profile/forms/elements/EditButton.jsx

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,43 @@
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, style }) => {
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,
4141
};
4242

4343
EditButton.defaultProps = {
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': { defaultMessage: '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/FormControls.jsx

Lines changed: 17 additions & 15 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,
15+
visibilityId,
16+
saveState,
1317
}) => {
18+
const intl = useIntl();
1419
const buttonState = saveState === 'error' ? null : saveState;
1520
const isVisibilityEnabled = useIsVisibilityEnabled();
1621

@@ -42,18 +47,17 @@ 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.
5761
if (buttonState === 'pending') {
5862
e.preventDefault();
5963
}
@@ -66,16 +70,14 @@ const FormControls = ({
6670
);
6771
};
6872

69-
export default injectIntl(FormControls);
73+
export default FormControls;
7074

7175
FormControls.propTypes = {
7276
saveState: PropTypes.oneOf([null, 'pending', 'complete', 'error']),
7377
visibility: PropTypes.oneOf(['private', 'all_users']),
7478
visibilityId: PropTypes.string.isRequired,
7579
cancelHandler: PropTypes.func.isRequired,
7680
changeHandler: PropTypes.func.isRequired,
77-
78-
intl: intlShape.isRequired,
7981
};
8082

8183
FormControls.defaultProps = {
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import React from 'react';
2+
import { render, fireEvent } from '@testing-library/react';
3+
import { IntlProvider } from '@edx/frontend-platform/i18n';
4+
import FormControls from './FormControls';
5+
import messages from './FormControls.messages';
6+
7+
describe('FormControls', () => {
8+
it('renders and triggers cancelHandler', () => {
9+
const cancelHandler = jest.fn();
10+
const changeHandler = jest.fn();
11+
const { getByText } = render(
12+
<IntlProvider locale="en" messages={messages}>
13+
<FormControls
14+
cancelHandler={cancelHandler}
15+
changeHandler={changeHandler}
16+
visibilityId="test-visibility"
17+
/>
18+
</IntlProvider>,
19+
);
20+
// Use the actual label from the messages file
21+
const cancelLabel = messages['profile.formcontrols.button.cancel'].defaultMessage;
22+
fireEvent.click(getByText(cancelLabel));
23+
expect(cancelHandler).toHaveBeenCalled();
24+
});
25+
});
Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
import React from 'react';
22
import PropTypes from 'prop-types';
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
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
66
import { faEyeSlash, faEye } from '@fortawesome/free-regular-svg-icons';
77

88
import messages from './Visibility.messages';
99

10-
const Visibility = ({ to, intl }) => {
10+
const Visibility = ({ to }) => {
11+
const intl = useIntl();
1112
const icon = to === 'private' ? faEyeSlash : faEye;
1213
const label = to === 'private'
1314
? intl.formatMessage(messages['profile.visibility.who.just.me'])
14-
: intl.formatMessage(messages['profile.visibility.who.everyone'], { siteName: getConfig().SITE_NAME });
15+
: intl.formatMessage(messages['profile.visibility.who.everyone'], {
16+
siteName: getConfig().SITE_NAME,
17+
});
1518

1619
return (
1720
<span className="ml-auto small text-muted">
@@ -22,14 +25,13 @@ const Visibility = ({ to, intl }) => {
2225

2326
Visibility.propTypes = {
2427
to: PropTypes.oneOf(['private', 'all_users']),
25-
26-
intl: intlShape.isRequired,
2728
};
2829
Visibility.defaultProps = {
2930
to: 'private',
3031
};
3132

32-
const VisibilitySelect = ({ intl, className, ...props }) => {
33+
const VisibilitySelect = ({ className, ...props }) => {
34+
const intl = useIntl();
3335
const { value } = props;
3436
const icon = value === 'private' ? faEyeSlash : faEye;
3537

@@ -43,7 +45,9 @@ const VisibilitySelect = ({ intl, className, ...props }) => {
4345
{intl.formatMessage(messages['profile.visibility.who.just.me'])}
4446
</option>
4547
<option key="all_users" value="all_users">
46-
{intl.formatMessage(messages['profile.visibility.who.everyone'], { siteName: getConfig().SITE_NAME })}
48+
{intl.formatMessage(messages['profile.visibility.who.everyone'], {
49+
siteName: getConfig().SITE_NAME,
50+
})}
4751
</option>
4852
</select>
4953
</span>
@@ -56,8 +60,6 @@ VisibilitySelect.propTypes = {
5660
name: PropTypes.string,
5761
value: PropTypes.oneOf(['private', 'all_users']),
5862
onChange: PropTypes.func,
59-
60-
intl: intlShape.isRequired,
6163
};
6264
VisibilitySelect.defaultProps = {
6365
id: null,
@@ -67,10 +69,4 @@ VisibilitySelect.defaultProps = {
6769
onChange: null,
6870
};
6971

70-
const intlVisibility = injectIntl(Visibility);
71-
const intlVisibilitySelect = injectIntl(VisibilitySelect);
72-
73-
export {
74-
intlVisibility as Visibility,
75-
intlVisibilitySelect as VisibilitySelect,
76-
};
72+
export { Visibility, VisibilitySelect };
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import React from 'react';
2+
import { render } from '@testing-library/react';
3+
import { IntlProvider } from '@edx/frontend-platform/i18n';
4+
import { Visibility, VisibilitySelect } from './Visibility';
5+
import '@testing-library/jest-dom';
6+
7+
const messages = {
8+
'profile.visibility.who.just.me': { defaultMessage: 'Just me' },
9+
'profile.visibility.who.everyone': { defaultMessage: 'Everyone' },
10+
};
11+
12+
describe('Visibility', () => {
13+
it('shows the correct icon and label for private', () => {
14+
const { getByText } = render(
15+
<IntlProvider locale="en" messages={messages}>
16+
<Visibility to="private" />
17+
</IntlProvider>,
18+
);
19+
expect(getByText(/just me/i)).toBeInTheDocument();
20+
});
21+
it('shows the correct icon and label for all_users', () => {
22+
const { getByText } = render(
23+
<IntlProvider locale="en" messages={messages}>
24+
<Visibility to="all_users" />
25+
</IntlProvider>,
26+
);
27+
expect(getByText(/everyone/i)).toBeInTheDocument();
28+
});
29+
});
30+
31+
describe('VisibilitySelect', () => {
32+
it('renders both options', () => {
33+
const { getByRole, getAllByRole } = render(
34+
<IntlProvider locale="en" messages={messages}>
35+
<VisibilitySelect value="private" onChange={() => {}} />
36+
</IntlProvider>,
37+
);
38+
const select = getByRole('combobox');
39+
const options = getAllByRole('option');
40+
expect(select).toBeInTheDocument();
41+
expect(options.length).toBe(2);
42+
});
43+
});

0 commit comments

Comments
 (0)