diff --git a/.env b/.env index ea2bb90a6..f4619b5aa 100644 --- a/.env +++ b/.env @@ -41,7 +41,6 @@ HOTJAR_APP_ID='' HOTJAR_VERSION=6 HOTJAR_DEBUG=false INVITE_STUDENTS_EMAIL_TO='' -ENABLE_HOME_PAGE_COURSE_API_V2=true ENABLE_CHECKLIST_QUALITY='' ENABLE_GRADING_METHOD_IN_PROBLEMS=false # "Multi-level" blocks are unsupported in libraries diff --git a/.env.development b/.env.development index 883f0f6bf..a0adf0c06 100644 --- a/.env.development +++ b/.env.development @@ -44,7 +44,6 @@ HOTJAR_APP_ID='' HOTJAR_VERSION=6 HOTJAR_DEBUG=true INVITE_STUDENTS_EMAIL_TO="someone@domain.com" -ENABLE_HOME_PAGE_COURSE_API_V2=true ENABLE_CHECKLIST_QUALITY=true ENABLE_GRADING_METHOD_IN_PROBLEMS=false # "Multi-level" blocks are unsupported in libraries diff --git a/src/index.jsx b/src/index.jsx index 67f0833c2..895a03008 100755 --- a/src/index.jsx +++ b/src/index.jsx @@ -158,7 +158,6 @@ initialize({ ENABLE_VIDEO_UPLOAD_PAGE_LINK_IN_CONTENT_DROPDOWN: process.env.ENABLE_VIDEO_UPLOAD_PAGE_LINK_IN_CONTENT_DROPDOWN || 'false', ENABLE_CERTIFICATE_PAGE: process.env.ENABLE_CERTIFICATE_PAGE || 'false', ENABLE_TAGGING_TAXONOMY_PAGES: process.env.ENABLE_TAGGING_TAXONOMY_PAGES || 'false', - ENABLE_HOME_PAGE_COURSE_API_V2: process.env.ENABLE_HOME_PAGE_COURSE_API_V2 === 'true', ENABLE_CHECKLIST_QUALITY: process.env.ENABLE_CHECKLIST_QUALITY || 'true', ENABLE_GRADING_METHOD_IN_PROBLEMS: process.env.ENABLE_GRADING_METHOD_IN_PROBLEMS === 'true', LIBRARY_UNSUPPORTED_BLOCKS: (process.env.LIBRARY_UNSUPPORTED_BLOCKS || 'conditional,step-builder,problem-builder').split(','), diff --git a/src/studio-home/StudioHome.tsx b/src/studio-home/StudioHome.tsx index fa2896ee3..57c6ad90d 100644 --- a/src/studio-home/StudioHome.tsx +++ b/src/studio-home/StudioHome.tsx @@ -31,7 +31,6 @@ const StudioHome = () => { const location = useLocation(); const navigate = useNavigate(); - const isPaginationCoursesEnabled = getConfig().ENABLE_HOME_PAGE_COURSE_API_V2; const { isLoadingPage, isFailedLoadingPage, @@ -153,7 +152,6 @@ const StudioHome = () => { showNewCourseContainer={showNewCourseContainer} onClickNewCourse={() => setShowNewCourseContainer(true)} isShowProcessing={isShowProcessing && !isFiltered} - isPaginationCoursesEnabled={isPaginationCoursesEnabled} librariesV1Enabled={librariesV1Enabled} librariesV2Enabled={librariesV2Enabled} /> diff --git a/src/studio-home/card-item/CardItem.test.tsx b/src/studio-home/card-item/CardItem.test.tsx index 986cb4099..b63102a59 100644 --- a/src/studio-home/card-item/CardItem.test.tsx +++ b/src/studio-home/card-item/CardItem.test.tsx @@ -29,18 +29,20 @@ describe('', () => { render(); const courseTitleLink = screen.getByText(props.displayName); expect(courseTitleLink).toHaveAttribute('href', `${getConfig().STUDIO_BASE_URL}${props.url}`); + const dropDownMenu = screen.getByRole('button', { name: /course actions/i }); + fireEvent.click(dropDownMenu); const btnReRunCourse = screen.getByText(messages.btnReRunText.defaultMessage); - expect(btnReRunCourse).toHaveAttribute('href', trimSlashes(props.rerunLink)); + expect(btnReRunCourse).toHaveAttribute('href', props.rerunLink); const viewLiveLink = screen.getByText(messages.viewLiveBtnText.defaultMessage); expect(viewLiveLink).toHaveAttribute('href', props.lmsLink); }); it('should render correct links for non-library course pagination', () => { const props = studioHomeMock.archivedCourses[0]; - render(); + render(); const courseTitleLink = screen.getByText(props.displayName); expect(courseTitleLink).toHaveAttribute('href', `${getConfig().STUDIO_BASE_URL}${props.url}`); - const dropDownMenu = screen.getByTestId('toggle-dropdown'); + const dropDownMenu = screen.getByRole('button', { name: /course actions/i }); fireEvent.click(dropDownMenu); const btnReRunCourse = screen.getByText(messages.btnReRunText.defaultMessage); expect(btnReRunCourse).toHaveAttribute('href', `/${trimSlashes(props.rerunLink)}`); diff --git a/src/studio-home/card-item/index.tsx b/src/studio-home/card-item/index.tsx index 420ae442d..22b5fc3a1 100644 --- a/src/studio-home/card-item/index.tsx +++ b/src/studio-home/card-item/index.tsx @@ -2,10 +2,8 @@ import React from 'react'; import { useSelector } from 'react-redux'; import { Card, - Hyperlink, Dropdown, IconButton, - ActionRow, } from '@openedx/paragon'; import { MoreHoriz } from '@openedx/paragon/icons'; import { useIntl } from '@edx/frontend-platform/i18n'; @@ -16,7 +14,6 @@ import { getWaffleFlags } from '../../data/selectors'; import { COURSE_CREATOR_STATES } from '../../constants'; import { getStudioHomeData } from '../data/selectors'; import messages from '../messages'; -import { trimSlashes } from './utils'; interface BaseProps { displayName: string; @@ -27,7 +24,6 @@ interface BaseProps { rerunLink?: string | null; courseKey?: string; isLibraries?: boolean; - isPaginated?: boolean; } type Props = BaseProps & ( /** If we should open this course/library in this MFE, this is the path to the edit page, e.g. '/course/foo' */ @@ -51,7 +47,6 @@ const CardItem: React.FC = ({ run = '', isLibraries = false, courseKey = '', - isPaginated = false, path, url, }) => { @@ -92,48 +87,27 @@ const CardItem: React.FC = ({ )} subtitle={subtitle} actions={showActions && ( - isPaginated ? ( - - - - {isShowRerunLink && ( - - {messages.btnReRunText.defaultMessage} - - )} - - {intl.formatMessage(messages.viewLiveBtnText)} - - - - ) : ( - - {isShowRerunLink && ( - - {intl.formatMessage(messages.btnReRunText)} - - )} - + + + {isShowRerunLink && ( + - {intl.formatMessage(messages.viewLiveBtnText)} - - - ) + {messages.btnReRunText.defaultMessage} + + )} + + {intl.formatMessage(messages.viewLiveBtnText)} + + + )} /> diff --git a/src/studio-home/data/api.test.js b/src/studio-home/data/api.test.js index 329d05b3b..febc5d07e 100644 --- a/src/studio-home/data/api.test.js +++ b/src/studio-home/data/api.test.js @@ -15,7 +15,7 @@ import { getStudioHomeLibraries, } from './api'; import { - generateGetStudioCoursesApiResponse, + generateGetStudioCoursesApiResponseV2, generateGetStudioHomeDataApiResponse, generateGetStudioHomeLibrariesApiResponse, } from '../factories/mockApiResponses'; @@ -50,9 +50,9 @@ describe('studio-home api calls', () => { it('should get studio courses data', async () => { const apiLink = `${getApiBaseUrl()}/api/contentstore/v1/home/courses`; - axiosMock.onGet(apiLink).reply(200, generateGetStudioCoursesApiResponse()); + axiosMock.onGet(apiLink).reply(200, generateGetStudioCoursesApiResponseV2()); const result = await getStudioHomeCourses(''); - const expected = generateGetStudioCoursesApiResponse(); + const expected = generateGetStudioCoursesApiResponseV2(); expect(axiosMock.history.get[0].url).toEqual(apiLink); expect(result).toEqual(expected); @@ -60,9 +60,9 @@ describe('studio-home api calls', () => { it('should get studio courses data v2', async () => { const apiLink = `${getApiBaseUrl()}/api/contentstore/v2/home/courses`; - axiosMock.onGet(apiLink).reply(200, generateGetStudioCoursesApiResponse()); + axiosMock.onGet(apiLink).reply(200, generateGetStudioCoursesApiResponseV2()); const result = await getStudioHomeCoursesV2(''); - const expected = generateGetStudioCoursesApiResponse(); + const expected = generateGetStudioCoursesApiResponseV2(); expect(axiosMock.history.get[0].url).toEqual(apiLink); expect(result).toEqual(expected); diff --git a/src/studio-home/data/slice.js b/src/studio-home/data/slice.js index 89ee211c0..feea8b082 100644 --- a/src/studio-home/data/slice.js +++ b/src/studio-home/data/slice.js @@ -3,6 +3,16 @@ import { createSlice } from '@reduxjs/toolkit'; import { RequestStatus } from '../../data/constants'; +export const studioHomeCoursesRequestParamsDefault = { + currentPage: 1, + search: '', + order: 'display_name', + archivedOnly: undefined, + activeOnly: undefined, + isFiltered: false, + cleanFilters: false, +}; + const slice = createSlice({ name: 'studioHome', initialState: { @@ -17,15 +27,7 @@ const slice = createSlice({ deleteNotificationSavingStatus: '', }, studioHomeData: {}, - studioHomeCoursesRequestParams: { - currentPage: 1, - search: undefined, - order: 'display_name', - archivedOnly: undefined, - activeOnly: undefined, - isFiltered: false, - cleanFilters: false, - }, + studioHomeCoursesRequestParams: studioHomeCoursesRequestParamsDefault, }, reducers: { updateLoadingStatuses: (state, { payload }) => { @@ -59,6 +61,9 @@ const slice = createSlice({ updateStudioHomeCoursesCustomParams: (state, { payload }) => { Object.assign(state.studioHomeCoursesRequestParams, payload); }, + resetStudioHomeCoursesCustomParams: (state) => { + state.studioHomeCoursesRequestParams = studioHomeCoursesRequestParamsDefault; + }, }, }); @@ -70,6 +75,7 @@ export const { fetchCourseDataSuccessV2, fetchLibraryDataSuccess, updateStudioHomeCoursesCustomParams, + resetStudioHomeCoursesCustomParams, } = slice.actions; export const { diff --git a/src/studio-home/data/slice.test.js b/src/studio-home/data/slice.test.js index 7340e5679..97e5446d3 100644 --- a/src/studio-home/data/slice.test.js +++ b/src/studio-home/data/slice.test.js @@ -1,4 +1,4 @@ -import { reducer, updateStudioHomeCoursesCustomParams } from './slice'; +import { reducer, resetStudioHomeCoursesCustomParams, updateStudioHomeCoursesCustomParams } from './slice'; import { RequestStatus } from '../../data/constants'; @@ -17,7 +17,7 @@ describe('updateStudioHomeCoursesCustomParams action', () => { studioHomeData: {}, studioHomeCoursesRequestParams: { currentPage: 1, - search: undefined, + search: '', order: 'display_name', archivedOnly: undefined, activeOnly: undefined, @@ -26,26 +26,9 @@ describe('updateStudioHomeCoursesCustomParams action', () => { }, }; - it('should return the initial state', () => { - const result = reducer(undefined, { type: undefined }); - expect(result).toEqual(initialState); - }); - - it('should update the payload passed in studioHomeCoursesRequestParams', () => { - const newState = { - ...initialState, - studioHomeCoursesRequestParams: { - currentPage: 2, - search: 'test', - order: 'display_name', - archivedOnly: true, - activeOnly: true, - isFiltered: true, - cleanFilters: true, - }, - }; - - const payload = { + const modifiedRequestParamsState = { + ...initialState, + studioHomeCoursesRequestParams: { currentPage: 2, search: 'test', order: 'display_name', @@ -53,9 +36,34 @@ describe('updateStudioHomeCoursesCustomParams action', () => { activeOnly: true, isFiltered: true, cleanFilters: true, - }; + }, + }; + const payload = { + currentPage: 2, + search: 'test', + order: 'display_name', + archivedOnly: true, + activeOnly: true, + isFiltered: true, + cleanFilters: true, + }; + + it('should return the initial state', () => { + const result = reducer(undefined, { type: undefined }); + expect(result).toEqual(initialState); + }); + + it('should update the payload passed in studioHomeCoursesRequestParams', () => { const result = reducer(initialState, updateStudioHomeCoursesCustomParams(payload)); - expect(result).toEqual(newState); + expect(result).toEqual(modifiedRequestParamsState); + }); + + it('should reset the studioHomeCoursesRequestParams state to the initial value', () => { + const stateChanged = reducer(initialState, updateStudioHomeCoursesCustomParams(payload)); + expect(stateChanged).toEqual(modifiedRequestParamsState); + + const stateReset = reducer(stateChanged, resetStudioHomeCoursesCustomParams()); + expect(stateReset.studioHomeCoursesRequestParams).toEqual(initialState.studioHomeCoursesRequestParams); }); }); diff --git a/src/studio-home/data/thunks.js b/src/studio-home/data/thunks.js index 57af75a42..bf2742795 100644 --- a/src/studio-home/data/thunks.js +++ b/src/studio-home/data/thunks.js @@ -3,13 +3,11 @@ import { getStudioHomeData, sendRequestForCourseCreator, handleCourseNotification, - getStudioHomeCourses, getStudioHomeLibraries, getStudioHomeCoursesV2, } from './api'; import { fetchStudioHomeDataSuccess, - fetchCourseDataSuccess, updateLoadingStatuses, updateSavingStatuses, fetchLibraryDataSuccess, @@ -20,7 +18,6 @@ function fetchStudioHomeData( search, hasHomeData, requestParams = {}, - isPaginationEnabled = false, shouldFetchCourses = true, ) { return async (dispatch) => { @@ -39,14 +36,8 @@ function fetchStudioHomeData( if (shouldFetchCourses) { dispatch(updateLoadingStatuses({ courseLoadingStatus: RequestStatus.IN_PROGRESS })); try { - if (isPaginationEnabled) { - const coursesData = await getStudioHomeCoursesV2(search || '', requestParams); - dispatch(fetchCourseDataSuccessV2(coursesData)); - } else { - const coursesData = await getStudioHomeCourses(search || ''); - dispatch(fetchCourseDataSuccess(coursesData)); - } - + const coursesData = await getStudioHomeCoursesV2(search || '', requestParams); + dispatch(fetchCourseDataSuccessV2(coursesData)); dispatch(updateLoadingStatuses({ courseLoadingStatus: RequestStatus.SUCCESSFUL })); } catch (error) { dispatch(updateLoadingStatuses({ courseLoadingStatus: RequestStatus.FAILED })); diff --git a/src/studio-home/factories/mockApiResponses.jsx b/src/studio-home/factories/mockApiResponses.jsx index 4f9ee03ed..8f313b29d 100644 --- a/src/studio-home/factories/mockApiResponses.jsx +++ b/src/studio-home/factories/mockApiResponses.jsx @@ -48,34 +48,6 @@ export const generateGetStudioHomeDataApiResponse = () => ({ allowToCreateNewOrg: false, }); -/** Mock for the deprecated /api/contentstore/v1/home/courses endpoint. Note this endpoint is NOT paginated. */ -export const generateGetStudioCoursesApiResponse = () => ({ - archivedCourses: /** @type {any[]} */([]), - courses: [ - { - courseKey: 'course-v1:HarvardX+123+2023', - displayName: 'Managing Risk in the Information Age', - lmsLink: '//localhost:18000/courses/course-v1:HarvardX+123+2023/jump_to/block-v1:HarvardX+123+2023+type@course+block@course', - number: '123', - org: 'HarvardX', - rerunLink: '/course_rerun/course-v1:HarvardX+123+2023', - run: '2023', - url: '/course/course-v1:HarvardX+123+2023', - }, - { - courseKey: 'org.0/course_0/Run_0', - displayName: 'Run 0', - lmsLink: null, - number: 'course_0', - org: 'org.0', - rerunLink: null, - run: 'Run_0', - url: null, - }, - ], - inProcessCourseActions: [], -}); - export const generateGetStudioCoursesApiResponseV2 = () => ({ count: 5, next: null, diff --git a/src/studio-home/hooks.jsx b/src/studio-home/hooks.jsx index 9a48fed09..0134111f2 100644 --- a/src/studio-home/hooks.jsx +++ b/src/studio-home/hooks.jsx @@ -1,7 +1,6 @@ import { useEffect, useState } from 'react'; import { useLocation } from 'react-router-dom'; import { useDispatch, useSelector } from 'react-redux'; -import { getConfig } from '@edx/frontend-platform'; import { RequestStatus } from '../data/constants'; import { COURSE_CREATOR_STATES } from '../constants'; @@ -19,7 +18,6 @@ import { updateSavingStatuses } from './data/slice'; const useStudioHome = () => { const location = useLocation(); const dispatch = useDispatch(); - const isPaginated = getConfig().ENABLE_HOME_PAGE_COURSE_API_V2; const studioHomeData = useSelector(getStudioHomeData); const studioHomeCoursesParams = useSelector(getStudioHomeCoursesParams); const { isFiltered } = studioHomeCoursesParams; @@ -35,18 +33,14 @@ const useStudioHome = () => { const isFailedLoadingPage = studioHomeLoadingStatus === RequestStatus.FAILED; useEffect(() => { - if (!isPaginated) { - dispatch(fetchStudioHomeData(location.search ?? '')); - setShowNewCourseContainer(false); - } + dispatch(fetchStudioHomeData(location.search ?? '')); + setShowNewCourseContainer(false); dispatch(fetchWaffleFlags()); }, [location.search]); useEffect(() => { - if (isPaginated) { - const firstPage = 1; - dispatch(fetchStudioHomeData(location.search ?? '', false, { page: firstPage, order: 'display_name' }, true)); - } + const firstPage = 1; + dispatch(fetchStudioHomeData(location.search ?? '', false, { page: firstPage, order: 'display_name' })); }, []); useEffect(() => { diff --git a/src/studio-home/messages.ts b/src/studio-home/messages.ts index 6a0e5ec7e..52978fbee 100644 --- a/src/studio-home/messages.ts +++ b/src/studio-home/messages.ts @@ -45,6 +45,10 @@ const messages = defineMessages({ id: 'course-authoring.studio-home.btn.re-run.text', defaultMessage: 'Re-run course', }, + btnDropDownText: { + id: 'course-authoring.studio-home.btn.dropdown.text', + defaultMessage: 'Course actions', + }, viewLiveBtnText: { id: 'course-authoring.studio-home.btn.view-live.text', defaultMessage: 'View live', diff --git a/src/studio-home/tabs-section/TabsSection.test.tsx b/src/studio-home/tabs-section/TabsSection.test.tsx index ef2e2ec9b..0537af17a 100644 --- a/src/studio-home/tabs-section/TabsSection.test.tsx +++ b/src/studio-home/tabs-section/TabsSection.test.tsx @@ -8,7 +8,6 @@ import TabsSection from '.'; import { initialState, generateGetStudioHomeDataApiResponse, - generateGetStudioCoursesApiResponse, generateGetStudioCoursesApiResponseV2, generateGetStudioHomeLibrariesApiResponse, } from '../factories/mockApiResponses'; @@ -28,7 +27,6 @@ const { studioShortName } = studioHomeMock; let axiosMock; let store; -const courseApiLink = `${getApiBaseUrl()}/api/contentstore/v1/home/courses`; const courseApiLinkV2 = `${getApiBaseUrl()}/api/contentstore/v2/home/courses`; const libraryApiLink = `${getApiBaseUrl()}/api/contentstore/v1/home/libraries`; @@ -37,7 +35,6 @@ const librariesBetaTabTitle = /Libraries Beta/; const tabSectionComponent = (overrideProps) => ( {}} isShowProcessing @@ -84,16 +81,6 @@ describe('', () => { it('should render all tabs correctly', async () => { const data: any = generateGetStudioHomeDataApiResponse(); - data.archivedCourses = [{ - courseKey: 'course-v1:MachineLearning+123+2023', - displayName: 'Machine Learning', - lmsLink: '//localhost:18000/courses/course-v1:MachineLearning+123+2023/jump_to/block-v1:MachineLearning+123+2023+type@course+block@course', - number: '123', - org: 'LSE', - rerunLink: '/course_rerun/course-v1:MachineLearning+123+2023', - run: '2023', - url: '/course/course-v1:MachineLearning+123+2023', - }]; render(); axiosMock.onGet(getStudioHomeApiUrl()).reply(200, data); @@ -104,8 +91,6 @@ describe('', () => { expect(screen.getByRole('tab', { name: librariesBetaTabTitle })).toBeInTheDocument(); expect(screen.getByRole('tab', { name: tabMessages.legacyLibrariesTabTitle.defaultMessage })).toBeInTheDocument(); - - expect(screen.getByRole('tab', { name: tabMessages.archivedTabTitle.defaultMessage })).toBeInTheDocument(); }); it('should render only 1 library tab when libraries-v2 disabled', async () => { @@ -143,9 +128,9 @@ describe('', () => { describe('course tab', () => { it('should render specific course details', async () => { render(); - const data = generateGetStudioCoursesApiResponse(); + const data = generateGetStudioCoursesApiResponseV2(); await axiosMock.onGet(getStudioHomeApiUrl()).reply(200, generateGetStudioHomeDataApiResponse()); - await axiosMock.onGet(courseApiLink).reply(200, data); + await axiosMock.onGet(courseApiLinkV2).reply(200, data); await executeThunk(fetchStudioHomeData(), store.dispatch); expect(screen.getByText(studioHomeMock.courses[0].displayName)).toBeVisible(); @@ -156,12 +141,12 @@ describe('', () => { }); it('should render default sections when courses are empty', async () => { - const data = generateGetStudioCoursesApiResponse(); - data.courses = []; + const data = generateGetStudioCoursesApiResponseV2(); + data.results.courses = []; render(); await axiosMock.onGet(getStudioHomeApiUrl()).reply(200, generateGetStudioHomeDataApiResponse()); - await axiosMock.onGet(courseApiLink).reply(200, data); + await axiosMock.onGet(courseApiLinkV2).reply(200, data); await executeThunk(fetchStudioHomeData(), store.dispatch); expect(screen.getByText(`Are you staff on an existing ${studioShortName} course?`)).toBeInTheDocument(); @@ -176,7 +161,7 @@ describe('', () => { it('should render course fetch failure alert', async () => { render(); await axiosMock.onGet(getStudioHomeApiUrl()).reply(200, generateGetStudioHomeDataApiResponse()); - await axiosMock.onGet(courseApiLink).reply(404); + await axiosMock.onGet(courseApiLinkV2).reply(404); await executeThunk(fetchStudioHomeData(), store.dispatch); expect(screen.getByText(tabMessages.courseTabErrorMessage.defaultMessage)).toBeVisible(); @@ -186,7 +171,7 @@ describe('', () => { render({ isPaginationCoursesEnabled: true }); await axiosMock.onGet(getStudioHomeApiUrl()).reply(200, generateGetStudioHomeDataApiResponse()); await axiosMock.onGet(courseApiLinkV2).reply(200, generateGetStudioCoursesApiResponseV2()); - await executeThunk(fetchStudioHomeData('', true, {}, true), store.dispatch); + await executeThunk(fetchStudioHomeData('', true, {}), store.dispatch); const data = generateGetStudioCoursesApiResponseV2(); const coursesLength = data.results.courses.length; const totalItems = data.count; @@ -279,47 +264,9 @@ describe('', () => { }); }); - describe('archived tab', () => { - it('should switch to Archived tab and render specific archived course details', async () => { - render(); - const data = generateGetStudioCoursesApiResponse(); - data.archivedCourses = studioHomeMock.archivedCourses; - await axiosMock.onGet(getStudioHomeApiUrl()).reply(200, generateGetStudioHomeDataApiResponse()); - await axiosMock.onGet(courseApiLink).reply(200, data); - await executeThunk(fetchStudioHomeData(), store.dispatch); - - const archivedTab = await screen.findByText(tabMessages.archivedTabTitle.defaultMessage); - fireEvent.click(archivedTab); - - expect(screen.getByText(studioHomeMock.archivedCourses[0].displayName)).toBeVisible(); - - expect(screen.getByText( - `${studioHomeMock.archivedCourses[0].org} / ${studioHomeMock.archivedCourses[0].number} / ${studioHomeMock.archivedCourses[0].run}`, - )).toBeVisible(); - }); - - it('should hide Archived tab when archived courses are empty', async () => { - const data = generateGetStudioCoursesApiResponse(); - data.archivedCourses = []; - - render(); - await axiosMock.onGet(getStudioHomeApiUrl()).reply(200, generateGetStudioHomeDataApiResponse()); - await axiosMock.onGet(courseApiLink).reply(200, data); - await executeThunk(fetchStudioHomeData(), store.dispatch); - - await screen.findByRole('tab', { name: tabMessages.coursesTabTitle.defaultMessage }); - - await screen.findByRole('tab', { name: librariesBetaTabTitle }); - - await screen.findByRole('tab', { name: tabMessages.legacyLibrariesTabTitle.defaultMessage }); - - expect(screen.queryByRole('tab', { name: tabMessages.archivedTabTitle.defaultMessage })).toBeNull(); - }); - }); - describe('library tab', () => { beforeEach(async () => { - await axiosMock.onGet(courseApiLink).reply(200, generateGetStudioCoursesApiResponse()); + await axiosMock.onGet(courseApiLinkV2).reply(200, generateGetStudioCoursesApiResponseV2()); }); it('should switch to Legacy Libraries tab and render specific v1 library details', async () => { render(); @@ -395,7 +342,7 @@ describe('', () => { expect(librariesTab).toHaveClass('active'); await screen.findByText('Showing 2 of 2'); - expect(screen.getByText('Page 1, Current Page, of 2')).toBeVisible(); + expect(screen.getAllByText('Page 1, Current Page, of 2')[0]).toBeVisible(); expect(screen.getByText(contentLibrariesListV2.results[0].title)).toBeVisible(); expect(screen.getByText( diff --git a/src/studio-home/tabs-section/archived-tab/index.jsx b/src/studio-home/tabs-section/archived-tab/index.jsx deleted file mode 100644 index 19404e2d7..000000000 --- a/src/studio-home/tabs-section/archived-tab/index.jsx +++ /dev/null @@ -1,78 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import { Icon, Row } from '@openedx/paragon'; -import { Error } from '@openedx/paragon/icons'; - -import { LoadingSpinner } from '../../../generic/Loading'; -import CardItem from '../../card-item'; -import { sortAlphabeticallyArray } from '../utils'; -import AlertMessage from '../../../generic/alert-message'; -import messages from '../messages'; - -const ArchivedTab = ({ - archivedCoursesData, - isLoading, - isFailed, - // injected - intl, -}) => { - if (isLoading) { - return ( - - - - ); - } - return ( - isFailed ? ( - - - {intl.formatMessage(messages.archiveTabErrorMessage)} - - )} - /> - ) : ( -
- {sortAlphabeticallyArray(archivedCoursesData).map(({ - courseKey, displayName, lmsLink, org, rerunLink, number, run, url, - }) => ( - - ))} -
- ) - ); -}; - -ArchivedTab.propTypes = { - archivedCoursesData: PropTypes.arrayOf( - PropTypes.shape({ - courseKey: PropTypes.string.isRequired, - displayName: PropTypes.string.isRequired, - lmsLink: PropTypes.string.isRequired, - number: PropTypes.string.isRequired, - org: PropTypes.string.isRequired, - rerunLink: PropTypes.string.isRequired, - run: PropTypes.string.isRequired, - url: PropTypes.string.isRequired, - }), - ).isRequired, - isLoading: PropTypes.bool.isRequired, - isFailed: PropTypes.bool.isRequired, - // injected - intl: intlShape.isRequired, -}; - -export default injectIntl(ArchivedTab); diff --git a/src/studio-home/tabs-section/courses-tab/courses-filters/index.jsx b/src/studio-home/tabs-section/courses-tab/courses-filters/index.jsx index fdea9411e..d3d6873ea 100644 --- a/src/studio-home/tabs-section/courses-tab/courses-filters/index.jsx +++ b/src/studio-home/tabs-section/courses-tab/courses-filters/index.jsx @@ -76,7 +76,7 @@ const CoursesFilters = ({ const handleSearchCourses = (searchValueDebounced) => { const valueFormatted = searchValueDebounced.trim(); const filterParams = { - search: valueFormatted.length > 0 ? valueFormatted : undefined, + search: valueFormatted.length > 0 ? valueFormatted : '', activeOnly, archivedOnly, order, 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 8c9f1d8dd..e871214f2 100644 --- a/src/studio-home/tabs-section/courses-tab/index.test.tsx +++ b/src/studio-home/tabs-section/courses-tab/index.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { initializeMockApp } from '@edx/frontend-platform'; -import { render, screen } from '@testing-library/react'; +import { fireEvent, render, screen } from '@testing-library/react'; import { IntlProvider } from '@edx/frontend-platform/i18n'; import { AppProvider } from '@edx/frontend-platform/react'; @@ -10,6 +10,7 @@ import { studioHomeMock } from '../../__mocks__'; import { initialState } from '../../factories/mockApiResponses'; import CoursesTab from '.'; +import { studioHomeCoursesRequestParamsDefault } from '../../data/slice'; const onClickNewCourse = jest.fn(); const isShowProcessing = false; @@ -17,7 +18,6 @@ const isLoading = false; const isFailed = false; const numPages = 1; const coursesCount = studioHomeMock.courses.length; -const isEnabledPagination = true; const showNewCourseContainer = true; const renderComponent = (overrideProps = {}, studioHomeState = {}) => { @@ -33,24 +33,26 @@ const renderComponent = (overrideProps = {}, studioHomeState = {}) => { // Initialize the store with the custom initial state const store = initializeStore(customInitialState); - return render( - - - - - , - ); + return { + ...render( + + + + + , + ), + store, + }; }; describe('', () => { @@ -77,18 +79,6 @@ describe('', () => { expect(coursesFilterSearchInput).toBeInTheDocument(); }); - it('should not render pagination and filter elements when isEnabledPagination is false', () => { - renderComponent({ isEnabledPagination: false }); - const coursesPaginationInfo = screen.queryByTestId('pagination-info'); - const coursesTypesMenu = screen.queryByTestId('dropdown-toggle-course-type-menu'); - const coursesOrderMenu = screen.queryByTestId('dropdown-toggle-courses-order-menu'); - const coursesFilterSearchInput = screen.queryByTestId('input-filter-courses-search'); - expect(coursesPaginationInfo).not.toBeInTheDocument(); - expect(coursesTypesMenu).not.toBeInTheDocument(); - expect(coursesOrderMenu).not.toBeInTheDocument(); - expect(coursesFilterSearchInput).not.toBeInTheDocument(); - }); - it('should render loading spinner when isLoading is true and isFiltered is false', () => { const props = { isLoading: true, coursesDataItems: [] }; const customStoreData = { studioHomeCoursesRequestParams: { currentPage: 1, isFiltered: true } }; @@ -141,4 +131,17 @@ describe('', () => { const collapsibleStateWithAction = screen.queryByTestId('collapsible-state-with-action'); expect(collapsibleStateWithAction).toBeInTheDocument(); }); + + it('should reset filters when in pressed the button to clean them', () => { + const props = { isLoading: false, coursesDataItems: [] }; + const customStoreData = { studioHomeCoursesRequestParams: { isFiltered: true } }; + const { store } = renderComponent(props, customStoreData); + const cleanFiltersButton = screen.getByRole('button', { name: /clear filters/i }); + expect(cleanFiltersButton).toBeInTheDocument(); + + fireEvent.click(cleanFiltersButton!); + + const state = store.getState(); + expect(state.studioHome.studioHomeCoursesRequestParams).toStrictEqual(studioHomeCoursesRequestParamsDefault); + }); }); diff --git a/src/studio-home/tabs-section/courses-tab/index.tsx b/src/studio-home/tabs-section/courses-tab/index.tsx index 43e62ec99..5d8aa50f1 100644 --- a/src/studio-home/tabs-section/courses-tab/index.tsx +++ b/src/studio-home/tabs-section/courses-tab/index.tsx @@ -13,7 +13,7 @@ import { Error } from '@openedx/paragon/icons'; import { COURSE_CREATOR_STATES } from '../../../constants'; import { getStudioHomeData, getStudioHomeCoursesParams } from '../../data/selectors'; -import { updateStudioHomeCoursesCustomParams } from '../../data/slice'; +import { resetStudioHomeCoursesCustomParams, updateStudioHomeCoursesCustomParams } from '../../data/slice'; import { fetchStudioHomeData } from '../../data/thunks'; import CardItem from '../../card-item'; import CollapsibleStateWithAction from '../../collapsible-state-with-action'; @@ -43,7 +43,6 @@ interface Props { isFailed: boolean; numPages: number; coursesCount: number; - isEnabledPagination?: boolean; } const CoursesTab: React.FC = ({ @@ -55,7 +54,6 @@ const CoursesTab: React.FC = ({ isFailed, numPages = 0, coursesCount = 0, - isEnabledPagination = false, }) => { const dispatch = useDispatch(); const intl = useIntl(); @@ -89,23 +87,13 @@ const CoursesTab: React.FC = ({ activeOnly, }; - dispatch(fetchStudioHomeData(locationValue, false, { page, ...customParams }, true)); + dispatch(fetchStudioHomeData(locationValue, false, { page, ...customParams })); dispatch(updateStudioHomeCoursesCustomParams({ currentPage: page, isFiltered: true })); }; const handleCleanFilters = () => { - const customParams = { - currentPage: 1, - search: undefined, - order: 'display_name', - isFiltered: true, - cleanFilters: true, - archivedOnly: undefined, - activeOnly: undefined, - }; - - dispatch(fetchStudioHomeData(locationValue, false, { page: 1, order: 'display_name' }, true)); - dispatch(updateStudioHomeCoursesCustomParams(customParams)); + dispatch(resetStudioHomeCoursesCustomParams()); + dispatch(fetchStudioHomeData(locationValue, false, { page: 1, order: 'display_name' })); }; const isNotFilteringCourses = !isFiltered && !isLoading; @@ -132,18 +120,16 @@ const CoursesTab: React.FC = ({ /> ) : (
- {isShowProcessing && !isEnabledPagination && } - {isEnabledPagination && ( -
- -

- {intl.formatMessage(messages.coursesPaginationInfo, { - length: coursesDataItems.length, - total: coursesCount, - })} -

-
- )} +
+ {isShowProcessing && } + +

+ {intl.formatMessage(messages.coursesPaginationInfo, { + length: coursesDataItems.length, + total: coursesCount, + })} +

+
{hasCourses ? ( <> {coursesDataItems.map( @@ -167,12 +153,11 @@ const CoursesTab: React.FC = ({ number={number} run={run} url={url} - isPaginated={isEnabledPagination} /> ), )} - {numPages > 1 && isEnabledPagination && ( + {numPages > 1 && ( { @@ -68,7 +66,7 @@ const TabsSection = ({ }, [pathname]); const { - courses, libraries, archivedCourses, + courses, libraries, numPages, coursesCount, } = useSelector(getStudioHomeData); const { @@ -99,27 +97,10 @@ const TabsSection = ({ isFailed={isFailedCoursesPage} numPages={numPages} coursesCount={coursesCount} - isEnabledPagination={isPaginationCoursesEnabled} /> , ); - if (archivedCourses?.length) { - tabs.push( - - - , - ); - } - if (librariesV2Enabled) { tabs.push( { if (tab === TABS_LIST.courses) { @@ -196,15 +177,10 @@ const TabsSection = ({ ); }; -TabsSection.defaultProps = { - isPaginationCoursesEnabled: false, -}; - TabsSection.propTypes = { showNewCourseContainer: PropTypes.bool.isRequired, onClickNewCourse: PropTypes.func.isRequired, isShowProcessing: PropTypes.bool.isRequired, - isPaginationCoursesEnabled: PropTypes.bool, librariesV1Enabled: PropTypes.bool, librariesV2Enabled: PropTypes.bool, };