@oat-sa/tao-test-runner-qti
Version:
TAO Test Runner QTI implementation
178 lines (161 loc) • 6.66 kB
JavaScript
define(function () { 'use strict';
/**
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; under version 2
* of the License (non-upgradable).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Copyright (c) 2016 (original work) Open Assessment Technologies SA ;
*/
/**
* This is a helper for navigation plugins. It decides if a warning should be displayed
* before actually moving to the next item
*
* @author Christophe Noël <christophe@taotesting.com>
*/
/**
* Convert a value to a boolean
* @param {*} value
* @param {Boolean} defaultValue
* @returns {Boolean}
*/
var toBoolean = function toBoolean(value, defaultValue) {
if (typeof value === 'undefined') {
return defaultValue;
} else {
return value === true || value === 'true';
}
};
/**
* @param {Object} options
* @param {Boolean} options.endTestWarning - enables the end test warning, when applicable
* @param {Boolean} options.isLast - if the item is the last of the test
* @param {Boolean} options.isLinear - if the current part is linear
* @param {Boolean} options.nextItemWarning - enables the next item warning, when applicable
* @param {Boolean} options.nextPartWarning - enables the next item warning on part change
* @param {Boolean} options.stats - current state of the test
* @param {Object} options.nextPart - description of the next part of the test
* @param {Number} options.remainingAttempts - remaining attempts for the current item
* @param {String} options.testPartId - current test part identifier
* @param {Boolean} options.unansweredOnly - warn only if there are unanswered/flagged items
* @returns {Object}
*/
var nextWarningHelper = function nextWarningHelper(options) {
var endTestWarning = toBoolean(options.endTestWarning, false),
isLast = toBoolean(options.isLast, false),
isLinear = toBoolean(options.isLinear, false),
nextItemWarning = toBoolean(options.nextItemWarning, false),
nextPartWarning = toBoolean(options.nextPartWarning, false),
stats = options.stats,
nextPart = options.nextPart || {},
remainingAttempts = typeof options.remainingAttempts === 'undefined' ? -1 : options.remainingAttempts,
testPartId = options.testPartId || '',
unansweredOnly = toBoolean(options.unansweredOnly, false),
warnBeforeNext = shouldWarnBeforeNext(),
warnBeforeEnd = shouldWarnBeforeEnd(),
warnBeforeEndPart = shouldWarnBeforeEndPart();
/**
* Decide if we should display a warning before moving to the next item.
* This is useful to prevent accidental navigation (for example by pressing a shortcut) that would occur
* before the test taker actually gets a chance to answer an item
*/
function shouldWarnBeforeNext() {
return nextItemWarning && !itemCanBeTriedAtWill();
//&& !exitTimedSectionWarning(); //todo: this should be implemented to prevent a double warning!
}
/**
* We try to decide if the test taker has the freedom to come back to this item as many times as he wants
*/
function itemCanBeTriedAtWill() {
return isLast === false &&
// the test is not over
isLinear === false &&
// the context is not linear
remainingAttempts === -1 &&
// the item doesn't have a configured max attempts number
!isNextItemInLinearPart() // the next item is not in a linear part
;
}
/**
* Check if the next item is in a linear part, as this would prevent the test taker to come back to the current item
*/
function isNextItemInLinearPart() {
return nextPart && typeof nextPart.isLinear !== 'undefined' && isLastOfPart() && nextPart.isLinear === true;
}
/**
* Decide if we should display a warning before ending the test
*/
function shouldWarnBeforeEnd() {
return shouldWarnOnTestEnd();
}
/**
* Decide if we should display a warning before ending the test part
*/
function shouldWarnBeforeEndPart() {
return shouldWarnOnPartChange();
}
/**
* Are we on the last test item?
* @returns {Boolean}
*/
function shouldWarnOnTestEnd() {
return isLast && (endTestWarning ||
// warning is explicitly required by endTestWarning category
warnBeforeNext) &&
// warning is implicitly triggered by the next item warning being true (prevent double warning)
shouldWarnForUnansweredItems();
}
/**
* Provide the opportunity to cancel the display of the warning if there are no unanswered/flagged item
* for this to work, the unansweredOnly option has to be set
* @returns {Boolean}
*/
function shouldWarnForUnansweredItems() {
var hasUnanswered = stats && stats.questions - stats.answered !== 0,
hasFlagged = stats && stats.flagged !== 0;
if (unansweredOnly) {
return hasUnanswered || hasFlagged;
} else {
return true;
}
}
/**
* Are we on the last part item?
* @returns {Boolean}
*/
function shouldWarnOnPartChange() {
return nextPartWarning && isLastOfPart() && shouldWarnForUnansweredItems();
}
/**
* Check if the next item belong to a different part
* @returns {Boolean}
*/
function isLastOfPart() {
return nextPart && nextPart.id && testPartId !== nextPart.id;
}
/**
* The helper object
*/
return {
shouldWarnBeforeEndPart: function () {
return warnBeforeEndPart;
},
shouldWarnBeforeEnd: function () {
return warnBeforeEnd;
},
shouldWarnBeforeNext: function () {
return warnBeforeNext;
}
};
};
return nextWarningHelper;
});