Skip to content
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

Merged
merged 2 commits into from
Oct 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/instructions/Instructions.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -502,14 +502,15 @@ describe('SequenceExamWrapper', () => {
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', () => {
it('Shows submitted practice exam instructions if exam is onboarding and attempt status is submitted on legacy LTI exams', () => {
store.getState = () => ({
specialExams: Factory.build('specialExams', {
activeAttempt: {},
exam: Factory.build('exam', {
is_proctored: true,
type: ExamType.PRACTICE,
attempt: Factory.build('attempt', {
use_legacy_attempt_api: true,
attempt_status: ExamStatus.SUBMITTED,
Comment on lines +513 to 514
Copy link
Member Author

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.

Copy link
Contributor

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

}),
}),
Expand Down
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this boolean value come from backend API?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The 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>
Expand All @@ -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>
);
};
Expand Down
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();
});
});
});
Loading