diff --git a/src/data/index.js b/src/data/index.js
index 5e2400f2..27ef04a1 100644
--- a/src/data/index.js
+++ b/src/data/index.js
@@ -13,6 +13,7 @@ export {
getExamReviewPolicy,
pingAttempt,
resetExam,
+ getAllowProctoringOptOut,
} from './thunks';
export { default as store } from './store';
diff --git a/src/data/slice.js b/src/data/slice.js
index 6866fba8..9b94c842 100644
--- a/src/data/slice.js
+++ b/src/data/slice.js
@@ -7,12 +7,16 @@ export const examSlice = createSlice({
isLoading: true,
timeIsOver: false,
activeAttempt: null,
+ allowProctoringOptOut: false,
proctoringSettings: {},
exam: {},
verification: {},
apiErrorMsg: '',
},
reducers: {
+ setAllowProctoringOptOut: (state, { payload }) => {
+ state.allowProctoringOptOut = payload.allowProctoringOptOut;
+ },
setIsLoading: (state, { payload }) => {
state.isLoading = payload.isLoading;
},
@@ -46,7 +50,7 @@ export const examSlice = createSlice({
export const {
setIsLoading, setExamState, getExamId, expireExamAttempt,
setActiveAttempt, setProctoringSettings, setVerificationData,
- setReviewPolicy, setApiError,
+ setReviewPolicy, setApiError, setAllowProctoringOptOut,
} = examSlice.actions;
export default examSlice.reducer;
diff --git a/src/data/thunks.js b/src/data/thunks.js
index 960f99c8..5ba37e86 100644
--- a/src/data/thunks.js
+++ b/src/data/thunks.js
@@ -22,6 +22,7 @@ import {
setVerificationData,
setReviewPolicy,
setApiError,
+ setAllowProctoringOptOut,
} from './slice';
import { ExamStatus } from '../constants';
import { workerPromiseForEventNames, pingApplication } from './messages/handlers';
@@ -359,3 +360,9 @@ export function getExamReviewPolicy() {
dispatch(setReviewPolicy({ policy: data.review_policy }));
};
}
+
+export function getAllowProctoringOptOut(allowProctoringOptOut) {
+ return (dispatch) => {
+ dispatch(setAllowProctoringOptOut({ allowProctoringOptOut }));
+ };
+}
diff --git a/src/exam/ExamWrapper.jsx b/src/exam/ExamWrapper.jsx
index ee9dd217..45c7c2c4 100644
--- a/src/exam/ExamWrapper.jsx
+++ b/src/exam/ExamWrapper.jsx
@@ -12,8 +12,8 @@ const ExamWrapper = ({ children, ...props }) => {
const loadInitialData = async () => {
await state.getExamAttemptsData(courseId, sequence.id);
+ await state.getAllowProctoringOptOut(sequence.allowProctoringOptOut);
state.getProctoringSettings();
- state.getVerificationData();
};
useEffect(() => {
@@ -31,6 +31,7 @@ ExamWrapper.propTypes = {
sequence: PropTypes.shape({
id: PropTypes.string.isRequired,
isTimeLimited: PropTypes.bool,
+ allowProctoringOptOut: PropTypes.bool,
}).isRequired,
courseId: PropTypes.string.isRequired,
children: PropTypes.element.isRequired,
diff --git a/src/instructions/EntranceInstructions.jsx b/src/instructions/EntranceInstructions.jsx
index 4bbe635a..e545d092 100644
--- a/src/instructions/EntranceInstructions.jsx
+++ b/src/instructions/EntranceInstructions.jsx
@@ -4,7 +4,7 @@ import { Container } from '@edx/paragon';
import { ExamType } from '../constants';
import { EntranceProctoredExamInstructions } from './proctored_exam';
import { EntranceOnboardingExamInstructions } from './onboarding_exam';
-import EntrancePracticeExamInstructions from './practice_exam';
+import { EntrancePracticeExamInstructions } from './practice_exam';
import { StartTimedExamInstructions, TimedExamFooter } from './timed_exam';
import Footer from './proctored_exam/Footer';
diff --git a/src/instructions/ErrorInstructions.jsx b/src/instructions/ErrorInstructions.jsx
new file mode 100644
index 00000000..2aadb35d
--- /dev/null
+++ b/src/instructions/ErrorInstructions.jsx
@@ -0,0 +1,38 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Container } from '@edx/paragon';
+import { ExamType } from '../constants';
+import { ErrorPracticeExamInstructions } from './practice_exam';
+import { ErrorOnboardingExamInstructions } from './onboarding_exam';
+import { ErrorProctoredExamInstructions } from './proctored_exam';
+import Footer from './proctored_exam/Footer';
+
+const ErrorExamInstructions = ({ examType }) => {
+ const renderInstructions = () => {
+ switch (examType) {
+ case ExamType.ONBOARDING:
+ return ;
+ case ExamType.PRACTICE:
+ return ;
+ case ExamType.PROCTORED:
+ return ;
+ default:
+ return null;
+ }
+ };
+
+ return (
+
+
+ {renderInstructions()}
+
+ {examType !== ExamType.TIMED && }
+
+ );
+};
+
+ErrorExamInstructions.propTypes = {
+ examType: PropTypes.string.isRequired,
+};
+
+export default ErrorExamInstructions;
diff --git a/src/instructions/Instructions.test.jsx b/src/instructions/Instructions.test.jsx
index 14c2b1d4..6be720c1 100644
--- a/src/instructions/Instructions.test.jsx
+++ b/src/instructions/Instructions.test.jsx
@@ -55,14 +55,12 @@ describe('SequenceExamWrapper', () => {
status: 'none',
can_verify: true,
},
- activeAttempt: {
- attempt_status: 'started',
- },
+ activeAttempt: {},
exam: {
time_limit_mins: 30,
type: ExamType.PROCTORED,
attempt: {
- attempt_status: 'started',
+ attempt_status: ExamStatus.STARTED,
},
},
},
@@ -163,12 +161,12 @@ describe('SequenceExamWrapper', () => {
examState: {
isLoading: false,
timeIsOver: true,
+ allowProctoringOptOut: true,
proctoringSettings: {
platform_name: 'Your Platform',
},
activeAttempt: {},
exam: {
- allow_proctoring_opt_out: true,
is_proctored: true,
time_limit_mins: 30,
attempt: {},
@@ -261,13 +259,12 @@ describe('SequenceExamWrapper', () => {
status: 'none',
can_verify: true,
},
- activeAttempt: {
- attempt_status: 'error',
- },
+ activeAttempt: {},
exam: {
+ type: ExamType.PROCTORED,
time_limit_mins: 30,
attempt: {
- attempt_status: 'error',
+ attempt_status: ExamStatus.ERROR,
},
},
},
@@ -296,14 +293,12 @@ describe('SequenceExamWrapper', () => {
status: 'none',
can_verify: true,
},
- activeAttempt: {
- attempt_status: 'ready_to_resume',
- },
+ activeAttempt: {},
exam: {
- type: 'proctored',
+ type: ExamType.PROCTORED,
time_limit_mins: 30,
attempt: {
- attempt_status: 'ready_to_resume',
+ attempt_status: ExamStatus.READY_TO_RESUME,
},
},
},
@@ -331,14 +326,12 @@ describe('SequenceExamWrapper', () => {
can_verify: true,
},
proctoringSettings: {},
- activeAttempt: {
- attempt_status: 'ready_to_submit',
- },
+ activeAttempt: {},
exam: {
- type: 'timed',
+ type: ExamType.TIMED,
time_limit_mins: 30,
attempt: {
- attempt_status: 'ready_to_submit',
+ attempt_status: ExamStatus.READY_TO_SUBMIT,
},
},
},
@@ -365,13 +358,12 @@ describe('SequenceExamWrapper', () => {
can_verify: true,
},
proctoringSettings: {},
- activeAttempt: {
- attempt_status: 'submitted',
- },
+ activeAttempt: {},
exam: {
+ type: ExamType.TIMED,
time_limit_mins: 30,
attempt: {
- attempt_status: 'submitted',
+ attempt_status: ExamStatus.SUBMITTED,
},
},
},
@@ -398,13 +390,12 @@ describe('SequenceExamWrapper', () => {
can_verify: true,
},
proctoringSettings: {},
- activeAttempt: {
- attempt_status: 'submitted',
- },
+ activeAttempt: {},
exam: {
+ type: ExamType.TIMED,
time_limit_mins: 30,
attempt: {
- attempt_status: 'submitted',
+ attempt_status: ExamStatus.SUBMITTED,
},
},
},
@@ -496,4 +487,189 @@ describe('SequenceExamWrapper', () => {
expect(getByTestId('submit-onboarding-exam')).toBeInTheDocument();
});
+
+ it('Shows error onboarding exam instructions if exam is onboarding and attempt status is error', () => {
+ store.getState = () => ({
+ examState: {
+ isLoading: false,
+ timeIsOver: false,
+ proctoringSettings: {
+ platform_name: 'Your Platform',
+ },
+ activeAttempt: {},
+ exam: {
+ is_proctored: true,
+ type: ExamType.ONBOARDING,
+ time_limit_mins: 30,
+ attempt: {
+ attempt_status: ExamStatus.ERROR,
+ },
+ prerequisite_status: {},
+ },
+ verification: {},
+ },
+ });
+
+ render(
+
+
+ Sequence
+
+ ,
+ { store },
+ );
+
+ expect(screen.getByText('Error: There was a problem with your onboarding session')).toBeInTheDocument();
+ expect(screen.getByTestId('retry-exam-button')).toHaveTextContent('Retry my exam');
+ });
+
+ it('Shows submitted onboarding exam instructions if exam is onboarding and attempt status is submitted', () => {
+ store.getState = () => ({
+ examState: {
+ isLoading: false,
+ timeIsOver: false,
+ proctoringSettings: {
+ platform_name: 'Your Platform',
+ integration_specific_email: 'test@example.com',
+ learner_notification_from_email: 'test_notification@example.com',
+ },
+ activeAttempt: {},
+ exam: {
+ is_proctored: true,
+ type: ExamType.ONBOARDING,
+ time_limit_mins: 30,
+ attempt: {
+ attempt_status: ExamStatus.SUBMITTED,
+ },
+ prerequisite_status: {},
+ },
+ verification: {},
+ },
+ });
+
+ render(
+
+
+ Sequence
+
+ ,
+ { store },
+ );
+
+ const retryExamButton = screen.getByTestId('retry-exam-button');
+ expect(retryExamButton).toHaveTextContent('Retry my exam');
+ expect(screen.getByText('You have submitted this onboarding exam')).toBeInTheDocument();
+ expect(screen.getByRole('link', { name: 'test@example.com' })).toBeInTheDocument();
+ expect(screen.getByRole('link', { name: 'test_notification@example.com' })).toBeInTheDocument();
+
+ expect(retryExamButton).toBeDisabled();
+ fireEvent.click(screen.getByRole('button', { name: 'I understand and want to reset this onboarding exam.' }));
+ expect(retryExamButton).not.toBeDisabled();
+ });
+
+ it('Shows verified onboarding exam instructions if exam is onboarding and attempt status is verified', () => {
+ store.getState = () => ({
+ examState: {
+ isLoading: false,
+ timeIsOver: false,
+ proctoringSettings: {
+ platform_name: 'Your Platform',
+ integration_specific_email: 'test@example.com',
+ },
+ activeAttempt: {},
+ exam: {
+ is_proctored: true,
+ type: ExamType.ONBOARDING,
+ time_limit_mins: 30,
+ attempt: {
+ attempt_status: ExamStatus.VERIFIED,
+ },
+ prerequisite_status: {},
+ },
+ verification: {},
+ },
+ });
+
+ render(
+
+
+ Sequence
+
+ ,
+ { store },
+ );
+
+ expect(screen.getByText('Your onboarding profile was reviewed successfully')).toBeInTheDocument();
+ expect(screen.getByRole('link')).toHaveTextContent('test@example.com');
+ });
+
+ it('Shows error practice exam instructions if exam is onboarding and attempt status is error', () => {
+ store.getState = () => ({
+ examState: {
+ isLoading: false,
+ timeIsOver: false,
+ proctoringSettings: {
+ platform_name: 'Your Platform',
+ },
+ activeAttempt: {},
+ exam: {
+ is_proctored: true,
+ type: ExamType.PRACTICE,
+ time_limit_mins: 30,
+ attempt: {
+ attempt_status: ExamStatus.ERROR,
+ },
+ prerequisite_status: {},
+ },
+ verification: {},
+ },
+ });
+
+ render(
+
+
+ Sequence
+
+ ,
+ { store },
+ );
+
+ expect(screen.getByText('There was a problem with your practice proctoring session')).toBeInTheDocument();
+ expect(screen.getByTestId('retry-exam-button')).toHaveTextContent('Retry my exam');
+ });
+
+ it('Shows submitted practice exam instructions if exam is onboarding and attempt status is submitted', () => {
+ store.getState = () => ({
+ examState: {
+ isLoading: false,
+ timeIsOver: false,
+ proctoringSettings: {
+ platform_name: 'Your Platform',
+ },
+ activeAttempt: {},
+ exam: {
+ is_proctored: true,
+ type: ExamType.PRACTICE,
+ time_limit_mins: 30,
+ attempt: {
+ attempt_status: ExamStatus.SUBMITTED,
+ },
+ prerequisite_status: {},
+ },
+ verification: {},
+ },
+ });
+
+ render(
+
+
+ Sequence
+
+ ,
+ { store },
+ );
+
+ expect(screen.getByText('You have submitted this practice proctored exam')).toBeInTheDocument();
+ expect(screen.getByTestId('retry-exam-button')).toHaveTextContent('Retry my exam');
+ });
});
diff --git a/src/instructions/SubmittedInstructions.jsx b/src/instructions/SubmittedInstructions.jsx
new file mode 100644
index 00000000..08579723
--- /dev/null
+++ b/src/instructions/SubmittedInstructions.jsx
@@ -0,0 +1,41 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Container } from '@edx/paragon';
+import { ExamType } from '../constants';
+import { SubmittedPracticeExamInstructions } from './practice_exam';
+import { SubmittedProctoredExamInstructions } from './proctored_exam';
+import { SubmittedOnboardingExamInstructions } from './onboarding_exam';
+import { SubmittedTimedExamInstructions } from './timed_exam';
+import Footer from './proctored_exam/Footer';
+
+const SubmittedExamInstructions = ({ examType }) => {
+ const renderInstructions = () => {
+ switch (examType) {
+ case ExamType.ONBOARDING:
+ return ;
+ case ExamType.PRACTICE:
+ return ;
+ case ExamType.PROCTORED:
+ return ;
+ case ExamType.TIMED:
+ return ;
+ default:
+ return null;
+ }
+ };
+
+ return (
+
+
+ {renderInstructions()}
+
+ {examType !== ExamType.TIMED && }
+
+ );
+};
+
+SubmittedExamInstructions.propTypes = {
+ examType: PropTypes.string.isRequired,
+};
+
+export default SubmittedExamInstructions;
diff --git a/src/instructions/VerifiedInstructions.jsx b/src/instructions/VerifiedInstructions.jsx
new file mode 100644
index 00000000..7d9a2705
--- /dev/null
+++ b/src/instructions/VerifiedInstructions.jsx
@@ -0,0 +1,35 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Container } from '@edx/paragon';
+import { ExamType } from '../constants';
+import { VerifiedOnboardingExamInstructions } from './onboarding_exam';
+import { VerifiedProctoredExamInstructions } from './proctored_exam';
+import Footer from './proctored_exam/Footer';
+
+const VerifiedExamInstructions = ({ examType }) => {
+ const renderInstructions = () => {
+ switch (examType) {
+ case ExamType.ONBOARDING:
+ return ;
+ case ExamType.PROCTORED:
+ return ;
+ default:
+ return null;
+ }
+ };
+
+ return (
+
+
+ {renderInstructions()}
+
+ {examType !== ExamType.TIMED && }
+
+ );
+};
+
+VerifiedExamInstructions.propTypes = {
+ examType: PropTypes.string.isRequired,
+};
+
+export default VerifiedExamInstructions;
diff --git a/src/instructions/index.jsx b/src/instructions/index.jsx
index 7b42979d..107e03fa 100644
--- a/src/instructions/index.jsx
+++ b/src/instructions/index.jsx
@@ -1,11 +1,7 @@
-import React, { useContext, useState } from 'react';
+import React, { useContext, useState, useEffect } from 'react';
import PropTypes from 'prop-types';
-import SubmittedExamInstructions from './SubmittedExamInstructions';
import {
- ErrorProctoredExamInstructions,
VerificationProctoredExamInstructions,
- SubmittedProctoredExamInstructions,
- VerifiedProctoredExamInstructions,
DownloadSoftwareProctoredExamInstructions,
ReadyToStartProctoredExamInstructions,
PrerequisitesProctoredExamInstructions,
@@ -17,10 +13,13 @@ import ExamStateContext from '../context';
import EntranceExamInstructions from './EntranceInstructions';
import SubmitExamInstructions from './SubmitInstructions';
import RejectedInstructions from './RejectedInstructions';
+import ErrorExamInstructions from './ErrorInstructions';
+import SubmittedExamInstructions from './SubmittedInstructions';
+import VerifiedExamInstructions from './VerifiedInstructions';
const Instructions = ({ children }) => {
const state = useContext(ExamStateContext);
- const { exam, verification } = state;
+ const { exam, verification, getVerificationData } = state;
const { attempt, type: examType, prerequisite_status: prerequisitesData } = exam || {};
const prerequisitesPassed = prerequisitesData ? prerequisitesData.are_prerequisites_satisifed : true;
let verificationStatus = verification.status || '';
@@ -28,6 +27,12 @@ const Instructions = ({ children }) => {
const [skipProctoring, toggleSkipProctoring] = useState(false);
const toggleSkipProctoredExam = () => toggleSkipProctoring(!skipProctoring);
+ useEffect(() => {
+ if (examType === ExamType.PROCTORED) {
+ getVerificationData();
+ }
+ }, []);
+
// The API does not explicitly return 'expired' status, so we have to check manually.
// expires attribute is returned only for approved status, so it is safe to do this
// (meaning we won't override 'must_reverify' status for example)
@@ -47,9 +52,9 @@ const Instructions = ({ children }) => {
:
: ;
case attempt.attempt_status === ExamStatus.CREATED:
- return verificationStatus === VerificationStatus.APPROVED
- ?
- : ;
+ return examType === ExamType.PROCTORED && verificationStatus !== VerificationStatus.APPROVED
+ ?
+ : ;
case attempt.attempt_status === ExamStatus.DOWNLOAD_SOFTWARE_CLICKED:
return ;
case attempt.attempt_status === ExamStatus.READY_TO_START:
@@ -57,15 +62,13 @@ const Instructions = ({ children }) => {
case attempt.attempt_status === ExamStatus.READY_TO_SUBMIT:
return ;
case attempt.attempt_status === ExamStatus.SUBMITTED:
- return examType === ExamType.PROCTORED
- ?
- : ;
+ return ;
case attempt.attempt_status === ExamStatus.VERIFIED:
- return ;
+ return ;
case attempt.attempt_status === ExamStatus.REJECTED:
return ;
case attempt.attempt_status === ExamStatus.ERROR:
- return ;
+ return ;
case attempt.attempt_status === ExamStatus.READY_TO_RESUME:
return ;
default:
diff --git a/src/instructions/onboarding_exam/ErrorOnboardingExamInstructions.jsx b/src/instructions/onboarding_exam/ErrorOnboardingExamInstructions.jsx
new file mode 100644
index 00000000..d6f264b2
--- /dev/null
+++ b/src/instructions/onboarding_exam/ErrorOnboardingExamInstructions.jsx
@@ -0,0 +1,39 @@
+import React, { useContext } from 'react';
+import { FormattedMessage } from '@edx/frontend-platform/i18n';
+import { Button } from '@edx/paragon';
+import ExamStateContext from '../../context';
+
+const ErrorOnboardingExamInstructions = () => {
+ const state = useContext(ExamStateContext);
+ const { resetExam } = state;
+
+ return (
+
+ );
+};
+
+export default ErrorOnboardingExamInstructions;
diff --git a/src/instructions/onboarding_exam/SubmittedOnboardingExamInstructions.jsx b/src/instructions/onboarding_exam/SubmittedOnboardingExamInstructions.jsx
new file mode 100644
index 00000000..506a55d7
--- /dev/null
+++ b/src/instructions/onboarding_exam/SubmittedOnboardingExamInstructions.jsx
@@ -0,0 +1,98 @@
+import React, { useContext } from 'react';
+import { FormattedMessage } from '@edx/frontend-platform/i18n';
+import { Button, MailtoLink, useToggle } from '@edx/paragon';
+import ExamStateContext from '../../context';
+
+const SubmittedOnboardingExamInstructions = () => {
+ const [isConfirm, confirm] = useToggle(false);
+ const state = useContext(ExamStateContext);
+ const { proctoringSettings, resetExam } = state;
+ const {
+ learner_notification_from_email: learnerNotificationFromEmail,
+ integration_specific_email: integrationSpecificEmail,
+ } = proctoringSettings || {};
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ {learnerNotificationFromEmail}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {integrationSpecificEmail}
+
+
+
+
+ );
+};
+
+export default SubmittedOnboardingExamInstructions;
diff --git a/src/instructions/onboarding_exam/VerifiedOnboardingExamInstructions.jsx b/src/instructions/onboarding_exam/VerifiedOnboardingExamInstructions.jsx
new file mode 100644
index 00000000..2f26ba97
--- /dev/null
+++ b/src/instructions/onboarding_exam/VerifiedOnboardingExamInstructions.jsx
@@ -0,0 +1,44 @@
+import React, { useContext } from 'react';
+import { FormattedMessage } from '@edx/frontend-platform/i18n';
+import { MailtoLink } from '@edx/paragon';
+import ExamStateContext from '../../context';
+
+const VerifiedOnboardingExamInstructions = () => {
+ const state = useContext(ExamStateContext);
+ const {
+ integration_specific_email: integrationSpecificEmail,
+ } = state.proctoringSettings || {};
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ {integrationSpecificEmail}
+
+
+
+
+ );
+};
+
+export default VerifiedOnboardingExamInstructions;
diff --git a/src/instructions/onboarding_exam/index.js b/src/instructions/onboarding_exam/index.js
index 47865c94..971f4280 100644
--- a/src/instructions/onboarding_exam/index.js
+++ b/src/instructions/onboarding_exam/index.js
@@ -1,2 +1,5 @@
export { default as EntranceOnboardingExamInstructions } from './EntranceOnboardingExamInstructions';
export { default as RejectedOnboardingExamInstructions } from './RejectedOnboardingExamInstructions';
+export { default as ErrorOnboardingExamInstructions } from './ErrorOnboardingExamInstructions';
+export { default as SubmittedOnboardingExamInstructions } from './SubmittedOnboardingExamInstructions';
+export { default as VerifiedOnboardingExamInstructions } from './VerifiedOnboardingExamInstructions';
diff --git a/src/instructions/practice_exam/ErrorPracticeExamInstructions.jsx b/src/instructions/practice_exam/ErrorPracticeExamInstructions.jsx
new file mode 100644
index 00000000..1b3de41f
--- /dev/null
+++ b/src/instructions/practice_exam/ErrorPracticeExamInstructions.jsx
@@ -0,0 +1,51 @@
+import React, { useContext } from 'react';
+import { FormattedMessage } from '@edx/frontend-platform/i18n';
+import { Button } from '@edx/paragon';
+import ExamStateContext from '../../context';
+
+const ErrorPracticeExamInstructions = () => {
+ const state = useContext(ExamStateContext);
+ const { resetExam } = state;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default ErrorPracticeExamInstructions;
diff --git a/src/instructions/practice_exam/SubmittedPracticeExamInstructions.jsx b/src/instructions/practice_exam/SubmittedPracticeExamInstructions.jsx
new file mode 100644
index 00000000..5ab89fc0
--- /dev/null
+++ b/src/instructions/practice_exam/SubmittedPracticeExamInstructions.jsx
@@ -0,0 +1,39 @@
+import React, { useContext } from 'react';
+import { FormattedMessage } from '@edx/frontend-platform/i18n';
+import { Button } from '@edx/paragon';
+import ExamStateContext from '../../context';
+
+const SubmittedPracticeExamInstructions = () => {
+ const state = useContext(ExamStateContext);
+ const { resetExam } = state;
+
+ return (
+
+ );
+};
+
+export default SubmittedPracticeExamInstructions;
diff --git a/src/instructions/practice_exam/index.js b/src/instructions/practice_exam/index.js
index 51b782d7..a7d57f0a 100644
--- a/src/instructions/practice_exam/index.js
+++ b/src/instructions/practice_exam/index.js
@@ -1 +1,3 @@
-export { default } from './EntrancePracticeExamInstructions';
+export { default as EntrancePracticeExamInstructions } from './EntrancePracticeExamInstructions';
+export { default as ErrorPracticeExamInstructions } from './ErrorPracticeExamInstructions';
+export { default as SubmittedPracticeExamInstructions } from './SubmittedPracticeExamInstructions';
diff --git a/src/instructions/proctored_exam/EntranceProctoredExamInstructions.jsx b/src/instructions/proctored_exam/EntranceProctoredExamInstructions.jsx
index 1ad69ac2..fe9080f9 100644
--- a/src/instructions/proctored_exam/EntranceProctoredExamInstructions.jsx
+++ b/src/instructions/proctored_exam/EntranceProctoredExamInstructions.jsx
@@ -8,8 +8,8 @@ import SkipProctoredExamButton from './SkipProctoredExamButton';
const EntranceProctoredExamInstructions = ({ skipProctoredExam }) => {
const state = useContext(ExamStateContext);
- const { exam, createProctoredExamAttempt } = state;
- const { attempt, allow_proctoring_opt_out: allowProctoringOptOut } = exam || {};
+ const { exam, createProctoredExamAttempt, allowProctoringOptOut } = state;
+ const { attempt } = exam || {};
const { total_time: totalTime = 0 } = attempt;
return (
diff --git a/src/instructions/proctored_exam/ErrorProctoredExamInstructions.jsx b/src/instructions/proctored_exam/ErrorProctoredExamInstructions.jsx
index 32aa4d0b..a108f073 100644
--- a/src/instructions/proctored_exam/ErrorProctoredExamInstructions.jsx
+++ b/src/instructions/proctored_exam/ErrorProctoredExamInstructions.jsx
@@ -1,8 +1,7 @@
import React, { useContext } from 'react';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
-import { Container, Hyperlink, MailtoLink } from '@edx/paragon';
+import { Hyperlink, MailtoLink } from '@edx/paragon';
import ExamStateContext from '../../context';
-import Footer from './Footer';
const ErrorProctoredExamInstructions = () => {
const state = useContext(ExamStateContext);
@@ -39,18 +38,15 @@ const ErrorProctoredExamInstructions = () => {
return (
-
-
-
-
-
- {renderBody()}
-
-
-
+
+
+
+
+ {renderBody()}
+
);
};
diff --git a/src/instructions/proctored_exam/ProctoredExamInstructions.test.jsx b/src/instructions/proctored_exam/ProctoredExamInstructions.test.jsx
index 5dbeb890..acb0da60 100644
--- a/src/instructions/proctored_exam/ProctoredExamInstructions.test.jsx
+++ b/src/instructions/proctored_exam/ProctoredExamInstructions.test.jsx
@@ -21,6 +21,7 @@ describe('SequenceExamWrapper', () => {
examState: {
isLoading: false,
activeAttempt: null,
+ allowProctoringOptOut: true,
proctoringSettings: {
platform_name: 'Your Platform',
},
@@ -30,7 +31,6 @@ describe('SequenceExamWrapper', () => {
},
exam: {
type: ExamType.PROCTORED,
- allow_proctoring_opt_out: true,
is_proctored: true,
time_limit_mins: 30,
attempt: {},
diff --git a/src/instructions/proctored_exam/SubmittedProctoredExamInstructions.jsx b/src/instructions/proctored_exam/SubmittedProctoredExamInstructions.jsx
index 6e56bc45..b75b3693 100644
--- a/src/instructions/proctored_exam/SubmittedProctoredExamInstructions.jsx
+++ b/src/instructions/proctored_exam/SubmittedProctoredExamInstructions.jsx
@@ -1,51 +1,46 @@
import React from 'react';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
-import { Container } from '@edx/paragon';
-import Footer from './Footer';
const SubmittedProctoredExamInstructions = () => (
-
-
+
+
+
+
-
-
+
+
+
-
-
-
+
+
+
+
+
+
);
diff --git a/src/instructions/proctored_exam/VerifiedProctoredExamInstructions.jsx b/src/instructions/proctored_exam/VerifiedProctoredExamInstructions.jsx
index c0137ed1..11aea312 100644
--- a/src/instructions/proctored_exam/VerifiedProctoredExamInstructions.jsx
+++ b/src/instructions/proctored_exam/VerifiedProctoredExamInstructions.jsx
@@ -1,20 +1,15 @@
import React from 'react';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
-import { Container } from '@edx/paragon';
-import Footer from './Footer';
const VerifiedProctoredExamInstructions = () => (
-
-
-
-
-
-
+
+
+
);
diff --git a/src/instructions/proctored_exam/prerequisites-instructions/index.jsx b/src/instructions/proctored_exam/prerequisites-instructions/index.jsx
index 60ae06c0..e3ebdc6d 100644
--- a/src/instructions/proctored_exam/prerequisites-instructions/index.jsx
+++ b/src/instructions/proctored_exam/prerequisites-instructions/index.jsx
@@ -9,8 +9,8 @@ import Footer from '../Footer';
const PrerequisitesProctoredExamInstructions = ({ skipProctoredExam }) => {
const state = useContext(ExamStateContext);
- const { exam, proctoringSettings } = state;
- const { prerequisite_status: prerequisitesData, allow_proctoring_opt_out: allowProctoringOptOut } = exam;
+ const { exam, proctoringSettings, allowProctoringOptOut } = state;
+ const { prerequisite_status: prerequisitesData } = exam;
const { pending_prerequisites: pending, failed_prerequisites: failed } = prerequisitesData;
const { platform_name: platformName } = proctoringSettings;
diff --git a/src/instructions/SubmittedExamInstructions.jsx b/src/instructions/timed_exam/SubmittedTimedExamInstructions.jsx
similarity index 81%
rename from src/instructions/SubmittedExamInstructions.jsx
rename to src/instructions/timed_exam/SubmittedTimedExamInstructions.jsx
index a9bf3897..81a66cd7 100644
--- a/src/instructions/SubmittedExamInstructions.jsx
+++ b/src/instructions/timed_exam/SubmittedTimedExamInstructions.jsx
@@ -1,13 +1,12 @@
import React, { useContext } from 'react';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
-import { Container } from '@edx/paragon';
-import ExamStateContext from '../context';
+import ExamStateContext from '../../context';
-const SubmittedExamInstructions = () => {
+const SubmittedTimedExamInstructions = () => {
const state = useContext(ExamStateContext);
return (
-
+ <>
{state.timeIsOver
? (
@@ -30,8 +29,8 @@ const SubmittedExamInstructions = () => {
+ ' but you cannot change your answers.'}
/>
-
+ >
);
};
-export default SubmittedExamInstructions;
+export default SubmittedTimedExamInstructions;
diff --git a/src/instructions/timed_exam/index.jsx b/src/instructions/timed_exam/index.jsx
index b3113b03..0fc34a67 100644
--- a/src/instructions/timed_exam/index.jsx
+++ b/src/instructions/timed_exam/index.jsx
@@ -1,3 +1,4 @@
export { default as StartTimedExamInstructions } from './StartTimedExamInstructions';
export { default as SubmitTimedExamInstructions } from './SubmitTimedExamInstructions';
+export { default as SubmittedTimedExamInstructions } from './SubmittedTimedExamInstructions';
export { default as TimedExamFooter } from './TimedExamFooter';