commit
476a1795ae
|
@ -604,6 +604,7 @@ class behat_app extends behat_app_helper {
|
||||||
$data = $data->getColumnsHash()[0];
|
$data = $data->getColumnsHash()[0];
|
||||||
$title = array_keys($data)[0];
|
$title = array_keys($data)[0];
|
||||||
$data = (object) $data;
|
$data = (object) $data;
|
||||||
|
$username = $data->user ?? '';
|
||||||
|
|
||||||
switch ($title) {
|
switch ($title) {
|
||||||
case 'discussion':
|
case 'discussion':
|
||||||
|
@ -645,7 +646,7 @@ class behat_app extends behat_app_helper {
|
||||||
throw new DriverException('Invalid custom link title - ' . $title);
|
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 string $script
|
||||||
* @param bool $blocking
|
* @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.
|
* @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);
|
$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;
|
$privatetoken = $usertoken->privatetoken;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate custom URL.
|
$url = $this->generate_custom_url([
|
||||||
$parsed_url = parse_url($CFG->behat_wwwroot);
|
'username' => $username,
|
||||||
$site = $parsed_url['host'] ?? '';
|
'token' => $token,
|
||||||
$site .= isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '';
|
'privatetoken' => $privatetoken,
|
||||||
$site .= $parsed_url['path'] ?? '';
|
'redirect' => $path,
|
||||||
$url = $this->get_mobile_url_scheme() . "://$username@$site?token=$token&privatetoken=$privatetoken";
|
]);
|
||||||
|
|
||||||
if (!empty($path)) {
|
if (empty($path)) {
|
||||||
$url .= '&redirect='.urlencode($CFG->behat_wwwroot.$path);
|
|
||||||
} else {
|
|
||||||
$successXPath = '//page-core-mainmenu';
|
$successXPath = '//page-core-mainmenu';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -434,14 +435,54 @@ class behat_app_helper extends behat_base {
|
||||||
*
|
*
|
||||||
* @param string $path To navigate.
|
* @param string $path To navigate.
|
||||||
* @param string $successXPath The XPath of the element to lookat after navigation.
|
* @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;
|
global $CFG;
|
||||||
|
|
||||||
$urlscheme = $this->get_mobile_url_scheme();
|
$url = $this->generate_custom_url([
|
||||||
$url = "$urlscheme://link=" . urlencode($CFG->behat_wwwroot.$path);
|
'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 $customurl To navigate.
|
||||||
* @param string $successXPath The XPath of the element to lookat after navigation.
|
* @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 = '') {
|
protected function handle_url(string $customurl, string $successXPath = '', string $texttofind = '') {
|
||||||
$result = $this->zone_js("customUrlSchemes.handleCustomURL('$customurl')");
|
$result = $this->zone_js("customUrlSchemes.handleCustomURL('$customurl')", false, $texttofind);
|
||||||
|
|
||||||
if ($result !== 'OK') {
|
if ($result !== 'OK') {
|
||||||
throw new DriverException('Error handling url - ' . $customurl . ' - '.$result);
|
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
|
Then I should find "No attempt" in the app
|
||||||
|
|
||||||
Scenario: Add submission offline (online text) & Submit for grading offline & Sync submissions
|
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
|
When I press "Add submission" in the app
|
||||||
And I switch network connection to offline
|
And I switch network connection to offline
|
||||||
And I set the field "Online text submissions" to "Submission test" in the app
|
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
|
But I should not find "This Assignment has offline data to be synchronised." in the app
|
||||||
|
|
||||||
Scenario: Edit an offline submission before synchronising it
|
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
|
When I press "Add submission" in the app
|
||||||
And I switch network connection to offline
|
And I switch network connection to offline
|
||||||
And I set the field "Online text submissions" to "Submission test original offline" in the app
|
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
|
@lms_from4.5
|
||||||
Scenario: Remove submission offline and syncrhonize it
|
Scenario: Remove submission offline and syncrhonize 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 "Add submission" 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 set the field "Online text submissions" to "Submission test" in the app
|
||||||
And I press "Save" in the app
|
And I press "Save" in the app
|
||||||
Then I should find "Draft (not submitted)" 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
|
@lms_from4.5
|
||||||
Scenario: Add submission offline after removing a submission offline
|
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
|
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 set the field "Online text submissions" to "Submission test online" in the app
|
||||||
And I press "Save" 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 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 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 ## |
|
| 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
|
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
|
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 & recordings | C1 | bbb1 | 0 |
|
||||||
| bigbluebuttonbn | Room only | C1 | bbb2 | 1 |
|
| bigbluebuttonbn | Room only | C1 | bbb2 | 1 |
|
||||||
| bigbluebuttonbn | Recordings only | C1 | bbb3 | 2 |
|
| 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
|
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 be able to press "Join session" in the app
|
||||||
And I should find "Recordings" 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:
|
Given the following "activities" exist:
|
||||||
| activity | name | intro | course | idnumber | option | allowmultiple | allowupdate | showresults |
|
| 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 |
|
| 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
|
When I select "Option 1" in the app
|
||||||
And I select "Option 2" in the app
|
And I select "Option 2" in the app
|
||||||
And I press "Save my choice" 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:
|
Given the following "activities" exist:
|
||||||
| activity | name | intro | course | idnumber | option | allowmultiple | allowupdate | showresults |
|
| 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 |
|
| 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
|
When I select "Option 1" in the app
|
||||||
And I switch network connection to offline
|
And I switch network connection to offline
|
||||||
And I select "Option 2" in the app
|
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 |
|
| data1 | text | Description | Link description |
|
||||||
|
|
||||||
Scenario: Create entry (offline)
|
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 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
|
When I press "Add entry" in the app
|
||||||
And I set the following fields to these values in the app:
|
And I set the following fields to these values in the app:
|
||||||
| URL | https://moodle.org/ |
|
| 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
|
Then I should find "https://moodle.org/" in the app
|
||||||
And I should find "Moodle community site" 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 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 switch network connection to wifi
|
||||||
And I press "Web links" near "General" in the app
|
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 find "Moodle community site" in the app
|
||||||
And I should not find "This Database has offline data to be synchronised" 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)
|
Scenario: Update entry (offline) & Delete 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 should find "No entries yet" in the app
|
And I press "Web links" in the app
|
||||||
And I press "Add entry" 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:
|
And I set the following fields to these values in the app:
|
||||||
| URL | https://moodle.org/ |
|
| URL | https://moodle.org/ |
|
||||||
| Description | Moodle community site |
|
| Description | Moodle community site |
|
||||||
And I press "Save" near "Web links" in the app
|
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 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 press "Download" in the app
|
||||||
And I wait until the page is ready
|
And I wait until the page is ready
|
||||||
And I close the popup in the app
|
And I close the popup in the app
|
||||||
And I switch network connection to offline
|
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 press "Edit" in the app
|
||||||
And I set the following fields to these values in the app:
|
And I set the following fields to these values in the app:
|
||||||
| URL | https://moodlecloud.com/ |
|
| 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 "https://moodlecloud.com/" in the app
|
||||||
And I should find "Moodle Cloud" 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 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 switch network connection to wifi
|
||||||
And I press "Web links" near "General" in the app
|
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 not find "Moodle community site" in the app
|
||||||
And I should find "https://moodlecloud.com/" 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 find "Moodle Cloud" in the app
|
||||||
And I should not find "This Database has offline data to be synchronised" 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 press "Refresh" in the app
|
||||||
And I wait until the page is ready
|
And I wait until the page is ready
|
||||||
And I switch network connection to offline
|
And I switch network connection to offline
|
||||||
And I press "Actions menu" in the app
|
And I press "Actions menu" in the app
|
||||||
And I press "Delete" 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
|
Then 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
|
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 "Moodle Cloud" in the app
|
||||||
And I should find "This Database has offline data to be synchronised" 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 switch network connection to wifi
|
||||||
And I press "Web links" near "General" in the app
|
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 "Moodle Cloud" in the app
|
||||||
And I should not find "This Database has offline data to be synchronised" 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
|
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
|
Given I entered the course "Course 1" as "student1" in the app
|
||||||
And I should find "No entries yet" in the app
|
And I press "Web links" in the app
|
||||||
And I press "Add entry" 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:
|
And I set the following fields to these values in the app:
|
||||||
| URL | https://moodle.org/ |
|
| URL | https://moodle.org/ |
|
||||||
| Description | Moodle community site |
|
| Description | Moodle community site |
|
||||||
And I press "Save" near "Web links" in the app
|
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 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 press "Download" in the app
|
||||||
And I wait until the page is ready
|
And I wait until the page is ready
|
||||||
And I close the popup in the app
|
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 "Actions menu" in the app
|
||||||
And I press "Delete" 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
|
Then 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
|
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 "Moodle community site" in the app
|
||||||
And I should find "This Database has offline data to be synchronised" 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 press "Restore" in the app
|
||||||
And I go back in the app
|
And I go back in the app
|
||||||
And I switch network connection to wifi
|
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
|
But I should not find "Not sent" in the app
|
||||||
|
|
||||||
Scenario: New discussion offline & Sync Forum
|
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
|
When I switch network connection to offline
|
||||||
And I press "Add discussion topic" in the app
|
And I press "Add discussion topic" in the app
|
||||||
And I set the following fields to these values 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 |
|
| forum2 | Disc vis ALL | Disc vis ALL | Disc vis ALL content | All participants |
|
||||||
|
|
||||||
Scenario: Student can only see the right groups
|
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
|
Then I should find "Disc sep G1" in the app
|
||||||
And I should find "Disc sep ALL" in the app
|
And I should find "Disc sep ALL" in the app
|
||||||
But I should not find "Disc sep G2" 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
|
But I should not find "Disc vis G2" in the app
|
||||||
|
|
||||||
Scenario: Teacher can see all groups
|
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
|
When I press "Separate groups" in the app
|
||||||
Then I should find "All participants" in the app
|
Then I should find "All participants" in the app
|
||||||
And I should find "Group 1" 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
|
But I should not find "Disc vis G2" in the app
|
||||||
|
|
||||||
Scenario: Student can only add discussions in his groups
|
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
|
When I press "Add discussion topic" in the app
|
||||||
And I press "Advanced" in the app
|
And I press "Advanced" in the app
|
||||||
Then I should not find "Post a copy to all groups" 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
|
Then I should find "My happy subject" in the app
|
||||||
|
|
||||||
Scenario: Teacher can add discussion to any group
|
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
|
Given I entered the course "Course 1" as "teacher1" in the app
|
||||||
And I press "Separate groups" 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 "All participants" in the app
|
||||||
And I press "Add discussion topic" in the app
|
And I press "Add discussion topic" in the app
|
||||||
And I press "Advanced" 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
|
Then I should find "My third subject" in the app
|
||||||
|
|
||||||
Scenario: Teacher can post a copy in all groups
|
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
|
Given I entered the course "Course 1" as "teacher1" in the app
|
||||||
And I press "Separate groups" 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 "Group 1" in the app
|
||||||
And I press "Add discussion topic" in the app
|
And I press "Add discussion topic" in the app
|
||||||
And I press "Advanced" 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)
|
Scenario: Navigate to glossary terms by link (auto-linking)
|
||||||
Given the "glossary" filter is "on"
|
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
|
Then the header should be "Test glossary" in the app
|
||||||
And I should find "Eggplant" in the app
|
And I should find "Eggplant" in the app
|
||||||
And I should find "Cucumber" 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 |
|
| 1 | True |
|
||||||
|
|
||||||
Scenario: Can review only when the attempt is allowed to be reviewed
|
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
|
Given I entered the course "Course 1" as "student1" in the app
|
||||||
And I press "Attempt 1" 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
|
Then I should not be able to press "Review" in the app
|
||||||
|
|
||||||
When I go back 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:
|
Given the following "activities" exist:
|
||||||
| activity | name | intro | template | course | idnumber | groupmode |
|
| activity | name | intro | template | course | idnumber | groupmode |
|
||||||
| survey | Test survey critical incidents | Test survey1 | 5 | C1 | survey1 | 0 |
|
| 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 entered the course "Course 1" as "student1" in the app
|
||||||
And I switch network connection to offline
|
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 "Submit" in the app
|
||||||
And I press "OK" in the app
|
And I press "OK" in the app
|
||||||
Then I should see "This Survey has offline data to be synchronised."
|
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 |
|
| \mod_workshop\event\course_module_viewed | workshop | Test workshop | Course 1 |
|
||||||
|
|
||||||
Scenario: Prefetch a workshop
|
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
|
When I press "Information" in the app
|
||||||
And I press "Download" in the app
|
And I press "Download" in the app
|
||||||
And I press "Close" in the app
|
And I press "Close" in the app
|
||||||
|
|
|
@ -19,6 +19,7 @@ import { CoreUrl } from '@singletons/url';
|
||||||
import { makeSingleton } from '@singletons';
|
import { makeSingleton } from '@singletons';
|
||||||
import { CoreText } from '@singletons/text';
|
import { CoreText } from '@singletons/text';
|
||||||
import { CorePromiseUtils } from '@singletons/promise-utils';
|
import { CorePromiseUtils } from '@singletons/promise-utils';
|
||||||
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface that all handlers must implement.
|
* 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.
|
// Wrap the action function in our own function to treat logged out sites.
|
||||||
const actionFunction = action.action;
|
const actionFunction = action.action;
|
||||||
action.action = async (siteId) => {
|
action.action = async (siteId) => {
|
||||||
const site = await CoreSites.getSite(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 } });
|
||||||
|
}
|
||||||
|
|
||||||
if (!site.isLoggedOut()) {
|
return;
|
||||||
// Call the action now.
|
|
||||||
return actionFunction(siteId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Site is logged out, authenticate first before treating the URL.
|
if (siteId !== CoreSites.getCurrentSiteId()) {
|
||||||
const willReload = await CoreSites.logoutForRedirect(siteId, {
|
// Different site, logout and login first before treating the URL because token could be expired.
|
||||||
urlToOpen: url,
|
await CoreSites.logout({ urlToOpen: url, siteId });
|
||||||
});
|
|
||||||
|
|
||||||
if (!willReload) {
|
return;
|
||||||
// Load the site with the redirect data.
|
|
||||||
await CoreSites.loadSite(siteId, {
|
|
||||||
urlToOpen: url,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
actionFunction(siteId);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,10 @@ const appRoutes: Routes = [
|
||||||
loadChildren: () => import('./login-lazy.module'),
|
loadChildren: () => import('./login-lazy.module'),
|
||||||
canActivate: [redirectGuard],
|
canActivate: [redirectGuard],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'logout',
|
||||||
|
loadComponent: () => import('@features/login/pages/logout/logout'),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@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,22 +412,19 @@ export class CoreLoginHelperProvider {
|
||||||
* @returns Promise resolved when done.
|
* @returns Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
async goToAddSite(setRoot = false, showKeyboard = false): Promise<void> {
|
async goToAddSite(setRoot = false, showKeyboard = false): Promise<void> {
|
||||||
let path = '/login/sites';
|
|
||||||
let params: Params = { openAddSite: true , showKeyboard };
|
|
||||||
|
|
||||||
if (CoreSites.isLoggedIn()) {
|
if (CoreSites.isLoggedIn()) {
|
||||||
const willReload = await CoreSites.logoutForRedirect(CoreConstants.NO_SITE_ID, {
|
// Logout first.
|
||||||
redirectPath: path,
|
await CoreSites.logout({
|
||||||
redirectOptions: { params },
|
siteId: CoreConstants.NO_SITE_ID,
|
||||||
|
redirectPath: '/login/sites',
|
||||||
|
redirectOptions: { params: { openAddSite: true , showKeyboard } },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (willReload) {
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
[path, params] = await this.getAddSiteRouteInfo(showKeyboard);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const [path, params] = await this.getAddSiteRouteInfo(showKeyboard);
|
||||||
|
|
||||||
await CoreNavigator.navigate(path, { params, reset: setRoot });
|
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
|
And I press "Connect to your site" in the app
|
||||||
Then I should find "Can't connect to 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
|
Scenario: Delete an account
|
||||||
Given I entered the app as "student1"
|
Given I entered the app as "student1"
|
||||||
When I log out in the app
|
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 "Moodle Mobile" in the app
|
||||||
And I press "Developer options" in the app
|
And I press "Developer options" in the app
|
||||||
And I press "Always show login form" 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
|
And I press "david student" in the app
|
||||||
Then the header should be "Reconnect" in the app
|
Then the header should be "Reconnect" in the app
|
||||||
And I should find "Log in" "ion-button" 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 { CoreSite } from '@classes/sites/site';
|
||||||
import { CoreSiteInfo } from '@classes/sites/unauthenticated-site';
|
import { CoreSiteInfo } from '@classes/sites/unauthenticated-site';
|
||||||
import { CoreFilter } from '@features/filter/services/filter';
|
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 { CoreUserAuthenticatedSupportConfig } from '@features/user/classes/support/authenticated-support-config';
|
||||||
import { CoreUserSupport } from '@features/user/services/support';
|
import { CoreUserSupport } from '@features/user/services/support';
|
||||||
import { CoreUser, CoreUserProfile } from '@features/user/services/user';
|
import { CoreUser, CoreUserProfile } from '@features/user/services/user';
|
||||||
|
@ -33,8 +32,9 @@ import { CoreNavigator } from '@services/navigator';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CorePromiseUtils } from '@singletons/promise-utils';
|
import { CorePromiseUtils } from '@singletons/promise-utils';
|
||||||
import { ModalController, Translate } from '@singletons';
|
import { ModalController } from '@singletons';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
|
import { CoreLoginHelper } from '@features/login/services/login-helper';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component to display a user menu.
|
* Component to display a user menu.
|
||||||
|
@ -208,12 +208,6 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy {
|
||||||
* @param event Click event
|
* @param event Click event
|
||||||
*/
|
*/
|
||||||
async logout(event: Event): Promise<void> {
|
async logout(event: Event): Promise<void> {
|
||||||
if (CoreNavigator.currentRouteCanBlockLeave()) {
|
|
||||||
await CoreDomUtils.showAlert(undefined, Translate.instant('core.cannotlogoutpageblocks'));
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.removeAccountOnLogout) {
|
if (this.removeAccountOnLogout) {
|
||||||
// Ask confirm.
|
// Ask confirm.
|
||||||
const siteName = this.siteName ?
|
const siteName = this.siteName ?
|
||||||
|
@ -242,12 +236,6 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy {
|
||||||
* @param event Click event
|
* @param event Click event
|
||||||
*/
|
*/
|
||||||
async switchAccounts(event: Event): Promise<void> {
|
async switchAccounts(event: Event): Promise<void> {
|
||||||
if (CoreNavigator.currentRouteCanBlockLeave()) {
|
|
||||||
await CoreDomUtils.showAlert(undefined, Translate.instant('core.cannotlogoutpageblocks'));
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const thisModal = await ModalController.getTop();
|
const thisModal = await ModalController.getTop();
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
|
@ -280,9 +280,7 @@ export class CorePolicySitePolicyPage implements OnInit, OnDestroy {
|
||||||
* @returns Promise resolved when done.
|
* @returns Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
async cancel(): Promise<void> {
|
async cancel(): Promise<void> {
|
||||||
await CorePromiseUtils.ignoreErrors(CoreSites.logout());
|
await CoreSites.logout();
|
||||||
|
|
||||||
await CoreNavigator.navigate('/login/sites', { reset: true });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -19,7 +19,8 @@ Feature: It synchronise sites properly
|
||||||
|
|
||||||
Scenario: Sync the current site
|
Scenario: Sync the current site
|
||||||
# Add something offline
|
# 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
|
When I switch network connection to offline
|
||||||
And I select "Option 1" in the app
|
And I select "Option 1" in the app
|
||||||
And I press "Save my choice" 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] }),
|
isStoredRootURL: () => Promise.resolve({ siteIds: [siteId] }),
|
||||||
getSite: () => Promise.resolve(new CoreSite(siteId, siteUrl, '')),
|
getSite: () => Promise.resolve(new CoreSite(siteId, siteUrl, '')),
|
||||||
getSiteIdsFromUrl: () => Promise.resolve([siteId]),
|
getSiteIdsFromUrl: () => Promise.resolve([siteId]),
|
||||||
|
getCurrentSiteId: () => siteId,
|
||||||
|
isLoggedIn: () => true,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
mockSingleton(CoreLoginHelper, { getAvailableSites: async () => [{ url: siteUrl, name: 'Example Campus' }] });
|
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> {
|
async navigateToSiteHome(options: Omit<CoreNavigationOptions, 'reset'> & { siteId?: string } = {}): Promise<boolean> {
|
||||||
const siteId = options.siteId ?? CoreSites.getCurrentSiteId();
|
const siteId = options.siteId ?? CoreSites.getCurrentSiteId();
|
||||||
const landingPagePath = CoreSites.isLoggedIn() && CoreSites.getCurrentSiteId() === siteId ?
|
const landingPagePath = CoreSites.isLoggedIn() && CoreSites.getCurrentSiteId() === siteId ?
|
||||||
this.getLandingTabPage() : 'main';
|
this.getLandingTabPage() : '';
|
||||||
|
|
||||||
return this.navigateToSitePath(landingPagePath, {
|
return this.navigateToSitePath(landingPagePath, {
|
||||||
...options,
|
...options,
|
||||||
|
@ -220,14 +220,12 @@ export class CoreNavigatorService {
|
||||||
|
|
||||||
// If we are logged into a different site, log out first.
|
// If we are logged into a different site, log out first.
|
||||||
if (CoreSites.isLoggedIn() && CoreSites.getCurrentSiteId() !== siteId) {
|
if (CoreSites.isLoggedIn() && CoreSites.getCurrentSiteId() !== siteId) {
|
||||||
const willReload = await CoreSites.logoutForRedirect(siteId, {
|
await CoreSites.logout({
|
||||||
redirectPath: path,
|
...this.getRedirectDataForSitePath(path, options),
|
||||||
redirectOptions: options || {},
|
siteId,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (willReload) {
|
return true;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the path doesn't belong to a site, call standard navigation.
|
// If the path doesn't belong to a site, call standard navigation.
|
||||||
|
@ -243,10 +241,7 @@ export class CoreNavigatorService {
|
||||||
const modal = await CoreLoadings.show();
|
const modal = await CoreLoadings.show();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const loggedIn = await CoreSites.loadSite(siteId, {
|
const loggedIn = await CoreSites.loadSite(siteId, this.getRedirectDataForSitePath(path, options));
|
||||||
redirectPath: path,
|
|
||||||
redirectOptions: options,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!loggedIn) {
|
if (!loggedIn) {
|
||||||
// User has been redirected to the login page and will be redirected to the site path after login.
|
// 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);
|
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.
|
* Get the active route path.
|
||||||
*
|
*
|
||||||
|
@ -547,6 +567,11 @@ export class CoreNavigatorService {
|
||||||
...options,
|
...options,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!path || path.match(/^\/?main\/?$/)) {
|
||||||
|
// Navigating to main, nothing else to do.
|
||||||
|
return this.navigate('/main', options);
|
||||||
|
}
|
||||||
|
|
||||||
path = path.replace(/^(\.|\/main)?\//, '');
|
path = path.replace(/^(\.|\/main)?\//, '');
|
||||||
|
|
||||||
const pathRoot = /^[^/]+/.exec(path)?.[0] ?? '';
|
const pathRoot = /^[^/]+/.exec(path)?.[0] ?? '';
|
||||||
|
|
|
@ -141,14 +141,6 @@ export class CoreSitesProvider {
|
||||||
|
|
||||||
// Remove version classes from body.
|
// Remove version classes from body.
|
||||||
CoreHTMLClasses.removeSiteClasses();
|
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) => {
|
CoreEvents.on(CoreEvents.LOGIN, async (data) => {
|
||||||
|
@ -964,7 +956,7 @@ export class CoreSitesProvider {
|
||||||
promise.finally(() => {
|
promise.finally(() => {
|
||||||
if (siteId) {
|
if (siteId) {
|
||||||
// Logout the currentSite and expire the token.
|
// Logout the currentSite and expire the token.
|
||||||
this.logout();
|
this.internalLogout();
|
||||||
this.setSiteLoggedOut(siteId);
|
this.setSiteLoggedOut(siteId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1123,7 +1115,7 @@ export class CoreSitesProvider {
|
||||||
this.logger.debug(`Delete site ${siteId}`);
|
this.logger.debug(`Delete site ${siteId}`);
|
||||||
|
|
||||||
if (this.currentSite !== undefined && this.currentSite.id == siteId) {
|
if (this.currentSite !== undefined && this.currentSite.id == siteId) {
|
||||||
this.logout();
|
this.internalLogout();
|
||||||
}
|
}
|
||||||
|
|
||||||
const site = await this.getSite(siteId);
|
const site = await this.getSite(siteId);
|
||||||
|
@ -1457,10 +1449,23 @@ export class CoreSitesProvider {
|
||||||
/**
|
/**
|
||||||
* Logout the user.
|
* Logout the user.
|
||||||
*
|
*
|
||||||
* @param options Logout options.
|
* @param options Options.
|
||||||
* @returns Promise resolved when the user is logged out.
|
|
||||||
*/
|
*/
|
||||||
async logout(options: CoreSitesLogoutOptions = {}): Promise<void> {
|
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) {
|
if (!this.currentSite) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1494,6 +1499,7 @@ export class CoreSitesProvider {
|
||||||
* @param siteId Site that will be opened after logout.
|
* @param siteId Site that will be opened after logout.
|
||||||
* @param redirectData Page/url to open after logout.
|
* @param redirectData Page/url to open after logout.
|
||||||
* @returns Promise resolved with boolean: true if app will be reloaded 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> {
|
async logoutForRedirect(siteId: string, redirectData: CoreRedirectPayload): Promise<boolean> {
|
||||||
if (!this.currentSite) {
|
if (!this.currentSite) {
|
||||||
|
@ -1505,7 +1511,7 @@ export class CoreSitesProvider {
|
||||||
CoreRedirects.storeRedirect(siteId, redirectData);
|
CoreRedirects.storeRedirect(siteId, redirectData);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.logout();
|
await this.internalLogout();
|
||||||
|
|
||||||
return CoreSitePlugins.hasSitePluginsLoaded;
|
return CoreSitePlugins.hasSitePluginsLoaded;
|
||||||
}
|
}
|
||||||
|
@ -2480,7 +2486,14 @@ export type CoreSitesLoginTokenResponse = {
|
||||||
/**
|
/**
|
||||||
* Options for logout.
|
* 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.
|
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.
|
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 { CoreLang, CoreLangProvider } from '@services/lang';
|
||||||
|
|
||||||
import { mock, mockSingleton } from '@/testing/utils';
|
import { mock, mockSingleton } from '@/testing/utils';
|
||||||
import { CoreNavigator, CoreNavigatorService } from '@services/navigator';
|
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { Http } from '@singletons';
|
import { Http } from '@singletons';
|
||||||
import { of } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
|
@ -34,13 +33,10 @@ describe('CoreSitesProvider', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('cleans up on logout', async () => {
|
it('cleans up on logout', async () => {
|
||||||
const navigator: CoreNavigatorService = mockSingleton(CoreNavigator, ['navigate']);
|
|
||||||
|
|
||||||
CoreSites.initialize();
|
CoreSites.initialize();
|
||||||
CoreEvents.trigger(CoreEvents.LOGOUT);
|
CoreEvents.trigger(CoreEvents.LOGOUT);
|
||||||
|
|
||||||
expect(langProvider.clearCustomStrings).toHaveBeenCalled();
|
expect(langProvider.clearCustomStrings).toHaveBeenCalled();
|
||||||
expect(navigator.navigate).toHaveBeenCalledWith('/login/sites', { reset: true });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds ionic platform and theme classes', async () => {
|
it('adds ionic platform and theme classes', async () => {
|
||||||
|
|
|
@ -432,14 +432,13 @@ export class CoreCustomURLSchemesProvider {
|
||||||
// Ask the user before changing site.
|
// Ask the user before changing site.
|
||||||
await CoreDomUtils.showConfirm(Translate.instant('core.contentlinks.confirmurlothersite'));
|
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',
|
redirectPath: '/login/credentials',
|
||||||
redirectOptions: { params: pageParams },
|
redirectOptions: { params: pageParams },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (willReload) {
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await CoreNavigator.navigateToLoginCredentials(pageParams);
|
await CoreNavigator.navigateToLoginCredentials(pageParams);
|
||||||
|
|
|
@ -3,9 +3,9 @@ Feature: It navigates properly using deep links.
|
||||||
|
|
||||||
Background:
|
Background:
|
||||||
Given the following "users" exist:
|
Given the following "users" exist:
|
||||||
| username |
|
| username | firstname | lastname |
|
||||||
| student1 |
|
| student1 | david | student |
|
||||||
| student2 |
|
| student2 | pau | student2 |
|
||||||
And the following "courses" exist:
|
And the following "courses" exist:
|
||||||
| fullname | shortname |
|
| fullname | shortname |
|
||||||
| Course 1 | C1 |
|
| Course 1 | C1 |
|
||||||
|
@ -20,7 +20,6 @@ Feature: It navigates properly using deep links.
|
||||||
| forum | user | name | message |
|
| forum | user | name | message |
|
||||||
| Test forum | student1 | Forum topic | Forum message |
|
| Test forum | student1 | Forum topic | Forum message |
|
||||||
And the following config values are set as admin:
|
And the following config values are set as admin:
|
||||||
| forcelogout | 1 | tool_mobile |
|
|
||||||
| defaulthomepage | 0 | |
|
| defaulthomepage | 0 | |
|
||||||
|
|
||||||
Scenario: Receive a push notification
|
Scenario: Receive a push notification
|
||||||
|
@ -78,3 +77,98 @@ Feature: It navigates properly using deep links.
|
||||||
When I go back in the app
|
When I go back in the app
|
||||||
Then I should find "Site home" in the app
|
Then I should find "Site home" in the app
|
||||||
But I should not find "Test forum" 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 |
|
| resource | Test DOC | Test DOC description | 5 | C1 | A doc.doc |
|
||||||
And the following config values are set as admin:
|
And the following config values are set as admin:
|
||||||
| filetypeexclusionlist | rtf,doc | tool_mobile |
|
| 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
|
When I press "Open" in the app
|
||||||
Then the app should have opened a browser tab with url "^blob:"
|
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.
|
* Run an operation inside the angular zone and return result.
|
||||||
*
|
*
|
||||||
* @param operation Operation callback.
|
* @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.
|
* @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();
|
const blockKey = blocking && TestingBehatBlocking.block();
|
||||||
|
let interval: number | undefined;
|
||||||
|
|
||||||
try {
|
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';
|
return 'OK';
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return 'ERROR: ' + error.message;
|
return 'ERROR: ' + error.message;
|
||||||
} finally {
|
} finally {
|
||||||
blockKey && TestingBehatBlocking.unblock(blockKey);
|
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/
|
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 ===
|
=== 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
|
- 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