Skip to content

Commit

Permalink
feat: [BD-26][EDUCATOR-5811] Add missed proctored exam instruction pa…
Browse files Browse the repository at this point in the history
…ges (#18)

* feat: [BD-26] Add missed proctored exam instruction pages

* test: add test for ready to resume and error instructions

* fix: use values from backend instead of hardcoding

Co-authored-by: Viktor Rusakov <[email protected]>
  • Loading branch information
UvgenGen and viktorrusakov authored Jun 8, 2021
1 parent 3106b5a commit 2b9effb
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 8 deletions.
2 changes: 2 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export const ExamStatus = Object.freeze({
TIMED_OUT: 'timed_out',
VERIFIED: 'verified',
REJECTED: 'rejected',
ERROR: 'error',
READY_TO_RESUME: 'ready_to_resume',
});

export const IS_STARTED_STATUS = (status) => [ExamStatus.STARTED, ExamStatus.READY_TO_SUBMIT].includes(status);
Expand Down
72 changes: 71 additions & 1 deletion src/instructions/Instructions.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import '@testing-library/jest-dom';
import React from 'react';
import Instructions from './index';
import { store, getExamAttemptsData, startExam } from '../data';
import { render } from '../setupTest';
import { render, screen } from '../setupTest';
import { ExamStateProvider } from '../index';

jest.mock('../data', () => ({
Expand Down Expand Up @@ -147,4 +147,74 @@ describe('SequenceExamWrapper', () => {

expect(getByTestId('pending-prerequisites')).toBeInTheDocument();
});

it('Instructions for error status', () => {
store.getState = () => ({
examState: {
isLoading: false,
proctoringSettings: {
link_urls: '',
},
verification: {
status: 'none',
can_verify: true,
},
activeAttempt: {
attempt_status: 'error',
},
exam: {
time_limit_mins: 30,
attempt: {
attempt_status: 'error',
},
},
},
});

render(
<ExamStateProvider>
<Instructions>
<div data-testid="sequence-content">Sequence</div>
</Instructions>
</ExamStateProvider>,
{ store },
);
expect(screen.getByText('Error with proctored exam')).toBeInTheDocument();
});

it('Instructions for ready to resume status', () => {
store.getState = () => ({
examState: {
isLoading: false,
proctoringSettings: {
link_urls: '',
platform_name: 'Platform Name',
},
verification: {
status: 'none',
can_verify: true,
},
activeAttempt: {
attempt_status: 'ready_to_resume',
},
exam: {
time_limit_mins: 30,
attempt: {
attempt_status: 'ready_to_resume',
},
},
},
});

render(
<ExamStateProvider>
<Instructions>
<div data-testid="sequence-content">Sequence</div>
</Instructions>
</ExamStateProvider>,
{ store },
);
expect(screen.getByText('Your exam is ready to be resumed.')).toBeInTheDocument();
expect(screen.getByTestId('start-exam-button')).toHaveTextContent('Continue to my proctored exam.');
});
});
5 changes: 5 additions & 0 deletions src/instructions/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import StartExamInstructions from './StartExamInstructions';
import SubmitExamInstructions from './SubmitExamInstructions';
import SubmittedExamInstructions from './SubmittedExamInstructions';
import {
ErrorProctoredExamInstructions,
EntranceProctoredExamInstructions,
VerificationProctoredExamInstructions,
SubmitProctoredExamInstructions,
Expand Down Expand Up @@ -61,6 +62,10 @@ const Instructions = ({ children }) => {
return <VerifiedProctoredExamInstructions />;
case attempt.attempt_status === ExamStatus.REJECTED:
return <RejectedProctoredExamInstructions />;
case attempt.attempt_status === ExamStatus.ERROR:
return <ErrorProctoredExamInstructions />;
case attempt.attempt_status === ExamStatus.READY_TO_RESUME:
return <EntranceProctoredExamInstructions />;
default:
return children;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,45 @@
import React, { useContext } from 'react';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { Button, Container } from '@edx/paragon';
import { ExamStatus } from '../../constants';
import ExamStateContext from '../../context';
import Footer from './Footer';

const EntranceProctoredExamInstructions = () => {
const state = useContext(ExamStateContext);
const { startProctoringExam } = state;
const { exam, startProctoringExam } = state;
const { attempt } = exam || {};
const { total_time: totalTime = 0 } = attempt;

return (
<div>
<Container className="border py-5 mb-4">
<div className="h3" data-testid="exam-instructions-title">
<FormattedMessage
id="exam.EntranceProctoredExamInstructions.title"
defaultMessage="This exam is proctored"
/>
</div>
{ exam.attempt.attempt_status === ExamStatus.READY_TO_RESUME ? (
<div>
<div className="h3" data-testid="exam-instructions-title">
<FormattedMessage
id="exam.ReadyToResumeProctoredExamInstructions.title"
defaultMessage="Your exam is ready to be resumed."
/>
</div>
<p>
<FormattedMessage
id="exam.ReadyToResumeProctoredExamInstructions.text"
defaultMessage="You will have {totalTime} to complete your exam."
values={{ totalTime }}
/>
</p>
</div>
) : (
<p>
<div className="h3" data-testid="exam-instructions-title">
<FormattedMessage
id="exam.EntranceProctoredExamInstructions.title"
defaultMessage="This exam is proctored"
/>
</div>
</p>
)}
<p>
<FormattedMessage
id="exam.EntranceProctoredExamInstructions.text1"
Expand Down
58 changes: 58 additions & 0 deletions src/instructions/proctored_exam/ErrorProctoredExamInstructions.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React, { useContext } from 'react';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { Container, Hyperlink, MailtoLink } from '@edx/paragon';
import ExamStateContext from '../../context';
import Footer from './Footer';

const ErrorProctoredExamInstructions = () => {
const state = useContext(ExamStateContext);
const {
link_urls: linkUrls,
platform_name: platformName,
proctoring_escalation_email: proctoringEscalationEmail,
} = state.proctoringSettings || {};
const contactUsUrl = linkUrls && linkUrls.contact_us;

const renderBody = () => {
if (proctoringEscalationEmail) {
return (
<FormattedMessage
id="exam.ErrorProctoredExamInstructions.text1"
defaultMessage={'A system error has occurred with your proctored exam. '
+ 'Please reach out to your course team at {supportLink} for assistance, '
+ 'and return to the exam once you receive further instructions.'}
values={{ supportLink: <MailtoLink to={proctoringEscalationEmail}>{proctoringEscalationEmail}</MailtoLink> }}
/>
);
}

return (
<FormattedMessage
id="exam.ErrorProctoredExamInstructions.text2"
defaultMessage={'A system error has occurred with your proctored exam. '
+ 'Please reach out to {supportLink} for assistance, and return to '
+ 'the exam once you receive further instructions.'}
values={{ supportLink: <Hyperlink href={contactUsUrl} target="_blank">{platformName} Support</Hyperlink> }}
/>
);
};

return (
<div>
<Container className="border py-5 mb-4">
<div className="h3">
<FormattedMessage
id="exam.ErrorProctoredExamInstructions.title"
defaultMessage="Error with proctored exam"
/>
</div>
<p className="mb-0">
{renderBody()}
</p>
</Container>
<Footer />
</div>
);
};

export default ErrorProctoredExamInstructions;
1 change: 1 addition & 0 deletions src/instructions/proctored_exam/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { default as ErrorProctoredExamInstructions } from './ErrorProctoredExamInstructions';
export { default as EntranceProctoredExamInstructions } from './EntranceProctoredExamInstructions';
export { default as RejectedProctoredExamInstructions } from './RejectedProctoredExamInstructions';
export { default as SubmitProctoredExamInstructions } from './SubmitProctoredExamInstructions';
Expand Down

0 comments on commit 2b9effb

Please sign in to comment.