Merge pull request #3809 from dpalou/MOBILE-4362

Mobile 4362
main
Noel De Martin 2023-10-09 15:48:14 +02:00 committed by GitHub
commit ee2e7851b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 1385 additions and 32 deletions

View File

@ -275,13 +275,13 @@ class behat_app_helper extends behat_base {
);
$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,
];
}
@ -608,4 +608,37 @@ EOF;
$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;
}
}
}

View File

@ -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);
}
/**
@ -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);

View File

@ -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;
@ -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;
@ -151,7 +156,7 @@ export class AddonModScormProvider {
let total = 0;
for (const attemptNumber in onlineAttempts) {
sumGrades += onlineAttempts[attemptNumber].grade;
sumGrades += onlineAttempts[attemptNumber].score;
total++;
}
@ -589,8 +594,8 @@ 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.
@ -598,7 +603,12 @@ export class AddonModScormProvider {
* @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,
@ -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,
};
}
/**
@ -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.
};
/**

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,42 @@
@mod @mod_scorm @app @javascript
Feature: Test availability options of SCORM activity in app
Only open SCORMs should be allowed to be played
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 |
And the following "activities" exist:
| activity | course | name | packagefilepath | timeopen | timeclose |
| scorm | C1 | Past SCORM | mod/scorm/tests/packages/singlesco_scorm12.zip | ##-2 days## | ##yesterday## |
| scorm | C1 | Current SCORM | mod/scorm/tests/packages/singlesco_scorm12.zip | ##yesterday## | ##tomorrow## |
| scorm | C1 | Future SCORM | mod/scorm/tests/packages/singlesco_scorm12.zip | ##tomorrow## | ##+2 days## |
Scenario: Only open SCORMs can be played
Given I entered the course "Course 1" as "student1" in the app
When I press "Past SCORM" in the app
Then I should find "Sorry, this activity closed on ## yesterday ##%A, %d %B %Y, %I:%M %p## and is no longer available" in the app
And I should find "## -2 days ##%d %B %Y, %I:%M %p##" near "Opened:" in the app
And I should find "## yesterday ##%d %B %Y, %I:%M %p##" near "Closed:" in the app
And I should not be able to press "Enter" in the app
When I press the back button in the app
And I press "Current SCORM" in the app
Then I should find "## yesterday ##%d %B %Y, %I:%M %p##" near "Opened:" in the app
And I should find "## tomorrow ##%d %B %Y, %I:%M %p##" near "Closes:" in the app
And I should be able to press "Enter" in the app
When I press the back button in the app
And I press "Future SCORM" in the app
Then I should find "Sorry, this activity is not available until ## tomorrow ##%A, %d %B %Y, %I:%M %p##" in the app
And I should find "## tomorrow ##%d %B %Y, %I:%M %p##" near "Opens:" in the app
And I should find "## +2days ##%d %B %Y, %I:%M %p##" near "Closes:" in the app
And I should not be able to press "Enter" in the app

View File

@ -38,7 +38,6 @@ Feature: Test basic usage of SCORM activity in app
When I switch to the main frame
And I press the back button in the app
And I wait loading to finish in the app
Then I should find "1" within "Number of attempts you have made" "ion-item" in the app
And I should find "3" within "Grade reported" "ion-item" in the app
@ -102,7 +101,6 @@ Feature: Test basic usage of SCORM activity in app
When I press "Close" in the app
And I press the back button in the app
And I wait loading to finish in the app
Then I should find "Completed" within "How to Play" "ion-item" in the app
And I should find "Completed" within "Par?" "ion-item" in the app
And I should find "Not attempted" within "Keeping Score" "ion-item" in the app
@ -143,7 +141,6 @@ Feature: Test basic usage of SCORM activity in app
Then I should find "11 / 11" in the app
When I press the back button in the app
And I wait loading to finish in the app
Then I should find "1" within "Number of attempts you have made" "ion-item" in the app
And I should find "9" within "Grade reported" "ion-item" in the app
@ -157,13 +154,94 @@ Feature: Test basic usage of SCORM activity in app
When I press "Close" in the app
And I press the back button in the app
And I wait loading to finish in the app
Then I should find "1" within "Number of attempts you have made" "ion-item" in the app
Scenario: Unsupported SCORM
Given the following "activities" exist:
| activity | name | course | idnumber | packagefilepath |
| scorm | SCORM 1.2 | C1 | scorm2 | mod/scorm/tests/packages/RuntimeBasicCalls_SCORM20043rdEdition.zip |
| scorm | SCORM 1.2 | C1 | scorm | mod/scorm/tests/packages/RuntimeBasicCalls_SCORM20043rdEdition.zip |
And I entered the course "Course 1" as "student1" in the app
When I press "SCORM 1.2" in the app
Then I should find "Sorry, the application only supports SCORM 1.2." in the app
Scenario: Hidden SCOs not displayed in TOC
Given the following "activities" exist:
| activity | name | course | idnumber | packagefilepath | displaycoursestructure | hidetoc |
| scorm | Complex SCORM | C1 | scorm | mod/scorm/tests/packages/complexscorm.zip | 1 | 0 |
And I entered the course "Course 1" as "student1" in the app
When I press "Complex SCORM" in the app
Then I should find "The first content (one SCO)" in the app
But I should not find "SCO not visible" in the app
When 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 "The first content (one SCO)" in the app
But I should not find "SCO not visible" in the app
Scenario: SCOs with prerequisites cannot be opened until prerequisites have been fulfilled
Given the following "activities" exist:
| activity | name | course | idnumber | packagefilepath | displaycoursestructure | hidetoc |
| scorm | Complex SCORM | C1 | scorm | mod/scorm/tests/packages/complexscorm.zip | 1 | 0 |
And I entered the course "Course 1" as "student1" in the app
When I press "Complex SCORM" in the app
Then I should find "SCO with prerequisite (first and secon SCO)" in the app
When I press "SCO with prerequisite (first and secon SCO)" 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
When I press "The first content (one SCO)" in the app
And I press "Disable fullscreen" in the app
And I switch to "scorm_object" iframe
And I click on "Common operations" "link"
And I click on "#set-lesson-status-button" "css_element"
And I click on "#ui-id-12" "css_element"
And I click on "#set-score-button" "css_element"
And I click on "#ui-id-26" "css_element"
And I press "Commit changes"
And I switch to the main frame
And I press "TOC" in the app
Then I should find "Passed" within "The first content (one SCO)" "ion-item" in the app
And I should not be able to press "SCO with prerequisite (first and secon SCO)" in the app
When I press "The second content (one SCO too)" in the app
And I switch to "scorm_object" iframe
And I click on "Common operations" "link"
And I click on "#set-lesson-status-button" "css_element"
And I click on "#ui-id-13" "css_element"
And I click on "#set-score-button" "css_element"
And I click on "#ui-id-28" "css_element"
And I press "Commit changes"
And I switch to the main frame
And I press "TOC" in the app
Then I should find "Completed" within "The second content (one SCO too)" "ion-item" in the app
And I should be able to press "SCO with prerequisite (first and secon SCO)" in the app
Scenario: View events are stored in the log
Given the following "activities" exist:
| activity | name | course | idnumber | packagefilepath |
| scorm | Basic SCORM | C1 | scorm | mod/scorm/tests/packages/RuntimeMinimumCalls_SCORM12-mini.zip |
And I entered the course "Course 1" as "student1" in the app
When I press "Basic SCORM" in the app
And I press "Enter" in the app
And I press "Disable fullscreen" in the app
Then I should find "2 / 11" in the app
When I open a browser tab with url "$WWWROOT"
And I log in as "admin"
And I am on the "System logs report" page
And I set the field "id" to "Course 1"
And I set the field "user" to "Student student"
And I press "Get these logs"
Then I should see "SCORM package: Basic SCORM" in the "Course module viewed" "table_row"
And I should see "SCORM package: Basic SCORM" in the "Sco launched" "table_row"
And I should see "1" occurrences of "Sco launched" in the "reportlog" "table"
When I switch back to the app
And I press "Next" in the app
Then I should find "3 / 11" in the app
When I switch to the browser tab opened by the app
And I press "Get these logs"
Then I should see "2" occurrences of "Sco launched" in the "reportlog" "table"

View File

@ -111,7 +111,7 @@ export class CoreSite {
'4.0': 2022041900,
'4.1': 2022112800,
'4.2': 2023042400,
'4.3': 2024000000, // @todo [4.3] replace with right value when released. Using a tmp value to be able to test new things.
'4.3': 2023100900,
};
// Possible cache update frequencies.