From bc8d59b0eb8d1dafa24116770ef2763656b4ae1c Mon Sep 17 00:00:00 2001 From: Peter Kulko <93188219+PKulkoRaccoonGang@users.noreply.github.com> Date: Thu, 21 Nov 2024 20:24:31 +0200 Subject: [PATCH] chore: [FC-0070] Some tests refactoring (#1518) --- src/CourseAuthoringPage.test.jsx | 63 ++---- src/CourseAuthoringRoutes.test.jsx | 100 ++++----- .../ChecklistSection.test.jsx | 6 +- src/custom-pages/CustomPages.test.jsx | 40 +--- .../grading-scale/react-ranger.test.js | 55 +++++ .../LibraryAuthoringPage.test.tsx | 3 - .../add-content/AddContentWorkflow.test.tsx | 2 - .../PickLibraryContentModal.test.tsx | 2 - .../pages/PageCard.test.jsx | 52 ++--- src/studio-home/StudioHome.test.jsx | 212 ++++++++---------- .../collapsible-state-with-action/index.jsx | 1 + .../tabs-section/courses-tab/index.test.tsx | 14 ++ src/testUtils.tsx | 5 + 13 files changed, 258 insertions(+), 297 deletions(-) create mode 100644 src/grading-settings/grading-scale/react-ranger.test.js diff --git a/src/CourseAuthoringPage.test.jsx b/src/CourseAuthoringPage.test.jsx index 4f342f826b..7640dc772d 100644 --- a/src/CourseAuthoringPage.test.jsx +++ b/src/CourseAuthoringPage.test.jsx @@ -1,19 +1,12 @@ -import React from 'react'; +import { getConfig } from '@edx/frontend-platform'; -import { render } from '@testing-library/react'; - -import { getConfig, initializeMockApp } from '@edx/frontend-platform'; -import MockAdapter from 'axios-mock-adapter'; -import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; -import { AppProvider } from '@edx/frontend-platform/react'; -import { IntlProvider } from '@edx/frontend-platform/i18n'; -import initializeStore from './store'; 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 { getApiWaffleFlagsUrl } from './data/api'; +import { initializeMocks, render } from './testUtils'; const courseId = 'course-v1:edX+TestX+Test_Course'; let mockPathname = '/evilguy/'; @@ -27,16 +20,9 @@ let axiosMock; let store; 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(getApiWaffleFlagsUrl(courseId)) .reply(200, {}); @@ -56,13 +42,9 @@ describe('Editor Pages Load no header', () => { mockPathname = '/editor/'; await mockStoreSuccess(); const wrapper = render( - - - - - - - + + + , ); expect(wrapper.queryByRole('status')).not.toBeInTheDocument(); @@ -71,13 +53,9 @@ describe('Editor Pages Load no header', () => { mockPathname = '/evilguy/'; await mockStoreSuccess(); const wrapper = render( - - - - - - - + + + , ); expect(wrapper.queryByRole('status')).toBeInTheDocument(); @@ -105,14 +83,7 @@ describe('Course authoring page', () => { }; test('renders not found page on non-existent course key', async () => { await mockStoreNotFound(); - const wrapper = render( - - - - - - , - ); + const wrapper = render(); expect(await wrapper.findByTestId('notFoundAlert')).toBeInTheDocument(); }); test('does not render not found page on other kinds of error', async () => { @@ -123,13 +94,9 @@ describe('Course authoring page', () => { // found alert is not present. const contentTestId = 'courseAuthoringPageContent'; const wrapper = render( - - - -
- - - + +
+ , ); expect(await wrapper.findByTestId(contentTestId)).toBeInTheDocument(); diff --git a/src/CourseAuthoringRoutes.test.jsx b/src/CourseAuthoringRoutes.test.jsx index 790c68326c..e2233559f7 100644 --- a/src/CourseAuthoringRoutes.test.jsx +++ b/src/CourseAuthoringRoutes.test.jsx @@ -1,15 +1,10 @@ -import React from 'react'; -import { AppProvider } from '@edx/frontend-platform/react'; -import { initializeMockApp } from '@edx/frontend-platform'; -import { render, screen } from '@testing-library/react'; -import { MemoryRouter } from 'react-router-dom'; -import MockAdapter from 'axios-mock-adapter'; -import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import CourseAuthoringRoutes from './CourseAuthoringRoutes'; -import initializeStore from './store'; import { executeThunk } from './utils'; import { getApiWaffleFlagsUrl } from './data/api'; import { fetchWaffleFlags } from './data/thunks'; +import { + screen, initializeMocks, render, waitFor, +} from './testUtils'; const courseId = 'course-v1:edX+TestX+Test_Course'; const pagesAndResourcesMockText = 'Pages And Resources'; @@ -17,7 +12,6 @@ const editorContainerMockText = 'Editor Container'; const videoSelectorContainerMockText = 'Video Selector Container'; const customPagesMockText = 'Custom Pages'; let store; -let axiosMock; const mockComponentFn = jest.fn(); jest.mock('react-router-dom', () => ({ @@ -57,72 +51,58 @@ jest.mock('./custom-pages/CustomPages', () => (props) => { describe('', () => { beforeEach(async () => { - initializeMockApp({ - authenticatedUser: { - userId: 3, - username: 'abc123', - administrator: true, - roles: [], - }, - }); - store = initializeStore(); - axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + const { axiosMock, reduxStore } = initializeMocks(); + store = reduxStore; axiosMock .onGet(getApiWaffleFlagsUrl(courseId)) .reply(200, {}); await executeThunk(fetchWaffleFlags(courseId), store.dispatch); }); - fit('renders the PagesAndResources component when the pages and resources route is active', () => { + it('renders the PagesAndResources component when the pages and resources route is active', async () => { render( - - - - - , - ); - - expect(screen.getByText(pagesAndResourcesMockText)).toBeVisible(); - expect(mockComponentFn).toHaveBeenCalledWith( - expect.objectContaining({ - courseId, - }), + , + { routerProps: { initialEntries: ['/pages-and-resources'] } }, ); + await waitFor(() => { + expect(screen.getByText(pagesAndResourcesMockText)).toBeVisible(); + expect(mockComponentFn).toHaveBeenCalledWith( + expect.objectContaining({ + courseId, + }), + ); + }); }); - it('renders the EditorContainer component when the course editor route is active', () => { + it('renders the EditorContainer component when the course editor route is active', async () => { render( - - - - - , - ); - - expect(screen.queryByText(editorContainerMockText)).toBeInTheDocument(); - expect(screen.queryByText(pagesAndResourcesMockText)).not.toBeInTheDocument(); - expect(mockComponentFn).toHaveBeenCalledWith( - expect.objectContaining({ - courseId, - }), + , + { routerProps: { initialEntries: ['/editor/video/block-id'] } }, ); + await waitFor(() => { + expect(screen.queryByText(editorContainerMockText)).toBeInTheDocument(); + expect(screen.queryByText(pagesAndResourcesMockText)).not.toBeInTheDocument(); + expect(mockComponentFn).toHaveBeenCalledWith( + expect.objectContaining({ + learningContextId: courseId, + }), + ); + }); }); - it('renders the VideoSelectorContainer component when the course videos route is active', () => { + it('renders the VideoSelectorContainer component when the course videos route is active', async () => { render( - - - - - , - ); - - expect(screen.queryByText(videoSelectorContainerMockText)).toBeInTheDocument(); - expect(screen.queryByText(pagesAndResourcesMockText)).not.toBeInTheDocument(); - expect(mockComponentFn).toHaveBeenCalledWith( - expect.objectContaining({ - courseId, - }), + , + { routerProps: { initialEntries: ['/editor/course-videos/block-id'] } }, ); + await waitFor(() => { + expect(screen.queryByText(videoSelectorContainerMockText)).toBeInTheDocument(); + expect(screen.queryByText(pagesAndResourcesMockText)).not.toBeInTheDocument(); + expect(mockComponentFn).toHaveBeenCalledWith( + expect.objectContaining({ + courseId, + }), + ); + }); }); }); diff --git a/src/course-checklist/ChecklistSection/ChecklistSection.test.jsx b/src/course-checklist/ChecklistSection/ChecklistSection.test.jsx index c4ad7f3262..09aa81d338 100644 --- a/src/course-checklist/ChecklistSection/ChecklistSection.test.jsx +++ b/src/course-checklist/ChecklistSection/ChecklistSection.test.jsx @@ -1,8 +1,8 @@ -import { within, screen } from '@testing-library/react'; -import '@testing-library/jest-dom'; import { camelCaseObject } from '@edx/frontend-platform'; -import { initializeMocks, render } from '../../testUtils'; +import { + initializeMocks, render, screen, within, +} from '../../testUtils'; import { getApiWaffleFlagsUrl } from '../../data/api'; import { fetchWaffleFlags } from '../../data/thunks'; import { generateCourseLaunchData } from '../factories/mockApiResponses'; diff --git a/src/custom-pages/CustomPages.test.jsx b/src/custom-pages/CustomPages.test.jsx index 8e8a4122c0..de57e25b80 100644 --- a/src/custom-pages/CustomPages.test.jsx +++ b/src/custom-pages/CustomPages.test.jsx @@ -1,18 +1,12 @@ +import ReactDOM from 'react-dom'; + import { - render, - act, + initializeMocks, fireEvent, screen, -} from '@testing-library/react'; -import ReactDOM from 'react-dom'; - -import { initializeMockApp } from '@edx/frontend-platform'; -import MockAdapter from 'axios-mock-adapter'; -import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; -import { AppProvider } from '@edx/frontend-platform/react'; -import { IntlProvider } from '@edx/frontend-platform/i18n'; - -import initializeStore from '../store'; + act, + render, +} from '../testUtils'; import { executeThunk } from '../utils'; import { RequestStatus } from '../data/constants'; import { getApiWaffleFlagsUrl } from '../data/api'; @@ -23,7 +17,6 @@ import { generateNewPageApiResponse, getStatusValue, courseId, - initialState, } from './factories/mockApiResponses'; import { @@ -39,13 +32,7 @@ let store; ReactDOM.createPortal = jest.fn(node => node); const renderComponent = () => { - render( - - - - - , - ); + render(); }; const mockStore = async (status) => { @@ -64,16 +51,9 @@ const mockStore = async (status) => { describe('CustomPages', () => { beforeEach(async () => { - initializeMockApp({ - authenticatedUser: { - userId: 3, - username: 'abc123', - administrator: false, - roles: [], - }, - }); - store = initializeStore(initialState); - axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + const mocks = initializeMocks(); + store = mocks.reduxStore; + axiosMock = mocks.axiosMock; axiosMock .onGet(getApiWaffleFlagsUrl(courseId)) .reply(200, { diff --git a/src/grading-settings/grading-scale/react-ranger.test.js b/src/grading-settings/grading-scale/react-ranger.test.js new file mode 100644 index 0000000000..76692970df --- /dev/null +++ b/src/grading-settings/grading-scale/react-ranger.test.js @@ -0,0 +1,55 @@ +import { renderHook, act } from '@testing-library/react-hooks'; + +import { useRanger } from './react-ranger'; + +describe('useRanger Hook', () => { + const mockOnChange = jest.fn(); + const mockOnDrag = jest.fn(); + + const defaultProps = { + values: [20, 80], + min: 0, + max: 100, + stepSize: 10, + onChange: mockOnChange, + onDrag: mockOnDrag, + }; + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('initializes with correct default properties', () => { + const { result } = renderHook(() => useRanger(defaultProps)); + + expect(result.current.ticks).toBeDefined(); + expect(result.current.segments).toBeDefined(); + expect(result.current.handles).toHaveLength(2); // Two handles for two values + expect(result.current.activeHandleIndex).toBeNull(); + }); + + it('calculates ticks based on min, max, and stepSize', () => { + const { result } = renderHook(() => useRanger(defaultProps)); + + const tickValues = result.current.ticks.map((tick) => tick.value); + expect(tickValues).toEqual([0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]); + }); + + it('resets active handle index after interaction', () => { + const { result } = renderHook(() => useRanger(defaultProps)); + + act(() => { + result.current.handles[0].getHandleProps().onMouseDown({ persist: jest.fn() }, 0); + document.dispatchEvent(new MouseEvent('mouseup')); + }); + + expect(result.current.activeHandleIndex).toBeNull(); + }); + + it('computes segments based on values', () => { + const { result } = renderHook(() => useRanger(defaultProps)); + + const segmentValues = result.current.segments.map((segment) => segment.value); + expect(segmentValues).toEqual([20, 80, 100]); + }); +}); diff --git a/src/library-authoring/LibraryAuthoringPage.test.tsx b/src/library-authoring/LibraryAuthoringPage.test.tsx index 990cbcb4b5..d352b0abb6 100644 --- a/src/library-authoring/LibraryAuthoringPage.test.tsx +++ b/src/library-authoring/LibraryAuthoringPage.test.tsx @@ -23,7 +23,6 @@ import { getStudioHomeApiUrl } from '../studio-home/data/api'; import { mockBroadcastChannel } from '../generic/data/api.mock'; import { LibraryLayout } from '.'; import { getLibraryCollectionsApiUrl } from './data/api'; -import { getApiWaffleFlagsUrl } from '../data/api'; mockGetCollectionMetadata.applyMock(); mockContentSearchConfig.applyMock(); @@ -56,7 +55,6 @@ describe('', () => { beforeEach(async () => { const { axiosMock } = initializeMocks(); axiosMock.onGet(getStudioHomeApiUrl()).reply(200, studioHomeMock); - axiosMock.onGet(getApiWaffleFlagsUrl()).reply(200, {}); // The Meilisearch client-side API uses fetch, not Axios. fetchMock.mockReset(); @@ -681,7 +679,6 @@ describe('', () => { it('Shows an error if libraries V2 is disabled', async () => { const { axiosMock } = initializeMocks(); - axiosMock.onGet(getApiWaffleFlagsUrl()).reply(200, {}); axiosMock.onGet(getStudioHomeApiUrl()).reply(200, { ...studioHomeMock, libraries_v2_enabled: false, diff --git a/src/library-authoring/add-content/AddContentWorkflow.test.tsx b/src/library-authoring/add-content/AddContentWorkflow.test.tsx index 2e0818172b..b019700a56 100644 --- a/src/library-authoring/add-content/AddContentWorkflow.test.tsx +++ b/src/library-authoring/add-content/AddContentWorkflow.test.tsx @@ -21,7 +21,6 @@ import { mockBroadcastChannel, mockClipboardEmpty } from '../../generic/data/api import { mockContentSearchConfig, mockSearchResult } from '../../search-manager/data/api.mock'; import { studioHomeMock } from '../../studio-home/__mocks__'; import { getStudioHomeApiUrl } from '../../studio-home/data/api'; -import { getApiWaffleFlagsUrl } from '../../data/api'; import LibraryLayout from '../LibraryLayout'; mockContentSearchConfig.applyMock(); @@ -52,7 +51,6 @@ describe('AddContentWorkflow test', () => { beforeEach(async () => { const { axiosMock } = initializeMocks(); axiosMock.onGet(getStudioHomeApiUrl()).reply(200, studioHomeMock); - axiosMock.onGet(getApiWaffleFlagsUrl()).reply(200, {}); }); it('can create an HTML component', async () => { diff --git a/src/library-authoring/add-content/PickLibraryContentModal.test.tsx b/src/library-authoring/add-content/PickLibraryContentModal.test.tsx index 0f327fd09d..a179dff4b6 100644 --- a/src/library-authoring/add-content/PickLibraryContentModal.test.tsx +++ b/src/library-authoring/add-content/PickLibraryContentModal.test.tsx @@ -17,7 +17,6 @@ import { mockGetCollectionMetadata, } from '../data/api.mocks'; import { PickLibraryContentModal } from './PickLibraryContentModal'; -import { getApiWaffleFlagsUrl } from '../../data/api'; mockContentSearchConfig.applyMock(); mockContentLibrary.applyMock(); @@ -48,7 +47,6 @@ describe('', () => { const mocks = initializeMocks(); mockShowToast = mocks.mockShowToast; mocks.axiosMock.onGet(getStudioHomeApiUrl()).reply(200, studioHomeMock); - mocks.axiosMock.onGet(getApiWaffleFlagsUrl()).reply(200, {}); }); it('can pick components from the modal', async () => { diff --git a/src/pages-and-resources/pages/PageCard.test.jsx b/src/pages-and-resources/pages/PageCard.test.jsx index 4c8a80a9f3..6a9c193fa9 100644 --- a/src/pages-and-resources/pages/PageCard.test.jsx +++ b/src/pages-and-resources/pages/PageCard.test.jsx @@ -1,13 +1,11 @@ +import { getConfig } from '@edx/frontend-platform'; + import { + initializeMocks, + screen, render, - queryAllByRole, -} from '@testing-library/react'; -import MockAdapter from 'axios-mock-adapter'; -import { initializeMockApp, getConfig } 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'; + waitFor, +} from '../../testUtils'; import PageGrid from './PageGrid'; import { executeThunk } from '../../utils'; import { getApiWaffleFlagsUrl } from '../../data/api'; @@ -42,34 +40,18 @@ const mockPageConfig = [ const renderComponent = () => { const wrapper = render( - - - - - - - , + + + , ); container = wrapper.container; }; describe('LiveSettings', () => { beforeEach(async () => { - initializeMockApp({ - authenticatedUser: { - userId: 3, - username: 'abc123', - administrator: false, - roles: [], - }, - }); - store = initializeStore({ - courseDetail: { - courseId: 'id', - status: 'sucessful', - }, - }); - axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + const mocks = initializeMocks(); + store = mocks.reduxStore; + axiosMock = mocks.axiosMock; axiosMock .onGet(getApiWaffleFlagsUrl(courseId)) .reply(200, { @@ -83,13 +65,17 @@ describe('LiveSettings', () => { it('should render three cards', async () => { renderComponent(); - expect(queryAllByRole(container, 'button')).toHaveLength(3); + waitFor(() => { + expect(screen.queryAllByRole(container, 'button')).toHaveLength(3); + }); }); it('should navigate to legacyLink', async () => { renderComponent(); const textbookPagePath = mockPageConfig[0][1]; - const textbookSettingsButton = queryAllByRole(container, 'link')[1]; - expect(textbookSettingsButton).toHaveAttribute('href', textbookPagePath); + waitFor(() => { + const textbookSettingsButton = screen.queryAllByRole(container, 'link')[1]; + expect(textbookSettingsButton).toHaveAttribute('href', textbookPagePath); + }); }); }); diff --git a/src/studio-home/StudioHome.test.jsx b/src/studio-home/StudioHome.test.jsx index b2a8fd871b..8e6eca953a 100644 --- a/src/studio-home/StudioHome.test.jsx +++ b/src/studio-home/StudioHome.test.jsx @@ -1,32 +1,22 @@ -import React from 'react'; import { useSelector } from 'react-redux'; import { MemoryRouter, Routes, Route } from 'react-router-dom'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -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 { - act, fireEvent, render, waitFor, -} from '@testing-library/react'; -import MockAdapter from 'axios-mock-adapter'; -import initializeStore from '../store'; import { RequestStatus } from '../data/constants'; import { COURSE_CREATOR_STATES } from '../constants'; -import { executeThunk } from '../utils'; import { studioHomeMock } from './__mocks__'; import { getStudioHomeApiUrl } from './data/api'; -import { fetchStudioHomeData } from './data/thunks'; -import { getApiWaffleFlagsUrl } from '../data/api'; -import { fetchWaffleFlags } from '../data/thunks'; +import { + act, + fireEvent, + render, + waitFor, + initializeMocks, +} from '../testUtils'; import messages from './messages'; import createNewCourseMessages from './create-new-course-form/messages'; import createOrRerunCourseMessages from '../generic/create-or-rerun-course/messages'; import { StudioHome } from '.'; -let axiosMock; -let store; const { studioShortName, studioRequestEmail, @@ -44,90 +34,60 @@ jest.mock('react-router-dom', () => ({ useNavigate: () => mockNavigate, })); -const queryClient = new QueryClient(); - const RootWrapper = () => ( - - - - - - } - /> - } - /> - } - /> - - - - - + + + } + /> + } + /> + } + /> + , + ); describe('', () => { describe('api fetch fails', () => { beforeEach(async () => { - initializeMockApp({ - authenticatedUser: { - userId: 3, - username: 'abc123', - administrator: true, - roles: [], - }, - }); - store = initializeStore(); - axiosMock = new MockAdapter(getAuthenticatedHttpClient()); - axiosMock.onGet(getStudioHomeApiUrl()).reply(404); - await executeThunk(fetchStudioHomeData(), store.dispatch); - axiosMock - .onGet(getApiWaffleFlagsUrl()) - .reply(200, {}); - await executeThunk(fetchWaffleFlags(), store.dispatch); + const mocks = initializeMocks(); + mocks.axiosMock.onGet(getStudioHomeApiUrl()).reply(404); useSelector.mockReturnValue({ studioHomeLoadingStatus: RequestStatus.FAILED }); }); it('should render fetch error', () => { const { getByText } = render(); - expect(getByText(messages.homePageLoadFailedMessage.defaultMessage)).toBeInTheDocument(); + waitFor(() => { + expect(getByText(messages.homePageLoadFailedMessage.defaultMessage)).toBeInTheDocument(); + }); }); it('should render Studio home title', () => { const { getByText } = render(); - expect(getByText('Studio home')).toBeInTheDocument(); + waitFor(() => { + expect(getByText('Studio home')).toBeInTheDocument(); + }); }); }); describe('api fetch succeeds', () => { beforeEach(async () => { - initializeMockApp({ - authenticatedUser: { - userId: 3, - username: 'abc123', - administrator: true, - roles: [], - }, - }); - store = initializeStore(); - axiosMock = new MockAdapter(getAuthenticatedHttpClient()); - axiosMock.onGet(getStudioHomeApiUrl()).reply(200, studioHomeMock); - await executeThunk(fetchStudioHomeData(), store.dispatch); + const mocks = initializeMocks(); + mocks.axiosMock.onGet(getStudioHomeApiUrl()).reply(200, studioHomeMock); useSelector.mockReturnValue(studioHomeMock); - axiosMock - .onGet(getApiWaffleFlagsUrl()) - .reply(200, {}); - await executeThunk(fetchWaffleFlags(), store.dispatch); }); it('should render page and page title correctly', () => { const { getByText } = render(); - expect(getByText(`${studioShortName} home`)).toBeInTheDocument(); + waitFor(() => { + expect(getByText(`${studioShortName} home`)).toBeInTheDocument(); + }); }); it('should render email staff header button', async () => { @@ -137,8 +97,10 @@ describe('', () => { }); const { getByRole } = render(); - expect(getByRole('link', { name: messages.emailStaffBtnText.defaultMessage })) - .toHaveAttribute('href', `mailto:${studioRequestEmail}`); + waitFor(() => { + expect(getByRole('link', { name: messages.emailStaffBtnText.defaultMessage })) + .toHaveAttribute('href', `mailto:${studioRequestEmail}`); + }); }); it('should render create new course button', async () => { @@ -148,7 +110,9 @@ describe('', () => { }); const { getByRole } = render(); - expect(getByRole('button', { name: messages.addNewCourseBtnText.defaultMessage })).toBeInTheDocument(); + waitFor(() => { + expect(getByRole('button', { name: messages.addNewCourseBtnText.defaultMessage })).toBeInTheDocument(); + }); }); it('should show verify email layout if user inactive', () => { @@ -158,7 +122,9 @@ describe('', () => { }); const { getByText } = render(); - expect(getByText('Thanks for signing up, abc123!', { exact: false })).toBeInTheDocument(); + waitFor(() => { + expect(getByText('Thanks for signing up, abc123!', { exact: false })).toBeInTheDocument(); + }); }); it('shows the spinner before the query is complete', async () => { @@ -169,8 +135,10 @@ describe('', () => { await act(async () => { const { getByRole } = render(); - const spinner = getByRole('status'); - expect(spinner.textContent).toEqual('Loading...'); + waitFor(() => { + const spinner = getByRole('status'); + expect(spinner.textContent).toEqual('Loading...'); + }); }); }); @@ -184,13 +152,15 @@ describe('', () => { const studioBaseUrl = 'http://localhost:18010'; const { getByTestId } = render(); - const createNewLibraryButton = getByTestId('new-library-button'); - - const { open } = window; - window.open = jest.fn(); - fireEvent.click(createNewLibraryButton); - expect(window.open).toHaveBeenCalledWith(`${studioBaseUrl}/home_library`); - window.open = open; + waitFor(() => { + const createNewLibraryButton = getByTestId('new-library-button'); + + const { open } = window; + window.open = jest.fn(); + fireEvent.click(createNewLibraryButton); + expect(window.open).toHaveBeenCalledWith(`${studioBaseUrl}/home_library`); + window.open = open; + }); }); it('should navigate to the library authoring page in course authoring', () => { @@ -199,11 +169,11 @@ describe('', () => { librariesV1Enabled: false, }); const { getByTestId } = render(); - const createNewLibraryButton = getByTestId('new-library-button'); - - fireEvent.click(createNewLibraryButton); - - expect(mockNavigate).toHaveBeenCalledWith('/library/create'); + waitFor(() => { + const createNewLibraryButton = getByTestId('new-library-button'); + fireEvent.click(createNewLibraryButton); + expect(mockNavigate).toHaveBeenCalledWith('/library/create'); + }); }); }); @@ -224,7 +194,9 @@ describe('', () => { librariesV1Enabled: false, }); const { queryByTestId } = render(); - expect(queryByTestId('new-library-button')).toBeInTheDocument(); + waitFor(() => { + expect(queryByTestId('new-library-button')).toBeInTheDocument(); + }); }); it('should render create new course container', async () => { @@ -234,10 +206,12 @@ describe('', () => { }); const { getByRole, getByText } = render(); - const createNewCourseButton = getByRole('button', { name: messages.addNewCourseBtnText.defaultMessage }); + waitFor(() => { + const createNewCourseButton = getByRole('button', { name: messages.addNewCourseBtnText.defaultMessage }); - await act(() => fireEvent.click(createNewCourseButton)); - expect(getByText(createNewCourseMessages.createNewCourse.defaultMessage)).toBeInTheDocument(); + act(() => fireEvent.click(createNewCourseButton)); + expect(getByText(createNewCourseMessages.createNewCourse.defaultMessage)).toBeInTheDocument(); + }); }); it('should hide create new course container', async () => { @@ -247,16 +221,16 @@ describe('', () => { }); const { getByRole, queryByText, getByText } = render(); - const createNewCourseButton = getByRole('button', { name: messages.addNewCourseBtnText.defaultMessage }); - fireEvent.click(createNewCourseButton); waitFor(() => { + const createNewCourseButton = getByRole('button', { name: messages.addNewCourseBtnText.defaultMessage }); + fireEvent.click(createNewCourseButton); expect(getByText(createNewCourseMessages.createNewCourse.defaultMessage)).toBeInTheDocument(); }); - const cancelButton = getByRole('button', { name: createOrRerunCourseMessages.cancelButton.defaultMessage }); - fireEvent.click(cancelButton); waitFor(() => { + const cancelButton = getByRole('button', { name: createOrRerunCourseMessages.cancelButton.defaultMessage }); + fireEvent.click(cancelButton); expect(queryByText(createNewCourseMessages.createNewCourse.defaultMessage)).not.toBeInTheDocument(); }); }); @@ -270,13 +244,15 @@ describe('', () => { }); const { getByText, queryByText } = render(); const defaultTitleMessage = messages.defaultSection_1_Title.defaultMessage; - const titleWithStudioName = defaultTitleMessage.replace('{studioShortName}', 'Studio'); - const administratorCardTitle = getByText(titleWithStudioName); + waitFor(() => { + const titleWithStudioName = defaultTitleMessage.replace('{studioShortName}', 'Studio'); + const administratorCardTitle = getByText(titleWithStudioName); - expect(administratorCardTitle).toBeVisible(); + expect(administratorCardTitle).toBeVisible(); - const addCourseButton = queryByText(messages.btnAddNewCourseText.defaultMessage); - expect(addCourseButton).toBeNull(); + const addCourseButton = queryByText(messages.btnAddNewCourseText.defaultMessage); + expect(addCourseButton).toBeNull(); + }); }); it('should show contact administrator card with add course buttons', () => { @@ -287,23 +263,27 @@ describe('', () => { }); const { getByText, getByTestId } = render(); const defaultTitleMessage = messages.defaultSection_1_Title.defaultMessage; - const titleWithStudioName = defaultTitleMessage.replace('{studioShortName}', 'Studio'); - const administratorCardTitle = getByText(titleWithStudioName); + waitFor(() => { + const titleWithStudioName = defaultTitleMessage.replace('{studioShortName}', 'Studio'); + const administratorCardTitle = getByText(titleWithStudioName); - expect(administratorCardTitle).toBeVisible(); + expect(administratorCardTitle).toBeVisible(); - const addCourseButton = getByTestId('contact-admin-create-course'); - expect(addCourseButton).toBeVisible(); + const addCourseButton = getByTestId('contact-admin-create-course'); + expect(addCourseButton).toBeVisible(); - fireEvent.click(addCourseButton); - expect(getByTestId('create-course-form')).toBeVisible(); + fireEvent.click(addCourseButton); + expect(getByTestId('create-course-form')).toBeVisible(); + }); }); }); it('should show footer', () => { const { getByText } = render(); - expect(getByText('Looking for help with Studio?')).toBeInTheDocument(); - expect(getByText('LMS')).toHaveAttribute('href', process.env.LMS_BASE_URL); + waitFor(() => { + expect(getByText('Looking for help with Studio?')).toBeInTheDocument(); + expect(getByText('LMS')).toHaveAttribute('href', process.env.LMS_BASE_URL); + }); }); }); }); diff --git a/src/studio-home/collapsible-state-with-action/index.jsx b/src/studio-home/collapsible-state-with-action/index.jsx index 196fa4a0b9..ee99398a8e 100644 --- a/src/studio-home/collapsible-state-with-action/index.jsx +++ b/src/studio-home/collapsible-state-with-action/index.jsx @@ -98,6 +98,7 @@ const CollapsibleStateWithAction = ({ state, className }) => { {title} diff --git a/src/studio-home/tabs-section/courses-tab/index.test.tsx b/src/studio-home/tabs-section/courses-tab/index.test.tsx index 81c72307e9..8c9f1d8dd4 100644 --- a/src/studio-home/tabs-section/courses-tab/index.test.tsx +++ b/src/studio-home/tabs-section/courses-tab/index.test.tsx @@ -4,6 +4,7 @@ import { render, screen } from '@testing-library/react'; import { IntlProvider } from '@edx/frontend-platform/i18n'; import { AppProvider } from '@edx/frontend-platform/react'; +import { COURSE_CREATOR_STATES } from '../../../constants'; import initializeStore from '../../../store'; import { studioHomeMock } from '../../__mocks__'; import { initialState } from '../../factories/mockApiResponses'; @@ -127,4 +128,17 @@ describe('', () => { const alertCoursesNotFound = screen.queryByTestId('processing-courses-title'); expect(alertCoursesNotFound).toBeInTheDocument(); }); + + it('should render CollapsibleStateWithAction when courseCreatorStatus is true', () => { + const props = { isShowProcessing: true, isEnabledPagination: false }; + const customStoreData = { + studioHomeData: { + inProcessCourseActions: [], + courseCreatorStatus: COURSE_CREATOR_STATES.denied, + }, + }; + renderComponent(props, customStoreData); + const collapsibleStateWithAction = screen.queryByTestId('collapsible-state-with-action'); + expect(collapsibleStateWithAction).toBeInTheDocument(); + }); }); diff --git a/src/testUtils.tsx b/src/testUtils.tsx index dc84448bde..6cfda2c0f8 100644 --- a/src/testUtils.tsx +++ b/src/testUtils.tsx @@ -24,6 +24,7 @@ import { import { ToastContext, type ToastContextData } from './generic/toast-context'; import initializeReduxStore from './store'; +import { getApiWaffleFlagsUrl } from './data/api'; /** @deprecated Use React Query and/or regular React Context instead of redux */ let reduxStore: Store; @@ -172,6 +173,10 @@ export function initializeMocks({ user = defaultUser, initialState = undefined } }); axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + axiosMock + .onGet(getApiWaffleFlagsUrl()) + .reply(200, {}); + // Reset `mockToastContext` for this current test mockToastContext = { showToast: jest.fn(),