From 1dee2bba58fc95fdbedc685d93820d39a5326624 Mon Sep 17 00:00:00 2001 From: Braden MacDonald Date: Tue, 3 Jun 2025 13:52:56 -0700 Subject: [PATCH] test: refactor many test suites to use testUtils/initializeMocks (#2067) Simplifies a bunch of test code by converting it to use our handy testUtils module. None of the app code is change, just test code. And none of the test cases are modified in any meaningful way - this just simplifies the setup/cleanup significantly. --- jest.config.js | 1 - .../learning_assistant/Settings.test.jsx | 16 +--- src/CourseAuthoringPage.test.jsx | 3 +- src/CourseAuthoringRoutes.test.jsx | 7 +- .../AccessibilityPage.test.jsx | 37 +------- .../AdvancedSettings.test.jsx | 62 +++++-------- src/certificates/Certificates.test.jsx | 30 ++----- .../CertificatesSidebar.test.jsx | 27 +----- .../ChecklistSection.test.jsx | 5 +- .../outline-sidebar/OutlineSidebar.test.jsx | 47 ++-------- src/course-rerun/CourseRerun.test.jsx | 57 +++--------- .../CourseRerunSidebar.test.jsx | 36 +------- src/course-team/CourseTeam.jsx | 4 +- src/course-team/CourseTeam.test.jsx | 84 +++++------------- .../CourseTeamSideBar.test.jsx | 28 +----- .../add-component/AddComponent.test.jsx | 56 +++++------- src/custom-pages/CustomPages.test.jsx | 2 - src/export-page/CourseExportPage.test.jsx | 87 ++++++++----------- .../export-sidebar/ExportSidebar.test.jsx | 30 +------ src/generic/help-sidebar/HelpSidebar.test.jsx | 51 +++-------- .../GroupConfigurations.test.jsx | 36 +++----- .../GroupConfigurationSidebar.test.jsx | 34 +------- src/import-page/CourseImportPage.test.jsx | 71 ++++++--------- .../import-sidebar/ImportSidebar.test.jsx | 30 +------ .../PagesAndResources.test.jsx | 21 ++--- .../pages/PageCard.test.jsx | 5 -- src/pages-and-resources/utils.test.jsx | 48 ---------- .../ScheduleAndDetails.test.jsx | 57 ++++-------- .../home-sidebar/HomeSidebar.test.jsx | 34 ++------ .../tabs-section/courses-tab/index.test.tsx | 52 ++++------- .../VerifyEmailLayout.test.jsx | 50 ++--------- src/textbooks/Textbook.test.jsx | 31 ++----- .../textbook-sidebar/TextbookSidebar.test.jsx | 32 +------ 33 files changed, 279 insertions(+), 892 deletions(-) delete mode 100644 src/pages-and-resources/utils.test.jsx diff --git a/jest.config.js b/jest.config.js index 0af1e62af..141b70a4c 100644 --- a/jest.config.js +++ b/jest.config.js @@ -14,6 +14,5 @@ module.exports = createConfig('jest', { '^CourseAuthoring/(.*)$': '/src/$1', }, modulePathIgnorePatterns: [ - '/src/pages-and-resources/utils.test.jsx', ], }); diff --git a/plugins/course-apps/learning_assistant/Settings.test.jsx b/plugins/course-apps/learning_assistant/Settings.test.jsx index a9bfa5820..a98ae8b6f 100644 --- a/plugins/course-apps/learning_assistant/Settings.test.jsx +++ b/plugins/course-apps/learning_assistant/Settings.test.jsx @@ -2,16 +2,12 @@ import React from 'react'; import { screen, waitFor } from '@testing-library/react'; import { RequestStatus } from 'CourseAuthoring/data/constants'; -import { render } from 'CourseAuthoring/pages-and-resources/utils.test'; +import { initializeMocks, render } from 'CourseAuthoring/testUtils'; import LearningAssistantSettings from './Settings'; const onClose = () => { }; describe('Learning Assistant Settings', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - it('renders', async () => { const initialState = { models: { @@ -38,14 +34,8 @@ describe('Learning Assistant Settings', () => { }, }; - render( - , - { - preloadedState: initialState, - }, - ); + initializeMocks({ initialState }); + render(); const toggleDescription = 'Reinforce learning concepts by sharing text-based course content ' + 'with OpenAI (via API) to power an in-course Learning Assistant. Learners can leave feedback about the quality ' diff --git a/src/CourseAuthoringPage.test.jsx b/src/CourseAuthoringPage.test.jsx index 3212b1761..6122056c2 100644 --- a/src/CourseAuthoringPage.test.jsx +++ b/src/CourseAuthoringPage.test.jsx @@ -4,7 +4,7 @@ import CourseAuthoringPage from './CourseAuthoringPage'; import PagesAndResources from './pages-and-resources/PagesAndResources'; import { executeThunk } from './utils'; import { fetchCourseApps } from './pages-and-resources/data/thunks'; -import { fetchCourseDetail, fetchWaffleFlags } from './data/thunks'; +import { fetchCourseDetail } from './data/thunks'; import { getApiWaffleFlagsUrl } from './data/api'; import { initializeMocks, render } from './testUtils'; @@ -26,7 +26,6 @@ beforeEach(async () => { axiosMock .onGet(getApiWaffleFlagsUrl(courseId)) .reply(200, {}); - await executeThunk(fetchWaffleFlags(courseId), store.dispatch); }); describe('Editor Pages Load no header', () => { diff --git a/src/CourseAuthoringRoutes.test.jsx b/src/CourseAuthoringRoutes.test.jsx index e2233559f..472862e80 100644 --- a/src/CourseAuthoringRoutes.test.jsx +++ b/src/CourseAuthoringRoutes.test.jsx @@ -1,7 +1,5 @@ import CourseAuthoringRoutes from './CourseAuthoringRoutes'; -import { executeThunk } from './utils'; import { getApiWaffleFlagsUrl } from './data/api'; -import { fetchWaffleFlags } from './data/thunks'; import { screen, initializeMocks, render, waitFor, } from './testUtils'; @@ -11,7 +9,6 @@ const pagesAndResourcesMockText = 'Pages And Resources'; const editorContainerMockText = 'Editor Container'; const videoSelectorContainerMockText = 'Video Selector Container'; const customPagesMockText = 'Custom Pages'; -let store; const mockComponentFn = jest.fn(); jest.mock('react-router-dom', () => ({ @@ -51,12 +48,10 @@ jest.mock('./custom-pages/CustomPages', () => (props) => { describe('', () => { beforeEach(async () => { - const { axiosMock, reduxStore } = initializeMocks(); - store = reduxStore; + const { axiosMock } = initializeMocks(); axiosMock .onGet(getApiWaffleFlagsUrl(courseId)) .reply(200, {}); - await executeThunk(fetchWaffleFlags(courseId), store.dispatch); }); it('renders the PagesAndResources component when the pages and resources route is active', async () => { diff --git a/src/accessibility-page/AccessibilityPage.test.jsx b/src/accessibility-page/AccessibilityPage.test.jsx index f686daf4d..185b5ce5e 100644 --- a/src/accessibility-page/AccessibilityPage.test.jsx +++ b/src/accessibility-page/AccessibilityPage.test.jsx @@ -1,42 +1,13 @@ -import { - render, - screen, -} from '@testing-library/react'; -import { AppProvider } from '@edx/frontend-platform/react'; -import { IntlProvider } from '@edx/frontend-platform/i18n'; -import { initializeMockApp } from '@edx/frontend-platform'; -import initializeStore from '../store'; +// @ts-check +import { initializeMocks, render, screen } from '../testUtils'; import AccessibilityPage from './index'; -const initialState = { - accessibilityPage: { - status: {}, - }, -}; -let store; - -const renderComponent = () => { - render( - - - - - , - ); -}; +const renderComponent = () => render(); describe('', () => { describe('renders', () => { beforeEach(async () => { - initializeMockApp({ - authenticatedUser: { - userId: 3, - username: 'abc123', - administrator: false, - roles: [], - }, - }); - store = initializeStore(initialState); + initializeMocks(); }); it('contains the policy body', () => { renderComponent(); diff --git a/src/advanced-settings/AdvancedSettings.test.jsx b/src/advanced-settings/AdvancedSettings.test.jsx index cc5144c64..cc76c8e8f 100644 --- a/src/advanced-settings/AdvancedSettings.test.jsx +++ b/src/advanced-settings/AdvancedSettings.test.jsx @@ -1,12 +1,9 @@ -import React from 'react'; -import { initializeMockApp } from '@edx/frontend-platform'; -import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; -import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n'; -import { AppProvider } from '@edx/frontend-platform/react'; -import { render, fireEvent, waitFor } from '@testing-library/react'; -import MockAdapter from 'axios-mock-adapter'; - -import initializeStore from '../store'; +import { + render as baseRender, + fireEvent, + initializeMocks, + waitFor, +} from '../testUtils'; import { executeThunk } from '../utils'; import { advancedSettingsMock } from './__mocks__'; import { getCourseAdvancedSettingsApiUrl } from './data/api'; @@ -28,39 +25,22 @@ jest.mock('react-textarea-autosize', () => jest.fn((props) => ( /> ))); -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useLocation: () => ({ - pathname: mockPathname, - }), -})); - -const RootWrapper = () => ( - - - - - +const render = () => baseRender( + , + { path: mockPathname }, ); describe('', () => { beforeEach(() => { - initializeMockApp({ - authenticatedUser: { - userId: 3, - username: 'abc123', - administrator: true, - roles: [], - }, - }); - store = initializeStore(); - axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + const mocks = initializeMocks(); + store = mocks.reduxStore; + axiosMock = mocks.axiosMock; axiosMock .onGet(`${getCourseAdvancedSettingsApiUrl(courseId)}?fetch_all=0`) .reply(200, advancedSettingsMock); }); it('should render without errors', async () => { - const { getByText } = render(); + const { getByText } = render(); await waitFor(() => { expect(getByText(messages.headingSubtitle.defaultMessage)).toBeInTheDocument(); const advancedSettingsElement = getByText(messages.headingTitle.defaultMessage, { @@ -72,7 +52,7 @@ describe('', () => { }); }); it('should render setting element', async () => { - const { getByText, queryByText } = render(); + const { getByText, queryByText } = render(); await waitFor(() => { const advancedModuleListTitle = getByText(/Advanced Module List/i); expect(advancedModuleListTitle).toBeInTheDocument(); @@ -80,7 +60,7 @@ describe('', () => { }); }); it('should change to onДhange', async () => { - const { getByLabelText } = render(); + const { getByLabelText } = render(); await waitFor(() => { const textarea = getByLabelText(/Advanced Module List/i); expect(textarea).toBeInTheDocument(); @@ -89,7 +69,7 @@ describe('', () => { }); }); it('should display a warning alert', async () => { - const { getByLabelText, getByText } = render(); + const { getByLabelText, getByText } = render(); await waitFor(() => { const textarea = getByLabelText(/Advanced Module List/i); fireEvent.change(textarea, { target: { value: '[3, 2, 1]' } }); @@ -100,7 +80,7 @@ describe('', () => { }); }); it('should display a tooltip on clicking on the icon', async () => { - const { getByLabelText, getByText } = render(); + const { getByLabelText, getByText } = render(); await waitFor(() => { const button = getByLabelText(/Show help text/i); fireEvent.click(button); @@ -108,7 +88,7 @@ describe('', () => { }); }); it('should change deprecated button text ', async () => { - const { getByText } = render(); + const { getByText } = render(); await waitFor(() => { const showDeprecatedItemsBtn = getByText(/Show Deprecated Settings/i); expect(showDeprecatedItemsBtn).toBeInTheDocument(); @@ -118,7 +98,7 @@ describe('', () => { expect(getByText('Certificate web/html view enabled')).toBeInTheDocument(); }); it('should reset to default value on click on Cancel button', async () => { - const { getByLabelText, getByText } = render(); + const { getByLabelText, getByText } = render(); let textarea; await waitFor(() => { textarea = getByLabelText(/Advanced Module List/i); @@ -129,7 +109,7 @@ describe('', () => { expect(textarea.value).toBe('[]'); }); it('should update the textarea value and display the updated value after clicking "Change manually"', async () => { - const { getByLabelText, getByText } = render(); + const { getByLabelText, getByText } = render(); let textarea; await waitFor(() => { textarea = getByLabelText(/Advanced Module List/i); @@ -141,7 +121,7 @@ describe('', () => { expect(textarea.value).toBe('[3, 2, 1,'); }); it('should show success alert after save', async () => { - const { getByLabelText, getByText } = render(); + const { getByLabelText, getByText } = render(); let textarea; await waitFor(() => { textarea = getByLabelText(/Advanced Module List/i); diff --git a/src/certificates/Certificates.test.jsx b/src/certificates/Certificates.test.jsx index e04d50a21..78fd1791a 100644 --- a/src/certificates/Certificates.test.jsx +++ b/src/certificates/Certificates.test.jsx @@ -1,14 +1,9 @@ -import { render, waitFor } from '@testing-library/react'; +// @ts-check import userEvent from '@testing-library/user-event'; -import { IntlProvider } from '@edx/frontend-platform/i18n'; -import { initializeMockApp } from '@edx/frontend-platform'; -import { AppProvider } from '@edx/frontend-platform/react'; -import MockAdapter from 'axios-mock-adapter'; -import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; +import { initializeMocks, render, waitFor } from '../testUtils'; import { RequestStatus } from '../data/constants'; import { executeThunk } from '../utils'; -import initializeStore from '../store'; import { getCertificatesApiUrl } from './data/api'; import { fetchCertificates } from './data/thunks'; import { certificatesDataMock } from './__mocks__'; @@ -19,26 +14,13 @@ let axiosMock; let store; const courseId = 'course-123'; -const renderComponent = (props) => render( - - - - - , -); +const renderComponent = (props) => render(); describe('Certificates', () => { beforeEach(async () => { - initializeMockApp({ - authenticatedUser: { - userId: 3, - username: 'abc123', - administrator: true, - roles: [], - }, - }); - store = initializeStore(); - axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + const mocks = initializeMocks(); + store = mocks.reduxStore; + axiosMock = mocks.axiosMock; }); it('renders WithoutModes when there are certificates but no certificate modes', async () => { diff --git a/src/certificates/layout/certificates-sidebar/CertificatesSidebar.test.jsx b/src/certificates/layout/certificates-sidebar/CertificatesSidebar.test.jsx index eb00e888d..3dd87ad7e 100644 --- a/src/certificates/layout/certificates-sidebar/CertificatesSidebar.test.jsx +++ b/src/certificates/layout/certificates-sidebar/CertificatesSidebar.test.jsx @@ -1,14 +1,9 @@ -import { render, waitFor } from '@testing-library/react'; -import { IntlProvider } from '@edx/frontend-platform/i18n'; -import { initializeMockApp } from '@edx/frontend-platform'; -import { AppProvider } from '@edx/frontend-platform/react'; - -import initializeStore from '../../../store'; +// @ts-check import CertificatesSidebar from './CertificatesSidebar'; import messages from './messages'; +import { initializeMocks, render, waitFor } from '../../../testUtils'; const courseId = 'course-123'; -let store; jest.mock('@edx/frontend-platform/i18n', () => ({ ...jest.requireActual('@edx/frontend-platform/i18n'), @@ -17,25 +12,11 @@ jest.mock('@edx/frontend-platform/i18n', () => ({ }), })); -const renderComponent = (props) => render( - - - - - , -); +const renderComponent = (props) => render(); describe('CertificatesSidebar', () => { beforeEach(() => { - initializeMockApp({ - authenticatedUser: { - userId: 3, - username: 'abc123', - administrator: true, - roles: [], - }, - }); - store = initializeStore(); + initializeMocks(); }); it('renders correctly', async () => { diff --git a/src/course-checklist/ChecklistSection/ChecklistSection.test.jsx b/src/course-checklist/ChecklistSection/ChecklistSection.test.jsx index 09aa81d33..2fc73e66d 100644 --- a/src/course-checklist/ChecklistSection/ChecklistSection.test.jsx +++ b/src/course-checklist/ChecklistSection/ChecklistSection.test.jsx @@ -4,9 +4,7 @@ import { initializeMocks, render, screen, within, } from '../../testUtils'; import { getApiWaffleFlagsUrl } from '../../data/api'; -import { fetchWaffleFlags } from '../../data/thunks'; import { generateCourseLaunchData } from '../factories/mockApiResponses'; -import { executeThunk } from '../../utils'; import { checklistItems } from './utils/courseChecklistData'; import messages from './messages'; @@ -34,7 +32,7 @@ const renderComponent = (props) => { describe('ChecklistSection', () => { beforeEach(async () => { - const { axiosMock, reduxStore } = initializeMocks(); + const { axiosMock } = initializeMocks(); axiosMock .onGet(getApiWaffleFlagsUrl(courseId)) .reply(200, { @@ -43,7 +41,6 @@ describe('ChecklistSection', () => { useNewScheduleDetailsPage: true, useNewCourseOutlinePage: true, }); - await executeThunk(fetchWaffleFlags(courseId), reduxStore.dispatch); }); it('a heading using the dataHeading prop', () => { diff --git a/src/course-outline/outline-sidebar/OutlineSidebar.test.jsx b/src/course-outline/outline-sidebar/OutlineSidebar.test.jsx index 0df0095bb..d9af4a979 100644 --- a/src/course-outline/outline-sidebar/OutlineSidebar.test.jsx +++ b/src/course-outline/outline-sidebar/OutlineSidebar.test.jsx @@ -1,29 +1,10 @@ -import React from 'react'; -import { render, waitFor } from '@testing-library/react'; -import MockAdapter from 'axios-mock-adapter'; -import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; -import { IntlProvider } from '@edx/frontend-platform/i18n'; -import { initializeMockApp } from '@edx/frontend-platform'; -import { AppProvider } from '@edx/frontend-platform/react'; - +// @ts-check +import { initializeMocks, render, waitFor } from '../../testUtils'; import { helpUrls } from '../../help-urls/__mocks__'; import { getHelpUrlsApiUrl } from '../../help-urls/data/api'; -import initializeStore from '../../store'; import OutlineSidebar from './OutlineSidebar'; import messages from './messages'; -let axiosMock; -let store; -const mockPathname = '/foo-bar'; -const courseId = '123'; - -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useLocation: () => ({ - pathname: mockPathname, - }), -})); - jest.mock('@edx/frontend-platform/i18n', () => ({ ...jest.requireActual('@edx/frontend-platform/i18n'), useIntl: () => ({ @@ -31,26 +12,16 @@ jest.mock('@edx/frontend-platform/i18n', () => ({ }), })); -const renderComponent = (props) => render( - - - - - , -); +let axiosMock; +const mockPathname = '/foo-bar'; +const courseId = '123'; + +const renderComponent = (props) => render(, { path: mockPathname }); describe('', () => { beforeEach(() => { - initializeMockApp({ - authenticatedUser: { - userId: 3, - username: 'abc123', - administrator: true, - roles: [], - }, - }); - store = initializeStore(); - axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + const mocks = initializeMocks(); + axiosMock = mocks.axiosMock; axiosMock .onGet(getHelpUrlsApiUrl()) .reply(200, helpUrls); diff --git a/src/course-rerun/CourseRerun.test.jsx b/src/course-rerun/CourseRerun.test.jsx index bb02822b7..6f5cc97d4 100644 --- a/src/course-rerun/CourseRerun.test.jsx +++ b/src/course-rerun/CourseRerun.test.jsx @@ -1,72 +1,37 @@ -import React from 'react'; import { useSelector } from 'react-redux'; -import { MemoryRouter } from 'react-router-dom'; -import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; -import { initializeMockApp } from '@edx/frontend-platform'; -import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n'; -import { AppProvider } from '@edx/frontend-platform/react'; -import { - fireEvent, render, waitFor, -} from '@testing-library/react'; -import MockAdapter from 'axios-mock-adapter'; -import initializeStore from '../store'; +import { + initializeMocks, + fireEvent, + render, + waitFor, +} from '../testUtils'; import { studioHomeMock } from '../studio-home/__mocks__'; import { getStudioHomeApiUrl } from '../studio-home/data/api'; import { RequestStatus } from '../data/constants'; import messages from './messages'; import CourseRerun from '.'; -let axiosMock; -let store; -const mockPathname = '/foo-bar'; - jest.mock('react-redux', () => ({ ...jest.requireActual('react-redux'), useSelector: jest.fn(), })); -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useLocation: () => ({ - pathname: mockPathname, - }), -})); - -const RootWrapper = () => ( - - - - - - - -); - describe('', () => { beforeEach(() => { - initializeMockApp({ - authenticatedUser: { - userId: 3, - username: 'abc123', - administrator: true, - roles: [], - }, - }); - store = initializeStore(); - axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + const { axiosMock } = initializeMocks(); axiosMock.onGet(getStudioHomeApiUrl()).reply(200, studioHomeMock); useSelector.mockReturnValue(studioHomeMock); }); it('should render successfully', () => { - const { getByText, getAllByRole } = render(); + const { getByText, getAllByRole } = render(); expect(getByText(messages.rerunTitle.defaultMessage)); expect(getAllByRole('button', { name: messages.cancelButton.defaultMessage }).length).toBe(2); }); it('should navigate to /home on cancel button click', () => { - const { getAllByRole } = render(); + const { getAllByRole } = render(); const cancelButton = getAllByRole('button', { name: messages.cancelButton.defaultMessage })[0]; fireEvent.click(cancelButton); @@ -78,13 +43,13 @@ describe('', () => { it('shows the spinner before the query is complete', async () => { useSelector.mockReturnValue({ organizationLoadingStatus: RequestStatus.IN_PROGRESS }); - const { findByRole } = render(); + const { findByRole } = render(); const spinner = await findByRole('status'); expect(spinner.textContent).toEqual('Loading...'); }); it('should show footer', async () => { - const { findByText } = render(); + const { findByText } = render(); await findByText('Looking for help with Studio?'); const lmsElement = await findByText('LMS'); expect(lmsElement).toHaveAttribute('href', process.env.LMS_BASE_URL); diff --git a/src/course-rerun/course-rerun-sidebar/CourseRerunSidebar.test.jsx b/src/course-rerun/course-rerun-sidebar/CourseRerunSidebar.test.jsx index d497d873f..ad97a4638 100644 --- a/src/course-rerun/course-rerun-sidebar/CourseRerunSidebar.test.jsx +++ b/src/course-rerun/course-rerun-sidebar/CourseRerunSidebar.test.jsx @@ -1,43 +1,15 @@ -import React from 'react'; -import { render } from '@testing-library/react'; -import { IntlProvider } from '@edx/frontend-platform/i18n'; -import { initializeMockApp } from '@edx/frontend-platform'; -import { AppProvider } from '@edx/frontend-platform/react'; - -import initializeStore from '../../store'; +// @ts-check +import { initializeMocks, render } from '../../testUtils'; import CourseRerunSideBar from '.'; import messages from './messages'; -let store; -const mockPathname = '/foo-bar'; const courseId = '123'; -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useLocation: () => ({ - pathname: mockPathname, - }), -})); - -const renderComponent = (props) => render( - - - - - , -); +const renderComponent = (props) => render(); describe('', () => { beforeEach(() => { - initializeMockApp({ - authenticatedUser: { - userId: 3, - username: 'abc123', - administrator: true, - roles: [], - }, - }); - store = initializeStore(); + initializeMocks(); }); it('render CourseRerunSideBar successfully', () => { diff --git a/src/course-team/CourseTeam.jsx b/src/course-team/CourseTeam.jsx index 29ad5b80f..20b594af2 100644 --- a/src/course-team/CourseTeam.jsx +++ b/src/course-team/CourseTeam.jsx @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { useIntl, injectIntl } from '@edx/frontend-platform/i18n'; +import { useIntl } from '@edx/frontend-platform/i18n'; import { Button, Container, @@ -175,4 +175,4 @@ CourseTeam.propTypes = { courseId: PropTypes.string.isRequired, }; -export default injectIntl(CourseTeam); +export default CourseTeam; diff --git a/src/course-team/CourseTeam.test.jsx b/src/course-team/CourseTeam.test.jsx index 046e8ef95..71239a198 100644 --- a/src/course-team/CourseTeam.test.jsx +++ b/src/course-team/CourseTeam.test.jsx @@ -1,16 +1,4 @@ -import { - render, - fireEvent, - cleanup, - waitFor, -} from '@testing-library/react'; -import { IntlProvider } from '@edx/frontend-platform/i18n'; -import { AppProvider } from '@edx/frontend-platform/react'; -import { initializeMockApp } from '@edx/frontend-platform'; -import MockAdapter from 'axios-mock-adapter'; -import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; - -import initializeStore from '../store'; +// @ts-check import { courseTeamMock, courseTeamWithOneUser, courseTeamWithoutUsers } from './__mocks__'; import { getCourseTeamApiUrl, updateCourseTeamUserApiUrl } from './data/api'; import CourseTeam from './CourseTeam'; @@ -19,40 +7,25 @@ import { USER_ROLES } from '../constants'; import { executeThunk } from '../utils'; import { RequestStatus } from '../data/constants'; import { changeRoleTeamUserQuery, deleteCourseTeamQuery } from './data/thunk'; +import { + fireEvent, + initializeMocks, + render as baseRender, + waitFor, +} from '../testUtils'; let axiosMock; let store; const mockPathname = '/foo-bar'; const courseId = '123'; -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useLocation: () => ({ - pathname: mockPathname, - }), -})); - -const RootWrapper = () => ( - - - - - -); +const render = () => baseRender(, { path: mockPathname }); describe('', () => { beforeEach(() => { - initializeMockApp({ - authenticatedUser: { - userId: 3, - username: 'abc123', - administrator: true, - roles: [], - }, - }); - - store = initializeStore(); - axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + const mocks = initializeMocks(); + store = mocks.reduxStore; + axiosMock = mocks.axiosMock; }); it('render CourseTeam component with 3 team members correctly', async () => { @@ -62,7 +35,7 @@ describe('', () => { const { getByText, getByRole, getByTestId, queryAllByTestId, - } = render(); + } = render(); await waitFor(() => { expect(getByText(messages.headingTitle.defaultMessage)).toBeInTheDocument(); @@ -74,14 +47,13 @@ describe('', () => { }); it('render CourseTeam component with 1 team member correctly', async () => { - cleanup(); axiosMock .onGet(getCourseTeamApiUrl(courseId)) .reply(200, courseTeamWithOneUser); const { getByText, getByRole, getByTestId, getAllByTestId, - } = render(); + } = render(); await waitFor(() => { expect(getByText(messages.headingTitle.defaultMessage)).toBeInTheDocument(); @@ -93,14 +65,13 @@ describe('', () => { }); it('render CourseTeam component without team member correctly', async () => { - cleanup(); axiosMock .onGet(getCourseTeamApiUrl(courseId)) .reply(200, courseTeamWithoutUsers); const { getByText, getByRole, getByTestId, queryAllByTestId, - } = render(); + } = render(); await waitFor(() => { expect(getByText(messages.headingTitle.defaultMessage)).toBeInTheDocument(); @@ -112,12 +83,11 @@ describe('', () => { }); it('render CourseTeam component with initial sidebar correctly', async () => { - cleanup(); axiosMock .onGet(getCourseTeamApiUrl(courseId)) .reply(200, courseTeamWithoutUsers); - const { getByTestId, queryByTestId } = render(); + const { getByTestId, queryByTestId } = render(); await waitFor(() => { expect(getByTestId('course-team-sidebar__initial')).toBeInTheDocument(); @@ -126,12 +96,11 @@ describe('', () => { }); it('render CourseTeam component without initial sidebar correctly', async () => { - cleanup(); axiosMock .onGet(getCourseTeamApiUrl(courseId)) .reply(200, courseTeamMock); - const { getByTestId, queryByTestId } = render(); + const { getByTestId, queryByTestId } = render(); await waitFor(() => { expect(queryByTestId('course-team-sidebar__initial')).not.toBeInTheDocument(); @@ -140,12 +109,11 @@ describe('', () => { }); it('displays AddUserForm when clicking the "Add New Member" button', async () => { - cleanup(); axiosMock .onGet(getCourseTeamApiUrl(courseId)) .reply(200, courseTeamWithOneUser); - const { getByRole, queryByTestId } = render(); + const { getByRole, queryByTestId } = render(); await waitFor(() => { expect(queryByTestId('add-user-form')).not.toBeInTheDocument(); @@ -156,12 +124,11 @@ describe('', () => { }); it('displays AddUserForm when clicking the "Add a New Team member" button', async () => { - cleanup(); axiosMock .onGet(getCourseTeamApiUrl(courseId)) .reply(200, courseTeamWithOneUser); - const { getByRole, queryByTestId } = render(); + const { getByRole, queryByTestId } = render(); await waitFor(() => { expect(queryByTestId('add-user-form')).not.toBeInTheDocument(); @@ -172,7 +139,6 @@ describe('', () => { }); it('not displays "Add New Member" and AddTeamMember component when isAllowActions is false', async () => { - cleanup(); axiosMock .onGet(getCourseTeamApiUrl(courseId)) .reply(200, { @@ -180,7 +146,7 @@ describe('', () => { allowActions: false, }); - const { queryByRole, queryByTestId } = render(); + const { queryByRole, queryByTestId } = render(); await waitFor(() => { expect(queryByRole('button', { name: messages.addNewMemberButton.defaultMessage })).not.toBeInTheDocument(); @@ -189,12 +155,11 @@ describe('', () => { }); it('should delete user', async () => { - cleanup(); axiosMock .onGet(getCourseTeamApiUrl(courseId)) .reply(200, courseTeamMock); - const { queryByText } = render(); + const { queryByText } = render(); axiosMock .onDelete(updateCourseTeamUserApiUrl(courseId, 'staff@example.com')) @@ -205,15 +170,14 @@ describe('', () => { }); it('should change role user', async () => { - cleanup(); axiosMock .onGet(getCourseTeamApiUrl(courseId)) .reply(200, courseTeamMock); - const { getAllByText } = render(); + const { getAllByText } = render(); axiosMock - .onPut(updateCourseTeamUserApiUrl(courseId, 'staff@example.com', { role: USER_ROLES.admin })) + .onPut(updateCourseTeamUserApiUrl(courseId, 'staff@example.com')) .reply(200, { role: USER_ROLES.admin }); await executeThunk(changeRoleTeamUserQuery(courseId, 'staff@example.com', { role: USER_ROLES.admin }), store.dispatch); @@ -225,7 +189,7 @@ describe('', () => { .onGet(getCourseTeamApiUrl(courseId)) .reply(403); - const { getByRole } = render(); + const { getByRole } = render(); await waitFor(() => { expect(getByRole('alert')).toBeInTheDocument(); @@ -239,7 +203,7 @@ describe('', () => { .onGet(getCourseTeamApiUrl(courseId)) .reply(404); - render(); + render(); await waitFor(() => { const { loadingCourseTeamStatus } = store.getState().courseTeam; diff --git a/src/course-team/course-team-sidebar/CourseTeamSideBar.test.jsx b/src/course-team/course-team-sidebar/CourseTeamSideBar.test.jsx index 7218ba40b..daab5fb7e 100644 --- a/src/course-team/course-team-sidebar/CourseTeamSideBar.test.jsx +++ b/src/course-team/course-team-sidebar/CourseTeamSideBar.test.jsx @@ -1,35 +1,15 @@ -import React from 'react'; -import { render } from '@testing-library/react'; -import { IntlProvider } from '@edx/frontend-platform/i18n'; -import { initializeMockApp } from '@edx/frontend-platform'; -import { AppProvider } from '@edx/frontend-platform/react'; - +// @ts-check +import { initializeMocks, render } from '../../testUtils'; import CourseTeamSidebar from './CourseTeamSidebar'; import messages from './messages'; -import initializeStore from '../../store'; const courseId = 'course-123'; -let store; -const renderComponent = (props) => render( - - - - - , -); +const renderComponent = (props) => render(); describe('', () => { beforeEach(() => { - initializeMockApp({ - authenticatedUser: { - userId: 3, - username: 'abc123', - administrator: true, - roles: [], - }, - }); - store = initializeStore(); + initializeMocks(); }); it('render CourseTeamSidebar component correctly', () => { diff --git a/src/course-unit/add-component/AddComponent.test.jsx b/src/course-unit/add-component/AddComponent.test.jsx index d3c31ec7b..cab41c0a9 100644 --- a/src/course-unit/add-component/AddComponent.test.jsx +++ b/src/course-unit/add-component/AddComponent.test.jsx @@ -1,16 +1,14 @@ /* eslint-disable react/prop-types */ -import MockAdapter from 'axios-mock-adapter'; -import { - act, render, screen, waitFor, within, -} from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { initializeMockApp } from '@edx/frontend-platform'; -import { AppProvider } from '@edx/frontend-platform/react'; -import { IntlProvider } from '@edx/frontend-platform/i18n'; -import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; - -import initializeStore from '../../store'; +import { + act, + render, + screen, + waitFor, + within, + initializeMocks, +} from '../../testUtils'; import { executeThunk } from '../../utils'; import { fetchCourseSectionVerticalData } from '../data/thunk'; import { getCourseSectionVerticalApiUrl } from '../data/api'; @@ -59,35 +57,23 @@ jest.mock('../../generic/hooks/context/hooks', () => ({ })); const renderComponent = (props) => render( - - - - - - - , + + + , ); describe('', () => { beforeEach(async () => { - initializeMockApp({ - authenticatedUser: { - userId: 3, - username: 'abc123', - administrator: true, - roles: [], - }, - }); - - store = initializeStore(); - axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + const mocks = initializeMocks(); + axiosMock = mocks.axiosMock; + store = mocks.reduxStore; axiosMock .onGet(getCourseSectionVerticalApiUrl(blockId)) .reply(200, courseSectionVerticalMock); diff --git a/src/custom-pages/CustomPages.test.jsx b/src/custom-pages/CustomPages.test.jsx index de57e25b8..a5f2c4407 100644 --- a/src/custom-pages/CustomPages.test.jsx +++ b/src/custom-pages/CustomPages.test.jsx @@ -10,7 +10,6 @@ import { import { executeThunk } from '../utils'; import { RequestStatus } from '../data/constants'; import { getApiWaffleFlagsUrl } from '../data/api'; -import { fetchWaffleFlags } from '../data/thunks'; import CustomPages from './CustomPages'; import { generateFetchPageApiResponse, @@ -62,7 +61,6 @@ describe('CustomPages', () => { useNewScheduleDetailsPage: true, useNewCourseOutlinePage: true, }); - await executeThunk(fetchWaffleFlags(courseId), store.dispatch); }); it('should ', async () => { renderComponent(); diff --git a/src/export-page/CourseExportPage.test.jsx b/src/export-page/CourseExportPage.test.jsx index 4c0a01d5e..466a5434f 100644 --- a/src/export-page/CourseExportPage.test.jsx +++ b/src/export-page/CourseExportPage.test.jsx @@ -1,13 +1,13 @@ -import { getConfig, initializeMockApp } from '@edx/frontend-platform'; -import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; -import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n'; -import { AppProvider } from '@edx/frontend-platform/react'; -import { fireEvent, render, waitFor } from '@testing-library/react'; -import MockAdapter from 'axios-mock-adapter'; +import { getConfig } from '@edx/frontend-platform'; import { Helmet } from 'react-helmet'; import Cookies from 'universal-cookie'; -import initializeStore from '../store'; +import { + initializeMocks, + fireEvent, + render, + waitFor, +} from '../testUtils'; import { RequestStatus } from '../data/constants'; import stepperMessages from './export-stepper/messages'; import modalErrorMessages from './export-modal-error/messages'; @@ -37,26 +37,13 @@ jest.mock('universal-cookie', () => { return jest.fn(() => mCookie); }); -const RootWrapper = () => ( - - - - - -); +const renderComponent = () => render(); describe('', () => { beforeEach(() => { - initializeMockApp({ - authenticatedUser: { - userId: 3, - username: 'abc123', - administrator: true, - roles: [], - }, - }); - store = initializeStore(); - axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + const mocks = initializeMocks(); + store = mocks.reduxStore; + axiosMock = mocks.axiosMock; axiosMock .onGet(postExportCourseApiUrl(courseId)) .reply(200, exportPageMock); @@ -64,7 +51,7 @@ describe('', () => { cookies.get.mockReturnValue(null); }); it('should render page title correctly', async () => { - render(); + renderComponent(); await waitFor(() => { const helmet = Helmet.peek(); expect(helmet.title).toEqual( @@ -73,7 +60,7 @@ describe('', () => { }); }); it('should render without errors', async () => { - const { getByText } = render(); + const { getByText } = renderComponent(); await waitFor(() => { expect(getByText(messages.headingSubtitle.defaultMessage)).toBeInTheDocument(); const exportPageElement = getByText(messages.headingTitle.defaultMessage, { @@ -86,7 +73,7 @@ describe('', () => { }); }); it('should start exporting on click', async () => { - const { getByText, container } = render(); + const { getByText, container } = renderComponent(); const button = container.querySelector('.btn-primary'); fireEvent.click(button); expect(getByText(stepperMessages.stepperPreparingDescription.defaultMessage)).toBeInTheDocument(); @@ -95,7 +82,7 @@ describe('', () => { axiosMock .onGet(getExportStatusApiUrl(courseId)) .reply(200, { exportStatus: EXPORT_STAGES.EXPORTING, exportError: { rawErrorMsg: 'test error', editUnitUrl: 'http://test-url.test' } }); - const { getByText, queryByText, container } = render(); + const { getByText, queryByText, container } = renderComponent(); const startExportButton = container.querySelector('.btn-primary'); fireEvent.click(startExportButton); // eslint-disable-next-line no-promise-executor-return @@ -108,45 +95,45 @@ describe('', () => { }); it('should fetch status without clicking when cookies has', async () => { cookies.get.mockReturnValue({ date: 1679787000 }); - const { getByText } = render(); + const { getByText } = renderComponent(); expect(getByText(stepperMessages.stepperPreparingDescription.defaultMessage)).toBeInTheDocument(); }); it('should show download path for relative path', async () => { axiosMock .onGet(getExportStatusApiUrl(courseId)) .reply(200, { exportStatus: EXPORT_STAGES.SUCCESS, exportOutput: '/test-download-path.test' }); - const { getByText, container } = render(); + const { getByText, container } = renderComponent(); const startExportButton = container.querySelector('.btn-primary'); fireEvent.click(startExportButton); - // eslint-disable-next-line no-promise-executor-return - await new Promise((r) => setTimeout(r, 3500)); - const downloadButton = getByText(stepperMessages.downloadCourseButtonTitle.defaultMessage); - expect(downloadButton).toBeInTheDocument(); - expect(downloadButton.getAttribute('href')).toEqual(`${getConfig().STUDIO_BASE_URL}/test-download-path.test`); + await waitFor(() => { + const downloadButton = getByText(stepperMessages.downloadCourseButtonTitle.defaultMessage); + expect(downloadButton).toBeInTheDocument(); + expect(downloadButton.getAttribute('href')).toEqual(`${getConfig().STUDIO_BASE_URL}/test-download-path.test`); + }, { timeout: 4_000 }); }); it('should show download path for absolute path', async () => { axiosMock .onGet(getExportStatusApiUrl(courseId)) .reply(200, { exportStatus: EXPORT_STAGES.SUCCESS, exportOutput: 'http://test-download-path.test' }); - const { getByText, container } = render(); + const { getByText, container } = renderComponent(); const startExportButton = container.querySelector('.btn-primary'); fireEvent.click(startExportButton); - // eslint-disable-next-line no-promise-executor-return - await new Promise((r) => setTimeout(r, 3500)); - const downloadButton = getByText(stepperMessages.downloadCourseButtonTitle.defaultMessage); - expect(downloadButton).toBeInTheDocument(); - expect(downloadButton.getAttribute('href')).toEqual('http://test-download-path.test'); + await waitFor(() => { + const downloadButton = getByText(stepperMessages.downloadCourseButtonTitle.defaultMessage); + expect(downloadButton).toBeInTheDocument(); + expect(downloadButton.getAttribute('href')).toEqual('http://test-download-path.test'); + }, { timeout: 4_000 }); }); it('displays an alert and sets status to DENIED when API responds with 403', async () => { axiosMock .onGet(getExportStatusApiUrl(courseId)) .reply(403); - const { getByRole, container } = render(); + const { getByRole, container } = renderComponent(); const startExportButton = container.querySelector('.btn-primary'); fireEvent.click(startExportButton); - // eslint-disable-next-line no-promise-executor-return - await new Promise((r) => setTimeout(r, 3500)); - expect(getByRole('alert')).toBeInTheDocument(); + await waitFor(() => { + expect(getByRole('alert')).toBeInTheDocument(); + }, { timeout: 4_000 }); const { loadingStatus } = store.getState().courseExport; expect(loadingStatus).toEqual(RequestStatus.DENIED); }); @@ -155,12 +142,12 @@ describe('', () => { axiosMock .onGet(getExportStatusApiUrl(courseId)) .reply(404); - const { container } = render(); + const { container } = renderComponent(); const startExportButton = container.querySelector('.btn-primary'); fireEvent.click(startExportButton); - // eslint-disable-next-line no-promise-executor-return - await new Promise((r) => setTimeout(r, 3500)); - const { loadingStatus } = store.getState().courseExport; - expect(loadingStatus).toEqual(RequestStatus.FAILED); + await waitFor(() => { + const { loadingStatus } = store.getState().courseExport; + expect(loadingStatus).toEqual(RequestStatus.FAILED); + }, { timeout: 4_000 }); }); }); diff --git a/src/export-page/export-sidebar/ExportSidebar.test.jsx b/src/export-page/export-sidebar/ExportSidebar.test.jsx index d770f4b15..7fe777425 100644 --- a/src/export-page/export-sidebar/ExportSidebar.test.jsx +++ b/src/export-page/export-sidebar/ExportSidebar.test.jsx @@ -1,38 +1,16 @@ -import React from 'react'; -import { render } from '@testing-library/react'; -import { IntlProvider } from '@edx/frontend-platform/i18n'; -import { initializeMockApp } from '@edx/frontend-platform'; -import { AppProvider } from '@edx/frontend-platform/react'; - -import initializeStore from '../../store'; +// @ts-check +import { initializeMocks, render } from '../../testUtils'; import messages from './messages'; import ExportSidebar from './ExportSidebar'; const courseId = 'course-123'; -let store; - -const RootWrapper = () => ( - - - - - -); describe('', () => { beforeEach(() => { - initializeMockApp({ - authenticatedUser: { - userId: 3, - username: 'abc123', - administrator: true, - roles: [], - }, - }); - store = initializeStore(); + initializeMocks(); }); it('render sidebar correctly', () => { - const { getByText } = render(); + const { getByText } = render(); expect(getByText(messages.title1.defaultMessage)).toBeInTheDocument(); expect(getByText(messages.exportedContentHeading.defaultMessage)).toBeInTheDocument(); }); diff --git a/src/generic/help-sidebar/HelpSidebar.test.jsx b/src/generic/help-sidebar/HelpSidebar.test.jsx index fe8b03db2..5e530be30 100644 --- a/src/generic/help-sidebar/HelpSidebar.test.jsx +++ b/src/generic/help-sidebar/HelpSidebar.test.jsx @@ -1,32 +1,16 @@ -import React from 'react'; -import { render } from '@testing-library/react'; -import { IntlProvider } from '@edx/frontend-platform/i18n'; -import { AppProvider } from '@edx/frontend-platform/react'; -import { initializeMockApp } from '@edx/frontend-platform'; -import initializeStore from '../../store'; +// @ts-check + +import { initializeMocks, render } from '../../testUtils'; import messages from './messages'; import { HelpSidebar } from '.'; const mockPathname = '/foo-bar'; -let store; -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useLocation: () => ({ - pathname: mockPathname, - }), -})); - -const RootWrapper = (props) => ( - - - -

Test children

-
-
-
+const renderHelpSidebar = (props) => render( + +

Test children

+
, + { path: mockPathname }, ); const props = { @@ -37,25 +21,16 @@ const props = { describe('HelpSidebar', () => { beforeEach(() => { - initializeMockApp({ - authenticatedUser: { - userId: 3, - username: 'abc123', - administrator: true, - roles: [], - }, - }); - - store = initializeStore(); + initializeMocks(); }); it('renders children correctly', () => { - const { getByText } = render(); + const { getByText } = renderHelpSidebar(props); expect(getByText('Test children')).toBeTruthy(); }); it('should render all sidebar links with correct text', () => { - const { getByText, queryByText } = render(); + const { getByText, queryByText } = renderHelpSidebar(props); expect(getByText(messages.sidebarTitleOther.defaultMessage)).toBeTruthy(); expect(getByText(messages.sidebarLinkToScheduleAndDetails.defaultMessage)).toBeTruthy(); expect(getByText(messages.sidebarLinkToGrading.defaultMessage)).toBeTruthy(); @@ -67,7 +42,7 @@ describe('HelpSidebar', () => { it('should hide other settings url if showOtherSettings disabled', () => { const initialProps = { ...props, showOtherSettings: false }; - const { queryByText } = render(); + const { queryByText } = renderHelpSidebar(initialProps); expect(queryByText(messages.sidebarTitleOther.defaultMessage)).toBeFalsy(); expect(queryByText(messages.sidebarLinkToScheduleAndDetails.defaultMessage)).toBeFalsy(); expect(queryByText(messages.sidebarLinkToGrading.defaultMessage)).toBeFalsy(); @@ -78,7 +53,7 @@ describe('HelpSidebar', () => { it('should render proctored mfe url only if passed not empty value', () => { const initialProps = { ...props, showOtherSettings: true, proctoredExamSettingsUrl: 'http:/link-to' }; - const { getByText } = render(); + const { getByText } = renderHelpSidebar(initialProps); expect(getByText(messages.sidebarLinkToProctoredExamSettings.defaultMessage)).toBeTruthy(); }); }); diff --git a/src/group-configurations/GroupConfigurations.test.jsx b/src/group-configurations/GroupConfigurations.test.jsx index 0cfec7b1d..b35784ae8 100644 --- a/src/group-configurations/GroupConfigurations.test.jsx +++ b/src/group-configurations/GroupConfigurations.test.jsx @@ -1,12 +1,10 @@ -import MockAdapter from 'axios-mock-adapter'; -import { render, waitFor, within } from '@testing-library/react'; -import { IntlProvider } from '@edx/frontend-platform/i18n'; -import { AppProvider } from '@edx/frontend-platform/react'; -import { initializeMockApp } from '@edx/frontend-platform'; -import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; - +import { + initializeMocks, + render, + waitFor, + within, +} from '../testUtils'; import { RequestStatus } from '../data/constants'; -import initializeStore from '../store'; import { executeThunk } from '../utils'; import { getContentStoreApiUrl } from './data/api'; import { fetchGroupConfigurationsQuery } from './data/thunk'; @@ -22,27 +20,13 @@ const courseId = 'course-v1:org+101+101'; const enrollmentTrackGroups = groupConfigurationResponseMock.allGroupConfigurations[0]; const contentGroups = groupConfigurationResponseMock.allGroupConfigurations[1]; -const renderComponent = () => render( - - - - - , -); +const renderComponent = () => render(); describe('', () => { beforeEach(async () => { - initializeMockApp({ - authenticatedUser: { - userId: 3, - username: 'abc123', - administrator: true, - roles: [], - }, - }); - - store = initializeStore(); - axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + const mocks = initializeMocks(); + store = mocks.reduxStore; + axiosMock = mocks.axiosMock; axiosMock .onGet(getContentStoreApiUrl(courseId)) .reply(200, groupConfigurationResponseMock); diff --git a/src/group-configurations/group-configuration-sidebar/GroupConfigurationSidebar.test.jsx b/src/group-configurations/group-configuration-sidebar/GroupConfigurationSidebar.test.jsx index eb5cb9988..3ffe15103 100644 --- a/src/group-configurations/group-configuration-sidebar/GroupConfigurationSidebar.test.jsx +++ b/src/group-configurations/group-configuration-sidebar/GroupConfigurationSidebar.test.jsx @@ -1,44 +1,18 @@ -import { render } from '@testing-library/react'; -import { IntlProvider } from '@edx/frontend-platform/i18n'; -import { initializeMockApp } from '@edx/frontend-platform'; -import { AppProvider } from '@edx/frontend-platform/react'; - -import initializeStore from '../../store'; +// @ts-check +import { initializeMocks, render } from '../../testUtils'; import GroupConfigurationSidebar from '.'; import messages from './messages'; -let store; const courseId = 'course-123'; const enrollmentTrackTitle = messages.about_3_title.defaultMessage; const contentGroupTitle = messages.aboutTitle.defaultMessage; const experimentGroupTitle = messages.about_2_title.defaultMessage; -jest.mock('@edx/frontend-platform/i18n', () => ({ - ...jest.requireActual('@edx/frontend-platform/i18n'), - useIntl: () => ({ - formatMessage: (message) => message.defaultMessage, - }), -})); - -const renderComponent = (props) => render( - - - - , - , -); +const renderComponent = (props) => render(); describe('GroupConfigurationSidebar', () => { beforeEach(() => { - initializeMockApp({ - authenticatedUser: { - userId: 3, - username: 'abc123', - administrator: true, - roles: [], - }, - }); - store = initializeStore(); + initializeMocks(); }); it('renders all groups when all props are true', async () => { diff --git a/src/import-page/CourseImportPage.test.jsx b/src/import-page/CourseImportPage.test.jsx index 646eadbe6..14727728b 100644 --- a/src/import-page/CourseImportPage.test.jsx +++ b/src/import-page/CourseImportPage.test.jsx @@ -1,13 +1,7 @@ -import { initializeMockApp } from '@edx/frontend-platform'; -import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n'; -import { AppProvider } from '@edx/frontend-platform/react'; -import { render, waitFor } from '@testing-library/react'; import { Helmet } from 'react-helmet'; -import MockAdapter from 'axios-mock-adapter'; -import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; - import Cookies from 'universal-cookie'; -import initializeStore from '../store'; + +import { initializeMocks, render, waitFor } from '../testUtils'; import { RequestStatus } from '../data/constants'; import messages from './messages'; import CourseImportPage from './CourseImportPage'; @@ -35,26 +29,13 @@ jest.mock('universal-cookie', () => { return jest.fn(() => Cookie); }); -const RootWrapper = () => ( - - - - - -); +const renderComponent = () => render(); describe('', () => { beforeEach(() => { - initializeMockApp({ - authenticatedUser: { - userId: 3, - username: 'abc123', - administrator: true, - roles: [], - }, - }); - store = initializeStore(); - axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + const mocks = initializeMocks(); + store = mocks.reduxStore; + axiosMock = mocks.axiosMock; axiosMock .onGet(getImportStatusApiUrl(courseId, 'testFileName.test')) .reply(200, { importStatus: 1, message: '' }); @@ -62,7 +43,7 @@ describe('', () => { cookies.get.mockReturnValue(null); }); it('should render page title correctly', async () => { - render(); + renderComponent(); await waitFor(() => { const helmet = Helmet.peek(); expect(helmet.title).toEqual( @@ -71,7 +52,7 @@ describe('', () => { }); }); it('should render without errors', async () => { - const { getByText } = render(); + const { getByText } = renderComponent(); await waitFor(() => { expect(getByText(messages.headingSubtitle.defaultMessage)).toBeInTheDocument(); const importPageElement = getByText(messages.headingTitle.defaultMessage, { @@ -85,7 +66,7 @@ describe('', () => { }); it('should fetch status without clicking when cookies has', async () => { cookies.get.mockReturnValue({ date: 1679787000, completed: false, fileName: 'testFileName.test' }); - const { getByText } = render(); + const { getByText } = renderComponent(); expect(getByText(stepperMessages.stepperUnpackingDescription.defaultMessage)).toBeInTheDocument(); }); it('should show error', async () => { @@ -93,20 +74,20 @@ describe('', () => { .onGet(getImportStatusApiUrl(courseId, 'testFileName.tar.gz')) .reply(200, { importStatus: -IMPORT_STAGES.UPDATING, message: '' }); cookies.get.mockReturnValue({ date: 1679787000, completed: false, fileName: 'testFileName.tar.gz' }); - const { getByText } = render(); - // eslint-disable-next-line no-promise-executor-return - await new Promise((r) => setTimeout(r, 3500)); - expect(getByText(stepperMessages.defaultErrorMessage.defaultMessage)).toBeInTheDocument(); + const { getByText } = renderComponent(); + await waitFor(() => { + expect(getByText(stepperMessages.defaultErrorMessage.defaultMessage)).toBeInTheDocument(); + }, { timeout: 4000 }); }); it('should show success button', async () => { axiosMock .onGet(getImportStatusApiUrl(courseId, 'testFileName.tar.gz')) .reply(200, { importStatus: IMPORT_STAGES.SUCCESS, message: '' }); cookies.get.mockReturnValue({ date: 1679787000, completed: false, fileName: 'testFileName.tar.gz' }); - const { getByText } = render(); - // eslint-disable-next-line no-promise-executor-return - await new Promise((r) => setTimeout(r, 3500)); - expect(getByText(stepperMessages.viewOutlineButton.defaultMessage)).toBeInTheDocument(); + const { getByText } = renderComponent(); + await waitFor(() => { + expect(getByText(stepperMessages.viewOutlineButton.defaultMessage)).toBeInTheDocument(); + }, { timeout: 4000 }); }); it('displays an alert and sets status to DENIED when API responds with 403', async () => { @@ -114,10 +95,10 @@ describe('', () => { .onGet(getImportStatusApiUrl(courseId, 'testFileName.tar.gz')) .reply(403); cookies.get.mockReturnValue({ date: 1679787000, completed: false, fileName: 'testFileName.tar.gz' }); - const { getByRole } = render(); - // eslint-disable-next-line no-promise-executor-return - await new Promise((r) => setTimeout(r, 3500)); - expect(getByRole('alert')).toBeInTheDocument(); + const { getByRole } = renderComponent(); + await waitFor(() => { + expect(getByRole('alert')).toBeInTheDocument(); + }, { timeout: 4000 }); const { loadingStatus } = store.getState().courseImport; expect(loadingStatus).toEqual(RequestStatus.DENIED); }); @@ -127,10 +108,10 @@ describe('', () => { .onGet(getImportStatusApiUrl(courseId, 'testFileName.tar.gz')) .reply(404); cookies.get.mockReturnValue({ date: 1679787000, completed: false, fileName: 'testFileName.tar.gz' }); - render(); - // eslint-disable-next-line no-promise-executor-return - await new Promise((r) => setTimeout(r, 3500)); - const { loadingStatus } = store.getState().courseImport; - expect(loadingStatus).toEqual(RequestStatus.FAILED); + renderComponent(); + await waitFor(() => { + const { loadingStatus } = store.getState().courseImport; + expect(loadingStatus).toEqual(RequestStatus.FAILED); + }, { timeout: 4000 }); }); }); diff --git a/src/import-page/import-sidebar/ImportSidebar.test.jsx b/src/import-page/import-sidebar/ImportSidebar.test.jsx index 1fb3c2e28..d80357f2b 100644 --- a/src/import-page/import-sidebar/ImportSidebar.test.jsx +++ b/src/import-page/import-sidebar/ImportSidebar.test.jsx @@ -1,38 +1,16 @@ -import React from 'react'; -import { render } from '@testing-library/react'; -import { IntlProvider } from '@edx/frontend-platform/i18n'; -import { initializeMockApp } from '@edx/frontend-platform'; -import { AppProvider } from '@edx/frontend-platform/react'; - -import initializeStore from '../../store'; +// @ts-check +import { initializeMocks, render } from '../../testUtils'; import messages from './messages'; import ImportSidebar from './ImportSidebar'; const courseId = 'course-123'; -let store; - -const RootWrapper = () => ( - - - - - -); describe('', () => { beforeEach(() => { - initializeMockApp({ - authenticatedUser: { - userId: 3, - username: 'abc123', - administrator: true, - roles: [], - }, - }); - store = initializeStore(); + initializeMocks(); }); it('render sidebar correctly', () => { - const { getByText } = render(); + const { getByText } = render(); expect(getByText(messages.title1.defaultMessage)).toBeInTheDocument(); expect(getByText(messages.importedContentHeading.defaultMessage)).toBeInTheDocument(); }); diff --git a/src/pages-and-resources/PagesAndResources.test.jsx b/src/pages-and-resources/PagesAndResources.test.jsx index 0e662e3f8..9888586cf 100644 --- a/src/pages-and-resources/PagesAndResources.test.jsx +++ b/src/pages-and-resources/PagesAndResources.test.jsx @@ -1,9 +1,10 @@ +// @ts-check import { screen, waitFor } from '@testing-library/react'; import { getConfig, setConfig } from '@edx/frontend-platform'; import { PLUGIN_OPERATIONS, DIRECT_PLUGIN } from '@openedx/frontend-plugin-framework'; import { PagesAndResources } from '.'; -import { render } from './utils.test'; +import { initializeMocks, render } from '../testUtils'; const mockPlugin = (identifier) => ({ plugins: [ @@ -43,10 +44,8 @@ describe('PagesAndResources', () => { }, }; - render( - , - { preloadedState: initialState }, - ); + initializeMocks({ initialState }); + render(); await waitFor(() => expect(screen.queryByRole('heading', { name: 'Content permissions' })).not.toBeInTheDocument()); await waitFor(() => expect(screen.queryByTestId('additional_course_plugin')).toBeInTheDocument()); @@ -75,10 +74,8 @@ describe('PagesAndResources', () => { }, }; - render( - , - { preloadedState: initialState }, - ); + initializeMocks({ initialState }); + render(); await waitFor(() => expect(screen.getByRole('heading', { name: 'Content permissions' })).toBeInTheDocument()); await waitFor(() => expect(screen.getByText('Learning Assistant')).toBeInTheDocument()); @@ -110,10 +107,8 @@ describe('PagesAndResources', () => { }, }; - render( - , - { preloadedState: initialState }, - ); + initializeMocks({ initialState }); + render(); await waitFor(() => expect(screen.getByRole('heading', { name: 'Content permissions' })).toBeInTheDocument()); await waitFor(() => expect(screen.getByText('Xpert unit summaries')).toBeInTheDocument()); diff --git a/src/pages-and-resources/pages/PageCard.test.jsx b/src/pages-and-resources/pages/PageCard.test.jsx index 6a9c193fa..56a2deae0 100644 --- a/src/pages-and-resources/pages/PageCard.test.jsx +++ b/src/pages-and-resources/pages/PageCard.test.jsx @@ -7,14 +7,11 @@ import { waitFor, } from '../../testUtils'; import PageGrid from './PageGrid'; -import { executeThunk } from '../../utils'; import { getApiWaffleFlagsUrl } from '../../data/api'; -import { fetchWaffleFlags } from '../../data/thunks'; import PagesAndResourcesProvider from '../PagesAndResourcesProvider'; let container; -let store; let axiosMock; const courseId = '123'; const mockPageConfig = [ @@ -50,7 +47,6 @@ const renderComponent = () => { describe('LiveSettings', () => { beforeEach(async () => { const mocks = initializeMocks(); - store = mocks.reduxStore; axiosMock = mocks.axiosMock; axiosMock .onGet(getApiWaffleFlagsUrl(courseId)) @@ -60,7 +56,6 @@ describe('LiveSettings', () => { useNewScheduleDetailsPage: true, useNewCourseOutlinePage: true, }); - await executeThunk(fetchWaffleFlags(courseId), store.dispatch); }); it('should render three cards', async () => { diff --git a/src/pages-and-resources/utils.test.jsx b/src/pages-and-resources/utils.test.jsx deleted file mode 100644 index debd09158..000000000 --- a/src/pages-and-resources/utils.test.jsx +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -import { render } from '@testing-library/react'; -import { BrowserRouter } from 'react-router-dom'; -import { configureStore } from '@reduxjs/toolkit'; -import { IntlProvider } from '@edx/frontend-platform/i18n'; -import { Provider } from 'react-redux'; - -import { reducer as modelsReducer } from '../generic/model-store'; -import { reducer as pagesAndResourcesReducer } from './data/slice'; -import PagesAndResourcesProvider from './PagesAndResourcesProvider'; - -function renderWithProviders( - ui, - { - preloadedState = {}, - // Automatically create a store instance if no store was passed in - store = configureStore( - { - reducer: { pagesAndResources: pagesAndResourcesReducer, models: modelsReducer }, preloadedState, - }, - ), - ...renderOptions - } = {}, -) { - const Wrapper = ({ children }) => ( - - {/* */} - - - {children} - - - {/* */} - - ); - - Wrapper.propTypes = { - children: PropTypes.node.isRequired, - }; - - // Return an object with the store and all of RTL's query functions - return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) }; -} - -// We may add additional exports to this file over time, so we will not export render as the default. -export { renderWithProviders as render }; diff --git a/src/schedule-and-details/ScheduleAndDetails.test.jsx b/src/schedule-and-details/ScheduleAndDetails.test.jsx index 0afee52fe..400700efe 100644 --- a/src/schedule-and-details/ScheduleAndDetails.test.jsx +++ b/src/schedule-and-details/ScheduleAndDetails.test.jsx @@ -1,14 +1,11 @@ -import React from 'react'; -import { initializeMockApp } from '@edx/frontend-platform'; -import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; -import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n'; -import { AppProvider } from '@edx/frontend-platform/react'; +// @ts-check import { - act, render, waitFor, fireEvent, -} from '@testing-library/react'; -import MockAdapter from 'axios-mock-adapter'; - -import initializeStore from '../store'; + act, + initializeMocks, + render, + waitFor, + fireEvent, +} from '../testUtils'; import { executeThunk } from '../utils'; import { courseDetailsMock, courseSettingsMock } from './__mocks__'; import { getCourseDetailsApiUrl, getCourseSettingsApiUrl } from './data/api'; @@ -50,27 +47,11 @@ jest.mock('react-textarea-autosize', () => jest.fn((props) => (