From 9163cba9766152ec783ce1385736f9cc700014bd Mon Sep 17 00:00:00 2001 From: diana-villalvazo-wgu Date: Wed, 6 Aug 2025 13:29:47 -0500 Subject: [PATCH 1/2] refactor: Replace of injectIntl with useIntl --- src/components/FilterBar.jsx | 9 +++---- src/components/Head/Head.jsx | 26 +++++++++---------- .../components/BackButton.jsx | 11 ++++---- .../topic-search/TopicSearchResultBar.jsx | 13 +++------- 4 files changed, 26 insertions(+), 33 deletions(-) diff --git a/src/components/FilterBar.jsx b/src/components/FilterBar.jsx index 0c506279a..da5b9427c 100644 --- a/src/components/FilterBar.jsx +++ b/src/components/FilterBar.jsx @@ -1,4 +1,4 @@ -import React, { useCallback, useMemo, useState } from 'react'; +import { useCallback, useMemo, useState } from 'react'; import PropTypes from 'prop-types'; import { @@ -8,7 +8,7 @@ import { Tune } from '@openedx/paragon/icons'; import { capitalize, toString } from 'lodash'; import { useSelector } from 'react-redux'; -import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import { useIntl } from '@edx/frontend-platform/i18n'; import { PostsStatusFilter, RequestStatus, @@ -19,12 +19,12 @@ import messages from '../discussions/posts/post-filter-bar/messages'; import { ActionItem } from '../discussions/posts/post-filter-bar/PostFilterBar'; const FilterBar = ({ - intl, filters, selectedFilters, onFilterChange, showCohortsFilter, }) => { + const intl = useIntl(); const [isOpen, setOpen] = useState(false); const cohorts = useSelector(selectCourseCohorts); const { status } = useSelector(state => state.cohorts); @@ -192,7 +192,6 @@ const FilterBar = ({ }; FilterBar.propTypes = { - intl: intlShape.isRequired, filters: PropTypes.arrayOf(PropTypes.shape({ name: PropTypes.string, filters: PropTypes.arrayOf(PropTypes.string), @@ -211,4 +210,4 @@ FilterBar.defaultProps = { showCohortsFilter: false, }; -export default injectIntl(FilterBar); +export default FilterBar; diff --git a/src/components/Head/Head.jsx b/src/components/Head/Head.jsx index 2cda3b1db..f81ab6320 100644 --- a/src/components/Head/Head.jsx +++ b/src/components/Head/Head.jsx @@ -1,23 +1,21 @@ -import React from 'react'; - import { Helmet } from 'react-helmet'; import { getConfig } from '@edx/frontend-platform'; -import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import { useIntl } from '@edx/frontend-platform/i18n'; import messages from './messages'; -const Head = ({ intl }) => ( - - - {intl.formatMessage(messages['discussions.page.title'], { siteName: getConfig().SITE_NAME })} - - - -); +const Head = () => { + const intl = useIntl(); -Head.propTypes = { - intl: intlShape.isRequired, + return ( + + + {intl.formatMessage(messages['discussions.page.title'], { siteName: getConfig().SITE_NAME })} + + + + ); }; -export default injectIntl(Head); +export default Head; diff --git a/src/discussions/in-context-topics/components/BackButton.jsx b/src/discussions/in-context-topics/components/BackButton.jsx index fb182831d..12d89ef72 100644 --- a/src/discussions/in-context-topics/components/BackButton.jsx +++ b/src/discussions/in-context-topics/components/BackButton.jsx @@ -1,17 +1,19 @@ -import React from 'react'; import PropTypes from 'prop-types'; import { Icon, IconButton, Spinner } from '@openedx/paragon'; import { ArrowBack } from '@openedx/paragon/icons'; import { useNavigate } from 'react-router-dom'; -import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import { useIntl } from '@edx/frontend-platform/i18n'; import messages from '../messages'; const BackButton = ({ - intl, path, title, loading, + path, + title, + loading, }) => { + const intl = useIntl(); const navigate = useNavigate(); return ( @@ -35,7 +37,6 @@ const BackButton = ({ }; BackButton.propTypes = { - intl: intlShape.isRequired, path: PropTypes.shape({}).isRequired, title: PropTypes.string.isRequired, loading: PropTypes.bool, @@ -45,4 +46,4 @@ BackButton.defaultProps = { loading: false, }; -export default injectIntl(BackButton); +export default BackButton; diff --git a/src/discussions/in-context-topics/topic-search/TopicSearchResultBar.jsx b/src/discussions/in-context-topics/topic-search/TopicSearchResultBar.jsx index 77e9c4515..16616c813 100644 --- a/src/discussions/in-context-topics/topic-search/TopicSearchResultBar.jsx +++ b/src/discussions/in-context-topics/topic-search/TopicSearchResultBar.jsx @@ -1,14 +1,13 @@ -import React from 'react'; - import { SearchField } from '@openedx/paragon'; import { useDispatch } from 'react-redux'; -import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import { useIntl } from '@edx/frontend-platform/i18n'; import { setFilter } from '../data'; import messages from '../messages'; -const TopicSearchResultBar = ({ intl }) => { +const TopicSearchResultBar = () => { + const intl = useIntl(); const dispatch = useDispatch(); return ( @@ -23,8 +22,4 @@ const TopicSearchResultBar = ({ intl }) => { ); }; -TopicSearchResultBar.propTypes = { - intl: intlShape.isRequired, -}; - -export default injectIntl(TopicSearchResultBar); +export default TopicSearchResultBar; From 3c25c43de1333fcd6aa1970b4a142e912c669cd4 Mon Sep 17 00:00:00 2001 From: diana-villalvazo-wgu Date: Fri, 8 Aug 2025 13:39:34 -0500 Subject: [PATCH 2/2] test: improve coverage --- .../common/ActionsDropdown.test.jsx | 19 ++++++ .../TopicSearchResultBar.test.jsx | 65 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 src/discussions/in-context-topics/topic-search/TopicSearchResultBar.test.jsx diff --git a/src/discussions/common/ActionsDropdown.test.jsx b/src/discussions/common/ActionsDropdown.test.jsx index 5a40f64a0..6da4385fd 100644 --- a/src/discussions/common/ActionsDropdown.test.jsx +++ b/src/discussions/common/ActionsDropdown.test.jsx @@ -242,6 +242,25 @@ describe('ActionsDropdown', () => { await waitFor(() => expect(screen.queryByText('Copy link')).not.toBeInTheDocument()); }); + it('should close the dropdown when pressing escape', async () => { + const discussionObject = buildTestContent({ editable_fields: ['copy_link'] }).discussion; + await mockThreadAndComment(discussionObject); + renderComponent({ ...camelCaseObject(discussionObject) }); + + const openButton = await findOpenActionsDropdownButton(); + await act(async () => { + fireEvent.click(openButton); + }); + + await waitFor(() => expect(screen.getByRole('button', { name: 'Copy link' })).toBeInTheDocument()); + + await act(async () => { + fireEvent.keyDown(document.body, { key: 'Escape', code: 'Escape' }); + }); + + await waitFor(() => expect(screen.queryByRole('button', { name: 'Copy link' })).toBeNull()); + }); + describe.each(canPerformActionTestData)('Actions', ({ testFor, action, label, ...commentOrPost }) => { diff --git a/src/discussions/in-context-topics/topic-search/TopicSearchResultBar.test.jsx b/src/discussions/in-context-topics/topic-search/TopicSearchResultBar.test.jsx new file mode 100644 index 000000000..5edce28eb --- /dev/null +++ b/src/discussions/in-context-topics/topic-search/TopicSearchResultBar.test.jsx @@ -0,0 +1,65 @@ +import { fireEvent, render } from '@testing-library/react'; +import { useDispatch } from 'react-redux'; + +import { IntlProvider } from '@edx/frontend-platform/i18n'; + +import { setFilter } from '../data'; +import messages from '../messages'; +import TopicSearchResultBar from './TopicSearchResultBar'; + +jest.mock('react-redux', () => ({ + useDispatch: jest.fn(), +})); + +jest.mock('../data', () => ({ + setFilter: jest.fn(), +})); + +describe('TopicSearchResultBar', () => { + const dispatch = jest.fn(); + + beforeEach(() => { + useDispatch.mockReturnValue(dispatch); + setFilter.mockReturnValue({ type: 'SET_FILTER' }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should render search field', () => { + const { getByPlaceholderText } = render( + + + , + ); + expect(getByPlaceholderText(messages.searchTopics.defaultMessage)).toBeInTheDocument(); + }); + + it('should dispatch setFilter on submit', () => { + const { getByPlaceholderText } = render( + + + , + ); + const searchField = getByPlaceholderText(messages.searchTopics.defaultMessage); + fireEvent.change(searchField, { target: { value: 'test query' } }); + fireEvent.submit(searchField); + + expect(setFilter).toHaveBeenCalledWith('test query'); + expect(dispatch).toHaveBeenCalled(); + }); + + it('should dispatch setFilter on change', () => { + const { getByPlaceholderText } = render( + + + , + ); + const searchField = getByPlaceholderText(messages.searchTopics.defaultMessage); + fireEvent.change(searchField, { target: { value: 'test' } }); + + expect(setFilter).toHaveBeenCalledWith('test'); + expect(dispatch).toHaveBeenCalled(); + }); +});