Skip to content

Commit

Permalink
Merge pull request #3809 from dpalou/MOBILE-4362
Browse files Browse the repository at this point in the history
Mobile 4362
  • Loading branch information
NoelDeMartin authored Oct 9, 2023
2 parents 63bd215 + f9e31e5 commit ee2e785
Show file tree
Hide file tree
Showing 11 changed files with 1,385 additions and 32 deletions.
37 changes: 35 additions & 2 deletions local_moodleappbehat/tests/behat/behat_app_helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -275,13 +275,13 @@ public function parse_element_locator(string $text): string {
);

$locator = [
'text' => str_replace('\\"', '"', $matches[1]),
'text' => $this->transform_time_to_string(str_replace('\\"', '"', $matches[1])),
'selector' => $matches[2] ?? null,
];

if (!empty($matches[3])) {
$locator[$matches[3]] = (object) [
'text' => str_replace('\\"', '"', $matches[4]),
'text' => $this->transform_time_to_string(str_replace('\\"', '"', $matches[4])),
'selector' => $matches[5] ?? null,
];
}
Expand Down Expand Up @@ -608,4 +608,37 @@ protected function resize_app_window(int $width = 500, int $height = 720) {

$this->getSession()->getDriver()->resizeWindow($width + $offset['x'], $height + $offset['y']);
}

/**
* Given a string, search if it contains a time with the ## format and convert it to a timestamp or readable time.
* Only allows 1 occurence, if the text contains more than one time sub-string it won't work as expected.
* This function is similar to the arg_time_to_string transformation, but it allows the time to be a sub-text of the string.
*
* @param string $text
* @return string Transformed text.
*/
protected function transform_time_to_string(string $text): string {
if (!preg_match('/##(.*)##/', $text, $matches)) {
// No time found, return the original text.
return $text;
}

$timepassed = explode('##', $matches[1]);

// If not a valid time string, then just return what was passed.
if ((($timestamp = strtotime($timepassed[0])) === false)) {
return $text;
}

$count = count($timepassed);
if ($count === 2) {
// If timestamp with specified strftime format, then return formatted date string.
return str_replace($matches[0], userdate($timestamp, $timepassed[1]), $text);
} else if ($count === 1) {
return str_replace($matches[0], $timestamp, $text);
} else {
// If not a valid time string, then just return what was passed.
return $text;
}
}
}
11 changes: 3 additions & 8 deletions src/addons/mod/scorm/components/index/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,12 +301,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
return;
}

const grade = await AddonModScorm.getAttemptGrade(this.scorm, attempt, offline);

attempts[attempt] = {
num: attempt,
grade: grade,
};
attempts[attempt] = await AddonModScorm.getAttemptGrade(this.scorm, attempt, offline);
}

