From 8e51de4ce7bf852bef20925da5fe8f178c4a60bf Mon Sep 17 00:00:00 2001
From: alangsto <46360176+alangsto@users.noreply.github.com>
Date: Mon, 5 Feb 2024 09:47:52 -0500
Subject: [PATCH] feat: refactor non instruction components (#132)
---
src/context.jsx | 4 -
src/core/ExamStateProvider.jsx | 35 ----
src/core/OuterExamTimer.jsx | 13 +-
src/core/OuterExamTimer.test.jsx | 13 +-
src/core/SequenceExamWrapper.jsx | 5 +-
src/data/index.js | 2 +-
src/data/store.js | 8 -
src/exam/Exam.jsx | 21 +-
src/exam/ExamAPIError.jsx | 7 +-
src/exam/ExamAPIError.test.jsx | 36 ++--
src/exam/ExamWrapper.jsx | 18 +-
src/exam/ExamWrapper.test.jsx | 180 +++++++-----------
src/hocs.jsx | 12 --
src/index.jsx | 1 -
src/instructions/Instructions.test.jsx | 14 +-
.../ProctoredExamInstructions.test.jsx | 19 +-
src/timer/CountDownTimer.test.jsx | 11 --
17 files changed, 147 insertions(+), 252 deletions(-)
delete mode 100644 src/context.jsx
delete mode 100644 src/core/ExamStateProvider.jsx
delete mode 100644 src/data/store.js
delete mode 100644 src/hocs.jsx
diff --git a/src/context.jsx b/src/context.jsx
deleted file mode 100644
index 3005f5a7..00000000
--- a/src/context.jsx
+++ /dev/null
@@ -1,4 +0,0 @@
-import React from 'react';
-
-const ExamStateContext = React.createContext({});
-export default ExamStateContext;
diff --git a/src/core/ExamStateProvider.jsx b/src/core/ExamStateProvider.jsx
deleted file mode 100644
index 174a2b61..00000000
--- a/src/core/ExamStateProvider.jsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import React, { useMemo } from 'react';
-import { withExamStore } from '../hocs';
-import * as dispatchActions from '../data/thunks';
-import ExamStateContext from '../context';
-import { IS_STARTED_STATUS } from '../constants';
-
-/**
- * Make exam state available as a context for all library components.
- * @param children - sequence content
- * @param state - exam state params and actions
- * @returns {JSX.Element}
- */
-
-// eslint-disable-next-line react/prop-types
-const StateProvider = ({ children, ...state }) => {
- const contextValue = useMemo(() => ({
- ...state,
- showTimer: !!(state.activeAttempt && IS_STARTED_STATUS(state.activeAttempt.attempt_status)),
- }), [state]);
- return (
-
- {children}
-
- );
-};
-
-const mapStateToProps = (state) => ({ ...state.specialExams });
-
-const ExamStateProvider = withExamStore(
- StateProvider,
- mapStateToProps,
- dispatchActions,
-);
-
-export default ExamStateProvider;
diff --git a/src/core/OuterExamTimer.jsx b/src/core/OuterExamTimer.jsx
index fe70e7d6..2ae0f571 100644
--- a/src/core/OuterExamTimer.jsx
+++ b/src/core/OuterExamTimer.jsx
@@ -2,16 +2,15 @@ import React, { useEffect, useContext } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import PropTypes from 'prop-types';
import { AppContext } from '@edx/frontend-platform/react';
-import ExamStateContext from '../context';
-import { getLatestAttemptData } from '../data';
import { ExamTimerBlock } from '../timer';
import ExamAPIError from '../exam/ExamAPIError';
-import ExamStateProvider from './ExamStateProvider';
+import { getLatestAttemptData } from '../data';
+import { IS_STARTED_STATUS } from '../constants';
const ExamTimer = ({ courseId }) => {
- const examState = useContext(ExamStateContext);
+ const { activeAttempt } = useSelector(state => state.specialExams);
const { authenticatedUser } = useContext(AppContext);
- const { showTimer } = examState;
+ const showTimer = !!(activeAttempt && IS_STARTED_STATUS(activeAttempt.attempt_status));
const { apiErrorMsg } = useSelector(state => state.specialExams);
@@ -48,9 +47,7 @@ ExamTimer.propTypes = {
* will be shown.
*/
const OuterExamTimer = ({ courseId }) => (
-
-
-
+
);
OuterExamTimer.propTypes = {
diff --git a/src/core/OuterExamTimer.test.jsx b/src/core/OuterExamTimer.test.jsx
index 056bfca6..7f96aaa6 100644
--- a/src/core/OuterExamTimer.test.jsx
+++ b/src/core/OuterExamTimer.test.jsx
@@ -2,12 +2,11 @@ import '@testing-library/jest-dom';
import { Factory } from 'rosie';
import React from 'react';
import OuterExamTimer from './OuterExamTimer';
-import { store, getLatestAttemptData } from '../data';
-import { render } from '../setupTest';
+import { getLatestAttemptData } from '../data';
+import { initializeTestStore, render } from '../setupTest';
import { ExamStatus } from '../constants';
jest.mock('../data', () => ({
- store: {},
getLatestAttemptData: jest.fn(),
Emitter: {
on: () => jest.fn(),
@@ -17,12 +16,16 @@ jest.mock('../data', () => ({
},
}));
getLatestAttemptData.mockReturnValue(jest.fn());
-store.subscribe = jest.fn();
-store.dispatch = jest.fn();
describe('OuterExamTimer', () => {
const courseId = 'course-v1:test+test+test';
+ let store;
+
+ beforeEach(async () => {
+ store = await initializeTestStore();
+ });
+
it('is successfully rendered and shows timer if there is an exam in progress', () => {
const attempt = Factory.build('attempt', {
attempt_status: ExamStatus.STARTED,
diff --git a/src/core/SequenceExamWrapper.jsx b/src/core/SequenceExamWrapper.jsx
index dea56e2b..4480ac7f 100644
--- a/src/core/SequenceExamWrapper.jsx
+++ b/src/core/SequenceExamWrapper.jsx
@@ -1,6 +1,5 @@
import React from 'react';
import ExamWrapper from '../exam/ExamWrapper';
-import ExamStateProvider from './ExamStateProvider';
/**
* SequenceExamWrapper is the component responsible for handling special exams.
@@ -14,9 +13,7 @@ import ExamStateProvider from './ExamStateProvider';
*
*/
const SequenceExamWrapper = (props) => (
-
-
-
+
);
export default SequenceExamWrapper;
diff --git a/src/data/index.js b/src/data/index.js
index 701c493b..e0ced278 100644
--- a/src/data/index.js
+++ b/src/data/index.js
@@ -16,11 +16,11 @@ export {
resetExam,
getAllowProctoringOptOut,
examRequiresAccessToken,
+ checkExamEntry,
} from './thunks';
export {
expireExamAttempt,
} from './slice';
-export { default as store } from './store';
export { default as Emitter } from './emitter';
diff --git a/src/data/store.js b/src/data/store.js
deleted file mode 100644
index 9fdef030..00000000
--- a/src/data/store.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import { configureStore } from '@reduxjs/toolkit';
-import examReducer from './slice';
-
-export default configureStore({
- reducer: {
- specialExams: examReducer,
- },
-});
diff --git a/src/exam/Exam.jsx b/src/exam/Exam.jsx
index 7c19b766..306b306e 100644
--- a/src/exam/Exam.jsx
+++ b/src/exam/Exam.jsx
@@ -1,15 +1,15 @@
-/* eslint-disable react-hooks/exhaustive-deps */
-import React, { useContext, useEffect, useState } from 'react';
+import React, { useEffect, useState } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
import PropTypes from 'prop-types';
import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n';
import { Alert, Spinner } from '@edx/paragon';
import { Info } from '@edx/paragon/icons';
import { ExamTimerBlock } from '../timer';
import Instructions from '../instructions';
-import ExamStateContext from '../context';
import ExamAPIError from './ExamAPIError';
-import { ExamStatus, ExamType } from '../constants';
+import { ExamStatus, ExamType, IS_STARTED_STATUS } from '../constants';
import messages from './messages';
+import { getProctoringSettings } from '../data';
/**
* Exam component is intended to render exam instructions before and after exam.
@@ -23,10 +23,12 @@ import messages from './messages';
const Exam = ({
isGated, isTimeLimited, originalUserIsStaff, canAccessProctoredExams, children, intl,
}) => {
- const state = useContext(ExamStateContext);
const {
- isLoading, showTimer, exam, apiErrorMsg, getProctoringSettings,
- } = state;
+ isLoading, activeAttempt, exam, apiErrorMsg,
+ } = useSelector(state => state.specialExams);
+ const dispatch = useDispatch();
+
+ const showTimer = !!(activeAttempt && IS_STARTED_STATUS(activeAttempt.attempt_status));
const {
attempt,
@@ -59,7 +61,7 @@ const Exam = ({
if (proctoredExamTypes.includes(examType)) {
// only fetch proctoring settings for a proctored exam
if (examId) {
- getProctoringSettings();
+ dispatch(getProctoringSettings());
}
// Only exclude Timed Exam when restricting access to exams
@@ -68,7 +70,8 @@ const Exam = ({
// this makes sure useEffect gets called only one time after the exam has been fetched
// we can't leave this empty since initially exam is just an empty object, so
// API calls above would not get triggered
- }, [examId]);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [examId, dispatch]);
if (isLoading) {
return (
diff --git a/src/exam/ExamAPIError.jsx b/src/exam/ExamAPIError.jsx
index a6b0e271..157f7e10 100644
--- a/src/exam/ExamAPIError.jsx
+++ b/src/exam/ExamAPIError.jsx
@@ -1,15 +1,14 @@
-import React, { useContext } from 'react';
+import React from 'react';
+import { useSelector } from 'react-redux';
import { getConfig } from '@edx/frontend-platform';
import { Alert, Hyperlink, Icon } from '@edx/paragon';
import { Info } from '@edx/paragon/icons';
import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n';
-import ExamStateContext from '../context';
import messages from './messages';
const ExamAPIError = ({ intl }) => {
- const state = useContext(ExamStateContext);
const { SITE_NAME, SUPPORT_URL } = getConfig();
- const { apiErrorMsg } = state;
+ const { apiErrorMsg } = useSelector(state => state.specialExams);
const shouldShowApiErrorMsg = !!apiErrorMsg && !apiErrorMsg.includes('<');
return (
diff --git a/src/exam/ExamAPIError.test.jsx b/src/exam/ExamAPIError.test.jsx
index edb16d1d..10c23f2b 100644
--- a/src/exam/ExamAPIError.test.jsx
+++ b/src/exam/ExamAPIError.test.jsx
@@ -1,9 +1,7 @@
import '@testing-library/jest-dom';
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
-import { store } from '../data';
-import { render } from '../setupTest';
-import ExamStateProvider from '../core/ExamStateProvider';
+import { initializeTestStore, render } from '../setupTest';
import ExamAPIError from './ExamAPIError';
const originalConfig = jest.requireActual('@edx/frontend-platform').getConfig();
@@ -13,22 +11,20 @@ jest.mock('@edx/frontend-platform', () => ({
}));
getConfig.mockImplementation(() => originalConfig);
-jest.mock('../data', () => ({
- store: {},
-}));
-store.subscribe = jest.fn();
-store.dispatch = jest.fn();
-
describe('ExamAPIError', () => {
const defaultMessage = 'A system error has occurred with your exam.';
+ let store;
+
+ beforeEach(async () => {
+ store = await initializeTestStore();
+ });
+
it('renders with the default information', () => {
store.getState = () => ({ specialExams: {} });
const tree = render(
-
-
- ,
+ ,
{ store },
);
@@ -45,9 +41,7 @@ describe('ExamAPIError', () => {
store.getState = () => ({ specialExams: {} });
const { getByTestId } = render(
-
-
- ,
+ ,
{ store },
);
@@ -62,9 +56,7 @@ describe('ExamAPIError', () => {
});
const { queryByTestId } = render(
-
-
- ,
+ ,
{ store },
);
@@ -77,9 +69,7 @@ describe('ExamAPIError', () => {
});
const { queryByTestId } = render(
-
-
- ,
+ ,
{ store },
);
@@ -92,9 +82,7 @@ describe('ExamAPIError', () => {
});
const { queryByTestId } = render(
-
-
- ,
+ ,
{ store },
);
diff --git a/src/exam/ExamWrapper.jsx b/src/exam/ExamWrapper.jsx
index 68b7eee8..8b5ccde6 100644
--- a/src/exam/ExamWrapper.jsx
+++ b/src/exam/ExamWrapper.jsx
@@ -1,14 +1,18 @@
+import { useDispatch, useSelector } from 'react-redux';
import React, { useContext, useEffect } from 'react';
import { AppContext } from '@edx/frontend-platform/react';
import PropTypes from 'prop-types';
import Exam from './Exam';
-import ExamStateContext from '../context';
+import {
+ getExamAttemptsData,
+ getAllowProctoringOptOut,
+ checkExamEntry,
+} from '../data';
/**
* Exam wrapper is responsible for triggering initial exam data fetching and rendering Exam.
*/
const ExamWrapper = ({ children, ...props }) => {
- const state = useContext(ExamStateContext);
const { authenticatedUser } = useContext(AppContext);
const {
sequence,
@@ -17,9 +21,13 @@ const ExamWrapper = ({ children, ...props }) => {
originalUserIsStaff,
canAccessProctoredExams,
} = props;
- const { getExamAttemptsData, getAllowProctoringOptOut, checkExamEntry } = state;
+
+ const { isLoading } = useSelector(state => state.specialExams);
+
+ const dispatch = useDispatch();
+
const loadInitialData = async () => {
- await getExamAttemptsData(courseId, sequence.id);
+ await dispatch(getExamAttemptsData(courseId, sequence.id));
await getAllowProctoringOptOut(sequence.allowProctoringOptOut);
await checkExamEntry();
};
@@ -28,7 +36,7 @@ const ExamWrapper = ({ children, ...props }) => {
useEffect(() => {
// fetch exam data on exam sequences or if no exam data has been fetched yet
- if (sequence.isTimeLimited || state.isLoading) {
+ if (sequence.isTimeLimited || isLoading) {
loadInitialData();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
diff --git a/src/exam/ExamWrapper.test.jsx b/src/exam/ExamWrapper.test.jsx
index 04e43ab1..6f529bf4 100644
--- a/src/exam/ExamWrapper.test.jsx
+++ b/src/exam/ExamWrapper.test.jsx
@@ -2,10 +2,9 @@ import '@testing-library/jest-dom';
import { Factory } from 'rosie';
import React from 'react';
import SequenceExamWrapper from './ExamWrapper';
-import { store, startTimedExam } from '../data';
+import { startTimedExam } from '../data';
import { getExamAttemptsData } from '../data/thunks';
-import { render, waitFor } from '../setupTest';
-import ExamStateProvider from '../core/ExamStateProvider';
+import { render, waitFor, initializeTestStore } from '../setupTest';
import { ExamStatus, ExamType } from '../constants';
jest.mock('../data', () => ({
@@ -26,8 +25,6 @@ jest.mock('../data/thunks', () => {
getExamAttemptsData.mockReturnValue(jest.fn());
startTimedExam.mockReturnValue(jest.fn());
-store.subscribe = jest.fn();
-store.dispatch = jest.fn();
describe('SequenceExamWrapper', () => {
const sequence = {
@@ -35,10 +32,11 @@ describe('SequenceExamWrapper', () => {
isTimeLimited: true,
};
const courseId = 'course-v1:test+test+test';
+ let store;
- beforeEach(() => {
+ beforeEach(async () => {
jest.clearAllMocks();
- store.getState = () => ({
+ store = await initializeTestStore({
specialExams: Factory.build('specialExams'),
isLoading: false,
});
@@ -46,11 +44,9 @@ describe('SequenceExamWrapper', () => {
it('is successfully rendered and shows instructions if the user is not staff', () => {
const { queryByTestId } = render(
-
-
- children
-
- ,
+
+ children
+ ,
{ store },
);
expect(queryByTestId('exam-instructions-title')).toHaveTextContent('Subsection is a Timed Exam (30 minutes)');
@@ -66,11 +62,9 @@ describe('SequenceExamWrapper', () => {
}),
});
const { queryByTestId } = render(
-
-
- children
-
- ,
+
+ children
+ ,
{ store },
);
expect(queryByTestId('proctored-exam-instructions-title')).toHaveTextContent('This exam is proctored');
@@ -83,11 +77,9 @@ describe('SequenceExamWrapper', () => {
}),
});
const { queryByTestId } = render(
-
-
- children
-
- ,
+
+ children
+ ,
{ store },
);
expect(queryByTestId('spinner')).toBeInTheDocument();
@@ -101,11 +93,9 @@ describe('SequenceExamWrapper', () => {
});
const { queryByTestId } = render(
-
-
- children
-
- ,
+
+ children
+ ,
{ store },
);
expect(queryByTestId('exam-instructions-title')).toHaveTextContent('Subsection is a Timed Exam (30 minutes)');
@@ -120,11 +110,9 @@ describe('SequenceExamWrapper', () => {
});
const { queryByTestId } = render(
-
-
- children
-
- ,
+
+ children
+ ,
{ store },
);
expect(queryByTestId('exam-instructions-title')).not.toBeInTheDocument();
@@ -133,11 +121,9 @@ describe('SequenceExamWrapper', () => {
it('does not fetch exam data if already loaded and the sequence is not an exam', async () => {
render(
-
-
- children
-
- ,
+
+ children
+ ,
{ store },
);
// assert the exam data is not fetched
@@ -153,11 +139,9 @@ describe('SequenceExamWrapper', () => {
});
render(
-
-
- children
-
- ,
+
+ children
+ ,
{ store },
);
await waitFor(() => expect(getExamAttemptsData).toHaveBeenCalled());
@@ -165,11 +149,9 @@ describe('SequenceExamWrapper', () => {
it('does not take any actions if sequence item is not exam', () => {
const { getByTestId } = render(
-
-
- children
-
- ,
+
+ children
+ ,
{ store },
);
expect(getByTestId('sequence-content')).toHaveTextContent('children');
@@ -180,11 +162,9 @@ describe('SequenceExamWrapper', () => {
authenticatedUser: null,
};
const { getByTestId } = render(
-
-
- children
-
- ,
+
+ children
+ ,
{ store, appContext },
);
expect(getByTestId('sequence-content')).toHaveTextContent('children');
@@ -199,11 +179,9 @@ describe('SequenceExamWrapper', () => {
}),
});
const { queryByTestId } = render(
-
-
- children
-
- ,
+
+ children
+ ,
{ store },
);
expect(queryByTestId('sequence-content')).toHaveTextContent('children');
@@ -220,11 +198,9 @@ describe('SequenceExamWrapper', () => {
}),
});
const { queryByTestId } = render(
-
-
- children
-
- ,
+
+ children
+ ,
{ store },
);
expect(queryByTestId('sequence-content')).toHaveTextContent('children');
@@ -243,11 +219,9 @@ describe('SequenceExamWrapper', () => {
}),
});
const { queryByTestId } = render(
-
-
- children
-
- ,
+
+ children
+ ,
{ store },
);
expect(queryByTestId('sequence-content')).toHaveTextContent('children');
@@ -267,11 +241,9 @@ describe('SequenceExamWrapper', () => {
}),
});
const { queryByTestId } = render(
-
-
- children
-
- ,
+
+ children
+ ,
{ store },
);
expect(queryByTestId('sequence-content')).toHaveTextContent('children');
@@ -292,11 +264,9 @@ describe('SequenceExamWrapper', () => {
}),
});
const { queryByTestId } = render(
-
-
- children
-
- ,
+
+ children
+ ,
{ store },
);
expect(queryByTestId('sequence-content')).toHaveTextContent('children');
@@ -305,11 +275,9 @@ describe('SequenceExamWrapper', () => {
it('does not display masquerade alert if sequence is not time gated', () => {
const { queryByTestId } = render(
-
-
- children
-
- ,
+
+ children
+ ,
{ store },
);
expect(queryByTestId('sequence-content')).toHaveTextContent('children');
@@ -328,15 +296,13 @@ describe('SequenceExamWrapper', () => {
}),
});
const { queryByTestId } = render(
-
-
- children
-
- ,
+
+ children
+ ,
{ store },
);
expect(queryByTestId('no-access')).toHaveTextContent('You do not have access to proctored exams with your current enrollment.');
@@ -355,15 +321,13 @@ describe('SequenceExamWrapper', () => {
}),
});
const { queryByTestId } = render(
-
-
- children
-
- ,
+
+ children
+ ,
{ store },
);
expect(queryByTestId('no-access')).toBeNull();
@@ -382,15 +346,13 @@ describe('SequenceExamWrapper', () => {
}),
});
const { queryByTestId } = render(
-
-
- children
-
- ,
+
+ children
+ ,
{ store },
);
expect(queryByTestId('no-access')).toBeNull();
diff --git a/src/hocs.jsx b/src/hocs.jsx
deleted file mode 100644
index 049ec614..00000000
--- a/src/hocs.jsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import React from 'react';
-import { connect } from 'react-redux';
-import { getDisplayName } from './helpers';
-import { store as examStore } from './data';
-
-// eslint-disable-next-line import/prefer-default-export
-export const withExamStore = (WrappedComponent, mapStateToProps = null, dispatchActions = null) => {
- const ConnectedComp = connect(mapStateToProps, dispatchActions)(WrappedComponent);
- const retValue = (props) => ;
- retValue.displayName = `WithExamStore(${getDisplayName(WrappedComponent)})`;
- return retValue;
-};
diff --git a/src/index.jsx b/src/index.jsx
index 84ae8cf9..e6aa4d3f 100644
--- a/src/index.jsx
+++ b/src/index.jsx
@@ -6,4 +6,3 @@ export {
isExam,
fetchExamAccess,
} from './api';
-export { store } from './data';
diff --git a/src/instructions/Instructions.test.jsx b/src/instructions/Instructions.test.jsx
index af9a5200..eda3eb2a 100644
--- a/src/instructions/Instructions.test.jsx
+++ b/src/instructions/Instructions.test.jsx
@@ -4,20 +4,19 @@ import React from 'react';
import { fireEvent, waitFor } from '@testing-library/dom';
import Instructions from './index';
import {
- store, continueExam, getExamAttemptsData, startProctoredExam, startTimedExam, submitExam,
+ continueExam, getExamAttemptsData, startProctoredExam, startTimedExam, submitExam,
} from '../data';
import { pollExamAttempt, softwareDownloadAttempt } from '../data/api';
import Emitter from '../data/emitter';
import { TIMER_REACHED_NULL } from '../timer/events';
import {
- render, screen, act, initializeMockApp,
+ render, screen, act, initializeMockApp, initializeTestStore,
} from '../setupTest';
import {
ExamStatus, ExamType, INCOMPLETE_STATUSES,
} from '../constants';
jest.mock('../data', () => ({
- store: {},
continueExam: jest.fn(),
getExamAttemptsData: jest.fn(),
getExamReviewPolicy: jest.fn(),
@@ -35,12 +34,15 @@ getExamAttemptsData.mockReturnValue(jest.fn());
startProctoredExam.mockReturnValue(jest.fn());
startTimedExam.mockReturnValue(jest.fn());
pollExamAttempt.mockReturnValue(Promise.resolve({}));
-store.subscribe = jest.fn();
-store.dispatch = jest.fn();
describe('SequenceExamWrapper', () => {
- beforeEach(() => {
+ let store;
+
+ beforeEach(async () => {
initializeMockApp();
+ store = await initializeTestStore();
+ store.subscribe = jest.fn();
+ store.dispatch = jest.fn();
});
it('Start exam instructions can be successfully rendered', () => {
diff --git a/src/instructions/proctored_exam/ProctoredExamInstructions.test.jsx b/src/instructions/proctored_exam/ProctoredExamInstructions.test.jsx
index df3d1023..0473b4d4 100644
--- a/src/instructions/proctored_exam/ProctoredExamInstructions.test.jsx
+++ b/src/instructions/proctored_exam/ProctoredExamInstructions.test.jsx
@@ -3,8 +3,13 @@ import { Factory } from 'rosie';
import React from 'react';
import { fireEvent, waitFor } from '@testing-library/dom';
import Instructions from '../index';
-import { store, getExamAttemptsData, submitExam } from '../../data';
-import { initializeMockApp, render, screen } from '../../setupTest';
+import { getExamAttemptsData, submitExam } from '../../data';
+import {
+ initializeMockApp,
+ initializeTestStore,
+ render,
+ screen,
+} from '../../setupTest';
import {
ExamType,
ExamStatus,
@@ -12,7 +17,6 @@ import {
} from '../../constants';
jest.mock('../../data', () => ({
- store: {},
getExamAttemptsData: jest.fn(),
getExamReviewPolicy: jest.fn(),
submitExam: jest.fn(),
@@ -20,13 +24,16 @@ jest.mock('../../data', () => ({
submitExam.mockReturnValue(jest.fn());
getExamAttemptsData.mockReturnValue(jest.fn());
-store.subscribe = jest.fn();
-store.dispatch = jest.fn();
describe('SequenceExamWrapper', () => {
- beforeEach(() => {
+ let store;
+
+ beforeEach(async () => {
initializeMockApp();
jest.clearAllMocks();
+ store = await initializeTestStore();
+ store.subscribe = jest.fn();
+ store.dispatch = jest.fn();
});
it('Start exam instructions can be successfully rendered', () => {
diff --git a/src/timer/CountDownTimer.test.jsx b/src/timer/CountDownTimer.test.jsx
index dc050bf2..160774f4 100644
--- a/src/timer/CountDownTimer.test.jsx
+++ b/src/timer/CountDownTimer.test.jsx
@@ -5,11 +5,6 @@ import {
render, screen, initializeTestStore, fireEvent,
} from '../setupTest';
import { stopExam, submitExam } from '../data';
-import specialExams from '../data/store';
-
-jest.mock('../data/store', () => ({
- specialExams: {},
-}));
// We do a partial mock to avoid mocking out other exported values (e.g. the store and the Emitter).
jest.mock('../data', () => {
@@ -50,7 +45,6 @@ describe('ExamTimerBlock', () => {
},
};
store = await initializeTestStore(preloadedState);
- specialExams.getState = store.getState;
attempt = store.getState().specialExams.activeAttempt;
});
@@ -113,7 +107,6 @@ describe('ExamTimerBlock', () => {
},
};
const testStore = await initializeTestStore(preloadedState);
- specialExams.getState = store.testStore;
attempt = testStore.getState().specialExams.activeAttempt;
render(
,
@@ -176,7 +169,6 @@ describe('ExamTimerBlock', () => {
},
};
const testStore = await initializeTestStore(preloadedState);
- specialExams.getState = store.testStore;
attempt = testStore.getState().specialExams.activeAttempt;
render(
@@ -219,7 +211,6 @@ describe('ExamTimerBlock', () => {
},
};
let testStore = await initializeTestStore(preloadedState);
- specialExams.getState = store.testStore;
attempt = testStore.getState().specialExams.activeAttempt;
const { rerender } = render(
,
@@ -231,7 +222,6 @@ describe('ExamTimerBlock', () => {
time_remaining_seconds: 20,
};
testStore = await initializeTestStore(preloadedState);
- specialExams.getState = store.testStore;
const updatedAttempt = testStore.getState().specialExams.activeAttempt;
expect(updatedAttempt.time_remaining_seconds).toBe(20);
@@ -279,7 +269,6 @@ describe('ExamTimerBlock', () => {
// Store it in the state
const testStore = await initializeTestStore(preloadedState);
- specialExams.getState = store.testStore;
attempt = testStore.getState().specialExams.activeAttempt;
// render an exam timer block with that data