From 360d30bdf0dd408a40a8a225b620f32475f40052 Mon Sep 17 00:00:00 2001 From: Jonathan Putney Date: Thu, 14 Nov 2019 13:17:16 -0500 Subject: [PATCH] Adding more API tests --- src/AICC.js | 9 +- src/BaseAPI.js | 109 ++++++++++++---- src/Scorm12API.js | 12 +- src/Scorm2004API.js | 16 +-- src/cmi/common.js | 28 ++--- src/cmi/scorm12_cmi.js | 34 +++-- src/exceptions.js | 8 -- test/Scorm12API.spec.js | 271 ++++++++++++++++++++++++++++++++++------ test/api_helpers.js | 73 ++++++++++- 9 files changed, 449 insertions(+), 111 deletions(-) diff --git a/src/AICC.js b/src/AICC.js index 9c78f0a..9d8f88c 100644 --- a/src/AICC.js +++ b/src/AICC.js @@ -26,15 +26,16 @@ export default class AICC extends Scorm12API { * * @param {string} CMIElement * @param {any} value + * @param {boolean} foundFirstIndex * @return {object} */ - getChildElement(CMIElement, value) { - let newChild = super.getChildElement(CMIElement); + getChildElement(CMIElement, value, foundFirstIndex) { + let newChild = super.getChildElement(CMIElement, value, foundFirstIndex); if (!newChild) { - if (this.stringContains(CMIElement, 'cmi.evaluation.comments')) { + if (this.stringMatches(CMIElement, 'cmi\\.evaluation\\.comments\\.\\d')) { newChild = new CMIEvaluationCommentsObject(this); - } else if (this.stringContains(CMIElement, 'cmi.student_data.tries')) { + } else if (this.stringMatches(CMIElement, 'cmi\\.student_data\\.tries\\.\\d')) { newChild = new CMITriesObject(this); } } diff --git a/src/BaseAPI.js b/src/BaseAPI.js index 87c8984..6d62db1 100644 --- a/src/BaseAPI.js +++ b/src/BaseAPI.js @@ -30,6 +30,9 @@ export default class BaseAPI { * @param {object} error_codes */ constructor(error_codes) { + if (new.target === BaseAPI) { + throw new TypeError('Cannot construct BaseAPI instances directly'); + } this.currentState = api_constants.STATE_NOT_INITIALIZED; this.apiLogLevel = api_constants.LOG_LEVEL_ERROR; this.lastErrorCode = 0; @@ -109,7 +112,7 @@ export default class BaseAPI { callbackName: String, checkTerminated: boolean, CMIElement: String) { - let returnValue = ''; + let returnValue; if (this.checkState(checkTerminated, this.#error_codes.RETRIEVE_BEFORE_INIT, @@ -351,8 +354,22 @@ export default class BaseAPI { * @param {string} tester String to check for * @return {boolean} */ - stringContains(str: String, tester: String) { - return str.indexOf(tester) > -1; + stringMatches(str: String, tester: String) { + return str && tester && str.match(tester); + } + + /** + * Check to see if the specific object has the given property + * @param {*} refObject + * @param {string} attribute + * @return {boolean} + * @private + */ + _checkObjectHasProperty(refObject, attribute: String) { + return Object.hasOwnProperty.call(refObject, attribute) || + Object.getOwnPropertyDescriptor( + Object.getPrototypeOf(refObject), attribute) || + (attribute in refObject); } /** @@ -362,9 +379,10 @@ export default class BaseAPI { * @param {(string|number)} _errorNumber * @param {boolean} _detail * @return {string} + * @abstract */ getLmsErrorMessageDetails(_errorNumber, _detail) { - return 'No error'; + throw new Error('The getLmsErrorMessageDetails method has not been implemented'); } /** @@ -373,9 +391,10 @@ export default class BaseAPI { * * @param {string} _CMIElement * @return {string} + * @abstract */ getCMIValue(_CMIElement) { - return ''; + throw new Error('The getCMIValue method has not been implemented'); } /** @@ -385,9 +404,10 @@ export default class BaseAPI { * @param {string} _CMIElement * @param {any} _value * @return {string} + * @abstract */ setCMIValue(_CMIElement, _value) { - return api_constants.SCORM_FALSE; + throw new Error('The setCMIValue method has not been implemented'); } /** @@ -408,6 +428,7 @@ export default class BaseAPI { const structure = CMIElement.split('.'); let refObject = this; let returnValue = api_constants.SCORM_FALSE; + let foundFirstIndex = false; const invalidErrorMessage = `The data model element passed to ${methodName} (${CMIElement}) is not a valid SCORM data model element.`; const invalidErrorCode = scorm2004 ? @@ -421,12 +442,10 @@ export default class BaseAPI { if (scorm2004 && (attribute.substr(0, 8) === '{target=') && (typeof refObject._isTargetValid == 'function')) { this.throwSCORMError(this.#error_codes.READ_ONLY_ELEMENT); - } else if (!Object.hasOwnProperty.call(refObject, attribute) && - !Object.getOwnPropertyDescriptor( - Object.getPrototypeOf(refObject), attribute)) { + } else if (!this._checkObjectHasProperty(refObject, attribute)) { this.throwSCORMError(invalidErrorCode, invalidErrorMessage); } else { - if (this.stringContains(CMIElement, '.correct_responses')) { + if (this.stringMatches(CMIElement, '.correct_responses')) { this.validateCorrectResponse(CMIElement, value); } @@ -452,7 +471,8 @@ export default class BaseAPI { if (item) { refObject = item; } else { - const newChild = this.getChildElement(CMIElement, value); + const newChild = this.getChildElement(CMIElement, value, foundFirstIndex); + foundFirstIndex = true; if (!newChild) { this.throwSCORMError(invalidErrorCode, invalidErrorMessage); @@ -494,10 +514,12 @@ export default class BaseAPI { * * @param {string} _CMIElement - unused * @param {*} _value - unused + * @param {boolean} _foundFirstIndex - unused * @return {*} + * @abstract */ - getChildElement(_CMIElement, _value) { - return null; + getChildElement(_CMIElement, _value, _foundFirstIndex) { + throw new Error('The getChildElement method has not been implemented'); } /** @@ -517,14 +539,20 @@ export default class BaseAPI { let refObject = this; let attribute = null; + const uninitializedErrorMessage = `The data model element passed to ${methodName} (${CMIElement}) has not been initialized.`; + 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++) { attribute = structure[i]; if (!scorm2004) { if (i === structure.length - 1) { - if (!{}.hasOwnProperty.call(refObject, attribute)) { - this.throwSCORMError(101, - 'getCMIValue did not find a value for: ' + CMIElement); + if (!this._checkObjectHasProperty(refObject, attribute)) { + this.throwSCORMError(invalidErrorCode, invalidErrorMessage); + return; } } } else { @@ -533,15 +561,37 @@ export default class BaseAPI { const target = String(attribute). substr(8, String(attribute).length - 9); return refObject._isTargetValid(target); - } else if (!{}.hasOwnProperty.call(refObject, attribute)) { - this.throwSCORMError(401, - 'The data model element passed to GetValue (' + CMIElement + - ') is not a valid SCORM data model element.'); - return ''; + } else if (!this._checkObjectHasProperty(refObject, attribute)) { + this.throwSCORMError(invalidErrorCode, invalidErrorMessage); + return; } } refObject = refObject[attribute]; + if (!refObject) { + this.throwSCORMError(invalidErrorCode, invalidErrorMessage); + break; + } + + if (refObject instanceof CMIArray) { + const index = parseInt(structure[i + 1], 10); + + // SCO is trying to set an item on an array + if (!isNaN(index)) { + const item = refObject.childArray[index]; + + if (item) { + refObject = item; + } else { + this.throwSCORMError(this.#error_codes.VALUE_NOT_INITIALIZED, + uninitializedErrorMessage); + break; + } + + // Have to update i value to skip the array position + i++; + } + } } if (refObject === null || refObject === undefined) { @@ -552,7 +602,7 @@ export default class BaseAPI { this.throwSCORMError(203); } } - return ''; + return; } else { return refObject; } @@ -657,7 +707,7 @@ export default class BaseAPI { * @param {string} success */ clearSCORMError(success: String) { - if (success !== api_constants.SCORM_FALSE) { + if (success !== undefined && success !== api_constants.SCORM_FALSE) { this.lastErrorCode = 0; } } @@ -701,13 +751,24 @@ export default class BaseAPI { * * @return {string} */ - renderCMIToJSON() { + renderCMIToJSONString() { const 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}); } + /** + * Returns a JS object representing the current cmi + * @return {object} + */ + renderCMIToJSONObject() { + const 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.parse(JSON.stringify(cmi)); + } + /** * Throws a SCORM error * diff --git a/src/Scorm12API.js b/src/Scorm12API.js index ac0e591..425f664 100644 --- a/src/Scorm12API.js +++ b/src/Scorm12API.js @@ -42,6 +42,7 @@ export default class Scorm12API extends BaseAPI { * @return {string} bool */ lmsInitialize() { + this.cmi.initialize(); return this.initialize('LMSInitialize', 'LMS was already initialized!', 'LMS is already finished!'); } @@ -140,18 +141,19 @@ export default class Scorm12API extends BaseAPI { * * @param {string} CMIElement * @param {*} value + * @param {boolean} foundFirstIndex * @return {object} */ - getChildElement(CMIElement, value) { + getChildElement(CMIElement, value, foundFirstIndex) { let newChild; - if (this.stringContains(CMIElement, 'cmi.objectives')) { + if (this.stringMatches(CMIElement, 'cmi\\.objectives\\.\\d')) { newChild = new CMIObjectivesObject(this); - } else if (this.stringContains(CMIElement, '.correct_responses')) { + } else if (foundFirstIndex && this.stringMatches(CMIElement, 'cmi\\.interactions\\.\\d\\.correct_responses\\.\\d')) { newChild = new CMIInteractionsCorrectResponsesObject(this); - } else if (this.stringContains(CMIElement, '.objectives')) { + } else if (foundFirstIndex && this.stringMatches(CMIElement, 'cmi\\.interactions\\.\\d\\.objectives\\.\\d')) { newChild = new CMIInteractionsObjectivesObject(this); - } else if (this.stringContains(CMIElement, 'cmi.interactions')) { + } else if (this.stringMatches(CMIElement, 'cmi\\.interactions\\.\\d')) { newChild = new CMIInteractionsObject(this); } diff --git a/src/Scorm2004API.js b/src/Scorm2004API.js index a8cc63f..afd1102 100644 --- a/src/Scorm2004API.js +++ b/src/Scorm2004API.js @@ -57,6 +57,7 @@ export default class Scorm2004API extends BaseAPI { * @return {string} bool */ lmsInitialize() { + this.cmi.initialize(); return this.initialize('Initialize'); } @@ -137,14 +138,15 @@ export default class Scorm2004API extends BaseAPI { * * @param {string} CMIElement * @param {any} value + * @param {boolean} foundFirstIndex * @return {any} */ - getChildElement(CMIElement, value) { + getChildElement(CMIElement, value, foundFirstIndex) { let newChild; - if (this.stringContains(CMIElement, 'cmi.objectives')) { + if (this.stringMatches(CMIElement, 'cmi\\.objectives\\.\\d')) { newChild = new CMIObjectivesObject(this); - } else if (this.stringContains(CMIElement, '.correct_responses')) { + } else if (foundFirstIndex && this.stringMatches(CMIElement, 'cmi\\.interactions\\.\\d\\.correct_responses\\.\\d')) { const parts = CMIElement.split('.'); const index = Number(parts[2]); const interaction = this.cmi.interactions.childArray[index]; @@ -181,13 +183,13 @@ export default class Scorm2004API extends BaseAPI { if (this.lastErrorCode === 0) { newChild = new CMIInteractionsCorrectResponsesObject(this); } - } else if (this.stringContains(CMIElement, '.objectives')) { + } else if (foundFirstIndex && this.stringMatches(CMIElement, 'cmi\\.interactions\\.\\d\\.objectives\\.\\d')) { newChild = new CMIInteractionsObjectivesObject(this); - } else if (this.stringContains(CMIElement, 'cmi.interactions')) { + } else if (this.stringMatches(CMIElement, 'cmi\\.interactions\\.\\d')) { newChild = new CMIInteractionsObject(this); - } else if (this.stringContains(CMIElement, 'cmi.comments_from_learner')) { + } else if (this.stringMatches(CMIElement, 'cmi\\.comments_from_learner\\.\\d')) { newChild = new CMICommentsFromLearnerObject(this); - } else if (this.stringContains(CMIElement, 'cmi.comments_from_lms')) { + } else if (this.stringMatches(CMIElement, 'cmi\\.comments_from_lms\\.\\d')) { newChild = new CMICommentsFromLMSObject(this); } diff --git a/src/cmi/common.js b/src/cmi/common.js index b83b62b..72f4298 100644 --- a/src/cmi/common.js +++ b/src/cmi/common.js @@ -59,6 +59,15 @@ export class BaseCMI { jsonString = false; #initialized = false; + /** + * Constructor for BaseCMI, just marks the class as abstract + */ + constructor() { + if (new.target === BaseCMI) { + throw new TypeError('Cannot construct BaseCMI instances directly'); + } + } + /** * Getter for #initialized * @return {boolean} @@ -101,22 +110,17 @@ export class CMIScore extends BaseCMI { }) { super(); - this.#_children = score_children ? - score_children : + this.#_children = score_children || scorm12_constants.score_children; this.#_score_range = !score_range ? false : scorm12_regex.score_range; this.#max = (max || max === '') ? max : '100'; - this.#_invalid_error_code = invalidErrorCode ? - invalidErrorCode : + this.#_invalid_error_code = invalidErrorCode || scorm12_error_codes.INVALID_SET_VALUE; - this.#_invalid_type_code = invalidTypeCode ? - invalidTypeCode : + this.#_invalid_type_code = invalidTypeCode || scorm12_error_codes.TYPE_MISMATCH; - this.#_invalid_range_code = invalidRangeCode ? - invalidRangeCode : + this.#_invalid_range_code = invalidRangeCode || scorm12_error_codes.VALUE_OUT_OF_RANGE; - this.#_decimal_regex = decimalRegex ? - decimalRegex : + this.#_decimal_regex = decimalRegex || scorm12_regex.CMIDecimal; } @@ -249,7 +253,6 @@ export class CMIArray extends BaseCMI { /** * Getter for _children * @return {*} - * @private */ get _children() { return this.#_children; @@ -258,7 +261,6 @@ export class CMIArray extends BaseCMI { /** * Setter for _children. Just throws an error. * @param {string} _children - * @private */ set _children(_children) { throw new ValidationError(this.#errorCode); @@ -267,7 +269,6 @@ export class CMIArray extends BaseCMI { /** * Getter for _count * @return {number} - * @private */ get _count() { return this.childArray.length; @@ -276,7 +277,6 @@ export class CMIArray extends BaseCMI { /** * Setter for _count. Just throws an error. * @param {number} _count - * @private */ set _count(_count) { throw new ValidationError(this.#errorCode); diff --git a/src/cmi/scorm12_cmi.js b/src/cmi/scorm12_cmi.js index e063efb..142854f 100644 --- a/src/cmi/scorm12_cmi.js +++ b/src/cmi/scorm12_cmi.js @@ -39,26 +39,35 @@ function throwInvalidValueError() { * Helper method, no reason to have to pass the same error codes every time * @param {*} value * @param {string} regexPattern + * @param {boolean} allowEmptyString * @return {boolean} */ -export function check12ValidFormat(value: String, regexPattern: String) { +export function check12ValidFormat( + value: String, + regexPattern: String, + allowEmptyString?: boolean) { return checkValidFormat(value, regexPattern, - scorm12_error_codes.TYPE_MISMATCH); + scorm12_error_codes.TYPE_MISMATCH, allowEmptyString); } /** * Helper method, no reason to have to pass the same error codes every time * @param {*} value * @param {string} rangePattern + * @param {boolean} allowEmptyString * @return {boolean} */ -export function check12ValidRange(value: any, rangePattern: String) { +export function check12ValidRange( + value: any, + rangePattern: String, + allowEmptyString?: boolean) { return checkValidRange(value, rangePattern, - scorm12_error_codes.VALUE_OUT_OF_RANGE); + scorm12_error_codes.VALUE_OUT_OF_RANGE, allowEmptyString); } /** * Class representing the cmi object for SCORM 1.2 + * @extends BaseCMI */ export class CMI extends BaseCMI { #_children = ''; @@ -138,7 +147,6 @@ export class CMI extends BaseCMI { /** * Getter for #_version * @return {string} - * @private */ get _version() { return this.#_version; @@ -147,7 +155,6 @@ export class CMI extends BaseCMI { /** * Setter for #_version. Just throws an error. * @param {string} _version - * @private */ set _version(_version) { throwInvalidValueError(); @@ -156,7 +163,6 @@ export class CMI extends BaseCMI { /** * Getter for #_children * @return {string} - * @private */ get _children() { return this.#_children; @@ -165,7 +171,6 @@ export class CMI extends BaseCMI { /** * Setter for #_version. Just throws an error. * @param {string} _children - * @private */ set _children(_children) { throwInvalidValueError(); @@ -244,6 +249,7 @@ export class CMI extends BaseCMI { /** * Class representing the cmi.core object + * @extends BaseCMI */ class CMICore extends BaseCMI { /** @@ -511,6 +517,7 @@ class CMICore extends BaseCMI { /** * Class representing SCORM 1.2's cmi.objectives object + * @extends CMIArray */ class CMIObjectives extends CMIArray { /** @@ -526,6 +533,7 @@ class CMIObjectives extends CMIArray { /** * Class representing SCORM 1.2's cmi.student_data object + * @extends BaseCMI */ export class CMIStudentData extends BaseCMI { #_children; @@ -642,6 +650,7 @@ export class CMIStudentData extends BaseCMI { /** * Class representing SCORM 1.2's cmi.student_preference object + * @extends BaseCMI */ class CMIStudentPreference extends BaseCMI { /** @@ -777,6 +786,7 @@ class CMIStudentPreference extends BaseCMI { /** * Class representing SCORM 1.2's cmi.interactions object + * @extends BaseCMI */ class CMIInteractions extends CMIArray { /** @@ -792,6 +802,7 @@ class CMIInteractions extends CMIArray { /** * Class representing SCORM 1.2's cmi.interactions.n object + * @extends BaseCMI */ export class CMIInteractionsObject extends BaseCMI { /** @@ -915,7 +926,7 @@ export class CMIInteractionsObject extends BaseCMI { * @param {string} student_response */ set student_response(student_response) { - if (check12ValidFormat(student_response, regex.CMIFeedback)) { + if (check12ValidFormat(student_response, regex.CMIFeedback, true)) { this.#student_response = student_response; } } @@ -993,6 +1004,7 @@ export class CMIInteractionsObject extends BaseCMI { /** * Class representing SCORM 1.2's cmi.objectives.n object + * @extends BaseCMI */ export class CMIObjectivesObject extends BaseCMI { /** @@ -1074,6 +1086,7 @@ export class CMIObjectivesObject extends BaseCMI { /** * Class representing SCORM 1.2's cmi.interactions.n.objectives.n object + * @extends BaseCMI */ export class CMIInteractionsObjectivesObject extends BaseCMI { /** @@ -1123,6 +1136,7 @@ export class CMIInteractionsObjectivesObject extends BaseCMI { /** * Class representing SCORM 1.2's cmi.interactions.correct_responses.n object + * @extends BaseCMI */ export class CMIInteractionsCorrectResponsesObject extends BaseCMI { /** @@ -1147,7 +1161,7 @@ export class CMIInteractionsCorrectResponsesObject extends BaseCMI { * @param {string} pattern */ set pattern(pattern) { - if (check12ValidFormat(pattern, regex.CMIFeedback)) { + if (check12ValidFormat(pattern, regex.CMIFeedback, true)) { this.#pattern = pattern; } } diff --git a/src/exceptions.js b/src/exceptions.js index c1dc089..981426b 100644 --- a/src/exceptions.js +++ b/src/exceptions.js @@ -15,14 +15,6 @@ export class ValidationError extends Error { #errorCode; - /** - * Getter for #errorCode - * @return {number} - */ - get errorCode() { - return this.#errorCode; - } - /** * Trying to override the default Error message * @return {string} diff --git a/test/Scorm12API.spec.js b/test/Scorm12API.spec.js index ac2aa1a..4973768 100644 --- a/test/Scorm12API.spec.js +++ b/test/Scorm12API.spec.js @@ -4,44 +4,245 @@ import Scorm12API from '../src/Scorm12API'; import * as h from './api_helpers'; import {scorm12_error_codes} from '../src/constants/error_codes'; +const api = () => { + const API = new Scorm12API(); + API.apiLogLevel = 1; + return API; +}; +const apiInitialized = () => { + const API = api(); + API.lmsInitialize(); + return API; +}; + describe('SCORM 1.2 API Tests', () => { - describe('Pre-Initialization', () => { - describe('LMSSetValue', () => { - const api = () => { - const API = new Scorm12API(); - API.apiLogLevel = 1; - return API; - }; - const apiInitialized = () => { - const API = api(); - API.lmsInitialize(); - return API; - }; - - describe('Should throw errors', () => { - h.checkWrite({ - api: api(), - fieldName: 'cmi.objectives.0.id', - expectedError: scorm12_error_codes.STORE_BEFORE_INIT, - }); - h.checkWrite({ - api: api(), - fieldName: 'cmi.interactions.0.id', - expectedError: scorm12_error_codes.STORE_BEFORE_INIT, - }); + describe('setCMIValue()', () => { + describe('Invalid Sets - Should Always Fail', () => { + h.checkSetCMIValue({ + api: api(), + fieldName: 'cmi._version', + expectedError: scorm12_error_codes.INVALID_SET_VALUE, }); + h.checkSetCMIValue({ + api: api(), + fieldName: 'cmi._children', + expectedError: scorm12_error_codes.INVALID_SET_VALUE, + }); + h.checkSetCMIValue({ + api: api(), + fieldName: 'cmi.core._children', + expectedError: scorm12_error_codes.INVALID_SET_VALUE, + }); + h.checkSetCMIValue({ + api: api(), + fieldName: 'cmi.core.score._children', + expectedError: scorm12_error_codes.INVALID_SET_VALUE, + }); + h.checkSetCMIValue({ + api: api(), + fieldName: 'cmi.objectives._children', + expectedError: scorm12_error_codes.INVALID_SET_VALUE, + }); + h.checkSetCMIValue({ + api: api(), + fieldName: 'cmi.objectives._count', + expectedError: scorm12_error_codes.INVALID_SET_VALUE, + }); + h.checkSetCMIValue({ + api: api(), + fieldName: 'cmi.interactions._children', + expectedError: scorm12_error_codes.INVALID_SET_VALUE, + }); + h.checkSetCMIValue({ + api: api(), + fieldName: 'cmi.interactions._count', + expectedError: scorm12_error_codes.INVALID_SET_VALUE, + }); + h.checkSetCMIValue({ + api: api(), + fieldName: 'cmi.interactions.0.objectives._count', + expectedError: scorm12_error_codes.INVALID_SET_VALUE, + }); + h.checkSetCMIValue({ + api: api(), + fieldName: 'cmi.interactions.0.correct_responses._count', + expectedError: scorm12_error_codes.INVALID_SET_VALUE, + }); + }); - describe('Should succeed', () => { - h.checkWrite({ - api: apiInitialized(), - fieldName: 'cmi.objectives.0.id', - valueToTest: 'AAA', - }); - h.checkWrite({ - api: apiInitialized(), - fieldName: 'cmi.interactions.0.id', - valueToTest: 'AAA', - }); + describe('Invalid Sets - Should Fail After Initialization', () => { + h.checkSetCMIValue({ + api: apiInitialized(), + fieldName: 'cmi.launch_data', + expectedError: scorm12_error_codes.READ_ONLY_ELEMENT, + }); + h.checkSetCMIValue({ + api: apiInitialized(), + fieldName: 'cmi.comments_from_lms', + expectedError: scorm12_error_codes.READ_ONLY_ELEMENT, + }); + h.checkSetCMIValue({ + api: apiInitialized(), + fieldName: 'cmi.core.student_id', + expectedError: scorm12_error_codes.READ_ONLY_ELEMENT, + }); + h.checkSetCMIValue({ + api: apiInitialized(), + fieldName: 'cmi.core.student_name', + expectedError: scorm12_error_codes.READ_ONLY_ELEMENT, + }); + h.checkSetCMIValue({ + api: apiInitialized(), + fieldName: 'cmi.core.credit', + expectedError: scorm12_error_codes.READ_ONLY_ELEMENT, + }); + h.checkSetCMIValue({ + api: apiInitialized(), + fieldName: 'cmi.core.entry', + expectedError: scorm12_error_codes.READ_ONLY_ELEMENT, + }); + h.checkSetCMIValue({ + api: apiInitialized(), + fieldName: 'cmi.core.total_time', + expectedError: scorm12_error_codes.READ_ONLY_ELEMENT, + }); + h.checkSetCMIValue({ + api: apiInitialized(), + fieldName: 'cmi.core.lesson_mode', + expectedError: scorm12_error_codes.READ_ONLY_ELEMENT, + }); + h.checkSetCMIValue({ + api: apiInitialized(), + fieldName: 'cmi.student_data.mastery_score', + expectedError: scorm12_error_codes.READ_ONLY_ELEMENT, + }); + h.checkSetCMIValue({ + api: apiInitialized(), + fieldName: 'cmi.student_data.max_time_allowed', + expectedError: scorm12_error_codes.READ_ONLY_ELEMENT, + }); + h.checkSetCMIValue({ + api: apiInitialized(), + fieldName: 'cmi.student_data.time_limit_action', + expectedError: scorm12_error_codes.READ_ONLY_ELEMENT, + }); + }); + }); + + describe('LMSGetValue()', () => { + describe('Invalid Properties - Should Always Fail', () => { + h.checkLMSGetValue({ + api: apiInitialized(), + fieldName: 'cmi.core.close', + expectedError: scorm12_error_codes.GENERAL, + errorThrown: false, + }); + h.checkLMSGetValue({ + api: apiInitialized(), + fieldName: 'cmi.exit', + expectedError: scorm12_error_codes.GENERAL, + errorThrown: false, + }); + h.checkLMSGetValue({ + api: apiInitialized(), + fieldName: 'cmi.entry', + expectedError: scorm12_error_codes.GENERAL, + errorThrown: false, + }); + }); + + describe('Write-Only Properties - Should Always Fail', () => { + h.checkLMSGetValue({ + api: apiInitialized(), + fieldName: 'cmi.core.exit', + expectedError: scorm12_error_codes.WRITE_ONLY_ELEMENT, + }); + h.checkLMSGetValue({ + api: apiInitialized(), + fieldName: 'cmi.core.session_time', + expectedError: scorm12_error_codes.WRITE_ONLY_ELEMENT, + }); + h.checkLMSGetValue({ + api: apiInitialized(), + fieldName: 'cmi.interactions.0.id', + initializeFirst: true, + initializationValue: 'AAA', + expectedError: scorm12_error_codes.WRITE_ONLY_ELEMENT, + }); + h.checkLMSGetValue({ + api: apiInitialized(), + fieldName: 'cmi.interactions.0.time', + initializeFirst: true, + initializationValue: '12:59:59', + expectedError: scorm12_error_codes.WRITE_ONLY_ELEMENT, + }); + h.checkLMSGetValue({ + api: apiInitialized(), + fieldName: 'cmi.interactions.0.type', + initializeFirst: true, + initializationValue: 'true-false', + expectedError: scorm12_error_codes.WRITE_ONLY_ELEMENT, + }); + h.checkLMSGetValue({ + api: apiInitialized(), + fieldName: 'cmi.interactions.0.weighting', + initializeFirst: true, + initializationValue: '0', + expectedError: scorm12_error_codes.WRITE_ONLY_ELEMENT, + }); + h.checkLMSGetValue({ + api: apiInitialized(), + fieldName: 'cmi.interactions.0.student_response', + initializeFirst: true, + expectedError: scorm12_error_codes.WRITE_ONLY_ELEMENT, + }); + h.checkLMSGetValue({ + api: apiInitialized(), + fieldName: 'cmi.interactions.0.result', + initializeFirst: true, + initializationValue: 'correct', + expectedError: scorm12_error_codes.WRITE_ONLY_ELEMENT, + }); + h.checkLMSGetValue({ + api: apiInitialized(), + fieldName: 'cmi.interactions.0.latency', + initializeFirst: true, + initializationValue: '01:59:59.99', + expectedError: scorm12_error_codes.WRITE_ONLY_ELEMENT, + }); + h.checkLMSGetValue({ + api: apiInitialized(), + fieldName: 'cmi.interactions.0.correct_responses.0.pattern', + initializeFirst: true, + expectedError: scorm12_error_codes.WRITE_ONLY_ELEMENT, + }); + }); + }); + + describe('LMSSetValue()', () => { + describe('Uninitialized - Should Fail', () => { + h.checkLMSSetValue({ + api: api(), + fieldName: 'cmi.objectives.0.id', + expectedError: scorm12_error_codes.STORE_BEFORE_INIT, + }); + h.checkLMSSetValue({ + api: api(), + fieldName: 'cmi.interactions.0.id', + expectedError: scorm12_error_codes.STORE_BEFORE_INIT, + }); + }); + + describe('Initialized - Should Succeed', () => { + h.checkLMSSetValue({ + api: apiInitialized(), + fieldName: 'cmi.objectives.0.id', + valueToTest: 'AAA', + }); + h.checkLMSSetValue({ + api: apiInitialized(), + fieldName: 'cmi.interactions.0.id', + valueToTest: 'AAA', }); }); }); diff --git a/test/api_helpers.js b/test/api_helpers.js index 07ba8f3..4c009e9 100644 --- a/test/api_helpers.js +++ b/test/api_helpers.js @@ -1,18 +1,83 @@ import {describe, it} from 'mocha'; import {expect} from 'chai'; -export const checkWrite = ( +export const checkLMSSetValue = ( { api, fieldName, valueToTest = 'xxx', expectedError = 0, + errorThrown = false, }) => { describe(`Field: ${fieldName}`, () => { const status = expectedError > 0 ? 'fail to' : 'successfully'; - it(`Should ${status} write to ${fieldName}`, () => { - eval(`api.lmsSetValue('${fieldName}', '${valueToTest}')`); - expect(String(api.lastErrorCode)).to.equal(String(expectedError)); + it(`Should ${status} set value for ${fieldName}`, () => { + if (errorThrown) { + expect(() => api.setValue(fieldName, valueToTest)). + to.throw(String(expectedError)); + } else { + api.setValue(fieldName, valueToTest); + expect(String(api.lmsGetLastError())).to.equal(String(expectedError)); + } + }); + }); +}; + +export const checkLMSGetValue = ( + { + api, + fieldName, + expectedValue = '', + initializeFirst = false, + initializationValue = '', + expectedError = 0, + errorThrown = true, + }) => { + describe(`Field: ${fieldName}`, () => { + const status = expectedError > 0 ? 'fail to' : 'successfully'; + + if (initializeFirst) { + api.setCMIValue(fieldName, initializationValue); + } + + it(`Should ${status} get value for ${fieldName}`, () => { + if (expectedError > 0) { + if (errorThrown) { + expect(() => api.lmsGetValue(fieldName)). + to.throw(String(expectedError)); + } else { + api.lmsGetValue(fieldName); + expect(String(api.lmsGetLastError())).to.equal(String(expectedError)); + } + } else { + expect(api.lmsGetValue(fieldName)).to.equal(expectedValue); + } + }); + }); +}; + +export const checkSetCMIValue = ( + { + api, + fieldName, + valueToTest = 'xxx', + expectedError = 0, + errorThrown = true, + }) => { + describe(`Field: ${fieldName}`, () => { + const status = expectedError > 0 ? 'fail to' : 'successfully'; + it(`Should ${status} set CMI value for ${fieldName}`, () => { + if (expectedError > 0) { + if (errorThrown) { + expect(() => api.setCMIValue(fieldName, valueToTest)). + to.throw(String(expectedError)); + } else { + api.setCMIValue(fieldName, valueToTest); + expect(String(api.lmsGetLastError())).to.equal(String(expectedError)); + } + } else { + expect(() => api.setCMIValue(fieldName, valueToTest)).to.not.throw(); + } }); }); };