/**
Expand Down Expand Up @@ -344,10 +339,10 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom

// Now format the grades.
this.onlineAttempts.forEach((attempt) => {
attempt.gradeFormatted = AddonModScorm.formatGrade(scorm, attempt.grade);
attempt.gradeFormatted = AddonModScorm.formatGrade(scorm, attempt.score);
});
this.offlineAttempts.forEach((attempt) => {
attempt.gradeFormatted = AddonModScorm.formatGrade(scorm, attempt.grade);
attempt.gradeFormatted = AddonModScorm.formatGrade(scorm, attempt.score);
});

this.gradeFormatted = AddonModScorm.formatGrade(scorm, this.grade);
Expand Down
47 changes: 31 additions & 16 deletions src/addons/mod/scorm/services/scorm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,17 +119,22 @@ export class AddonModScormProvider {

switch (scorm.whatgrade) {
case AddonModScormProvider.FIRSTATTEMPT:
return onlineAttempts[1] ? onlineAttempts[1].grade : -1;
return onlineAttempts[1] ? onlineAttempts[1].score : -1;

case AddonModScormProvider.LASTATTEMPT: {
// Search the last attempt number.
let max = 0;
Object.keys(onlineAttempts).forEach((attemptNumber) => {
max = Math.max(Number(attemptNumber), max);
});
// Search the last completed attempt number.
let lastCompleted = 0;
for (const attemptNumber in onlineAttempts) {
if (onlineAttempts[attemptNumber].hasCompletedPassedSCO) {
lastCompleted = Math.max(onlineAttempts[attemptNumber].num, lastCompleted);
}
}

if (max > 0) {
return onlineAttempts[max].grade;
if (lastCompleted > 0) {
return onlineAttempts[lastCompleted].score;
} else if (onlineAttempts[1]) {
// If no completed attempt found, use the first attempt for consistency with LMS.
return onlineAttempts[1].score;
}

return -1;
Expand All @@ -139,7 +144,7 @@ export class AddonModScormProvider {
// Search the highest grade.
let grade = 0;
for (const attemptNumber in onlineAttempts) {
grade = Math.max(onlineAttempts[attemptNumber].grade, grade);
grade = Math.max(onlineAttempts[attemptNumber].score, grade);
}

return grade;
Expand All @@ -151,7 +156,7 @@ export class AddonModScormProvider {
let total = 0;

for (const attemptNumber in onlineAttempts) {
sumGrades += onlineAttempts[attemptNumber].grade;
sumGrades += onlineAttempts[attemptNumber].score;
total++;
}

Expand Down Expand Up @@ -589,16 +594,21 @@ export class AddonModScormProvider {
}

/**
* Get the grade for a certain SCORM and attempt.
* Based on Moodle's scorm_grade_user_attempt.
* Get the grade data for a certain attempt.
* Mostly based on Moodle's scorm_grade_user_attempt.
*
* @param scorm SCORM.
* @param attempt Attempt number.
* @param offline Whether the attempt is offline.
* @param siteId Site ID. If not defined, current site.
* @returns Promise resolved with the grade. If the attempt hasn't reported grade/completion, it will be -1.
*/
async getAttemptGrade(scorm: AddonModScormScorm, attempt: number, offline?: boolean, siteId?: string): Promise<number> {
async getAttemptGrade(
scorm: AddonModScormScorm,
attempt: number,
offline?: boolean,
siteId?: string,
): Promise<AddonModScormAttemptGrade> {
const attemptScore = {
scos: 0,
values: 0,
Expand Down Expand Up @@ -654,7 +664,11 @@ export class AddonModScormProvider {
score = attemptScore.max; // Remote Learner GRADEHIGHEST is default.
}

return score;
return {
num: attempt,
score,
hasCompletedPassedSCO: attemptScore.scos > 0,
};
}

/**
Expand Down Expand Up @@ -2052,11 +2066,12 @@ export type AddonModScormOrganization = {
};

/**
* Grade for an attempt.
* Grade data for an attempt.
*/
export type AddonModScormAttemptGrade = {
num: number;
grade: number;
score: number;
hasCompletedPassedSCO: boolean; // Whether it has at least 1 SCO with status completed or passed.
};

/**
Expand Down
170 changes: 170 additions & 0 deletions src/addons/mod/scorm/tests/behat/appearance_options.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
@mod @mod_scorm @app @javascript
Feature: Test appearance options of SCORM activity in app
In order to play a SCORM while using the mobile app
As a student
I need appearance options to be applied properly

Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | teacher | teacher1@example.com |
| student1 | Student | student | student1@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |

Scenario: Apply width and height when using New window mode
Given the following "activities" exist:
| activity | name | course | idnumber | packagefilepath | popup | width | height |
| scorm | Current window SCORM | C1 | scorm | mod/scorm/tests/packages/RuntimeMinimumCalls_SCORM12-mini.zip | 0 | 300 | 300 |
| scorm | New window px SCORM | C1 | scorm2 | mod/scorm/tests/packages/RuntimeMinimumCalls_SCORM12-mini.zip | 1 | 300 | 300 |
| scorm | New window perc SCORM | C1 | scorm3 | mod/scorm/tests/packages/RuntimeMinimumCalls_SCORM12-mini.zip | 1 | 50% | 60% |
And I entered the course "Course 1" as "student1" in the app
And I change viewport size to "1200x640" in the app
When I press "Current window SCORM" in the app
And I press "Enter" in the app
And I press "Disable fullscreen" in the app
Then the UI should match the snapshot

When I press the back button in the app
And I press the back button in the app
And I press "New window px SCORM" in the app
And I press "Enter" in the app
And I press "Disable fullscreen" in the app
Then the UI should match the snapshot

# SCORMs with percentage sizes are displayed with full size in the app. See MOBILE-3426 for details.
When I press the back button in the app
And I press the back button in the app
And I press "New window perc SCORM" in the app
And I press "Enter" in the app
And I press "Disable fullscreen" in the app
Then the UI should match the snapshot

Scenario: Skip SCORM entry page if needed
Given the following "activities" exist:
| activity | name | course | idnumber | packagefilepath | skipview |
| scorm | No skip SCORM | C1 | scorm | mod/scorm/tests/packages/RuntimeMinimumCalls_SCORM12-mini.zip | 0 |
| scorm | Skip first access SCORM | C1 | scorm2 | mod/scorm/tests/packages/RuntimeMinimumCalls_SCORM12-mini.zip | 1 |
| scorm | Always skip SCORM | C1 | scorm3 | mod/scorm/tests/packages/RuntimeMinimumCalls_SCORM12-mini.zip | 2 |
And I entered the course "Course 1" as "student1" in the app
When I press "No skip SCORM" in the app
Then I should be able to press "Enter" in the app

When I press the back button in the app
And I press "Skip first access SCORM" in the app
And I press "Disable fullscreen" in the app
Then I should find "2 / 11" in the app

When I press the back button in the app
And I press the back button in the app
And I press "Skip first access SCORM" in the app
Then I should be able to press "Enter" in the app
And I should not be able to press "Disable fullscreen" in the app
And I should not find "3 / 11" in the app

When I press the back button in the app
And I press "Always skip SCORM" in the app
And I press "Disable fullscreen" in the app
Then I should find "2 / 11" in the app

When I press the back button in the app
And I press the back button in the app
And I press "Always skip SCORM" in the app
And I press "Disable fullscreen" in the app
Then I should find "3 / 11" in the app

Scenario: Disable preview mode
Given the following "activities" exist:
| activity | name | course | idnumber | packagefilepath | hidebrowse |
| scorm | SCORM without preview | C1 | scorm | mod/scorm/tests/packages/RuntimeMinimumCalls_SCORM12-mini.zip | 1 |
| scorm | SCORM with preview | C1 | scorm2 | mod/scorm/tests/packages/RuntimeMinimumCalls_SCORM12-mini.zip | 0 |
And I entered the course "Course 1" as "student1" in the app
When I press "SCORM without preview" in the app
Then I should not be able to press "Preview" in the app

When I press the back button in the app
And I press "SCORM with preview" in the app
Then I should be able to press "Preview" in the app

Scenario: Display course structure on entry page
Given the following "activities" exist:
| activity | name | course | idnumber | packagefilepath | displaycoursestructure |
| scorm | SCORM without structure | C1 | scorm | mod/scorm/tests/packages/RuntimeMinimumCalls_SCORM12-mini.zip | 0 |
| scorm | SCORM with structure | C1 | scorm2 | mod/scorm/tests/packages/RuntimeMinimumCalls_SCORM12-mini.zip | 1 |
And I entered the course "Course 1" as "student1" in the app
When I press "SCORM without structure" in the app
Then I should not find "Other Scoring Systems" in the app

When I press the back button in the app
And I press "SCORM with structure" in the app
Then I should find "Other Scoring Systems" in the app

Scenario: Display course structure in player
Given the following "activities" exist:
| activity | name | course | idnumber | packagefilepath | hidetoc |
| scorm | SCORM To the side | C1 | scorm | mod/scorm/tests/packages/RuntimeMinimumCalls_SCORM12-mini.zip | 0 |
| scorm | SCORM Hidden | C1 | scorm2 | mod/scorm/tests/packages/RuntimeMinimumCalls_SCORM12-mini.zip | 1 |
| scorm | SCORM Drop Down | C1 | scorm3 | mod/scorm/tests/packages/RuntimeMinimumCalls_SCORM12-mini.zip | 2 |
| scorm | SCORM Disabled | C1 | scorm4 | mod/scorm/tests/packages/RuntimeMinimumCalls_SCORM12-mini.zip | 3 |
# In the app, the TOC is always displayed the same unless it's disabled.
And I entered the course "Course 1" as "student1" in the app
When I press "SCORM To the side" in the app
And I press "Enter" in the app
And I press "Disable fullscreen" in the app
And I press "TOC" in the app
Then I should find "Other Scoring Systems" in the app

When I press "Close" in the app
And I press the back button in the app
And I press the back button in the app
And I press "SCORM Hidden" in the app
And I press "Enter" in the app
And I press "Disable fullscreen" in the app
And I press "TOC" in the app
Then I should find "Other Scoring Systems" in the app

When I press "Close" in the app
And I press the back button in the app
And I press the back button in the app
And I press "SCORM Drop Down" in the app
And I press "Enter" in the app
And I press "Disable fullscreen" in the app
And I press "TOC" in the app
Then I should find "Other Scoring Systems" in the app

When I press "Close" in the app
And I press the back button in the app
And I press the back button in the app
And I press "SCORM Disabled" in the app
And I press "Enter" in the app
And I press "Disable fullscreen" in the app
And I should not be able to press "TOC" in the app

Scenario: Display attempt status
Given the following "activities" exist:
| activity | name | course | idnumber | packagefilepath | displayattemptstatus |
| scorm | SCORM no attempt status | C1 | scorm | mod/scorm/tests/packages/RuntimeMinimumCalls_SCORM12-mini.zip | 0 |
| scorm | SCORM both att status | C1 | scorm2 | mod/scorm/tests/packages/RuntimeMinimumCalls_SCORM12-mini.zip | 1 |
| scorm | SCORM dashb att status | C1 | scorm2 | mod/scorm/tests/packages/RuntimeMinimumCalls_SCORM12-mini.zip | 2 |
| scorm | SCORM entry att status | C1 | scorm2 | mod/scorm/tests/packages/RuntimeMinimumCalls_SCORM12-mini.zip | 3 |
# In the app, the attempt status is always displayed the same unless it's disabled.
And I entered the course "Course 1" as "student1" in the app
When I press "SCORM no attempt status" in the app
Then I should not find "Number of attempts allowed" in the app

When I press the back button in the app
And I press "SCORM both att status" in the app
Then I should find "Number of attempts allowed" in the app

When I press the back button in the app
And I press "SCORM dashb att status" in the app
Then I should find "Number of attempts allowed" in the app

When I press the back button in the app
And I press "SCORM entry att status" in the app
Then I should find "Number of attempts allowed" in the app
Loading

0 comments on commit ee2e785

Please sign in to comment.