commit
476a1795ae
|
@ -604,6 +604,7 @@ class behat_app extends behat_app_helper {
|
|||
$data = $data->getColumnsHash()[0];
|
||||
$title = array_keys($data)[0];
|
||||
$data = (object) $data;
|
||||
$username = $data->user ?? '';
|
||||
|
||||
switch ($title) {
|
||||
case 'discussion':
|
||||
|
@ -645,7 +646,7 @@ class behat_app extends behat_app_helper {
|
|||
throw new DriverException('Invalid custom link title - ' . $title);
|
||||
}
|
||||
|
||||
$this->open_moodleapp_custom_url($pageurl);
|
||||
$this->open_moodleapp_custom_url($pageurl, '', $username);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -366,12 +366,15 @@ class behat_app_helper extends behat_base {
|
|||
*
|
||||
* @param string $script
|
||||
* @param bool $blocking
|
||||
* @param string $texttofind If set, when this text is found the operation is considered finished. This is useful for
|
||||
* operations that might expect user input before finishing, like a confirm modal.
|
||||
* @return mixed Result.
|
||||
*/
|
||||
protected function zone_js(string $script, bool $blocking = false) {
|
||||
protected function zone_js(string $script, bool $blocking = false, string $texttofind = '') {
|
||||
$blockingjson = json_encode($blocking);
|
||||
$locatortofind = !empty($texttofind) ? json_encode((object) ['text' => $texttofind]) : null;
|
||||
|
||||
return $this->runtime_js("runInZone(() => window.behat.$script, $blockingjson)");
|
||||
return $this->runtime_js("runInZone(() => window.behat.$script, $blockingjson, $locatortofind)");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -411,16 +414,14 @@ class behat_app_helper extends behat_base {
|
|||
$privatetoken = $usertoken->privatetoken;
|
||||
}
|
||||
|
||||
// Generate custom URL.
|
||||
$parsed_url = parse_url($CFG->behat_wwwroot);
|
||||
$site = $parsed_url['host'] ?? '';
|
||||
$site .= isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '';
|
||||
$site .= $parsed_url['path'] ?? '';
|
||||
$url = $this->get_mobile_url_scheme() . "://$username@$site?token=$token&privatetoken=$privatetoken";
|
||||
$url = $this->generate_custom_url([
|
||||
'username' => $username,
|
||||
'token' => $token,
|
||||
'privatetoken' => $privatetoken,
|
||||
'redirect' => $path,
|
||||
]);
|
||||
|
||||
if (!empty($path)) {
|
||||
$url .= '&redirect='.urlencode($CFG->behat_wwwroot.$path);
|
||||
} else {
|
||||
if (empty($path)) {
|
||||
$successXPath = '//page-core-mainmenu';
|
||||
}
|
||||
|
||||
|
@ -434,14 +435,54 @@ class behat_app_helper extends behat_base {
|
|||
*
|
||||
* @param string $path To navigate.
|
||||
* @param string $successXPath The XPath of the element to lookat after navigation.
|
||||
* @param string $username The username to use.
|
||||
*/
|
||||
protected function open_moodleapp_custom_url(string $path, string $successXPath = '') {
|
||||
protected function open_moodleapp_custom_url(string $path, string $successXPath = '', string $username = '') {
|
||||
global $CFG;
|
||||
|
||||
$urlscheme = $this->get_mobile_url_scheme();
|
||||
$url = "$urlscheme://link=" . urlencode($CFG->behat_wwwroot.$path);
|
||||
$url = $this->generate_custom_url([
|
||||
'username' => $username,
|
||||
'redirect' => $path,
|
||||
]);
|
||||
|
||||
$this->handle_url($url);
|
||||
$this->handle_url($url, $successXPath, $username ? 'This link belongs to another site' : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a custom URL to be treated by the app.
|
||||
*
|
||||
* @param array $data Data to generate the URL.
|
||||
*/
|
||||
protected function generate_custom_url(array $data): string {
|
||||
global $CFG;
|
||||
|
||||
$parsed_url = parse_url($CFG->behat_wwwroot);
|
||||
$parameters = [];
|
||||
|
||||
$url = $this->get_mobile_url_scheme() . '://' . ($parsed_url['scheme'] ?? 'http') . '://';
|
||||
if (!empty($data['username'])) {
|
||||
$url .= $data['username'] . '@';
|
||||
}
|
||||
$url .= $parsed_url['host'] ?? '';
|
||||
$url .= isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '';
|
||||
$url .= $parsed_url['path'] ?? '';
|
||||
|
||||
if (!empty($data['token'])) {
|
||||
$parameters[] = 'token=' . $data['token'];
|
||||
if (!empty($data['privatetoken'])) {
|
||||
$parameters[] = 'privatetoken=' . $data['privatetoken'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($data['redirect'])) {
|
||||
$parameters[] = 'redirect=' . urlencode($data['redirect']);
|
||||
}
|
||||
|
||||
if (!empty($parameters)) {
|
||||
$url .= '?' . implode('&', $parameters);
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -449,9 +490,11 @@ class behat_app_helper extends behat_base {
|
|||
*
|
||||
* @param string $customurl To navigate.
|
||||
* @param string $successXPath The XPath of the element to lookat after navigation.
|
||||
* @param string $texttofind If set, when this text is found the operation is considered finished. This is useful for
|
||||
* operations that might expect user input before finishing, like a confirm modal.
|
||||
*/
|
||||
protected function handle_url(string $customurl, string $successXPath = '') {
|
||||
$result = $this->zone_js("customUrlSchemes.handleCustomURL('$customurl')");
|
||||
protected function handle_url(string $customurl, string $successXPath = '', string $texttofind = '') {
|
||||
$result = $this->zone_js("customUrlSchemes.handleCustomURL('$customurl')", false, $texttofind);
|
||||
|
||||
if ($result !== 'OK') {
|
||||
throw new DriverException('Error handling url - ' . $customurl . ' - '.$result);
|
||||
|
|
|
@ -132,7 +132,8 @@ Feature: Test basic usage of assignment activity in app
|
|||
Then I should find "No attempt" in the app
|
||||
|
||||
Scenario: Add submission offline (online text) & Submit for grading offline & Sync submissions
|
||||
Given I entered the assign activity "assignment1" on course "Course 1" as "student1" in the app
|
||||
Given I entered the course "Course 1" as "student1" in the app
|
||||
And I press "assignment1" in the app
|
||||
When I press "Add submission" in the app
|
||||
And I switch network connection to offline
|
||||
And I set the field "Online text submissions" to "Submission test" in the app
|
||||
|
@ -150,7 +151,8 @@ Feature: Test basic usage of assignment activity in app
|
|||
But I should not find "This Assignment has offline data to be synchronised." in the app
|
||||
|
||||
Scenario: Edit an offline submission before synchronising it
|
||||
Given I entered the assign activity "assignment1" on course "Course 1" as "student1" in the app
|
||||
Given I entered the course "Course 1" as "student1" in the app
|
||||
And I press "assignment1" in the app
|
||||
When I press "Add submission" in the app
|
||||
And I switch network connection to offline
|
||||
And I set the field "Online text submissions" to "Submission test original offline" in the app
|
||||
|
@ -178,8 +180,9 @@ Feature: Test basic usage of assignment activity in app
|
|||
|
||||
@lms_from4.5
|
||||
Scenario: Remove submission offline and syncrhonize it
|
||||
Given I entered the assign activity "assignment1" on course "Course 1" as "student1" in the app
|
||||
And I press "Add submission" in the app
|
||||
Given I entered the course "Course 1" as "student1" in the app
|
||||
And I press "assignment1" in the app
|
||||
When I press "Add submission" in the app
|
||||
And I set the field "Online text submissions" to "Submission test" in the app
|
||||
And I press "Save" in the app
|
||||
Then I should find "Draft (not submitted)" in the app
|
||||
|
@ -223,7 +226,8 @@ Feature: Test basic usage of assignment activity in app
|
|||
|
||||
@lms_from4.5
|
||||
Scenario: Add submission offline after removing a submission offline
|
||||
Given I entered the assign activity "assignment1" on course "Course 1" as "student1" in the app
|
||||
Given I entered the course "Course 1" as "student1" in the app
|
||||
And I press "assignment1" in the app
|
||||
When I press "Add submission" in the app
|
||||
And I set the field "Online text submissions" to "Submission test online" in the app
|
||||
And I press "Save" in the app
|
||||
|
|
|
@ -25,7 +25,8 @@ Feature: Test basic usage of BBB activity in app
|
|||
| bigbluebuttonbn | BBB 1 | Test BBB description | C1 | bbb1 | 0 | ## 1 January 2050 00:00 ## | 0 |
|
||||
| bigbluebuttonbn | BBB 2 | Test BBB description | C1 | bbb2 | 0 | 0 | ## 1 January 2000 00:00 ## |
|
||||
| bigbluebuttonbn | BBB 3 | Test BBB description | C1 | bbb3 | 0 | ## 1 January 2000 00:00 ## | ## 1 January 2050 00:00 ## |
|
||||
And I entered the bigbluebuttonbn activity "BBB 1" on course "Course 1" as "student1" in the app
|
||||
And I entered the course "Course 1" as "student1" in the app
|
||||
And I press "BBB 1" in the app
|
||||
Then I should find "The session has not started yet." in the app
|
||||
And I should find "Saturday, 1 January 2050, 12:00 AM" within "Open" "ion-item" in the app
|
||||
|
||||
|
@ -107,7 +108,8 @@ Feature: Test basic usage of BBB activity in app
|
|||
| bigbluebuttonbn | Room & recordings | C1 | bbb1 | 0 |
|
||||
| bigbluebuttonbn | Room only | C1 | bbb2 | 1 |
|
||||
| bigbluebuttonbn | Recordings only | C1 | bbb3 | 2 |
|
||||
And I entered the bigbluebuttonbn activity "Room & recordings" on course "Course 1" as "student1" in the app
|
||||
And I entered the course "Course 1" as "student1" in the app
|
||||
And I press "Room & recordings" in the app
|
||||
Then I should find "This room is ready. You can join the session now." in the app
|
||||
And I should be able to press "Join session" in the app
|
||||
And I should find "Recordings" in the app
|
||||
|
|
|
@ -21,7 +21,8 @@ Feature: Test basic usage of choice activity in app
|
|||
Given the following "activities" exist:
|
||||
| activity | name | intro | course | idnumber | option | allowmultiple | allowupdate | showresults |
|
||||
| choice | Test single choice name | Test single choice description | C1 | choice1 | Option 1, Option 2, Option 3 | 0 | 0 | 1 |
|
||||
And I entered the choice activity "Test single choice name" on course "Course 1" as "student1" in the app
|
||||
And I entered the course "Course 1" as "student1" in the app
|
||||
And I press "Test single choice name" in the app
|
||||
When I select "Option 1" in the app
|
||||
And I select "Option 2" in the app
|
||||
And I press "Save my choice" in the app
|
||||
|
@ -74,7 +75,8 @@ Feature: Test basic usage of choice activity in app
|
|||
Given the following "activities" exist:
|
||||
| activity | name | intro | course | idnumber | option | allowmultiple | allowupdate | showresults |
|
||||
| choice | Test single choice name | Test single choice description | C1 | choice1 | Option 1, Option 2, Option 3 | 0 | 0 | 1 |
|
||||
And I entered the choice activity "Test single choice name" on course "Course 1" as "student1" in the app
|
||||
And I entered the course "Course 1" as "student1" in the app
|
||||
And I press "Test single choice name" in the app
|
||||
When I select "Option 1" in the app
|
||||
And I switch network connection to offline
|
||||
And I select "Option 2" in the app
|
||||
|
|
|
@ -28,9 +28,11 @@ Feature: Users can store entries in database activities when offline and sync wh
|
|||
| data1 | text | Description | Link description |
|
||||
|
||||
Scenario: Create entry (offline)
|
||||
Given I entered the data activity "Web links" on course "Course 1" as "student1" in the app
|
||||
Given I entered the course "Course 1" as "student1" in the app
|
||||
And I press "Web links" in the app
|
||||
And I switch network connection to offline
|
||||
And I should find "No entries yet" in the app
|
||||
Then I should find "No entries yet" in the app
|
||||
|
||||
When I press "Add entry" in the app
|
||||
And I set the following fields to these values in the app:
|
||||
| URL | https://moodle.org/ |
|
||||
|
@ -39,29 +41,33 @@ Feature: Users can store entries in database activities when offline and sync wh
|
|||
Then I should find "https://moodle.org/" in the app
|
||||
And I should find "Moodle community site" in the app
|
||||
And I should find "This Database has offline data to be synchronised" in the app
|
||||
And I go back in the app
|
||||
|
||||
When I go back in the app
|
||||
And I switch network connection to wifi
|
||||
And I press "Web links" near "General" in the app
|
||||
And I should find "https://moodle.org/" in the app
|
||||
Then I should find "https://moodle.org/" in the app
|
||||
And I should find "Moodle community site" in the app
|
||||
And I should not find "This Database has offline data to be synchronised" in the app
|
||||
|
||||
Scenario: Update entry (offline) & Delete entry (offline)
|
||||
Given I entered the data activity "Web links" on course "Course 1" as "student1" in the app
|
||||
And I should find "No entries yet" in the app
|
||||
And I press "Add entry" in the app
|
||||
Given I entered the course "Course 1" as "student1" in the app
|
||||
And I press "Web links" in the app
|
||||
Then I should find "No entries yet" in the app
|
||||
|
||||
When I press "Add entry" in the app
|
||||
And I set the following fields to these values in the app:
|
||||
| URL | https://moodle.org/ |
|
||||
| Description | Moodle community site |
|
||||
And I press "Save" near "Web links" in the app
|
||||
And I should find "https://moodle.org/" in the app
|
||||
Then I should find "https://moodle.org/" in the app
|
||||
And I should find "Moodle community site" in the app
|
||||
And I press "Information" in the app
|
||||
|
||||
When I press "Information" in the app
|
||||
And I press "Download" in the app
|
||||
And I wait until the page is ready
|
||||
And I close the popup in the app
|
||||
And I switch network connection to offline
|
||||
When I press "Actions menu" in the app
|
||||
And I press "Actions menu" in the app
|
||||
And I press "Edit" in the app
|
||||
And I set the following fields to these values in the app:
|
||||
| URL | https://moodlecloud.com/ |
|
||||
|
@ -72,55 +78,64 @@ Feature: Users can store entries in database activities when offline and sync wh
|
|||
And I should find "https://moodlecloud.com/" in the app
|
||||
And I should find "Moodle Cloud" in the app
|
||||
And I should find "This Database has offline data to be synchronised" in the app
|
||||
And I go back in the app
|
||||
|
||||
When I go back in the app
|
||||
And I switch network connection to wifi
|
||||
And I press "Web links" near "General" in the app
|
||||
And I should not find "https://moodle.org/" in the app
|
||||
Then I should not find "https://moodle.org/" in the app
|
||||
And I should not find "Moodle community site" in the app
|
||||
And I should find "https://moodlecloud.com/" in the app
|
||||
And I should find "Moodle Cloud" in the app
|
||||
And I should not find "This Database has offline data to be synchronised" in the app
|
||||
And I press "Information" in the app
|
||||
|
||||
When I press "Information" in the app
|
||||
And I press "Refresh" in the app
|
||||
And I wait until the page is ready
|
||||
And I switch network connection to offline
|
||||
And I press "Actions menu" in the app
|
||||
And I press "Delete" in the app
|
||||
And I should find "Are you sure you want to delete this entry?" in the app
|
||||
And I press "Delete" in the app
|
||||
And I should find "https://moodlecloud.com/" in the app
|
||||
Then I should find "Are you sure you want to delete this entry?" in the app
|
||||
|
||||
When I press "Delete" in the app
|
||||
Then I should find "https://moodlecloud.com/" in the app
|
||||
And I should find "Moodle Cloud" in the app
|
||||
And I should find "This Database has offline data to be synchronised" in the app
|
||||
And I go back in the app
|
||||
|
||||
When I go back in the app
|
||||
And I switch network connection to wifi
|
||||
And I press "Web links" near "General" in the app
|
||||
And I should not find "https://moodlecloud.com/" in the app
|
||||
Then I should not find "https://moodlecloud.com/" in the app
|
||||
And I should not find "Moodle Cloud" in the app
|
||||
And I should not find "This Database has offline data to be synchronised" in the app
|
||||
|
||||
Scenario: Students can undo deleting entries to a database in the app while offline
|
||||
Given I entered the data activity "Web links" on course "Course 1" as "student1" in the app
|
||||
And I should find "No entries yet" in the app
|
||||
And I press "Add entry" in the app
|
||||
Given I entered the course "Course 1" as "student1" in the app
|
||||
And I press "Web links" in the app
|
||||
Then I should find "No entries yet" in the app
|
||||
|
||||
When I press "Add entry" in the app
|
||||
And I set the following fields to these values in the app:
|
||||
| URL | https://moodle.org/ |
|
||||
| Description | Moodle community site |
|
||||
And I press "Save" near "Web links" in the app
|
||||
And I should find "https://moodle.org/" in the app
|
||||
Then I should find "https://moodle.org/" in the app
|
||||
And I should find "Moodle community site" in the app
|
||||
And I press "Information" in the app
|
||||
|
||||
When I press "Information" in the app
|
||||
And I press "Download" in the app
|
||||
And I wait until the page is ready
|
||||
And I close the popup in the app
|
||||
When I switch network connection to offline
|
||||
And I switch network connection to offline
|
||||
And I press "Actions menu" in the app
|
||||
And I press "Delete" in the app
|
||||
And I should find "Are you sure you want to delete this entry?" in the app
|
||||
And I press "Delete" in the app
|
||||
And I should find "https://moodle.org/" in the app
|
||||
Then I should find "Are you sure you want to delete this entry?" in the app
|
||||
|
||||
When I press "Delete" in the app
|
||||
Then I should find "https://moodle.org/" in the app
|
||||
And I should find "Moodle community site" in the app
|
||||
And I should find "This Database has offline data to be synchronised" in the app
|
||||
And I press "Actions menu" in the app
|
||||
|
||||
When I press "Actions menu" in the app
|
||||
And I press "Restore" in the app
|
||||
And I go back in the app
|
||||
And I switch network connection to wifi
|
||||
|
|
|
@ -276,7 +276,8 @@ Feature: Test basic usage of forum activity in app
|
|||
But I should not find "Not sent" in the app
|
||||
|
||||
Scenario: New discussion offline & Sync Forum
|
||||
Given I entered the forum activity "Test forum name" on course "Course 1" as "student1" in the app
|
||||
Given I entered the course "Course 1" as "student1" in the app
|
||||
And I press "Test forum name" in the app
|
||||
When I switch network connection to offline
|
||||
And I press "Add discussion topic" in the app
|
||||
And I set the following fields to these values in the app:
|
||||
|
|
|
@ -35,7 +35,8 @@ Feature: Test usage of forum activity with groups in app
|
|||
| forum2 | Disc vis ALL | Disc vis ALL | Disc vis ALL content | All participants |
|
||||
|
||||
Scenario: Student can only see the right groups
|
||||
Given I entered the forum activity "Separate groups forum" on course "Course 1" as "student1" in the app
|
||||
Given I entered the course "Course 1" as "student1" in the app
|
||||
And I press "Separate groups forum" in the app
|
||||
Then I should find "Disc sep G1" in the app
|
||||
And I should find "Disc sep ALL" in the app
|
||||
But I should not find "Disc sep G2" in the app
|
||||
|
@ -65,7 +66,8 @@ Feature: Test usage of forum activity with groups in app
|
|||
But I should not find "Disc vis G2" in the app
|
||||
|
||||
Scenario: Teacher can see all groups
|
||||
Given I entered the forum activity "Separate groups forum" on course "Course 1" as "teacher1" in the app
|
||||
Given I entered the course "Course 1" as "teacher1" in the app
|
||||
And I press "Separate groups forum" in the app
|
||||
When I press "Separate groups" in the app
|
||||
Then I should find "All participants" in the app
|
||||
And I should find "Group 1" in the app
|
||||
|
@ -107,7 +109,8 @@ Feature: Test usage of forum activity with groups in app
|
|||
But I should not find "Disc vis G2" in the app
|
||||
|
||||
Scenario: Student can only add discussions in his groups
|
||||
Given I entered the forum activity "Separate groups forum" on course "Course 1" as "student1" in the app
|
||||
Given I entered the course "Course 1" as "student1" in the app
|
||||
And I press "Separate groups forum" in the app
|
||||
When I press "Add discussion topic" in the app
|
||||
And I press "Advanced" in the app
|
||||
Then I should not find "Post a copy to all groups" in the app
|
||||
|
@ -165,8 +168,9 @@ Feature: Test usage of forum activity with groups in app
|
|||
Then I should find "My happy subject" in the app
|
||||
|
||||
Scenario: Teacher can add discussion to any group
|
||||
Given I entered the forum activity "Separate groups forum" on course "Course 1" as "teacher1" in the app
|
||||
And I press "Separate groups" in the app
|
||||
Given I entered the course "Course 1" as "teacher1" in the app
|
||||
And I press "Separate groups forum" in the app
|
||||
When I press "Separate groups" in the app
|
||||
And I press "All participants" in the app
|
||||
And I press "Add discussion topic" in the app
|
||||
And I press "Advanced" in the app
|
||||
|
@ -279,8 +283,9 @@ Feature: Test usage of forum activity with groups in app
|
|||
Then I should find "My third subject" in the app
|
||||
|
||||
Scenario: Teacher can post a copy in all groups
|
||||
Given I entered the forum activity "Separate groups forum" on course "Course 1" as "teacher1" in the app
|
||||
And I press "Separate groups" in the app
|
||||
Given I entered the course "Course 1" as "teacher1" in the app
|
||||
And I press "Separate groups forum" in the app
|
||||
When I press "Separate groups" in the app
|
||||
And I press "Group 1" in the app
|
||||
And I press "Add discussion topic" in the app
|
||||
And I press "Advanced" in the app
|
||||
|
|
|
@ -52,7 +52,8 @@ Feature: Test basic usage of glossary in app
|
|||
|
||||
Scenario: Navigate to glossary terms by link (auto-linking)
|
||||
Given the "glossary" filter is "on"
|
||||
And I entered the glossary activity "Test glossary" on course "Course 1" as "student1" in the app
|
||||
And I entered the course "Course 1" as "student1" in the app
|
||||
And I press "Test glossary" in the app
|
||||
Then the header should be "Test glossary" in the app
|
||||
And I should find "Eggplant" in the app
|
||||
And I should find "Cucumber" in the app
|
||||
|
|
|
@ -49,8 +49,9 @@ Feature: Users can only review attempts that are allowed to be reviewed
|
|||
| 1 | True |
|
||||
|
||||
Scenario: Can review only when the attempt is allowed to be reviewed
|
||||
Given I entered the quiz activity "Quiz review after immed" on course "Course 1" as "student1" in the app
|
||||
And I press "Attempt 1" in the app
|
||||
Given I entered the course "Course 1" as "student1" in the app
|
||||
And I press "Quiz review after immed" in the app
|
||||
When I press "Attempt 1" in the app
|
||||
Then I should not be able to press "Review" in the app
|
||||
|
||||
When I go back in the app
|
||||
|
|
|
@ -238,8 +238,9 @@ Feature: Test basic usage of survey activity in app
|
|||
Given the following "activities" exist:
|
||||
| activity | name | intro | template | course | idnumber | groupmode |
|
||||
| survey | Test survey critical incidents | Test survey1 | 5 | C1 | survey1 | 0 |
|
||||
Given I entered the survey activity "Test survey critical incidents" on course "Course 1" as "student1" in the app
|
||||
And I switch network connection to offline
|
||||
And I entered the course "Course 1" as "student1" in the app
|
||||
And I press "Test survey critical incidents" in the app
|
||||
When I switch network connection to offline
|
||||
And I press "Submit" in the app
|
||||
And I press "OK" in the app
|
||||
Then I should see "This Survey has offline data to be synchronised."
|
||||
|
|
|
@ -130,7 +130,8 @@ Feature: Test basic usage of workshop activity in app
|
|||
| \mod_workshop\event\course_module_viewed | workshop | Test workshop | Course 1 |
|
||||
|
||||
Scenario: Prefetch a workshop
|
||||
Given I entered the workshop activity "workshop" on course "Course 1" as "teacher1" in the app
|
||||
Given I entered the course "Course 1" as "teacher1" in the app
|
||||
And I press "workshop" in the app
|
||||
When I press "Information" in the app
|
||||
And I press "Download" in the app
|
||||
And I press "Close" in the app
|
||||
|
|
|
@ -19,6 +19,7 @@ import { CoreUrl } from '@singletons/url';
|
|||
import { makeSingleton } from '@singletons';
|
||||
import { CoreText } from '@singletons/text';
|
||||
import { CorePromiseUtils } from '@singletons/promise-utils';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
|
||||
/**
|
||||
* Interface that all handlers must implement.
|
||||
|
@ -208,24 +209,24 @@ export class CoreContentLinksDelegateService {
|
|||
// Wrap the action function in our own function to treat logged out sites.
|
||||
const actionFunction = action.action;
|
||||
action.action = async (siteId) => {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
||||
if (!site.isLoggedOut()) {
|
||||
// Call the action now.
|
||||
return actionFunction(siteId);
|
||||
if (!CoreSites.isLoggedIn()) {
|
||||
// Not logged in, load site first.
|
||||
const loggedIn = await CoreSites.loadSite(siteId, { urlToOpen: url });
|
||||
if (loggedIn) {
|
||||
await CoreNavigator.navigateToSiteHome({ params: { urlToOpen: url } });
|
||||
}
|
||||
|
||||
// Site is logged out, authenticate first before treating the URL.
|
||||
const willReload = await CoreSites.logoutForRedirect(siteId, {
|
||||
urlToOpen: url,
|
||||
});
|
||||
|
||||
if (!willReload) {
|
||||
// Load the site with the redirect data.
|
||||
await CoreSites.loadSite(siteId, {
|
||||
urlToOpen: url,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (siteId !== CoreSites.getCurrentSiteId()) {
|
||||
// Different site, logout and login first before treating the URL because token could be expired.
|
||||
await CoreSites.logout({ urlToOpen: url, siteId });
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
actionFunction(siteId);
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -41,6 +41,10 @@ const appRoutes: Routes = [
|
|||
loadChildren: () => import('./login-lazy.module'),
|
||||
canActivate: [redirectGuard],
|
||||
},
|
||||
{
|
||||
path: 'logout',
|
||||
loadComponent: () => import('@features/login/pages/logout/logout'),
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<ion-content>
|
||||
<core-loading />
|
||||
</ion-content>
|
|
@ -0,0 +1,104 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreConstants } from '@/core/constants';
|
||||
import { CoreNavigationOptions, CoreNavigator, CoreRedirectPayload } from '@services/navigator';
|
||||
import { CoreSharedModule } from '@/core/shared.module';
|
||||
import { CoreSitePlugins } from '@features/siteplugins/services/siteplugins';
|
||||
import { CoreRedirects } from '@singletons/redirects';
|
||||
|
||||
/**
|
||||
* Page that logs the user out.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'page-core-login-logout',
|
||||
templateUrl: 'logout.html',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CoreSharedModule,
|
||||
],
|
||||
})
|
||||
export default class CoreLoginLogoutPage implements OnInit {
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async ngOnInit(): Promise<void> {
|
||||
const siteId = CoreNavigator.getRouteParam('siteId') ?? CoreConstants.NO_SITE_ID;
|
||||
const logoutOptions = {
|
||||
forceLogout: CoreNavigator.getRouteBooleanParam('forceLogout'),
|
||||
removeAccount: CoreNavigator.getRouteBooleanParam('removeAccount') ?? !!CoreConstants.CONFIG.removeaccountonlogout,
|
||||
};
|
||||
const redirectData = {
|
||||
redirectPath: CoreNavigator.getRouteParam('redirectPath'),
|
||||
redirectOptions: CoreNavigator.getRouteParam<CoreNavigationOptions>('redirectOptions'),
|
||||
urlToOpen: CoreNavigator.getRouteParam('urlToOpen'),
|
||||
};
|
||||
|
||||
if (!CoreSites.isLoggedIn()) {
|
||||
// This page shouldn't open if user isn't logged in, but if that happens just navigate to the right page.
|
||||
await this.navigateAfterLogout(siteId, redirectData);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const shouldReload = CoreSitePlugins.hasSitePluginsLoaded;
|
||||
if (shouldReload && (siteId !== CoreConstants.NO_SITE_ID || redirectData.redirectPath || redirectData.urlToOpen)) {
|
||||
// The app will reload and we need to open a page that isn't the default page. Store the redirect first.
|
||||
CoreRedirects.storeRedirect(siteId, redirectData);
|
||||
}
|
||||
|
||||
await CoreSites.internalLogout(logoutOptions);
|
||||
|
||||
if (shouldReload) {
|
||||
// We need to reload the app to unload all the plugins. Leave the logout page first.
|
||||
await CoreNavigator.navigate('/login', { reset: true });
|
||||
|
||||
window.location.reload();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await this.navigateAfterLogout(siteId, redirectData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to the right page after logout is done.
|
||||
*
|
||||
* @param siteId Site ID to load.
|
||||
* @param redirectData Redirect data.
|
||||
*/
|
||||
protected async navigateAfterLogout(siteId: string, redirectData: CoreRedirectPayload): Promise<void> {
|
||||
if (siteId === CoreConstants.NO_SITE_ID) {
|
||||
// No site to load now, just navigate.
|
||||
await CoreNavigator.navigate(redirectData.redirectPath ?? '/login/sites', {
|
||||
...redirectData.redirectOptions,
|
||||
reset: true,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Load the site and navigate.
|
||||
const loggedIn = await CoreSites.loadSite(siteId, redirectData);
|
||||
if (!loggedIn) {
|
||||
return; // Session expired.
|
||||
}
|
||||
|
||||
await CoreNavigator.navigateToSiteHome({ params: redirectData, preferCurrentTab: false, siteId });
|
||||
}
|
||||
|
||||
}
|
|
@ -412,21 +412,18 @@ export class CoreLoginHelperProvider {
|
|||
* @returns Promise resolved when done.
|
||||
*/
|
||||
async goToAddSite(setRoot = false, showKeyboard = false): Promise<void> {
|
||||
let path = '/login/sites';
|
||||
let params: Params = { openAddSite: true , showKeyboard };
|
||||
|
||||
if (CoreSites.isLoggedIn()) {
|
||||
const willReload = await CoreSites.logoutForRedirect(CoreConstants.NO_SITE_ID, {
|
||||
redirectPath: path,
|
||||
redirectOptions: { params },
|
||||
// Logout first.
|
||||
await CoreSites.logout({
|
||||
siteId: CoreConstants.NO_SITE_ID,
|
||||
redirectPath: '/login/sites',
|
||||
redirectOptions: { params: { openAddSite: true , showKeyboard } },
|
||||
});
|
||||
|
||||
if (willReload) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
[path, params] = await this.getAddSiteRouteInfo(showKeyboard);
|
||||
}
|
||||
|
||||
const [path, params] = await this.getAddSiteRouteInfo(showKeyboard);
|
||||
|
||||
await CoreNavigator.navigate(path, { params, reset: setRoot });
|
||||
}
|
||||
|
|
|
@ -66,13 +66,6 @@ Feature: Test basic usage of login in app
|
|||
And I press "Connect to your site" in the app
|
||||
Then I should find "Can't connect to site" in the app
|
||||
|
||||
Scenario: Log out from the app
|
||||
Given I entered the app as "student1"
|
||||
And I press the user menu button in the app
|
||||
When I press "Log out" in the app
|
||||
And I wait the app to restart
|
||||
Then the header should be "Accounts" in the app
|
||||
|
||||
Scenario: Delete an account
|
||||
Given I entered the app as "student1"
|
||||
When I log out in the app
|
||||
|
|
|
@ -0,0 +1,160 @@
|
|||
@core_login @app @javascript
|
||||
Feature: Test different cases of logout and switch account
|
||||
I need different logout use cases to work
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname |
|
||||
| student1 | david | student |
|
||||
| student2 | pau | student2 |
|
||||
|
||||
Scenario: Log out and re-login
|
||||
Given I entered the app as "student1"
|
||||
When I press the user menu button in the app
|
||||
And I press "Log out" in the app
|
||||
And I wait the app to restart
|
||||
Then the header should be "Accounts" in the app
|
||||
|
||||
When I press "david student" in the app
|
||||
Then the header should be "Reconnect" in the app
|
||||
And I should find "david student" in the app
|
||||
|
||||
When I set the following fields to these values in the app:
|
||||
| Password | student1 |
|
||||
And I press "Log in" near "Lost password?" in the app
|
||||
Then the header should be "Acceptance test site" in the app
|
||||
|
||||
Scenario: Exit account using switch account and re-enter
|
||||
Given I entered the app as "student1"
|
||||
When I press the user menu button in the app
|
||||
And I press "Switch account" in the app
|
||||
And I press "Add" in the app
|
||||
And I wait the app to restart
|
||||
Then I should find "Connect to Moodle" in the app
|
||||
|
||||
When I go back in the app
|
||||
And I press "david student" in the app
|
||||
Then the header should be "Acceptance test site" in the app
|
||||
|
||||
Scenario: Exit account using switch account and re-enter when forcelogout is enabled
|
||||
Given the following config values are set as admin:
|
||||
| forcelogout | 1 | tool_mobile |
|
||||
And I entered the app as "student1"
|
||||
When I press the user menu button in the app
|
||||
And I press "Switch account" in the app
|
||||
And I press "Add" in the app
|
||||
And I wait the app to restart
|
||||
And I go back in the app
|
||||
And I press "david student" in the app
|
||||
Then the header should be "Reconnect" in the app
|
||||
And I should find "david student" in the app
|
||||
|
||||
Scenario: Switch to a different account
|
||||
Given I entered the app as "student1"
|
||||
And I entered the app as "student2"
|
||||
When I press the user menu button in the app
|
||||
Then I should find "pau student2" in the app
|
||||
|
||||
When I press "Switch account" in the app
|
||||
And I press "david student" in the app
|
||||
And I wait the app to restart
|
||||
Then the header should be "Acceptance test site" in the app
|
||||
|
||||
When I press the user menu button in the app
|
||||
Then I should find "david student" in the app
|
||||
|
||||
Scenario: Logout when there is unsaved data
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname |
|
||||
| Course 1 | C1 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| student1 | C1 | student |
|
||||
And the following "activities" exist:
|
||||
| activity | name | intro | course | idnumber |
|
||||
| forum | Test forum | Test forum | C1 | forum |
|
||||
And the following forum discussions exist in course "Course 1":
|
||||
| forum | user | name | message |
|
||||
| Test forum | student1 | Forum topic 1 | Forum message 1 |
|
||||
| Test forum | student1 | Forum topic 2 | Forum message 2 |
|
||||
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 "Test forum" in the app
|
||||
And I press "Add discussion topic" in the app
|
||||
And I set the following fields to these values in the app:
|
||||
| Subject | My happy subject |
|
||||
| Message | An awesome message |
|
||||
And I press the user menu button in the app
|
||||
And I press "Log out" in the app
|
||||
Then I should find "Are you sure you want to leave this page?" in the app
|
||||
|
||||
# Check that the app continues working fine if the user cancels the logout.
|
||||
When I press "Cancel" in the app
|
||||
And I press "Forum topic 1" in the app
|
||||
And I press "OK" in the app
|
||||
Then I should find "Forum message 1" in the app
|
||||
|
||||
When I press "Forum topic 2" in the app
|
||||
Then I should find "Forum message 2" in the app
|
||||
|
||||
# Now confirm the logout.
|
||||
When I press "Add discussion topic" in the app
|
||||
And I set the following fields to these values in the app:
|
||||
| Subject | My happy subject |
|
||||
| Message | An awesome message |
|
||||
And I press the user menu button in the app
|
||||
And I press "Log out" in the app
|
||||
And I press "OK" in the app
|
||||
And I wait the app to restart
|
||||
Then the header should be "Accounts" in the app
|
||||
|
||||
Scenario: Switch account when there is unsaved data
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname |
|
||||
| Course 1 | C1 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| student1 | C1 | student |
|
||||
And the following "activities" exist:
|
||||
| activity | name | intro | course | idnumber |
|
||||
| forum | Test forum | Test forum | C1 | forum |
|
||||
And the following forum discussions exist in course "Course 1":
|
||||
| forum | user | name | message |
|
||||
| Test forum | student1 | Forum topic 1 | Forum message 1 |
|
||||
| Test forum | student1 | Forum topic 2 | Forum message 2 |
|
||||
And I entered the app as "student2"
|
||||
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 "Test forum" in the app
|
||||
And I press "Add discussion topic" in the app
|
||||
And I set the following fields to these values in the app:
|
||||
| Subject | My happy subject |
|
||||
| Message | An awesome message |
|
||||
And I press the user menu button in the app
|
||||
And I press "Switch account" in the app
|
||||
And I press "pau student2" in the app
|
||||
Then I should find "Are you sure you want to leave this page?" in the app
|
||||
|
||||
# Check that the app continues working fine if the user cancels the switch account.
|
||||
When I press "Cancel" in the app
|
||||
And I press "Forum topic 1" in the app
|
||||
And I press "OK" in the app
|
||||
Then I should find "Forum message 1" in the app
|
||||
|
||||
When I press "Forum topic 2" in the app
|
||||
Then I should find "Forum message 2" in the app
|
||||
|
||||
# Now confirm the switch account.
|
||||
When I press "Add discussion topic" in the app
|
||||
And I set the following fields to these values in the app:
|
||||
| Subject | My happy subject |
|
||||
| Message | An awesome message |
|
||||
And I press the user menu button in the app
|
||||
And I press "Switch account" in the app
|
||||
And I press "pau student2" in the app
|
||||
And I press "OK" in the app
|
||||
And I wait the app to restart
|
||||
And I press the user menu button in the app
|
||||
Then I should find "pau student2" in the app
|
|
@ -48,7 +48,7 @@ Feature: Test showloginform setting in the app
|
|||
And I press "Moodle Mobile" in the app
|
||||
And I press "Developer options" in the app
|
||||
And I press "Always show login form" in the app
|
||||
And I go back 4 times in the app
|
||||
And I go back to the root page in the app
|
||||
And I press "david student" in the app
|
||||
Then the header should be "Reconnect" in the app
|
||||
And I should find "Log in" "ion-button" in the app
|
||||
|
|
|
@ -18,7 +18,6 @@ import { Component, OnDestroy, OnInit } from '@angular/core';
|
|||
import { CoreSite } from '@classes/sites/site';
|
||||
import { CoreSiteInfo } from '@classes/sites/unauthenticated-site';
|
||||
import { CoreFilter } from '@features/filter/services/filter';
|
||||
import { CoreLoginHelper } from '@features/login/services/login-helper';
|
||||
import { CoreUserAuthenticatedSupportConfig } from '@features/user/classes/support/authenticated-support-config';
|
||||
import { CoreUserSupport } from '@features/user/services/support';
|
||||
import { CoreUser, CoreUserProfile } from '@features/user/services/user';
|
||||
|
@ -33,8 +32,9 @@ import { CoreNavigator } from '@services/navigator';
|
|||
import { CoreSites } from '@services/sites';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CorePromiseUtils } from '@singletons/promise-utils';
|
||||
import { ModalController, Translate } from '@singletons';
|
||||
import { ModalController } from '@singletons';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { CoreLoginHelper } from '@features/login/services/login-helper';
|
||||
|
||||
/**
|
||||
* Component to display a user menu.
|
||||
|
@ -208,12 +208,6 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy {
|
|||
* @param event Click event
|
||||
*/
|
||||
async logout(event: Event): Promise<void> {
|
||||
if (CoreNavigator.currentRouteCanBlockLeave()) {
|
||||
await CoreDomUtils.showAlert(undefined, Translate.instant('core.cannotlogoutpageblocks'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.removeAccountOnLogout) {
|
||||
// Ask confirm.
|
||||
const siteName = this.siteName ?
|
||||
|
@ -242,12 +236,6 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy {
|
|||
* @param event Click event
|
||||
*/
|
||||
async switchAccounts(event: Event): Promise<void> {
|
||||
if (CoreNavigator.currentRouteCanBlockLeave()) {
|
||||
await CoreDomUtils.showAlert(undefined, Translate.instant('core.cannotlogoutpageblocks'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const thisModal = await ModalController.getTop();
|
||||
|
||||
event.preventDefault();
|
||||
|
|
|
@ -280,9 +280,7 @@ export class CorePolicySitePolicyPage implements OnInit, OnDestroy {
|
|||
* @returns Promise resolved when done.
|
||||
*/
|
||||
async cancel(): Promise<void> {
|
||||
await CorePromiseUtils.ignoreErrors(CoreSites.logout());
|
||||
|
||||
await CoreNavigator.navigate('/login/sites', { reset: true });
|
||||
await CoreSites.logout();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -19,7 +19,8 @@ Feature: It synchronise sites properly
|
|||
|
||||
Scenario: Sync the current site
|
||||
# Add something offline
|
||||
Given I entered the choice activity "Sync choice" on course "Course 1" as "student1" in the app
|
||||
Given I entered the course "Course 1" as "student1" in the app
|
||||
And I press "Sync choice" in the app
|
||||
When I switch network connection to offline
|
||||
And I select "Option 1" in the app
|
||||
And I press "Save my choice" in the app
|
||||
|
|
|
@ -32,6 +32,8 @@ describe('Site Home link handlers', () => {
|
|||
isStoredRootURL: () => Promise.resolve({ siteIds: [siteId] }),
|
||||
getSite: () => Promise.resolve(new CoreSite(siteId, siteUrl, '')),
|
||||
getSiteIdsFromUrl: () => Promise.resolve([siteId]),
|
||||
getCurrentSiteId: () => siteId,
|
||||
isLoggedIn: () => true,
|
||||
}));
|
||||
|
||||
mockSingleton(CoreLoginHelper, { getAvailableSites: async () => [{ url: siteUrl, name: 'Example Campus' }] });
|
||||
|
|
|
@ -195,7 +195,7 @@ export class CoreNavigatorService {
|
|||
async navigateToSiteHome(options: Omit<CoreNavigationOptions, 'reset'> & { siteId?: string } = {}): Promise<boolean> {
|
||||
const siteId = options.siteId ?? CoreSites.getCurrentSiteId();
|
||||
const landingPagePath = CoreSites.isLoggedIn() && CoreSites.getCurrentSiteId() === siteId ?
|
||||
this.getLandingTabPage() : 'main';
|
||||
this.getLandingTabPage() : '';
|
||||
|
||||
return this.navigateToSitePath(landingPagePath, {
|
||||
...options,
|
||||
|
@ -220,15 +220,13 @@ export class CoreNavigatorService {
|
|||
|
||||
// If we are logged into a different site, log out first.
|
||||
if (CoreSites.isLoggedIn() && CoreSites.getCurrentSiteId() !== siteId) {
|
||||
const willReload = await CoreSites.logoutForRedirect(siteId, {
|
||||
redirectPath: path,
|
||||
redirectOptions: options || {},
|
||||
await CoreSites.logout({
|
||||
...this.getRedirectDataForSitePath(path, options),
|
||||
siteId,
|
||||
});
|
||||
|
||||
if (willReload) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// If the path doesn't belong to a site, call standard navigation.
|
||||
if (siteId === CoreConstants.NO_SITE_ID) {
|
||||
|
@ -243,10 +241,7 @@ export class CoreNavigatorService {
|
|||
const modal = await CoreLoadings.show();
|
||||
|
||||
try {
|
||||
const loggedIn = await CoreSites.loadSite(siteId, {
|
||||
redirectPath: path,
|
||||
redirectOptions: options,
|
||||
});
|
||||
const loggedIn = await CoreSites.loadSite(siteId, this.getRedirectDataForSitePath(path, options));
|
||||
|
||||
if (!loggedIn) {
|
||||
// User has been redirected to the login page and will be redirected to the site path after login.
|
||||
|
@ -264,6 +259,31 @@ export class CoreNavigatorService {
|
|||
return this.navigateToMainMenuPath(path, navigationOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the redirect data to use when navigating to a site path.
|
||||
*
|
||||
* @param path Site path.
|
||||
* @param options Navigation options.
|
||||
* @returns Redirect data.
|
||||
*/
|
||||
protected getRedirectDataForSitePath(path: string, options: CoreNavigationOptions = {}): CoreRedirectPayload {
|
||||
if (!path || path.match(/^\/?main\/?$/)) {
|
||||
// Navigating to main, obtain the redirect from the navigation parameters (if any).
|
||||
// If there is no redirect path or url to open, use 'main' to open the site's main menu.
|
||||
return {
|
||||
redirectPath: !options.params?.redirectPath && !options.params?.urlToOpen ? 'main' : options.params?.redirectPath,
|
||||
redirectOptions: options.params?.redirectOptions,
|
||||
urlToOpen: options.params?.urlToOpen,
|
||||
};
|
||||
}
|
||||
|
||||
// Use the path to navigate as the redirect path.
|
||||
return {
|
||||
redirectPath: path,
|
||||
redirectOptions: options || {},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the active route path.
|
||||
*
|
||||
|
@ -547,6 +567,11 @@ export class CoreNavigatorService {
|
|||
...options,
|
||||
};
|
||||
|
||||
if (!path || path.match(/^\/?main\/?$/)) {
|
||||
// Navigating to main, nothing else to do.
|
||||
return this.navigate('/main', options);
|
||||
}
|
||||
|
||||
path = path.replace(/^(\.|\/main)?\//, '');
|
||||
|
||||
const pathRoot = /^[^/]+/.exec(path)?.[0] ?? '';
|
||||
|
|
|
@ -141,14 +141,6 @@ export class CoreSitesProvider {
|
|||
|
||||
// Remove version classes from body.
|
||||
CoreHTMLClasses.removeSiteClasses();
|
||||
|
||||
// Go to sites page when user is logged out.
|
||||
await CoreNavigator.navigate('/login/sites', { reset: true });
|
||||
|
||||
if (CoreSitePlugins.hasSitePluginsLoaded) {
|
||||
// Temporary fix. Reload the page to unload all plugins.
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
|
||||
CoreEvents.on(CoreEvents.LOGIN, async (data) => {
|
||||
|
@ -964,7 +956,7 @@ export class CoreSitesProvider {
|
|||
promise.finally(() => {
|
||||
if (siteId) {
|
||||
// Logout the currentSite and expire the token.
|
||||
this.logout();
|
||||
this.internalLogout();
|
||||
this.setSiteLoggedOut(siteId);
|
||||
}
|
||||
});
|
||||
|
@ -1123,7 +1115,7 @@ export class CoreSitesProvider {
|
|||
this.logger.debug(`Delete site ${siteId}`);
|
||||
|
||||
if (this.currentSite !== undefined && this.currentSite.id == siteId) {
|
||||
this.logout();
|
||||
this.internalLogout();
|
||||
}
|
||||
|
||||
const site = await this.getSite(siteId);
|
||||
|
@ -1457,10 +1449,23 @@ export class CoreSitesProvider {
|
|||
/**
|
||||
* Logout the user.
|
||||
*
|
||||
* @param options Logout options.
|
||||
* @returns Promise resolved when the user is logged out.
|
||||
* @param options Options.
|
||||
*/
|
||||
async logout(options: CoreSitesLogoutOptions = {}): Promise<void> {
|
||||
await CoreNavigator.navigate('/logout', {
|
||||
params: { ...options },
|
||||
reset: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout the user.
|
||||
* This function is for internal usage, please use CoreSites.logout instead. The reason this function is public is because
|
||||
* it's called from the CoreLoginLogoutPage page.
|
||||
*
|
||||
* @param options Logout options.
|
||||
*/
|
||||
async internalLogout(options: InternalLogoutOptions = {}): Promise<void> {
|
||||
if (!this.currentSite) {
|
||||
return;
|
||||
}
|
||||
|
@ -1494,6 +1499,7 @@ export class CoreSitesProvider {
|
|||
* @param siteId Site that will be opened after logout.
|
||||
* @param redirectData Page/url to open after logout.
|
||||
* @returns Promise resolved with boolean: true if app will be reloaded after logout.
|
||||
* @deprecated since 5.0. Use CoreSites.logout instead, it automatically handles redirects.
|
||||
*/
|
||||
async logoutForRedirect(siteId: string, redirectData: CoreRedirectPayload): Promise<boolean> {
|
||||
if (!this.currentSite) {
|
||||
|
@ -1505,7 +1511,7 @@ export class CoreSitesProvider {
|
|||
CoreRedirects.storeRedirect(siteId, redirectData);
|
||||
}
|
||||
|
||||
await this.logout();
|
||||
await this.internalLogout();
|
||||
|
||||
return CoreSitePlugins.hasSitePluginsLoaded;
|
||||
}
|
||||
|
@ -2480,7 +2486,14 @@ export type CoreSitesLoginTokenResponse = {
|
|||
/**
|
||||
* Options for logout.
|
||||
*/
|
||||
export type CoreSitesLogoutOptions = {
|
||||
export type CoreSitesLogoutOptions = CoreRedirectPayload & InternalLogoutOptions & {
|
||||
siteId?: string; // Site ID to load after logout.
|
||||
};
|
||||
|
||||
/**
|
||||
* Options for internal logout.
|
||||
*/
|
||||
type InternalLogoutOptions = {
|
||||
forceLogout?: boolean; // If true, site will be marked as logged out, no matter the value tool_mobile_forcelogout.
|
||||
removeAccount?: boolean; // If true, site will be removed too after logout.
|
||||
};
|
||||
|
|
|
@ -16,7 +16,6 @@ import { CoreEvents } from '@singletons/events';
|
|||
import { CoreLang, CoreLangProvider } from '@services/lang';
|
||||
|
||||
import { mock, mockSingleton } from '@/testing/utils';
|
||||
import { CoreNavigator, CoreNavigatorService } from '@services/navigator';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { Http } from '@singletons';
|
||||
import { of } from 'rxjs';
|
||||
|
@ -34,13 +33,10 @@ describe('CoreSitesProvider', () => {
|
|||
});
|
||||
|
||||
it('cleans up on logout', async () => {
|
||||
const navigator: CoreNavigatorService = mockSingleton(CoreNavigator, ['navigate']);
|
||||
|
||||
CoreSites.initialize();
|
||||
CoreEvents.trigger(CoreEvents.LOGOUT);
|
||||
|
||||
expect(langProvider.clearCustomStrings).toHaveBeenCalled();
|
||||
expect(navigator.navigate).toHaveBeenCalledWith('/login/sites', { reset: true });
|
||||
});
|
||||
|
||||
it('adds ionic platform and theme classes', async () => {
|
||||
|
|
|
@ -432,15 +432,14 @@ export class CoreCustomURLSchemesProvider {
|
|||
// Ask the user before changing site.
|
||||
await CoreDomUtils.showConfirm(Translate.instant('core.contentlinks.confirmurlothersite'));
|
||||
|
||||
const willReload = await CoreSites.logoutForRedirect(CoreConstants.NO_SITE_ID, {
|
||||
await CoreSites.logout({
|
||||
siteId: CoreConstants.NO_SITE_ID,
|
||||
redirectPath: '/login/credentials',
|
||||
redirectOptions: { params: pageParams },
|
||||
});
|
||||
|
||||
if (willReload) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await CoreNavigator.navigateToLoginCredentials(pageParams);
|
||||
}
|
||||
|
|
|
@ -3,9 +3,9 @@ Feature: It navigates properly using deep links.
|
|||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username |
|
||||
| student1 |
|
||||
| student2 |
|
||||
| username | firstname | lastname |
|
||||
| student1 | david | student |
|
||||
| student2 | pau | student2 |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname |
|
||||
| Course 1 | C1 |
|
||||
|
@ -20,7 +20,6 @@ Feature: It navigates properly using deep links.
|
|||
| forum | user | name | message |
|
||||
| Test forum | student1 | Forum topic | Forum message |
|
||||
And the following config values are set as admin:
|
||||
| forcelogout | 1 | tool_mobile |
|
||||
| defaulthomepage | 0 | |
|
||||
|
||||
Scenario: Receive a push notification
|
||||
|
@ -78,3 +77,98 @@ Feature: It navigates properly using deep links.
|
|||
When I go back in the app
|
||||
Then I should find "Site home" in the app
|
||||
But I should not find "Test forum" in the app
|
||||
|
||||
Scenario: Open a deep link in a different account not stored in the app
|
||||
Given I entered the app as "student1"
|
||||
When I open a custom link in the app for:
|
||||
| discussion | user |
|
||||
| Forum topic | student2 |
|
||||
Then I should find "This link belongs to another site" in the app
|
||||
|
||||
When I press "OK" in the app
|
||||
And I wait the app to restart
|
||||
Then the header should be "Log in" in the app
|
||||
|
||||
When I set the following fields to these values in the app:
|
||||
| Password | student2 |
|
||||
And I press "Log in" near "Lost password?" in the app
|
||||
Then I should find "Forum topic" in the app
|
||||
And I should find "Forum message" in the app
|
||||
|
||||
When I go back to the root page in the app
|
||||
And I press the user menu button in the app
|
||||
Then I should find "pau student2" in the app
|
||||
|
||||
Scenario: Open a deep link in a different account stored in the app
|
||||
Given I entered the app as "student2"
|
||||
And I entered the app as "student1"
|
||||
When I open a custom link in the app for:
|
||||
| discussion | user |
|
||||
| Forum topic | student2 |
|
||||
Then I should find "This link belongs to another site" in the app
|
||||
|
||||
When I press "OK" in the app
|
||||
And I wait the app to restart
|
||||
Then I should find "Forum topic" in the app
|
||||
And I should find "Forum message" in the app
|
||||
|
||||
When I go back to the root page in the app
|
||||
And I press the user menu button in the app
|
||||
Then I should find "pau student2" in the app
|
||||
|
||||
Scenario: Open a deep link in a different account stored in the app but logged out
|
||||
Given I entered the app as "student2"
|
||||
And I press the user menu button in the app
|
||||
And I press "Log out" in the app
|
||||
And I wait the app to restart
|
||||
And I entered the app as "student1"
|
||||
When I open a custom link in the app for:
|
||||
| discussion | user |
|
||||
| Forum topic | student2 |
|
||||
Then I should find "This link belongs to another site" in the app
|
||||
|
||||
When I press "OK" in the app
|
||||
And I wait the app to restart
|
||||
Then the header should be "Reconnect" in the app
|
||||
And I should find "pau student2" in the app
|
||||
|
||||
When I set the following fields to these values in the app:
|
||||
| Password | student2 |
|
||||
And I press "Log in" near "Lost password?" in the app
|
||||
Then I should find "Forum topic" in the app
|
||||
And I should find "Forum message" in the app
|
||||
|
||||
When I go back to the root page in the app
|
||||
And I press the user menu button in the app
|
||||
Then I should find "pau student2" in the app
|
||||
|
||||
Scenario: Open a deep link in a different account when there is unsaved data
|
||||
Given I entered the app as "student2"
|
||||
And I entered the forum activity "Test forum" on course "Course 1" as "student1" in the app
|
||||
When I press "Add discussion topic" in the app
|
||||
And I set the following fields to these values in the app:
|
||||
| Subject | My happy subject |
|
||||
| Message | An awesome message |
|
||||
And I open a custom link in the app for:
|
||||
| discussion | user |
|
||||
| Forum topic | student2 |
|
||||
Then I should find "This link belongs to another site" in the app
|
||||
|
||||
When I press "OK" in the app
|
||||
Then I should find "Are you sure you want to leave this page?" in the app
|
||||
|
||||
When I press "Cancel" in the app
|
||||
Then I should not find "Forum message" in the app
|
||||
|
||||
When I open a custom link in the app for:
|
||||
| discussion | user |
|
||||
| Forum topic | student2 |
|
||||
And I press "OK" in the app
|
||||
And I press "OK" in the app
|
||||
And I wait the app to restart
|
||||
Then I should find "Forum topic" in the app
|
||||
And I should find "Forum message" in the app
|
||||
|
||||
When I go back to the root page in the app
|
||||
And I press the user menu button in the app
|
||||
Then I should find "pau student2" in the app
|
||||
|
|
|
@ -21,7 +21,8 @@ Feature: It opens files properly.
|
|||
| resource | Test DOC | Test DOC description | 5 | C1 | A doc.doc |
|
||||
And the following config values are set as admin:
|
||||
| filetypeexclusionlist | rtf,doc | tool_mobile |
|
||||
And I entered the resource activity "Test TXT" on course "Course 1" as "student1" in the app
|
||||
And I entered the course "Course 1" as "student1" in the app
|
||||
And I press "Test TXT" in the app
|
||||
When I press "Open" in the app
|
||||
Then the app should have opened a browser tab with url "^blob:"
|
||||
|
||||
|
|
|
@ -136,19 +136,39 @@ export class TestingBehatRuntimeService {
|
|||
* Run an operation inside the angular zone and return result.
|
||||
*
|
||||
* @param operation Operation callback.
|
||||
* @param blocking Whether the operation is blocking or not.
|
||||
* @param locatorToFind If set, when this locator is found the operation is considered finished. This is useful for
|
||||
* operations that might expect user input before finishing, like a confirm modal.
|
||||
* @returns OK if successful, or ERROR: followed by message.
|
||||
*/
|
||||
async runInZone(operation: () => unknown, blocking: boolean = false): Promise<string> {
|
||||
async runInZone(
|
||||
operation: () => unknown,
|
||||
blocking: boolean = false,
|
||||
locatorToFind?: TestingBehatElementLocator,
|
||||
): Promise<string> {
|
||||
const blockKey = blocking && TestingBehatBlocking.block();
|
||||
let interval: number | undefined;
|
||||
|
||||
try {
|
||||
await NgZone.run(operation);
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
Promise.resolve(NgZone.run(operation)).then(resolve).catch(reject);
|
||||
|
||||
if (locatorToFind) {
|
||||
interval = window.setInterval(() => {
|
||||
if (TestingBehatDomUtils.findElementBasedOnText(locatorToFind, { onlyClickable: false })) {
|
||||
clearInterval(interval);
|
||||
resolve();
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
|
||||
return 'OK';
|
||||
} catch (error) {
|
||||
return 'ERROR: ' + error.message;
|
||||
} finally {
|
||||
blockKey && TestingBehatBlocking.unblock(blockKey);
|
||||
window.clearInterval(interval);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,10 @@ This file describes API changes in the Moodle App that affect site plugins, info
|
|||
|
||||
For more information about upgrading, read the official documentation: https://moodledev.io/general/app/upgrading/
|
||||
|
||||
=== 5.0.0 ===
|
||||
|
||||
- The logout process has been refactored, now it uses a logout page to trigger Angular guards. CoreSites.logout now uses this process, and CoreSites.logoutForRedirect is deprecated and shouldn't be used anymore.
|
||||
|
||||
=== 4.5.0 ===
|
||||
|
||||
- Ionic has been upgraded to major version 8. See breaking changes and upgrade guide here: https://ionicframework.com/docs/updating/8-0
|
||||
|
|
Loading…
Reference in New Issue