diff --git a/src/head/Head.jsx b/src/head/Head.jsx index b84fd32a2..247815391 100644 --- a/src/head/Head.jsx +++ b/src/head/Head.jsx @@ -1,21 +1,26 @@ import React from 'react'; import { Helmet } from 'react-helmet'; -import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import { useIntl } from '@edx/frontend-platform/i18n'; import { getConfig } from '@edx/frontend-platform'; import messages from './messages'; -const Head = ({ intl }) => ( - - - {intl.formatMessage(messages['profile.page.title'], { siteName: getConfig().SITE_NAME })} - - - -); - -Head.propTypes = { - intl: intlShape.isRequired, +const Head = () => { + const intl = useIntl(); + return ( + + + {intl.formatMessage(messages['profile.page.title'], { + siteName: getConfig().SITE_NAME, + })} + + + + ); }; -export default injectIntl(Head); +export default Head; diff --git a/src/profile/forms/elements/EditButton.jsx b/src/profile/forms/elements/EditButton.jsx index 80cb47946..8444a450e 100644 --- a/src/profile/forms/elements/EditButton.jsx +++ b/src/profile/forms/elements/EditButton.jsx @@ -1,43 +1,43 @@ import React from 'react'; import PropTypes from 'prop-types'; import { EditOutline } from '@openedx/paragon/icons'; -import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import { useIntl } from '@edx/frontend-platform/i18n'; import { Button, OverlayTrigger, Tooltip } from '@openedx/paragon'; import messages from './EditButton.messages'; -const EditButton = ({ - onClick, className, style, intl, -}) => ( - -

- {intl.formatMessage(messages['profile.editbutton.edit'])} -

- - )} - > - -
-); + + + ); +}; -export default injectIntl(EditButton); +export default EditButton; EditButton.propTypes = { onClick: PropTypes.func.isRequired, className: PropTypes.string, style: PropTypes.object, // eslint-disable-line - intl: intlShape.isRequired, }; EditButton.defaultProps = { diff --git a/src/profile/forms/elements/EditButton.test.jsx b/src/profile/forms/elements/EditButton.test.jsx new file mode 100644 index 000000000..d37f8666c --- /dev/null +++ b/src/profile/forms/elements/EditButton.test.jsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { render, fireEvent } from '@testing-library/react'; +import { IntlProvider } from '@edx/frontend-platform/i18n'; +import EditButton from './EditButton'; + +const messages = { + 'profile.editbutton.edit': { defaultMessage: 'Edit' }, +}; + +describe('EditButton', () => { + it('renders and calls onClick when clicked', () => { + const onClick = jest.fn(); + const { getByRole } = render( + + + , + ); + const button = getByRole('button'); + fireEvent.click(button); + expect(onClick).toHaveBeenCalled(); + }); +}); diff --git a/src/profile/forms/elements/FormControls.jsx b/src/profile/forms/elements/FormControls.jsx index ddb286671..304c69850 100644 --- a/src/profile/forms/elements/FormControls.jsx +++ b/src/profile/forms/elements/FormControls.jsx @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Button, StatefulButton } from '@openedx/paragon'; -import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import { useIntl } from '@edx/frontend-platform/i18n'; import messages from './FormControls.messages'; @@ -9,8 +9,13 @@ import { VisibilitySelect } from './Visibility'; import { useIsVisibilityEnabled } from '../../data/hooks'; const FormControls = ({ - cancelHandler, changeHandler, visibility, visibilityId, saveState, intl, + cancelHandler, + changeHandler, + visibility, + visibilityId, + saveState, }) => { + const intl = useIntl(); const buttonState = saveState === 'error' ? null : saveState; const isVisibilityEnabled = useIsVisibilityEnabled(); @@ -42,18 +47,17 @@ const FormControls = ({ type="submit" state={buttonState} labels={{ - default: intl.formatMessage(messages['profile.formcontrols.button.save']), - pending: intl.formatMessage(messages['profile.formcontrols.button.saving']), - complete: intl.formatMessage(messages['profile.formcontrols.button.saved']), + default: intl.formatMessage( + messages['profile.formcontrols.button.save'], + ), + pending: intl.formatMessage( + messages['profile.formcontrols.button.saving'], + ), + complete: intl.formatMessage( + messages['profile.formcontrols.button.saved'], + ), }} onClick={(e) => { - // Swallow clicks if the state is pending. - // We do this instead of disabling the button to prevent - // it from losing focus (disabled elements cannot have focus). - // Disabling it would causes upstream issues in focus management. - // Swallowing the onSubmit event on the form would be better, but - // we would have to add that logic for every field given our - // current structure of the application. if (buttonState === 'pending') { e.preventDefault(); } @@ -66,7 +70,7 @@ const FormControls = ({ ); }; -export default injectIntl(FormControls); +export default FormControls; FormControls.propTypes = { saveState: PropTypes.oneOf([null, 'pending', 'complete', 'error']), @@ -74,8 +78,6 @@ FormControls.propTypes = { visibilityId: PropTypes.string.isRequired, cancelHandler: PropTypes.func.isRequired, changeHandler: PropTypes.func.isRequired, - - intl: intlShape.isRequired, }; FormControls.defaultProps = { diff --git a/src/profile/forms/elements/Visibility.jsx b/src/profile/forms/elements/Visibility.jsx index cfe67a26e..315c148ff 100644 --- a/src/profile/forms/elements/Visibility.jsx +++ b/src/profile/forms/elements/Visibility.jsx @@ -1,17 +1,20 @@ 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 { getConfig } from '@edx/frontend-platform'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faEyeSlash, faEye } from '@fortawesome/free-regular-svg-icons'; import messages from './Visibility.messages'; -const Visibility = ({ to, intl }) => { +const Visibility = ({ to }) => { + const intl = useIntl(); const icon = to === 'private' ? faEyeSlash : faEye; const label = to === 'private' ? intl.formatMessage(messages['profile.visibility.who.just.me']) - : intl.formatMessage(messages['profile.visibility.who.everyone'], { siteName: getConfig().SITE_NAME }); + : intl.formatMessage(messages['profile.visibility.who.everyone'], { + siteName: getConfig().SITE_NAME, + }); return ( @@ -22,14 +25,13 @@ const Visibility = ({ to, intl }) => { Visibility.propTypes = { to: PropTypes.oneOf(['private', 'all_users']), - - intl: intlShape.isRequired, }; Visibility.defaultProps = { to: 'private', }; -const VisibilitySelect = ({ intl, className, ...props }) => { +const VisibilitySelect = ({ className, ...props }) => { + const intl = useIntl(); const { value } = props; const icon = value === 'private' ? faEyeSlash : faEye; @@ -43,7 +45,9 @@ const VisibilitySelect = ({ intl, className, ...props }) => { {intl.formatMessage(messages['profile.visibility.who.just.me'])} @@ -56,8 +60,6 @@ VisibilitySelect.propTypes = { name: PropTypes.string, value: PropTypes.oneOf(['private', 'all_users']), onChange: PropTypes.func, - - intl: intlShape.isRequired, }; VisibilitySelect.defaultProps = { id: null, @@ -67,10 +69,4 @@ VisibilitySelect.defaultProps = { onChange: null, }; -const intlVisibility = injectIntl(Visibility); -const intlVisibilitySelect = injectIntl(VisibilitySelect); - -export { - intlVisibility as Visibility, - intlVisibilitySelect as VisibilitySelect, -}; +export { Visibility, VisibilitySelect }; diff --git a/src/profile/forms/elements/Visibility.test.jsx b/src/profile/forms/elements/Visibility.test.jsx new file mode 100644 index 000000000..4fab7ef26 --- /dev/null +++ b/src/profile/forms/elements/Visibility.test.jsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import { IntlProvider } from '@edx/frontend-platform/i18n'; +import { Visibility, VisibilitySelect } from './Visibility'; +import '@testing-library/jest-dom'; + +const messages = { + 'profile.visibility.who.just.me': { defaultMessage: 'Just me' }, + 'profile.visibility.who.everyone': { defaultMessage: 'Everyone' }, +}; + +describe('Visibility', () => { + it('shows the correct icon and label for private', () => { + const { getByText } = render( + + + , + ); + expect(getByText(/just me/i)).toBeInTheDocument(); + }); + it('shows the correct icon and label for all_users', () => { + const { getByText } = render( + + + , + ); + expect(getByText(/everyone/i)).toBeInTheDocument(); + }); +}); + +describe('VisibilitySelect', () => { + it('renders both options', () => { + const { getByRole, getAllByRole } = render( + + {}} /> + , + ); + const select = getByRole('combobox'); + const options = getAllByRole('option'); + expect(select).toBeInTheDocument(); + expect(options.length).toBe(2); + }); +});