Initial push, working on test cases
This commit is contained in:
10
.babelrc
Normal file
10
.babelrc
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"presets": [
|
||||
"@babel/preset-env",
|
||||
"@babel/preset-flow"
|
||||
],
|
||||
"plugins": [
|
||||
"@babel/plugin-proposal-class-properties",
|
||||
"@babel/plugin-proposal-private-methods"
|
||||
]
|
||||
}
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -59,3 +59,6 @@ typings/
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
# JetBrains Project Directory
|
||||
.idea/
|
||||
5508
package-lock.json
generated
Normal file
5508
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
42
package.json
Normal file
42
package.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "scorm-again",
|
||||
"version": "1.0.0",
|
||||
"description": "A modern SCORM JavaScript runtime library for AICC, Scorm 1.2, and Scorm 2004",
|
||||
"main": "index.js",
|
||||
"directories": {
|
||||
"test": "test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.7.0",
|
||||
"@babel/core": "^7.7.2",
|
||||
"@babel/node": "^7.7.0",
|
||||
"@babel/plugin-proposal-class-properties": "^7.7.0",
|
||||
"@babel/plugin-proposal-private-methods": "^7.6.0",
|
||||
"@babel/preset-env": "^7.7.1",
|
||||
"@babel/preset-flow": "^7.0.0",
|
||||
"@babel/register": "^7.7.0",
|
||||
"chai": "^4.2.0",
|
||||
"mocha": "^6.2.2",
|
||||
"nodemon": "^1.19.4",
|
||||
"nyc": "^14.1.1"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "./node_modules/.bin/mocha --compilers js:@babel/register"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+ssh://git@github.com/jcputney/scorm-again.git"
|
||||
},
|
||||
"keywords": [
|
||||
"scorm",
|
||||
"aicc",
|
||||
"tincan",
|
||||
"cmi"
|
||||
],
|
||||
"author": "Jonathan Putney <jonathan@putney.io>",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"bugs": {
|
||||
"url": "https://github.com/jcputney/scorm-again/issues"
|
||||
},
|
||||
"homepage": "https://github.com/jcputney/scorm-again#readme"
|
||||
}
|
||||
11
src/.flowconfig
Normal file
11
src/.flowconfig
Normal file
@@ -0,0 +1,11 @@
|
||||
[ignore]
|
||||
|
||||
[include]
|
||||
|
||||
[libs]
|
||||
|
||||
[lints]
|
||||
|
||||
[options]
|
||||
|
||||
[strict]
|
||||
57
src/AICC.js
Normal file
57
src/AICC.js
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (C) Noverant, Inc - All Rights Reserved Unauthorized copying of this file, via any
|
||||
* medium is strictly prohibited Proprietary and confidential
|
||||
*/
|
||||
|
||||
// @flow
|
||||
import Scorm12API from './Scorm12API';
|
||||
import {
|
||||
CMIInteractionsCorrectResponsesObject,
|
||||
CMIInteractionsObject,
|
||||
CMIInteractionsObjectivesObject,
|
||||
CMIObjectivesObject
|
||||
} from "./cmi/scorm12_cmi";
|
||||
import {CMIEvaluationCommentsObject, CMITriesObject, NAV} from "./cmi/aicc_cmi";
|
||||
|
||||
class AICC extends Scorm12API {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.nav = new NAV(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or builds a new child element to add to the array.
|
||||
*
|
||||
* @param CMIElement
|
||||
* @param value
|
||||
*/
|
||||
getChildElement(CMIElement, value) {
|
||||
let newChild;
|
||||
|
||||
if (this.stringContains(CMIElement, "cmi.objectives")) {
|
||||
newChild = new CMIObjectivesObject(this);
|
||||
} else if (this.stringContains(CMIElement, ".correct_responses")) {
|
||||
newChild = new CMIInteractionsCorrectResponsesObject(this);
|
||||
} else if (this.stringContains(CMIElement, ".objectives")) {
|
||||
newChild = new CMIInteractionsObjectivesObject(this);
|
||||
} else if (this.stringContains(CMIElement, "cmi.interactions")) {
|
||||
newChild = new CMIInteractionsObject(this);
|
||||
} else if (this.stringContains(CMIElement, "cmi.evaluation.comments")) {
|
||||
newChild = new CMIEvaluationCommentsObject(this);
|
||||
} else if (this.stringContains(CMIElement, "cmi.student_data.tries")) {
|
||||
newChild = new CMITriesObject(this);
|
||||
}
|
||||
|
||||
return newChild;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the whole API with another
|
||||
*/
|
||||
replaceWithAnotherScormAPI(newAPI) {
|
||||
// Data Model
|
||||
this.cmi = newAPI.cmi;
|
||||
this.nav = newAPI.nav;
|
||||
}
|
||||
}
|
||||
665
src/BaseAPI.js
Normal file
665
src/BaseAPI.js
Normal file
@@ -0,0 +1,665 @@
|
||||
/*
|
||||
* Copyright (C) Noverant, Inc - All Rights Reserved Unauthorized copying of this file, via any
|
||||
* medium is strictly prohibited Proprietary and confidential
|
||||
*/
|
||||
|
||||
// @flow
|
||||
import {CMIArray} from "./cmi/common";
|
||||
import {base_error_codes} from "./constants";
|
||||
|
||||
const api_constants = {
|
||||
SCORM_TRUE: "true",
|
||||
SCORM_FALSE: "false",
|
||||
STATE_NOT_INITIALIZED: 0,
|
||||
STATE_INITIALIZED: 1,
|
||||
STATE_TERMINATED: 2,
|
||||
LOG_LEVEL_DEBUG: 1,
|
||||
LOG_LEVEL_INFO: 2,
|
||||
LOG_LEVEL_WARNING: 3,
|
||||
LOG_LEVEL_ERROR: 4,
|
||||
LOG_LEVEL_NONE: 5
|
||||
};
|
||||
|
||||
export default class BaseAPI {
|
||||
#timeout;
|
||||
#error_codes;
|
||||
cmi;
|
||||
|
||||
constructor(error_codes) {
|
||||
this.currentState = api_constants.STATE_NOT_INITIALIZED;
|
||||
this.apiLogLevel = api_constants.LOG_LEVEL_ERROR;
|
||||
this.lastErrorCode = 0;
|
||||
this.listenerArray = [];
|
||||
|
||||
this.#timeout = null;
|
||||
this.#error_codes = error_codes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string} bool
|
||||
*/
|
||||
APIInitialize(callbackName: String, initializeMessage?: String, terminationMessage?: String) {
|
||||
let returnValue = api_constants.SCORM_FALSE;
|
||||
|
||||
if (this.isInitialized()) {
|
||||
this.throwSCORMError(this.#error_codes.INITIALIZED, initializeMessage);
|
||||
} else if (this.isTerminated()) {
|
||||
this.throwSCORMError(this.#error_codes.TERMINATED, terminationMessage);
|
||||
} else {
|
||||
this.currentState = api_constants.STATE_INITIALIZED;
|
||||
this.lastErrorCode = 0;
|
||||
returnValue = api_constants.SCORM_TRUE;
|
||||
this.processListeners(callbackName);
|
||||
}
|
||||
|
||||
this.apiLog(callbackName, null, "returned: " + returnValue, api_constants.LOG_LEVEL_INFO);
|
||||
this.clearSCORMError(returnValue);
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string} bool
|
||||
*/
|
||||
APITerminate(callbackName: String, checkTerminated: boolean) {
|
||||
let returnValue = api_constants.SCORM_FALSE;
|
||||
|
||||
if (this.checkState(checkTerminated, this.#error_codes.TERMINATION_BEFORE_INIT, this.#error_codes.MULTIPLE_TERMINATION)) {
|
||||
if (checkTerminated) this.lastErrorCode = 0;
|
||||
this.currentState = api_constants.STATE_TERMINATED;
|
||||
returnValue = api_constants.SCORM_TRUE;
|
||||
this.processListeners(callbackName);
|
||||
}
|
||||
|
||||
this.apiLog(callbackName, null, "returned: " + returnValue, api_constants.LOG_LEVEL_INFO);
|
||||
this.clearSCORMError(returnValue);
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callbackName
|
||||
* @param checkTerminated
|
||||
* @param CMIElement
|
||||
* @returns {string}
|
||||
*/
|
||||
APIGetValue(callbackName: String, checkTerminated: boolean, CMIElement) {
|
||||
let returnValue = "";
|
||||
|
||||
if (this.checkState(checkTerminated, this.#error_codes.RETRIEVE_BEFORE_INIT, this.#error_codes.RETRIEVE_AFTER_TERM)) {
|
||||
if (checkTerminated) this.lastErrorCode = 0;
|
||||
returnValue = this.getCMIValue(CMIElement);
|
||||
this.processListeners(callbackName, CMIElement);
|
||||
}
|
||||
|
||||
this.apiLog(callbackName, CMIElement, ": returned: " + returnValue, api_constants.LOG_LEVEL_INFO);
|
||||
this.clearSCORMError(returnValue);
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callbackName
|
||||
* @param checkTerminated
|
||||
* @param CMIElement
|
||||
* @param value
|
||||
* @returns {string}
|
||||
*/
|
||||
APISetValue(callbackName: String, checkTerminated: boolean, CMIElement, value) {
|
||||
let returnValue = "";
|
||||
|
||||
if (this.checkState(checkTerminated, this.#error_codes.STORE_BEFORE_INIT, this.#error_codes.STORE_AFTER_TERM)) {
|
||||
if (checkTerminated) this.lastErrorCode = 0;
|
||||
returnValue = this.setCMIValue(CMIElement, value);
|
||||
this.processListeners(callbackName, CMIElement, value);
|
||||
}
|
||||
|
||||
this.apiLog(callbackName, CMIElement, ": " + value + ": result: " + returnValue, api_constants.LOG_LEVEL_INFO);
|
||||
this.clearSCORMError(returnValue);
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Orders LMS to store all content parameters
|
||||
*
|
||||
* @returns {string} bool
|
||||
*/
|
||||
APICommit(callbackName: String, checkTerminated: boolean) {
|
||||
let returnValue = api_constants.SCORM_FALSE;
|
||||
|
||||
if (this.checkState(checkTerminated, this.#error_codes.COMMIT_BEFORE_INIT, this.#error_codes.COMMIT_AFTER_TERM)) {
|
||||
if (checkTerminated) this.lastErrorCode = 0;
|
||||
returnValue = api_constants.SCORM_TRUE;
|
||||
this.processListeners(callbackName);
|
||||
}
|
||||
|
||||
this.apiLog(callbackName, null, "returned: " + returnValue, api_constants.LOG_LEVEL_INFO);
|
||||
this.clearSCORMError(returnValue);
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns last error code
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
APIGetLastError(callbackName: String) {
|
||||
let returnValue = String(this.lastErrorCode);
|
||||
|
||||
this.processListeners(callbackName);
|
||||
|
||||
this.apiLog(callbackName, null, "returned: " + returnValue, api_constants.LOG_LEVEL_INFO);
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the errorNumber error description
|
||||
*
|
||||
* @param callbackName
|
||||
* @param CMIErrorCode
|
||||
* @returns {string}
|
||||
*/
|
||||
APIGetErrorString(callbackName: String, CMIErrorCode) {
|
||||
let returnValue = "";
|
||||
|
||||
if (CMIErrorCode !== null && CMIErrorCode !== "") {
|
||||
returnValue = this.getLmsErrorMessageDetails(CMIErrorCode);
|
||||
this.processListeners(callbackName);
|
||||
}
|
||||
|
||||
this.apiLog(callbackName, null, "returned: " + returnValue, api_constants.LOG_LEVEL_INFO);
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a comprehensive description of the errorNumber error.
|
||||
*
|
||||
* @param callbackName
|
||||
* @param CMIErrorCode
|
||||
* @returns {string}
|
||||
*/
|
||||
APIGetDiagnostic(callbackName: String, CMIErrorCode) {
|
||||
let returnValue = "";
|
||||
|
||||
if (CMIErrorCode !== null && CMIErrorCode !== "") {
|
||||
returnValue = this.getLmsErrorMessageDetails(CMIErrorCode, true);
|
||||
this.processListeners(callbackName);
|
||||
}
|
||||
|
||||
this.apiLog(callbackName, null, "returned: " + returnValue, api_constants.LOG_LEVEL_INFO);
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the LMS state and ensures it has been initialized
|
||||
*/
|
||||
checkState(checkTerminated: boolean, beforeInitError: number, afterTermError?: number) {
|
||||
if (this.isNotInitialized()) {
|
||||
this.throwSCORMError(beforeInitError);
|
||||
return false;
|
||||
} else if (checkTerminated && this.isTerminated()) {
|
||||
this.throwSCORMError(afterTermError);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logging for all SCORM actions
|
||||
*
|
||||
* @param functionName
|
||||
* @param CMIElement
|
||||
* @param logMessage
|
||||
* @param messageLevel
|
||||
*/
|
||||
apiLog(functionName: String, CMIElement: String, logMessage: String, messageLevel: number) {
|
||||
logMessage = this.formatMessage(functionName, CMIElement, logMessage);
|
||||
|
||||
if (messageLevel >= this.apiLogLevel) {
|
||||
switch (messageLevel) {
|
||||
case api_constants.LOG_LEVEL_ERROR:
|
||||
console.error(logMessage);
|
||||
break;
|
||||
case api_constants.LOG_LEVEL_WARNING:
|
||||
console.warn(logMessage);
|
||||
break;
|
||||
case api_constants.LOG_LEVEL_INFO:
|
||||
console.info(logMessage);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clears the last SCORM error code on success
|
||||
*/
|
||||
clearSCORMError(success: String) {
|
||||
if (success !== api_constants.SCORM_FALSE) {
|
||||
this.lastErrorCode = 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Formats the SCORM messages for easy reading
|
||||
*
|
||||
* @param functionName
|
||||
* @param CMIElement
|
||||
* @param message
|
||||
* @returns {string}
|
||||
*/
|
||||
formatMessage(functionName: String, CMIElement: String, message: String) {
|
||||
let baseLength = 20;
|
||||
let messageString = "";
|
||||
|
||||
messageString += functionName;
|
||||
|
||||
let fillChars = baseLength - messageString.length;
|
||||
|
||||
for (let i = 0; i < fillChars; i++) {
|
||||
messageString += " ";
|
||||
}
|
||||
|
||||
messageString += ": ";
|
||||
|
||||
if (CMIElement) {
|
||||
let CMIElementBaseLength = 70;
|
||||
|
||||
messageString += CMIElement;
|
||||
|
||||
fillChars = CMIElementBaseLength - messageString.length;
|
||||
|
||||
for (let j = 0; j < fillChars; j++) {
|
||||
messageString += " ";
|
||||
}
|
||||
}
|
||||
|
||||
if (message) {
|
||||
messageString += message;
|
||||
}
|
||||
|
||||
return messageString;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks to see if {str} contains {tester}
|
||||
*
|
||||
* @param str String to check against
|
||||
* @param tester String to check for
|
||||
*/
|
||||
stringContains(str: String, tester: String) {
|
||||
return str.indexOf(tester) > -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the message that corresponds to errorNumber
|
||||
* APIs that inherit BaseAPI should override this function
|
||||
*/
|
||||
getLmsErrorMessageDetails(_errorNumber, _detail) {
|
||||
return "No error";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value for the specific element.
|
||||
* APIs that inherit BaseAPI should override this function
|
||||
*/
|
||||
getCMIValue(_CMIElement) {
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value for the specific element.
|
||||
* APIs that inherit BaseAPI should override this function
|
||||
*/
|
||||
setCMIValue(_CMIElement, _value) {
|
||||
return "";
|
||||
}
|
||||
|
||||
_commonSetCMIValue(methodName: String, scorm2004: boolean, CMIElement, value) {
|
||||
if (!CMIElement || CMIElement === "") {
|
||||
return api_constants.SCORM_FALSE;
|
||||
}
|
||||
|
||||
let structure = CMIElement.split(".");
|
||||
let refObject = this;
|
||||
let returnValue = api_constants.SCORM_FALSE;
|
||||
|
||||
const invalidErrorMessage = `The data model element passed to ${methodName} (${CMIElement}) is not a valid SCORM data model element.`;
|
||||
const invalidErrorCode = scorm2004 ? this.#error_codes.UNDEFINED_DATA_MODEL: this.#error_codes.GENERAL;
|
||||
|
||||
for (let i = 0; i < structure.length; i++) {
|
||||
let attribute = structure[i];
|
||||
|
||||
if (i === structure.length - 1) {
|
||||
if (scorm2004 && (attribute.substr(0, 8) === "{target=") && (typeof refObject._isTargetValid == "function")) {
|
||||
this.throwSCORMError(this.#error_codes.READ_ONLY_ELEMENT);
|
||||
} else if (!refObject.hasOwnProperty(attribute)) {
|
||||
this.throwSCORMError(invalidErrorCode, invalidErrorMessage);
|
||||
} else {
|
||||
if (this.stringContains(CMIElement, ".correct_responses")){
|
||||
this.validateCorrectResponse(CMIElement, value)
|
||||
}
|
||||
|
||||
if (!scorm2004 || this.lastErrorCode === 0) {
|
||||
refObject[attribute] = value;
|
||||
returnValue = api_constants.SCORM_TRUE;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
refObject = refObject[attribute];
|
||||
if (!refObject) {
|
||||
this.throwSCORMError(invalidErrorCode, invalidErrorMessage);
|
||||
break;
|
||||
}
|
||||
|
||||
if (refObject.prototype === CMIArray) {
|
||||
let index = parseInt(structure[i + 1], 10);
|
||||
|
||||
// SCO is trying to set an item on an array
|
||||
if (!isNaN(index)) {
|
||||
let item = refObject.childArray[index];
|
||||
|
||||
if (item) {
|
||||
refObject = item;
|
||||
} else {
|
||||
let newChild = this.getChildElement(CMIElement, value);
|
||||
|
||||
if (!newChild) {
|
||||
this.throwSCORMError(invalidErrorCode, invalidErrorMessage);
|
||||
} else {
|
||||
refObject.childArray.push(newChild);
|
||||
refObject = newChild;
|
||||
}
|
||||
}
|
||||
|
||||
// Have to update i value to skip the array position
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (returnValue === api_constants.SCORM_FALSE) {
|
||||
this.apiLog(methodName, null, `There was an error setting the value for: ${CMIElement}, value of: ${value}`, api_constants.LOG_LEVEL_WARNING);
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
validateCorrectResponse(_CMIElement, _value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or builds a new child element to add to the array.
|
||||
* APIs that inherit BaseAPI should override this method
|
||||
*/
|
||||
getChildElement(_CMIElement) {
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a value from the CMI Object
|
||||
*
|
||||
* @param methodName
|
||||
* @param scorm2004
|
||||
* @param CMIElement
|
||||
* @returns {*}
|
||||
*/
|
||||
_commonGetCMIValue(methodName: String, scorm2004: boolean, CMIElement) {
|
||||
if (!CMIElement || CMIElement === "") {
|
||||
return "";
|
||||
}
|
||||
|
||||
let structure = CMIElement.split(".");
|
||||
let refObject = this;
|
||||
let attribute = null;
|
||||
|
||||
for (let i = 0; i < structure.length; i++) {
|
||||
attribute = structure[i];
|
||||
|
||||
if(!scorm2004) {
|
||||
if (i === structure.length - 1) {
|
||||
if (!refObject.hasOwnProperty(attribute)) {
|
||||
this.throwSCORMError(101, "getCMIValue did not find a value for: " + CMIElement);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ((String(attribute).substr(0, 8) === "{target=") && (typeof refObject._isTargetValid == "function")) {
|
||||
let target = String(attribute).substr(8, String(attribute).length - 9);
|
||||
return refObject._isTargetValid(target);
|
||||
} else if (!refObject.hasOwnProperty(attribute)) {
|
||||
this.throwSCORMError(401, "The data model element passed to GetValue (" + CMIElement + ") is not a valid SCORM data model element.");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
refObject = refObject[attribute];
|
||||
}
|
||||
|
||||
if (refObject === null || refObject === undefined) {
|
||||
if(!scorm2004) {
|
||||
if (attribute === "_children") {
|
||||
this.throwSCORMError(202);
|
||||
} else if (attribute === "_count") {
|
||||
this.throwSCORMError(203);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
} else {
|
||||
return refObject;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the API's current state is STATE_INITIALIZED
|
||||
*/
|
||||
isInitialized() {
|
||||
return this.currentState === api_constants.STATE_INITIALIZED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the API's current state is STATE_NOT_INITIALIZED
|
||||
*/
|
||||
isNotInitialized() {
|
||||
return this.currentState === api_constants.STATE_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the API's current state is STATE_TERMINATED
|
||||
*/
|
||||
isTerminated() {
|
||||
return this.currentState === api_constants.STATE_TERMINATED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a mechanism for attaching to a specific SCORM event
|
||||
*
|
||||
* @param listenerName
|
||||
* @param callback
|
||||
*/
|
||||
on(listenerName: String, callback: function) {
|
||||
if (!callback) return;
|
||||
|
||||
let listenerFunctions = listenerName.split(" ");
|
||||
for (let i = 0; i < listenerFunctions.length; i++) {
|
||||
let listenerSplit = listenerFunctions[i].split(".");
|
||||
if (listenerSplit.length === 0) return;
|
||||
|
||||
let functionName = listenerSplit[0];
|
||||
|
||||
let CMIElement = null;
|
||||
if (listenerSplit.length > 1) {
|
||||
CMIElement = listenerName.replace(functionName + ".", "");
|
||||
}
|
||||
|
||||
this.listenerArray.push({
|
||||
functionName: functionName,
|
||||
CMIElement: CMIElement,
|
||||
callback: callback
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Processes any 'on' listeners that have been created
|
||||
*
|
||||
* @param functionName
|
||||
* @param CMIElement
|
||||
* @param value
|
||||
*/
|
||||
processListeners(functionName: String, CMIElement: String, value: any) {
|
||||
for (let i = 0; i < this.listenerArray.length; i++) {
|
||||
let listener = this.listenerArray[i];
|
||||
let functionsMatch = listener.functionName === functionName;
|
||||
let listenerHasCMIElement = !!listener.CMIElement;
|
||||
let CMIElementsMatch = listener.CMIElement === CMIElement;
|
||||
|
||||
if (functionsMatch && (!listenerHasCMIElement || CMIElementsMatch)) {
|
||||
listener.callback(CMIElement, value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Throws a SCORM error
|
||||
*
|
||||
* @param errorNumber
|
||||
* @param message
|
||||
*/
|
||||
throwSCORMError(errorNumber: number, message: String) {
|
||||
if (!message) {
|
||||
message = this.getLmsErrorMessageDetails(errorNumber);
|
||||
}
|
||||
|
||||
this.apiLog("throwSCORMError", null, errorNumber + ": " + message, api_constants.LOG_LEVEL_ERROR);
|
||||
|
||||
this.lastErrorCode = String(errorNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads CMI data from a JSON object.
|
||||
*/
|
||||
loadFromJSON(json, CMIElement) {
|
||||
if (!this.isNotInitialized()) {
|
||||
console.error("loadFromJSON can only be called before the call to LMSInitialize.");
|
||||
return;
|
||||
}
|
||||
|
||||
CMIElement = CMIElement || "cmi";
|
||||
|
||||
for (let key in json) {
|
||||
if (json.hasOwnProperty(key) && json[key]) {
|
||||
let currentCMIElement = CMIElement + "." + key;
|
||||
let value = json[key];
|
||||
|
||||
if (value["childArray"]) {
|
||||
for (let i = 0; i < value["childArray"].length; i++) {
|
||||
this.loadFromJSON(value["childArray"][i], currentCMIElement + "." + i);
|
||||
}
|
||||
} else if (value.constructor === Object) {
|
||||
this.loadFromJSON(value, currentCMIElement);
|
||||
} else {
|
||||
this.setCMIValue(currentCMIElement, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
renderCMIToJSON() {
|
||||
let cmi = this.cmi;
|
||||
// Do we want/need to return fields that have no set value?
|
||||
// return JSON.stringify({ cmi }, (k, v) => v === undefined ? null : v, 2);
|
||||
return JSON.stringify({ cmi });
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the value matches the proper format. If not, throw proper error code.
|
||||
*
|
||||
* @param value
|
||||
* @param regexPattern
|
||||
* @returns {boolean}
|
||||
*/
|
||||
checkValidFormat(value: String, regexPattern: String) {
|
||||
const formatRegex = new RegExp(regexPattern);
|
||||
if(!value || !value.match(formatRegex)) {
|
||||
this.throwSCORMError(this.#error_codes.TYPE_MISMATCH);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Check if the value matches the proper range. If not, throw proper error code.
|
||||
*
|
||||
* @param value
|
||||
* @param rangePattern
|
||||
* @returns {boolean}
|
||||
*/
|
||||
checkValidRange(value: any, rangePattern: String) {
|
||||
const ranges = rangePattern.split('#');
|
||||
value = value * 1.0;
|
||||
if(value >= ranges[0]) {
|
||||
if((ranges[1] === '*') || (value <= ranges[1])) {
|
||||
this.clearSCORMError(api_constants.SCORM_TRUE);
|
||||
return true;
|
||||
} else {
|
||||
this.throwSCORMError(this.#error_codes.VALUE_OUT_OF_RANGE);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
this.throwSCORMError(this.#error_codes.VALUE_OUT_OF_RANGE);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws a SCORM error
|
||||
*
|
||||
* @param when the number of milliseconds to wait before committing
|
||||
*/
|
||||
scheduleCommit(when: number) {
|
||||
this.#timeout = new ScheduledCommit(this, when);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears and cancels any currently scheduled commits
|
||||
*/
|
||||
clearScheduledCommit() {
|
||||
if (this.#timeout) {
|
||||
this.#timeout.cancel();
|
||||
this.#timeout = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ScheduledCommit {
|
||||
#API;
|
||||
#cancelled: false;
|
||||
#timeout;
|
||||
|
||||
constructor(API: any, when: number) {
|
||||
this.#API = API;
|
||||
this.#timeout = setTimeout(this.#wrapper, when);
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.#cancelled = true;
|
||||
if (this.#timeout) {
|
||||
clearTimeout(this.#timeout);
|
||||
}
|
||||
}
|
||||
|
||||
#wrapper() {
|
||||
if (!this.#cancelled) {
|
||||
this.#API.LMSCommit();
|
||||
}
|
||||
}
|
||||
}
|
||||
181
src/Scorm12API.js
Normal file
181
src/Scorm12API.js
Normal file
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Copyright (C) Noverant, Inc - All Rights Reserved Unauthorized copying of this file, via any
|
||||
* medium is strictly prohibited Proprietary and confidential
|
||||
*/
|
||||
|
||||
// @flow
|
||||
import BaseAPI from './BaseAPI';
|
||||
import {
|
||||
CMI,
|
||||
CMIInteractionsCorrectResponsesObject,
|
||||
CMIInteractionsObject, CMIInteractionsObjectivesObject,
|
||||
CMIObjectivesObject
|
||||
} from "./cmi/scorm12_cmi";
|
||||
import * as Utilities from './utilities';
|
||||
import {scorm12_constants, scorm12_error_codes} from "./constants";
|
||||
import {scorm12_regex} from "./regex";
|
||||
|
||||
const constants = scorm12_constants;
|
||||
|
||||
export default class Scorm12API extends BaseAPI {
|
||||
constructor() {
|
||||
super(scorm12_error_codes);
|
||||
|
||||
this.cmi = new CMI(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string} bool
|
||||
*/
|
||||
LMSInitialize() {
|
||||
return this.APIInitialize("LMSInitialize", "LMS was already initialized!", "LMS is already finished!");
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string} bool
|
||||
*/
|
||||
LMSFinish() {
|
||||
return this.APITerminate("LMSFinish", false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CMIElement
|
||||
* @returns {string}
|
||||
*/
|
||||
LMSGetValue(CMIElement) {
|
||||
return this.APIGetValue("LMSGetValue", false, CMIElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CMIElement
|
||||
* @param value
|
||||
* @returns {string}
|
||||
*/
|
||||
LMSSetValue(CMIElement, value) {
|
||||
return this.APISetValue("LMSSetValue", false, CMIElement, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Orders LMS to store all content parameters
|
||||
*
|
||||
* @returns {string} bool
|
||||
*/
|
||||
LMSCommit() {
|
||||
return this.APICommit("LMSCommit", false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns last error code
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
LMSGetLastError() {
|
||||
return this.APIGetLastError("LMSGetLastError");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the errorNumber error description
|
||||
*
|
||||
* @param CMIErrorCode
|
||||
* @returns {string}
|
||||
*/
|
||||
LMSGetErrorString(CMIErrorCode) {
|
||||
return this.APIGetErrorString("LMSGetErrorString", CMIErrorCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a comprehensive description of the errorNumber error.
|
||||
*
|
||||
* @param CMIErrorCode
|
||||
* @returns {string}
|
||||
*/
|
||||
LMSGetDiagnostic(CMIErrorCode) {
|
||||
return this.APIGetDiagnostic("LMSGetDiagnostic", CMIErrorCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a value on the CMI Object
|
||||
*
|
||||
* @param CMIElement
|
||||
* @param value
|
||||
* @returns {string}
|
||||
*/
|
||||
setCMIValue(CMIElement, value) {
|
||||
this._commonSetCMIValue("LMSSetValue", false, CMIElement, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a value from the CMI Object
|
||||
*
|
||||
* @param CMIElement
|
||||
* @returns {*}
|
||||
*/
|
||||
getCMIValue(CMIElement) {
|
||||
return this._commonGetCMIValue("getCMIValue", false, CMIElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or builds a new child element to add to the array.
|
||||
*
|
||||
* @param CMIElement
|
||||
*/
|
||||
getChildElement(CMIElement, value) {
|
||||
let newChild;
|
||||
|
||||
if (this.stringContains(CMIElement, "cmi.objectives")) {
|
||||
newChild = new CMIObjectivesObject(this);
|
||||
} else if (this.stringContains(CMIElement, ".correct_responses")) {
|
||||
newChild = new CMIInteractionsCorrectResponsesObject(this);
|
||||
} else if (this.stringContains(CMIElement, ".objectives")) {
|
||||
newChild = new CMIInteractionsObjectivesObject(this);
|
||||
} else if (this.stringContains(CMIElement, "cmi.interactions")) {
|
||||
newChild = new CMIInteractionsObject(this);
|
||||
}
|
||||
|
||||
return newChild;
|
||||
}
|
||||
|
||||
validateCorrectResponse(CMIElement, value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message that corresponds to errorNumber.
|
||||
*/
|
||||
getLmsErrorMessageDetails(errorNumber, detail) {
|
||||
let basicMessage = "No Error";
|
||||
let detailMessage = "No Error";
|
||||
|
||||
// Set error number to string since inconsistent from modules if string or number
|
||||
errorNumber = String(errorNumber);
|
||||
if(constants.error_descriptions[errorNumber]) {
|
||||
basicMessage = constants.error_descriptions[errorNumber].basicMessage;
|
||||
detailMessage = constants.error_descriptions[errorNumber].detailMessage;
|
||||
}
|
||||
|
||||
return detail ? detailMessage : basicMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the current session time to the existing total time.
|
||||
*/
|
||||
getCurrentTotalTime() {
|
||||
const timeRegex = new RegExp(scorm12_regex.CMITime);
|
||||
|
||||
const totalTime = this.cmi.core.total_time;
|
||||
const sessionTime = this.cmi.core.session_time;
|
||||
|
||||
const totalSeconds = Utilities.getTimeAsSeconds(totalTime, timeRegex);
|
||||
const sessionSeconds = Utilities.getTimeAsSeconds(sessionTime, timeRegex);
|
||||
|
||||
return Utilities.getSecondsAsHHMMSS(totalSeconds + sessionSeconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the whole API with another
|
||||
*/
|
||||
replaceWithAnotherScormAPI(newAPI) {
|
||||
// Data Model
|
||||
this.cmi = newAPI.cmi;
|
||||
}
|
||||
}
|
||||
382
src/Scorm2004API.js
Normal file
382
src/Scorm2004API.js
Normal file
@@ -0,0 +1,382 @@
|
||||
/*
|
||||
* Copyright (C) Noverant, Inc - All Rights Reserved Unauthorized copying of this file, via any
|
||||
* medium is strictly prohibited Proprietary and confidential
|
||||
*/
|
||||
|
||||
// @flow
|
||||
import BaseAPI from './BaseAPI';
|
||||
import {
|
||||
CMI,
|
||||
ADL,
|
||||
CMIObjectivesObject,
|
||||
CMIInteractionsObject,
|
||||
CMICommentsFromLearnerObject,
|
||||
CMICommentsFromLMSObject, CMIInteractionsCorrectResponsesObject, CMIInteractionsObjectivesObject
|
||||
} from './cmi/scorm2004_cmi';
|
||||
import * as Utilities from "./utilities";
|
||||
import {scorm2004_constants, correct_responses, scorm2004_error_codes} from "./constants";
|
||||
import {scorm2004_regex} from "./regex";
|
||||
|
||||
const constants = scorm2004_constants;
|
||||
const valid_languages = constants.valid_languages;
|
||||
|
||||
class Scorm2004API extends BaseAPI {
|
||||
version: "1.0";
|
||||
|
||||
constructor() {
|
||||
super(scorm2004_error_codes);
|
||||
|
||||
this.cmi = new CMI(this);
|
||||
this.adl = new ADL(this);
|
||||
|
||||
// Rename functions to match 2004 Spec
|
||||
this.Initialize = this.LMSInitialize;
|
||||
this.Terminate = this.LMSTerminate;
|
||||
this.GetValue = this.LMSGetValue;
|
||||
this.SetValue = this.LMSSetValue;
|
||||
this.Commit = this.LMSCommit;
|
||||
this.GetLastError = this.LMSGetLastError;
|
||||
this.GetErrorString = this.LMSGetErrorString;
|
||||
this.GetDiagnostic = this.LMSGetDiagnostic;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string} bool
|
||||
*/
|
||||
LMSInitialize() {
|
||||
return this.APIInitialize("Initialize");
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string} bool
|
||||
*/
|
||||
LMSTerminate() {
|
||||
return this.APITerminate("Terminate", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CMIElement
|
||||
* @returns {string}
|
||||
*/
|
||||
LMSGetValue(CMIElement) {
|
||||
return this.APIGetValue("GetValue", true, CMIElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CMIElement
|
||||
* @param value
|
||||
* @returns {string}
|
||||
*/
|
||||
LMSSetValue(CMIElement, value) {
|
||||
return this.APISetValue("SetValue", true, CMIElement, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Orders LMS to store all content parameters
|
||||
*
|
||||
* @returns {string} bool
|
||||
*/
|
||||
LMSCommit() {
|
||||
return this.APICommit("Commit");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns last error code
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
LMSGetLastError() {
|
||||
return this.APIGetLastError("GetLastError");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the errorNumber error description
|
||||
*
|
||||
* @param CMIErrorCode
|
||||
* @returns {string}
|
||||
*/
|
||||
LMSGetErrorString(CMIErrorCode) {
|
||||
return this.APIGetErrorString("GetErrorString", CMIErrorCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a comprehensive description of the errorNumber error.
|
||||
*
|
||||
* @param CMIErrorCode
|
||||
* @returns {string}
|
||||
*/
|
||||
LMSGetDiagnostic(CMIErrorCode) {
|
||||
return this.APIGetDiagnostic("GetDiagnostic", CMIErrorCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a value on the CMI Object
|
||||
*
|
||||
* @param CMIElement
|
||||
* @param value
|
||||
* @returns {string}
|
||||
*/
|
||||
setCMIValue(CMIElement, value) {
|
||||
this._commonSetCMIValue("SetValue", true, CMIElement, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or builds a new child element to add to the array.
|
||||
*
|
||||
* @param CMIElement
|
||||
* @param value
|
||||
*/
|
||||
getChildElement(CMIElement, value) {
|
||||
let newChild;
|
||||
|
||||
if (this.stringContains(CMIElement, "cmi.objectives")) {
|
||||
newChild = new CMIObjectivesObject(this);
|
||||
} else if (this.stringContains(CMIElement, ".correct_responses")) {
|
||||
const parts = CMIElement.split('.');
|
||||
const index = Number(parts[2]);
|
||||
let interaction = this.cmi.interactions.childArray[index];
|
||||
if(typeof interaction.type === 'undefined') {
|
||||
this.throwSCORMError(scorm2004_error_codes.DEPENDENCY_NOT_ESTABLISHED);
|
||||
} else {
|
||||
let interaction_type = interaction.type;
|
||||
let interaction_count = interaction.correct_responses._count;
|
||||
if(interaction_type === 'choice') {
|
||||
for(let i = 0; i < interaction_count && this.lastErrorCode === 0; i++) {
|
||||
const response = interaction.correct_responses.childArray[i];
|
||||
if(response.pattern === value) {
|
||||
this.throwSCORMError(scorm2004_error_codes.GENERAL_SET_FAILURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const response_type = correct_responses[interaction_type];
|
||||
let nodes = [];
|
||||
if(response_type.delimiter !== '') {
|
||||
nodes = value.split(response_type.delimiter);
|
||||
} else {
|
||||
nodes[0] = value;
|
||||
}
|
||||
|
||||
if(nodes.length > 0 && nodes.length <= response_type.max) {
|
||||
this.#checkCorrectResponseValue(interaction_type, nodes, value);
|
||||
} else if (nodes.length > response_type.max) {
|
||||
this.throwSCORMError(scorm2004_error_codes.GENERAL_SET_FAILURE, "Data Model Element Pattern Too Long");
|
||||
}
|
||||
}
|
||||
if(this.lastErrorCode === 0) {
|
||||
newChild = new CMIInteractionsCorrectResponsesObject(this);
|
||||
}
|
||||
} else if (this.stringContains(CMIElement, ".objectives")) {
|
||||
newChild = new CMIInteractionsObjectivesObject(this);
|
||||
} else if (this.stringContains(CMIElement, "cmi.interactions")) {
|
||||
newChild = new CMIInteractionsObject(this);
|
||||
} else if (this.stringContains(CMIElement, "cmi.comments_from_learner")) {
|
||||
newChild = new CMICommentsFromLearnerObject(this);
|
||||
} else if (this.stringContains(CMIElement, "cmi.comments_from_lms")) {
|
||||
newChild = new CMICommentsFromLMSObject(this);
|
||||
}
|
||||
|
||||
return newChild;
|
||||
}
|
||||
|
||||
validateCorrectResponse(CMIElement, value) {
|
||||
const parts = CMIElement.split('.');
|
||||
const index = Number(parts[2]);
|
||||
const pattern_index = Number(parts[4]);
|
||||
let interaction = this.cmi.interactions.childArray[index];
|
||||
|
||||
let interaction_type = interaction.type;
|
||||
let interaction_count = interaction.correct_responses._count;
|
||||
if(interaction_type === 'choice') {
|
||||
for(let i = 0; i < interaction_count && this.lastErrorCode === 0; i++) {
|
||||
const response = interaction.correct_responses.childArray[i];
|
||||
if(response.pattern === value) {
|
||||
this.throwSCORMError(scorm2004_error_codes.GENERAL_SET_FAILURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const response_type = scorm2004_constants.correct_responses[interaction_type];
|
||||
if(typeof response_type.limit !== 'undefined' || interaction_count < response_type.limit) {
|
||||
let nodes = [];
|
||||
if (response_type.delimiter !== '') {
|
||||
nodes = value.split(response_type.delimiter);
|
||||
} else {
|
||||
nodes[0] = value;
|
||||
}
|
||||
|
||||
if(nodes.length > 0 && nodes.length <= response_type.max) {
|
||||
this.#checkCorrectResponseValue(interaction_type, nodes, value);
|
||||
} else if (nodes.length > response_type.max) {
|
||||
this.throwSCORMError(scorm2004_error_codes.GENERAL_SET_FAILURE, "Data Model Element Pattern Too Long");
|
||||
}
|
||||
|
||||
if(this.lastErrorCode === 0
|
||||
&& (!response_type.duplicate || !this.#checkDuplicatedPattern(interaction.correct_responses, pattern_index, value))
|
||||
|| (this.lastErrorCode === 0 && value === '')) {
|
||||
// do nothing, we want the inverse
|
||||
} else {
|
||||
if(this.lastErrorCode === 0) {
|
||||
this.throwSCORMError(scorm2004_error_codes.GENERAL_SET_FAILURE, "Data Model Element Pattern Already Exists");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.throwSCORMError(scorm2004_error_codes.GENERAL_SET_FAILURE, "Data Model Element Collection Limit Reached");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a value from the CMI Object
|
||||
*
|
||||
* @param CMIElement
|
||||
* @returns {*}
|
||||
*/
|
||||
getCMIValue(CMIElement) {
|
||||
return this._commonGetCMIValue("GetValue", true, CMIElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message that corresponds to errorNumber.
|
||||
*/
|
||||
getLmsErrorMessageDetails(errorNumber, detail) {
|
||||
let basicMessage = "";
|
||||
let detailMessage = "";
|
||||
|
||||
// Set error number to string since inconsistent from modules if string or number
|
||||
errorNumber = String(errorNumber);
|
||||
if(constants.error_descriptions[errorNumber]) {
|
||||
basicMessage = constants.error_descriptions[errorNumber].basicMessage;
|
||||
detailMessage = constants.error_descriptions[errorNumber].detailMessage;
|
||||
}
|
||||
|
||||
return detail ? detailMessage : basicMessage;
|
||||
}
|
||||
|
||||
#checkDuplicatedPattern(correct_response, current_index, value) {
|
||||
let found = false;
|
||||
let count = correct_response._count;
|
||||
for(let i = 0; i < count && !found; i++) {
|
||||
if(i !== current_index && correct_response.childArray[i] === value) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
#checkCorrectResponseValue(interaction_type, nodes, value) {
|
||||
const response = correct_responses[interaction_type];
|
||||
const formatRegex = new RegExp(response.format);
|
||||
for(let i = 0; i < nodes.length && this.lastErrorCode === 0; i++) {
|
||||
if(interaction_type.match('^(fill-in|long-fill-in|matching|performance|sequencing)$')) {
|
||||
nodes[i] = #removeCorrectResponsePrefixes(nodes[i]);
|
||||
}
|
||||
|
||||
if(response.delimiter2 !== undefined) {
|
||||
let values = nodes[i].split(response.delimiter2);
|
||||
if(values.length === 2) {
|
||||
let matches = values[0].match(formatRegex);
|
||||
if(!matches) {
|
||||
this.throwSCORMError(scorm2004_error_codes.TYPE_MISMATCH);
|
||||
} else {
|
||||
if(!values[1].match(new RegExp(response.format2))) {
|
||||
this.throwSCORMError(scorm2004_error_codes.TYPE_MISMATCH);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.throwSCORMError(scorm2004_error_codes.TYPE_MISMATCH);
|
||||
}
|
||||
} else {
|
||||
let matches = nodes[i].match(formatRegex);
|
||||
if((!matches && value !== '') || (!matches && interaction_type === 'true-false')) {
|
||||
this.throwSCORMError(scorm2004_error_codes.TYPE_MISMATCH);
|
||||
} else {
|
||||
if (interaction_type === 'numeric' && nodes.length > 1) {
|
||||
if(Number(nodes[0]) > Number(nodes[1])) {
|
||||
this.throwSCORMError(scorm2004_error_codes.TYPE_MISMATCH);
|
||||
}
|
||||
} else {
|
||||
if(nodes[i] !== '' && response.unique) {
|
||||
for(let j = 0; j < i && this.lastErrorCode === 0; j++) {
|
||||
if(nodes[i] === nodes[j]) {
|
||||
this.throwSCORMError(scorm2004_error_codes.TYPE_MISMATCH);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#removeCorrectResponsePrefixes(node) {
|
||||
let seenOrder = false;
|
||||
let seenCase = false;
|
||||
let seenLang = false;
|
||||
|
||||
let prefixRegex = '^(\{(lang|case_matters|order_matters)=([^\}]+)\})';
|
||||
let matches = node.match(prefixRegex);
|
||||
while(matches) {
|
||||
switch (matches[2]) {
|
||||
case 'lang':
|
||||
let langMatches = node.match(scorm2004_regex.CMILangcr);
|
||||
if(langMatches) {
|
||||
let lang = langMatches[3];
|
||||
if (lang !== undefined && lang.length > 0) {
|
||||
if(valid_languages[lang.toLowerCase()] === undefined) {
|
||||
this.throwSCORMError(scorm2004_error_codes.TYPE_MISMATCH);
|
||||
}
|
||||
}
|
||||
}
|
||||
seenLang = true;
|
||||
break;
|
||||
case 'case_matters':
|
||||
if(!seenLang && !seenOrder && !seenCase) {
|
||||
if(matches[3] !== 'true' && matches[3] !== 'false') {
|
||||
this.throwSCORMError(scorm2004_error_codes.TYPE_MISMATCH);
|
||||
}
|
||||
}
|
||||
|
||||
seenCase = true;
|
||||
break;
|
||||
case 'order_matters':
|
||||
if(!seenCase && !seenLang && !seenOrder) {
|
||||
if(matches[3] !== 'true' && matches[3] !== 'false') {
|
||||
this.throwSCORMError(scorm2004_error_codes.TYPE_MISMATCH);
|
||||
}
|
||||
}
|
||||
|
||||
seenOrder = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
node = node.substr(matches[1].length);
|
||||
matches = node.match(prefixRegex);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the whole API with another
|
||||
*/
|
||||
replaceWithAnotherScormAPI(newAPI) {
|
||||
// Data Model
|
||||
this.cmi = newAPI.cmi;
|
||||
this.adl = newAPI.adl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the current session time to the existing total time.
|
||||
*/
|
||||
getCurrentTotalTime() {
|
||||
const totalTime = this.cmi.total_time;
|
||||
const sessionTime = this.cmi.session_time;
|
||||
|
||||
const durationRegex = scorm2004_regex.CMITimespan;
|
||||
const totalSeconds = Utilities.getDurationAsSeconds(totalTime, durationRegex);
|
||||
const sessionSeconds = Utilities.getDurationAsSeconds(sessionTime, durationRegex);
|
||||
|
||||
return Utilities.getSecondsAsISODuration(totalSeconds + sessionSeconds);
|
||||
}
|
||||
}
|
||||
120
src/cmi/aicc_cmi.js
Normal file
120
src/cmi/aicc_cmi.js
Normal file
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright (C) Noverant, Inc - All Rights Reserved Unauthorized copying of this file, via any
|
||||
* medium is strictly prohibited Proprietary and confidential
|
||||
*/
|
||||
|
||||
import * as Scorm12CMI from './scorm12_cmi';
|
||||
import {BaseCMI, CMIArray, CMIScore} from "./common";
|
||||
import {aicc_constants} from "../constants";
|
||||
import {aicc_regex} from "../regex";
|
||||
|
||||
const constants = aicc_constants;
|
||||
const regex = aicc_regex;
|
||||
|
||||
export class CMI extends Scorm12CMI.CMI {
|
||||
constructor(API) {
|
||||
super(API, constants.cmi_children, new AICCCMIStudentData(API));
|
||||
|
||||
this.evaluation = new CMIEvaluation(API);
|
||||
}
|
||||
}
|
||||
|
||||
class CMIEvaluation extends BaseCMI {
|
||||
constructor(API) {
|
||||
super(API);
|
||||
}
|
||||
|
||||
comments = new class extends CMIArray {
|
||||
constructor(API) {
|
||||
super(API, constants.comments_children, 402);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class AICCCMIStudentData extends Scorm12CMI.CMIStudentData {
|
||||
constructor(API) {
|
||||
super(API, constants.student_data_children);
|
||||
}
|
||||
|
||||
#tries_during_lesson = "";
|
||||
|
||||
get tries_during_lesson() { return this.#tries_during_lesson; }
|
||||
set tries_during_lesson(tries_during_lesson) { this.API.isNotInitialized() ? this.#tries_during_lesson = tries_during_lesson : this.throwReadOnlyError() }
|
||||
|
||||
tries = new class extends CMIArray {
|
||||
constructor(API) {
|
||||
super(API, aicc_constants.tries_children);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export class CMITriesObject extends BaseCMI {
|
||||
constructor(API) {
|
||||
super(API);
|
||||
}
|
||||
|
||||
#status = "";
|
||||
#time = "";
|
||||
|
||||
get status() { return this.#status; }
|
||||
set status(status) {
|
||||
if(this.API.checkValidFormat(status, regex.CMIStatus2)) {
|
||||
this.#status = status;
|
||||
}
|
||||
}
|
||||
|
||||
get time() { return this.#time; }
|
||||
set time(time) {
|
||||
if(this.API.checkValidFormat(time, regex.CMITime)) {
|
||||
this.#time = time;
|
||||
}
|
||||
}
|
||||
|
||||
score = new CMIScore(API);
|
||||
}
|
||||
|
||||
export class CMIEvaluationCommentsObject extends BaseCMI {
|
||||
constructor(API) {
|
||||
super(API);
|
||||
}
|
||||
|
||||
#content = "";
|
||||
#location = "";
|
||||
#time = "";
|
||||
|
||||
get content() { return this.#content; }
|
||||
set content(content) {
|
||||
if(this.API.checkValidFormat(content, regex.CMIString256)) {
|
||||
this.#content = content;
|
||||
}
|
||||
}
|
||||
|
||||
get location() { return this.#location; }
|
||||
set location(location) {
|
||||
if(this.API.checkValidFormat(location, regex.CMIString256)) {
|
||||
this.#location = location;
|
||||
}
|
||||
}
|
||||
|
||||
get time() { return this.#time; }
|
||||
set time(time) {
|
||||
if(this.API.checkValidFormat(time, regex.CMITime)) {
|
||||
this.#time = time;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class NAV extends BaseCMI {
|
||||
constructor(API) {
|
||||
super(API);
|
||||
}
|
||||
|
||||
#event = "";
|
||||
|
||||
get event() { return (!this.jsonString) ? this.API.throwSCORMError(404) : this.#event; }
|
||||
set event(event) {
|
||||
if(this.API.checkValidFormat(event, regex.NAVEvent)) {
|
||||
this.#event = event;
|
||||
}
|
||||
}
|
||||
}
|
||||
95
src/cmi/common.js
Normal file
95
src/cmi/common.js
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (C) Noverant, Inc - All Rights Reserved Unauthorized copying of this file, via any
|
||||
* medium is strictly prohibited Proprietary and confidential
|
||||
*/
|
||||
|
||||
import {scorm12_constants, scorm12_error_codes} from "../constants";
|
||||
|
||||
export class BaseCMI {
|
||||
jsonString = false;
|
||||
API;
|
||||
|
||||
constructor(API: any) {
|
||||
this.API = API;
|
||||
}
|
||||
}
|
||||
|
||||
export class CMIScore extends BaseCMI {
|
||||
constructor(API, score_children?, score_range?, invalidErrorCode) {
|
||||
super(API);
|
||||
|
||||
this.#_children = score_children? score_children : scorm12_constants.score_children;
|
||||
this.#_score_range = score_range? score_range : false;
|
||||
this.#_invalid_error_code = invalidErrorCode ? invalidErrorCode : scorm12_error_codes.INVALID_SET_VALUE;
|
||||
}
|
||||
|
||||
#_children;
|
||||
#_score_range;
|
||||
#_invalid_error_code;
|
||||
#raw = "";
|
||||
#min = "";
|
||||
#max = "100";
|
||||
|
||||
get _children() { return this.#_children; }
|
||||
set _children(_children) { this.API.throwSCORMError(this.#_invalid_error_code); }
|
||||
|
||||
get raw() { return this.#raw; }
|
||||
set raw(raw) {
|
||||
if(this.API.checkValidFormat(raw, scorm12_constants.CMIDecimal)
|
||||
&& (!this.#_score_range || this.API.checkValidRange(raw, this.#_score_range))) {
|
||||
this.#raw = raw;
|
||||
}
|
||||
}
|
||||
|
||||
get min() { return this.#min; }
|
||||
set min(min) {
|
||||
if(this.API.checkValidFormat(min, scorm12_constants.CMIDecimal)
|
||||
&& (!this.#_score_range || this.API.checkValidRange(min, this.#_score_range))) {
|
||||
this.#min = min;
|
||||
}
|
||||
}
|
||||
|
||||
get max() { return this.#max; }
|
||||
set max(max) {
|
||||
if(this.API.checkValidFormat(max, scorm12_constants.CMIDecimal)
|
||||
&& (!this.#_score_range || this.API.checkValidRange(max, this.#_score_range))) {
|
||||
this.#max = max;
|
||||
}
|
||||
}
|
||||
|
||||
toJSON = () => {
|
||||
return {
|
||||
'raw': this.raw,
|
||||
'min': this.min,
|
||||
'max': this.max
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class CMIArray extends BaseCMI {
|
||||
constructor({API, children, errorCode}) {
|
||||
super(API);
|
||||
this.#_children = children;
|
||||
this.#errorCode = errorCode;
|
||||
this.childArray = [];
|
||||
}
|
||||
|
||||
#errorCode;
|
||||
#_children;
|
||||
|
||||
get _children() { return this.#_children; }
|
||||
set _children(_children) { this.API.throwSCORMError(this.#errorCode); }
|
||||
|
||||
get _count() { return this.childArray.length; }
|
||||
set _count(_count) { this.API.throwSCORMError(this.#errorCode); }
|
||||
|
||||
toJSON = () => {
|
||||
this.jsonString = true;
|
||||
let result = {};
|
||||
for(let i = 0; i < this.childArray.length; i++) {
|
||||
result[i + ''] = this.childArray[i];
|
||||
}
|
||||
delete this.jsonString;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
467
src/cmi/scorm12_cmi.js
Normal file
467
src/cmi/scorm12_cmi.js
Normal file
@@ -0,0 +1,467 @@
|
||||
/*
|
||||
* Copyright (C) Noverant, Inc - All Rights Reserved Unauthorized copying of this file, via any
|
||||
* medium is strictly prohibited Proprietary and confidential
|
||||
*/
|
||||
|
||||
import {BaseCMI, CMIArray, CMIScore} from './common';
|
||||
import {scorm12_constants, scorm12_error_codes} from "../constants";
|
||||
import {scorm12_regex} from "../regex";
|
||||
|
||||
const constants = scorm12_constants;
|
||||
const regex = scorm12_regex;
|
||||
|
||||
function throwReadOnlyError(API) {
|
||||
API.throwSCORMError(scorm12_error_codes.READ_ONLY_ELEMENT);
|
||||
}
|
||||
|
||||
function throwWriteOnlyError(API) {
|
||||
API.throwSCORMError(scorm12_error_codes.WRITE_ONLY_ELEMENT);
|
||||
}
|
||||
|
||||
function throwInvalidValueError(API) {
|
||||
API.throwSCORMError(scorm12_error_codes.INVALID_SET_VALUE);
|
||||
}
|
||||
|
||||
export class CMI extends BaseCMI {
|
||||
#_children = "";
|
||||
#_version = "3.4";
|
||||
#suspend_data = "";
|
||||
#launch_data = "";
|
||||
#comments = "";
|
||||
#comments_from_lms = "";
|
||||
|
||||
student_data = null;
|
||||
|
||||
constructor(API, cmi_children, student_data) {
|
||||
super(API);
|
||||
|
||||
this.#_children = cmi_children ? cmi_children : constants.cmi_children;
|
||||
this.core = new CMICore(API);
|
||||
this.objectives = new CMIObjectives(API);
|
||||
this.student_data = student_data ? student_data : new CMIStudentData(API);
|
||||
this.student_preference = new CMIStudentPreference(API);
|
||||
this.interactions = new CMIInteractions(API);
|
||||
}
|
||||
|
||||
toJSON = () => {
|
||||
this.jsonString = true;
|
||||
const result = {
|
||||
'suspend_data': this.suspend_data,
|
||||
'launch_data': this.launch_data,
|
||||
'comments': this.comments,
|
||||
'comments_from_lms': this.comments_from_lms,
|
||||
'core': this.core,
|
||||
'objectives': this.objectives,
|
||||
'student_data': this.student_data,
|
||||
'student_preference': this.student_preference,
|
||||
'interactions': this.interactions
|
||||
};
|
||||
delete this.jsonString;
|
||||
return result;
|
||||
};
|
||||
|
||||
get _version() { return this.#_version; }
|
||||
set _version(_version) { throwInvalidValueError(this.API); }
|
||||
|
||||
get _children() { return this.#_children; }
|
||||
set _children(_children) { throwInvalidValueError(this.API); }
|
||||
|
||||
get suspend_data() { return this.#suspend_data; }
|
||||
set suspend_data(suspend_data) {
|
||||
if(this.API.checkValidFormat(suspend_data, regex.CMIString4096)) {
|
||||
this.#suspend_data = suspend_data;
|
||||
}
|
||||
}
|
||||
|
||||
get launch_data() { return this.#launch_data; }
|
||||
set launch_data(launch_data) { this.API.isNotInitialized() ? this.#launch_data = launch_data : throwReadOnlyError(this.API); }
|
||||
|
||||
get comments() { return this.#comments; }
|
||||
set comments(comments) {
|
||||
if(this.API.checkValidFormat(comments, regex.CMIString4096)) {
|
||||
this.#comments = comments;
|
||||
}
|
||||
}
|
||||
|
||||
get comments_from_lms() { return this.#comments_from_lms; }
|
||||
set comments_from_lms(comments_from_lms) { this.API.isNotInitialized() ? this.#comments_from_lms = comments_from_lms : throwReadOnlyError(this.API); }
|
||||
}
|
||||
|
||||
class CMICore extends BaseCMI {
|
||||
constructor(API) {
|
||||
super(API);
|
||||
|
||||
this.score = new CMIScore(API, constants.score_children, regex.score_range);
|
||||
}
|
||||
|
||||
#_children = constants.core_children;
|
||||
#student_id = "";
|
||||
#student_name = "";
|
||||
#lesson_location = "";
|
||||
#credit = "";
|
||||
#lesson_status = "";
|
||||
#entry = "";
|
||||
#total_time = "";
|
||||
#lesson_mode = "normal";
|
||||
#exit = "";
|
||||
#session_time = "00:00:00";
|
||||
|
||||
get _children() { return this.#_children; }
|
||||
set _children(_children) { throwInvalidValueError(this.API); }
|
||||
|
||||
get student_id() { return this.#student_id; }
|
||||
set student_id(student_id) { this.API.isNotInitialized() ? this.#student_id = student_id : throwReadOnlyError(this.API); }
|
||||
|
||||
get student_name() { return this.#student_name; }
|
||||
set student_name(student_name) { this.API.isNotInitialized() ? this.#student_name = student_name : throwReadOnlyError(this.API); }
|
||||
|
||||
get lesson_location() { return this.#lesson_location; }
|
||||
set lesson_location(lesson_location) {
|
||||
if(this.API.checkValidFormat(lesson_location, regex.CMIString256)) {
|
||||
this.#lesson_location = lesson_location;
|
||||
}
|
||||
}
|
||||
|
||||
get credit() { return this.#credit; }
|
||||
set credit(credit) { this.API.isNotInitialized() ? this.#credit = credit : throwReadOnlyError(this.API); }
|
||||
|
||||
get lesson_status() { return this.#lesson_status; }
|
||||
set lesson_status(lesson_status) {
|
||||
if(this.API.checkValidFormat(lesson_status, regex.CMIStatus)) {
|
||||
this.#lesson_status = lesson_status;
|
||||
}
|
||||
}
|
||||
|
||||
get entry() { return this.#entry; }
|
||||
set entry(entry) { this.API.isNotInitialized() ? this.#entry = entry : throwReadOnlyError(this.API); }
|
||||
|
||||
get total_time() { return this.#total_time; }
|
||||
set total_time(total_time) { this.API.isNotInitialized() ? this.#total_time = total_time : throwReadOnlyError(this.API); }
|
||||
|
||||
get lesson_mode() { return this.#lesson_mode; }
|
||||
set lesson_mode(lesson_mode) { this.API.isNotInitialized() ? this.#lesson_mode = lesson_mode : throwReadOnlyError(this.API); }
|
||||
|
||||
get exit() { return (!this.jsonString) ? throwWriteOnlyError(this.API) : this.#exit; }
|
||||
set exit(exit) {
|
||||
if(this.API.checkValidFormat(exit, regex.CMIExit)) {
|
||||
this.#exit = exit;
|
||||
}
|
||||
}
|
||||
|
||||
get session_time() { return (!this.jsonString) ? throwWriteOnlyError(this.API) : this.#session_time; }
|
||||
set session_time(session_time) {
|
||||
if(this.API.checkValidFormat(session_time, regex.CMITimespan)) {
|
||||
this.#session_time = session_time;
|
||||
}
|
||||
}
|
||||
|
||||
toJSON = () => {
|
||||
this.jsonString = true;
|
||||
const result = {
|
||||
'student_id': this.student_id,
|
||||
'student_name': this.student_name,
|
||||
'lesson_location': this.lesson_location,
|
||||
'credit': this.credit,
|
||||
'lesson_status': this.lesson_status,
|
||||
'entry': this.entry,
|
||||
'total_time': this.total_time,
|
||||
'lesson_mode': this.lesson_mode,
|
||||
'exit': this.exit,
|
||||
'session_time': this.session_time,
|
||||
'score': this.score
|
||||
};
|
||||
delete this.jsonString;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class CMIObjectives extends CMIArray {
|
||||
constructor(API) {
|
||||
super({
|
||||
API: API,
|
||||
children: constants.objectives_children,
|
||||
errorCode: scorm12_error_codes.INVALID_SET_VALUE
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class CMIStudentData extends BaseCMI {
|
||||
#_children;
|
||||
#mastery_score = "";
|
||||
#max_time_allowed = "";
|
||||
#time_limit_action = "";
|
||||
|
||||
constructor(API, student_data_children) {
|
||||
super(API);
|
||||
|
||||
this.#_children = student_data_children? student_data_children : constants.student_data_children;
|
||||
}
|
||||
|
||||
get _children() { return this.#_children; }
|
||||
set _children(_children) { throwInvalidValueError(this.API); }
|
||||
|
||||
get mastery_score() { return this.#mastery_score; }
|
||||
set mastery_score(mastery_score) { this.API.isNotInitialized() ? this.#mastery_score = mastery_score : throwReadOnlyError(this.API); }
|
||||
|
||||
get max_time_allowed() { return this.#max_time_allowed; }
|
||||
set max_time_allowed(max_time_allowed) { this.API.isNotInitialized() ? this.#max_time_allowed = max_time_allowed : throwReadOnlyError(this.API); }
|
||||
|
||||
get time_limit_action() { return this.#time_limit_action; }
|
||||
set time_limit_action(time_limit_action) { this.API.isNotInitialized() ? this.#time_limit_action = time_limit_action : throwReadOnlyError(this.API); }
|
||||
|
||||
toJSON = () => {
|
||||
this.jsonString = true;
|
||||
const result = {
|
||||
'mastery_score': this.mastery_score,
|
||||
'max_time_allowed': this.max_time_allowed,
|
||||
'time_limit_action': this.time_limit_action
|
||||
};
|
||||
delete this.jsonString;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class CMIStudentPreference extends BaseCMI {
|
||||
constructor(API) {
|
||||
super(API);
|
||||
}
|
||||
|
||||
#_children = constants.student_preference_children;
|
||||
#audio = "";
|
||||
#language = "";
|
||||
#speed = "";
|
||||
#text = "";
|
||||
|
||||
get _children() { return this.#_children; }
|
||||
set _children(_children) { throwInvalidValueError(this.API); }
|
||||
|
||||
get audio() { return this.#audio; }
|
||||
set audio(audio) {
|
||||
if(this.API.checkValidFormat(audio, regex.CMISInteger)
|
||||
&& this.API.checkValidRange(audio, regex.audio_range)) {
|
||||
this.#audio = audio;
|
||||
}
|
||||
}
|
||||
|
||||
get language() { return this.#language; }
|
||||
set language(language) {
|
||||
if(this.API.checkValidFormat(language, regex.CMIString256)) {
|
||||
this.#language = language;
|
||||
}
|
||||
}
|
||||
|
||||
get speed() { return this.#speed; }
|
||||
set speed(speed) {
|
||||
if(this.API.checkValidFormat(speed, regex.CMISInteger)
|
||||
&& this.API.checkValidRange(speed, regex.speed_range)) {
|
||||
this.#speed = speed;
|
||||
}
|
||||
}
|
||||
|
||||
get text() { return this.#text; }
|
||||
set text(text) {
|
||||
if(this.API.checkValidFormat(text, regex.CMISInteger)
|
||||
&& this.API.checkValidRange(text, regex.text_range)) {
|
||||
this.#text = text;
|
||||
}
|
||||
}
|
||||
|
||||
toJSON = () => {
|
||||
this.jsonString = true;
|
||||
const result = {
|
||||
'audio': this.audio,
|
||||
'language': this.language,
|
||||
'speed': this.speed,
|
||||
'text': this.text
|
||||
};
|
||||
delete this.jsonString;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class CMIInteractions extends CMIArray {
|
||||
constructor(API) {
|
||||
super({
|
||||
API: API,
|
||||
children: constants.interactions_children,
|
||||
errorCode: scorm12_error_codes.INVALID_SET_VALUE
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class CMIInteractionsObject extends BaseCMI {
|
||||
constructor(API) {
|
||||
super(API);
|
||||
|
||||
this.objectives = new CMIArray({
|
||||
API: API,
|
||||
errorCode: 402,
|
||||
children: constants.objectives_children
|
||||
});
|
||||
this.correct_responses = new CMIArray({
|
||||
API: API,
|
||||
errorCode: 402,
|
||||
children: constants.correct_responses_children
|
||||
});
|
||||
}
|
||||
|
||||
#id: "";
|
||||
#time: "";
|
||||
#type: "";
|
||||
#weighting: "";
|
||||
#student_response: "";
|
||||
#result: "";
|
||||
#latency: "";
|
||||
|
||||
get id() { return (!this.jsonString) ? throwWriteOnlyError(this.API) : this.#id; }
|
||||
set id(id) {
|
||||
if(this.API.checkValidFormat(id, regex.CMIIdentifier)) {
|
||||
this.#id = id;
|
||||
}
|
||||
}
|
||||
|
||||
get time() { return (!this.jsonString) ? throwWriteOnlyError(this.API) : this.#time; }
|
||||
set time(time) {
|
||||
if(this.API.checkValidFormat(time, regex.CMITime)) {
|
||||
this.#time = time;
|
||||
}
|
||||
}
|
||||
|
||||
get type() { return (!this.jsonString) ? throwWriteOnlyError(this.API) : this.#type; }
|
||||
set type(type) {
|
||||
if(this.API.checkValidFormat(type, regex.CMIType)) {
|
||||
this.#type = type;
|
||||
}
|
||||
}
|
||||
|
||||
get weighting() { return (!this.jsonString) ? throwWriteOnlyError(this.API) : this.#weighting; }
|
||||
set weighting(weighting) {
|
||||
if(this.API.checkValidFormat(weighting, regex.CMIDecimal)
|
||||
&& this.API.checkValidRange(weighting, regex.weighting_range)) {
|
||||
this.#weighting = weighting;
|
||||
}
|
||||
}
|
||||
|
||||
get student_response() { return (!this.jsonString) ? throwWriteOnlyError(this.API) : this.#student_response; }
|
||||
set student_response(student_response) {
|
||||
if(this.API.checkValidFormat(student_response, regex.CMIFeedback)) {
|
||||
this.#student_response = student_response;
|
||||
}
|
||||
}
|
||||
|
||||
get result() { return (!this.jsonString) ? throwWriteOnlyError(this.API) : this.#result; }
|
||||
set result(result) {
|
||||
if(this.API.checkValidFormat(result, regex.CMIResult)) {
|
||||
this.#result = result;
|
||||
}
|
||||
}
|
||||
|
||||
get latency() { return (!this.jsonString) ? throwWriteOnlyError(this.API) : this.#latency; }
|
||||
set latency(latency) {
|
||||
if(this.API.checkValidFormat(latency, regex.CMITimespan)) {
|
||||
this.#latency = latency;
|
||||
}
|
||||
}
|
||||
|
||||
toJSON = () => {
|
||||
this.jsonString = true;
|
||||
const result = {
|
||||
'id': this.id,
|
||||
'time': this.time,
|
||||
'type': this.type,
|
||||
'weighting': this.weighting,
|
||||
'student_response': this.student_response,
|
||||
'result': this.result,
|
||||
'latency': this.latency,
|
||||
'objectives': this.objectives,
|
||||
'correct_responses': this.correct_responses
|
||||
};
|
||||
delete this.jsonString;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export class CMIObjectivesObject extends BaseCMI {
|
||||
constructor(API) {
|
||||
super(API);
|
||||
|
||||
this.score = new CMIScore(API);
|
||||
}
|
||||
|
||||
#id: "";
|
||||
#status: "";
|
||||
|
||||
get id() { return this.#id; }
|
||||
set id(id) {
|
||||
if(this.API.checkValidFormat(id, regex.CMIIdentifier)) {
|
||||
this.#id = id;
|
||||
}
|
||||
}
|
||||
|
||||
get status() { return this.#status; }
|
||||
set status(status) {
|
||||
if(this.API.checkValidFormat(status, regex.CMIStatus2)) {
|
||||
this.#status = status;
|
||||
}
|
||||
}
|
||||
|
||||
toJSON = () => {
|
||||
this.jsonString = true;
|
||||
const result = {
|
||||
'id': this.id,
|
||||
'status': this.status,
|
||||
'score': this.score
|
||||
};
|
||||
delete this.jsonString;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export class CMIInteractionsObjectivesObject extends BaseCMI {
|
||||
constructor(API) {
|
||||
super(API);
|
||||
}
|
||||
|
||||
#id: "";
|
||||
|
||||
get id() { return this.#id; }
|
||||
set id(id) {
|
||||
if(this.API.checkValidFormat(id, regex.CMIIdentifier)) {
|
||||
this.#id = id;
|
||||
}
|
||||
}
|
||||
|
||||
toJSON = () => {
|
||||
this.jsonString = true;
|
||||
const result = {
|
||||
'id': this.id,
|
||||
};
|
||||
delete this.jsonString;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export class CMIInteractionsCorrectResponsesObject extends BaseCMI {
|
||||
constructor(API) {
|
||||
super(API);
|
||||
}
|
||||
|
||||
#pattern: "";
|
||||
|
||||
get pattern() { return this.#pattern; }
|
||||
set pattern(pattern) {
|
||||
if(this.API.checkValidFormat(pattern, regex.CMIFeedback)) {
|
||||
this.#pattern = pattern;
|
||||
}
|
||||
}
|
||||
|
||||
toJSON = () => {
|
||||
this.jsonString = true;
|
||||
const result = {
|
||||
'pattern': this.pattern,
|
||||
};
|
||||
delete this.jsonString;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
522
src/cmi/scorm2004_cmi.js
Normal file
522
src/cmi/scorm2004_cmi.js
Normal file
@@ -0,0 +1,522 @@
|
||||
/*
|
||||
* Copyright (C) Noverant, Inc - All Rights Reserved Unauthorized copying of this file, via any
|
||||
* medium is strictly prohibited Proprietary and confidential
|
||||
*/
|
||||
|
||||
import {BaseCMI, CMIScore, CMIArray} from './common';
|
||||
import {scorm2004_constants, learner_responses} from "../constants";
|
||||
import {scorm2004_regex} from "../regex";
|
||||
|
||||
const constants = scorm2004_constants;
|
||||
const regex = scorm2004_regex;
|
||||
|
||||
export class CMI extends BaseCMI {
|
||||
constructor(API) {
|
||||
super(API);
|
||||
|
||||
this.learner_preference = new CMILearnerPreference(API);
|
||||
}
|
||||
|
||||
#_version: "1.0";
|
||||
#_children = constants.cmi_children;
|
||||
#completion_status: "unknown";
|
||||
#completion_threshold: "";
|
||||
#credit: "credit";
|
||||
#entry: "";
|
||||
#exit: "";
|
||||
#launch_data: "";
|
||||
#learner_id: "";
|
||||
#learner_name: "";
|
||||
#location: "";
|
||||
#max_time_allowed: "";
|
||||
#mode: "normal";
|
||||
#progress_measure: "";
|
||||
#scaled_passing_score: "";
|
||||
#session_time: "PT0H0M0S";
|
||||
#success_status: "unknown";
|
||||
#suspend_data: "";
|
||||
#time_limit_action: "continue,no message";
|
||||
#total_time: "0";
|
||||
|
||||
get _version() { return this.#_version; }
|
||||
set _version(_version) { this.throwReadOnlyError(); }
|
||||
|
||||
get _children() { return this.#_children; }
|
||||
set _children(_children) { this.throwReadOnlyError(); }
|
||||
|
||||
get completion_status() { return this.#completion_status; }
|
||||
set completion_status(completion_status) {
|
||||
if(this.API.checkValidFormat(completion_status, regex.CMICStatus)) {
|
||||
this.#completion_status = completion_status;
|
||||
}
|
||||
}
|
||||
|
||||
get completion_threshold() { return this.#completion_threshold; }
|
||||
set completion_threshold(completion_threshold) { this.API.isNotInitialized() ? this.#completion_threshold = completion_threshold : this.throwReadOnlyError(); }
|
||||
|
||||
get credit() { return this.#credit; }
|
||||
set credit(credit) { this.API.isNotInitialized() ? this.#credit = credit : this.throwReadOnlyError(); }
|
||||
|
||||
get entry() { return this.#entry; }
|
||||
set entry(entry) { this.API.isNotInitialized() ? this.#entry = entry : this.throwReadOnlyError(); }
|
||||
|
||||
get exit() { return (!this.jsonString) ? this.throwWriteOnlyError() : this.#exit; }
|
||||
set exit(exit) {
|
||||
if(this.API.checkValidFormat(exit, regex.CMIExit)) {
|
||||
this.#exit = exit;
|
||||
}
|
||||
}
|
||||
|
||||
get launch_data() { return this.#launch_data; }
|
||||
set launch_data(launch_data) { this.API.isNotInitialized() ? this.#launch_data = launch_data : this.throwReadOnlyError(); }
|
||||
|
||||
get learner_id() { return this.#learner_id; }
|
||||
set learner_id(learner_id) { this.API.isNotInitialized() ? this.#learner_id = learner_id : this.throwReadOnlyError(); }
|
||||
|
||||
get learner_name() { return this.#learner_name; }
|
||||
set learner_name(learner_name) { this.API.isNotInitialized() ? this.#learner_name = learner_name : this.throwReadOnlyError(); }
|
||||
|
||||
get location() { return this.#location; }
|
||||
set location(location) {
|
||||
if(this.API.checkValidFormat(location, regex.CMIString1000)) {
|
||||
this.#location = location;
|
||||
}
|
||||
}
|
||||
|
||||
get max_time_allowed() { return this.#max_time_allowed; }
|
||||
set max_time_allowed(max_time_allowed) { this.API.isNotInitialized() ? this.#max_time_allowed = max_time_allowed : this.throwReadOnlyError(); }
|
||||
|
||||
get mode() { return this.#mode; }
|
||||
set mode(mode) { this.API.isNotInitialized() ? this.#mode = mode : this.throwReadOnlyError(); }
|
||||
|
||||
get progress_measure() { return this.#progress_measure; }
|
||||
set progress_measure(progress_measure) {
|
||||
if(this.API.checkValidFormat(progress_measure, regex.CMIDecimal)
|
||||
&& this.API.checkValidRange(progress_measure, regex.progress_range)) {
|
||||
this.#progress_measure = progress_measure;
|
||||
}
|
||||
}
|
||||
|
||||
get scaled_passing_score() { return this.#scaled_passing_score; }
|
||||
set scaled_passing_score(scaled_passing_score) { this.API.isNotInitialized() ? this.#scaled_passing_score = scaled_passing_score : this.throwReadOnlyError(); }
|
||||
|
||||
get session_time() { return (!this.jsonString) ? this.API.throwSCORMError(405) : this.#session_time; }
|
||||
set session_time(session_time) {
|
||||
if(this.API.checkValidFormat(session_time, regex.CMITimespan)) {
|
||||
this.#session_time = session_time;
|
||||
}
|
||||
}
|
||||
|
||||
get success_status() { return this.#success_status; }
|
||||
set success_status(success_status) {
|
||||
if(this.API.checkValidFormat(success_status, regex.CMISStatus)) {
|
||||
this.#success_status = success_status;
|
||||
}
|
||||
}
|
||||
|
||||
get suspend_data() { return this.#suspend_data; }
|
||||
set suspend_data(suspend_data) {
|
||||
if(this.API.checkValidFormat(suspend_data, regex.CMIString64000)) {
|
||||
this.#suspend_data = suspend_data;
|
||||
}
|
||||
}
|
||||
|
||||
get time_limit_action() { return this.#time_limit_action; }
|
||||
set time_limit_action(time_limit_action) { this.API.isNotInitialized() ? this.#time_limit_action = time_limit_action : this.throwReadOnlyError(); }
|
||||
|
||||
get total_time() { return this.#total_time; }
|
||||
set total_time(total_time) { this.API.isNotInitialized() ? this.#total_time = total_time : this.throwReadOnlyError(); }
|
||||
|
||||
comments_from_learner = new class extends CMIArray {
|
||||
constructor(API) {
|
||||
super({
|
||||
API: API,
|
||||
children: constants.comments_children,
|
||||
errorCode: 404
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
comments_from_lms = new class extends CMIArray {
|
||||
constructor(API) {
|
||||
super({
|
||||
API: API,
|
||||
children: constants.comments_children,
|
||||
errorCode: 404
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
interactions = new class extends CMIArray {
|
||||
constructor(API) {
|
||||
super({
|
||||
API: API,
|
||||
children: constants.interactions_children,
|
||||
errorCode: 404
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
objectives = new class extends CMIArray {
|
||||
constructor(API) {
|
||||
super({
|
||||
API: API,
|
||||
children: constants.objectives_children,
|
||||
errorCode: 404
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class CMILearnerPreference extends BaseCMI {
|
||||
constructor(API) {
|
||||
super(API);
|
||||
}
|
||||
|
||||
#_children: constants.student_preference_children;
|
||||
#audio_level: "1";
|
||||
#language: "";
|
||||
#delivery_speed: "1";
|
||||
#audio_captioning: "0";
|
||||
|
||||
get _children() { return this.#_children; }
|
||||
set _children(_children) { this.throwReadOnlyError(); }
|
||||
|
||||
get audio_level() { return this.#audio_level; }
|
||||
set audio_level(audio_level) {
|
||||
if(this.API.checkValidFormat(audio_level, regex.CMIDecimal)
|
||||
&& this.API.checkValidRange(audio_level, regex.audio_range)) {
|
||||
this.#audio_level = audio_level;
|
||||
}
|
||||
}
|
||||
|
||||
get language() { return this.#language; }
|
||||
set language(language) {
|
||||
if(this.API.checkValidFormat(language, regex.CMILang)) {
|
||||
this.#language = language;
|
||||
}
|
||||
}
|
||||
|
||||
get delivery_speed() { return this.#delivery_speed; }
|
||||
set delivery_speed(delivery_speed) {
|
||||
if(this.API.checkValidFormat(delivery_speed, regex.CMIDecimal)
|
||||
&& this.API.checkValidRange(delivery_speed, regex.speed_range)) {
|
||||
this.#delivery_speed = delivery_speed;
|
||||
}
|
||||
}
|
||||
|
||||
get audio_captioning() { return this.#audio_captioning; }
|
||||
set audio_captioning(audio_captioning) {
|
||||
if(this.API.checkValidFormat(audio_captioning, regex.CMISInteger)
|
||||
&& this.API.checkValidRange(audio_captioning, regex.text_range)) {
|
||||
this.#audio_captioning = audio_captioning;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class CMIInteractionsObject extends BaseCMI {
|
||||
constructor(API) {
|
||||
super(API);
|
||||
|
||||
this.objectives = new CMIArray({
|
||||
API: API,
|
||||
errorCode: 404,
|
||||
children: constants.objectives_children
|
||||
});
|
||||
this.correct_responses = new CMIArray({
|
||||
API: API,
|
||||
errorCode: 404,
|
||||
children: constants.correct_responses_children
|
||||
});
|
||||
}
|
||||
|
||||
#id: "";
|
||||
#type: "";
|
||||
#timestamp: "";
|
||||
#weighting: "";
|
||||
#learner_response: "";
|
||||
#result: "";
|
||||
#latency: "";
|
||||
#description: "";
|
||||
|
||||
get id() { return this.#id; }
|
||||
set id(id) {
|
||||
if(this.API.checkValidFormat(id, regex.CMILongIdentifier)) {
|
||||
this.#id = id;
|
||||
}
|
||||
}
|
||||
|
||||
get type() { return this.#type; }
|
||||
set type(type) {
|
||||
if(this.API.checkValidFormat(type, regex.CMIType)) {
|
||||
this.#type = type;
|
||||
}
|
||||
}
|
||||
|
||||
get timestamp() { return this.#timestamp; }
|
||||
set timestamp(timestamp) {
|
||||
if(this.API.checkValidFormat(timestamp, regex.CMITime)) {
|
||||
this.#timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
get weighting() { return this.#weighting; }
|
||||
set weighting(weighting) {
|
||||
if (this.API.checkValidFormat(weighting, regex.CMIDecimal)) {
|
||||
this.#weighting = weighting;
|
||||
}
|
||||
}
|
||||
|
||||
get learner_response() { return this.#learner_response; }
|
||||
set learner_response(learner_response) {
|
||||
if(typeof this.type === 'undefined') {
|
||||
this.API.throwSCORMError(this.API.error.DEPENDENCY_NOT_ESTABLISHED);
|
||||
} else {
|
||||
let nodes = [];
|
||||
let response_type = learner_responses[this.type];
|
||||
if(response_type.delimiter !== '') {
|
||||
nodes = learner_response.split(response_type.delimiter);
|
||||
} else {
|
||||
nodes[0] = learner_response;
|
||||
}
|
||||
|
||||
if((nodes.length > 0) && (nodes.length <= response_type.max)) {
|
||||
const formatRegex = new RegExp(response_type.format);
|
||||
for(let i = 0; (i < nodes.length) && (this.API.lastErrorCode === 0); i++) {
|
||||
if(typeof response_type.delimiter2 !== 'undefined') {
|
||||
let values = nodes[i].split(response_type.delimiter2);
|
||||
if(values.length === 2) {
|
||||
if(!values[0].match(formatRegex)) {
|
||||
this.API.throwSCORMError(this.API.error.TYPE_MISMATCH);
|
||||
} else {
|
||||
if(!values[1].match(new RegExp(response_type.format2))) {
|
||||
this.API.throwSCORMError(this.API.error.TYPE_MISMATCH);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.API.throwSCORMError(this.API.error.TYPE_MISMATCH);
|
||||
}
|
||||
} else {
|
||||
if(!nodes[i].match(formatRegex)) {
|
||||
this.API.throwSCORMError(this.API.error.TYPE_MISMATCH);
|
||||
} else {
|
||||
if(nodes[i] !== '' && response_type.unique) {
|
||||
for(let j = 0; (j < i) && this.API.lastErrorCode === 0; j++) {
|
||||
if(nodes[i] === nodes[j]) {
|
||||
this.API.throwSCORMError(this.API.error.TYPE_MISMATCH);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.API.throwSCORMError(this.API.error.GENERAL_SET_FAILURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get result() { return this.#result; }
|
||||
set result(result) {
|
||||
if(this.API.checkValidFormat(result, regex.CMIResult)) {
|
||||
this.#result = result;
|
||||
}
|
||||
}
|
||||
|
||||
get latency() { return this.#latency; }
|
||||
set latency(latency) {
|
||||
if(this.API.checkValidFormat(latency, regex.CMITimespan)) {
|
||||
this.#latency = latency;
|
||||
}
|
||||
}
|
||||
|
||||
get description() { return this.#description; }
|
||||
set description(description) {
|
||||
if(this.API.checkValidFormat(description, regex.CMILangString250)) {
|
||||
this.#description = description;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class CMIObjectivesObject extends BaseCMI {
|
||||
constructor(API) {
|
||||
super(API);
|
||||
}
|
||||
|
||||
#id: "";
|
||||
#success_status: "unknown"; // Allowed values: "passed", "failed", "unknown"
|
||||
#completion_status: "unknown"; // Allowed values: "completed", "incomplete", "not attempted", "unknown"
|
||||
#progress_measure: ""; // Data type: real (10,7). Range: 0.0 to 1.0
|
||||
#description: ""; // SPM 250 characters
|
||||
|
||||
get id() { return this.#id; }
|
||||
set id(id) {
|
||||
if(this.API.checkValidFormat(id, regex.CMILongIdentifier)) {
|
||||
this.#id = id;
|
||||
}
|
||||
}
|
||||
|
||||
get success_status() { return this.#success_status; }
|
||||
set success_status(success_status) {
|
||||
if(this.API.checkValidFormat(success_status, regex.CMISStatus)) {
|
||||
this.#success_status = success_status;
|
||||
}
|
||||
}
|
||||
|
||||
get completion_status() { return this.#completion_status; }
|
||||
set completion_status(completion_status) {
|
||||
if(this.API.checkValidFormat(completion_status, regex.CMICStatus)) {
|
||||
this.#completion_status = completion_status;
|
||||
}
|
||||
}
|
||||
|
||||
get progress_measure() { return this.#progress_measure; }
|
||||
set progress_measure(progress_measure) {
|
||||
if(this.API.checkValidFormat(progress_measure, regex.CMIDecimal)
|
||||
&& this.API.checkValidRange(progress_measure, regex.progress_range)) {
|
||||
this.#progress_measure = progress_measure;
|
||||
}
|
||||
}
|
||||
|
||||
get description() { return this.#description; }
|
||||
set description(description) {
|
||||
if(this.API.checkValidFormat(description, regex.CMILangString250)) {
|
||||
this.#description = description;
|
||||
}
|
||||
}
|
||||
|
||||
score = new Scorm2004CMIScore(this.API);
|
||||
}
|
||||
|
||||
class Scorm2004CMIScore extends CMIScore {
|
||||
constructor(API) {
|
||||
super(API, constants.score_children);
|
||||
this.max = "";
|
||||
}
|
||||
|
||||
#scaled: "";
|
||||
|
||||
get scaled() { return this.#scaled; }
|
||||
set scaled(scaled) {
|
||||
if(this.API.checkValidFormat(scaled, regex.CMIDecimal)
|
||||
&& this.API.checkValidRange(scaled, regex.scaled_range)) {
|
||||
this.#scaled = scaled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class CMICommentsFromLearnerObject extends BaseCMI {
|
||||
constructor(API) {
|
||||
super(API);
|
||||
}
|
||||
#comment = "";
|
||||
#location = "";
|
||||
#timestamp = "";
|
||||
|
||||
get comment() { return this.#comment; }
|
||||
set comment(comment) {
|
||||
if(this.API.checkValidFormat(comment, regex.CMILangString4000)) {
|
||||
this.#comment = comment;
|
||||
}
|
||||
}
|
||||
|
||||
get location() { return this.#location; }
|
||||
set location(location) {
|
||||
if(this.API.checkValidFormat(location, regex.CMIString250)) {
|
||||
this.#location = location;
|
||||
}
|
||||
}
|
||||
|
||||
get timestamp() { return this.#timestamp; }
|
||||
set timestamp(timestamp) {
|
||||
if(this.API.checkValidFormat(timestamp, regex.CMITime)) {
|
||||
this.#timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class CMICommentsFromLMSObject extends CMICommentsFromLearnerObject {
|
||||
constructor(API) {
|
||||
super(API);
|
||||
}
|
||||
|
||||
set comment(comment) { this.API.isNotInitialized() ? this.comment = comment : this.throwReadOnlyError(); }
|
||||
|
||||
set location(location) { this.API.isNotInitialized() ? this.location = location : this.throwReadOnlyError(); }
|
||||
|
||||
set timestamp(timestamp) { this.API.isNotInitialized() ? this.timestamp = timestamp : this.throwReadOnlyError(); }
|
||||
}
|
||||
|
||||
export class CMIInteractionsObjectivesObject extends BaseCMI {
|
||||
constructor(API) {
|
||||
super(API);
|
||||
}
|
||||
|
||||
#id: "";
|
||||
|
||||
get id() { return this.#id; }
|
||||
set id(id) {
|
||||
if(this.API.checkValidFormat(id, regex.CMILongIdentifier)) {
|
||||
this.#id = id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class CMIInteractionsCorrectResponsesObject extends BaseCMI {
|
||||
constructor(API) {
|
||||
super(API);
|
||||
}
|
||||
|
||||
#pattern: "";
|
||||
|
||||
get pattern() { return this.#pattern; }
|
||||
set pattern(pattern) {
|
||||
if(this.API.checkValidFormat(pattern, regex.CMIFeedback)) {
|
||||
this.#pattern = pattern;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ADL extends BaseCMI {
|
||||
constructor(API) {
|
||||
super(API);
|
||||
}
|
||||
|
||||
nav = new class extends BaseCMI {
|
||||
constructor(API) {
|
||||
super(API);
|
||||
}
|
||||
|
||||
#request = "_none_"; // Allowed values: "continue", "previous", "choice", "jump", "exit", "exitAll", "abandon", "abandonAll", "_none_"
|
||||
|
||||
get request() { return this.#request; }
|
||||
set request(request) {
|
||||
if(this.API.checkValidFormat(request, regex.NAVEvent)) {
|
||||
this.#request = request;
|
||||
}
|
||||
}
|
||||
|
||||
request_valid = new class extends BaseCMI {
|
||||
constructor(API) {
|
||||
super(API);
|
||||
}
|
||||
|
||||
#continue = "unknown"; // Allowed values: "true", "false", "unknown"
|
||||
#previous = "unknown"; // Allowed values: "true", "false", "unknown"
|
||||
|
||||
get continue() { return this.#continue; }
|
||||
set continue(_) { API.throwSCORMError(404); }
|
||||
|
||||
get previous() { return this.#previous; }
|
||||
set previous(_) { API.throwSCORMError(404); }
|
||||
|
||||
choice = class {
|
||||
_isTargetValid = (_target) => "unknown";
|
||||
};
|
||||
|
||||
jump = class {
|
||||
_isTargetValid = (_target) => "unknown";
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
814
src/constants.js
Normal file
814
src/constants.js
Normal file
@@ -0,0 +1,814 @@
|
||||
/*
|
||||
* Copyright (C) Noverant, Inc - All Rights Reserved Unauthorized copying of this file, via any
|
||||
* medium is strictly prohibited Proprietary and confidential
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
|
||||
import {scorm2004_regex} from "./regex";
|
||||
|
||||
export const base_error_codes = {
|
||||
GENERAL: 101,
|
||||
INITIALIZATION_FAILED: 101,
|
||||
INITIALIZED: 101,
|
||||
TERMINATED: 101,
|
||||
TERMINATION_FAILURE: 101,
|
||||
TERMINATION_BEFORE_INIT: 101,
|
||||
MULTIPLE_TERMINATION: 101,
|
||||
RETRIEVE_BEFORE_INIT: 101,
|
||||
RETRIEVE_AFTER_TERM: 101,
|
||||
STORE_BEFORE_INIT: 101,
|
||||
STORE_AFTER_TERM: 101,
|
||||
COMMIT_BEFORE_INIT: 101,
|
||||
COMMIT_AFTER_TERM: 101,
|
||||
ARGUMENT_ERROR: 101,
|
||||
CHILDREN_ERROR: 101,
|
||||
COUNT_ERROR: 101,
|
||||
GENERAL_GET_FAILURE: 101,
|
||||
GENERAL_SET_FAILURE: 101,
|
||||
GENERAL_COMMIT_FAILURE: 101,
|
||||
UNDEFINED_DATA_MODEL: 101,
|
||||
UNIMPLEMENTED_ELEMENT: 101,
|
||||
VALUE_NOT_INITIALIZED: 101,
|
||||
INVALID_SET_VALUE: 101,
|
||||
READ_ONLY_ELEMENT: 101,
|
||||
WRITE_ONLY_ELEMENT: 101,
|
||||
TYPE_MISMATCH: 101,
|
||||
VALUE_OUT_OF_RANGE: 101,
|
||||
DEPENDENCY_NOT_ESTABLISHED: 101
|
||||
};
|
||||
|
||||
export const scorm12_error_codes = {
|
||||
...base_error_codes, ...{
|
||||
RETRIEVE_BEFORE_INIT: 301,
|
||||
STORE_BEFORE_INIT: 301,
|
||||
COMMIT_BEFORE_INIT: 301,
|
||||
ARGUMENT_ERROR: 201,
|
||||
CHILDREN_ERROR: 202,
|
||||
COUNT_ERROR: 203,
|
||||
UNDEFINED_DATA_MODEL: 401,
|
||||
UNIMPLEMENTED_ELEMENT: 401,
|
||||
VALUE_NOT_INITIALIZED: 301,
|
||||
INVALID_SET_VALUE: 402,
|
||||
READ_ONLY_ELEMENT: 403,
|
||||
WRITE_ONLY_ELEMENT: 404,
|
||||
TYPE_MISMATCH: 405,
|
||||
VALUE_OUT_OF_RANGE: 407,
|
||||
DEPENDENCY_NOT_ESTABLISHED: 408
|
||||
}
|
||||
};
|
||||
|
||||
export const scorm2004_error_codes = {
|
||||
...base_error_codes, ...{
|
||||
INITIALIZATION_FAILED: 102,
|
||||
INITIALIZED: 103,
|
||||
TERMINATED: 104,
|
||||
TERMINATION_FAILURE: 111,
|
||||
TERMINATION_BEFORE_INIT: 112,
|
||||
MULTIPLE_TERMINATIONS: 113,
|
||||
RETRIEVE_BEFORE_INIT: 122,
|
||||
RETRIEVE_AFTER_TERM: 123,
|
||||
STORE_BEFORE_INIT: 132,
|
||||
STORE_AFTER_TERM: 133,
|
||||
COMMIT_BEFORE_INIT: 142,
|
||||
COMMIT_AFTER_TERM: 143,
|
||||
ARGUMENT_ERROR: 201,
|
||||
GENERAL_GET_FAILURE: 301,
|
||||
GENERAL_SET_FAILURE: 351,
|
||||
GENERAL_COMMIT_FAILURE: 391,
|
||||
UNDEFINED_DATA_MODEL: 401,
|
||||
UNIMPLEMENTED_ELEMENT: 402,
|
||||
VALUE_NOT_INITIALIZED: 403,
|
||||
READ_ONLY_ELEMENT: 404,
|
||||
WRITE_ONLY_ELEMENT: 405,
|
||||
TYPE_MISMATCH: 406,
|
||||
VALUE_OUT_OF_RANGE: 407,
|
||||
DEPENDENCY_NOT_ESTABLISHED: 408
|
||||
}
|
||||
};
|
||||
|
||||
export const scorm12_constants = {
|
||||
// Children lists
|
||||
cmi_children: 'core,suspend_data,launch_data,comments,objectives,student_data,student_preference,interactions',
|
||||
core_children: 'student_id,student_name,lesson_location,credit,lesson_status,entry,score,total_time,lesson_mode,exit,session_time',
|
||||
score_children: 'raw,min,max',
|
||||
comments_children: 'content,location,time',
|
||||
objectives_children: 'id,score,status',
|
||||
correct_responses_children: 'pattern',
|
||||
student_data_children: 'mastery_score,max_time_allowed,time_limit_action',
|
||||
student_preference_children: 'audio,language,speed,text',
|
||||
interactions_children: 'id,objectives,time,type,correct_responses,weighting,student_response,result,latency',
|
||||
|
||||
error_descriptions: {
|
||||
"101": {
|
||||
basicMessage: "General Exception",
|
||||
detailMessage: "No specific error code exists to describe the error. Use LMSGetDiagnostic for more information",
|
||||
},
|
||||
"201": {
|
||||
basicMessage: "Invalid argument error",
|
||||
detailMessage: "Indicates that an argument represents an invalid data model element or is otherwise incorrect.",
|
||||
},
|
||||
"202": {
|
||||
basicMessage: "Element cannot have children",
|
||||
detailMessage: "Indicates that LMSGetValue was called with a data model element name that ends in \"_children\" for a data model element that does not support the \"_children\" suffix.",
|
||||
},
|
||||
"203": {
|
||||
basicMessage: "Element not an array - cannot have count",
|
||||
detailMessage: "Indicates that LMSGetValue was called with a data model element name that ends in \"_count\" for a data model element that does not support the \"_count\" suffix.",
|
||||
},
|
||||
"301": {
|
||||
basicMessage: "Not initialized",
|
||||
detailMessage: "Indicates that an API call was made before the call to LMSInitialize.",
|
||||
},
|
||||
"401": {
|
||||
basicMessage: "Not implemented error",
|
||||
detailMessage: "The data model element indicated in a call to LMSGetValue or LMSSetValue is valid, but was not implemented by this LMS. SCORM 1.2 defines a set of data model elements as being optional for an LMS to implement.",
|
||||
},
|
||||
"402": {
|
||||
basicMessage: "Invalid set value, element is a keyword",
|
||||
detailMessage: "Indicates that LMSSetValue was called on a data model element that represents a keyword (elements that end in \"_children\" and \"_count\").",
|
||||
},
|
||||
"403": {
|
||||
basicMessage: "Element is read only",
|
||||
detailMessage: "LMSSetValue was called with a data model element that can only be read.",
|
||||
},
|
||||
"404": {
|
||||
basicMessage: "Element is write only",
|
||||
detailMessage: "LMSGetValue was called on a data model element that can only be written to.",
|
||||
},
|
||||
"405": {
|
||||
basicMessage: "Incorrect Data Type",
|
||||
detailMessage: "LMSSetValue was called with a value that is not consistent with the data format of the supplied data model element.",
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const aicc_constants = {
|
||||
...scorm12_constants, ...{
|
||||
cmi_children: 'core,suspend_data,launch_data,comments,objectives,student_data,student_preference,interactions,evaluation',
|
||||
student_data_children: 'attempt_number,tries,mastery_score,max_time_allowed,time_limit_action',
|
||||
tries_children: "time,status,score"
|
||||
}
|
||||
};
|
||||
|
||||
export const scorm2004_constants = {
|
||||
// Children lists
|
||||
cmi_children: '_version,comments_from_learner,comments_from_lms,completion_status,credit,entry,exit,interactions,launch_data,learner_id,learner_name,learner_preference,location,max_time_allowed,mode,objectives,progress_measure,scaled_passing_score,score,session_time,success_status,suspend_data,time_limit_action,total_time',
|
||||
comments_children: 'comment,timestamp,location',
|
||||
score_children: 'max,raw,scaled,min',
|
||||
objectives_children: 'progress_measure,completion_status,success_status,description,score,id',
|
||||
correct_responses_children: 'pattern',
|
||||
student_data_children: 'mastery_score,max_time_allowed,time_limit_action',
|
||||
student_preference_children: 'audio_level,audio_captioning,delivery_speed,language',
|
||||
interactions_children: 'id,type,objectives,timestamp,correct_responses,weighting,learner_response,result,latency,description',
|
||||
|
||||
error_descriptions: {
|
||||
"0": {
|
||||
basicMessage: "No Error",
|
||||
detailMessage: "No error occurred, the previous API call was successful.",
|
||||
},
|
||||
"101": {
|
||||
basicMessage: "General Exception",
|
||||
detailMessage: "No specific error code exists to describe the error. Use GetDiagnostic for more information.",
|
||||
},
|
||||
"102": {
|
||||
basicMessage: "General Initialization Failure",
|
||||
detailMessage: "Call to Initialize failed for an unknown reason.",
|
||||
},
|
||||
"103": {
|
||||
basicMessage: "Already Initialized",
|
||||
detailMessage: "Call to Initialize failed because Initialize was already called.",
|
||||
},
|
||||
"104": {
|
||||
basicMessage: "Content Instance Terminated",
|
||||
detailMessage: "Call to Initialize failed because Terminate was already called.",
|
||||
},
|
||||
"111": {
|
||||
basicMessage: "General Termination Failure",
|
||||
detailMessage: "Call to Terminate failed for an unknown reason.",
|
||||
},
|
||||
"112": {
|
||||
basicMessage: "Termination Before Initialization",
|
||||
detailMessage: "Call to Terminate failed because it was made before the call to Initialize.",
|
||||
},
|
||||
"113": {
|
||||
basicMessage: "Termination After Termination",
|
||||
detailMessage: "Call to Terminate failed because Terminate was already called.",
|
||||
},
|
||||
"122": {
|
||||
basicMessage: "Retrieve Data Before Initialization",
|
||||
detailMessage: "Call to GetValue failed because it was made before the call to Initialize.",
|
||||
},
|
||||
"123": {
|
||||
basicMessage: "Retrieve Data After Termination",
|
||||
detailMessage: "Call to GetValue failed because it was made after the call to Terminate.",
|
||||
},
|
||||
"132": {
|
||||
basicMessage: "Store Data Before Initialization",
|
||||
detailMessage: "Call to SetValue failed because it was made before the call to Initialize.",
|
||||
},
|
||||
"133": {
|
||||
basicMessage: "Store Data After Termination",
|
||||
detailMessage: "Call to SetValue failed because it was made after the call to Terminate.",
|
||||
},
|
||||
"142": {
|
||||
basicMessage: "Commit Before Initialization",
|
||||
detailMessage: "Call to Commit failed because it was made before the call to Initialize.",
|
||||
},
|
||||
"143": {
|
||||
basicMessage: "Commit After Termination",
|
||||
detailMessage: "Call to Commit failed because it was made after the call to Terminate.",
|
||||
},
|
||||
"201": {
|
||||
basicMessage: "General Argument Error",
|
||||
detailMessage: "An invalid argument was passed to an API method (usually indicates that Initialize, Commit or Terminate did not receive the expected empty string argument.",
|
||||
},
|
||||
"301": {
|
||||
basicMessage: "General Get Failure",
|
||||
detailMessage: "Indicates a failed GetValue call where no other specific error code is applicable. Use GetDiagnostic for more information.",
|
||||
},
|
||||
"351": {
|
||||
basicMessage: "General Set Failure",
|
||||
detailMessage: "Indicates a failed SetValue call where no other specific error code is applicable. Use GetDiagnostic for more information.",
|
||||
},
|
||||
"391": {
|
||||
basicMessage: "General Commit Failure",
|
||||
detailMessage: "Indicates a failed Commit call where no other specific error code is applicable. Use GetDiagnostic for more information.",
|
||||
},
|
||||
"401": {
|
||||
basicMessage: "Undefined Data Model Element",
|
||||
detailMessage: "The data model element name passed to GetValue or SetValue is not a valid SCORM data model element.",
|
||||
},
|
||||
"402": {
|
||||
basicMessage: "Unimplemented Data Model Element",
|
||||
detailMessage: "The data model element indicated in a call to GetValue or SetValue is valid, but was not implemented by this LMS. In SCORM 2004, this error would indicate an LMS that is not fully SCORM conformant.",
|
||||
},
|
||||
"403": {
|
||||
basicMessage: "Data Model Element Value Not Initialized",
|
||||
detailMessage: "Attempt to read a data model element that has not been initialized by the LMS or through a SetValue call. This error condition is often reached during normal execution of a SCO.",
|
||||
},
|
||||
"404": {
|
||||
basicMessage: "Data Model Element Is Read Only",
|
||||
detailMessage: "SetValue was called with a data model element that can only be read.",
|
||||
},
|
||||
"405": {
|
||||
basicMessage: "Data Model Element Is Write Only",
|
||||
detailMessage: "GetValue was called on a data model element that can only be written to.",
|
||||
},
|
||||
"406": {
|
||||
basicMessage: "Data Model Element Type Mismatch",
|
||||
detailMessage: "SetValue was called with a value that is not consistent with the data format of the supplied data model element.",
|
||||
},
|
||||
"407": {
|
||||
basicMessage: "Data Model Element Value Out Of Range",
|
||||
detailMessage: "The numeric value supplied to a SetValue call is outside of the numeric range allowed for the supplied data model element.",
|
||||
},
|
||||
"408": {
|
||||
basicMessage: "Data Model Dependency Not Established",
|
||||
detailMessage: "Some data model elements cannot be set until another data model element was set. This error condition indicates that the prerequisite element was not set before the dependent element.",
|
||||
}
|
||||
},
|
||||
|
||||
valid_languages: {
|
||||
'aa': 'aa',
|
||||
'ab': 'ab',
|
||||
'ae': 'ae',
|
||||
'af': 'af',
|
||||
'ak': 'ak',
|
||||
'am': 'am',
|
||||
'an': 'an',
|
||||
'ar': 'ar',
|
||||
'as': 'as',
|
||||
'av': 'av',
|
||||
'ay': 'ay',
|
||||
'az': 'az',
|
||||
'ba': 'ba',
|
||||
'be': 'be',
|
||||
'bg': 'bg',
|
||||
'bh': 'bh',
|
||||
'bi': 'bi',
|
||||
'bm': 'bm',
|
||||
'bn': 'bn',
|
||||
'bo': 'bo',
|
||||
'br': 'br',
|
||||
'bs': 'bs',
|
||||
'ca': 'ca',
|
||||
'ce': 'ce',
|
||||
'ch': 'ch',
|
||||
'co': 'co',
|
||||
'cr': 'cr',
|
||||
'cs': 'cs',
|
||||
'cu': 'cu',
|
||||
'cv': 'cv',
|
||||
'cy': 'cy',
|
||||
'da': 'da',
|
||||
'de': 'de',
|
||||
'dv': 'dv',
|
||||
'dz': 'dz',
|
||||
'ee': 'ee',
|
||||
'el': 'el',
|
||||
'en': 'en',
|
||||
'eo': 'eo',
|
||||
'es': 'es',
|
||||
'et': 'et',
|
||||
'eu': 'eu',
|
||||
'fa': 'fa',
|
||||
'ff': 'ff',
|
||||
'fi': 'fi',
|
||||
'fj': 'fj',
|
||||
'fo': 'fo',
|
||||
'fr': 'fr',
|
||||
'fy': 'fy',
|
||||
'ga': 'ga',
|
||||
'gd': 'gd',
|
||||
'gl': 'gl',
|
||||
'gn': 'gn',
|
||||
'gu': 'gu',
|
||||
'gv': 'gv',
|
||||
'ha': 'ha',
|
||||
'he': 'he',
|
||||
'hi': 'hi',
|
||||
'ho': 'ho',
|
||||
'hr': 'hr',
|
||||
'ht': 'ht',
|
||||
'hu': 'hu',
|
||||
'hy': 'hy',
|
||||
'hz': 'hz',
|
||||
'ia': 'ia',
|
||||
'id': 'id',
|
||||
'ie': 'ie',
|
||||
'ig': 'ig',
|
||||
'ii': 'ii',
|
||||
'ik': 'ik',
|
||||
'io': 'io',
|
||||
'is': 'is',
|
||||
'it': 'it',
|
||||
'iu': 'iu',
|
||||
'ja': 'ja',
|
||||
'jv': 'jv',
|
||||
'ka': 'ka',
|
||||
'kg': 'kg',
|
||||
'ki': 'ki',
|
||||
'kj': 'kj',
|
||||
'kk': 'kk',
|
||||
'kl': 'kl',
|
||||
'km': 'km',
|
||||
'kn': 'kn',
|
||||
'ko': 'ko',
|
||||
'kr': 'kr',
|
||||
'ks': 'ks',
|
||||
'ku': 'ku',
|
||||
'kv': 'kv',
|
||||
'kw': 'kw',
|
||||
'ky': 'ky',
|
||||
'la': 'la',
|
||||
'lb': 'lb',
|
||||
'lg': 'lg',
|
||||
'li': 'li',
|
||||
'ln': 'ln',
|
||||
'lo': 'lo',
|
||||
'lt': 'lt',
|
||||
'lu': 'lu',
|
||||
'lv': 'lv',
|
||||
'mg': 'mg',
|
||||
'mh': 'mh',
|
||||
'mi': 'mi',
|
||||
'mk': 'mk',
|
||||
'ml': 'ml',
|
||||
'mn': 'mn',
|
||||
'mo': 'mo',
|
||||
'mr': 'mr',
|
||||
'ms': 'ms',
|
||||
'mt': 'mt',
|
||||
'my': 'my',
|
||||
'na': 'na',
|
||||
'nb': 'nb',
|
||||
'nd': 'nd',
|
||||
'ne': 'ne',
|
||||
'ng': 'ng',
|
||||
'nl': 'nl',
|
||||
'nn': 'nn',
|
||||
'no': 'no',
|
||||
'nr': 'nr',
|
||||
'nv': 'nv',
|
||||
'ny': 'ny',
|
||||
'oc': 'oc',
|
||||
'oj': 'oj',
|
||||
'om': 'om',
|
||||
'or': 'or',
|
||||
'os': 'os',
|
||||
'pa': 'pa',
|
||||
'pi': 'pi',
|
||||
'pl': 'pl',
|
||||
'ps': 'ps',
|
||||
'pt': 'pt',
|
||||
'qu': 'qu',
|
||||
'rm': 'rm',
|
||||
'rn': 'rn',
|
||||
'ro': 'ro',
|
||||
'ru': 'ru',
|
||||
'rw': 'rw',
|
||||
'sa': 'sa',
|
||||
'sc': 'sc',
|
||||
'sd': 'sd',
|
||||
'se': 'se',
|
||||
'sg': 'sg',
|
||||
'sh': 'sh',
|
||||
'si': 'si',
|
||||
'sk': 'sk',
|
||||
'sl': 'sl',
|
||||
'sm': 'sm',
|
||||
'sn': 'sn',
|
||||
'so': 'so',
|
||||
'sq': 'sq',
|
||||
'sr': 'sr',
|
||||
'ss': 'ss',
|
||||
'st': 'st',
|
||||
'su': 'su',
|
||||
'sv': 'sv',
|
||||
'sw': 'sw',
|
||||
'ta': 'ta',
|
||||
'te': 'te',
|
||||
'tg': 'tg',
|
||||
'th': 'th',
|
||||
'ti': 'ti',
|
||||
'tk': 'tk',
|
||||
'tl': 'tl',
|
||||
'tn': 'tn',
|
||||
'to': 'to',
|
||||
'tr': 'tr',
|
||||
'ts': 'ts',
|
||||
'tt': 'tt',
|
||||
'tw': 'tw',
|
||||
'ty': 'ty',
|
||||
'ug': 'ug',
|
||||
'uk': 'uk',
|
||||
'ur': 'ur',
|
||||
'uz': 'uz',
|
||||
've': 've',
|
||||
'vi': 'vi',
|
||||
'vo': 'vo',
|
||||
'wa': 'wa',
|
||||
'wo': 'wo',
|
||||
'xh': 'xh',
|
||||
'yi': 'yi',
|
||||
'yo': 'yo',
|
||||
'za': 'za',
|
||||
'zh': 'zh',
|
||||
'zu': 'zu',
|
||||
'aar': 'aar',
|
||||
'abk': 'abk',
|
||||
'ave': 'ave',
|
||||
'afr': 'afr',
|
||||
'aka': 'aka',
|
||||
'amh': 'amh',
|
||||
'arg': 'arg',
|
||||
'ara': 'ara',
|
||||
'asm': 'asm',
|
||||
'ava': 'ava',
|
||||
'aym': 'aym',
|
||||
'aze': 'aze',
|
||||
'bak': 'bak',
|
||||
'bel': 'bel',
|
||||
'bul': 'bul',
|
||||
'bih': 'bih',
|
||||
'bis': 'bis',
|
||||
'bam': 'bam',
|
||||
'ben': 'ben',
|
||||
'tib': 'tib',
|
||||
'bod': 'bod',
|
||||
'bre': 'bre',
|
||||
'bos': 'bos',
|
||||
'cat': 'cat',
|
||||
'che': 'che',
|
||||
'cha': 'cha',
|
||||
'cos': 'cos',
|
||||
'cre': 'cre',
|
||||
'cze': 'cze',
|
||||
'ces': 'ces',
|
||||
'chu': 'chu',
|
||||
'chv': 'chv',
|
||||
'wel': 'wel',
|
||||
'cym': 'cym',
|
||||
'dan': 'dan',
|
||||
'ger': 'ger',
|
||||
'deu': 'deu',
|
||||
'div': 'div',
|
||||
'dzo': 'dzo',
|
||||
'ewe': 'ewe',
|
||||
'gre': 'gre',
|
||||
'ell': 'ell',
|
||||
'eng': 'eng',
|
||||
'epo': 'epo',
|
||||
'spa': 'spa',
|
||||
'est': 'est',
|
||||
'baq': 'baq',
|
||||
'eus': 'eus',
|
||||
'per': 'per',
|
||||
'fas': 'fas',
|
||||
'ful': 'ful',
|
||||
'fin': 'fin',
|
||||
'fij': 'fij',
|
||||
'fao': 'fao',
|
||||
'fre': 'fre',
|
||||
'fra': 'fra',
|
||||
'fry': 'fry',
|
||||
'gle': 'gle',
|
||||
'gla': 'gla',
|
||||
'glg': 'glg',
|
||||
'grn': 'grn',
|
||||
'guj': 'guj',
|
||||
'glv': 'glv',
|
||||
'hau': 'hau',
|
||||
'heb': 'heb',
|
||||
'hin': 'hin',
|
||||
'hmo': 'hmo',
|
||||
'hrv': 'hrv',
|
||||
'hat': 'hat',
|
||||
'hun': 'hun',
|
||||
'arm': 'arm',
|
||||
'hye': 'hye',
|
||||
'her': 'her',
|
||||
'ina': 'ina',
|
||||
'ind': 'ind',
|
||||
'ile': 'ile',
|
||||
'ibo': 'ibo',
|
||||
'iii': 'iii',
|
||||
'ipk': 'ipk',
|
||||
'ido': 'ido',
|
||||
'ice': 'ice',
|
||||
'isl': 'isl',
|
||||
'ita': 'ita',
|
||||
'iku': 'iku',
|
||||
'jpn': 'jpn',
|
||||
'jav': 'jav',
|
||||
'geo': 'geo',
|
||||
'kat': 'kat',
|
||||
'kon': 'kon',
|
||||
'kik': 'kik',
|
||||
'kua': 'kua',
|
||||
'kaz': 'kaz',
|
||||
'kal': 'kal',
|
||||
'khm': 'khm',
|
||||
'kan': 'kan',
|
||||
'kor': 'kor',
|
||||
'kau': 'kau',
|
||||
'kas': 'kas',
|
||||
'kur': 'kur',
|
||||
'kom': 'kom',
|
||||
'cor': 'cor',
|
||||
'kir': 'kir',
|
||||
'lat': 'lat',
|
||||
'ltz': 'ltz',
|
||||
'lug': 'lug',
|
||||
'lim': 'lim',
|
||||
'lin': 'lin',
|
||||
'lao': 'lao',
|
||||
'lit': 'lit',
|
||||
'lub': 'lub',
|
||||
'lav': 'lav',
|
||||
'mlg': 'mlg',
|
||||
'mah': 'mah',
|
||||
'mao': 'mao',
|
||||
'mri': 'mri',
|
||||
'mac': 'mac',
|
||||
'mkd': 'mkd',
|
||||
'mal': 'mal',
|
||||
'mon': 'mon',
|
||||
'mol': 'mol',
|
||||
'mar': 'mar',
|
||||
'may': 'may',
|
||||
'msa': 'msa',
|
||||
'mlt': 'mlt',
|
||||
'bur': 'bur',
|
||||
'mya': 'mya',
|
||||
'nau': 'nau',
|
||||
'nob': 'nob',
|
||||
'nde': 'nde',
|
||||
'nep': 'nep',
|
||||
'ndo': 'ndo',
|
||||
'dut': 'dut',
|
||||
'nld': 'nld',
|
||||
'nno': 'nno',
|
||||
'nor': 'nor',
|
||||
'nbl': 'nbl',
|
||||
'nav': 'nav',
|
||||
'nya': 'nya',
|
||||
'oci': 'oci',
|
||||
'oji': 'oji',
|
||||
'orm': 'orm',
|
||||
'ori': 'ori',
|
||||
'oss': 'oss',
|
||||
'pan': 'pan',
|
||||
'pli': 'pli',
|
||||
'pol': 'pol',
|
||||
'pus': 'pus',
|
||||
'por': 'por',
|
||||
'que': 'que',
|
||||
'roh': 'roh',
|
||||
'run': 'run',
|
||||
'rum': 'rum',
|
||||
'ron': 'ron',
|
||||
'rus': 'rus',
|
||||
'kin': 'kin',
|
||||
'san': 'san',
|
||||
'srd': 'srd',
|
||||
'snd': 'snd',
|
||||
'sme': 'sme',
|
||||
'sag': 'sag',
|
||||
'slo': 'slo',
|
||||
'sin': 'sin',
|
||||
'slk': 'slk',
|
||||
'slv': 'slv',
|
||||
'smo': 'smo',
|
||||
'sna': 'sna',
|
||||
'som': 'som',
|
||||
'alb': 'alb',
|
||||
'sqi': 'sqi',
|
||||
'srp': 'srp',
|
||||
'ssw': 'ssw',
|
||||
'sot': 'sot',
|
||||
'sun': 'sun',
|
||||
'swe': 'swe',
|
||||
'swa': 'swa',
|
||||
'tam': 'tam',
|
||||
'tel': 'tel',
|
||||
'tgk': 'tgk',
|
||||
'tha': 'tha',
|
||||
'tir': 'tir',
|
||||
'tuk': 'tuk',
|
||||
'tgl': 'tgl',
|
||||
'tsn': 'tsn',
|
||||
'ton': 'ton',
|
||||
'tur': 'tur',
|
||||
'tso': 'tso',
|
||||
'tat': 'tat',
|
||||
'twi': 'twi',
|
||||
'tah': 'tah',
|
||||
'uig': 'uig',
|
||||
'ukr': 'ukr',
|
||||
'urd': 'urd',
|
||||
'uzb': 'uzb',
|
||||
'ven': 'ven',
|
||||
'vie': 'vie',
|
||||
'vol': 'vol',
|
||||
'wln': 'wln',
|
||||
'wol': 'wol',
|
||||
'xho': 'xho',
|
||||
'yid': 'yid',
|
||||
'yor': 'yor',
|
||||
'zha': 'zha',
|
||||
'chi': 'chi',
|
||||
'zho': 'zho',
|
||||
'zul': 'zul'
|
||||
}
|
||||
};
|
||||
|
||||
export const learner_responses = {
|
||||
'true-false': {
|
||||
format: '^true$|^false$',
|
||||
max: 1,
|
||||
delimiter: '',
|
||||
unique: false
|
||||
},
|
||||
'choice': {
|
||||
format: scorm2004_regex.CMIShortIdentifier,
|
||||
max: 36,
|
||||
delimiter: '[,]',
|
||||
unique: true
|
||||
},
|
||||
'fill-in': {
|
||||
format: scorm2004_regex.CMILangString250,
|
||||
max: 10,
|
||||
delimiter: '[,]',
|
||||
unique: false
|
||||
},
|
||||
'long-fill-in': {
|
||||
format: scorm2004_regex.CMILangString4000,
|
||||
max: 1,
|
||||
delimiter: '',
|
||||
unique: false
|
||||
},
|
||||
'matching': {
|
||||
format: scorm2004_regex.CMIShortIdentifier,
|
||||
format2: scorm2004_regex.CMIShortIdentifier,
|
||||
max: 36,
|
||||
delimiter: '[,]',
|
||||
delimiter2: '[.]',
|
||||
unique: false
|
||||
},
|
||||
'performance': {
|
||||
format: '^$|' + scorm2004_regex.CMIShortIdentifier,
|
||||
format2: scorm2004_regex.CMIDecimal + '|^$|' + scorm2004_regex.CMIShortIdentifier,
|
||||
max: 250,
|
||||
delimiter: '[,]',
|
||||
delimiter2: '[.]',
|
||||
unique: false
|
||||
},
|
||||
'sequencing': {
|
||||
format: scorm2004_regex.CMIShortIdentifier,
|
||||
max: 36,
|
||||
delimiter: '[,]',
|
||||
unique: false
|
||||
},
|
||||
'likert': {
|
||||
format: scorm2004_regex.CMIShortIdentifier,
|
||||
max: 1,
|
||||
delimiter: '',
|
||||
unique: false
|
||||
},
|
||||
'numeric': {
|
||||
format: scorm2004_regex.CMIDecimal,
|
||||
max: 1,
|
||||
delimiter: '',
|
||||
unique: false
|
||||
},
|
||||
'other': {
|
||||
format: scorm2004_regex.CMIString4000,
|
||||
max: 1,
|
||||
delimiter: '',
|
||||
unique: false
|
||||
}
|
||||
};
|
||||
|
||||
export const correct_responses = {
|
||||
'true-false': {
|
||||
max: 1,
|
||||
delimiter: '',
|
||||
unique: false,
|
||||
duplicate: false,
|
||||
format: '^true$|^false$',
|
||||
limit: 1
|
||||
},
|
||||
'choice': {
|
||||
max: 36,
|
||||
delimiter: '[,]',
|
||||
unique: true,
|
||||
duplicate: false,
|
||||
format: scorm2004_regex.CMIShortIdentifier
|
||||
},
|
||||
'fill-in': {
|
||||
max: 10,
|
||||
delimiter: '[,]',
|
||||
unique: false,
|
||||
duplicate: false,
|
||||
format: scorm2004_regex.CMILangString250cr
|
||||
},
|
||||
'long-fill-in': {
|
||||
max: 1,
|
||||
delimiter: '',
|
||||
unique: false,
|
||||
duplicate: true,
|
||||
format: scorm2004_regex.CMILangString4000
|
||||
},
|
||||
'matching': {
|
||||
max: 36,
|
||||
delimiter: '[,]',
|
||||
delimiter2: '[.]',
|
||||
unique: false,
|
||||
duplicate: false,
|
||||
format: scorm2004_regex.CMIShortIdentifier,
|
||||
format2: scorm2004_regex.CMIShortIdentifier
|
||||
},
|
||||
'performance': {
|
||||
max: 250,
|
||||
delimiter: '[,]',
|
||||
delimiter2: '[.]',
|
||||
unique: false,
|
||||
duplicate: false,
|
||||
format: '^$|' + scorm2004_regex.CMIShortIdentifier,
|
||||
format2: scorm2004_regex.CMIDecimal + '|^$|' + scorm2004_regex.CMIShortIdentifier
|
||||
},
|
||||
'sequencing': {
|
||||
max: 36,
|
||||
delimiter: '[,]',
|
||||
unique: false,
|
||||
duplicate: false,
|
||||
format: scorm2004_regex.CMIShortIdentifier
|
||||
},
|
||||
'likert': {
|
||||
max: 1,
|
||||
delimiter: '',
|
||||
unique: false,
|
||||
duplicate: false,
|
||||
format: scorm2004_regex.CMIShortIdentifier,
|
||||
limit: 1
|
||||
},
|
||||
'numeric': {
|
||||
max: 2,
|
||||
delimiter: '[:]',
|
||||
unique: false,
|
||||
duplicate: false,
|
||||
format: scorm2004_regex.CMIDecimal,
|
||||
limit: 1
|
||||
},
|
||||
'other': {
|
||||
max: 1,
|
||||
delimiter: '',
|
||||
unique: false,
|
||||
duplicate: false,
|
||||
format: scorm2004_regex.CMIString4000,
|
||||
limit: 1
|
||||
}
|
||||
};
|
||||
76
src/regex.js
Normal file
76
src/regex.js
Normal file
@@ -0,0 +1,76 @@
|
||||
// @flow
|
||||
|
||||
export const scorm12_regex = {
|
||||
CMIString256: '^.{0,255}$',
|
||||
CMIString4096: '^.{0,4096}$',
|
||||
CMITime: '^([0-2]{1}[0-9]{1}):([0-5]{1}[0-9]{1}):([0-5]{1}[0-9]{1})(\.[0-9]{1,6})?$',
|
||||
CMITimespan: '^([0-9]{2,4}):([0-9]{2}):([0-9]{2})(\.[0-9]{1,2})?$',
|
||||
CMIInteger: '^\\d+$',
|
||||
CMISInteger: '^-?([0-9]+)$',
|
||||
CMIDecimal: '^-?([0-9]{0,3})(\.[0-9]*)?$',
|
||||
CMIIdentifier: '^[\\u0021-\\u007E]{0,255}$',
|
||||
CMIFeedback: '^.{0,255}$', // This must be redefined
|
||||
CMIIndex: '[._](\\d+).',
|
||||
|
||||
// Vocabulary Data Type Definition
|
||||
CMIStatus: '^passed$|^completed$|^failed$|^incomplete$|^browsed$',
|
||||
CMIStatus2: '^passed$|^completed$|^failed$|^incomplete$|^browsed$|^not attempted$',
|
||||
CMIExit: '^time-out$|^suspend$|^logout$|^$',
|
||||
CMIType: '^true-false$|^choice$|^fill-in$|^matching$|^performance$|^sequencing$|^likert$|^numeric$',
|
||||
CMIResult: '^correct$|^wrong$|^unanticipated$|^neutral$|^([0-9]{0,3})?(\.[0-9]*)?$',
|
||||
NAVEvent: '^previous$|^continue$',
|
||||
|
||||
// Data ranges
|
||||
score_range: '0#100',
|
||||
audio_range: '-1#100',
|
||||
speed_range: '-100#100',
|
||||
weighting_range: '-100#100',
|
||||
text_range: '-1#1',
|
||||
};
|
||||
|
||||
export const aicc_regex = {
|
||||
...scorm12_regex, ...{
|
||||
CMIIdentifier: '^\\w{1,255}$',
|
||||
}
|
||||
};
|
||||
|
||||
export const scorm2004_regex = {
|
||||
CMIString200: '^[\\u0000-\\uFFFF]{0,200}$',
|
||||
CMIString250: '^[\\u0000-\\uFFFF]{0,250}$',
|
||||
CMIString1000: '^[\\u0000-\\uFFFF]{0,1000}$',
|
||||
CMIString4000: '^[\\u0000-\\uFFFF]{0,4000}$',
|
||||
CMIString64000: '^[\\u0000-\\uFFFF]{0,64000}$',
|
||||
CMILang: '^([a-zA-Z]{2,3}|i|x)(\-[a-zA-Z0-9\-]{2,8})?$|^$',
|
||||
CMILangString250: '^(\{lang=([a-zA-Z]{2,3}|i|x)(\-[a-zA-Z0-9\-]{2,8})?\})?([^\{].{0,250}$)?',
|
||||
CMILangcr: '^((\{lang=([a-zA-Z]{2,3}|i|x)?(\-[a-zA-Z0-9\-]{2,8})?\}))(.*?)$',
|
||||
CMILangString250cr: '^((\{lang=([a-zA-Z]{2,3}|i|x)?(\-[a-zA-Z0-9\-]{2,8})?\})?(.{0,250})?)?$',
|
||||
CMILangString4000: '^(\{lang=([a-zA-Z]{2,3}|i|x)(\-[a-zA-Z0-9\-]{2,8})?\})?([^\{].{0,4000}$)?',
|
||||
CMITime: '^(19[7-9]{1}[0-9]{1}|20[0-2]{1}[0-9]{1}|203[0-8]{1})((-(0[1-9]{1}|1[0-2]{1}))((-(0[1-9]{1}|[1-2]{1}[0-9]{1}|3[0-1]{1}))(T([0-1]{1}[0-9]{1}|2[0-3]{1})((:[0-5]{1}[0-9]{1})((:[0-5]{1}[0-9]{1})((\\.[0-9]{1,2})((Z|([+|-]([0-1]{1}[0-9]{1}|2[0-3]{1})))(:[0-5]{1}[0-9]{1})?)?)?)?)?)?)?)?$',
|
||||
CMITimespan: '^P(?:([.,\\d]+)Y)?(?:([.,\\d]+)M)?(?:([.,\\d]+)W)?(?:([.,\\d]+)D)?(?:T?(?:([.,\\d]+)H)?(?:([.,\\d]+)M)?(?:([.,\\d]+)S)?)?$',
|
||||
CMIInteger: '^\\d+$',
|
||||
CMISInteger: '^-?([0-9]+)$',
|
||||
CMIDecimal: '^-?([0-9]{1,5})(\\.[0-9]{1,18})?$',
|
||||
CMIIdentifier: '^\\S{1,250}[a-zA-Z0-9]$',
|
||||
CMIShortIdentifier: '^[\\w\.]{1,250}$',
|
||||
CMILongIdentifier: '^(?:(?!urn:)\\S{1,4000}|urn:[A-Za-z0-9-]{1,31}:\\S{1,4000})$',
|
||||
CMIFeedback: '^.*$', // This must be redefined
|
||||
CMIIndex: '[._](\\d+).',
|
||||
CMIIndexStore: '.N(\\d+).',
|
||||
|
||||
// Vocabulary Data Type Definition
|
||||
CMICStatus: '^completed$|^incomplete$|^not attempted$|^unknown$',
|
||||
CMISStatus: '^passed$|^failed$|^unknown$',
|
||||
CMIExit: '^time-out$|^suspend$|^logout$|^normal$|^$',
|
||||
CMIType: '^true-false$|^choice$|^(long-)?fill-in$|^matching$|^performance$|^sequencing$|^likert$|^numeric$|^other$',
|
||||
CMIResult: '^correct$|^incorrect$|^unanticipated$|^neutral$|^-?([0-9]{1,4})(\\.[0-9]{1,18})?$',
|
||||
NAVEvent: '^previous$|^continue$|^exit$|^exitAll$|^abandon$|^abandonAll$|^suspendAll$|^\{target=\\S{0,200}[a-zA-Z0-9]\}choice|jump$',
|
||||
NAVBoolean: '^unknown$|^true$|^false$',
|
||||
NAVTarget: '^previous$|^continue$|^choice.{target=\\S{0,200}[a-zA-Z0-9]}$',
|
||||
|
||||
// Data ranges
|
||||
scaled_range: '-1#1',
|
||||
audio_range: '0#*',
|
||||
speed_range: '0#*',
|
||||
text_range: '-1#1',
|
||||
progress_range: '0#1',
|
||||
};
|
||||
116
src/utilities.js
Normal file
116
src/utilities.js
Normal file
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright (C) Noverant, Inc - All Rights Reserved Unauthorized copying of this file, via any
|
||||
* medium is strictly prohibited Proprietary and confidential
|
||||
*/
|
||||
|
||||
// @flow
|
||||
export const SECONDS_PER_SECOND = 1;
|
||||
export const SECONDS_PER_MINUTE = 60;
|
||||
export const SECONDS_PER_HOUR = 60 * SECONDS_PER_MINUTE;
|
||||
export const SECONDS_PER_DAY = 24 * SECONDS_PER_HOUR;
|
||||
|
||||
const designations = [
|
||||
['D', SECONDS_PER_DAY],
|
||||
['H', SECONDS_PER_HOUR],
|
||||
['M', SECONDS_PER_MINUTE],
|
||||
['S', SECONDS_PER_SECOND],
|
||||
];
|
||||
|
||||
/**
|
||||
* Converts a Number to a String of HH:MM:SS
|
||||
*
|
||||
* @param totalSeconds
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getSecondsAsHHMMSS(totalSeconds: Number) {
|
||||
// SCORM spec does not deal with negative durations, give zero back
|
||||
if(!totalSeconds || totalSeconds <= 0) {
|
||||
return '00:00:00';
|
||||
}
|
||||
|
||||
let hours = Math.floor(totalSeconds / SECONDS_PER_HOUR);
|
||||
|
||||
let dateObj = new Date(totalSeconds * 1000);
|
||||
let minutes = dateObj.getUTCMinutes();
|
||||
let seconds = dateObj.getSeconds();
|
||||
|
||||
return hours.toString().padStart(2, '0') + ':' +
|
||||
minutes.toString().padStart(2, '0') + ':' +
|
||||
seconds.toString().padStart(2, '0');
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the number of seconds from ISO 8601 Duration
|
||||
*
|
||||
* @param seconds
|
||||
* @returns {String}
|
||||
*/
|
||||
export function getSecondsAsISODuration(seconds: Number) {
|
||||
// SCORM spec does not deal with negative durations, give zero back
|
||||
if(!seconds || seconds <= 0) {
|
||||
return 'P0S';
|
||||
}
|
||||
|
||||
let duration = 'P';
|
||||
let remainder = seconds;
|
||||
|
||||
designations.forEach(([sign, current_seconds]) => {
|
||||
const value = Math.floor(remainder / current_seconds);
|
||||
|
||||
remainder = remainder % current_seconds;
|
||||
|
||||
if (value) {
|
||||
duration += `${value}${sign}`;
|
||||
}
|
||||
});
|
||||
|
||||
return duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the number of seconds from HH:MM:SS.DDDDDD
|
||||
*
|
||||
* @param timeString
|
||||
* @param timeRegex
|
||||
* @returns {number}
|
||||
*/
|
||||
export function getTimeAsSeconds(timeString: String, timeRegex: RegExp) {
|
||||
if(!timeString || typeof timeString !== 'string' || !timeString.match(timeRegex)) {
|
||||
return 0;
|
||||
}
|
||||
const parts = timeString.split(':');
|
||||
const hours = Number(parts[0]);
|
||||
const minutes = Number(parts[1]);
|
||||
const seconds = Number(parts[2]);
|
||||
return (hours * 3600) + (minutes * 60) + seconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the number of seconds from ISO 8601 Duration
|
||||
*
|
||||
* @param duration
|
||||
* @param durationRegex
|
||||
* @returns {number}
|
||||
*/
|
||||
export function getDurationAsSeconds(duration: String, durationRegex: RegExp) {
|
||||
if(!duration || !duration.match(durationRegex)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let [,years,months,,days,hours,minutes,seconds] = new RegExp(durationRegex).exec(duration) || [];
|
||||
|
||||
let now = new Date();
|
||||
let anchor = new Date(now);
|
||||
anchor.setFullYear(anchor.getFullYear() + Number(years || 0));
|
||||
anchor.setMonth(anchor.getMonth() + Number(months || 0));
|
||||
anchor.setDate(anchor.getDate() + Number(days || 0));
|
||||
anchor.setHours(anchor.getHours() + Number(hours || 0));
|
||||
anchor.setMinutes(anchor.getMinutes() + Number(minutes || 0));
|
||||
anchor.setSeconds(anchor.getSeconds() + Number(seconds || 0));
|
||||
if(seconds) {
|
||||
let milliseconds = Number(Number(seconds) % 1).toFixed(6) * 1000.0;
|
||||
anchor.setMilliseconds(anchor.getMilliseconds() + milliseconds);
|
||||
}
|
||||
|
||||
return (anchor - now) / 1000.0;
|
||||
}
|
||||
282
test/Scorm12API.spec.js
Normal file
282
test/Scorm12API.spec.js
Normal file
@@ -0,0 +1,282 @@
|
||||
import { expect, assert } from 'chai';
|
||||
import {describe, it, before, beforeEach, after, afterEach, } from "mocha";
|
||||
import Scorm12API from "../src/Scorm12API";
|
||||
import {scorm12_constants, scorm12_error_codes} from "../src/constants";
|
||||
|
||||
let API;
|
||||
|
||||
const checkFieldConstraintSize = (fieldName: String, limit: Number, expectedValue: String = '') => {
|
||||
describe(`Field: ${fieldName}`, () => {
|
||||
it(`Should be able to read from ${fieldName}`, () => {
|
||||
expect(eval(`API.${fieldName}`)).to.equal(expectedValue);
|
||||
});
|
||||
|
||||
it(`Should be able to write upto ${limit} characters to ${fieldName}`, () => {
|
||||
eval(`API.${fieldName} = 'x'.repeat(${limit})`);
|
||||
expect(0).to.equal(API.lastErrorCode);
|
||||
});
|
||||
|
||||
it(`Should fail to write more than ${limit} characters to ${fieldName}`, () => {
|
||||
eval(`API.${fieldName} = 'x'.repeat(${limit + 1})`);
|
||||
expect(scorm12_error_codes.TYPE_MISMATCH + '').to.equal(API.lastErrorCode);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const checkInvalidSet = (fieldName: String, expectedValue: String = '') => {
|
||||
describe(`Field: ${fieldName}`, () => {
|
||||
it(`Should be able to read from ${fieldName}`, () => {
|
||||
expect(eval(`API.${fieldName}`)).to.equal(expectedValue);
|
||||
});
|
||||
|
||||
it(`Should fail to write to ${fieldName}`, () => {
|
||||
eval(`API.${fieldName} = 'xxx'`);
|
||||
expect(API.lastErrorCode).to.equal(scorm12_error_codes.INVALID_SET_VALUE + '');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const checkReadOnly = (fieldName: String, expectedValue: String = '') => {
|
||||
describe(`Field: ${fieldName}`, () => {
|
||||
it(`Should be able to read from ${fieldName}`, () => {
|
||||
expect(eval(`API.${fieldName}`)).to.equal(expectedValue);
|
||||
});
|
||||
|
||||
it(`Should fail to write to ${fieldName}`, () => {
|
||||
eval(`API.${fieldName} = 'xxx'`);
|
||||
expect(API.lastErrorCode).to.equal(scorm12_error_codes.READ_ONLY_ELEMENT + '');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const checkRead = (fieldName: String, expectedValue: String = '') => {
|
||||
describe(`Field: ${fieldName}`, () => {
|
||||
it(`Should be able to read from ${fieldName}`, () => {
|
||||
expect(eval(`API.${fieldName}`)).to.equal(expectedValue);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const checkWriteOnly = (fieldName: String, valueToTest: String = 'xxx') => {
|
||||
describe(`Field: ${fieldName}`, () => {
|
||||
it(`Should fail to read from ${fieldName}`, () => {
|
||||
eval(`API.${fieldName}`);
|
||||
expect(API.lastErrorCode).to.equal(scorm12_error_codes.WRITE_ONLY_ELEMENT + '');
|
||||
});
|
||||
|
||||
it(`Should successfully write to ${fieldName}`, () => {
|
||||
eval(`API.${fieldName} = '${valueToTest}'`);
|
||||
expect(API.lastErrorCode).to.equal(0);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const checkWrite = (fieldName: String, valueToTest: String = 'xxx') => {
|
||||
describe(`Field: ${fieldName}`, () => {
|
||||
it(`Should successfully write to ${fieldName}`, () => {
|
||||
eval(`API.${fieldName} = '${valueToTest}'`);
|
||||
expect(API.lastErrorCode).to.equal(0);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const checkValidValues = (fieldName: String, expectedError: Number, validValues: Array, invalidValues: Array) => {
|
||||
describe(`Field: ${fieldName}`, () => {
|
||||
for(let idx in validValues) {
|
||||
it(`Should successfully write '${validValues[idx]}' to ${fieldName}`, () => {
|
||||
eval(`API.${fieldName} = '${validValues[idx]}'`);
|
||||
expect(API.lastErrorCode).to.equal(0);
|
||||
});
|
||||
}
|
||||
|
||||
for(let idx in invalidValues) {
|
||||
it(`Should fail to write '${invalidValues[idx]}' to ${fieldName}`, () => {
|
||||
eval(`API.${fieldName} = '${invalidValues[idx]}'`);
|
||||
expect(API.lastErrorCode).to.equal(expectedError + '');
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
describe('SCORM 1.2 API Tests', () => {
|
||||
describe('CMI Spec Tests', () => {
|
||||
describe('Post-LMSInitialize Tests', () => {
|
||||
beforeEach('Create the API object', () => {
|
||||
API = new Scorm12API();
|
||||
API.LMSInitialize();
|
||||
});
|
||||
afterEach('Destroy API object', () => {
|
||||
API = null;
|
||||
});
|
||||
|
||||
it('LMSInitialize should create CMI object', () => {
|
||||
assert(API.cmi !== undefined, 'CMI object is created');
|
||||
});
|
||||
|
||||
it('Exporting CMI to JSON produces proper Object', () => {
|
||||
expect(
|
||||
JSON.parse(API.renderCMIToJSON())
|
||||
).to.hasOwnProperty('core')
|
||||
});
|
||||
|
||||
/**
|
||||
* Base CMI Properties
|
||||
*/
|
||||
checkInvalidSet('cmi._version', '3.4');
|
||||
checkInvalidSet('cmi._children', scorm12_constants.cmi_children);
|
||||
checkFieldConstraintSize('cmi.suspend_data', 4096);
|
||||
checkReadOnly('cmi.launch_data');
|
||||
checkFieldConstraintSize('cmi.comments', 4096);
|
||||
checkReadOnly('cmi.comments_from_lms');
|
||||
|
||||
/**
|
||||
* cmi.core Properties
|
||||
*/
|
||||
checkInvalidSet('cmi.core._children', scorm12_constants.core_children);
|
||||
checkReadOnly('cmi.core.student_id');
|
||||
checkReadOnly('cmi.core.student_name');
|
||||
checkFieldConstraintSize('cmi.core.lesson_location', 255);
|
||||
checkReadOnly('cmi.core.credit');
|
||||
checkRead('cmi.core.lesson_status');
|
||||
checkValidValues('cmi.core.lesson_status', scorm12_error_codes.TYPE_MISMATCH, [
|
||||
'passed',
|
||||
'completed',
|
||||
'failed',
|
||||
'incomplete',
|
||||
'browsed'
|
||||
], [
|
||||
'Passed',
|
||||
'P',
|
||||
'F',
|
||||
'p',
|
||||
'true',
|
||||
'false',
|
||||
'complete'
|
||||
]);
|
||||
checkReadOnly('cmi.core.entry');
|
||||
checkReadOnly('cmi.core.total_time');
|
||||
checkReadOnly('cmi.core.lesson_mode', 'normal');
|
||||
checkWrite('cmi.core.exit', 'suspend');
|
||||
checkValidValues('cmi.core.exit', scorm12_error_codes.TYPE_MISMATCH, [
|
||||
'time-out',
|
||||
'suspend',
|
||||
'logout',
|
||||
], [
|
||||
'complete',
|
||||
'exit'
|
||||
]);
|
||||
checkWriteOnly('cmi.core.session_time', '00:00:00');
|
||||
checkValidValues('cmi.core.session_time', scorm12_error_codes.TYPE_MISMATCH, [
|
||||
'10:06:57',
|
||||
'00:00:01.56',
|
||||
'23:59:59',
|
||||
'47:59:59',
|
||||
], [
|
||||
'06:5:13',
|
||||
'23:59:59.123',
|
||||
'P1DT23H59M59S'
|
||||
]);
|
||||
|
||||
/**
|
||||
* cmi.core.score Properties
|
||||
*/
|
||||
checkInvalidSet('cmi.core.score._children', scorm12_constants.score_children);
|
||||
checkValidValues('cmi.core.score.raw', scorm12_error_codes.VALUE_OUT_OF_RANGE, [
|
||||
'0',
|
||||
'25.1',
|
||||
'50.5',
|
||||
'75',
|
||||
'100',
|
||||
], [
|
||||
'-1',
|
||||
'101'
|
||||
]);
|
||||
checkValidValues('cmi.core.score.min', scorm12_error_codes.VALUE_OUT_OF_RANGE, [
|
||||
'0',
|
||||
'25.1',
|
||||
'50.5',
|
||||
'75',
|
||||
'100',
|
||||
], [
|
||||
'-1',
|
||||
'101'
|
||||
]);
|
||||
checkValidValues('cmi.core.score.max', scorm12_error_codes.VALUE_OUT_OF_RANGE, [
|
||||
'0',
|
||||
'25.1',
|
||||
'50.5',
|
||||
'75',
|
||||
'100',
|
||||
], [
|
||||
'-1',
|
||||
'101'
|
||||
]);
|
||||
|
||||
/**
|
||||
* cmi.objectives Properties
|
||||
*/
|
||||
checkInvalidSet('cmi.objectives._children', scorm12_constants.objectives_children);
|
||||
checkInvalidSet('cmi.objectives._count', 0);
|
||||
|
||||
/**
|
||||
* cmi.student_data Properties
|
||||
*/
|
||||
checkInvalidSet('cmi.student_data._children', scorm12_constants.student_data_children);
|
||||
checkReadOnly('cmi.student_data.mastery_score');
|
||||
checkReadOnly('cmi.student_data.max_time_allowed');
|
||||
checkReadOnly('cmi.student_data.time_limit_action');
|
||||
|
||||
/**
|
||||
* cmi.student_preference Properties
|
||||
*/
|
||||
checkInvalidSet('cmi.student_preference._children', scorm12_constants.student_preference_children);
|
||||
checkValidValues('cmi.student_preference.audio', scorm12_error_codes.TYPE_MISMATCH, [
|
||||
'1',
|
||||
'-1',
|
||||
'50',
|
||||
'100',
|
||||
], [
|
||||
'invalid',
|
||||
'a100'
|
||||
]);
|
||||
checkValidValues('cmi.student_preference.audio', scorm12_error_codes.VALUE_OUT_OF_RANGE, [], [
|
||||
'101',
|
||||
'5000000',
|
||||
'-500'
|
||||
]);
|
||||
checkFieldConstraintSize('cmi.student_preference.language', 255);
|
||||
checkValidValues('cmi.student_preference.speed', scorm12_error_codes.TYPE_MISMATCH, [
|
||||
'1',
|
||||
'-100',
|
||||
'50',
|
||||
'100',
|
||||
], [
|
||||
'invalid',
|
||||
'a100'
|
||||
]);
|
||||
checkValidValues('cmi.student_preference.speed', scorm12_error_codes.VALUE_OUT_OF_RANGE, [], [
|
||||
'101',
|
||||
'-101',
|
||||
'5000000',
|
||||
'-500'
|
||||
]);
|
||||
checkValidValues('cmi.student_preference.text', scorm12_error_codes.TYPE_MISMATCH, [
|
||||
'1',
|
||||
'-1'
|
||||
], [
|
||||
'invalid',
|
||||
'a100'
|
||||
]);
|
||||
checkValidValues('cmi.student_preference.text', scorm12_error_codes.VALUE_OUT_OF_RANGE, [], [
|
||||
'2',
|
||||
'-2'
|
||||
]);
|
||||
|
||||
/**
|
||||
* cmi.interactions Properties
|
||||
*/
|
||||
checkInvalidSet('cmi.interactions._children', scorm12_constants.interactions_children);
|
||||
checkInvalidSet('cmi.interactions._count', 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
220
test/utilities.spec.js
Normal file
220
test/utilities.spec.js
Normal file
@@ -0,0 +1,220 @@
|
||||
import { expect } from 'chai';
|
||||
import {describe, it} from "mocha";
|
||||
import * as Utilities from '../src/utilities';
|
||||
import {scorm12_regex, scorm2004_regex} from "../src/regex";
|
||||
|
||||
describe('Utility Tests', () => {
|
||||
describe('function getSecondsAsHHMMSS()', () => {
|
||||
it('10 returns 00:00:10', () => {
|
||||
expect(
|
||||
Utilities.getSecondsAsHHMMSS(10)
|
||||
).to.equal('00:00:10');
|
||||
});
|
||||
|
||||
it('60 returns 00:01:00', () => {
|
||||
expect(
|
||||
Utilities.getSecondsAsHHMMSS(60)
|
||||
).to.equal('00:01:00');
|
||||
});
|
||||
|
||||
it('3600 returns 01:00:00', () => {
|
||||
expect(
|
||||
Utilities.getSecondsAsHHMMSS(3600)
|
||||
).to.equal('01:00:00');
|
||||
});
|
||||
|
||||
it('70 returns 00:01:10', () => {
|
||||
expect(
|
||||
Utilities.getSecondsAsHHMMSS(70)
|
||||
).to.equal('00:01:10');
|
||||
});
|
||||
|
||||
it('3670 returns 01:01:10', () => {
|
||||
expect(
|
||||
Utilities.getSecondsAsHHMMSS(3670)
|
||||
).to.equal('01:01:10');
|
||||
});
|
||||
|
||||
it('90000 returns 25:00:00, check for hours greater than 24', () => {
|
||||
expect(
|
||||
Utilities.getSecondsAsHHMMSS(90000)
|
||||
).to.equal('25:00:00');
|
||||
});
|
||||
|
||||
it('-3600 returns 00:00:00, negative time not allowed in SCORM session times', () => {
|
||||
expect(
|
||||
Utilities.getSecondsAsHHMMSS(-3600)
|
||||
).to.equal('00:00:00');
|
||||
});
|
||||
|
||||
it('Empty seconds returns 00:00:00', () => {
|
||||
expect(
|
||||
Utilities.getSecondsAsHHMMSS(null)
|
||||
).to.equal('00:00:00');
|
||||
});
|
||||
});
|
||||
|
||||
describe('function getSecondsAsISODuration()', () => {
|
||||
it('10 returns P10S', () => {
|
||||
expect(
|
||||
Utilities.getSecondsAsISODuration(10)
|
||||
).to.equal('P10S');
|
||||
});
|
||||
|
||||
it('60 returns P1M', () => {
|
||||
expect(
|
||||
Utilities.getSecondsAsISODuration(60)
|
||||
).to.equal('P1M');
|
||||
});
|
||||
|
||||
it('3600 returns P1H', () => {
|
||||
expect(
|
||||
Utilities.getSecondsAsISODuration(3600)
|
||||
).to.equal('P1H');
|
||||
});
|
||||
|
||||
it('70 returns P1M10S', () => {
|
||||
expect(
|
||||
Utilities.getSecondsAsISODuration(70)
|
||||
).to.equal('P1M10S');
|
||||
});
|
||||
|
||||
it('3670 returns P1H1M10S', () => {
|
||||
expect(
|
||||
Utilities.getSecondsAsISODuration(3670)
|
||||
).to.equal('P1H1M10S');
|
||||
});
|
||||
|
||||
it('90000 returns P1D1H', () => {
|
||||
expect(
|
||||
Utilities.getSecondsAsISODuration(90000)
|
||||
).to.equal('P1D1H');
|
||||
});
|
||||
|
||||
it('90061 returns P1D1H1M1S', () => {
|
||||
expect(
|
||||
Utilities.getSecondsAsISODuration(90061)
|
||||
).to.equal('P1D1H1M1S');
|
||||
});
|
||||
|
||||
it('-3600 returns P0S, negative time not allowed in SCORM session times', () => {
|
||||
expect(
|
||||
Utilities.getSecondsAsISODuration(-3600)
|
||||
).to.equal('P0S');
|
||||
});
|
||||
|
||||
it('Empty seconds returns P0S', () => {
|
||||
expect(
|
||||
Utilities.getSecondsAsISODuration(null)
|
||||
).to.equal('P0S');
|
||||
});
|
||||
});
|
||||
|
||||
describe('function getTimeAsSeconds()', () => {
|
||||
it('00:00:10 returns 10', () => {
|
||||
expect(
|
||||
Utilities.getTimeAsSeconds('00:00:10', scorm12_regex.CMITimespan)
|
||||
).to.equal(10);
|
||||
});
|
||||
|
||||
it('00:01:10 returns 70', () => {
|
||||
expect(
|
||||
Utilities.getTimeAsSeconds('00:01:10', scorm12_regex.CMITimespan)
|
||||
).to.equal(70);
|
||||
});
|
||||
|
||||
it('01:01:10 returns 3670', () => {
|
||||
expect(
|
||||
Utilities.getTimeAsSeconds('01:01:10', scorm12_regex.CMITimespan)
|
||||
).to.equal(3670);
|
||||
});
|
||||
|
||||
it('100:00:00 returns 3670', () => {
|
||||
expect(
|
||||
Utilities.getTimeAsSeconds('100:00:00', scorm12_regex.CMITimespan)
|
||||
).to.equal(360000);
|
||||
});
|
||||
|
||||
it('-01:00:00 returns 0', () => {
|
||||
expect(
|
||||
Utilities.getTimeAsSeconds('-01:00:00', scorm12_regex.CMITimespan)
|
||||
).to.equal(0);
|
||||
});
|
||||
|
||||
it('Number value returns 0', () => {
|
||||
expect(
|
||||
Utilities.getTimeAsSeconds(999, scorm12_regex.CMITimespan)
|
||||
).to.equal(0);
|
||||
});
|
||||
|
||||
it('boolean value returns 0', () => {
|
||||
expect(
|
||||
Utilities.getTimeAsSeconds(true, scorm12_regex.CMITimespan)
|
||||
).to.equal(0);
|
||||
});
|
||||
|
||||
it('Empty value returns 0', () => {
|
||||
expect(
|
||||
Utilities.getTimeAsSeconds(null, scorm12_regex.CMITimespan)
|
||||
).to.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('function getDurationAsSeconds()', () => {
|
||||
it('P0S returns 0', () => {
|
||||
expect(
|
||||
Utilities.getDurationAsSeconds('P0S', scorm2004_regex.CMITimespan)
|
||||
).to.equal(0);
|
||||
});
|
||||
|
||||
it('P70S returns 70', () => {
|
||||
expect(
|
||||
Utilities.getDurationAsSeconds('P70S', scorm2004_regex.CMITimespan)
|
||||
).to.equal(70);
|
||||
});
|
||||
|
||||
it('PT1M10S returns 70', () => {
|
||||
expect(
|
||||
Utilities.getDurationAsSeconds('PT1M10S', scorm2004_regex.CMITimespan)
|
||||
).to.equal(70);
|
||||
});
|
||||
|
||||
it('P1D returns 86400', () => {
|
||||
expect(
|
||||
Utilities.getDurationAsSeconds('P1D', scorm2004_regex.CMITimespan)
|
||||
).to.equal(86400);
|
||||
});
|
||||
|
||||
it('P1M returns number of seconds for one month from now', () => {
|
||||
const now = new Date();
|
||||
let oneMonthFromNow = new Date(now);
|
||||
oneMonthFromNow.setMonth(oneMonthFromNow.getMonth() + 1);
|
||||
|
||||
expect(
|
||||
Utilities.getDurationAsSeconds('P1M', scorm2004_regex.CMITimespan)
|
||||
).to.equal((oneMonthFromNow - now) / 1000.0);
|
||||
});
|
||||
|
||||
it('P1Y returns number of seconds for one year from now', () => {
|
||||
const now = new Date();
|
||||
let oneYearFromNow = new Date(now);
|
||||
oneYearFromNow.setFullYear(oneYearFromNow.getFullYear() + 1);
|
||||
|
||||
expect(
|
||||
Utilities.getDurationAsSeconds('P1Y', scorm2004_regex.CMITimespan)
|
||||
).to.equal((oneYearFromNow - now) / 1000.0);
|
||||
});
|
||||
|
||||
it('Invalid duration returns 0', () => {
|
||||
expect(
|
||||
Utilities.getDurationAsSeconds('T1M10S', scorm2004_regex.CMITimespan)
|
||||
).to.equal(0);
|
||||
});
|
||||
|
||||
it('Empty duration returns 0', () => {
|
||||
expect(
|
||||
Utilities.getDurationAsSeconds(null, scorm2004_regex.CMITimespan)
|
||||
).to.equal(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user