Commit 2a7c7e96 by Afzal Wali Naushahi

Merge pull request #118 from edx/afzaledx/phx-108_client_side_time_limit

The remaining time is now independent of the browser's system clock.
parents 53968f79 4dae2267
...@@ -16,32 +16,24 @@ ...@@ -16,32 +16,24 @@
course_id: null, course_id: null,
lastFetched: new Date() lastFetched: new Date()
}, },
getRemainingSeconds: function () { getFormattedRemainingTime: function (secondsLeft) {
var currentTime = (new Date()).getTime();
var lastFetched = this.get('lastFetched').getTime();
var totalSeconds = this.get('time_remaining_seconds') - (currentTime - lastFetched) / 1000;
return totalSeconds;
},
getFormattedRemainingTime: function () {
var totalSeconds = this.getRemainingSeconds();
/* since we can have a small grace period, we can end in the negative numbers */ /* since we can have a small grace period, we can end in the negative numbers */
if (totalSeconds < 0) if (secondsLeft < 0)
totalSeconds = 0; secondsLeft = 0;
var hours = parseInt(totalSeconds / 3600) % 24; var hours = parseInt(secondsLeft / 3600) % 24;
var minutes = parseInt(totalSeconds / 60) % 60; var minutes = parseInt(secondsLeft / 60) % 60;
var seconds = Math.floor(totalSeconds % 60); var seconds = Math.floor(secondsLeft % 60);
return hours + ":" + (minutes < 10 ? "0" + minutes : minutes) return hours + ":" + (minutes < 10 ? "0" + minutes : minutes)
+ ":" + (seconds < 10 ? "0" + seconds : seconds); + ":" + (seconds < 10 ? "0" + seconds : seconds);
}, },
getRemainingTimeState: function () { getRemainingTimeState: function (secondsLeft) {
var totalSeconds = this.getRemainingSeconds(); if (secondsLeft > this.get('low_threshold_sec')) {
if (totalSeconds > this.get('low_threshold_sec')) {
return ""; return "";
} }
else if (totalSeconds <= this.get('low_threshold_sec') && totalSeconds > this.get('critically_low_threshold_sec')) { else if (secondsLeft <= this.get('low_threshold_sec') && secondsLeft > this.get('critically_low_threshold_sec')) {
// returns the class name that has some css properties // returns the class name that has some css properties
// and it displays the user with the waring message if // and it displays the user with the waring message if
// total seconds is less than the low_threshold value. // total seconds is less than the low_threshold value.
......
...@@ -17,6 +17,7 @@ var edx = edx || {}; ...@@ -17,6 +17,7 @@ var edx = edx || {};
this.template = null; this.template = null;
this.timerId = null; this.timerId = null;
this.timerTick = 0; this.timerTick = 0;
this.secondsLeft = 0;
/* give an extra 5 seconds where the timer holds at 00:00 before page refreshes */ /* give an extra 5 seconds where the timer holds at 00:00 before page refreshes */
this.grace_period_secs = 5; this.grace_period_secs = 5;
...@@ -62,6 +63,7 @@ var edx = edx || {}; ...@@ -62,6 +63,7 @@ var edx = edx || {};
// should not be navigating around the courseware // should not be navigating around the courseware
var taking_as_proctored = this.model.get('taking_as_proctored'); var taking_as_proctored = this.model.get('taking_as_proctored');
var time_left = this.model.get('time_remaining_seconds') > 0; var time_left = this.model.get('time_remaining_seconds') > 0;
this.secondsLeft = this.model.get('time_remaining_seconds');
var status = this.model.get('attempt_status'); var status = this.model.get('attempt_status');
var in_courseware = document.location.href.indexOf('/courses/' + this.model.get('course_id') + '/courseware/') > -1; var in_courseware = document.location.href.indexOf('/courses/' + this.model.get('course_id') + '/courseware/') > -1;
...@@ -125,6 +127,7 @@ var edx = edx || {}; ...@@ -125,6 +127,7 @@ var edx = edx || {};
}, },
updateRemainingTime: function (self) { updateRemainingTime: function (self) {
self.timerTick ++; self.timerTick ++;
self.secondsLeft --;
if (self.timerTick % 5 === 0){ if (self.timerTick % 5 === 0){
var url = self.model.url + '/' + self.model.get('attempt_id'); var url = self.model.url + '/' + self.model.get('attempt_id');
$.ajax(url).success(function(data) { $.ajax(url).success(function(data) {
...@@ -136,12 +139,15 @@ var edx = edx || {}; ...@@ -136,12 +139,15 @@ var edx = edx || {};
// refresh the page when the timer expired // refresh the page when the timer expired
location.reload(); location.reload();
} }
else {
self.secondsLeft = data.time_remaining_seconds;
}
}); });
} }
self.$el.find('div.exam-timer').removeClass("low-time warning critical"); self.$el.find('div.exam-timer').removeClass("low-time warning critical");
self.$el.find('div.exam-timer').addClass(self.model.getRemainingTimeState()); self.$el.find('div.exam-timer').addClass(self.model.getRemainingTimeState(self.secondsLeft));
self.$el.find('span#time_remaining_id b').html(self.model.getFormattedRemainingTime()); self.$el.find('span#time_remaining_id b').html(self.model.getFormattedRemainingTime(self.secondsLeft));
if (self.model.getRemainingSeconds() <= -self.grace_period_secs) { if (self.secondsLeft <= -self.grace_period_secs) {
clearInterval(self.timerId); // stop the timer once the time finishes. clearInterval(self.timerId); // stop the timer once the time finishes.
$(window).unbind('beforeunload', this.unloadMessage); $(window).unbind('beforeunload', this.unloadMessage);
// refresh the page when the timer expired // refresh the page when the timer expired
......
...@@ -46,31 +46,36 @@ describe('ProctoredExamView', function () { ...@@ -46,31 +46,36 @@ describe('ProctoredExamView', function () {
expect(this.proctored_exam_view.$el.find('a')).toContainHtml(this.model.get('exam_display_name')); expect(this.proctored_exam_view.$el.find('a')).toContainHtml(this.model.get('exam_display_name'));
}); });
it('changes behavior when clock time decreases low threshold', function () { it('changes behavior when clock time decreases low threshold', function () {
spyOn(this.model, 'getRemainingSeconds').and.callFake(function() { this.proctored_exam_view.secondsLeft = 25;
return 25;
});
expect(this.model.getRemainingSeconds()).toEqual(25);
expect(this.proctored_exam_view.$el.find('div.exam-timer')).not.toHaveClass('low-time warning');
this.proctored_exam_view.render(); this.proctored_exam_view.render();
expect(this.proctored_exam_view.$el.find('div.exam-timer')).toHaveClass('low-time warning'); expect(this.proctored_exam_view.$el.find('div.exam-timer')).toHaveClass('low-time warning');
}); });
it('changes behavior when clock time decreases critically low threshold', function () { it('changes behavior when clock time decreases critically low threshold', function () {
spyOn(this.model, 'getRemainingSeconds').and.callFake(function () { this.proctored_exam_view.secondsLeft = 5;
return 5;
});
expect(this.model.getRemainingSeconds()).toEqual(5);
expect(this.proctored_exam_view.$el.find('div.exam-timer')).not.toHaveClass('low-time critical');
this.proctored_exam_view.render(); this.proctored_exam_view.render();
expect(this.proctored_exam_view.$el.find('div.exam-timer')).toHaveClass('low-time critical'); expect(this.proctored_exam_view.$el.find('div.exam-timer')).toHaveClass('low-time critical');
}); });
it("reload the page when the exam time finishes", function(){ it("reload the page when the exam time finishes", function(){
spyOn(this.model, 'getRemainingSeconds').and.callFake(function() { this.proctored_exam_view.secondsLeft = -10;
return -10;
});
expect(this.model.getRemainingSeconds()).toEqual(-10);
var reloadPage = spyOn(this.proctored_exam_view, 'reloadPage'); var reloadPage = spyOn(this.proctored_exam_view, 'reloadPage');
this.proctored_exam_view.render(); this.proctored_exam_view.updateRemainingTime(this.proctored_exam_view);
expect(reloadPage).toHaveBeenCalled();
});
it("resets the remainig exam time after the ajax response", function(){
this.server.respondWith("GET", "/api/edx_proctoring/v1/proctored_exam/attempt/" + this.proctored_exam_view.model.get('attempt_id'),
[
200,
{"Content-Type": "application/json"},
JSON.stringify({
time_remaining_seconds: -10
})
]
);
this.proctored_exam_view.timerTick = 4; // to make the ajax call.
var reloadPage = spyOn(this.proctored_exam_view, 'reloadPage');
this.proctored_exam_view.updateRemainingTime(this.proctored_exam_view);
this.server.respond();
this.proctored_exam_view.updateRemainingTime(this.proctored_exam_view);
expect(reloadPage).toHaveBeenCalled(); expect(reloadPage).toHaveBeenCalled();
}); });
}); });
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment