From d2cc923f94f8443f50de2708b34d8bfc60b25481 Mon Sep 17 00:00:00 2001 From: Jonathan Putney Date: Tue, 12 Nov 2019 21:41:39 -0500 Subject: [PATCH] More CMI tests --- src/cmi/aicc_cmi.js | 148 +++++++++++++- src/cmi/common.js | 2 +- src/cmi/scorm12_cmi.js | 68 ++++--- src/regex.js | 30 +-- test/cmi/aicc_cmi.spec.js | 237 +++++++++++++++++++++- test/cmi/scorm12_cmi.spec.js | 376 +++++++++++++++++++++++++++++++++-- 6 files changed, 793 insertions(+), 68 deletions(-) diff --git a/src/cmi/aicc_cmi.js b/src/cmi/aicc_cmi.js index aba8ea9..a2b7308 100644 --- a/src/cmi/aicc_cmi.js +++ b/src/cmi/aicc_cmi.js @@ -3,10 +3,11 @@ import {BaseCMI, CMIArray, CMIScore} from './common'; import {aicc_constants} from '../constants/api_constants'; import {aicc_regex} from '../regex'; import {scorm12_error_codes} from '../constants/error_codes'; -import {ValidationError} from '../exceptions'; -import {check12ValidFormat} from './scorm12_cmi'; -import {throwReadOnlyError} from './scorm12_cmi'; -import {throwWriteOnlyError} from './scorm12_cmi'; +import { + check12ValidFormat, + throwReadOnlyError, + throwWriteOnlyError, +} from './scorm12_cmi'; const constants = aicc_constants; const regex = aicc_regex; @@ -36,6 +37,41 @@ export class CMI extends Scorm12CMI.CMI { this.student_data?.initialize(); this.evaluation?.initialize(); } + + /** + * toJSON for cmi + * + * @return { + * { + * suspend_data: string, + * launch_data: string, + * comments: string, + * comments_from_lms: string, + * core: CMICore, + * objectives: CMIObjectives, + * student_data: CMIStudentData, + * student_preference: CMIStudentPreference, + * interactions: CMIInteractions + * } + * } + */ + 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, + 'evaluation': this.evaluation, + }; + delete this.jsonString; + return result; + } } /** @@ -58,6 +94,19 @@ class CMIEvaluation extends BaseCMI { super.initialize(); this.comments?.initialize(); } + + /** + * toJSON for cmi.evaluation object + * @return {{comments: CMIEvaluationComments}} + */ + toJSON() { + this.jsonString = true; + const result = { + 'comments': this.comments, + }; + delete this.jsonString; + return result; + } } /** @@ -114,6 +163,29 @@ class AICCCMIStudentData extends Scorm12CMI.CMIStudentData { this.#tries_during_lesson = tries_during_lesson : throwReadOnlyError(); } + + /** + * toJSON for cmi.student_data object + * @return { + * { + * mastery_score: string, + * max_time_allowed: string, + * time_limit_action: string, + * tries: CMITries + * } + * } + */ + 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, + 'tries': this.tries, + }; + delete this.jsonString; + return result; + } } /** @@ -138,7 +210,14 @@ export class CMITriesObject extends BaseCMI { constructor() { super(); - this.score = new CMIScore(); + this.score = new CMIScore( + { + score_children: constants.score_children, + score_range: regex.score_range, + invalidErrorCode: scorm12_error_codes.INVALID_SET_VALUE, + invalidTypeCode: scorm12_error_codes.TYPE_MISMATCH, + invalidRangeCode: scorm12_error_codes.VALUE_OUT_OF_RANGE, + }); } /** @@ -187,6 +266,27 @@ export class CMITriesObject extends BaseCMI { this.#time = time; } } + + /** + * toJSON for cmi.student_data.tries.n object + * @return { + * { + * status: string, + * time: string, + * score: CMIScore + * } + * } + */ + toJSON() { + this.jsonString = true; + const result = { + 'status': this.status, + 'time': this.time, + 'score': this.score, + }; + delete this.jsonString; + return result; + } } /** @@ -257,6 +357,27 @@ export class CMIEvaluationCommentsObject extends BaseCMI { this.#time = time; } } + + /** + * toJSON for cmi.evaulation.comments.n object + * @return { + * { + * content: string, + * location: string, + * time: string + * } + * } + */ + toJSON() { + this.jsonString = true; + const result = { + 'content': this.content, + 'location': this.location, + 'time': this.time, + }; + delete this.jsonString; + return result; + } } /** @@ -289,4 +410,21 @@ export class NAV extends BaseCMI { this.#event = event; } } + + /** + * toJSON for nav object + * @returns { + * { + * event: string + * } + * } + */ + toJSON() { + this.jsonString = true; + const result = { + 'event': this.event, + }; + delete this.jsonString; + return result; + } } diff --git a/src/cmi/common.js b/src/cmi/common.js index d1d3c70..1a462a8 100644 --- a/src/cmi/common.js +++ b/src/cmi/common.js @@ -96,7 +96,7 @@ export class CMIScore extends BaseCMI { this.#_children = score_children ? score_children : scorm12_constants.score_children; - this.#_score_range = score_range ? score_range : false; + this.#_score_range = !score_range ? false : scorm12_regex.score_range; this.#max = max ? max : '100'; this.#_invalid_error_code = invalidErrorCode ? invalidErrorCode : diff --git a/src/cmi/scorm12_cmi.js b/src/cmi/scorm12_cmi.js index 2a98e91..2941bef 100644 --- a/src/cmi/scorm12_cmi.js +++ b/src/cmi/scorm12_cmi.js @@ -236,7 +236,9 @@ export class CMI extends BaseCMI { * @param {string} comments_from_lms */ set comments_from_lms(comments_from_lms) { - !this.initialized ? this.#comments_from_lms = comments_from_lms : throwReadOnlyError(); + !this.initialized ? + this.#comments_from_lms = comments_from_lms : + throwReadOnlyError(); } } @@ -250,7 +252,7 @@ class CMICore extends BaseCMI { constructor() { super(); - this.score = new Scorm12CMIScore( + this.score = new CMIScore( { score_children: constants.score_children, score_range: regex.score_range, @@ -327,7 +329,9 @@ class CMICore extends BaseCMI { * @param {string} student_name */ set student_name(student_name) { - !this.initialized ? this.#student_name = student_name : throwReadOnlyError(); + !this.initialized ? + this.#student_name = student_name : + throwReadOnlyError(); } /** @@ -474,7 +478,7 @@ class CMICore extends BaseCMI { * student_name: string, * entry: string, * exit: string, - * score: Scorm12CMIScore, + * score: CMIScore, * student_id: string, * lesson_mode: string, * lesson_location: string, @@ -572,7 +576,9 @@ export class CMIStudentData extends BaseCMI { * @param {string} mastery_score */ set mastery_score(mastery_score) { - !this.initialized ? this.#mastery_score = mastery_score : throwReadOnlyError(); + !this.initialized ? + this.#mastery_score = mastery_score : + throwReadOnlyError(); } /** @@ -588,7 +594,9 @@ export class CMIStudentData extends BaseCMI { * @param {string} max_time_allowed */ set max_time_allowed(max_time_allowed) { - !this.initialized ? this.#max_time_allowed = max_time_allowed : throwReadOnlyError(); + !this.initialized ? + this.#max_time_allowed = max_time_allowed : + throwReadOnlyError(); } /** @@ -604,7 +612,9 @@ export class CMIStudentData extends BaseCMI { * @param {string} time_limit_action */ set time_limit_action(time_limit_action) { - !this.initialized ? this.#time_limit_action = time_limit_action : throwReadOnlyError(); + !this.initialized ? + this.#time_limit_action = time_limit_action : + throwReadOnlyError(); } /** @@ -809,13 +819,13 @@ export class CMIInteractionsObject extends BaseCMI { this.correct_responses?.initialize(); } - #id: ''; - #time: ''; - #type: ''; - #weighting: ''; - #student_response: ''; - #result: ''; - #latency: ''; + #id = ''; + #time = ''; + #type = ''; + #weighting = ''; + #student_response = ''; + #result = ''; + #latency = ''; /** * Getter for #id. Should only be called during JSON export. @@ -991,11 +1001,18 @@ export class CMIObjectivesObject extends BaseCMI { constructor() { super(); - this.score = new Scorm12CMIScore(); + this.score = new CMIScore( + { + score_children: constants.score_children, + score_range: regex.score_range, + invalidErrorCode: scorm12_error_codes.INVALID_SET_VALUE, + invalidTypeCode: scorm12_error_codes.TYPE_MISMATCH, + invalidRangeCode: scorm12_error_codes.VALUE_OUT_OF_RANGE, + }); } - #id: ''; - #status: ''; + #id = ''; + #status = ''; /** * Getter for #id @@ -1039,7 +1056,7 @@ export class CMIObjectivesObject extends BaseCMI { * { * id: string, * status: string, - * score: Scorm12CMIScore + * score: CMIScore * } * } */ @@ -1066,7 +1083,7 @@ export class CMIInteractionsObjectivesObject extends BaseCMI { super(); } - #id: ''; + #id = ''; /** * Getter for #id @@ -1115,14 +1132,14 @@ export class CMIInteractionsCorrectResponsesObject extends BaseCMI { super(); } - #pattern: ''; + #pattern = ''; /** * Getter for #pattern - * @return {""} + * @return {string} */ get pattern() { - return this.#pattern; + return (!this.jsonString) ? throwWriteOnlyError() : this.#pattern; } /** @@ -1152,10 +1169,3 @@ export class CMIInteractionsCorrectResponsesObject extends BaseCMI { return result; } } - -/** - * Basic extension of CMIScore - */ -class Scorm12CMIScore extends CMIScore { - toJSON = super.toJSON; -} diff --git a/src/regex.js b/src/regex.js index 47359b0..350ba41 100644 --- a/src/regex.js +++ b/src/regex.js @@ -3,7 +3,7 @@ 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})?$', // eslint-disable-line + CMITime: '^(?:[01]\\d|2[0123]):(?:[012345]\\d):(?:[012345]\\d)$', // eslint-disable-line CMITimespan: '^([0-9]{2,}):([0-9]{2}):([0-9]{2})(\.[0-9]{1,2})?$', // eslint-disable-line CMIInteger: '^\\d+$', CMISInteger: '^-?([0-9]+)$', @@ -13,12 +13,12 @@ export const scorm12_regex = { 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]*)?$', // eslint-disable-line - NAVEvent: '^previous$|^continue$', + 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]*)?)$', // eslint-disable-line + NAVEvent: '^(previous|continue)$', // Data ranges score_range: '0#100', @@ -58,14 +58,14 @@ export const scorm2004_regex = { 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$', // eslint-disable-line - NAVBoolean: '^unknown$|^true$|^false$', - NAVTarget: '^previous$|^continue$|^choice.{target=\\S{0,200}[a-zA-Z0-9]}$', + 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)$', // eslint-disable-line + NAVBoolean: '^(unknown|true|false$)', + NAVTarget: '^(previous|continue|choice.{target=\\S{0,200}[a-zA-Z0-9]})$', // Data ranges scaled_range: '-1#1', diff --git a/test/cmi/aicc_cmi.spec.js b/test/cmi/aicc_cmi.spec.js index e6a2c24..e263b9a 100644 --- a/test/cmi/aicc_cmi.spec.js +++ b/test/cmi/aicc_cmi.spec.js @@ -1,8 +1,18 @@ -import {describe} from 'mocha'; +import {describe, it} from 'mocha'; import {aicc_constants} from '../../src/constants/api_constants'; import {scorm12_error_codes} from '../../src/constants/error_codes'; -import {CMI} from '../../src/cmi/aicc_cmi'; +import { + CMI, + CMIEvaluationCommentsObject, + CMITriesObject, NAV, +} from '../../src/cmi/aicc_cmi'; import * as h from '../helpers'; +import { + CMIInteractionsCorrectResponsesObject, + CMIInteractionsObject, + CMIObjectivesObject, +} from '../../src/cmi/scorm12_cmi'; +import {expect} from 'chai'; const invalid_set = scorm12_error_codes.INVALID_SET_VALUE; const type_mismatch = scorm12_error_codes.TYPE_MISMATCH; @@ -707,6 +717,229 @@ describe('AICC CMI Tests', () => { fieldName: 'cmi.interactions._count', expectedValue: 0, expectedError: invalid_set, }); + + it('should export JSON', () => { + const cmiObj = cmi(); + cmiObj.objectives.childArray.push(new CMIObjectivesObject()); + cmiObj.interactions.childArray.push(new CMIInteractionsObject()); + cmiObj.evaluation.comments.childArray.push( + new CMIEvaluationCommentsObject()); + cmiObj.student_data.tries.childArray.push(new CMITriesObject()); + expect( + JSON.stringify(cmiObj), + ). + to. + equal( + '{"suspend_data":"","launch_data":"","comments":"","comments_from_lms":"","core":{"student_id":"","student_name":"","lesson_location":"","credit":"","lesson_status":"","entry":"","total_time":"","lesson_mode":"normal","exit":"","session_time":"00:00:00","score":{"raw":"","min":"","max":"100"}},"objectives":{"0":{"id":"","status":"","score":{"raw":"","min":"","max":"100"}}},"student_data":{"mastery_score":"","max_time_allowed":"","time_limit_action":"","tries":{"0":{"status":"","time":"","score":{"raw":"","min":"","max":"100"}}}},"student_preference":{"audio":"","language":"","speed":"","text":""},"interactions":{"0":{"id":"","time":"","type":"","weighting":"","student_response":"","result":"","latency":"","objectives":{},"correct_responses":{}}},"evaluation":{"comments":{"0":{"content":"","location":"","time":""}}}}'); + }); + }); + + describe('CMITriesObject Tests', () => { + const triesObject = () => { + return new CMITriesObject(); + }; + const triesInitialized = () => { + const cmi = new CMITriesObject(); + cmi.initialize(); + return cmi; + }; + + /** + * cmi.interactions.n.objectives.n object + */ + h.checkRead({ + cmi: triesObject(), + fieldName: 'cmi.status', + }); + h.checkRead({ + cmi: triesInitialized(), + fieldName: 'cmi.status', + }); + h.checkValidValues({ + cmi: triesObject(), + fieldName: 'cmi.status', + validValues: [ + 'passed', + 'completed', + 'failed', + 'incomplete', + 'browsed', + 'not attempted', + ], + invalidValues: [ + 'P', + 'f', + 'complete', + 'started', + 'in progress', + ], + }); + h.checkReadAndWrite({ + cmi: triesObject(), + fieldName: 'cmi.time', + expectedError: write_only, + valueToTest: '23:59:59', + }); + h.checkValidValues({ + cmi: triesObject(), + fieldName: 'cmi.time', + validValues: [ + '15:00:30', + '00:50:30', + '23:00:30', + ], + invalidValues: [ + '-00:00:30', + '0:50:30', + '23:00:30.', + ], + }); + + /** + * cmi.student_data.tries.n.score Properties + */ + h.checkInvalidSet({ + cmi: triesObject(), + fieldName: 'cmi.score._children', + expectedValue: aicc_constants.score_children, + expectedError: invalid_set, + }); + h.checkValidValues({ + cmi: triesObject(), + fieldName: 'cmi.score.raw', + validValues: [ + '0', + '25.1', + '50.5', + '75', + '100', + ], + invalidValues: [ + '-1', + '101', + ], + }); + h.checkValidValues({ + cmi: triesObject(), + fieldName: 'cmi.score.min', + validValues: [ + '0', + '25.1', + '50.5', + '75', + '100', + ], + invalidValues: [ + '-1', + '101', + ], + }); + h.checkValidValues({ + cmi: triesObject(), + fieldName: 'cmi.score.max', + validValues: [ + '0', + '25.1', + '50.5', + '75', + '100', + ], + invalidValues: [ + '-1', + '101', + ], + }); + + it('should export JSON', () => { + const cmi = triesObject(); + expect( + JSON.stringify(cmi), + ). + to. + equal( + '{"status":"","time":"","score":{"raw":"","min":"","max":"100"}}'); + }); + }); + + describe('CMIEvaluationCommentsObject Tests', () => { + const evaluationComment = () => { + return new CMIEvaluationCommentsObject(); + }; + + /** + * cmi.evaluation.comments.n object + */ + h.checkFieldConstraintSize({ + cmi: evaluationComment(), + fieldName: 'cmi.content', + expectedError: type_mismatch, + limit: 255, + }); + h.checkFieldConstraintSize({ + cmi: evaluationComment(), + fieldName: 'cmi.location', + expectedError: type_mismatch, + limit: 255, + }); + h.checkReadAndWrite({ + cmi: evaluationComment(), + fieldName: 'cmi.time', + expectedError: write_only, + valueToTest: '23:59:59', + }); + h.checkValidValues({ + cmi: evaluationComment(), + fieldName: 'cmi.time', + validValues: [ + '15:00:30', + '00:50:30', + '23:00:30', + ], + invalidValues: [ + '-00:00:30', + '0:50:30', + '23:00:30.', + ], + }); + + it('should export JSON', () => { + const cmi = evaluationComment(); + expect( + JSON.stringify(cmi), + ).to.equal('{"content":"","location":"","time":""}'); + }); + }); + + describe('NAV Tests', () => { + const nav = () => { + return new NAV(); + }; + + /** + * cmi.interactions.n.correct_responses.n object + */ + h.checkValidValues({ + cmi: nav(), + fieldName: 'cmi.event', + validValues: [ + 'previous', + 'continue', + ], + invalidValues: [ + 'P', + 'f', + 'complete', + 'started', + 'in progress', + ], + }); + + it('should export JSON', () => { + const cmi = nav(); + expect( + JSON.stringify(cmi), + ).to.equal('{"event":""}'); + }); }); }); }); diff --git a/test/cmi/scorm12_cmi.spec.js b/test/cmi/scorm12_cmi.spec.js index 4fa0dfc..e7d42ed 100644 --- a/test/cmi/scorm12_cmi.spec.js +++ b/test/cmi/scorm12_cmi.spec.js @@ -1,7 +1,14 @@ -import {describe, beforeEach, afterEach} from 'mocha'; +import {expect} from 'chai'; +import {describe, it} from 'mocha'; import {scorm12_constants} from '../../src/constants/api_constants'; import {scorm12_error_codes} from '../../src/constants/error_codes'; -import {CMI} from '../../src/cmi/scorm12_cmi'; +import { + CMI, + CMIInteractionsCorrectResponsesObject, + CMIInteractionsObject, + CMIInteractionsObjectivesObject, + CMIObjectivesObject, +} from '../../src/cmi/scorm12_cmi'; import * as h from '../helpers'; const invalid_set = scorm12_error_codes.INVALID_SET_VALUE; @@ -164,6 +171,10 @@ describe('SCORM 1.2 CMI Tests', () => { expectedValue: scorm12_constants.score_children, expectedError: invalid_set, }); + h.checkRead({ + cmi: cmi(), + fieldName: 'cmi.core.score.raw', + }); h.checkValidValues({ cmi: cmi(), fieldName: 'cmi.core.score.raw', @@ -179,6 +190,10 @@ describe('SCORM 1.2 CMI Tests', () => { '101', ], }); + h.checkRead({ + cmi: cmi(), + fieldName: 'cmi.core.score.min', + }); h.checkValidValues({ cmi: cmi(), fieldName: 'cmi.core.score.min', @@ -194,6 +209,11 @@ describe('SCORM 1.2 CMI Tests', () => { '101', ], }); + h.checkRead({ + cmi: cmi(), + fieldName: 'cmi.core.score.max', + expectedValue: '100', + }); h.checkValidValues({ cmi: cmi(), fieldName: 'cmi.core.score.max', @@ -257,6 +277,10 @@ describe('SCORM 1.2 CMI Tests', () => { expectedValue: scorm12_constants.student_preference_children, expectedError: invalid_set, }); + h.checkRead({ + cmi: cmi(), + fieldName: 'cmi.student_preference.audio', + }); h.checkValidValues({ cmi: cmi(), fieldName: 'cmi.student_preference.audio', @@ -269,13 +293,6 @@ describe('SCORM 1.2 CMI Tests', () => { invalidValues: [ 'invalid', 'a100', - ], - }); - h.checkValidValues({ - cmi: cmi(), - fieldName: 'cmi.student_preference.audio', - validValues: [], - invalidValues: [ '101', '5000000', '-500', @@ -287,6 +304,10 @@ describe('SCORM 1.2 CMI Tests', () => { limit: 255, expectedError: type_mismatch, }); + h.checkRead({ + cmi: cmi(), + fieldName: 'cmi.student_preference.speed', + }); h.checkValidValues({ cmi: cmi(), fieldName: 'cmi.student_preference.speed', @@ -299,19 +320,16 @@ describe('SCORM 1.2 CMI Tests', () => { invalidValues: [ 'invalid', 'a100', - ], - }); - h.checkValidValues({ - cmi: cmi(), - fieldName: 'cmi.student_preference.speed', - validValues: [], - invalidValues: [ '101', '-101', '5000000', '-500', ], }); + h.checkRead({ + cmi: cmi(), + fieldName: 'cmi.student_preference.text', + }); h.checkValidValues({ cmi: cmi(), fieldName: 'cmi.student_preference.text', @@ -702,6 +720,332 @@ describe('SCORM 1.2 CMI Tests', () => { expectedValue: 0, expectedError: invalid_set, }); + + it('should export JSON', () => { + const cmiObj = cmi(); + cmiObj.objectives.childArray.push(new CMIObjectivesObject()); + cmiObj.interactions.childArray.push(new CMIInteractionsObject()); + expect( + JSON.stringify(cmiObj), + ).to.equal('{"suspend_data":"","launch_data":"","comments":"","comments_from_lms":"","core":{"student_id":"","student_name":"","lesson_location":"","credit":"","lesson_status":"","entry":"","total_time":"","lesson_mode":"normal","exit":"","session_time":"00:00:00","score":{"raw":"","min":"","max":"100"}},"objectives":{"0":{"id":"","status":"","score":{"raw":"","min":"","max":"100"}}},"student_data":{"mastery_score":"","max_time_allowed":"","time_limit_action":""},"student_preference":{"audio":"","language":"","speed":"","text":""},"interactions":{"0":{"id":"","time":"","type":"","weighting":"","student_response":"","result":"","latency":"","objectives":{},"correct_responses":{}}}}'); + }); + }); + + describe('CMIInteractionsObject Tests', () => { + const interaction = () => { + return new CMIInteractionsObject(); + }; + const interactionInitialized = () => { + const cmi = new CMIInteractionsObject(); + cmi.initialize(); + return cmi; + }; + + /** + * cmi.interactions.n object + */ + h.checkWriteOnly({ + cmi: interaction(), + fieldName: 'cmi.id', + expectedError: write_only, + }); + h.checkWriteOnly({ + cmi: interactionInitialized(), + fieldName: 'cmi.id', + expectedError: write_only, + }); + h.checkWriteOnly({ + cmi: interaction(), + fieldName: 'cmi.time', + expectedError: write_only, + valueToTest: '23:59:59', + }); + h.checkValidValues({ + cmi: interaction(), + fieldName: 'cmi.time', + validValues: [ + '15:00:30', + '00:50:30', + '23:00:30', + ], + invalidValues: [ + '-00:00:30', + '0:50:30', + '23:00:30.', + ], + }); + h.checkWriteOnly({ + cmi: interaction(), + fieldName: 'cmi.type', + expectedError: write_only, + valueToTest: 'true-false', + }); + h.checkValidValues({ + cmi: interaction(), + fieldName: 'cmi.type', + validValues: [ + 'true-false', + 'choice', + 'fill-in', + 'matching', + 'performance', + 'sequencing', + 'likert', + 'numeric', + ], + invalidValues: [ + 'correct', + 'wrong', + 'logout', + ], + }); + h.checkReadOnly({ + cmi: interaction(), + fieldName: 'cmi.objectives._count', + expectedValue: 0, + expectedError: invalid_set, + }); + h.checkReadOnly({ + cmi: interaction(), + fieldName: 'cmi.correct_responses._count', + expectedValue: 0, + expectedError: invalid_set, + }); + h.checkWriteOnly({ + cmi: interaction(), + fieldName: 'cmi.weighting', + expectedError: write_only, + valueToTest: '0', + }); + h.checkValidValues({ + cmi: interaction(), + fieldName: 'cmi.weighting', + validValues: [ + '-100', + '-1', + '1', + '100', + ], + invalidValues: [ + '-101', + '101', + 'invalid', + ], + }); + h.checkWriteOnly({ + cmi: interaction(), + fieldName: 'cmi.student_response', + expectedError: write_only, + }); + h.checkWriteOnly({ + cmi: interaction(), + fieldName: 'cmi.result', + expectedError: write_only, + valueToTest: 'correct', + }); + h.checkValidValues({ + cmi: interaction(), + fieldName: 'cmi.result', + validValues: [ + 'correct', + 'wrong', + 'unanticipated', + 'neutral', + '1', + '999', + '999.99999', + ], + invalidValues: [ + '-1', + '10000', + 'invalid', + ], + }); + h.checkWriteOnly({ + cmi: interaction(), + fieldName: 'cmi.latency', + valueToTest: '00:00:00', + expectedError: write_only, + }); + h.checkValidValues({ + cmi: interaction(), + fieldName: 'cmi.latency', + validValues: [ + '10:06:57', + '00:00:01.56', + '23:59:59', + '47:59:59', + ], + invalidValues: [ + '06:5:13', + '23:59:59.123', + 'P1DT23H59M59S', + ], + }); + + it('should export JSON', () => { + const cmi = interaction(); + cmi.objectives.childArray.push(new CMIInteractionsObjectivesObject()); + cmi.correct_responses.childArray.push(new CMIInteractionsCorrectResponsesObject()); + expect( + JSON.stringify(cmi), + ).to.equal('{"id":"","time":"","type":"","weighting":"","student_response":"","result":"","latency":"","objectives":{"0":{"id":""}},"correct_responses":{"0":{"pattern":""}}}'); + }); + }); + + describe('CMIInteractionsObjectivesObject Tests', () => { + const interactionObjective = () => { + return new CMIInteractionsObjectivesObject(); + }; + + /** + * cmi.interactions.n.objectives.n object + */ + h.checkReadAndWrite({ + cmi: interactionObjective(), + fieldName: 'cmi.id', + }); + + it('should export JSON', () => { + const cmi = interactionObjective(); + expect( + JSON.stringify(cmi), + ).to.equal('{"id":""}'); + }); + }); + + describe('CMIInteractionsCorrectResponsesObject Tests', () => { + const correctResponse = () => { + return new CMIInteractionsCorrectResponsesObject(); + }; + + /** + * cmi.interactions.n.correct_responses.n object + */ + h.checkWriteOnly({ + cmi: correctResponse(), + fieldName: 'cmi.pattern', + expectedError: write_only, + }); + + it('should export JSON', () => { + const cmi = correctResponse(); + expect( + JSON.stringify(cmi), + ).to.equal('{"pattern":""}'); + }); + }); + + describe('CMIObjectivesObject Tests', () => { + const objective = () => { + return new CMIObjectivesObject(); + }; + + /** + * cmi.objectives.n object + */ + h.checkReadAndWrite({ + cmi: objective(), + fieldName: 'cmi.id', + }); + h.checkRead({ + cmi: objective(), + fieldName: 'cmi.status', + }); + h.checkValidValues({ + cmi: objective(), + fieldName: 'cmi.status', + validValues: [ + 'passed', + 'completed', + 'failed', + 'incomplete', + 'browsed', + 'not attempted', + ], + invalidValues: [ + 'P', + 'f', + 'complete', + 'started', + 'in progress', + ], + }); + + /** + * cmi.objectives.n.score Properties + */ + h.checkInvalidSet({ + cmi: objective(), + fieldName: 'cmi.score._children', + expectedValue: scorm12_constants.score_children, + expectedError: invalid_set, + }); + h.checkRead({ + cmi: objective(), + fieldName: 'cmi.score.raw', + }); + h.checkValidValues({ + cmi: objective(), + fieldName: 'cmi.score.raw', + validValues: [ + '0', + '25.1', + '50.5', + '75', + '100', + ], + invalidValues: [ + '-1', + '101', + ], + }); + h.checkRead({ + cmi: objective(), + fieldName: 'cmi.score.min', + }); + h.checkValidValues({ + cmi: objective(), + fieldName: 'cmi.score.min', + validValues: [ + '0', + '25.1', + '50.5', + '75', + '100', + ], + invalidValues: [ + '-1', + '101', + ], + }); + h.checkRead({ + cmi: objective(), + fieldName: 'cmi.score.max', + expectedValue: '100', + }); + h.checkValidValues({ + cmi: objective(), + fieldName: 'cmi.score.max', + validValues: [ + '0', + '25.1', + '50.5', + '75', + '100', + ], + invalidValues: [ + '-1', + '101', + ], + }); + + it('should export JSON', () => { + const cmi = objective(); + expect( + JSON.stringify(cmi), + ).to.equal('{"id":"","status":"","score":{"raw":"","min":"","max":"100"}}'); + }); }); }); });