scorm-again 3.1.2 → 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.
@@ -10426,6 +10426,26 @@ class RollupProcess {
10426
10426
  }
10427
10427
  }
10428
10428
 
10429
+ function evaluateCompletionStatusFromThreshold({
10430
+ completionThreshold,
10431
+ progressMeasure,
10432
+ storedCompletionStatus
10433
+ }) {
10434
+ if (completionThreshold !== "" && completionThreshold !== null && completionThreshold !== void 0) {
10435
+ const thresholdValue = parseFloat(String(completionThreshold));
10436
+ if (!isNaN(thresholdValue)) {
10437
+ if (progressMeasure !== "" && progressMeasure !== null && progressMeasure !== void 0) {
10438
+ const progressValue = parseFloat(String(progressMeasure));
10439
+ if (!isNaN(progressValue)) {
10440
+ return progressValue >= thresholdValue ? CompletionStatus.COMPLETED : CompletionStatus.INCOMPLETE;
10441
+ }
10442
+ }
10443
+ return CompletionStatus.UNKNOWN;
10444
+ }
10445
+ }
10446
+ return storedCompletionStatus || CompletionStatus.UNKNOWN;
10447
+ }
10448
+
10429
10449
  const VALID_COMPLETION_STATUSES = [
10430
10450
  CompletionStatus.COMPLETED,
10431
10451
  CompletionStatus.INCOMPLETE,
@@ -10480,10 +10500,38 @@ class RteDataTransferService {
10480
10500
  * @param {CMIDataForTransfer} cmiData - CMI data from runtime
10481
10501
  */
10482
10502
  transferPrimaryObjective(activity, cmiData) {
10483
- const validatedCompletionStatus = validateCompletionStatus(cmiData.completion_status);
10484
- if (validatedCompletionStatus && validatedCompletionStatus !== CompletionStatus.UNKNOWN) {
10485
- activity.completionStatus = validatedCompletionStatus;
10486
- activity.attemptProgressStatus = true;
10503
+ let hasProgressMeasure = false;
10504
+ if (cmiData.progress_measure && cmiData.progress_measure !== "") {
10505
+ const progressMeasure = parseFloat(cmiData.progress_measure);
10506
+ if (!isNaN(progressMeasure)) {
10507
+ hasProgressMeasure = true;
10508
+ activity.progressMeasure = progressMeasure;
10509
+ activity.progressMeasureStatus = true;
10510
+ activity.attemptCompletionAmount = progressMeasure;
10511
+ activity.attemptCompletionAmountStatus = true;
10512
+ if (activity.primaryObjective) {
10513
+ activity.primaryObjective.progressMeasure = progressMeasure;
10514
+ activity.primaryObjective.progressMeasureStatus = true;
10515
+ }
10516
+ }
10517
+ }
10518
+ if (!hasProgressMeasure) {
10519
+ activity.attemptCompletionAmountStatus = false;
10520
+ }
10521
+ if (activity.completedByMeasure) {
10522
+ const completionStatus = evaluateCompletionStatusFromThreshold({
10523
+ completionThreshold: activity.minProgressMeasure,
10524
+ progressMeasure: cmiData.progress_measure,
10525
+ storedCompletionStatus: CompletionStatus.UNKNOWN
10526
+ });
10527
+ activity.completionStatus = completionStatus;
10528
+ activity.attemptProgressStatus = completionStatus !== CompletionStatus.UNKNOWN;
10529
+ } else {
10530
+ const validatedCompletionStatus = validateCompletionStatus(cmiData.completion_status);
10531
+ if (validatedCompletionStatus && validatedCompletionStatus !== CompletionStatus.UNKNOWN) {
10532
+ activity.completionStatus = validatedCompletionStatus;
10533
+ activity.attemptProgressStatus = true;
10534
+ }
10487
10535
  }
10488
10536
  let hasSuccessStatus = false;
10489
10537
  let successStatus = false;
@@ -10517,17 +10565,6 @@ class RteDataTransferService {
10517
10565
  activity.primaryObjective.progressStatus = true;
10518
10566
  }
10519
10567
  }
10520
- if (cmiData.progress_measure && cmiData.progress_measure !== "") {
10521
- const progressMeasure = parseFloat(cmiData.progress_measure);
10522
- if (!isNaN(progressMeasure)) {
10523
- activity.progressMeasure = progressMeasure;
10524
- activity.progressMeasureStatus = true;
10525
- if (activity.primaryObjective) {
10526
- activity.primaryObjective.progressMeasure = progressMeasure;
10527
- activity.primaryObjective.progressMeasureStatus = true;
10528
- }
10529
- }
10530
- }
10531
10568
  }
10532
10569
  /**
10533
10570
  * Transfer non-primary objective data from CMI to activity objectives
@@ -10732,6 +10769,18 @@ class TerminationHandler {
10732
10769
  this.endAttempt(this.activityTree.currentActivity);
10733
10770
  }
10734
10771
  }
10772
+ const ancestorExitResult = this.applyAncestorExitActionRules(currentActivity);
10773
+ if (ancestorExitResult.action === "EXIT_ALL") {
10774
+ return this.handleExitAll(ancestorExitResult.activity || currentActivity);
10775
+ }
10776
+ if (ancestorExitResult.exception) {
10777
+ return {
10778
+ terminationRequest: SequencingRequestType.EXIT,
10779
+ sequencingRequest: null,
10780
+ exception: ancestorExitResult.exception,
10781
+ valid: false
10782
+ };
10783
+ }
10735
10784
  let processedExit;
10736
10785
  let postConditionResult;
10737
10786
  do {
@@ -10959,16 +11008,14 @@ class TerminationHandler {
10959
11008
  for (const rule of exitRules) {
10960
11009
  let conditionsMet;
10961
11010
  if (rule.conditionCombination === "all") {
10962
- conditionsMet = rule.conditions.every(
10963
- (condition) => condition.evaluate(activity)
10964
- );
11011
+ conditionsMet = rule.conditions.every((condition) => condition.evaluate(activity));
10965
11012
  } else {
10966
- conditionsMet = rule.conditions.some(
10967
- (condition) => condition.evaluate(activity)
10968
- );
11013
+ conditionsMet = rule.conditions.some((condition) => condition.evaluate(activity));
10969
11014
  }
10970
11015
  if (conditionsMet) {
10971
- if (rule.action === RuleActionType.EXIT_PARENT) {
11016
+ if (rule.action === RuleActionType.EXIT) {
11017
+ return { action: "EXIT", recursionDepth };
11018
+ } else if (rule.action === RuleActionType.EXIT_PARENT) {
10972
11019
  return { action: "EXIT_PARENT", recursionDepth };
10973
11020
  } else if (rule.action === RuleActionType.EXIT_ALL) {
10974
11021
  return { action: "EXIT_ALL", recursionDepth };
@@ -11002,6 +11049,55 @@ class TerminationHandler {
11002
11049
  this.processExitAtLevel(rootActivity, 0);
11003
11050
  this.terminateAll(rootActivity);
11004
11051
  }
11052
+ /**
11053
+ * Sequencing Exit Action Rules Subprocess (TB.2.1) ancestor walk
11054
+ * @spec SN Book: TB.2.1 (Sequencing Exit Action Rules Subprocess)
11055
+ * @param {Activity} terminatingActivity - The activity whose attempt just ended
11056
+ * @return {{action: string | null, activity?: Activity, exception?: string}}
11057
+ */
11058
+ applyAncestorExitActionRules(terminatingActivity) {
11059
+ const activityPath = [];
11060
+ let current = terminatingActivity.parent;
11061
+ while (current) {
11062
+ activityPath.unshift(current);
11063
+ current = current.parent;
11064
+ }
11065
+ for (const activity of activityPath) {
11066
+ const exitAction = this.exitActionRulesSubprocess(activity);
11067
+ if (exitAction === "EXIT_ALL") {
11068
+ this.fireEvent("onAncestorExitAction", {
11069
+ activity: activity.id,
11070
+ action: exitAction
11071
+ });
11072
+ return { action: "EXIT_ALL", activity };
11073
+ }
11074
+ if (exitAction === "EXIT_PARENT") {
11075
+ if (!activity.parent) {
11076
+ return { action: "EXIT_PARENT", activity, exception: "TB.2.3-4" };
11077
+ }
11078
+ this.terminateDescendants(activity.parent);
11079
+ this.activityTree.currentActivity = activity.parent;
11080
+ this.endAttempt(activity.parent);
11081
+ this.fireEvent("onAncestorExitAction", {
11082
+ activity: activity.id,
11083
+ action: exitAction,
11084
+ currentActivity: activity.parent.id
11085
+ });
11086
+ return { action: "EXIT_PARENT", activity: activity.parent };
11087
+ }
11088
+ if (exitAction === "EXIT") {
11089
+ this.terminateDescendants(activity);
11090
+ this.activityTree.currentActivity = activity;
11091
+ this.endAttempt(activity);
11092
+ this.fireEvent("onAncestorExitAction", {
11093
+ activity: activity.id,
11094
+ action: exitAction
11095
+ });
11096
+ return { action: "EXIT", activity };
11097
+ }
11098
+ }
11099
+ return { action: null };
11100
+ }
11005
11101
  /**
11006
11102
  * Process exit actions at specific level
11007
11103
  * @param {Activity} activity - Activity to process
@@ -11084,16 +11180,14 @@ class TerminationHandler {
11084
11180
  for (const rule of exitRules) {
11085
11181
  let conditionsMet;
11086
11182
  if (rule.conditionCombination === "all") {
11087
- conditionsMet = rule.conditions.every(
11088
- (condition) => condition.evaluate(activity)
11089
- );
11183
+ conditionsMet = rule.conditions.every((condition) => condition.evaluate(activity));
11090
11184
  } else {
11091
- conditionsMet = rule.conditions.some(
11092
- (condition) => condition.evaluate(activity)
11093
- );
11185
+ conditionsMet = rule.conditions.some((condition) => condition.evaluate(activity));
11094
11186
  }
11095
11187
  if (conditionsMet) {
11096
- if (rule.action === RuleActionType.EXIT_PARENT) {
11188
+ if (rule.action === RuleActionType.EXIT) {
11189
+ return "EXIT";
11190
+ } else if (rule.action === RuleActionType.EXIT_PARENT) {
11097
11191
  return "EXIT_PARENT";
11098
11192
  } else if (rule.action === RuleActionType.EXIT_ALL) {
11099
11193
  return "EXIT_ALL";
@@ -14647,7 +14741,29 @@ class SequencingService {
14647
14741
  * Update activity properties from current CMI values
14648
14742
  */
14649
14743
  updateActivityFromCMI(activity) {
14650
- if (this.cmi.completion_status !== "unknown") {
14744
+ let hasProgressMeasure = false;
14745
+ if (this.cmi.progress_measure !== "") {
14746
+ const progressMeasure = parseFloat(this.cmi.progress_measure);
14747
+ if (!isNaN(progressMeasure)) {
14748
+ hasProgressMeasure = true;
14749
+ activity.progressMeasure = progressMeasure;
14750
+ activity.progressMeasureStatus = true;
14751
+ activity.attemptCompletionAmount = progressMeasure;
14752
+ activity.attemptCompletionAmountStatus = true;
14753
+ }
14754
+ }
14755
+ if (!hasProgressMeasure) {
14756
+ activity.attemptCompletionAmountStatus = false;
14757
+ }
14758
+ if (activity.completedByMeasure) {
14759
+ const completionStatus = evaluateCompletionStatusFromThreshold({
14760
+ completionThreshold: activity.minProgressMeasure,
14761
+ progressMeasure: this.cmi.progress_measure,
14762
+ storedCompletionStatus: CompletionStatus.UNKNOWN
14763
+ });
14764
+ activity.completionStatus = completionStatus;
14765
+ activity.attemptProgressStatus = completionStatus !== CompletionStatus.UNKNOWN;
14766
+ } else if (this.cmi.completion_status !== "unknown") {
14651
14767
  activity.completionStatus = this.cmi.completion_status;
14652
14768
  activity.attemptProgressStatus = true;
14653
14769
  }
@@ -14659,13 +14775,6 @@ class SequencingService {
14659
14775
  activity.primaryObjective.progressStatus = true;
14660
14776
  }
14661
14777
  }
14662
- if (this.cmi.progress_measure !== "") {
14663
- const progressMeasure = parseFloat(this.cmi.progress_measure);
14664
- if (!isNaN(progressMeasure)) {
14665
- activity.progressMeasure = progressMeasure;
14666
- activity.progressMeasureStatus = true;
14667
- }
14668
- }
14669
14778
  if (this.cmi.score && this.cmi.score.scaled !== "") {
14670
14779
  const scaledScore = parseFloat(this.cmi.score.scaled);
14671
14780
  if (!isNaN(scaledScore)) {
@@ -18859,6 +18968,7 @@ class CMI extends BaseRootCMI {
18859
18968
  */
18860
18969
  reset() {
18861
18970
  this._initialized = false;
18971
+ this._start_time = void 0;
18862
18972
  this.metadata?.reset();
18863
18973
  this.learner?.reset();
18864
18974
  this.status?.reset();
@@ -20822,22 +20932,11 @@ class Scorm2004CMIHandler {
20822
20932
  * @returns {string} The evaluated completion status
20823
20933
  */
20824
20934
  evaluateCompletionStatus() {
20825
- const threshold = this.context.cmi.completion_threshold;
20826
- const progressMeasure = this.context.cmi.progress_measure;
20827
- const storedStatus = this.context.cmi.completion_status;
20828
- if (threshold !== "" && threshold !== null && threshold !== void 0) {
20829
- const thresholdValue = parseFloat(String(threshold));
20830
- if (!isNaN(thresholdValue)) {
20831
- if (progressMeasure !== "" && progressMeasure !== null && progressMeasure !== void 0) {
20832
- const progressValue = parseFloat(String(progressMeasure));
20833
- if (!isNaN(progressValue)) {
20834
- return progressValue >= thresholdValue ? CompletionStatus.COMPLETED : CompletionStatus.INCOMPLETE;
20835
- }
20836
- }
20837
- return CompletionStatus.UNKNOWN;
20838
- }
20839
- }
20840
- return storedStatus || CompletionStatus.UNKNOWN;
20935
+ return evaluateCompletionStatusFromThreshold({
20936
+ completionThreshold: this.context.cmi.completion_threshold,
20937
+ progressMeasure: this.context.cmi.progress_measure,
20938
+ storedCompletionStatus: this.context.cmi.completion_status
20939
+ });
20841
20940
  }
20842
20941
  /**
20843
20942
  * Evaluates success_status per SCORM 2004 RTE Table 4.2.21.1a
@@ -21497,6 +21596,21 @@ class ActivityTreeBuilder {
21497
21596
  if (activitySettings.rollupConsiderations) {
21498
21597
  activity.applyRollupConsiderations(activitySettings.rollupConsiderations);
21499
21598
  }
21599
+ if (activitySettings.completionThreshold) {
21600
+ const threshold = activitySettings.completionThreshold;
21601
+ if (threshold.completedByMeasure !== void 0) {
21602
+ activity.completedByMeasure = threshold.completedByMeasure;
21603
+ }
21604
+ if (threshold.minProgressMeasure !== void 0) {
21605
+ activity.minProgressMeasure = threshold.minProgressMeasure;
21606
+ activity.completionThreshold = threshold.minProgressMeasure.toString();
21607
+ } else if (threshold.completedByMeasure) {
21608
+ activity.completionThreshold = activity.minProgressMeasure.toString();
21609
+ }
21610
+ if (threshold.progressWeight !== void 0) {
21611
+ activity.progressWeight = threshold.progressWeight;
21612
+ }
21613
+ }
21500
21614
  if (activitySettings.hideLmsUi) {
21501
21615
  const mergedHide = this.sequencingConfigBuilder.mergeHideLmsUi(
21502
21616
  activity.hideLmsUi,
@@ -22540,8 +22654,23 @@ class Scorm2004API extends BaseAPI {
22540
22654
  reset(settings) {
22541
22655
  this.commonReset(settings);
22542
22656
  this.cmi?.reset();
22657
+ this.applyCurrentActivityLaunchData();
22543
22658
  this.adl?.reset();
22544
22659
  }
22660
+ /**
22661
+ * Apply launch-static activity data to CMI while the new SCO is pre-initialize.
22662
+ *
22663
+ * @spec SCORM 2004 4th Ed. RTE 4.2.9 - cmi.completion_threshold
22664
+ */
22665
+ applyCurrentActivityLaunchData() {
22666
+ const currentActivity = this._sequencing?.getCurrentActivity();
22667
+ if (!this.cmi || !currentActivity) {
22668
+ return;
22669
+ }
22670
+ const contentActivityData = this._sequencing.overallSequencingProcess?.getContentActivityData(currentActivity);
22671
+ const completionThreshold = contentActivityData?.completionThreshold ?? currentActivity.completionThreshold?.toString() ?? "";
22672
+ this.cmi.completion_threshold = completionThreshold;
22673
+ }
22545
22674
  /**
22546
22675
  * Getter for _version
22547
22676
  * @return {string}
@@ -23052,14 +23181,14 @@ class Scorm2004API extends BaseAPI {
23052
23181
  if (this.cmi.mode === "normal") {
23053
23182
  if (this.cmi.credit === "credit") {
23054
23183
  if (this.cmi.completion_threshold && this.cmi.progress_measure) {
23055
- if (this.cmi.progress_measure >= this.cmi.completion_threshold) {
23184
+ if (parseFloat(this.cmi.progress_measure) >= parseFloat(this.cmi.completion_threshold)) {
23056
23185
  this.cmi.completion_status = "completed";
23057
23186
  } else {
23058
23187
  this.cmi.completion_status = "incomplete";
23059
23188
  }
23060
23189
  }
23061
23190
  if (this.cmi.scaled_passing_score && this.cmi.score.scaled) {
23062
- if (this.cmi.score.scaled >= this.cmi.scaled_passing_score) {
23191
+ if (parseFloat(this.cmi.score.scaled) >= parseFloat(this.cmi.scaled_passing_score)) {
23063
23192
  this.cmi.success_status = "passed";
23064
23193
  } else {
23065
23194
  this.cmi.success_status = "failed";