UNPKG

scorm-again

Version:

A modern SCORM JavaScript run-time library for AICC, SCORM 1.2, and SCORM 2004

1,181 lines (1,079 loc) 42.4 kB
import { expect } from "expect"; import { after, before, describe, it } from "mocha"; import { Scorm12API } from "../src/Scorm12API"; import * as h from "./api_helpers"; import { scorm12_errors } from "../src/constants/error_codes"; import { scorm12Values } from "./field_values"; import * as sinon from "sinon"; import Pretender from "fetch-pretender"; import { RefObject, Settings } from "../src/types/api_types"; import { DefaultSettings } from "../src/constants/default_settings"; let clock: sinon.SinonFakeTimers; const api = (settings?: Settings, startingData: RefObject = {}) => { const API = new Scorm12API(settings); API.apiLogLevel = 5; API.startingData = startingData; return API; }; const apiInitialized = (settings?: Settings, startingData: RefObject = {}) => { const API = api(settings); API.loadFromJSON(startingData ? startingData : {}); API.lmsInitialize(); return API; }; describe("SCORM 1.2 API Tests", () => { before(() => { clock = sinon.useFakeTimers(); const server = new Pretender(); server.post( "/scorm12", () => { return [200, { "Content-Type": "application/json" }, "{}"]; }, false, ); server.post( "/scorm12/error", () => { return [500, { "Content-Type": "application/json" }, "{}"]; }, false, ); }); after(() => { clock.restore(); }); describe("loadFromJSON()", () => { it("should load JSON data into the CMI object", () => { const scorm12API = api(); const jsonData = { cmi: { core: { student_id: "student_1", student_name: "John Doe", lesson_status: "incomplete", }, }, }; scorm12API.loadFromJSON(jsonData); scorm12API.lmsInitialize(); expect(scorm12API.cmi.core.student_id).toEqual("student_1"); expect(scorm12API.cmi.core.student_name).toEqual("John Doe"); expect(scorm12API.cmi.core.lesson_status).toEqual("incomplete"); }); it("should load nested JSON data into the CMI object", () => { const scorm12API = api(); const jsonData = { cmi: { objectives: { "0": { id: "obj_1", score: { raw: "85", min: "0", max: "100", }, }, }, }, }; scorm12API.loadFromJSON(jsonData); expect(scorm12API.cmi.objectives.childArray[0].id).toEqual("obj_1"); expect(scorm12API.cmi.objectives.childArray[0].score.raw).toEqual("85"); expect(scorm12API.cmi.objectives.childArray[0].score.min).toEqual("0"); expect(scorm12API.cmi.objectives.childArray[0].score.max).toEqual("100"); }); it("should load nested cmi JSON data into the CMI object", () => { const scorm12API = api(); const jsonData = { objectives: { "0": { id: "obj_1", score: { raw: "85", min: "0", max: "100", }, }, }, }; scorm12API.loadFromJSON(jsonData, "cmi"); expect(scorm12API.cmi.objectives.childArray[0].id).toEqual("obj_1"); expect(scorm12API.cmi.objectives.childArray[0].score.raw).toEqual("85"); expect(scorm12API.cmi.objectives.childArray[0].score.min).toEqual("0"); expect(scorm12API.cmi.objectives.childArray[0].score.max).toEqual("100"); }); it("should load nested JSON data into the CMI object in a backwards compatible way with v1", () => { const scorm12API = api(); const jsonData = { objectives: { "0": { id: "obj_1", score: { raw: "85", min: "0", max: "100", }, }, }, }; scorm12API.loadFromJSON(jsonData); expect(scorm12API.cmi.objectives.childArray[0].id).toEqual("obj_1"); expect(scorm12API.cmi.objectives.childArray[0].score.raw).toEqual("85"); expect(scorm12API.cmi.objectives.childArray[0].score.min).toEqual("0"); expect(scorm12API.cmi.objectives.childArray[0].score.max).toEqual("100"); }); it("should handle empty JSON data", () => { const scorm12API = api(); const jsonData = {}; scorm12API.loadFromJSON(jsonData); expect(scorm12API.cmi.core.student_id).toBeFalsy(); expect(scorm12API.cmi.core.student_name).toBeFalsy(); expect(scorm12API.cmi.core.lesson_status).toBe("not attempted"); }); it("should not load data if API is initialized", () => { const scorm12API = apiInitialized(); const jsonData = { cmi: { core: { student_id: "student_1", student_name: "John Doe", lesson_status: "incomplete", }, }, }; scorm12API.loadFromJSON(jsonData); expect(scorm12API.cmi.core.student_id).toBeFalsy(); expect(scorm12API.cmi.core.student_name).toBeFalsy(); expect(scorm12API.cmi.core.lesson_status).toBe("not attempted"); }); }); describe("LMSSetValue()", () => { h.checkValidValues({ api: apiInitialized(), fieldName: "cmi.core.score.raw", validValues: scorm12Values.validScoreRange, invalidValues: scorm12Values.invalidScoreRange, }); h.checkValidValues({ api: apiInitialized(), fieldName: "cmi.core.score.min", validValues: scorm12Values.validScoreRange, invalidValues: scorm12Values.invalidScoreRange, }); h.checkValidValues({ api: apiInitialized(), fieldName: "cmi.core.score.max", validValues: scorm12Values.validScoreRange, invalidValues: scorm12Values.invalidScoreRange, }); }); describe("setCMIValue()", () => { describe("Invalid Sets - Should Always Fail", () => { h.checkSetCMIValue({ api: api(), fieldName: "cmi._version", expectedError: scorm12_errors.INVALID_SET_VALUE, }); h.checkSetCMIValue({ api: api(), fieldName: "cmi._children", expectedError: scorm12_errors.INVALID_SET_VALUE, }); h.checkSetCMIValue({ api: api(), fieldName: "cmi.core._children", expectedError: scorm12_errors.INVALID_SET_VALUE, }); h.checkSetCMIValue({ api: api(), fieldName: "cmi.core.score._children", expectedError: scorm12_errors.INVALID_SET_VALUE, }); h.checkSetCMIValue({ api: api(), fieldName: "cmi.objectives._children", expectedError: scorm12_errors.INVALID_SET_VALUE, }); h.checkSetCMIValue({ api: api(), fieldName: "cmi.objectives._count", expectedError: scorm12_errors.INVALID_SET_VALUE, }); h.checkSetCMIValue({ api: api(), fieldName: "cmi.interactions._children", expectedError: scorm12_errors.INVALID_SET_VALUE, }); h.checkSetCMIValue({ api: api(), fieldName: "cmi.interactions._count", expectedError: scorm12_errors.INVALID_SET_VALUE, }); h.checkSetCMIValue({ api: api(), fieldName: "cmi.interactions.0.objectives._count", expectedError: scorm12_errors.INVALID_SET_VALUE, }); h.checkSetCMIValue({ api: api(), fieldName: "cmi.interactions.0.correct_responses._count", expectedError: scorm12_errors.INVALID_SET_VALUE, }); }); describe("Invalid Sets - Should Fail After Initialization", () => { h.checkSetCMIValue({ api: apiInitialized(), fieldName: "cmi.launch_data", expectedError: scorm12_errors.READ_ONLY_ELEMENT, }); h.checkSetCMIValue({ api: apiInitialized(), fieldName: "cmi.comments_from_lms", expectedError: scorm12_errors.READ_ONLY_ELEMENT, }); h.checkSetCMIValue({ api: apiInitialized(), fieldName: "cmi.core.student_id", expectedError: scorm12_errors.READ_ONLY_ELEMENT, }); h.checkSetCMIValue({ api: apiInitialized(), fieldName: "cmi.core.student_name", expectedError: scorm12_errors.READ_ONLY_ELEMENT, }); h.checkSetCMIValue({ api: apiInitialized(), fieldName: "cmi.core.credit", expectedError: scorm12_errors.READ_ONLY_ELEMENT, }); h.checkSetCMIValue({ api: apiInitialized(), fieldName: "cmi.core.entry", expectedError: scorm12_errors.READ_ONLY_ELEMENT, }); h.checkSetCMIValue({ api: apiInitialized(), fieldName: "cmi.core.total_time", expectedError: scorm12_errors.READ_ONLY_ELEMENT, }); h.checkSetCMIValue({ api: apiInitialized(), fieldName: "cmi.core.lesson_mode", expectedError: scorm12_errors.READ_ONLY_ELEMENT, }); h.checkSetCMIValue({ api: apiInitialized(), fieldName: "cmi.student_data.mastery_score", expectedError: scorm12_errors.READ_ONLY_ELEMENT, }); h.checkSetCMIValue({ api: apiInitialized(), fieldName: "cmi.student_data.max_time_allowed", expectedError: scorm12_errors.READ_ONLY_ELEMENT, }); h.checkSetCMIValue({ api: apiInitialized(), fieldName: "cmi.student_data.time_limit_action", expectedError: scorm12_errors.READ_ONLY_ELEMENT, }); }); }); describe("LMSGetValue()", () => { describe("Invalid Properties - Should Always Fail", () => { h.checkLMSGetValue({ api: apiInitialized(), fieldName: "cmi.core.close", expectedError: scorm12_errors.GENERAL, errorThrown: false, }); h.checkLMSGetValue({ api: apiInitialized(), fieldName: "cmi.exit", expectedError: scorm12_errors.GENERAL, errorThrown: false, }); h.checkLMSGetValue({ api: apiInitialized(), fieldName: "cmi.entry", expectedError: scorm12_errors.GENERAL, errorThrown: false, }); }); describe("Read and Write Properties - Should Success", () => { h.checkLMSGetValue({ api: apiInitialized(), fieldName: "cmi.interactions.0.objectives.0.id", initializeFirst: true, initializationValue: "AAA", expectedValue: "AAA", }); }); describe("Write-Only Properties - Should Always Fail", () => { h.checkLMSGetValue({ api: apiInitialized(), fieldName: "cmi.core.exit", expectedError: scorm12_errors.WRITE_ONLY_ELEMENT, }); h.checkLMSGetValue({ api: apiInitialized(), fieldName: "cmi.core.session_time", expectedError: scorm12_errors.WRITE_ONLY_ELEMENT, }); h.checkLMSGetValue({ api: apiInitialized(), fieldName: "cmi.interactions.0.id", initializeFirst: true, initializationValue: "AAA", expectedError: scorm12_errors.WRITE_ONLY_ELEMENT, }); h.checkLMSGetValue({ api: apiInitialized(), fieldName: "cmi.interactions.0.time", initializeFirst: true, initializationValue: "12:59:59", expectedError: scorm12_errors.WRITE_ONLY_ELEMENT, }); h.checkLMSGetValue({ api: apiInitialized(), fieldName: "cmi.interactions.0.type", initializeFirst: true, initializationValue: "true-false", expectedError: scorm12_errors.WRITE_ONLY_ELEMENT, }); h.checkLMSGetValue({ api: apiInitialized(), fieldName: "cmi.interactions.0.weighting", initializeFirst: true, initializationValue: "0", expectedError: scorm12_errors.WRITE_ONLY_ELEMENT, }); h.checkLMSGetValue({ api: apiInitialized(), fieldName: "cmi.interactions.0.student_response", initializeFirst: true, expectedError: scorm12_errors.WRITE_ONLY_ELEMENT, }); h.checkLMSGetValue({ api: apiInitialized(), fieldName: "cmi.interactions.0.result", initializeFirst: true, initializationValue: "correct", expectedError: scorm12_errors.WRITE_ONLY_ELEMENT, }); h.checkLMSGetValue({ api: apiInitialized(), fieldName: "cmi.interactions.0.latency", initializeFirst: true, initializationValue: "01:59:59.99", expectedError: scorm12_errors.WRITE_ONLY_ELEMENT, }); h.checkLMSGetValue({ api: apiInitialized(), fieldName: "cmi.interactions.0.correct_responses.0.pattern", initializeFirst: true, initializationValue: "AAA", expectedValue: "AAA", expectedError: scorm12_errors.WRITE_ONLY_ELEMENT, }); }); }); describe("LMSSetValue()", () => { describe("Uninitialized - Should Fail", () => { h.checkLMSSetValue({ api: api(), fieldName: "cmi.objectives.0.id", expectedError: scorm12_errors.STORE_BEFORE_INIT, }); h.checkLMSSetValue({ api: api(), fieldName: "cmi.interactions.0.id", expectedError: scorm12_errors.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", }); h.checkLMSSetValue({ api: apiInitialized(), fieldName: "cmi.interactions.0.objectives.0.id", valueToTest: "AAA", }); h.checkLMSSetValue({ api: apiInitialized(), fieldName: "cmi.interactions.10.correct_responses.0.pattern", valueToTest: "t", }); }); }); describe("replaceWithAnotherScormAPI()", () => { it("should replace the current API with another API", () => { const firstAPI = api(); const secondAPI = api(); firstAPI.cmi.core.student_id = "student_1"; secondAPI.cmi.core.student_id = "student_2"; firstAPI.replaceWithAnotherScormAPI(secondAPI); expect(firstAPI.cmi.core.student_id).toEqual("student_2"); }); }); describe("renderCommitCMI()", () => { it("should calculate total time when terminateCommit passed", () => { const scorm12API = api(); scorm12API.cmi.core.total_time = "12:34:56"; scorm12API.cmi.core.session_time = "23:59:59"; const cmiExport: RefObject = scorm12API.renderCommitCMI(true); expect(cmiExport.cmi.core.total_time).toEqual("36:34:55"); }); it("if the user passes, should calculate total time when terminateCommit passed", () => { const scorm12API = api(); scorm12API.cmi.core.score.max = "100"; scorm12API.cmi.core.score.min = "0"; scorm12API.cmi.core.score.raw = "100"; scorm12API.cmi.core.exit = "suspend"; scorm12API.cmi.core.lesson_status = "completed"; scorm12API.cmi.core.total_time = "0000:00:00"; scorm12API.cmi.core.session_time = "23:59:59"; const cmiExport: RefObject = scorm12API.renderCommitCMI(true); expect(cmiExport.cmi.core.total_time).toEqual("23:59:59"); }); it("should return flattened format when dataCommitFormat is 'flattened'", function () { const scorm12API = api({ ...DefaultSettings, ...{ dataCommitFormat: "flattened" }, }); scorm12API.cmi.core.student_id = "student_1"; scorm12API.cmi.core.student_name = "Student 1"; scorm12API.cmi.core.lesson_status = "completed"; scorm12API.cmi.core.score.raw = "100"; scorm12API.cmi.core.score.max = "100"; scorm12API.cmi.core.score.min = "0"; scorm12API.cmi.core.session_time = "23:59:59"; const cmiExport: RefObject = scorm12API.renderCommitCMI(true); expect(cmiExport["cmi.core.student_id"]).toEqual("student_1"); expect(cmiExport["cmi.core.student_name"]).toEqual("Student 1"); expect(cmiExport["cmi.core.lesson_status"]).toEqual("completed"); expect(cmiExport["cmi.core.score.raw"]).toEqual("100"); expect(cmiExport["cmi.core.score.max"]).toEqual("100"); expect(cmiExport["cmi.core.score.min"]).toEqual("0"); expect(cmiExport["cmi.core.session_time"]).toEqual("23:59:59"); }); it("should return params format when dataCommitFormat is 'params'", function () { const scorm12API = api({ ...DefaultSettings, ...{ dataCommitFormat: "params" }, }); scorm12API.cmi.core.student_id = "student_1"; scorm12API.cmi.core.student_name = "Student 1"; scorm12API.cmi.core.lesson_status = "completed"; scorm12API.cmi.core.score.raw = "100"; scorm12API.cmi.core.score.max = "100"; scorm12API.cmi.core.score.min = "0"; scorm12API.cmi.core.session_time = "23:59:59"; const result = scorm12API.renderCommitCMI(true); expect(result).toBeInstanceOf(Array); expect(result).toContain("cmi.core.lesson_status=completed"); expect(result).toContain("cmi.core.score.max=100"); expect(result).toContain("cmi.core.score.min=0"); expect(result).toContain("cmi.core.score.raw=100"); expect(result).toContain("cmi.core.session_time=23:59:59"); expect(result).toContain("cmi.core.student_id=student_1"); }); }); describe("renderCommitObject()", () => { it("should render commit object with default settings and no score", () => { const scorm12API = api(); scorm12API.cmi.core.lesson_status = "incomplete"; scorm12API.cmi.core.total_time = "12:34:56"; scorm12API.cmi.core.session_time = "23:59:59"; const commitObject = scorm12API.renderCommitObject(true); expect(commitObject.successStatus).toEqual("unknown"); expect(commitObject.completionStatus).toEqual("incomplete"); expect(commitObject.runtimeData.cmi.core.lesson_status).toEqual( "incomplete", ); expect(commitObject.totalTimeSeconds).toEqual( 12 * 3600 + 34 * 60 + 56 + (23 * 3600 + 59 * 60 + 59), ); expect(commitObject?.score?.max).toEqual(100); }); it("should render commit object with score data", () => { const scorm12API = api(); scorm12API.cmi.core.lesson_status = "completed"; scorm12API.cmi.core.score.raw = "85"; scorm12API.cmi.core.score.min = "0"; scorm12API.cmi.core.score.max = "100"; const commitObject = scorm12API.renderCommitObject(true); expect(commitObject.successStatus).toEqual("unknown"); expect(commitObject.completionStatus).toEqual("completed"); expect(commitObject.runtimeData.cmi.core.lesson_status).toEqual( "completed", ); expect(commitObject.runtimeData.cmi.core.score.raw).toEqual("85"); expect(commitObject.runtimeData.cmi.core.score.min).toEqual("0"); expect(commitObject.runtimeData.cmi.core.score.max).toEqual("100"); expect(commitObject.totalTimeSeconds).toEqual(0); expect(commitObject.score).toEqual({ raw: 85, min: 0, max: 100, }); }); it("should render commit object with completion and success status", () => { const scorm12API = api(); scorm12API.cmi.core.lesson_status = "passed"; const commitObject = scorm12API.renderCommitObject(true); expect(commitObject.successStatus).toEqual("passed"); expect(commitObject.completionStatus).toEqual("completed"); expect(commitObject.runtimeData.cmi.core.lesson_status).toEqual("passed"); }); it("should render commit object with failed success status", () => { const scorm12API = api(); scorm12API.cmi.core.lesson_status = "failed"; const commitObject = scorm12API.renderCommitObject(true); expect(commitObject.successStatus).toEqual("failed"); expect(commitObject.completionStatus).toEqual("incomplete"); expect(commitObject.runtimeData.cmi.core.lesson_status).toEqual("failed"); }); it("should calculate total time when terminateCommit is true", () => { const scorm12API = api(); scorm12API.cmi.core.total_time = "12:34:56"; scorm12API.cmi.core.session_time = "23:59:59"; const commitObject = scorm12API.renderCommitObject(true); expect(commitObject.runtimeData.cmi.core.total_time).toEqual("36:34:55"); }); }); describe("storeData()", () => { it('should set cmi.core.lesson_status to "completed"', () => { const scorm12API = api(); scorm12API.storeData(true); expect(scorm12API.cmi.core.lesson_status).toEqual("completed"); }); it('should set cmi.core.lesson_status to "browsed"', () => { const scorm12API = api(); scorm12API.cmi.core.lesson_mode = "browse"; scorm12API.storeData(true); expect(scorm12API.cmi.core.lesson_status).toEqual("browsed"); }); it('should set cmi.core.lesson_status to "browsed" - Initial Status', () => { const scorm12API = api(); scorm12API.startingData = { cmi: { core: { lesson_status: "" } } }; scorm12API.cmi.core.lesson_mode = "browse"; scorm12API.storeData(true); expect(scorm12API.cmi.core.lesson_status).toEqual("browsed"); }); it('should set cmi.core.lesson_status to "passed" - mastery_override: true', () => { const scorm12API = api({ ...DefaultSettings, ...{ mastery_override: true }, }); scorm12API.cmi.core.credit = "credit"; scorm12API.cmi.student_data.mastery_score = "60.0"; scorm12API.cmi.core.score.raw = "75.0"; scorm12API.storeData(true); expect(scorm12API.cmi.core.lesson_status).toEqual("passed"); }); it('should set cmi.core.lesson_status to "failed" - mastery_override: true', () => { const scorm12API = api({ ...DefaultSettings, ...{ mastery_override: true }, }); scorm12API.cmi.core.credit = "credit"; scorm12API.cmi.student_data.mastery_score = "60.0"; scorm12API.cmi.core.score.raw = "55.0"; scorm12API.storeData(true); expect(scorm12API.cmi.core.lesson_status).toEqual("failed"); }); it('should set cmi.core.lesson_status to "passed" - mastery_override: false', () => { const scorm12API = api({ ...DefaultSettings, ...{ mastery_override: false }, }); scorm12API.cmi.core.lesson_status = "failed"; // module author wanted the user to pass, so we don't override scorm12API.cmi.core.credit = "credit"; scorm12API.cmi.student_data.mastery_score = "60.0"; scorm12API.cmi.core.score.raw = "75.0"; scorm12API.storeData(true); expect(scorm12API.cmi.core.lesson_status).toEqual("failed"); }); it('should set cmi.core.lesson_status to "failed" - mastery_override: false', () => { const scorm12API = api({ ...DefaultSettings, ...{ mastery_override: false }, }); scorm12API.cmi.core.lesson_status = "passed"; // module author wanted the user to pass, so we don't override scorm12API.cmi.core.credit = "credit"; scorm12API.cmi.student_data.mastery_score = "60.0"; scorm12API.cmi.core.score.raw = "55.0"; scorm12API.storeData(true); expect(scorm12API.cmi.core.lesson_status).toEqual("passed"); }); }); describe("LMSCommit Debounce Tests", () => { it("should debounce LMSCommit calls when autocommit is true", async () => { const scorm12API = api({ ...DefaultSettings, autocommit: true, autocommitSeconds: 1, }); scorm12API.lmsInitialize(); const commitSpy = sinon.spy(scorm12API, "commit"); scorm12API.lmsSetValue("cmi.core.session_time", "00:01:00"); scorm12API.lmsSetValue("cmi.core.session_time", "00:02:00"); scorm12API.lmsSetValue("cmi.core.session_time", "00:03:00"); clock.tick(2000); await clock.runAllAsync(); expect(commitSpy.calledOnce).toBe(true); }); it("should call LMSCommit only once within the debounce period ", async () => { const scorm12API = api({ ...DefaultSettings, asyncCommit: true, autocommit: true, }); scorm12API.lmsInitialize(); const commitSpy = sinon.spy(scorm12API, "commit"); scorm12API.lmsSetValue("cmi.core.session_time", "00:01:00"); scorm12API.lmsCommit(); clock.tick(100); scorm12API.lmsCommit(); clock.tick(100); scorm12API.lmsCommit(); clock.tick(100); clock.tick(1000); await clock.runAllAsync(); expect(commitSpy.calledOnce).toBe(true); }); it("should call LMSCommit multiple times if debounce period is exceeded", async () => { const scorm12API = api({ ...DefaultSettings, autocommit: true, autocommitSeconds: 1, }); scorm12API.lmsInitialize(); const commitSpy = sinon.spy(scorm12API, "commit"); scorm12API.lmsSetValue("cmi.core.session_time", "00:01:00"); clock.tick(2000); scorm12API.lmsSetValue("cmi.core.session_time", "00:02:00"); clock.tick(2000); scorm12API.lmsSetValue("cmi.core.session_time", "00:03:00"); clock.tick(2000); await clock.runAllAsync(); expect(commitSpy.calledThrice).toBe(true); }); }); describe("Event Handlers", () => { it("Should handle SetValue.cmi.core.student_name event", () => { const scorm12API = apiInitialized(); const callback = sinon.spy(); scorm12API.on("LMSSetValue.cmi.core.student_name", callback); scorm12API.lmsSetValue("cmi.core.student_name", "@jcputney"); expect(callback.called).toBe(true); }); it("Should handle SetValue.cmi.* event", () => { const scorm12API = apiInitialized(); const callback = sinon.spy(); scorm12API.on("LMSSetValue.cmi.*", callback); scorm12API.lmsSetValue("cmi.core.student_name", "@jcputney"); expect(callback.called).toBe(true); }); it("Should handle CommitSuccess event", async () => { const scorm12API = api({ ...DefaultSettings, ...{ lmsCommitUrl: "/scorm12", autocommit: true, autocommitSeconds: 1, }, }); scorm12API.lmsInitialize(); const callback = sinon.spy(); scorm12API.on("CommitSuccess", callback); scorm12API.lmsSetValue("cmi.core.session_time", "00:01:00"); clock.tick(2000); await clock.runAllAsync(); expect(callback.called).toBe(true); }); it("Should clear all event listeners for CommitSuccess", async () => { const scorm12API = api({ ...DefaultSettings, ...{ lmsCommitUrl: "/scorm12", autocommit: true, autocommitSeconds: 1, }, }); scorm12API.lmsInitialize(); const callback = sinon.spy(); const callback2 = sinon.spy(); scorm12API.on("CommitSuccess", callback); scorm12API.on("CommitSuccess", callback2); scorm12API.lmsSetValue("cmi.core.session_time", "00:01:00"); clock.tick(2000); await clock.runAllAsync(); expect(callback.calledOnce).toBe(true); expect(callback2.calledOnce).toBe(true); scorm12API.clear("CommitSuccess"); scorm12API.lmsSetValue("cmi.core.session_time", "00:01:00"); clock.tick(2000); await clock.runAllAsync(); expect(callback.calledTwice).toBe(false); expect(callback2.calledTwice).toBe(false); }); it("Should clear only the specific event listener for CommitSuccess", async () => { const scorm12API = api({ ...DefaultSettings, ...{ lmsCommitUrl: "/scorm12", autocommit: true, autocommitSeconds: 1, }, }); scorm12API.lmsInitialize(); const callback = sinon.spy(() => 1); const callback2 = sinon.spy(() => 2); const callback3 = sinon.spy(() => 3); const callback4 = sinon.spy(() => 4); scorm12API.on("CommitSuccess", callback); scorm12API.on("CommitSuccess", callback2); scorm12API.on("LMSCommit", callback3); scorm12API.on("LMSSetValue", callback4); scorm12API.lmsSetValue("cmi.core.session_time", "00:01:00"); clock.tick(2000); await clock.runAllAsync(); expect(callback.calledOnce).toBe(true); expect(callback2.calledOnce).toBe(true); expect(callback3.calledOnce).toBe(true); expect(callback4.calledOnce).toBe(true); scorm12API.off("CommitSuccess", callback); scorm12API.lmsSetValue("cmi.core.session_time", "00:01:00"); clock.tick(2000); await clock.runAllAsync(); expect(callback.calledTwice).toBe(false); // removed callback should not be called a second time expect(callback2.calledTwice).toBe(true); expect(callback3.calledTwice).toBe(true); expect(callback4.calledTwice).toBe(true); }); it("Should handle multiple events in one listener string", async () => { const scorm12API = api({ ...DefaultSettings, ...{ lmsCommitUrl: "/scorm12", autocommit: true, autocommitSeconds: 1, }, }); scorm12API.lmsInitialize(); const callback = sinon.spy(); scorm12API.on("LMSSetValue.cmi.core.session_time CommitSuccess", callback); scorm12API.lmsSetValue("cmi.core.session_time", "00:01:00"); clock.tick(2000); await clock.runAllAsync(); expect(callback.calledTwice).toBe(true); }); it("Should detach multiple events using off()", async () => { const scorm12API = api({ ...DefaultSettings, ...{ lmsCommitUrl: "/scorm12", autocommit: true, autocommitSeconds: 1, }, }); scorm12API.lmsInitialize(); const callback = sinon.spy(); scorm12API.on("LMSSetValue.cmi.core.session_time CommitSuccess", callback); scorm12API.off("LMSSetValue.cmi.core.session_time CommitSuccess", callback); scorm12API.lmsSetValue("cmi.core.session_time", "00:01:00"); clock.tick(2000); await clock.runAllAsync(); expect(callback.called).toBe(false); }); it("Should handle CommitError event", async () => { const scorm12API = api({ ...DefaultSettings, ...{ lmsCommitUrl: "/scorm12/error", autocommit: true, autocommitSeconds: 1, }, }); scorm12API.lmsInitialize(); const callback = sinon.spy(); scorm12API.on("CommitError", callback); scorm12API.lmsSetValue("cmi.core.session_time", "00:01:00"); clock.tick(2000); await clock.runAllAsync(); expect(callback.called).toBe(true); }); it("Should handle CommitError event when offline", async () => { const scorm2004API = api({ ...DefaultSettings, ...{ lmsCommitUrl: "/scorm12/does_not_exist", autocommit: true, autocommitSeconds: 1, }, }); scorm2004API.lmsInitialize(); const callback = sinon.spy(); scorm2004API.on("CommitError", callback); scorm2004API.lmsSetValue("cmi.core.session_time", "00:01:00"); clock.tick(2000); await clock.runAllAsync(); expect(callback.called).toBe(true); }); }); describe("Test issues from users", () => { it("Should be able to load the JSON data from issue #587", () => { const scorm12api = api({ ...DefaultSettings, autocommit: false, lmsCommitUrl: "/scorm12", dataCommitFormat: "flattened", logLevel: 1, }); scorm12api.loadFromFlattenedJSON( { "cmi.comments": "", "cmi.comments_from_lms": "", "cmi.core.credit": "", "cmi.core.entry": "", "cmi.core.exit": "", "cmi.core.lesson_location": "topic-nOmtUy", "cmi.core.lesson_mode": "normal", "cmi.core.lesson_status": "completed", "cmi.core.score.max": "100", "cmi.core.score.min": "", "cmi.core.score.raw": "", "cmi.core.session_time": "00:03:50", "cmi.core.student_id": "student1", "cmi.core.student_name": "Student 1", "cmi.core.total_time": "00:03:50", "cmi.interactions": {}, "cmi.launch_data": "", "cmi.objectives.0.id": "topic-aMMlFF", "cmi.objectives.0.score.max": "100", "cmi.objectives.0.score.min": "", "cmi.objectives.0.score.raw": "", "cmi.objectives.0.status": "completed", "cmi.objectives.1.id": "topic-6ReD72", "cmi.objectives.1.score.max": "100", "cmi.objectives.1.score.min": "", "cmi.objectives.1.score.raw": "", "cmi.objectives.1.status": "completed", "cmi.objectives.10.id": "topic-MllWvr", "cmi.objectives.10.score.max": "100", "cmi.objectives.10.score.min": "", "cmi.objectives.10.score.raw": "", "cmi.objectives.10.status": "completed", "cmi.objectives.11.id": "topic-m0dnP3", "cmi.objectives.11.score.max": "100", "cmi.objectives.11.score.min": "", "cmi.objectives.11.score.raw": "", "cmi.objectives.11.status": "completed", "cmi.objectives.12.id": "topic-so4hKC", "cmi.objectives.12.score.max": "100", "cmi.objectives.12.score.min": "", "cmi.objectives.12.score.raw": "", "cmi.objectives.12.status": "completed", "cmi.objectives.13.id": "topic-S9p8FE", "cmi.objectives.13.score.max": "100", "cmi.objectives.13.score.min": "", "cmi.objectives.13.score.raw": "", "cmi.objectives.13.status": "completed", "cmi.objectives.14.id": "topic-E97B5s", "cmi.objectives.14.score.max": "100", "cmi.objectives.14.score.min": "", "cmi.objectives.14.score.raw": "", "cmi.objectives.14.status": "completed", "cmi.objectives.15.id": "topic-TTyEf0", "cmi.objectives.15.score.max": "100", "cmi.objectives.15.score.min": "", "cmi.objectives.15.score.raw": "", "cmi.objectives.15.status": "completed", "cmi.objectives.16.id": "topic-Bk8ZLK", "cmi.objectives.16.score.max": "100", "cmi.objectives.16.score.min": "", "cmi.objectives.16.score.raw": "", "cmi.objectives.16.status": "completed", "cmi.objectives.17.id": "topic-5rxzyV", "cmi.objectives.17.score.max": "100", "cmi.objectives.17.score.min": "", "cmi.objectives.17.score.raw": "", "cmi.objectives.17.status": "completed", "cmi.objectives.18.id": "topic-Q3rjln", "cmi.objectives.18.score.max": "100", "cmi.objectives.18.score.min": "", "cmi.objectives.18.score.raw": "", "cmi.objectives.18.status": "completed", "cmi.objectives.19.id": "topic-eIbwi4", "cmi.objectives.19.score.max": "100", "cmi.objectives.19.score.min": "", "cmi.objectives.19.score.raw": "", "cmi.objectives.19.status": "completed", "cmi.objectives.2.id": "topic-sfvmuO", "cmi.objectives.2.score.max": "100", "cmi.objectives.2.score.min": "", "cmi.objectives.2.score.raw": "", "cmi.objectives.2.status": "completed", "cmi.objectives.20.id": "topic-AzAspy", "cmi.objectives.20.score.max": "100", "cmi.objectives.20.score.min": "", "cmi.objectives.20.score.raw": "", "cmi.objectives.20.status": "completed", "cmi.objectives.21.id": "topic-ehMV4A", "cmi.objectives.21.score.max": "100", "cmi.objectives.21.score.min": "", "cmi.objectives.21.score.raw": "", "cmi.objectives.21.status": "completed", "cmi.objectives.22.id": "topic-U52hDp", "cmi.objectives.22.score.max": "100", "cmi.objectives.22.score.min": "", "cmi.objectives.22.score.raw": "", "cmi.objectives.22.status": "completed", "cmi.objectives.23.id": "topic-mmAPuC", "cmi.objectives.23.score.max": "100", "cmi.objectives.23.score.min": "", "cmi.objectives.23.score.raw": "", "cmi.objectives.23.status": "completed", "cmi.objectives.24.id": "topic-UyqqeN", "cmi.objectives.24.score.max": "100", "cmi.objectives.24.score.min": "", "cmi.objectives.24.score.raw": "", "cmi.objectives.24.status": "completed", "cmi.objectives.25.id": "topic-UIALBn", "cmi.objectives.25.score.max": "100", "cmi.objectives.25.score.min": "", "cmi.objectives.25.score.raw": "", "cmi.objectives.25.status": "completed", "cmi.objectives.26.id": "topic-oLGCQE", "cmi.objectives.26.score.max": "100", "cmi.objectives.26.score.min": "", "cmi.objectives.26.score.raw": "", "cmi.objectives.26.status": "completed", "cmi.objectives.27.id": "topic-JhRmY0", "cmi.objectives.27.score.max": "100", "cmi.objectives.27.score.min": "", "cmi.objectives.27.score.raw": "", "cmi.objectives.27.status": "completed", "cmi.objectives.28.id": "topic-677fLH", "cmi.objectives.28.score.max": "100", "cmi.objectives.28.score.min": "", "cmi.objectives.28.score.raw": "", "cmi.objectives.28.status": "completed", "cmi.objectives.29.id": "topic-rKvnIL", "cmi.objectives.29.score.max": "100", "cmi.objectives.29.score.min": "", "cmi.objectives.29.score.raw": "", "cmi.objectives.29.status": "completed", "cmi.objectives.3.id": "topic-IObzTr", "cmi.objectives.3.score.max": "100", "cmi.objectives.3.score.min": "", "cmi.objectives.3.score.raw": "", "cmi.objectives.3.status": "completed", "cmi.objectives.30.id": "topic-Vjd6mO", "cmi.objectives.30.score.max": "100", "cmi.objectives.30.score.min": "", "cmi.objectives.30.score.raw": "", "cmi.objectives.30.status": "completed", "cmi.objectives.31.id": "topic-jUsEtX", "cmi.objectives.31.score.max": "100", "cmi.objectives.31.score.min": "", "cmi.objectives.31.score.raw": "", "cmi.objectives.31.status": "completed", "cmi.objectives.32.id": "topic-SvCWWr", "cmi.objectives.32.score.max": "100", "cmi.objectives.32.score.min": "", "cmi.objectives.32.score.raw": "", "cmi.objectives.32.status": "completed", "cmi.objectives.33.id": "topic-knRFfG", "cmi.objectives.33.score.max": "100", "cmi.objectives.33.score.min": "", "cmi.objectives.33.score.raw": "", "cmi.objectives.33.status": "completed", "cmi.objectives.34.id": "topic-wlFwhf", "cmi.objectives.34.score.max": "100", "cmi.objectives.34.score.min": "", "cmi.objectives.34.score.raw": "", "cmi.objectives.34.status": "completed", "cmi.objectives.35.id": "topic-3b86fq", "cmi.objectives.35.score.max": "100", "cmi.objectives.35.score.min": "", "cmi.objectives.35.score.raw": "", "cmi.objectives.35.status": "completed", "cmi.objectives.36.id": "topic-ZjIFKf", "cmi.objectives.36.score.max": "100", "cmi.objectives.36.score.min": "", "cmi.objectives.36.score.raw": "", "cmi.objectives.36.status": "completed", "cmi.objectives.37.id": "topic-A9spZz", "cmi.objectives.37.score.max": "100", "cmi.objectives.37.score.min": "", "cmi.objectives.37.score.raw": "", "cmi.objectives.37.status": "completed", "cmi.objectives.38.id": "topic-nOmtUy", "cmi.objectives.38.score.max": "100", "cmi.objectives.38.score.min": "", "cmi.objectives.38.score.raw": "", "cmi.objectives.38.status": "completed", "cmi.objectives.39.id": "topic-jxtabl", "cmi.objectives.39.score.max": "100", "cmi.objectives.39.score.min": "", "cmi.objectives.39.score.raw": "", "cmi.objectives.39.status": "completed", "cmi.objectives.4.id": "topic-Bq3O5v", "cmi.objectives.4.score.max": "100", "cmi.objectives.4.score.min": "", "cmi.objectives.4.score.raw": "", "cmi.objectives.4.status": "completed", "cmi.objectives.5.id": "topic-HmnDxg", "cmi.objectives.5.score.max": "100", "cmi.objectives.5.score.min": "", "cmi.objectives.5.score.raw": "", "cmi.objectives.5.status": "completed", "cmi.objectives.6.id": "topic-4YswmY", "cmi.objectives.6.score.max": "100", "cmi.objectives.6.score.min": "", "cmi.objectives.6.score.raw": "", "cmi.objectives.6.status": "completed", "cmi.objectives.7.id": "topic-LuAS5e", "cmi.objectives.7.score.max": "100", "cmi.objectives.7.score.min": "", "cmi.objectives.7.score.raw": "", "cmi.objectives.7.status": "completed", "cmi.objectives.8.id": "topic-K6ECXa", "cmi.objectives.8.score.max": "100", "cmi.objectives.8.score.min": "", "cmi.objectives.8.score.raw": "", "cmi.objectives.8.status": "completed", "cmi.objectives.9.id": "topic-2lpxvN", "cmi.objectives.9.score.max": "100", "cmi.objectives.9.score.min": "", "cmi.objectives.9.score.raw": "", "cmi.objectives.9.status": "completed", "cmi.student_data.mastery_score": "", "cmi.student_data.max_time_allowed": "", "cmi.student_data.time_limit_action": "", "cmi.student_preference.audio": "", "cmi.student_preference.language": "", "cmi.student_preference.speed": "", "cmi.student_preference.text": "", "cmi.suspend_data": "", }, "", ); scorm12api.lmsInitialize(); expect(scorm12api.getCMIValue("cmi.objectives.10.id")).toEqual( "topic-MllWvr", ); }); }); });