Skip to content

Commit

Permalink
refactor: replace use of context with thunks and Redux store in instr…
Browse files Browse the repository at this point in the history
…uctions components

This commit replaces the use of the ExamStateContext with the use of thunks and the Redux store in the instructions components.

The original pattern was to use the withExamStore higher-order component to provide context to the instructions components. This context contained provided the Redux store state and action creators as props by using the connect API. This posed a problem for our need to merge the frontend-app-learning and frontend-lib-special-exams stores, because the special exams store is initialized in this repository and used by the higher-order component. In order to eventually be able to remove the creation of the store in this repository, we have to remove references to the store by interfacing with the Redux more directly by using the useDispatch and useSelector hooks.
  • Loading branch information
MichaelRoytman committed Feb 1, 2024
1 parent 48ef153 commit 7c39a7d
Show file tree
Hide file tree
Showing 24 changed files with 146 additions and 120 deletions.
1 change: 1 addition & 0 deletions src/data/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export {
createProctoredExamAttempt,
getExamAttemptsData,
getLatestAttemptData,
getProctoringSettings,
Expand Down
13 changes: 7 additions & 6 deletions src/instructions/Instructions.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import { Factory } from 'rosie';
import React from 'react';
import { fireEvent, waitFor } from '@testing-library/dom';
import Instructions from './index';
import { store, getExamAttemptsData, startTimedExam } from '../data';
import {
store, continueExam, getExamAttemptsData, startProctoredExam, startTimedExam, submitExam,
} from '../data';
import { pollExamAttempt, softwareDownloadAttempt } from '../data/api';
import { continueExam, submitExam } from '../data/thunks';
import Emitter from '../data/emitter';
import { TIMER_REACHED_NULL } from '../timer/events';
import {
Expand All @@ -18,12 +19,11 @@ import {

jest.mock('../data', () => ({
store: {},
getExamAttemptsData: jest.fn(),
startTimedExam: jest.fn(),
}));
jest.mock('../data/thunks', () => ({
continueExam: jest.fn(),
getExamAttemptsData: jest.fn(),
getExamReviewPolicy: jest.fn(),
startProctoredExam: jest.fn(),
startTimedExam: jest.fn(),
submitExam: jest.fn(),
}));
jest.mock('../data/api', () => ({
Expand All @@ -33,6 +33,7 @@ jest.mock('../data/api', () => ({
continueExam.mockReturnValue(jest.fn());
submitExam.mockReturnValue(jest.fn());
getExamAttemptsData.mockReturnValue(jest.fn());
startProctoredExam.mockReturnValue(jest.fn());
startTimedExam.mockReturnValue(jest.fn());
pollExamAttempt.mockReturnValue(Promise.resolve({}));
store.subscribe = jest.fn();
Expand Down
13 changes: 8 additions & 5 deletions src/instructions/SubmitInstructions.jsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import React, { useContext, useEffect, useState } from 'react';
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Button, Container } from '@edx/paragon';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import Emitter from '../data/emitter';
import { ExamType } from '../constants';
import { continueExam } from '../data';
import { SubmitProctoredExamInstructions } from './proctored_exam';
import { SubmitTimedExamInstructions } from './timed_exam';
import Footer from './proctored_exam/Footer';
import ExamStateContext from '../context';
import { TIMER_REACHED_NULL } from '../timer/events';

const SubmitExamInstructions = () => {
const state = useContext(ExamStateContext);
const { exam, continueExam, activeAttempt } = state;
const { exam, activeAttempt } = useSelector(state => state.specialExams);

const dispatch = useDispatch();

const { time_remaining_seconds: timeRemaining } = activeAttempt;
const { type: examType } = exam || {};
const [canContinue, setCanContinue] = useState(timeRemaining > 0);
Expand All @@ -33,7 +36,7 @@ const SubmitExamInstructions = () => {
? <SubmitTimedExamInstructions />
: <SubmitProctoredExamInstructions />}
{canContinue && (
<Button variant="outline-primary" onClick={continueExam} data-testid="continue-exam-button">
<Button variant="outline-primary" onClick={() => dispatch(continueExam())} data-testid="continue-exam-button">
<FormattedMessage
id="exam.SubmitExamInstructions.continueButton"
defaultMessage="No, I'd like to continue working"
Expand Down
7 changes: 3 additions & 4 deletions src/instructions/index.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useContext, useState } from 'react';
import React, { useState } from 'react';
import { useSelector } from 'react-redux';
import PropTypes from 'prop-types';
import {
DownloadSoftwareProctoredExamInstructions,
Expand All @@ -13,7 +14,6 @@ import {
ExamType,
IS_ONBOARDING_ERROR,
} from '../constants';
import ExamStateContext from '../context';
import EntranceExamInstructions from './EntranceInstructions';
import SubmitExamInstructions from './SubmitInstructions';
import RejectedInstructions from './RejectedInstructions';
Expand All @@ -24,8 +24,7 @@ import ExpiredInstructions from './ExpiredInstructions';
import UnknownAttemptStatusError from './UnknownAttemptStatusError';

const Instructions = ({ children }) => {
const state = useContext(ExamStateContext);
const { exam } = state;
const { exam } = useSelector(state => state.specialExams);
const {
attempt,
type: examType,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import React, { useContext } from 'react';
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { Button, MailtoLink } from '@edx/paragon';
import ExamStateContext from '../../context';

import { createProctoredExamAttempt } from '../../data';

const EntranceOnboardingExamInstructions = () => {
const state = useContext(ExamStateContext);
const { createProctoredExamAttempt, proctoringSettings } = state;
const { proctoringSettings } = useSelector(state => state.specialExams);

const dispatch = useDispatch();

const {
provider_name: providerName,
learner_notification_from_email: learnerNotificationFromEmail,
Expand Down Expand Up @@ -89,7 +93,7 @@ const EntranceOnboardingExamInstructions = () => {
<Button
data-testid="start-exam-button"
variant="primary"
onClick={createProctoredExamAttempt}
onClick={() => dispatch(createProctoredExamAttempt())}

Check warning on line 96 in src/instructions/onboarding_exam/EntranceOnboardingExamInstructions.jsx

View check run for this annotation

Codecov / codecov/patch

src/instructions/onboarding_exam/EntranceOnboardingExamInstructions.jsx#L96

Added line #L96 was not covered by tests
>
<FormattedMessage
id="exam.EntranceOnboardingExamInstructions.startExamButtonText"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React, { useContext } from 'react';
import React from 'react';
import { useDispatch } from 'react-redux';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { Button } from '@edx/paragon';
import ExamStateContext from '../../context';

import { resetExam } from '../../data';

const ErrorOnboardingExamInstructions = () => {
const state = useContext(ExamStateContext);
const { resetExam } = state;
const dispatch = useDispatch();

return (
<div>
Expand All @@ -25,7 +26,7 @@ const ErrorOnboardingExamInstructions = () => {
<Button
data-testid="retry-exam-button"
variant="primary"
onClick={resetExam}
onClick={() => dispatch(resetExam())}

Check warning on line 29 in src/instructions/onboarding_exam/ErrorOnboardingExamInstructions.jsx

View check run for this annotation

Codecov / codecov/patch

src/instructions/onboarding_exam/ErrorOnboardingExamInstructions.jsx#L29

Added line #L29 was not covered by tests
>
<FormattedMessage
id="exam.ErrorOnboardingExamInstructions.retryExamButton"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import React, { useContext } from 'react';
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { Button, MailtoLink } from '@edx/paragon';
import ExamStateContext from '../../context';

import { resetExam } from '../../data';

const RejectedOnboardingExamInstructions = () => {
const state = useContext(ExamStateContext);
const { proctoringSettings, resetExam } = state;
const { proctoringSettings } = useSelector(state => state.specialExams);

const dispatch = useDispatch();

const { integration_specific_email: integrationSpecificEmail } = proctoringSettings || {};

return (
Expand Down Expand Up @@ -34,7 +38,7 @@ const RejectedOnboardingExamInstructions = () => {
<Button
data-testid="reset-exam-button"
variant="primary"
onClick={resetExam}
onClick={() => dispatch(resetExam())}

Check warning on line 41 in src/instructions/onboarding_exam/RejectedOnboardingExamInstructions.jsx

View check run for this annotation

Codecov / codecov/patch

src/instructions/onboarding_exam/RejectedOnboardingExamInstructions.jsx#L41

Added line #L41 was not covered by tests
>
<FormattedMessage
id="exam.RejectedOnboardingExamInstructions.resetExamButton"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import React, { useContext } from 'react';
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { Button, MailtoLink, useToggle } from '@edx/paragon';
import ExamStateContext from '../../context';

import { resetExam } from '../../data';

const SubmittedOnboardingExamInstructions = () => {
const [isConfirm, confirm] = useToggle(false);
const state = useContext(ExamStateContext);
const { proctoringSettings, resetExam } = state;

const { proctoringSettings } = useSelector(state => state.specialExams);

const dispatch = useDispatch();

const {
learner_notification_from_email: learnerNotificationFromEmail,
integration_specific_email: integrationSpecificEmail,
Expand Down Expand Up @@ -70,7 +75,7 @@ const SubmittedOnboardingExamInstructions = () => {
<Button
data-testid="retry-exam-button"
variant="primary"
onClick={resetExam}
onClick={() => dispatch(resetExam())}

Check warning on line 78 in src/instructions/onboarding_exam/SubmittedOnboardingExamInstructions.jsx

View check run for this annotation

Codecov / codecov/patch

src/instructions/onboarding_exam/SubmittedOnboardingExamInstructions.jsx#L78

Added line #L78 was not covered by tests
disabled={!isConfirm}
>
<FormattedMessage
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import React, { useContext } from 'react';
import React from 'react';
import { useSelector } from 'react-redux';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { MailtoLink } from '@edx/paragon';
import ExamStateContext from '../../context';

const VerifiedOnboardingExamInstructions = () => {
const state = useContext(ExamStateContext);
const { proctoringSettings } = useSelector(state => state.specialExams);

const {
integration_specific_email: integrationSpecificEmail,
} = state.proctoringSettings || {};
} = proctoringSettings || {};

return (
<div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React, { useContext } from 'react';
import React from 'react';
import { useDispatch } from 'react-redux';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { Button } from '@edx/paragon';
import ExamStateContext from '../../context';

import { createProctoredExamAttempt } from '../../data';

const EntrancePracticeExamInstructions = () => {
const state = useContext(ExamStateContext);
const { createProctoredExamAttempt } = state;
const dispatch = useDispatch();

return (
<>
Expand All @@ -26,7 +27,7 @@ const EntrancePracticeExamInstructions = () => {
<Button
data-testid="start-exam-button"
variant="primary"
onClick={createProctoredExamAttempt}
onClick={() => dispatch(createProctoredExamAttempt())}

Check warning on line 30 in src/instructions/practice_exam/EntrancePracticeExamInstructions.jsx

View check run for this annotation

Codecov / codecov/patch

src/instructions/practice_exam/EntrancePracticeExamInstructions.jsx#L30

Added line #L30 was not covered by tests
>
<FormattedMessage
id="exam.EntrancePracticeExamInstructions.startExamButtonText"
Expand Down
11 changes: 6 additions & 5 deletions src/instructions/practice_exam/ErrorPracticeExamInstructions.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React, { useContext } from 'react';
import React from 'react';
import { useDispatch } from 'react-redux';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { Button } from '@edx/paragon';
import ExamStateContext from '../../context';

import { resetExam } from '../../data';

const ErrorPracticeExamInstructions = () => {
const state = useContext(ExamStateContext);
const { resetExam } = state;
const dispatch = useDispatch();

return (
<div>
Expand Down Expand Up @@ -37,7 +38,7 @@ const ErrorPracticeExamInstructions = () => {
<Button
data-testid="retry-exam-button"
variant="primary"
onClick={resetExam}
onClick={() => dispatch(resetExam())}

Check warning on line 41 in src/instructions/practice_exam/ErrorPracticeExamInstructions.jsx

View check run for this annotation

Codecov / codecov/patch

src/instructions/practice_exam/ErrorPracticeExamInstructions.jsx#L41

Added line #L41 was not covered by tests
>
<FormattedMessage
id="exam.ErrorOnboardingExamInstructions.retryExamButton"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React, { useContext } from 'react';
import React from 'react';
import { useDispatch } from 'react-redux';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { Button } from '@edx/paragon';
import ExamStateContext from '../../context';

import { resetExam } from '../../data';

const SubmittedPracticeExamInstructions = () => {
const state = useContext(ExamStateContext);
const { resetExam } = state;
const dispatch = useDispatch();

return (
<div>
Expand All @@ -25,7 +26,7 @@ const SubmittedPracticeExamInstructions = () => {
<Button
data-testid="retry-exam-button"
variant="primary"
onClick={resetExam}
onClick={() => dispatch(resetExam())}

Check warning on line 29 in src/instructions/practice_exam/SubmittedPracticeExamInstructions.jsx

View check run for this annotation

Codecov / codecov/patch

src/instructions/practice_exam/SubmittedPracticeExamInstructions.jsx#L29

Added line #L29 was not covered by tests
>
<FormattedMessage
id="exam.SubmittedPracticeExamInstructions.retryExamButton"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import React, { useContext } from 'react';
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import PropTypes from 'prop-types';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { Button } from '@edx/paragon';
import ExamStateContext from '../../context';

import { createProctoredExamAttempt } from '../../data';
import SkipProctoredExamButton from './SkipProctoredExamButton';

const EntranceProctoredExamInstructions = ({ skipProctoredExam }) => {
const state = useContext(ExamStateContext);
const { exam, createProctoredExamAttempt, allowProctoringOptOut } = state;
const { exam, allowProctoringOptOut } = useSelector(state => state.specialExams);

const dispatch = useDispatch();

const { attempt } = exam || {};
const { total_time: totalTime = 0 } = attempt;

Expand Down Expand Up @@ -54,7 +58,7 @@ const EntranceProctoredExamInstructions = ({ skipProctoredExam }) => {
<Button
data-testid="start-exam-button"
variant="primary"
onClick={createProctoredExamAttempt}
onClick={() => dispatch(createProctoredExamAttempt())}

Check warning on line 61 in src/instructions/proctored_exam/EntranceProctoredExamInstructions.jsx

View check run for this annotation

Codecov / codecov/patch

src/instructions/proctored_exam/EntranceProctoredExamInstructions.jsx#L61

Added line #L61 was not covered by tests
>
<FormattedMessage
id="exam.startExamInstructions.startExamButtonText"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import React, { useContext } from 'react';
import React from 'react';
import { useSelector } from 'react-redux';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { getConfig } from '@edx/frontend-platform';
import { Hyperlink, MailtoLink } from '@edx/paragon';
import ExamStateContext from '../../context';

const ErrorProctoredExamInstructions = () => {
const state = useContext(ExamStateContext);
const {
proctoring_escalation_email: proctoringEscalationEmail,
} = state.proctoringSettings || {};
const { proctoringEscalationEmail } = useSelector(state => state.specialExams.proctoringSettings);

const platformName = getConfig().SITE_NAME;
const contactUsUrl = getConfig().CONTACT_URL;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import React, { useContext } from 'react';
import React from 'react';
import { useSelector } from 'react-redux';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { Container, MailtoLink, Hyperlink } from '@edx/paragon';
import ExamStateContext from '../../context';
import { ExamStatus } from '../../constants';
import Footer from './Footer';

const OnboardingErrorProctoredExamInstructions = () => {
const state = useContext(ExamStateContext);
const { exam, proctoringSettings } = state;
const { exam, proctoringSettings } = useSelector(state => state.specialExams);
const { attempt, onboarding_link: onboardingLink } = exam;
const {
integration_specific_email: integrationSpecificEmail,
Expand Down
Loading

0 comments on commit 7c39a7d

Please sign in to comment.