From 586a81d6a09fab50943c5b99aa563bf143dd4413 Mon Sep 17 00:00:00 2001
From: Viktor Rusakov <52399399+ViktorRusakov@users.noreply.github.com>
Date: Tue, 15 Jun 2021 18:21:05 +0300
Subject: [PATCH] fix: do not show continue button on ready_to_submit pages if
timer reached 0 (#22)
---
src/instructions/Instructions.test.jsx | 37 +++++++++++--
src/instructions/SubmitInstructions.jsx | 55 ++++++++++++++-----
src/instructions/index.jsx | 2 +-
.../ProctoredExamInstructions.test.jsx | 15 ++++-
.../SubmitProctoredExamInstructions.jsx | 10 +---
.../SubmitTimedExamInstructions.jsx | 11 +---
src/timer/TimerProvider.jsx | 7 +++
src/timer/events.js | 1 +
8 files changed, 96 insertions(+), 42 deletions(-)
diff --git a/src/instructions/Instructions.test.jsx b/src/instructions/Instructions.test.jsx
index fd5bebbe..78ab827c 100644
--- a/src/instructions/Instructions.test.jsx
+++ b/src/instructions/Instructions.test.jsx
@@ -3,7 +3,10 @@ import React from 'react';
import { fireEvent } from '@testing-library/dom';
import Instructions from './index';
import { store, getExamAttemptsData, startTimedExam } from '../data';
-import { render, screen } from '../setupTest';
+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 { ExamStateProvider } from '../index';
import { ExamStatus, ExamType, INCOMPLETE_STATUSES } from '../constants';
@@ -12,6 +15,13 @@ jest.mock('../data', () => ({
getExamAttemptsData: jest.fn(),
startTimedExam: jest.fn(),
}));
+jest.mock('../data/thunks', () => ({
+ continueExam: jest.fn(),
+ getExamReviewPolicy: jest.fn(),
+ submitExam: jest.fn(),
+}));
+continueExam.mockReturnValue(jest.fn());
+submitExam.mockReturnValue(jest.fn());
getExamAttemptsData.mockReturnValue(jest.fn());
startTimedExam.mockReturnValue(jest.fn());
store.subscribe = jest.fn();
@@ -319,7 +329,7 @@ describe('SequenceExamWrapper', () => {
expect(screen.getByTestId('start-exam-button')).toHaveTextContent('Continue to my proctored exam.');
});
- it('Instructions for ready to submit status', () => {
+ it.each([10, 0])('Shows correct instructions when attempt status is ready_to_submit and %s seconds left', async (secondsLeft) => {
store.getState = () => ({
examState: {
isLoading: false,
@@ -329,7 +339,9 @@ describe('SequenceExamWrapper', () => {
can_verify: true,
},
proctoringSettings: {},
- activeAttempt: {},
+ activeAttempt: {
+ time_remaining_seconds: secondsLeft,
+ },
exam: {
type: ExamType.TIMED,
time_limit_mins: 30,
@@ -341,7 +353,7 @@ describe('SequenceExamWrapper', () => {
},
});
- const { getByTestId } = render(
+ const { queryByTestId } = render(
Sequence
@@ -349,7 +361,22 @@ describe('SequenceExamWrapper', () => {
,
{ store },
);
- expect(getByTestId('exam-instructions-title')).toHaveTextContent('Are you sure that you want to submit your timed exam?');
+
+ expect(queryByTestId('exam-instructions-title')).toHaveTextContent('Are you sure that you want to submit your timed exam?');
+ fireEvent.click(queryByTestId('end-exam-button'));
+ expect(submitExam).toHaveBeenCalled();
+ const continueButton = queryByTestId('continue-exam-button');
+ if (secondsLeft > 0) {
+ expect(continueButton).toBeInTheDocument();
+ fireEvent.click(continueButton);
+ expect(continueExam).toHaveBeenCalledTimes(1);
+ act(() => {
+ Emitter.emit(TIMER_REACHED_NULL);
+ });
+ expect(queryByTestId('continue-exam-button')).not.toBeInTheDocument();
+ } else {
+ expect(continueButton).not.toBeInTheDocument();
+ }
});
it('Instructions for submitted status', () => {
diff --git a/src/instructions/SubmitInstructions.jsx b/src/instructions/SubmitInstructions.jsx
index 01b0b544..a93a29ef 100644
--- a/src/instructions/SubmitInstructions.jsx
+++ b/src/instructions/SubmitInstructions.jsx
@@ -1,24 +1,49 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { Container } from '@edx/paragon';
+import React, { useContext, useEffect, useState } from 'react';
+import { Button, Container } from '@edx/paragon';
+import { FormattedMessage } from '@edx/frontend-platform/i18n';
+import Emitter from '../data/emitter';
import { ExamType } from '../constants';
import { SubmitProctoredExamInstructions } from './proctored_exam';
import { SubmitTimedExamInstructions } from './timed_exam';
import Footer from './proctored_exam/Footer';
+import ExamStateContext from '../context';
+import { TIMER_REACHED_NULL } from '../timer/events';
-const SubmitExamInstructions = ({ examType }) => (
-
-
- {examType === ExamType.TIMED
- ?
- : }
-
- {examType !== ExamType.TIMED && }
-
-);
+const SubmitExamInstructions = () => {
+ const state = useContext(ExamStateContext);
+ const { exam, continueExam, activeAttempt } = state;
+ const { time_remaining_seconds: timeRemaining } = activeAttempt;
+ const { type: examType } = exam || {};
+ const [canContinue, setCanContinue] = useState(timeRemaining > 0);
-SubmitExamInstructions.propTypes = {
- examType: PropTypes.string.isRequired,
+ const hideContinueButton = () => setCanContinue(false);
+
+ useEffect(() => {
+ Emitter.once(TIMER_REACHED_NULL, hideContinueButton);
+
+ return () => {
+ Emitter.off(TIMER_REACHED_NULL, hideContinueButton);
+ };
+ }, []);
+
+ return (
+
+
+ {examType === ExamType.TIMED
+ ?
+ : }
+ {canContinue && (
+
+ )}
+
+ {examType !== ExamType.TIMED && }
+
+ );
};
export default SubmitExamInstructions;
diff --git a/src/instructions/index.jsx b/src/instructions/index.jsx
index 7b4c6440..356d55db 100644
--- a/src/instructions/index.jsx
+++ b/src/instructions/index.jsx
@@ -70,7 +70,7 @@ const Instructions = ({ children }) => {
case attempt.attempt_status === ExamStatus.READY_TO_START:
return ;
case attempt.attempt_status === ExamStatus.READY_TO_SUBMIT:
- return ;
+ return ;
case attempt.attempt_status === ExamStatus.SUBMITTED:
return ;
case attempt.attempt_status === ExamStatus.VERIFIED:
diff --git a/src/instructions/proctored_exam/ProctoredExamInstructions.test.jsx b/src/instructions/proctored_exam/ProctoredExamInstructions.test.jsx
index 1e8cc83c..1a5e328c 100644
--- a/src/instructions/proctored_exam/ProctoredExamInstructions.test.jsx
+++ b/src/instructions/proctored_exam/ProctoredExamInstructions.test.jsx
@@ -3,6 +3,7 @@ import React from 'react';
import { fireEvent } from '@testing-library/dom';
import Instructions from '../index';
import { store, getExamAttemptsData } from '../../data';
+import { submitExam } from '../../data/thunks';
import { render } from '../../setupTest';
import { ExamStateProvider } from '../../index';
import {
@@ -16,6 +17,11 @@ jest.mock('../../data', () => ({
store: {},
getExamAttemptsData: jest.fn(),
}));
+jest.mock('../../data/thunks', () => ({
+ getExamReviewPolicy: jest.fn(),
+ submitExam: jest.fn(),
+}));
+submitExam.mockReturnValue(jest.fn());
getExamAttemptsData.mockReturnValue(jest.fn());
store.subscribe = jest.fn();
store.dispatch = jest.fn();
@@ -170,7 +176,7 @@ describe('SequenceExamWrapper', () => {
expect(getByTestId('proctored-exam-instructions-title')).toHaveTextContent('You have submitted this proctored exam for review');
});
- it('Instructions are shown when attempt status is ready_to_submit', () => {
+ it('Shows correct instructions when attempt status is ready_to_submit ', () => {
store.getState = () => ({
examState: {
isLoading: false,
@@ -194,7 +200,7 @@ describe('SequenceExamWrapper', () => {
},
});
- const { getByTestId } = render(
+ const { queryByTestId } = render(
Sequence
@@ -202,7 +208,10 @@ describe('SequenceExamWrapper', () => {
,
{ store },
);
- expect(getByTestId('proctored-exam-instructions-title')).toHaveTextContent('Are you sure you want to end your proctored exam?');
+
+ expect(queryByTestId('proctored-exam-instructions-title')).toHaveTextContent('Are you sure you want to end your proctored exam?');
+ fireEvent.click(queryByTestId('end-exam-button'));
+ expect(submitExam).toHaveBeenCalled();
});
it('Instructions are shown when attempt status is verified', () => {
diff --git a/src/instructions/proctored_exam/SubmitProctoredExamInstructions.jsx b/src/instructions/proctored_exam/SubmitProctoredExamInstructions.jsx
index 94b144a9..ca99f02a 100644
--- a/src/instructions/proctored_exam/SubmitProctoredExamInstructions.jsx
+++ b/src/instructions/proctored_exam/SubmitProctoredExamInstructions.jsx
@@ -8,7 +8,6 @@ const SubmitProctoredExamInstructions = () => {
const state = useContext(ExamStateContext);
const {
submitExam,
- continueExam,
exam,
activeAttempt,
} = state;
@@ -49,19 +48,12 @@ const SubmitProctoredExamInstructions = () => {
/>
)}
-