scorm-again 3.1.3 → 3.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10646,6 +10646,26 @@ class RollupProcess {
10646
10646
  }
10647
10647
  }
10648
10648
 
10649
+ function evaluateCompletionStatusFromThreshold({
10650
+ completionThreshold,
10651
+ progressMeasure,
10652
+ storedCompletionStatus
10653
+ }) {
10654
+ if (completionThreshold !== "" && completionThreshold !== null && completionThreshold !== void 0) {
10655
+ const thresholdValue = parseFloat(String(completionThreshold));
10656
+ if (!isNaN(thresholdValue)) {
10657
+ if (progressMeasure !== "" && progressMeasure !== null && progressMeasure !== void 0) {
10658
+ const progressValue = parseFloat(String(progressMeasure));
10659
+ if (!isNaN(progressValue)) {
10660
+ return progressValue >= thresholdValue ? CompletionStatus.COMPLETED : CompletionStatus.INCOMPLETE;
10661
+ }
10662
+ }
10663
+ return CompletionStatus.UNKNOWN;
10664
+ }
10665
+ }
10666
+ return storedCompletionStatus || CompletionStatus.UNKNOWN;
10667
+ }
10668
+
10649
10669
  const VALID_COMPLETION_STATUSES = [
10650
10670
  CompletionStatus.COMPLETED,
10651
10671
  CompletionStatus.INCOMPLETE,
@@ -10700,10 +10720,38 @@ class RteDataTransferService {
10700
10720
  * @param {CMIDataForTransfer} cmiData - CMI data from runtime
10701
10721
  */
10702
10722
  transferPrimaryObjective(activity, cmiData) {
10703
- const validatedCompletionStatus = validateCompletionStatus(cmiData.completion_status);
10704
- if (validatedCompletionStatus && validatedCompletionStatus !== CompletionStatus.UNKNOWN) {
10705
- activity.completionStatus = validatedCompletionStatus;
10706
- activity.attemptProgressStatus = true;
10723
+ let hasProgressMeasure = false;
10724
+ if (cmiData.progress_measure && cmiData.progress_measure !== "") {
10725
+ const progressMeasure = parseFloat(cmiData.progress_measure);
10726
+ if (!isNaN(progressMeasure)) {
10727
+ hasProgressMeasure = true;
10728
+ activity.progressMeasure = progressMeasure;
10729
+ activity.progressMeasureStatus = true;
10730
+ activity.attemptCompletionAmount = progressMeasure;
10731
+ activity.attemptCompletionAmountStatus = true;
10732
+ if (activity.primaryObjective) {
10733
+ activity.primaryObjective.progressMeasure = progressMeasure;
10734
+ activity.primaryObjective.progressMeasureStatus = true;
10735
+ }
10736
+ }
10737
+ }
10738
+ if (!hasProgressMeasure) {
10739
+ activity.attemptCompletionAmountStatus = false;
10740
+ }
10741
+ if (activity.completedByMeasure) {
10742
+ const completionStatus = evaluateCompletionStatusFromThreshold({
10743
+ completionThreshold: activity.minProgressMeasure,
10744
+ progressMeasure: cmiData.progress_measure,
10745
+ storedCompletionStatus: CompletionStatus.UNKNOWN
10746
+ });
10747
+ activity.completionStatus = completionStatus;
10748
+ activity.attemptProgressStatus = completionStatus !== CompletionStatus.UNKNOWN;
10749
+ } else {
10750
+ const validatedCompletionStatus = validateCompletionStatus(cmiData.completion_status);
10751
+ if (validatedCompletionStatus && validatedCompletionStatus !== CompletionStatus.UNKNOWN) {
10752
+ activity.completionStatus = validatedCompletionStatus;
10753
+ activity.attemptProgressStatus = true;
10754
+ }
10707
10755
  }
10708
10756
  let hasSuccessStatus = false;
10709
10757
  let successStatus = false;
@@ -10737,17 +10785,6 @@ class RteDataTransferService {
10737
10785
  activity.primaryObjective.progressStatus = true;
10738
10786
  }
10739
10787
  }
10740
- if (cmiData.progress_measure && cmiData.progress_measure !== "") {
10741
- const progressMeasure = parseFloat(cmiData.progress_measure);
10742
- if (!isNaN(progressMeasure)) {
10743
- activity.progressMeasure = progressMeasure;
10744
- activity.progressMeasureStatus = true;
10745
- if (activity.primaryObjective) {
10746
- activity.primaryObjective.progressMeasure = progressMeasure;
10747
- activity.primaryObjective.progressMeasureStatus = true;
10748
- }
10749
- }
10750
- }
10751
10788
  }
10752
10789
  /**
10753
10790
  * Transfer non-primary objective data from CMI to activity objectives
@@ -10952,6 +10989,18 @@ class TerminationHandler {
10952
10989
  this.endAttempt(this.activityTree.currentActivity);
10953
10990
  }
10954
10991
  }
10992
+ const ancestorExitResult = this.applyAncestorExitActionRules(currentActivity);
10993
+ if (ancestorExitResult.action === "EXIT_ALL") {
10994
+ return this.handleExitAll(ancestorExitResult.activity || currentActivity);
10995
+ }
10996
+ if (ancestorExitResult.exception) {
10997
+ return {
10998
+ terminationRequest: SequencingRequestType.EXIT,
10999
+ sequencingRequest: null,
11000
+ exception: ancestorExitResult.exception,
11001
+ valid: false
11002
+ };
11003
+ }
10955
11004
  let processedExit;
10956
11005
  let postConditionResult;
10957
11006
  do {
@@ -11179,16 +11228,14 @@ class TerminationHandler {
11179
11228
  for (const rule of exitRules) {
11180
11229
  let conditionsMet;
11181
11230
  if (rule.conditionCombination === "all") {
11182
- conditionsMet = rule.conditions.every(
11183
- (condition) => condition.evaluate(activity)
11184
- );
11231
+ conditionsMet = rule.conditions.every((condition) => condition.evaluate(activity));
11185
11232
  } else {
11186
- conditionsMet = rule.conditions.some(
11187
- (condition) => condition.evaluate(activity)
11188
- );
11233
+ conditionsMet = rule.conditions.some((condition) => condition.evaluate(activity));
11189
11234
  }
11190
11235
  if (conditionsMet) {
11191
- if (rule.action === RuleActionType.EXIT_PARENT) {
11236
+ if (rule.action === RuleActionType.EXIT) {
11237
+ return { action: "EXIT", recursionDepth };
11238
+ } else if (rule.action === RuleActionType.EXIT_PARENT) {
11192
11239
  return { action: "EXIT_PARENT", recursionDepth };
11193
11240
  } else if (rule.action === RuleActionType.EXIT_ALL) {
11194
11241
  return { action: "EXIT_ALL", recursionDepth };
@@ -11222,6 +11269,55 @@ class TerminationHandler {
11222
11269
  this.processExitAtLevel(rootActivity, 0);
11223
11270
  this.terminateAll(rootActivity);
11224
11271
  }
11272
+ /**
11273
+ * Sequencing Exit Action Rules Subprocess (TB.2.1) ancestor walk
11274
+ * @spec SN Book: TB.2.1 (Sequencing Exit Action Rules Subprocess)
11275
+ * @param {Activity} terminatingActivity - The activity whose attempt just ended
11276
+ * @return {{action: string | null, activity?: Activity, exception?: string}}
11277
+ */
11278
+ applyAncestorExitActionRules(terminatingActivity) {
11279
+ const activityPath = [];
11280
+ let current = terminatingActivity.parent;
11281
+ while (current) {
11282
+ activityPath.unshift(current);
11283
+ current = current.parent;
11284
+ }
11285
+ for (const activity of activityPath) {
11286
+ const exitAction = this.exitActionRulesSubprocess(activity);
11287
+ if (exitAction === "EXIT_ALL") {
11288
+ this.fireEvent("onAncestorExitAction", {
11289
+ activity: activity.id,
11290
+ action: exitAction
11291
+ });
11292
+ return { action: "EXIT_ALL", activity };
11293
+ }
11294
+ if (exitAction === "EXIT_PARENT") {
11295
+ if (!activity.parent) {
11296
+ return { action: "EXIT_PARENT", activity, exception: "TB.2.3-4" };
11297
+ }
11298
+ this.terminateDescendants(activity.parent);
11299
+ this.activityTree.currentActivity = activity.parent;
11300
+ this.endAttempt(activity.parent);
11301
+ this.fireEvent("onAncestorExitAction", {
11302
+ activity: activity.id,
11303
+ action: exitAction,
11304
+ currentActivity: activity.parent.id
11305
+ });
11306
+ return { action: "EXIT_PARENT", activity: activity.parent };
11307
+ }
11308
+ if (exitAction === "EXIT") {
11309
+ this.terminateDescendants(activity);
11310
+ this.activityTree.currentActivity = activity;
11311
+ this.endAttempt(activity);
11312
+ this.fireEvent("onAncestorExitAction", {
11313
+ activity: activity.id,
11314
+ action: exitAction
11315
+ });
11316
+ return { action: "EXIT", activity };
11317
+ }
11318
+ }
11319
+ return { action: null };
11320
+ }
11225
11321
  /**
11226
11322
  * Process exit actions at specific level
11227
11323
  * @param {Activity} activity - Activity to process
@@ -11304,16 +11400,14 @@ class TerminationHandler {
11304
11400
  for (const rule of exitRules) {
11305
11401
  let conditionsMet;
11306
11402
  if (rule.conditionCombination === "all") {
11307
- conditionsMet = rule.conditions.every(
11308
- (condition) => condition.evaluate(activity)
11309
- );
11403
+ conditionsMet = rule.conditions.every((condition) => condition.evaluate(activity));
11310
11404
  } else {
11311
- conditionsMet = rule.conditions.some(
11312
- (condition) => condition.evaluate(activity)
11313
- );
11405
+ conditionsMet = rule.conditions.some((condition) => condition.evaluate(activity));
11314
11406
  }
11315
11407
  if (conditionsMet) {
11316
- if (rule.action === RuleActionType.EXIT_PARENT) {
11408
+ if (rule.action === RuleActionType.EXIT) {
11409
+ return "EXIT";
11410
+ } else if (rule.action === RuleActionType.EXIT_PARENT) {
11317
11411
  return "EXIT_PARENT";
11318
11412
  } else if (rule.action === RuleActionType.EXIT_ALL) {
11319
11413
  return "EXIT_ALL";
@@ -14867,7 +14961,29 @@ class SequencingService {
14867
14961
  * Update activity properties from current CMI values
14868
14962
  */
14869
14963
  updateActivityFromCMI(activity) {
14870
- if (this.cmi.completion_status !== "unknown") {
14964
+ let hasProgressMeasure = false;
14965
+ if (this.cmi.progress_measure !== "") {
14966
+ const progressMeasure = parseFloat(this.cmi.progress_measure);
14967
+ if (!isNaN(progressMeasure)) {
14968
+ hasProgressMeasure = true;
14969
+ activity.progressMeasure = progressMeasure;
14970
+ activity.progressMeasureStatus = true;
14971
+ activity.attemptCompletionAmount = progressMeasure;
14972
+ activity.attemptCompletionAmountStatus = true;
14973
+ }
14974
+ }
14975
+ if (!hasProgressMeasure) {
14976
+ activity.attemptCompletionAmountStatus = false;
14977
+ }
14978
+ if (activity.completedByMeasure) {
14979
+ const completionStatus = evaluateCompletionStatusFromThreshold({
14980
+ completionThreshold: activity.minProgressMeasure,
14981
+ progressMeasure: this.cmi.progress_measure,
14982
+ storedCompletionStatus: CompletionStatus.UNKNOWN
14983
+ });
14984
+ activity.completionStatus = completionStatus;
14985
+ activity.attemptProgressStatus = completionStatus !== CompletionStatus.UNKNOWN;
14986
+ } else if (this.cmi.completion_status !== "unknown") {
14871
14987
  activity.completionStatus = this.cmi.completion_status;
14872
14988
  activity.attemptProgressStatus = true;
14873
14989
  }
@@ -14879,13 +14995,6 @@ class SequencingService {
14879
14995
  activity.primaryObjective.progressStatus = true;
14880
14996
  }
14881
14997
  }
14882
- if (this.cmi.progress_measure !== "") {
14883
- const progressMeasure = parseFloat(this.cmi.progress_measure);
14884
- if (!isNaN(progressMeasure)) {
14885
- activity.progressMeasure = progressMeasure;
14886
- activity.progressMeasureStatus = true;
14887
- }
14888
- }
14889
14998
  if (this.cmi.score && this.cmi.score.scaled !== "") {
14890
14999
  const scaledScore = parseFloat(this.cmi.score.scaled);
14891
15000
  if (!isNaN(scaledScore)) {
@@ -23014,22 +23123,11 @@ class Scorm2004CMIHandler {
23014
23123
  * @returns {string} The evaluated completion status
23015
23124
  */
23016
23125
  evaluateCompletionStatus() {
23017
- const threshold = this.context.cmi.completion_threshold;
23018
- const progressMeasure = this.context.cmi.progress_measure;
23019
- const storedStatus = this.context.cmi.completion_status;
23020
- if (threshold !== "" && threshold !== null && threshold !== void 0) {
23021
- const thresholdValue = parseFloat(String(threshold));
23022
- if (!isNaN(thresholdValue)) {
23023
- if (progressMeasure !== "" && progressMeasure !== null && progressMeasure !== void 0) {
23024
- const progressValue = parseFloat(String(progressMeasure));
23025
- if (!isNaN(progressValue)) {
23026
- return progressValue >= thresholdValue ? CompletionStatus.COMPLETED : CompletionStatus.INCOMPLETE;
23027
- }
23028
- }
23029
- return CompletionStatus.UNKNOWN;
23030
- }
23031
- }
23032
- return storedStatus || CompletionStatus.UNKNOWN;
23126
+ return evaluateCompletionStatusFromThreshold({
23127
+ completionThreshold: this.context.cmi.completion_threshold,
23128
+ progressMeasure: this.context.cmi.progress_measure,
23129
+ storedCompletionStatus: this.context.cmi.completion_status
23130
+ });
23033
23131
  }
23034
23132
  /**
23035
23133
  * Evaluates success_status per SCORM 2004 RTE Table 4.2.21.1a
@@ -23689,6 +23787,21 @@ class ActivityTreeBuilder {
23689
23787
  if (activitySettings.rollupConsiderations) {
23690
23788
  activity.applyRollupConsiderations(activitySettings.rollupConsiderations);
23691
23789
  }
23790
+ if (activitySettings.completionThreshold) {
23791
+ const threshold = activitySettings.completionThreshold;
23792
+ if (threshold.completedByMeasure !== void 0) {
23793
+ activity.completedByMeasure = threshold.completedByMeasure;
23794
+ }
23795
+ if (threshold.minProgressMeasure !== void 0) {
23796
+ activity.minProgressMeasure = threshold.minProgressMeasure;
23797
+ activity.completionThreshold = threshold.minProgressMeasure.toString();
23798
+ } else if (threshold.completedByMeasure) {
23799
+ activity.completionThreshold = activity.minProgressMeasure.toString();
23800
+ }
23801
+ if (threshold.progressWeight !== void 0) {
23802
+ activity.progressWeight = threshold.progressWeight;
23803
+ }
23804
+ }
23692
23805
  if (activitySettings.hideLmsUi) {
23693
23806
  const mergedHide = this.sequencingConfigBuilder.mergeHideLmsUi(
23694
23807
  activity.hideLmsUi,
@@ -24732,8 +24845,23 @@ class Scorm2004API extends BaseAPI {
24732
24845
  reset(settings) {
24733
24846
  this.commonReset(settings);
24734
24847
  this.cmi?.reset();
24848
+ this.applyCurrentActivityLaunchData();
24735
24849
  this.adl?.reset();
24736
24850
  }
24851
+ /**
24852
+ * Apply launch-static activity data to CMI while the new SCO is pre-initialize.
24853
+ *
24854
+ * @spec SCORM 2004 4th Ed. RTE 4.2.9 - cmi.completion_threshold
24855
+ */
24856
+ applyCurrentActivityLaunchData() {
24857
+ const currentActivity = this._sequencing?.getCurrentActivity();
24858
+ if (!this.cmi || !currentActivity) {
24859
+ return;
24860
+ }
24861
+ const contentActivityData = this._sequencing.overallSequencingProcess?.getContentActivityData(currentActivity);
24862
+ const completionThreshold = contentActivityData?.completionThreshold ?? currentActivity.completionThreshold?.toString() ?? "";
24863
+ this.cmi.completion_threshold = completionThreshold;
24864
+ }
24737
24865
  /**
24738
24866
  * Getter for _version
24739
24867
  * @return {string}
@@ -25244,14 +25372,14 @@ class Scorm2004API extends BaseAPI {
25244
25372
  if (this.cmi.mode === "normal") {
25245
25373
  if (this.cmi.credit === "credit") {
25246
25374
  if (this.cmi.completion_threshold && this.cmi.progress_measure) {
25247
- if (this.cmi.progress_measure >= this.cmi.completion_threshold) {
25375
+ if (parseFloat(this.cmi.progress_measure) >= parseFloat(this.cmi.completion_threshold)) {
25248
25376
  this.cmi.completion_status = "completed";
25249
25377
  } else {
25250
25378
  this.cmi.completion_status = "incomplete";
25251
25379
  }
25252
25380
  }
25253
25381
  if (this.cmi.scaled_passing_score && this.cmi.score.scaled) {
25254
- if (this.cmi.score.scaled >= this.cmi.scaled_passing_score) {
25382
+ if (parseFloat(this.cmi.score.scaled) >= parseFloat(this.cmi.scaled_passing_score)) {
25255
25383
  this.cmi.success_status = "passed";
25256
25384
  } else {
25257
25385
  this.cmi.success_status = "failed";