-
Notifications
You must be signed in to change notification settings - Fork 18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Remove retry button in LTI Proctorio exams #155
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,20 @@ | ||
import React from 'react'; | ||
import { useDispatch } from 'react-redux'; | ||
import { useDispatch, useSelector } from 'react-redux'; | ||
import { FormattedMessage } from '@edx/frontend-platform/i18n'; | ||
import { Button } from '@openedx/paragon'; | ||
|
||
import { resetExam } from '../../data'; | ||
import { ExamStatus } from '../../constants'; | ||
|
||
const SubmittedPracticeExamInstructions = () => { | ||
const dispatch = useDispatch(); | ||
const { exam } = useSelector(state => state.specialExams); | ||
|
||
// It does not show the reload button if the exam is submitted and not legacy | ||
const showRetryButton = !( | ||
exam.attempt?.attempt_status === ExamStatus.SUBMITTED | ||
&& !exam.attempt?.use_legacy_attempt_api | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this boolean value come from backend API? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think so. I was using this usage as a reference: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. It's using setExamState() which uses fetchExamAttemptsData() to call an API. |
||
); | ||
|
||
return ( | ||
<div> | ||
|
@@ -23,16 +31,18 @@ const SubmittedPracticeExamInstructions = () => { | |
+ 'completed this practice exam and can continue with your course work.'} | ||
/> | ||
</p> | ||
<Button | ||
data-testid="retry-exam-button" | ||
variant="primary" | ||
onClick={() => dispatch(resetExam())} | ||
> | ||
<FormattedMessage | ||
id="exam.SubmittedPracticeExamInstructions.retryExamButton" | ||
defaultMessage="Retry my exam" | ||
/> | ||
</Button> | ||
{showRetryButton ? ( | ||
<Button | ||
data-testid="retry-exam-button" | ||
variant="primary" | ||
onClick={() => dispatch(resetExam())} | ||
> | ||
<FormattedMessage | ||
id="exam.SubmittedPracticeExamInstructions.retryExamButton" | ||
defaultMessage="Retry my exam" | ||
/> | ||
</Button> | ||
) : null} | ||
</div> | ||
); | ||
}; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
import React from 'react'; | ||
import { Factory } from 'rosie'; | ||
import { SubmittedPracticeExamInstructions } from './index'; | ||
import { | ||
render, screen, initializeTestStore, fireEvent, | ||
} from '../../setupTest'; | ||
import { ExamStatus, ExamType } from '../../constants'; | ||
|
||
const mockresetReturn = {}; | ||
const mockDispatch = jest.fn(); | ||
|
||
jest.mock('react-redux', () => ({ | ||
...jest.requireActual('react-redux'), | ||
useDispatch: () => mockDispatch, | ||
})); | ||
|
||
jest.mock('../../data', () => ({ | ||
...jest.requireActual('../../data'), | ||
resetExam: () => mockresetReturn, | ||
})); | ||
|
||
describe('ExamTimerBlock', () => { | ||
afterEach(() => { | ||
jest.resetAllMocks(); | ||
}); | ||
|
||
describe('when the exam is not proctored', () => { | ||
beforeEach(() => { | ||
const preloadedState = { | ||
specialExams: { | ||
exam: Factory.build('exam', { | ||
is_proctored: false, | ||
type: ExamType.ONBOARDING, | ||
attempt: Factory.build('attempt'), | ||
}), | ||
}, | ||
}; | ||
initializeTestStore(preloadedState); | ||
|
||
render( | ||
<SubmittedPracticeExamInstructions />, | ||
); | ||
}); | ||
|
||
it('renders the component correctly', async () => { | ||
expect(screen.getByText('You have submitted this practice proctored exam')).toBeInTheDocument(); | ||
expect(screen.getByText( | ||
'Practice exams do not affect your grade. You have ' | ||
+ 'completed this practice exam and can continue with your course work.', | ||
)).toBeInTheDocument(); | ||
expect(screen.queryByTestId('retry-exam-button')).toBeInTheDocument(); | ||
}); | ||
|
||
it('calls resetExam() when clicking the retry button', () => { | ||
expect(mockDispatch).not.toHaveBeenCalled(); | ||
|
||
fireEvent.click(screen.getByTestId('retry-exam-button')); | ||
|
||
expect(mockDispatch).toHaveBeenCalledTimes(1); | ||
expect(mockDispatch).toHaveBeenCalledWith(mockresetReturn); | ||
}); | ||
}); | ||
|
||
describe('when the exam is not proctored', () => { | ||
beforeEach(() => { | ||
const preloadedState = { | ||
specialExams: { | ||
exam: Factory.build('exam', { | ||
is_proctored: false, | ||
type: ExamType.ONBOARDING, | ||
attempt: Factory.build('attempt'), | ||
}), | ||
}, | ||
}; | ||
initializeTestStore(preloadedState); | ||
|
||
render( | ||
<SubmittedPracticeExamInstructions />, | ||
); | ||
}); | ||
|
||
it('renders the component correctly', async () => { | ||
expect(screen.getByText('You have submitted this practice proctored exam')).toBeInTheDocument(); | ||
expect(screen.getByText( | ||
'Practice exams do not affect your grade. You have ' | ||
+ 'completed this practice exam and can continue with your course work.', | ||
)).toBeInTheDocument(); | ||
expect(screen.queryByTestId('retry-exam-button')).toBeInTheDocument(); | ||
}); | ||
|
||
it('calls resetExam() when clicking the retry button', () => { | ||
expect(mockDispatch).not.toHaveBeenCalled(); | ||
|
||
fireEvent.click(screen.getByTestId('retry-exam-button')); | ||
|
||
expect(mockDispatch).toHaveBeenCalledTimes(1); | ||
expect(mockDispatch).toHaveBeenCalledWith(mockresetReturn); | ||
}); | ||
}); | ||
|
||
describe('when a legacy proctoring attempt API is used', () => { | ||
beforeEach(() => { | ||
const preloadedState = { | ||
specialExams: { | ||
exam: Factory.build('exam', { | ||
is_proctored: true, | ||
type: ExamType.PROCTORED, | ||
attempt: Factory.build('attempt', { | ||
use_legacy_attempt_api: true, | ||
}), | ||
}), | ||
}, | ||
}; | ||
initializeTestStore(preloadedState); | ||
|
||
render( | ||
<SubmittedPracticeExamInstructions />, | ||
); | ||
}); | ||
|
||
it('renders the component correctly', async () => { | ||
expect(screen.getByText('You have submitted this practice proctored exam')).toBeInTheDocument(); | ||
expect(screen.getByText( | ||
'Practice exams do not affect your grade. You have ' | ||
+ 'completed this practice exam and can continue with your course work.', | ||
)).toBeInTheDocument(); | ||
expect(screen.queryByTestId('retry-exam-button')).toBeInTheDocument(); | ||
}); | ||
|
||
it('calls resetExam() when clicking the retry button', () => { | ||
expect(mockDispatch).not.toHaveBeenCalled(); | ||
|
||
fireEvent.click(screen.getByTestId('retry-exam-button')); | ||
|
||
expect(mockDispatch).toHaveBeenCalledTimes(1); | ||
expect(mockDispatch).toHaveBeenCalledWith(mockresetReturn); | ||
}); | ||
}); | ||
|
||
describe('when an LTI provider is used but it has an error', () => { | ||
beforeEach(() => { | ||
const preloadedState = { | ||
specialExams: { | ||
exam: Factory.build('exam', { | ||
is_proctored: true, | ||
type: ExamType.PROCTORED, | ||
attempt: Factory.build('attempt', { | ||
use_legacy_attempt_api: false, | ||
attempt_status: ExamStatus.ERROR, | ||
}), | ||
}), | ||
}, | ||
}; | ||
initializeTestStore(preloadedState); | ||
|
||
render( | ||
<SubmittedPracticeExamInstructions />, | ||
); | ||
}); | ||
|
||
it('renders the component correctly', async () => { | ||
expect(screen.getByText('You have submitted this practice proctored exam')).toBeInTheDocument(); | ||
expect(screen.getByText( | ||
'Practice exams do not affect your grade. You have ' | ||
+ 'completed this practice exam and can continue with your course work.', | ||
)).toBeInTheDocument(); | ||
expect(screen.queryByTestId('retry-exam-button')).toBeInTheDocument(); | ||
}); | ||
|
||
it('calls resetExam() when clicking the retry button', () => { | ||
expect(mockDispatch).not.toHaveBeenCalled(); | ||
|
||
fireEvent.click(screen.getByTestId('retry-exam-button')); | ||
|
||
expect(mockDispatch).toHaveBeenCalledTimes(1); | ||
expect(mockDispatch).toHaveBeenCalledWith(mockresetReturn); | ||
}); | ||
}); | ||
|
||
describe('when an LTI provider is used and the exam is submitted', () => { | ||
beforeEach(() => { | ||
const preloadedState = { | ||
specialExams: { | ||
exam: Factory.build('exam', { | ||
is_proctored: true, | ||
type: ExamType.PROCTORED, | ||
attempt: Factory.build('attempt', { | ||
use_legacy_attempt_api: false, | ||
attempt_status: ExamStatus.SUBMITTED, | ||
}), | ||
}), | ||
}, | ||
}; | ||
initializeTestStore(preloadedState); | ||
|
||
render( | ||
<SubmittedPracticeExamInstructions />, | ||
); | ||
}); | ||
|
||
it('doesn\'t show the button if it has an LTI provider', async () => { | ||
expect(screen.getByText('You have submitted this practice proctored exam')).toBeInTheDocument(); | ||
expect(screen.getByText( | ||
'Practice exams do not affect your grade. You have ' | ||
+ 'completed this practice exam and can continue with your course work.', | ||
)).toBeInTheDocument(); | ||
expect(screen.queryByTestId('retry-exam-button')).not.toBeInTheDocument(); | ||
}); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now if a Proctored exam with an LTI provider is submitted no longer shows the "Retry my exam" button. That's the reason for this update.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NIT: we should remove LTI from this since LTI implies the new (not legacy) exam system