Skip to content

Commit 591444d

Browse files
test: Clean up editor tests (#2343)
* test: improve the editorRender helper * fix: redux state bug introduced in #2326 * test: add note for future reference about accessing the editor redux store
1 parent 2f9566c commit 591444d

File tree

11 files changed

+80
-126
lines changed

11 files changed

+80
-126
lines changed

src/editors/VideoSelector.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from 'react';
22
import * as reactRedux from 'react-redux';
33
import * as hooks from './hooks';
44
import VideoSelector from './VideoSelector';
5-
import editorRender from './editorTestRender';
5+
import { editorRender } from './editorTestRender';
66
import { initializeMocks, screen } from '../testUtils';
77

88
const defaultProps = {

src/editors/containers/ProblemEditor/components/EditProblemView/index.jsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,7 @@ const EditProblemView = ({ returnFunction }) => {
3737
const lmsEndpointUrl = useSelector(selectors.app.lmsEndpointUrl);
3838
const returnUrl = useSelector(selectors.app.returnUrl);
3939
const problemType = useSelector(selectors.problem.problemType);
40-
const problemState = useSelector(selectors.problem.completeState)?.completeState;
41-
const problemStateWithoutComplete = useSelector(selectors.problem.completeState);
40+
const problemState = useSelector(selectors.problem.completeState);
4241
const isDirty = useSelector(selectors.problem.isDirty);
4342

4443
const isMarkdownEditorEnabledSelector = useSelector(selectors.problem.isMarkdownEditorEnabled);
@@ -60,7 +59,7 @@ const EditProblemView = ({ returnFunction }) => {
6059
return (
6160
<EditorContainer
6261
getContent={() => getContent({
63-
problemState: problemStateWithoutComplete,
62+
problemState,
6463
openSaveWarningModal,
6564
isAdvancedProblemType,
6665
isMarkdownEditorEnabled,

src/editors/containers/ProblemEditor/components/EditProblemView/index.test.jsx renamed to src/editors/containers/ProblemEditor/components/EditProblemView/index.test.tsx

Lines changed: 13 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import React from 'react';
2-
import { screen, fireEvent, initializeMocks } from '../../../../../testUtils';
3-
import editorRender from '../../../../editorTestRender';
2+
import { screen, fireEvent, initializeMocks } from '@src/testUtils';
3+
import { editorRender, type PartialEditorState } from '@src/editors/editorTestRender';
4+
import { ProblemTypeKeys } from '@src/editors/data/constants/problem';
45
import EditProblemView from './index';
5-
import { initializeStore } from '../../../../data/redux';
6-
import { ProblemTypeKeys } from '../../../../data/constants/problem';
76

87
const { saveBlock } = require('../../../../hooks');
98
const { saveWarningModalToggle } = require('./hooks');
@@ -41,20 +40,16 @@ jest.mock('./hooks', () => ({
4140
}));
4241

4342
// 🗂️ Initial state based on baseProps
44-
const initialState = {
43+
const initialState: PartialEditorState = {
4544
app: {
46-
analytics: {},
4745
lmsEndpointUrl: null,
48-
returnUrl: '/return',
4946
isMarkdownEditorEnabledForCourse: false,
5047
},
5148
problem: {
52-
problemType: 'standard',
49+
problemType: null,
5350
isMarkdownEditorEnabled: false,
54-
completeState: {
55-
rawOLX: '<problem></problem>',
56-
rawMarkdown: '## Problem',
57-
},
51+
rawOLX: '<problem></problem>',
52+
rawMarkdown: '## Problem',
5853
isDirty: false,
5954
},
6055
};
@@ -63,11 +58,11 @@ describe('EditProblemView', () => {
6358
const returnFunction = jest.fn();
6459

6560
beforeEach(() => {
66-
initializeMocks({ initialState, initializeStore });
61+
initializeMocks();
6762
});
6863

6964
it('renders standard problem widgets', () => {
70-
editorRender(<EditProblemView returnFunction={returnFunction} />);
65+
editorRender(<EditProblemView returnFunction={returnFunction} />, { initialState });
7166
expect(screen.getByText('QuestionWidget')).toBeInTheDocument();
7267
expect(screen.getByText('ExplanationWidget')).toBeInTheDocument();
7368
expect(screen.getByText('AnswerWidget')).toBeInTheDocument();
@@ -77,14 +72,6 @@ describe('EditProblemView', () => {
7772
});
7873

7974
it('renders advanced problem with RawEditor', () => {
80-
initializeMocks({
81-
initializeStore,
82-
...initialState,
83-
problem: {
84-
...initialState.problem,
85-
problemType: ProblemTypeKeys.ADVANCED,
86-
},
87-
});
8875
editorRender(<EditProblemView returnFunction={returnFunction} />, {
8976
initialState: {
9077
...initialState,
@@ -99,27 +86,19 @@ describe('EditProblemView', () => {
9986
});
10087

10188
it('renders markdown editor with RawEditor', () => {
102-
const modifiedInitialState = {
89+
const modifiedInitialState: PartialEditorState = {
10390
app: {
104-
analytics: {},
10591
lmsEndpointUrl: null,
106-
returnUrl: '/return',
10792
isMarkdownEditorEnabledForCourse: true,
10893
},
10994
problem: {
110-
problemType: 'standard',
95+
problemType: null,
11196
isMarkdownEditorEnabled: true,
112-
completeState: {
113-
rawOLX: '<problem></problem>',
114-
rawMarkdown: '## Problem',
115-
},
97+
rawOLX: '<problem></problem>',
98+
rawMarkdown: '## Problem',
11699
isDirty: false,
117100
},
118101
};
119-
initializeMocks({
120-
initializeStore,
121-
initialState: modifiedInitialState,
122-
});
123102
editorRender(<EditProblemView returnFunction={returnFunction} />, { initialState: modifiedInitialState });
124103
expect(screen.getByText('markdown:## Problem')).toBeInTheDocument();
125104
});

src/editors/containers/ProblemEditor/components/SelectTypeModal/SelectTypeWrapper/index.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import React from 'react';
22
import {
33
screen, fireEvent, initializeMocks,
4-
} from '../../../../../../testUtils';
5-
import editorRender from '../../../../../editorTestRender';
4+
} from '@src/testUtils';
5+
import { editorRender } from '@src/editors/editorTestRender';
66
import SelectTypeWrapper from './index';
77
import * as hooks from '../hooks';
88

src/editors/containers/ProblemEditor/components/SelectTypeModal/index.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import {
22
fireEvent,
33
screen,
44
initializeMocks,
5-
} from '../../../../../testUtils';
6-
import editorRender from '../../../../editorTestRender';
5+
} from '@src/testUtils';
6+
import { editorRender } from '@src/editors/editorTestRender';
77
import * as hooks from './hooks';
88
import SelectTypeModal from '.';
99

src/editors/containers/ProblemEditor/index.test.tsx

Lines changed: 39 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,22 @@
11
import React from 'react';
2-
import { screen, initializeMocks } from '../../../testUtils';
3-
import editorRender from '../../editorTestRender';
4-
import { initializeStore } from '../../data/redux';
2+
import { screen, initializeMocks } from '@src/testUtils';
3+
import { editorRender, type PartialEditorState } from '@src/editors/editorTestRender';
4+
import { thunkActions } from '@src/editors/data/redux';
5+
56
import ProblemEditor from './index';
67
import messages from './messages';
8+
79
// Mock child components for easy selection
810
jest.mock('./components/SelectTypeModal', () => function mockSelectTypeModal(props: any) {
911
return <div>SelectTypeModal {props.onClose && 'withOnClose'}</div>;
1012
});
1113
jest.mock('./components/EditProblemView', () => function mockEditProblemView(props: any) {
1214
return <div>EditProblemView {props.onClose && 'withOnClose'} {props.returnFunction && 'withReturnFunction'}</div>;
1315
});
14-
15-
jest.mock('../../data/redux', () => ({
16-
__esModule: true,
17-
...jest.requireActual('../../data/redux'),
18-
thunkActions: {
19-
...jest.requireActual('../../data/redux').thunkActions,
20-
problem: {
21-
...jest.requireActual('../../data/redux').thunkActions.problem,
22-
initializeProblem: jest.fn(() => () => Promise.resolve()),
23-
},
24-
},
25-
}));
16+
// Mock the initializeProblem method:
17+
jest.spyOn(thunkActions.problem, 'initializeProblem').mockImplementation(
18+
() => () => Promise.resolve(),
19+
);
2620

2721
describe('ProblemEditor', () => {
2822
const baseProps = {
@@ -31,76 +25,67 @@ describe('ProblemEditor', () => {
3125
};
3226

3327
beforeEach(() => {
34-
jest.clearAllMocks();
28+
initializeMocks();
3529
});
3630

3731
it('renders Spinner when blockFinished is false', () => {
38-
const initialState = {
39-
app: { shouldCreateBlock: false },
40-
problem: { problemType: 'standard' },
32+
const initialState: PartialEditorState = {
33+
app: {
34+
blockId: 'problem1',
35+
blockType: 'problem',
36+
},
37+
problem: { problemType: 'multiplechoiceresponse' },
4138
requests: {
42-
fetchBlock: { status: 'completed' },
43-
fetchAdvancedSettings: { status: 'pending' },
44-
39+
fetchBlock: { status: 'pending' },
40+
fetchAdvancedSettings: { status: 'completed' },
4541
},
4642
};
47-
initializeMocks({
48-
initializeStore,
49-
initialState,
50-
});
5143

52-
const { container } = editorRender(<ProblemEditor {...baseProps} />, { initialState });
53-
const spinner = container.querySelector('.pgn__spinner');
54-
expect(spinner).toBeInTheDocument();
55-
expect(spinner).toHaveAttribute('screenreadertext', 'Loading Problem Editor');
44+
editorRender(<ProblemEditor {...baseProps} />, { initialState });
45+
const spinnerText = screen.getByText('Loading Problem Editor');
46+
expect(spinnerText.parentElement).toHaveClass('pgn__spinner');
5647
});
5748

5849
it('renders Spinner when advancedSettingsFinished is false', () => {
59-
const initialState = {
60-
app: { shouldCreateBlock: false },
50+
const initialState: PartialEditorState = {
51+
app: {
52+
blockId: 'problem1',
53+
blockType: 'problem',
54+
},
6155
problem: { problemType: null },
6256
requests: {
6357
fetchBlock: { status: 'pending' },
6458
fetchAdvancedSettings: { status: 'completed' },
6559
},
6660
};
67-
initializeMocks({
68-
initializeStore,
69-
initialState,
70-
});
7161

72-
const { container } = editorRender(<ProblemEditor {...baseProps} />, { initialState });
73-
const spinner = container.querySelector('.pgn__spinner');
74-
expect(spinner).toBeInTheDocument();
75-
expect(spinner).toHaveAttribute('screenreadertext', 'Loading Problem Editor');
62+
editorRender(<ProblemEditor {...baseProps} />, { initialState });
63+
const spinnerText = screen.getByText('Loading Problem Editor');
64+
expect(spinnerText.parentElement).toHaveClass('pgn__spinner');
7665
});
7766

7867
it('renders block failed message when blockFailed is true', () => {
79-
const initialState = {
68+
const initialState: PartialEditorState = {
8069
app: {
81-
blockId: '',
82-
blockType: true,
70+
blockId: 'problem1',
71+
blockType: 'problem',
8372
},
84-
problem: { problemType: 'standard' },
73+
problem: { problemType: 'multiplechoiceresponse' },
8574
requests: {
8675
fetchBlock: { status: 'failed' },
8776
fetchAdvancedSettings: { status: 'completed' },
8877
},
8978
};
9079

91-
initializeMocks({
92-
initializeStore,
93-
initialState,
94-
});
9580
editorRender(<ProblemEditor {...baseProps} />, { initialState });
9681
expect(screen.getByText(messages.blockFailed.defaultMessage)).toBeInTheDocument();
9782
});
9883

9984
it('renders SelectTypeModal when problemType is null', () => {
100-
const initialState = {
85+
const initialState: PartialEditorState = {
10186
app: {
102-
blockId: '',
103-
blockType: true,
87+
blockId: 'problem1',
88+
blockType: 'problem',
10489
},
10590
problem: { problemType: null },
10691
requests: {
@@ -109,19 +94,15 @@ describe('ProblemEditor', () => {
10994

11095
},
11196
};
112-
initializeMocks({
113-
initializeStore,
114-
initialState,
115-
});
11697
editorRender(<ProblemEditor {...baseProps} />, { initialState });
11798
expect(screen.getByText(/SelectTypeModal/)).toBeInTheDocument();
11899
});
119100

120101
it('renders EditProblemView when problemType is not null', () => {
121-
const initialState = {
102+
const initialState: PartialEditorState = {
122103
app: {
123-
blockId: '',
124-
blockType: true,
104+
blockId: 'problem1',
105+
blockType: 'problem',
125106
},
126107
problem: { problemType: 'advanced' },
127108
requests: {
@@ -130,10 +111,6 @@ describe('ProblemEditor', () => {
130111
},
131112

132113
};
133-
initializeMocks({
134-
initializeStore,
135-
initialState,
136-
});
137114

138115
editorRender(<ProblemEditor {...baseProps} />, { initialState });
139116
expect(screen.getByText(/EditProblemView/)).toBeInTheDocument();

src/editors/containers/ProblemEditor/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ const ProblemEditor: React.FC<Props> = ({
4242
<Spinner
4343
animation="border"
4444
className="m-3"
45-
screenreadertext="Loading Problem Editor"
45+
screenReaderText="Loading Problem Editor"
4646
/>
4747
</div>
4848
);

src/editors/data/store.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export const createStore = () => {
1010

1111
const middleware = [thunkMiddleware, loggerMiddleware];
1212

13-
const store = redux.createStore<EditorState, any, any, any>(
13+
const store: redux.Store<EditorState> = redux.createStore<EditorState, any, any, any>(
1414
reducer as any,
1515
composeWithDevToolsLogOnlyInProduction(redux.applyMiddleware(...middleware)),
1616
);

src/editors/editorTestRender.jsx renamed to src/editors/editorTestRender.tsx

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,36 @@
11
import React from 'react';
22
import { Provider } from 'react-redux';
3-
import { render as baseRender } from '../testUtils';
3+
import { render as baseRender, WrapperOptions } from '../testUtils';
44
import { EditorContextProvider } from './EditorContext';
5-
import { initializeStore } from './data/redux'; // adjust path if needed
5+
import { type EditorState, initializeStore } from './data/redux'; // adjust path if needed
6+
7+
type RecursivePartial<T> = {
8+
[P in keyof T]?: RecursivePartial<T[P]>;
9+
};
10+
11+
export type PartialEditorState = RecursivePartial<EditorState>;
612

713
/**
814
* Custom render function for testing React components with the editor context and Redux store.
915
*
1016
* Wraps the provided UI in both the EditorContextProvider and Redux Provider,
1117
* ensuring that components under test have access to the necessary context and store.
1218
*
13-
* @param {React.ReactElement} ui - The React element to render.
14-
* @param {object} [options] - Optional parameters.
15-
* @param {object} [options.initialState] - Optional initial state for the store.
16-
* @param {string} [options.learningContextId] - Optional learning context ID.
17-
* @returns {RenderResult} The result of the render, as returned by RTL render.
1819
*/
19-
const editorRender = (
20-
ui,
20+
export const editorRender = (
21+
ui: React.ReactElement,
2122
{
2223
initialState = {},
2324
learningContextId = 'course-v1:Org+COURSE+RUN',
24-
} = {},
25+
...options
26+
}: Omit<WrapperOptions, 'extraWrapper'> & { initialState?: PartialEditorState, learningContextId?: string } = {},
2527
) => {
26-
const store = initializeStore(initialState);
28+
// We might need a way for the test cases to access this store directly. In that case we could allow either an
29+
// initialState parameter OR an editorStore parameter.
30+
const store = initializeStore(initialState as any);
2731

2832
return baseRender(ui, {
33+
...options,
2934
extraWrapper: ({ children }) => (
3035
<EditorContextProvider learningContextId={learningContextId}>
3136
<Provider store={store}>
@@ -35,5 +40,3 @@ const editorRender = (
3540
),
3641
});
3742
};
38-
39-
export default editorRender;

0 commit comments

Comments
 (0)