@oat-sa/tao-test-runner-qti
Version:
TAO Test Runner QTI implementation
299 lines (274 loc) • 13.5 kB
JavaScript
define(['jquery', 'lodash', 'i18n', 'core/polling', 'ui/waitingDialog/waitingDialog', 'taoTests/runner/plugin', 'handlebars', 'lib/handlebars/helpers', 'util/namespace'], function ($$1, _, __, pollingFactory, waitingDialog, pluginFactory, Handlebars, Helpers0, namespaceHelper) { 'use strict';
$$1 = $$1 && Object.prototype.hasOwnProperty.call($$1, 'default') ? $$1['default'] : $$1;
_ = _ && Object.prototype.hasOwnProperty.call(_, 'default') ? _['default'] : _;
__ = __ && Object.prototype.hasOwnProperty.call(__, 'default') ? __['default'] : __;
pollingFactory = pollingFactory && Object.prototype.hasOwnProperty.call(pollingFactory, 'default') ? pollingFactory['default'] : pollingFactory;
waitingDialog = waitingDialog && Object.prototype.hasOwnProperty.call(waitingDialog, 'default') ? waitingDialog['default'] : waitingDialog;
pluginFactory = pluginFactory && Object.prototype.hasOwnProperty.call(pluginFactory, 'default') ? pluginFactory['default'] : pluginFactory;
Handlebars = Handlebars && Object.prototype.hasOwnProperty.call(Handlebars, 'default') ? Handlebars['default'] : Handlebars;
Helpers0 = Helpers0 && Object.prototype.hasOwnProperty.call(Helpers0, 'default') ? Helpers0['default'] : Helpers0;
namespaceHelper = namespaceHelper && Object.prototype.hasOwnProperty.call(namespaceHelper, 'default') ? namespaceHelper['default'] : namespaceHelper;
if (!Helpers0.__initialized) {
Helpers0(Handlebars);
Helpers0.__initialized = true;
}
var Template = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
var buffer = "", stack1, helper, options, helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression, functionType="function", self=this;
function program1(depth0,data) {
return " with-message";
}
function program3(depth0,data) {
var buffer = "", helper, options;
buffer += "<span class=\"message-connect\">"
+ escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, "Online", options) : helperMissing.call(depth0, "__", "Online", options)))
+ "</span>";
return buffer;
}
function program5(depth0,data) {
var buffer = "", helper, options;
buffer += "<span class=\"message-disconnected\">"
+ escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, "Offline", options) : helperMissing.call(depth0, "__", "Offline", options)))
+ "</span>";
return buffer;
}
buffer += "<div class=\"connectivity-box ";
if (helper = helpers.state) { stack1 = helper.call(depth0, {hash:{},data:data}); }
else { helper = (depth0 && depth0.state); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
buffer += escapeExpression(stack1);
stack1 = helpers['if'].call(depth0, (depth0 && depth0.message), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
if(stack1 || stack1 === 0) { buffer += stack1; }
buffer += "\">\n ";
stack1 = helpers['if'].call(depth0, (depth0 && depth0.message), {hash:{},inverse:self.noop,fn:self.program(3, program3, data),data:data});
if(stack1 || stack1 === 0) { buffer += stack1; }
buffer += "<span data-control=\"connectivity-connected\" class=\"qti-controls icon-connect\" title=\""
+ escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, "Connected to server", options) : helperMissing.call(depth0, "__", "Connected to server", options)))
+ "\"></span>\n ";
stack1 = helpers['if'].call(depth0, (depth0 && depth0.message), {hash:{},inverse:self.noop,fn:self.program(5, program5, data),data:data});
if(stack1 || stack1 === 0) { buffer += stack1; }
buffer += "<span data-control=\"connectivity-disconnected\" class=\"qti-controls icon-disconnect\" title=\""
+ escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, "Disconnected from server", options) : helperMissing.call(depth0, "__", "Disconnected from server", options)))
+ "\"></span>\n</div>\n";
return buffer;
});
function connectivityTpl(data, options, asString) {
var html = Template(data, options);
return (asString || true) ? html : $(html);
}
/**
* 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-2021 (original work) Open Assessment Technologies SA ;
*/
/**
* The plugin default configuration
* @type {Object}
* @property {Number} checkInterval - when offline, interval to check if we're back online
* @property {Boolean} indicator - do we display the indicator in the test UI
* @property {Boolean} message - do we display the message in the test UI
*/
var defaultConfig = {
checkInterval: 30 * 1000,
indicator: true,
message: false
};
/**
* Creates the connectivity plugin.
* Detects connectivity issues
*/
var connectivity = pluginFactory({
name: 'connectivity',
/**
* Initializes the plugin (called during runner's init)
*/
init: function init() {
var self = this;
var testRunner = this.getTestRunner();
var proxy = testRunner.getProxy();
var config = Object.assign({}, defaultConfig, this.getConfig());
//Displays a connectivity indicator
if (config && config.indicator) {
//create the indicator
this.$element = $$1(connectivityTpl({
state: proxy.isOnline() ? 'connected' : 'disconnected',
message: config.message
}));
testRunner.on('disconnect', function () {
self.$element.removeClass('connected').addClass('disconnected');
}).on('reconnect', function () {
self.$element.removeClass('disconnected').addClass('connected');
});
}
//update the interval, with the new value
if (this.polling && _.isNumber(config.checkInterval)) {
this.polling.setInterval(config.checkInterval);
}
},
/**
* Installs the plugin (called when the runner bind the plugin)
* We do it before init to catch even offline during the init sequence
*/
install: function install() {
var self = this;
var waiting = false;
var testRunner = this.getTestRunner();
var proxy = testRunner.getProxy();
/**
* Display the waiting dialog, while waiting the connection to be back
* @param {String} [messsage] - additional message for the dialog
* @returns {Promise} resolves once the wait is over and the user click on 'proceed'
*/
this.displayWaitingDialog = function displayWaitingDialog() {
let message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
var dialog;
return new Promise(function (resolve) {
if (!waiting) {
waiting = true;
//if a pause event occurs while waiting,
//we also wait the connection to be back
testRunner.before('pause.waiting', function () {
return new Promise(function (pauseResolve) {
proxy.off('reconnect.pausing').after('reconnect.pausing', pauseResolve);
});
});
//creates the waiting modal dialog
dialog = waitingDialog({
message: __('You are encountering a prolonged connectivity loss. ') + message,
waitContent: __('Please wait while we try to restore the connection.'),
proceedContent: __('The connection seems to be back, please proceed')
}).on('proceed', function () {
resolve();
}).on('render', function () {
proxy.off('reconnect.waiting').after('reconnect.waiting', function () {
testRunner.off('pause.waiting');
waiting = false;
dialog.endWait();
});
});
}
});
};
//Last chance to check the connection,
//by regular polling on the "up" signal
this.polling = pollingFactory({
action: function action() {
testRunner.getProxy().telemetry(testRunner.getTestContext().itemIdentifier, 'up').catch(_.noop);
},
interval: defaultConfig.checkInterval,
autoStart: false
});
//the Proxy is the only one to know something about connectivity
proxy.on('disconnect', function disconnect(source) {
if (!testRunner.getState('disconnected')) {
testRunner.setState('disconnected', true);
testRunner.trigger('disconnect', source);
self.polling.start();
}
}).on('reconnect', function reconnect() {
if (testRunner.getState('disconnected')) {
testRunner.setState('disconnected', false);
testRunner.trigger('reconnect');
self.polling.stop();
}
});
//intercept tries to leave while offline
//this could be caused by pauses for example.
//If caused by an action like exitTest it will be handled
//by navigation errors (see below)
testRunner.before('leave', function (e, data) {
if (proxy.isOffline()) {
self.displayWaitingDialog(data.message).then(function () {
testRunner.trigger('leave', data);
}).catch(function (generalErr) {
testRunner.trigger('error', generalErr);
});
return false;
}
});
//intercept offline navigation errors
testRunner.before('error.connectivity', function (e, err) {
// detect and prevent connectivity errors
if (proxy.isConnectivityError(err)) {
return false;
}
if (proxy.isOffline()) {
self.displayWaitingDialog().then(function () {
if (err.type === 'nav') {
testRunner.loadItem(testRunner.getTestContext().itemIdentifier);
}
if (err.type === 'finish') {
testRunner.finish();
}
if (err.type === 'pause') {
testRunner.trigger('pause', {
reasons: err.data && err.data.reasons,
message: err.data && err.data.comment
});
}
}).catch(function (generalErr) {
testRunner.trigger('error', generalErr);
});
return false;
}
});
testRunner.before('loaditem.connectivity', function (e, itemRef, item) {
const testContext = testRunner.getTestContext();
const {
flags
} = item;
if (!flags) {
return true;
}
if (flags.hasFeedbacks) {
testContext.hasFeedbacks = true;
}
if ((flags.containsNonPreloadedAssets || flags.hasPci) && proxy.isOffline()) {
self.displayWaitingDialog().then(() => {
testRunner.loadItem(itemRef);
});
return false;
}
});
testRunner.before(namespaceHelper.namespaceAll('move skip timeout', 'connectivity'), function (e) {
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
var testContext = testRunner.getTestContext();
var currentItem = testRunner.getCurrentItem();
if (proxy.isOffline() && (currentItem.hasFeedbacks || testContext.hasFeedbacks)) {
testRunner.trigger('disableitem');
self.displayWaitingDialog().then(function () {
testRunner.trigger('enableitem').trigger(e.name, ...args);
});
return false;
}
});
},
destroy() {
this.getTestRunner().off('.connectivity');
},
/**
* Called during the runner's render phase
*/
render: function render() {
var $container = this.getAreaBroker().getControlArea();
if (this.$element) {
$container.append(this.$element);
}
}
});
return connectivity;
});