UNPKG

@oat-sa/tao-test-runner-qti

Version:
273 lines (267 loc) 12.5 kB
define(['lodash', 'taoQtiTest/runner/helpers/map'], function (_, mapHelper) { 'use strict'; _ = _ && Object.prototype.hasOwnProperty.call(_, 'default') ? _['default'] : _; mapHelper = mapHelper && Object.prototype.hasOwnProperty.call(mapHelper, 'default') ? mapHelper['default'] : mapHelper; /** * 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) 2017 (original work) Open Assessment Technologies SA ; */ /** * @typedef {Object} navigationHelper */ var navigationHelper = { /** * Check whether the test taker is leaving a section * * @param {Object} testContext - the actual test context * @param {String} testContext.itemIdentifier - the id of the current item * @param {String} testContext.sectionId - the id of the current section * @param {Object} testMap - the actual test map * @param {String} direction - the move direction (next, previous or jump) * @param {String} scope - the move scope (item, section, testPart) * @param {Number} [position] - the position in case of jump * @returns {Boolean} true if the action leads to a section leave * @throws {TypeError} if the context or the map are incorrect */ isLeavingSection: function isLeavingSection(testContext, testMap, direction, scope, position) { var section; var sectionStats; var nbItems; var item; if (_.isPlainObject(testContext) && _.isPlainObject(testMap) && !_.isEmpty(testContext.sectionId) && !_.isEmpty(testContext.itemIdentifier)) { section = mapHelper.getSection(testMap, testContext.sectionId); sectionStats = mapHelper.getSectionStats(testMap, testContext.sectionId); nbItems = sectionStats && sectionStats.total; item = mapHelper.getItem(testMap, testContext.itemIdentifier); return scope === 'section' || scope === 'testPart' || direction === 'next' && item.positionInSection + 1 === nbItems || direction === 'previous' && item.positionInSection === 0 || direction === 'jump' && position > 0 && (position < section.position || position >= section.position + nbItems); } throw new TypeError('Invalid test context and test map'); }, /** * Check whether the test taker is leaving a test part * * @param {Object} testContext - the actual test context * @param {String} testContext.itemIdentifier - the id of the current item * @param {String} testContext.sectionId - the id of the current section * @param {String} testContext.testPartId - the id of the current testPart * @param {Object} testMap - the actual test map * @param {String} direction - the move direction (next, previous or jump) * @param {String} scope - the move scope (item, section, testPart) * @param {Number} [position] - the position in case of jump * @returns {Boolean} true if the action leads to a section leave * @throws {TypeError} if the context or the map are incorrect */ isLeavingTestPart: function isLeavingTestPart(testContext, testMap, direction, scope, position) { var testPart; var testPartStats; var nbItems; var item; var section; var sectionStats; if (_.isPlainObject(testContext) && _.isPlainObject(testMap) && !_.isEmpty(testContext.testPartId) && !_.isEmpty(testContext.sectionId) && !_.isEmpty(testContext.itemIdentifier)) { testPart = mapHelper.getPart(testMap, testContext.testPartId); testPartStats = mapHelper.getPartStats(testMap, testContext.testPartId); nbItems = testPartStats && testPartStats.total; item = mapHelper.getItem(testMap, testContext.itemIdentifier); if (scope === 'section') { section = mapHelper.getSection(testMap, testContext.sectionId); sectionStats = mapHelper.getSectionStats(testMap, testContext.sectionId); } return scope === 'testPart' || direction === 'next' && scope === 'item' && item.positionInPart + 1 === nbItems || direction === 'next' && scope === 'section' && section.position + sectionStats.total >= nbItems || direction === 'previous' && scope === 'item' && item.positionInPart === 0 || direction === 'previous' && scope === 'section' && section.position === testPart.position || direction === 'jump' && position > 0 && (position < testPart.position || position >= testPart.position + nbItems); } throw new TypeError('Invalid test context and test map'); }, /** * Check if the given item is the last of the test * @param {Object} testMap - the test map * @param {String} itemIdentifier - the identifier of the item * @returns {Boolean} true if the item is the last one */ isLast: function isLast(testMap, itemIdentifier) { return this.isLastOf(testMap, itemIdentifier, 'test'); }, /** * Check if the given item is the first of a test * @param {Object} testMap - the test map * @param {String} itemIdentifier - the identifier of the item * @returns {Boolean} true if the item is the first one */ isFirst: function isFirst(testMap, itemIdentifier) { return this.isFirstOf(testMap, itemIdentifier, 'test'); }, /** * Check if the given item is the last of a the given scope * @param {Object} testMap - the test map * @param {String} itemIdentifier - the identifier of the item * @param {String} [scope = 'test'] - the target scope * @returns {Boolean} true if the item is the last one */ isLastOf: function isLastOf(testMap, itemIdentifier, scope) { var item; var stats; if (!_.isPlainObject(testMap)) { throw new TypeError('Invalid test map'); } if (_.isEmpty(itemIdentifier)) { throw new TypeError('Invalid item identifier'); } scope = scope || 'test'; item = mapHelper.getItem(testMap, itemIdentifier); stats = mapHelper.getScopeStats(testMap, item.position, scope); if (stats && _.isNumber(stats.total)) { if (scope === 'test') { return item.position + 1 === stats.total; } if (scope === 'section' || scope === 'assessmentSection' || scope === 'testSection') { return item.positionInSection + 1 === stats.total; } if (scope === 'part' || scope === 'testPart') { return item.positionInPart + 1 === stats.total; } } return false; }, /** * Check if the given item is the first of a the given scope * @param {Object} testMap - the test map * @param {String} itemIdentifier - the identifier of the item * @param {String} [scope = 'test'] - the target scope * @returns {Boolean} true if the item is the first one */ isFirstOf: function isFirstOf(testMap, itemIdentifier, scope) { var item; if (!_.isPlainObject(testMap)) { throw new TypeError('Invalid test map'); } if (_.isEmpty(itemIdentifier)) { throw new TypeError('Invalid item identifier'); } scope = scope || 'test'; item = mapHelper.getItem(testMap, itemIdentifier); if (scope === 'test') { return item.position === 0; } if (scope === 'section' || scope === 'assessmentSection' || scope === 'testSection') { return item.positionInSection === 0; } if (scope === 'part' || scope === 'testPart') { return item.positionInPart === 0; } return false; }, /** * Gets the map descriptors of the sibling items * @param {Object} testMap * @param {Number|String} itemPosition - (could be also the item id) * @param {String} [direction='both'] - previous/next/both * @param {Number} [size=3] - will be 2xsize if direction is both * @returns {Object[]} the collections of items */ getSiblingItems: function getSiblingItems(testMap, itemPosition, direction, size) { var itemId = mapHelper.getItemIdentifier(testMap, itemPosition); var previous = null; var siblings = []; var directions; var itemChain = _.reduce(testMap && testMap.jumps, function (map, jump) { var ref = jump.identifier; if (previous) { map[previous].next = ref; } map[ref] = { identifier: ref, previous: previous, next: null }; previous = ref; return map; }, {}); size = _.isFinite(size) ? parseInt(size, 10) : 3; if (!direction || direction === 'both') { directions = ['previous', 'next']; } else { directions = [direction]; } _.forEach(directions, function walkDirection(link) { var id = itemId; _.times(size, function getNeighbor() { id = itemChain[id] && itemChain[id][link]; if (id) { siblings.push(mapHelper.getItem(testMap, id)); } else { return false; } }); }); return siblings; }, /** * Gets the map descriptor of the next item * @param {Object} testMap * @param {Number|String} itemPosition - (could be also the item id) * @returns {Object} */ getNextItem: function getNextItem(testMap, itemPosition) { var siblings = navigationHelper.getSiblingItems(testMap, itemPosition, 'next', 1); if (siblings.length) { return siblings[0]; } return null; }, /** * Gets the map descriptor of the previous item * @param {Object} testMap * @param {Number|String} itemPosition - (could be also the item id) * @returns {Object} */ getPreviousItem: function getPreviousItem(testMap, itemPosition) { var siblings = navigationHelper.getSiblingItems(testMap, itemPosition, 'previous', 1); if (siblings.length) { return siblings[0]; } return null; }, /** * Checks if an action will move forward. * @param {String} action - the name of the action that will be performed * @param {Object} [params] - some optional parameters that apply to the action * @returns {Boolean} */ isMovingToNextItem: function isMovingToNextItem(action, params) { params = params || {}; return action === 'timeout' || action === 'skip' || action === 'move' && params.direction === 'next' && params.scope === 'item'; }, /** * Checks if an action will move backward. * @param {String} action - the name of the action that will be performed * @param {Object} [params] - some optional parameters that apply to the action * @returns {Boolean} */ isMovingToPreviousItem: function isMovingToPreviousItem(action, params) { params = params || {}; return action === 'move' && params.direction === 'previous' && params.scope === 'item'; }, /** * Checks if an action will jump on another item. * @param {String} action - the name of the action that will be performed * @param {Object} [params] - some optional parameters that apply to the action * @returns {Boolean} */ isJumpingToItem: function isJumpingToItem(action, params) { params = params || {}; return action === 'move' && params.direction === 'jump' && params.scope === 'item'; } }; return navigationHelper; });