Skip to content

Commit 641fc58

Browse files
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.
1 parent 0c88fd6 commit 641fc58

File tree

22 files changed

+158
-98
lines changed

22 files changed

+158
-98
lines changed

src/constants.js renamed to src/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export const COURSE_CREATOR_STATES = {
4343
granted: 'granted',
4444
denied: 'denied',
4545
disallowedForThisSite: 'disallowed_for_this_site',
46-
};
46+
} as const;
4747

4848
export const DECODED_ROUTES = {
4949
COURSE_UNIT: [

src/data/constants.ts

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* @readonly
44
* @enum {string}
55
*/
6-
export const RequestStatus = /** @type {const} */ ({
6+
export const RequestStatus = {
77
IN_PROGRESS: 'in-progress',
88
SUCCESSFUL: 'successful',
99
FAILED: 'failed',
@@ -13,7 +13,8 @@ export const RequestStatus = /** @type {const} */ ({
1313
PARTIAL: 'partial',
1414
PARTIAL_FAILURE: 'partial failure',
1515
NOT_FOUND: 'not-found',
16-
});
16+
} as const;
17+
export type RequestStatusType = (typeof RequestStatus)[keyof typeof RequestStatus];
1718

1819
export const RequestFailureStatuses = [
1920
RequestStatus.FAILED,
@@ -25,39 +26,37 @@ export const RequestFailureStatuses = [
2526
/**
2627
* Team sizes enum
2728
* @enum
28-
* @type {{MIN: number, MAX: number, DEFAULT: number}}
2929
*/
30-
export const TeamSizes = /** @type {const} */ ({
30+
export const TeamSizes = {
3131
DEFAULT: 5,
3232
MIN: 1,
3333
MAX: 500,
34-
});
34+
} as const;
3535

3636
/**
3737
* Group types enum
3838
* @enum
39-
* @type {{PRIVATE_MANAGED: string, PUBLIC_MANAGED: string, OPEN: string}}
4039
*/
41-
export const GroupTypes = /** @type {const} */ ({
40+
export const GroupTypes = {
4241
OPEN: 'open',
4342
PUBLIC_MANAGED: 'public_managed',
4443
PRIVATE_MANAGED: 'private_managed',
4544
OPEN_MANAGED: 'open_managed',
46-
});
45+
} as const;
4746

48-
export const DivisionSchemes = /** @type {const} */ ({
47+
export const DivisionSchemes = {
4948
NONE: 'none',
5049
COHORT: 'cohort',
51-
});
50+
} as const;
5251

53-
export const VisibilityTypes = /** @type {const} */ ({
52+
export const VisibilityTypes = {
5453
GATED: 'gated',
5554
LIVE: 'live',
5655
STAFF_ONLY: 'staff_only',
5756
HIDE_AFTER_DUE: 'hide_after_due',
5857
UNSCHEDULED: 'unscheduled',
5958
NEEDS_ATTENTION: 'needs_attention',
60-
});
59+
} as const;
6160

6261
export const TOTAL_LENGTH_KEY = 'total-length';
6362

src/data/slice.js renamed to src/data/slice.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
/* eslint-disable no-param-reassign */
22
import { createSlice } from '@reduxjs/toolkit';
3+
import { type RequestStatusType } from './constants';
34

45
export const LOADED = 'LOADED';
56

67
const slice = createSlice({
78
name: 'courseDetail',
89
initialState: {
9-
courseId: null,
10-
status: null,
11-
canChangeProvider: null,
10+
courseId: null as string | null,
11+
status: null as RequestStatusType | null,
12+
canChangeProviders: null as null | boolean,
1213
},
1314
reducers: {
1415
updateStatus: (state, { payload }) => {

src/data/thunks.js renamed to src/data/thunks.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export function fetchCourseDetail(courseId) {
2020
canChangeProviders: getAuthenticatedUser().administrator || new Date(courseDetail.start) > new Date(),
2121
}));
2222
} catch (error) {
23-
if (error.response && error.response.status === 404) {
23+
if ((error as any).response && (error as any).response.status === 404) {
2424
dispatch(updateStatus({ courseId, status: RequestStatus.NOT_FOUND }));
2525
} else {
2626
dispatch(updateStatus({ courseId, status: RequestStatus.FAILED }));

src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/AnswerOption.test.tsx

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,12 @@
11
import React from 'react';
22
import { render, screen, initializeMocks } from '@src/testUtils';
3+
import { selectors } from '@src/editors/data/redux';
34
import AnswerOption from './AnswerOption';
45
import * as hooks from './hooks';
5-
import { selectors } from '../../../../../data/redux';
66

77
const { problem } = selectors;
88

9-
const initialState = {
10-
problem: {
11-
problemType: 'multiplechoiceresponse', // No problem type selected by default
12-
// ... other problem-related state
13-
},
14-
app: {
15-
images: {}, // No images loaded by default; use {} if it's an object keyed by IDs, or [] if it's a list
16-
isLibrary: false, // Default to false; not in library context initially
17-
learningContextId: 'course+org+run', // No context ID by default
18-
blockId: 'block-id', // No block ID initially
19-
// ... other app-related state
20-
},
21-
// ... any other top-level state slices
22-
};
23-
24-
export default initialState;
25-
26-
jest.mock('../../../../../data/redux', () => ({
9+
jest.mock('@src/editors/data/redux', () => ({
2710
__esModule: true,
2811
default: jest.fn(),
2912
selectors: {
@@ -44,7 +27,7 @@ jest.mock('../../../../../data/redux', () => ({
4427
},
4528
}));
4629

47-
jest.mock('../../../../../sharedComponents/ExpandableTextArea', () => 'ExpandableTextArea');
30+
jest.mock('@src/editors/sharedComponents/ExpandableTextArea', () => 'ExpandableTextArea');
4831

4932
describe('AnswerOption', () => {
5033
const answerWithOnlyFeedback = {
@@ -86,7 +69,7 @@ describe('AnswerOption', () => {
8669
isFeedbackVisible: false,
8770
toggleFeedback: jest.fn(),
8871
});
89-
initializeMocks({ initialState });
72+
initializeMocks();
9073
});
9174

9275
test('renders correct option with feedback', () => {

src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/AnswersContainer.test.tsx

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import React from 'react';
33
import { ProblemTypeKeys } from '@src/editors/data/constants/problem';
44
import {
55
render, screen, fireEvent, initializeMocks,
6-
} from '../../../../../../testUtils';
6+
} from '@src/testUtils';
7+
import { actions } from '@src/editors/data/redux';
78
import AnswersContainer from './AnswersContainer';
8-
import { actions } from '../../../../../data/redux';
99

1010
const { useAnswerContainer } = require('./hooks');
1111

@@ -15,12 +15,6 @@ const answers = [
1515
{ id: 'a2', isAnswerRange: false },
1616
];
1717

18-
const initialState = {
19-
problem: {
20-
answers,
21-
},
22-
};
23-
2418
// Mock actions module
2519
jest.mock('../../../../../data/redux', () => ({
2620
__esModule: true,
@@ -57,7 +51,7 @@ describe('AnswersContainer', () => {
5751
};
5852

5953
beforeEach(() => {
60-
initializeMocks({ initialState });
54+
initializeMocks();
6155
jest.clearAllMocks();
6256
});
6357

src/group-configurations/data/api.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// @ts-check
12
import { camelCaseObject, getConfig } from '@edx/frontend-platform';
23
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
34

src/group-configurations/data/selectors.js

Lines changed: 0 additions & 4 deletions
This file was deleted.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { DeprecatedReduxState } from '@src/store';
2+
3+
export const getGroupConfigurationsData = (state: DeprecatedReduxState) => (
4+
state.groupConfigurations.groupConfigurations
5+
);
6+
export const getLoadingStatus = (state: DeprecatedReduxState) => state.groupConfigurations.loadingStatus;
7+
export const getSavingStatus = (state: DeprecatedReduxState) => state.groupConfigurations.savingStatus;
8+
export const getErrorMessage = (state: DeprecatedReduxState) => state.groupConfigurations.errorMessage;

src/group-configurations/data/slice.test.js renamed to src/group-configurations/data/slice.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ describe('groupConfigurations slice', () => {
6363
it('should delete an experiment configuration with deleteExperimentConfigurationSuccess', () => {
6464
const initialStateWithExperiment = {
6565
savingStatus: '',
66+
errorMessage: '',
6667
loadingStatus: RequestStatus.IN_PROGRESS,
6768
groupConfigurations: {
6869
allGroupConfigurations: [],

0 commit comments

Comments
 (0)