-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: start lti proctoring software (#101)
* feat: start lti proctoring software
- Loading branch information
1 parent
431ab82
commit 34b88df
Showing
5 changed files
with
255 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,16 @@ | ||
import '@testing-library/jest-dom'; | ||
import { Factory } from 'rosie'; | ||
import React from 'react'; | ||
import { fireEvent } from '@testing-library/dom'; | ||
import { fireEvent, waitFor } from '@testing-library/dom'; | ||
import Instructions from './index'; | ||
import { store, getExamAttemptsData, startTimedExam } from '../data'; | ||
import { pollExamAttempt } from '../data/api'; | ||
import { continueExam, submitExam } from '../data/thunks'; | ||
import Emitter from '../data/emitter'; | ||
import { TIMER_REACHED_NULL } from '../timer/events'; | ||
import { render, screen, act } from '../setupTest'; | ||
import { | ||
render, screen, act, initializeMockApp, | ||
} from '../setupTest'; | ||
import ExamStateProvider from '../core/ExamStateProvider'; | ||
import { | ||
ExamStatus, ExamType, INCOMPLETE_STATUSES, | ||
|
@@ -23,14 +26,23 @@ jest.mock('../data/thunks', () => ({ | |
getExamReviewPolicy: jest.fn(), | ||
submitExam: jest.fn(), | ||
})); | ||
jest.mock('../data/api', () => ({ | ||
pollExamAttempt: jest.fn(), | ||
softwareDownloadAttempt: jest.fn(), | ||
})); | ||
continueExam.mockReturnValue(jest.fn()); | ||
submitExam.mockReturnValue(jest.fn()); | ||
getExamAttemptsData.mockReturnValue(jest.fn()); | ||
startTimedExam.mockReturnValue(jest.fn()); | ||
pollExamAttempt.mockReturnValue(Promise.resolve({})); | ||
store.subscribe = jest.fn(); | ||
store.dispatch = jest.fn(); | ||
|
||
describe('SequenceExamWrapper', () => { | ||
beforeEach(() => { | ||
initializeMockApp(); | ||
}); | ||
|
||
it('Start exam instructions can be successfully rendered', () => { | ||
store.getState = () => ({ examState: Factory.build('examState') }); | ||
|
||
|
@@ -709,7 +721,116 @@ describe('SequenceExamWrapper', () => { | |
expect(screen.getByText('You have submitted this proctored exam for review')).toBeInTheDocument(); | ||
}); | ||
|
||
it('Shows download software proctored exam instructions if attempt status is created', () => { | ||
it('Shows correct download instructions for LTI provider if attempt status is created', () => { | ||
store.getState = () => ({ | ||
examState: Factory.build('examState', { | ||
activeAttempt: {}, | ||
proctoringSettings: Factory.build('proctoringSettings', { | ||
provider_name: 'LTI Provider', | ||
provider_tech_support_email: '[email protected]', | ||
provider_tech_support_phone: '+123456789', | ||
}), | ||
exam: Factory.build('exam', { | ||
is_proctored: true, | ||
type: ExamType.PROCTORED, | ||
attempt: Factory.build('attempt', { | ||
attempt_status: ExamStatus.CREATED, | ||
}), | ||
}), | ||
}), | ||
}); | ||
|
||
render( | ||
<ExamStateProvider> | ||
<Instructions> | ||
<div>Sequence</div> | ||
</Instructions> | ||
</ExamStateProvider>, | ||
{ store }, | ||
); | ||
|
||
expect(screen.getByText( | ||
'If you have issues relating to proctoring, you can contact ' | ||
+ 'LTI Provider technical support by emailing [email protected] or by calling +123456789.', | ||
)).toBeInTheDocument(); | ||
expect(screen.getByText('Set up and start your proctored exam.')).toBeInTheDocument(); | ||
expect(screen.getByText('Start System Check')).toBeInTheDocument(); | ||
expect(screen.getByText('Start Exam')).toBeInTheDocument(); | ||
}); | ||
|
||
it('Hides support contact info on download instructions for LTI provider if not provided', () => { | ||
store.getState = () => ({ | ||
examState: Factory.build('examState', { | ||
activeAttempt: {}, | ||
proctoringSettings: Factory.build('proctoringSettings', { | ||
provider_name: 'LTI Provider', | ||
}), | ||
exam: Factory.build('exam', { | ||
is_proctored: true, | ||
type: ExamType.PROCTORED, | ||
attempt: Factory.build('attempt', { | ||
attempt_status: ExamStatus.CREATED, | ||
}), | ||
}), | ||
}), | ||
}); | ||
|
||
render( | ||
<ExamStateProvider> | ||
<Instructions> | ||
<div>Sequence</div> | ||
</Instructions> | ||
</ExamStateProvider>, | ||
{ store }, | ||
); | ||
|
||
expect(screen.queryByText('If you have issues relating to proctoring, you can contact LTI Provider')).toBeNull(); | ||
expect(screen.getByText('Set up and start your proctored exam.')).toBeInTheDocument(); | ||
expect(screen.getByText('Start System Check')).toBeInTheDocument(); | ||
expect(screen.getByText('Start Exam')).toBeInTheDocument(); | ||
}); | ||
|
||
it('Initiates an LTI launch in a new window when the user clicks the System Check button', async () => { | ||
const windowSpy = jest.spyOn(window, 'open'); | ||
windowSpy.mockImplementation(() => ({})); | ||
store.getState = () => ({ | ||
examState: Factory.build('examState', { | ||
activeAttempt: {}, | ||
proctoringSettings: Factory.build('proctoringSettings', { | ||
provider_name: 'LTI Provider', | ||
provider_tech_support_email: '[email protected]', | ||
provider_tech_support_phone: '+123456789', | ||
}), | ||
exam: Factory.build('exam', { | ||
is_proctored: true, | ||
type: ExamType.PROCTORED, | ||
attempt: Factory.build('attempt', { | ||
attempt_id: 4321, | ||
attempt_status: ExamStatus.CREATED, | ||
}), | ||
}), | ||
getExamAttemptsData, | ||
}), | ||
}); | ||
|
||
render( | ||
<ExamStateProvider> | ||
<Instructions> | ||
<div>Sequence</div> | ||
</Instructions> | ||
</ExamStateProvider>, | ||
{ store }, | ||
); | ||
fireEvent.click(screen.getByText('Start System Check')); | ||
await waitFor(() => { expect(windowSpy).toHaveBeenCalledWith('http://localhost:18740/lti/start_proctoring/4321', '_blank'); }); | ||
|
||
// also validate start button works | ||
pollExamAttempt.mockReturnValue(Promise.resolve({ status: ExamStatus.READY_TO_START })); | ||
fireEvent.click(screen.getByText('Start Exam')); | ||
await waitFor(() => { expect(getExamAttemptsData).toHaveBeenCalled(); }); | ||
}); | ||
|
||
it('Shows correct download instructions for legacy rest provider if attempt status is created', () => { | ||
const instructions = [ | ||
'instruction 1', | ||
'instruction 2', | ||
|
@@ -729,8 +850,10 @@ describe('SequenceExamWrapper', () => { | |
exam: Factory.build('exam', { | ||
is_proctored: true, | ||
type: ExamType.PROCTORED, | ||
use_legacy_attempt_api: true, | ||
attempt: Factory.build('attempt', { | ||
attempt_status: ExamStatus.CREATED, | ||
use_legacy_attempt_api: true, | ||
}), | ||
}), | ||
}), | ||
|
@@ -755,6 +878,41 @@ describe('SequenceExamWrapper', () => { | |
}); | ||
}); | ||
|
||
it('Shows correct download instructions for legacy rpnow provider if attempt status is created', () => { | ||
store.getState = () => ({ | ||
examState: Factory.build('examState', { | ||
activeAttempt: {}, | ||
proctoringSettings: Factory.build('proctoringSettings', { | ||
provider_name: 'Provider Name', | ||
exam_proctoring_backend: {}, | ||
}), | ||
exam: Factory.build('exam', { | ||
is_proctored: true, | ||
type: ExamType.PROCTORED, | ||
use_legacy_attempt_api: true, | ||
attempt: Factory.build('attempt', { | ||
attempt_status: ExamStatus.CREATED, | ||
attempt_code: '1234-5678-9012-3456', | ||
use_legacy_attempt_api: true, | ||
}), | ||
}), | ||
}), | ||
}); | ||
|
||
render( | ||
<ExamStateProvider> | ||
<Instructions> | ||
<div>Sequence</div> | ||
</Instructions> | ||
</ExamStateProvider>, | ||
{ store }, | ||
); | ||
expect(screen.getByDisplayValue('1234-5678-9012-3456')).toBeInTheDocument(); | ||
expect(screen.getByText('For security and exam integrity reasons, ' | ||
+ 'we ask you to sign in to your edX account. Then we will ' | ||
+ 'direct you to the RPNow proctoring experience.')).toBeInTheDocument(); | ||
}); | ||
|
||
it('Shows error message if receives unknown attempt status', () => { | ||
store.getState = () => ({ | ||
examState: Factory.build('examState', { | ||
|
@@ -806,7 +964,7 @@ describe('SequenceExamWrapper', () => { | |
expect(screen.getByText('You must adhere to the following rules while you complete this exam.')).toBeInTheDocument(); | ||
}); | ||
|
||
it('Shows loading spinner while waiting to start exam', () => { | ||
it('Shows loading spinner while waiting to start exam', async () => { | ||
store.getState = () => ({ | ||
examState: Factory.build('examState', { | ||
activeAttempt: {}, | ||
|
@@ -831,6 +989,7 @@ describe('SequenceExamWrapper', () => { | |
); | ||
|
||
fireEvent.click(screen.getByTestId('start-exam-button')); | ||
waitFor(() => expect(getExamAttemptsData).toHaveBeenCalled()); | ||
expect(screen.getByTestId('exam-loading-spinner')).toBeInTheDocument(); | ||
}); | ||
}); |
47 changes: 47 additions & 0 deletions
47
src/instructions/proctored_exam/download-instructions/LtiProviderInstructions.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import { FormattedMessage } from '@edx/frontend-platform/i18n'; | ||
|
||
const LtiProviderExamInstructions = ({ | ||
providerName, supportEmail, supportPhone, | ||
}) => ( | ||
<> | ||
<p> | ||
<FormattedMessage | ||
id="exam.DownloadSoftwareProctoredExamInstructions.text1" | ||
defaultMessage={'Note: As part of the proctored exam setup, you ' | ||
+ 'will be asked to verify your identity. Before you begin, make ' | ||
+ 'sure you are on a computer with a webcam, and that you have a ' | ||
+ 'valid form of photo identification such as a driver’s license or passport.'} | ||
/> | ||
</p> | ||
{supportEmail && supportPhone && ( | ||
<p> | ||
<FormattedMessage | ||
id="exam.DownloadSoftwareProctoredExamInstructions.supportText" | ||
defaultMessage={'If you have issues relating to proctoring, you can contact ' | ||
+ '{providerName} technical support by emailing {supportEmail} or by calling {supportPhone}.'} | ||
values={{ | ||
providerName, | ||
supportEmail, | ||
supportPhone, | ||
}} | ||
/> | ||
</p> | ||
)} | ||
</> | ||
); | ||
|
||
LtiProviderExamInstructions.propTypes = { | ||
providerName: PropTypes.string, | ||
supportEmail: PropTypes.string, | ||
supportPhone: PropTypes.string, | ||
}; | ||
|
||
LtiProviderExamInstructions.defaultProps = { | ||
providerName: '', | ||
supportEmail: '', | ||
supportPhone: '', | ||
}; | ||
|
||
export default LtiProviderExamInstructions; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.