From 641fc589a4edf3f329ce713175d35f545fb02ba7 Mon Sep 17 00:00:00 2001 From: Braden MacDonald Date: Fri, 22 Aug 2025 09:00:19 -0700 Subject: [PATCH] Add TypeScript types to the redux state (#2394) Adds some TypeScript types to the global redux state that's in `src/store.ts`. I've only added types for a few parts of the state but already it's caught quite a few bugs in the code, which I've tried to fix in this PR. --- src/{constants.js => constants.ts} | 2 +- src/data/constants.ts | 23 ++++---- src/data/{slice.js => slice.ts} | 7 ++- src/data/{thunks.js => thunks.ts} | 2 +- .../AnswerWidget/AnswerOption.test.tsx | 25 ++------- .../AnswerWidget/AnswersContainer.test.tsx | 12 +--- src/group-configurations/data/api.js | 1 + src/group-configurations/data/selectors.js | 4 -- src/group-configurations/data/selectors.ts | 8 +++ .../data/{slice.test.js => slice.test.ts} | 1 + .../data/{slice.js => slice.ts} | 2 +- .../data/{thunk.js => thunk.ts} | 10 ++-- src/{store.js => store.ts} | 41 +++++++++++++- src/studio-home/StudioHome.tsx | 2 +- src/studio-home/__mocks__/studioHomeMock.ts | 1 - src/studio-home/data/selectors.js | 4 -- src/studio-home/data/selectors.ts | 8 +++ src/studio-home/data/{slice.js => slice.ts} | 55 ++++++++++++++++--- ...kApiResponses.jsx => mockApiResponses.tsx} | 9 ++- .../tabs-section/courses-tab/index.test.tsx | 9 ++- .../tabs-section/courses-tab/index.tsx | 26 ++++----- src/testUtils.tsx | 4 +- 22 files changed, 158 insertions(+), 98 deletions(-) rename src/{constants.js => constants.ts} (99%) rename src/data/{slice.js => slice.ts} (76%) rename src/data/{thunks.js => thunks.ts} (93%) delete mode 100644 src/group-configurations/data/selectors.js create mode 100644 src/group-configurations/data/selectors.ts rename src/group-configurations/data/{slice.test.js => slice.test.ts} (99%) rename src/group-configurations/data/{slice.js => slice.ts} (98%) rename src/group-configurations/data/{thunk.js => thunk.ts} (94%) rename src/{store.js => store.ts} (68%) delete mode 100644 src/studio-home/data/selectors.js create mode 100644 src/studio-home/data/selectors.ts rename src/studio-home/data/{slice.js => slice.ts} (58%) rename src/studio-home/factories/{mockApiResponses.jsx => mockApiResponses.tsx} (93%) diff --git a/src/constants.js b/src/constants.ts similarity index 99% rename from src/constants.js rename to src/constants.ts index beb4a74d1..d5480c0cf 100644 --- a/src/constants.js +++ b/src/constants.ts @@ -43,7 +43,7 @@ export const COURSE_CREATOR_STATES = { granted: 'granted', denied: 'denied', disallowedForThisSite: 'disallowed_for_this_site', -}; +} as const; export const DECODED_ROUTES = { COURSE_UNIT: [ diff --git a/src/data/constants.ts b/src/data/constants.ts index 7aa59f063..2fc511d85 100644 --- a/src/data/constants.ts +++ b/src/data/constants.ts @@ -3,7 +3,7 @@ * @readonly * @enum {string} */ -export const RequestStatus = /** @type {const} */ ({ +export const RequestStatus = { IN_PROGRESS: 'in-progress', SUCCESSFUL: 'successful', FAILED: 'failed', @@ -13,7 +13,8 @@ export const RequestStatus = /** @type {const} */ ({ PARTIAL: 'partial', PARTIAL_FAILURE: 'partial failure', NOT_FOUND: 'not-found', -}); +} as const; +export type RequestStatusType = (typeof RequestStatus)[keyof typeof RequestStatus]; export const RequestFailureStatuses = [ RequestStatus.FAILED, @@ -25,39 +26,37 @@ export const RequestFailureStatuses = [ /** * Team sizes enum * @enum - * @type {{MIN: number, MAX: number, DEFAULT: number}} */ -export const TeamSizes = /** @type {const} */ ({ +export const TeamSizes = { DEFAULT: 5, MIN: 1, MAX: 500, -}); +} as const; /** * Group types enum * @enum - * @type {{PRIVATE_MANAGED: string, PUBLIC_MANAGED: string, OPEN: string}} */ -export const GroupTypes = /** @type {const} */ ({ +export const GroupTypes = { OPEN: 'open', PUBLIC_MANAGED: 'public_managed', PRIVATE_MANAGED: 'private_managed', OPEN_MANAGED: 'open_managed', -}); +} as const; -export const DivisionSchemes = /** @type {const} */ ({ +export const DivisionSchemes = { NONE: 'none', COHORT: 'cohort', -}); +} as const; -export const VisibilityTypes = /** @type {const} */ ({ +export const VisibilityTypes = { GATED: 'gated', LIVE: 'live', STAFF_ONLY: 'staff_only', HIDE_AFTER_DUE: 'hide_after_due', UNSCHEDULED: 'unscheduled', NEEDS_ATTENTION: 'needs_attention', -}); +} as const; export const TOTAL_LENGTH_KEY = 'total-length'; diff --git a/src/data/slice.js b/src/data/slice.ts similarity index 76% rename from src/data/slice.js rename to src/data/slice.ts index 749e53f2b..539c6bdd7 100644 --- a/src/data/slice.js +++ b/src/data/slice.ts @@ -1,14 +1,15 @@ /* eslint-disable no-param-reassign */ import { createSlice } from '@reduxjs/toolkit'; +import { type RequestStatusType } from './constants'; export const LOADED = 'LOADED'; const slice = createSlice({ name: 'courseDetail', initialState: { - courseId: null, - status: null, - canChangeProvider: null, + courseId: null as string | null, + status: null as RequestStatusType | null, + canChangeProviders: null as null | boolean, }, reducers: { updateStatus: (state, { payload }) => { diff --git a/src/data/thunks.js b/src/data/thunks.ts similarity index 93% rename from src/data/thunks.js rename to src/data/thunks.ts index 5e2e862bf..bbfc86340 100644 --- a/src/data/thunks.js +++ b/src/data/thunks.ts @@ -20,7 +20,7 @@ export function fetchCourseDetail(courseId) { canChangeProviders: getAuthenticatedUser().administrator || new Date(courseDetail.start) > new Date(), })); } catch (error) { - if (error.response && error.response.status === 404) { + if ((error as any).response && (error as any).response.status === 404) { dispatch(updateStatus({ courseId, status: RequestStatus.NOT_FOUND })); } else { dispatch(updateStatus({ courseId, status: RequestStatus.FAILED })); diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/AnswerOption.test.tsx b/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/AnswerOption.test.tsx index 02e2a2772..a97a6998c 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/AnswerOption.test.tsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/AnswerOption.test.tsx @@ -1,29 +1,12 @@ import React from 'react'; import { render, screen, initializeMocks } from '@src/testUtils'; +import { selectors } from '@src/editors/data/redux'; import AnswerOption from './AnswerOption'; import * as hooks from './hooks'; -import { selectors } from '../../../../../data/redux'; const { problem } = selectors; -const initialState = { - problem: { - problemType: 'multiplechoiceresponse', // No problem type selected by default - // ... other problem-related state - }, - app: { - images: {}, // No images loaded by default; use {} if it's an object keyed by IDs, or [] if it's a list - isLibrary: false, // Default to false; not in library context initially - learningContextId: 'course+org+run', // No context ID by default - blockId: 'block-id', // No block ID initially - // ... other app-related state - }, - // ... any other top-level state slices -}; - -export default initialState; - -jest.mock('../../../../../data/redux', () => ({ +jest.mock('@src/editors/data/redux', () => ({ __esModule: true, default: jest.fn(), selectors: { @@ -44,7 +27,7 @@ jest.mock('../../../../../data/redux', () => ({ }, })); -jest.mock('../../../../../sharedComponents/ExpandableTextArea', () => 'ExpandableTextArea'); +jest.mock('@src/editors/sharedComponents/ExpandableTextArea', () => 'ExpandableTextArea'); describe('AnswerOption', () => { const answerWithOnlyFeedback = { @@ -86,7 +69,7 @@ describe('AnswerOption', () => { isFeedbackVisible: false, toggleFeedback: jest.fn(), }); - initializeMocks({ initialState }); + initializeMocks(); }); test('renders correct option with feedback', () => { diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/AnswersContainer.test.tsx b/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/AnswersContainer.test.tsx index 5d03e976e..2f6bebdb9 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/AnswersContainer.test.tsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/AnswersContainer.test.tsx @@ -3,9 +3,9 @@ import React from 'react'; import { ProblemTypeKeys } from '@src/editors/data/constants/problem'; import { render, screen, fireEvent, initializeMocks, -} from '../../../../../../testUtils'; +} from '@src/testUtils'; +import { actions } from '@src/editors/data/redux'; import AnswersContainer from './AnswersContainer'; -import { actions } from '../../../../../data/redux'; const { useAnswerContainer } = require('./hooks'); @@ -15,12 +15,6 @@ const answers = [ { id: 'a2', isAnswerRange: false }, ]; -const initialState = { - problem: { - answers, - }, -}; - // Mock actions module jest.mock('../../../../../data/redux', () => ({ __esModule: true, @@ -57,7 +51,7 @@ describe('AnswersContainer', () => { }; beforeEach(() => { - initializeMocks({ initialState }); + initializeMocks(); jest.clearAllMocks(); }); diff --git a/src/group-configurations/data/api.js b/src/group-configurations/data/api.js index 2c5aceb60..ca1a5b8ec 100644 --- a/src/group-configurations/data/api.js +++ b/src/group-configurations/data/api.js @@ -1,3 +1,4 @@ +// @ts-check import { camelCaseObject, getConfig } from '@edx/frontend-platform'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; diff --git a/src/group-configurations/data/selectors.js b/src/group-configurations/data/selectors.js deleted file mode 100644 index 465d800d5..000000000 --- a/src/group-configurations/data/selectors.js +++ /dev/null @@ -1,4 +0,0 @@ -export const getGroupConfigurationsData = (state) => state.groupConfigurations.groupConfigurations; -export const getLoadingStatus = (state) => state.groupConfigurations.loadingStatus; -export const getSavingStatus = (state) => state.groupConfigurations.savingStatus; -export const getErrorMessage = (state) => state.groupConfigurations.errorMessage; diff --git a/src/group-configurations/data/selectors.ts b/src/group-configurations/data/selectors.ts new file mode 100644 index 000000000..29a128c92 --- /dev/null +++ b/src/group-configurations/data/selectors.ts @@ -0,0 +1,8 @@ +import { DeprecatedReduxState } from '@src/store'; + +export const getGroupConfigurationsData = (state: DeprecatedReduxState) => ( + state.groupConfigurations.groupConfigurations +); +export const getLoadingStatus = (state: DeprecatedReduxState) => state.groupConfigurations.loadingStatus; +export const getSavingStatus = (state: DeprecatedReduxState) => state.groupConfigurations.savingStatus; +export const getErrorMessage = (state: DeprecatedReduxState) => state.groupConfigurations.errorMessage; diff --git a/src/group-configurations/data/slice.test.js b/src/group-configurations/data/slice.test.ts similarity index 99% rename from src/group-configurations/data/slice.test.js rename to src/group-configurations/data/slice.test.ts index ebc6ef878..7686facaa 100644 --- a/src/group-configurations/data/slice.test.js +++ b/src/group-configurations/data/slice.test.ts @@ -63,6 +63,7 @@ describe('groupConfigurations slice', () => { it('should delete an experiment configuration with deleteExperimentConfigurationSuccess', () => { const initialStateWithExperiment = { savingStatus: '', + errorMessage: '', loadingStatus: RequestStatus.IN_PROGRESS, groupConfigurations: { allGroupConfigurations: [], diff --git a/src/group-configurations/data/slice.js b/src/group-configurations/data/slice.ts similarity index 98% rename from src/group-configurations/data/slice.js rename to src/group-configurations/data/slice.ts index 27e7ec8aa..8802e7c10 100644 --- a/src/group-configurations/data/slice.js +++ b/src/group-configurations/data/slice.ts @@ -9,7 +9,7 @@ const slice = createSlice({ savingStatus: '', errorMessage: '', loadingStatus: RequestStatus.IN_PROGRESS, - groupConfigurations: {}, + groupConfigurations: {} as Record, }, reducers: { fetchGroupConfigurations: (state, { payload }) => { diff --git a/src/group-configurations/data/thunk.js b/src/group-configurations/data/thunk.ts similarity index 94% rename from src/group-configurations/data/thunk.js rename to src/group-configurations/data/thunk.ts index 30ae35473..16cdef563 100644 --- a/src/group-configurations/data/thunk.js +++ b/src/group-configurations/data/thunk.ts @@ -1,10 +1,10 @@ -import { RequestStatus } from '../../data/constants'; -import { NOTIFICATION_MESSAGES } from '../../constants'; +import { RequestStatus } from '@src/data/constants'; +import { NOTIFICATION_MESSAGES } from '@src/constants'; import { hideProcessingNotification, showProcessingNotification, -} from '../../generic/processing-notification/data/slice'; -import { handleResponseErrors } from '../../generic/saving-error-alert'; +} from '@src/generic/processing-notification/data/slice'; +import { handleResponseErrors } from '@src/generic/saving-error-alert'; import { getGroupConfigurations, createContentGroup, @@ -33,7 +33,7 @@ export function fetchGroupConfigurationsQuery(courseId) { dispatch(fetchGroupConfigurations({ groupConfigurations })); dispatch(updateLoadingStatus({ status: RequestStatus.SUCCESSFUL })); } catch (error) { - if (error.response && error.response.status === 403) { + if ((error as any).response && (error as any).response.status === 403) { dispatch(updateLoadingStatus({ status: RequestStatus.DENIED })); } else { dispatch(updateLoadingStatus({ courseId, status: RequestStatus.FAILED })); diff --git a/src/store.js b/src/store.ts similarity index 68% rename from src/store.js rename to src/store.ts index e979d8591..003e141dc 100644 --- a/src/store.js +++ b/src/store.ts @@ -1,4 +1,4 @@ -import { configureStore } from '@reduxjs/toolkit'; +import { configureStore, Reducer } from '@reduxjs/toolkit'; // FIXME: because the 'live' plugin is using Redux, we have to hard-code a reference to it here. // If this app + the plugin were using React-query, there'd be no issues. @@ -30,8 +30,43 @@ import { reducer as textbooksReducer } from './textbooks/data/slice'; import { reducer as certificatesReducer } from './certificates/data/slice'; import { reducer as groupConfigurationsReducer } from './group-configurations/data/slice'; -export default function initializeStore(preloadedState = undefined) { - return configureStore({ +type InferState = ReducerType extends Reducer ? T : never; + +/** + * @deprecated The global Redux state for Authoring MFE, excluding editors. + * TODO: refactor each part to use React Context and React Query instead. + */ +export interface DeprecatedReduxState { + courseDetail: InferState; + customPages: Record; + discussions: Record; + assets: Record; + pagesAndResources: Record; + scheduleAndDetails: Record; + advancedSettings: Record; + studioHome: InferState; + models: Record; + live: Record; + courseTeam: Record; + courseUpdates: Record; + processingNotification: Record; + helpUrls: Record; + courseExport: Record; + courseOptimizer: Record; + generic: Record; + courseImport: Record; + videos: Record; + courseOutline: Record; + courseUnit: Record; + courseChecklist: Record; + accessibilityPage: Record; + certificates: Record; + groupConfigurations: InferState; + textbooks: Record; +} + +export default function initializeStore(preloadedState: Partial | undefined = undefined) { + return configureStore({ reducer: { courseDetail: courseDetailReducer, customPages: customPagesReducer, diff --git a/src/studio-home/StudioHome.tsx b/src/studio-home/StudioHome.tsx index 32915bf25..ea7a2d5ea 100644 --- a/src/studio-home/StudioHome.tsx +++ b/src/studio-home/StudioHome.tsx @@ -150,7 +150,7 @@ const StudioHome = () => { setShowNewCourseContainer(true)} - isShowProcessing={isShowProcessing && !isFiltered} + isShowProcessing={Boolean(isShowProcessing) && !isFiltered} librariesV1Enabled={librariesV1Enabled} librariesV2Enabled={librariesV2Enabled} /> diff --git a/src/studio-home/__mocks__/studioHomeMock.ts b/src/studio-home/__mocks__/studioHomeMock.ts index 28a70ab3a..c5c9a7005 100644 --- a/src/studio-home/__mocks__/studioHomeMock.ts +++ b/src/studio-home/__mocks__/studioHomeMock.ts @@ -68,7 +68,6 @@ export default { rerunCreatorStatus: true, showNewLibraryButton: true, showNewLibraryV2Button: true, - splitStudioHome: false, studioName: 'Studio', studioShortName: 'Studio', studioRequestEmail: 'request@email.com', diff --git a/src/studio-home/data/selectors.js b/src/studio-home/data/selectors.js deleted file mode 100644 index 186673eda..000000000 --- a/src/studio-home/data/selectors.js +++ /dev/null @@ -1,4 +0,0 @@ -export const getStudioHomeData = state => state.studioHome.studioHomeData; -export const getLoadingStatuses = (state) => state.studioHome.loadingStatuses; -export const getSavingStatuses = (state) => state.studioHome.savingStatuses; -export const getStudioHomeCoursesParams = (state) => state.studioHome.studioHomeCoursesRequestParams; diff --git a/src/studio-home/data/selectors.ts b/src/studio-home/data/selectors.ts new file mode 100644 index 000000000..125eb5dd1 --- /dev/null +++ b/src/studio-home/data/selectors.ts @@ -0,0 +1,8 @@ +import { type DeprecatedReduxState } from '@src/store'; + +export const getStudioHomeData = (state: DeprecatedReduxState) => state.studioHome.studioHomeData; +export const getLoadingStatuses = (state: DeprecatedReduxState) => state.studioHome.loadingStatuses; +export const getSavingStatuses = (state: DeprecatedReduxState) => state.studioHome.savingStatuses; +export const getStudioHomeCoursesParams = (state: DeprecatedReduxState) => ( + state.studioHome.studioHomeCoursesRequestParams +); diff --git a/src/studio-home/data/slice.js b/src/studio-home/data/slice.ts similarity index 58% rename from src/studio-home/data/slice.js rename to src/studio-home/data/slice.ts index feea8b082..eeb9297ff 100644 --- a/src/studio-home/data/slice.js +++ b/src/studio-home/data/slice.ts @@ -1,9 +1,20 @@ /* eslint-disable no-param-reassign */ import { createSlice } from '@reduxjs/toolkit'; +import { type COURSE_CREATOR_STATES } from '@src/constants'; -import { RequestStatus } from '../../data/constants'; +import { RequestStatus, type RequestStatusType } from '@src/data/constants'; -export const studioHomeCoursesRequestParamsDefault = { +export interface Params { + currentPage: number; + search?: string; + order?: string; + archivedOnly?: boolean; + activeOnly?: boolean; + isFiltered?: boolean; + cleanFilters?: boolean; +} + +export const studioHomeCoursesRequestParamsDefault: Params = { currentPage: 1, search: '', order: 'display_name', @@ -17,16 +28,42 @@ const slice = createSlice({ name: 'studioHome', initialState: { loadingStatuses: { - studioHomeLoadingStatus: RequestStatus.IN_PROGRESS, - courseNotificationLoadingStatus: RequestStatus.IN_PROGRESS, - courseLoadingStatus: RequestStatus.IN_PROGRESS, - libraryLoadingStatus: RequestStatus.IN_PROGRESS, + studioHomeLoadingStatus: RequestStatus.IN_PROGRESS as RequestStatusType, + courseNotificationLoadingStatus: RequestStatus.IN_PROGRESS as RequestStatusType, + courseLoadingStatus: RequestStatus.IN_PROGRESS as RequestStatusType, + libraryLoadingStatus: RequestStatus.IN_PROGRESS as RequestStatusType, }, savingStatuses: { - courseCreatorSavingStatus: '', - deleteNotificationSavingStatus: '', + courseCreatorSavingStatus: '' as RequestStatusType | '', + deleteNotificationSavingStatus: '' as RequestStatusType | '', + }, + studioHomeData: {} as { + allowCourseReruns?: boolean; + allowToCreateNewOrg?: boolean; + canCreateOrganizations?: boolean; // TODO: redundant with 'allowToCreateNewOrg' ??? + allowedOrganizations?: string[]; + allowedOrganizationsForLibraries?: string[]; + courseCreatorStatus?: (typeof COURSE_CREATOR_STATES)[keyof typeof COURSE_CREATOR_STATES]; + coursesCount?: any; + courses?: any; + archivedCourses?: any; + inProcessCourseActions?: any; + numPages?: any; + optimizationEnabled?: boolean; + libraries?: any; + librariesV1Enabled?: boolean; + librariesV2Enabled?: boolean; + platformName?: string; + rerunCreatorStatus?: boolean; + requestCourseCreatorUrl?: string; + showNewLibraryButton?: boolean; + showNewLibraryV2Button?: boolean; + studioRequestEmail?: string; + studioName?: string; + studioShortName?: string; + techSupportEmail?: string; + userIsActive?: boolean; }, - studioHomeData: {}, studioHomeCoursesRequestParams: studioHomeCoursesRequestParamsDefault, }, reducers: { diff --git a/src/studio-home/factories/mockApiResponses.jsx b/src/studio-home/factories/mockApiResponses.tsx similarity index 93% rename from src/studio-home/factories/mockApiResponses.jsx rename to src/studio-home/factories/mockApiResponses.tsx index 8f313b29d..f8199aca1 100644 --- a/src/studio-home/factories/mockApiResponses.jsx +++ b/src/studio-home/factories/mockApiResponses.tsx @@ -1,4 +1,5 @@ -import { RequestStatus } from '../../data/constants'; +import { type DeprecatedReduxState } from '@src/store'; +import { RequestStatus } from '@src/data/constants'; export const courseId = 'course'; @@ -19,10 +20,9 @@ export const initialState = { currentPage: 1, }, }, -}; +} satisfies Partial; -export const generateGetStudioHomeDataApiResponse = () => ({ - activeTab: 'courses', +export const generateGetStudioHomeDataApiResponse = (): DeprecatedReduxState['studioHome']['studioHomeData'] => ({ allowCourseReruns: true, allowedOrganizations: ['edx', 'org'], archivedCourses: [], @@ -38,7 +38,6 @@ export const generateGetStudioHomeDataApiResponse = () => ({ rerunCreatorStatus: true, showNewLibraryButton: true, showNewLibraryV2Button: true, - splitStudioHome: false, studioName: 'Studio', studioShortName: 'Studio', studioRequestEmail: 'request@email.com', 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 050c94c8a..8a069357a 100644 --- a/src/studio-home/tabs-section/courses-tab/index.test.tsx +++ b/src/studio-home/tabs-section/courses-tab/index.test.tsx @@ -5,12 +5,15 @@ import { screen, } from '@src/testUtils'; import { COURSE_CREATOR_STATES } from '@src/constants'; +import { type DeprecatedReduxState } from '@src/store'; import studioHomeMock from '@src/studio-home/__mocks__/studioHomeMock'; import { initialState } from '../../factories/mockApiResponses'; import CoursesTab from '.'; import { studioHomeCoursesRequestParamsDefault } from '../../data/slice'; +type StudioHomeState = DeprecatedReduxState['studioHome']; + const onClickNewCourse = jest.fn(); const isShowProcessing = false; const isLoading = false; @@ -19,9 +22,9 @@ const numPages = 1; const coursesCount = studioHomeMock.courses.length; const showNewCourseContainer = true; -const renderComponent = (overrideProps = {}, studioHomeState = {}) => { +const renderComponent = (overrideProps = {}, studioHomeState: Partial = {}) => { // Generate a custom initial state based on studioHomeCoursesRequestParams - const customInitialState: any = { // TODO: remove 'any' once our redux state has proper types + const customInitialState: Partial = { ...initialState, studioHome: { ...initialState.studioHome, @@ -118,7 +121,7 @@ describe('', () => { it('should reset filters when in pressed the button to clean them', () => { const props = { isLoading: false, coursesDataItems: [] }; - const customStoreData = { studioHomeCoursesRequestParams: { isFiltered: true } }; + const customStoreData = { studioHomeCoursesRequestParams: { currentPage: 1, isFiltered: true } }; const { store } = renderComponent(props, customStoreData); const cleanFiltersButton = screen.getByRole('button', { name: /clear filters/i }); expect(cleanFiltersButton).toBeInTheDocument(); diff --git a/src/studio-home/tabs-section/courses-tab/index.tsx b/src/studio-home/tabs-section/courses-tab/index.tsx index 5d8aa50f1..214b4e611 100644 --- a/src/studio-home/tabs-section/courses-tab/index.tsx +++ b/src/studio-home/tabs-section/courses-tab/index.tsx @@ -11,18 +11,18 @@ import { } from '@openedx/paragon'; import { Error } from '@openedx/paragon/icons'; -import { COURSE_CREATOR_STATES } from '../../../constants'; -import { getStudioHomeData, getStudioHomeCoursesParams } from '../../data/selectors'; -import { resetStudioHomeCoursesCustomParams, updateStudioHomeCoursesCustomParams } from '../../data/slice'; -import { fetchStudioHomeData } from '../../data/thunks'; -import CardItem from '../../card-item'; -import CollapsibleStateWithAction from '../../collapsible-state-with-action'; -import ContactAdministrator from './contact-administrator'; -import CoursesFilters from './courses-filters'; -import ProcessingCourses from '../../processing-courses'; -import { LoadingSpinner } from '../../../generic/Loading'; -import AlertMessage from '../../../generic/alert-message'; +import { COURSE_CREATOR_STATES } from '@src/constants'; +import { getStudioHomeData, getStudioHomeCoursesParams } from '@src/studio-home/data/selectors'; +import { resetStudioHomeCoursesCustomParams, updateStudioHomeCoursesCustomParams } from '@src/studio-home/data/slice'; +import { fetchStudioHomeData } from '@src/studio-home/data/thunks'; +import CardItem from '@src/studio-home/card-item'; +import CollapsibleStateWithAction from '@src/studio-home/collapsible-state-with-action'; +import ProcessingCourses from '@src/studio-home/processing-courses'; +import { LoadingSpinner } from '@src/generic/Loading'; +import AlertMessage from '@src/generic/alert-message'; import messages from '../messages'; +import CoursesFilters from './courses-filters'; +import ContactAdministrator from './contact-administrator'; import './index.scss'; interface Props { @@ -69,7 +69,7 @@ const CoursesTab: React.FC = ({ COURSE_CREATOR_STATES.denied, COURSE_CREATOR_STATES.pending, COURSE_CREATOR_STATES.unrequested, - ].includes(courseCreatorStatus); + ].includes(courseCreatorStatus as any); const locationValue = location.search ?? ''; const handlePageSelected = (page) => { @@ -191,7 +191,7 @@ const CoursesTab: React.FC = ({ )} {showCollapsible && ( )} diff --git a/src/testUtils.tsx b/src/testUtils.tsx index 3cb64361a..7270d3c0c 100644 --- a/src/testUtils.tsx +++ b/src/testUtils.tsx @@ -24,7 +24,7 @@ import { } from 'react-router-dom'; import { ToastContext, type ToastContextData } from './generic/toast-context'; -import initializeReduxStore from './store'; +import initializeReduxStore, { type DeprecatedReduxState } from './store'; import { getApiWaffleFlagsUrl } from './data/api'; /** @deprecated Use React Query and/or regular React Context instead of redux */ @@ -157,7 +157,7 @@ const defaultUser = { */ export function initializeMocks({ user = defaultUser, initialState = undefined }: { user?: { userId: number, username: string }, - initialState?: Record, // TODO: proper typing for our redux state + initialState?: Partial } = {}) { initializeMockApp({ authenticatedUser: user,