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 (