diff --git a/src/timer/CountDownTimer.test.jsx b/src/timer/CountDownTimer.test.jsx index 4d90c506..a706a264 100644 --- a/src/timer/CountDownTimer.test.jsx +++ b/src/timer/CountDownTimer.test.jsx @@ -52,6 +52,7 @@ describe('ExamTimerBlock', () => { stopExamAttempt={stopExamAttempt} expireExamAttempt={expireExamAttempt} pollExamAttempt={pollAttempt} + submitExam={submitAttempt} />, ); @@ -80,6 +81,7 @@ describe('ExamTimerBlock', () => { stopExamAttempt={stopExamAttempt} expireExamAttempt={expireExamAttempt} pollExamAttempt={pollAttempt} + submitExam={submitAttempt} />, ); expect(container.firstChild).not.toBeInTheDocument(); @@ -92,6 +94,7 @@ describe('ExamTimerBlock', () => { stopExamAttempt={stopExamAttempt} expireExamAttempt={expireExamAttempt} pollExamAttempt={pollAttempt} + submitExam={submitAttempt} />, ); await waitFor(() => expect(screen.getByText('00:00:09')).toBeInTheDocument()); @@ -127,6 +130,7 @@ describe('ExamTimerBlock', () => { stopExamAttempt={stopExamAttempt} expireExamAttempt={expireExamAttempt} pollExamAttempt={pollAttempt} + submitExam={submitAttempt} />, ); await waitFor(() => expect(screen.getByText('00:00:04')).toBeInTheDocument()); @@ -140,6 +144,7 @@ describe('ExamTimerBlock', () => { stopExamAttempt={stopExamAttempt} expireExamAttempt={expireExamAttempt} pollExamAttempt={pollAttempt} + submitExam={submitAttempt} />, ); await waitFor(() => expect(screen.getByText('00:00:09')).toBeInTheDocument()); @@ -162,6 +167,7 @@ describe('ExamTimerBlock', () => { stopExamAttempt={stopExamAttempt} expireExamAttempt={expireExamAttempt} pollExamAttempt={pollAttempt} + submitExam={submitAttempt} />, ); await waitFor(() => expect(screen.getByText('00:00:09')).toBeInTheDocument()); @@ -230,4 +236,61 @@ describe('ExamTimerBlock', () => { fireEvent.click(screen.getByTestId('end-button')); expect(stopExamAttempt).toHaveBeenCalledTimes(1); }); + + it('Update exam timer when attempt time_remaining_seconds is smaller than displayed time', async () => { + const preloadedState = { + examState: { + isLoading: true, + timeIsOver: false, + activeAttempt: { + attempt_status: 'started', + exam_url_path: 'exam_url_path', + exam_display_name: 'exam name', + time_remaining_seconds: 240, + low_threshold_sec: 15, + critically_low_threshold_sec: 5, + exam_started_poll_url: '', + taking_as_proctored: false, + exam_type: 'a timed exam', + }, + proctoringSettings: {}, + exam: {}, + }, + }; + let testStore = await initializeTestStore(preloadedState); + examStore.getState = store.testStore; + attempt = testStore.getState().examState.activeAttempt; + const { rerender } = render( + , + ); + await waitFor(() => expect(screen.getByText('00:03:59')).toBeInTheDocument()); + + preloadedState.examState.activeAttempt = { + ...attempt, + time_remaining_seconds: 20, + }; + testStore = await initializeTestStore(preloadedState); + examStore.getState = store.testStore; + const updatedAttempt = testStore.getState().examState.activeAttempt; + + expect(updatedAttempt.time_remaining_seconds).toBe(20); + + rerender( + , + ); + + await waitFor(() => expect(screen.getByText('00:00:19')).toBeInTheDocument()); + }); }); diff --git a/src/timer/TimerProvider.jsx b/src/timer/TimerProvider.jsx index 781a32c8..3e1bfd05 100644 --- a/src/timer/TimerProvider.jsx +++ b/src/timer/TimerProvider.jsx @@ -39,8 +39,8 @@ const TimerServiceProvider = ({ critically_low_threshold_sec: criticalLowTime, low_threshold_sec: lowTime, } = attempt; - const startValue = Math.floor(timeRemaining); const LIMIT = GRACE_PERIOD_SECS ? 0 - GRACE_PERIOD_SECS : 0; + let liveInterval = null; const getTimeString = () => Object.values(timeState).map( item => { @@ -77,26 +77,29 @@ const TimerServiceProvider = ({ }; useEffect(() => { - let secondsLeft = startValue; let timerTick = 0; - const interval = setInterval(() => { + let secondsLeft = Math.floor(timeRemaining); + liveInterval = setInterval(() => { secondsLeft -= 1; timerTick += 1; setTimeState(getFormattedRemainingTime(secondsLeft)); - processTimeLeft(interval, secondsLeft); + processTimeLeft(liveInterval, secondsLeft); // no polling during grace period if (timerTick % POLL_INTERVAL === 0 && secondsLeft >= 0) { pollExam(); } - // if exam is proctored ping provider app also if (workerUrl && timerTick % pingInterval === pingInterval / 2) { pingHandler(pingInterval, workerUrl); } }, 1000); - - return () => { clearInterval(interval); }; - }, []); + return () => { + if (liveInterval) { + clearInterval(liveInterval); + liveInterval = null; + } + }; + }, [timeRemaining]); return (