diff --git a/local_moodleappbehat/tests/behat/behat_app.php b/local_moodleappbehat/tests/behat/behat_app.php index 0f3b92d5e..59ece39db 100644 --- a/local_moodleappbehat/tests/behat/behat_app.php +++ b/local_moodleappbehat/tests/behat/behat_app.php @@ -91,8 +91,6 @@ class behat_app extends behat_app_helper { * @throws ExpectationException Problem with resizing window */ public function i_launch_the_app(string $runtime = '') { - $this->check_tags(); - // Go to page and prepare browser for app. $this->prepare_browser(['skiponboarding' => empty($runtime)]); } @@ -101,18 +99,27 @@ class behat_app extends behat_app_helper { * @Then I wait the app to restart */ public function i_wait_the_app_to_restart() { - // Wait window to reload. - $this->spin(function() { - if ($this->runtime_js('hasInitialized()')) { - // Behat runtime shouldn't be initialized after reload. - throw new DriverException('Window is not reloading properly.'); - } - - return true; - }); - // Prepare testing runtime again. - $this->prepare_browser(['restart' => false]); + $this->prepare_browser(); + } + + /** + * @Then I log out in the app + * + * @param bool $force If force logout or not. + */ + public function i_log_out_in_app($force = true) { + $options = json_encode([ + 'forceLogout' => $force, + ]); + + $result = $this->zone_js("sites.logout($options)"); + + if ($result !== 'OK') { + throw new DriverException('Error on log out - ' . $result); + } + + $this->i_wait_the_app_to_restart(); } /** @@ -567,7 +574,7 @@ class behat_app extends behat_app_helper { /** * Performs a pull to refresh gesture. * - * @When /^I pull to refresh in the app$/ + * @When I pull to refresh in the app * @throws DriverException If the gesture is not available */ public function i_pull_to_refresh_in_the_app() { @@ -841,9 +848,31 @@ class behat_app extends behat_app_helper { * @Given /^I switch offline mode to "(true|false)"$/ * @param string $offline New value for navigator online mode * @throws DriverException If the navigator.online mode is not available + * @deprecated since 4.1 use i_switch_network_connection instead. */ public function i_switch_offline_mode(string $offline) { - $this->runtime_js("network.setForceOffline($offline)"); + $this->i_switch_network_connection($offline == 'true' ? 'offline' : 'wifi'); + } + + /** + * Switch network connection. + * + * @When /^I switch network connection to (wifi|cellular|offline)$/ + * @param string $more New network mode. + * @throws DriverException If the navigator.online mode is not available + */ + public function i_switch_network_connection(string $mode) { + switch ($mode) { + case 'wifi': + $this->runtime_js("network.setForceConnectionMode('$mode');"); + break; + case 'cellular': + $this->runtime_js("network.setForceConnectionMode('$mode');"); + break; + case 'offline': + $this->runtime_js("network.setForceConnectionMode('none');"); + break; + } } } diff --git a/local_moodleappbehat/tests/behat/behat_app_helper.php b/local_moodleappbehat/tests/behat/behat_app_helper.php index 9f8236183..3142ce5f9 100644 --- a/local_moodleappbehat/tests/behat/behat_app_helper.php +++ b/local_moodleappbehat/tests/behat/behat_app_helper.php @@ -95,6 +95,12 @@ class behat_app_helper extends behat_base { public function start_scenario() { $this->check_behat_setup(); $this->fix_moodle_setup(); + + if ($this->apprunning) { + $this->notify_unload(); + $this->apprunning = false; + } + $this->ionicurl = $this->start_or_reuse_ionic(); } @@ -274,17 +280,20 @@ class behat_app_helper extends behat_base { * @throws DriverException If the app fails to load properly */ protected function prepare_browser(array $options = []) { - $restart = $options['restart'] ?? true; + if ($this->evaluate_script('window.behat') && $this->runtime_js('hasInitialized()')) { + // Already initialized. + return; + } - if ($restart) { - if ($this->apprunning) { - $this->notify_unload(); - } + $restart = false; - // Restart the browser and set its size. - $this->getSession()->restart(); + if (!$this->apprunning) { + $this->check_tags(); + + $restart = true; + + // Reset its size. $this->resize_window($this->windowsize, true); - if (empty($this->ionicurl)) { $this->ionicurl = $this->start_or_reuse_ionic(); } @@ -506,6 +515,8 @@ class behat_app_helper extends behat_base { $successXPath = '//page-core-mainmenu'; } + $this->i_log_out_in_app(false); + $this->handle_url($url, $successXPath); } @@ -536,7 +547,6 @@ class behat_app_helper extends behat_base { if ($result !== 'OK') { throw new DriverException('Error handling url - ' . $result); } - if (!empty($successXPath)) { // Wait until the page appears. $this->spin( @@ -550,6 +560,8 @@ class behat_app_helper extends behat_base { } $this->wait_for_pending_js(); + + $this->i_wait_the_app_to_restart(); } /** diff --git a/scripts/create_langindex.sh b/scripts/create_langindex.sh index 3171c270a..a34fe6f6e 100755 --- a/scripts/create_langindex.sh +++ b/scripts/create_langindex.sh @@ -68,6 +68,8 @@ function do_match { print_message "$2" tput setaf 6 grep "$match" $LANGPACKSFOLDER/en/*.php + else + coincidence=0 fi } diff --git a/scripts/langindex.json b/scripts/langindex.json index c61abf87b..5a015724d 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -1705,7 +1705,9 @@ "core.erroropenfilenoextension": "local_moodlemobileapp", "core.erroropenpopup": "local_moodlemobileapp", "core.errorrenamefile": "local_moodlemobileapp", + "core.errorsitesupport": "local_moodlemobileapp", "core.errorsomedatanotdownloaded": "local_moodlemobileapp", + "core.errorsomethingwrong": "local_moodlemobileapp", "core.errorsync": "local_moodlemobileapp", "core.errorsyncblocked": "local_moodlemobileapp", "core.errorurlschemeinvalidscheme": "local_moodlemobileapp", @@ -2179,6 +2181,8 @@ "core.settings.colorscheme-system": "local_moodlemobileapp", "core.settings.colorscheme-system-notice": "local_moodlemobileapp", "core.settings.compilationinfo": "local_moodlemobileapp", + "core.settings.connecttosync": "local_moodlemobileapp", + "core.settings.connectwifitosync": "local_moodlemobileapp", "core.settings.copyinfo": "local_moodlemobileapp", "core.settings.cordovadevicemodel": "local_moodlemobileapp", "core.settings.cordovadeviceosversion": "local_moodlemobileapp", @@ -2200,9 +2204,7 @@ "core.settings.enablefirebaseanalyticsdescription": "local_moodlemobileapp", "core.settings.enablerichtexteditor": "local_moodlemobileapp", "core.settings.enablerichtexteditordescription": "local_moodlemobileapp", - "core.settings.enablesyncwifi": "local_moodlemobileapp", "core.settings.entriesincache": "local_moodlemobileapp", - "core.settings.errorsyncsite": "local_moodlemobileapp", "core.settings.estimatedfreespace": "local_moodlemobileapp", "core.settings.filesystemroot": "local_moodlemobileapp", "core.settings.fontsize": "local_moodlemobileapp", @@ -2219,6 +2221,7 @@ "core.settings.locationhref": "local_moodlemobileapp", "core.settings.loggedin": "message", "core.settings.loggedoff": "message", + "core.settings.logintosync": "local_moodlemobileapp", "core.settings.navigatorlanguage": "local_moodlemobileapp", "core.settings.navigatoruseragent": "local_moodlemobileapp", "core.settings.networkstatus": "local_moodlemobileapp", @@ -2233,7 +2236,9 @@ "core.settings.showdownloadoptions": "local_moodlemobileapp", "core.settings.siteinfo": "local_moodlemobileapp", "core.settings.sites": "moodle", + "core.settings.sitesyncfailed": "local_moodlemobileapp", "core.settings.spaceusage": "local_moodlemobileapp", + "core.settings.syncdatasaver": "local_moodlemobileapp", "core.settings.synchronization": "local_moodlemobileapp", "core.settings.synchronizenow": "local_moodlemobileapp", "core.settings.synchronizenowhelp": "local_moodlemobileapp", diff --git a/src/addons/block/timeline/tests/behat/basic_usage.feature b/src/addons/block/timeline/tests/behat/basic_usage.feature index d91bff7f4..573d6c6c3 100644 --- a/src/addons/block/timeline/tests/behat/basic_usage.feature +++ b/src/addons/block/timeline/tests/behat/basic_usage.feature @@ -49,7 +49,6 @@ Feature: Timeline block. @lms_from3.11 Scenario: See courses inside block Given I entered the app as "student1" - And I press "Open block drawer" in the app Then I should find "Assignment 00" within "Timeline" "ion-card" in the app And I should find "Assignment 02" within "Timeline" "ion-card" in the app And I should find "Assignment 05" within "Timeline" "ion-card" in the app diff --git a/src/addons/messages/tests/behat/basic_usage.feature b/src/addons/messages/tests/behat/basic_usage.feature index 0fcc067db..bd35f8756 100755 --- a/src/addons/messages/tests/behat/basic_usage.feature +++ b/src/addons/messages/tests/behat/basic_usage.feature @@ -163,7 +163,7 @@ Feature: Test basic usage of messages in app And I set the field "Search" to "student1" in the app And I press "Search" "button" in the app And I press "Student1 student1" in the app - And I switch offline mode to "true" + And I switch network connection to offline And I set the field "New message" to "heeey student" in the app And I press "Send" in the app Then I should find "heeey student" in the app @@ -172,7 +172,7 @@ Feature: Test basic usage of messages in app And I press "Send" in the app Then I should find "byee" in the app - When I switch offline mode to "false" + When I switch network connection to wifi And I press the back button in the app And I press "Student1 student1" in the app Then I should find "heeey student" in the app @@ -192,14 +192,14 @@ Feature: Test basic usage of messages in app And I set the field "Search" to "student1" in the app And I press "Search" "button" in the app And I press "Student1 student1" in the app - And I switch offline mode to "true" + And I switch network connection to offline And I set the field "New message" to "heeey student" in the app And I press "Send" in the app And I set the field "New message" to "byee" in the app And I press "Send" in the app Then I should find "byee" in the app - When I switch offline mode to "false" + When I switch network connection to wifi And I run cron tasks in the app Given I entered the app as "student1" @@ -346,12 +346,12 @@ Feature: Test basic usage of messages in app And I press "Send" in the app Then I should find "self conversation online" in the app - When I switch offline mode to "true" + When I switch network connection to offline And I set the field "New message" to "self conversation offline" in the app And I press "Send" in the app Then I should find "self conversation offline" in the app - When I switch offline mode to "false" + When I switch network connection to wifi And I press the back button in the app And I press "Student1 student1" in the app And I press "Display options" in the app diff --git a/src/addons/mod/assign/tests/behat/basic_usage.feature b/src/addons/mod/assign/tests/behat/basic_usage.feature index ec41e1017..67e535139 100755 --- a/src/addons/mod/assign/tests/behat/basic_usage.feature +++ b/src/addons/mod/assign/tests/behat/basic_usage.feature @@ -79,6 +79,7 @@ Feature: Test basic usage of assignment activity in app # Submit second attempt as a student Given I entered the assign activity "assignment1" on course "Course 1" as "student1" in the app + When I pull to refresh in the app Then I should find "Reopened" in the app And I should find "2 out of Unlimited" in the app And I should find "Add a new attempt based on previous submission" in the app @@ -97,6 +98,7 @@ Feature: Test basic usage of assignment activity in app # View second attempt as a teacher Given I entered the assign activity "assignment1" on course "Course 1" as "teacher1" in the app When I press "Participants" in the app + And I pull to refresh in the app And I press "Student student" near "assignment1" in the app Then I should find "Online text submissions" in the app And I should find "Submission test 2nd attempt" in the app @@ -104,14 +106,14 @@ Feature: Test basic usage of assignment activity in 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 When I press "Add submission" in the app - And I switch offline mode to "true" + And I switch network connection to offline And I set the field "Online text submissions" to "Submission test" in the app And I press "Save" in the app And I press "Submit assignment" in the app And I press "OK" in the app Then I should find "This Assignment has offline data to be synchronised." in the app - When I switch offline mode to "false" + When I switch network connection to wifi And I press the back button in the app And I press "assignment1" in the app And I press "Information" in the app @@ -122,7 +124,7 @@ Feature: Test basic usage of assignment activity in 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 When I press "Add submission" in the app - And I switch offline mode to "true" + 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 press "Save" in the app Then I should find "This Assignment has offline data to be synchronised." in the app @@ -139,7 +141,7 @@ Feature: Test basic usage of assignment activity in app And I press "OK" in the app Then I should find "This Assignment has offline data to be synchronised." in the app - When I switch offline mode to "false" + When I switch network connection to wifi And I press the back button in the app And I press "assignment1" in the app Then I should find "Submitted for grading" in the app diff --git a/src/addons/mod/chat/tests/behat/basic_usage.feature b/src/addons/mod/chat/tests/behat/basic_usage.feature index 35a4972f6..8b0cd1f65 100755 --- a/src/addons/mod/chat/tests/behat/basic_usage.feature +++ b/src/addons/mod/chat/tests/behat/basic_usage.feature @@ -34,6 +34,9 @@ Feature: Test basic usage of chat in app And I press "Send" in the app Then I should find "Hi!" in the app And I should find "I am David" in the app + # Confirm leave the page + And I press the back button in the app + And I press "OK" in the app # Read messages, view connected users, send beep and reply as student2 Given I entered the chat activity "Test chat name" on course "Course 1" as "student2" in the app @@ -62,6 +65,9 @@ Feature: Test basic usage of chat in app When I set the field "New message" to "I am David" in the app And I press "Send" in the app Then I should find "I am David" in the app + # Confirm leave the page + And I press the back button in the app + And I press "OK" in the app # Read messages from past sessions as student2 Given I entered the chat activity "Test chat name" on course "Course 1" as "student2" in the app diff --git a/src/addons/mod/chat/tests/behat/navigation.feature b/src/addons/mod/chat/tests/behat/navigation.feature index 3490ea6f0..75b06cd18 100644 --- a/src/addons/mod/chat/tests/behat/navigation.feature +++ b/src/addons/mod/chat/tests/behat/navigation.feature @@ -23,6 +23,9 @@ Feature: Test chat navigation And I set the field "New message" to "Test message" in the app And I press "Send" in the app Then I should find "Test message" in the app + # Confirm leave the page + And I press the back button in the app + And I press "OK" in the app Scenario: Tablet navigation on chat Given I entered the course "Course 1" as "student2" in the app diff --git a/src/addons/mod/choice/tests/behat/basic_usage.feature b/src/addons/mod/choice/tests/behat/basic_usage.feature index 753181599..aae180c51 100755 --- a/src/addons/mod/choice/tests/behat/basic_usage.feature +++ b/src/addons/mod/choice/tests/behat/basic_usage.feature @@ -72,7 +72,7 @@ Feature: Test basic usage of choice activity in app | 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 When I select "Option 1" in the app - And I switch offline mode to "true" + And I switch network connection to offline And I select "Option 2" in the app And I press "Save my choice" in the app Then I should find "Are you sure" in the app @@ -85,7 +85,7 @@ Feature: Test basic usage of choice activity in app And I should not find "Option 2: 1" in the app And I should not find "Option 3: 0" in the app - When I switch offline mode to "false" + When I switch network connection to wifi And I press the back button in the app And I press "Test single choice name" in the app Then I should find "Test single choice description" in the app @@ -103,7 +103,7 @@ Feature: Test basic usage of choice activity in app | 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 When I select "Option 1" in the app - And I switch offline mode to "true" + And I switch network connection to offline And I select "Option 2" in the app And I press "Save my choice" in the app Then I should find "Are you sure" in the app @@ -114,7 +114,7 @@ Feature: Test basic usage of choice activity in app And I should not find "Option 2: 1" in the app And I should not find "Option 3: 0" in the app - When I switch offline mode to "false" + When I switch network connection to wifi And I run cron tasks in the app And I wait loading to finish in the app Then I should find "Option 1: 0" in the app @@ -133,7 +133,7 @@ Feature: Test basic usage of choice activity in app Then I should find "Downloaded" within "Test single choice name" "ion-item" in the app And I press the back button in the app - When I switch offline mode to "true" + When I switch network connection to offline And I press "Test multi choice name" in the app Then I should find "There was a problem connecting to the site. Please check your connection and try again." in the app @@ -152,7 +152,7 @@ Feature: Test basic usage of choice activity in app And I should not find "Option 2: 1" in the app And I should not find "Option 3: 0" in the app - When I switch offline mode to "false" + When I switch network connection to wifi And I press the back button in the app And I press "Test single choice name" in the app Then I should find "Option 1: 0" in the app diff --git a/src/addons/mod/data/tests/behat/sync.feature b/src/addons/mod/data/tests/behat/sync.feature index 699739215..41b7b9333 100644 --- a/src/addons/mod/data/tests/behat/sync.feature +++ b/src/addons/mod/data/tests/behat/sync.feature @@ -33,7 +33,7 @@ Feature: Users can store entries in database activities when offline and sync wh Scenario: Create entry (offline) Given I entered the data activity "Web links" on course "Course 1" as "student1" in the app - And I switch offline mode to "true" + And I switch network connection to offline And I should find "No entries in database" in the app When I press "Add entries" in the app And I set the following fields to these values in the app: @@ -44,7 +44,7 @@ Feature: Users can store entries in database activities when offline and sync wh 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 the back button in the app - And I switch offline mode to "false" + 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 And I should find "Moodle community site" in the app @@ -63,7 +63,8 @@ Feature: Users can store entries in database activities when offline and sync wh And I press "Information" in the app And I press "Download" in the app And I wait until the page is ready - And I switch offline mode to "true" + And I close the popup in the app + And I switch network connection to offline When I press "Edit" in the app And I set the following fields to these values in the app: | URL | https://moodlecloud.com/ | @@ -75,7 +76,7 @@ Feature: Users can store entries in database activities when offline and sync wh 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 press the back button in the app - And I switch offline mode to "false" + 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 And I should not find "Moodle community site" in the app @@ -85,7 +86,7 @@ Feature: Users can store entries in database activities when offline and sync wh And I press "Information" in the app And I press "Refresh" in the app And I wait until the page is ready - And I switch offline mode to "true" + And I switch network connection to offline 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 @@ -93,7 +94,7 @@ Feature: Users can store entries in database activities when offline and sync wh 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 press the back button in the app - And I switch offline mode to "false" + 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 And I should not find "Moodle Cloud" in the app @@ -112,7 +113,8 @@ Feature: Users can store entries in database activities when offline and sync wh And I press "Information" in the app And I press "Download" in the app And I wait until the page is ready - When I switch offline mode to "true" + And I close the popup in the app + When I switch network connection to offline 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 @@ -121,7 +123,7 @@ Feature: Users can store entries in database activities when offline and sync wh And I should find "This Database has offline data to be synchronised" in the app And I press "Restore" in the app And I press the back button in the app - And I switch offline mode to "false" + And I switch network connection to wifi And I press "Web links" near "General" in the app Then I should find "https://moodle.org/" in the app And I should find "Moodle community site" in the app diff --git a/src/addons/mod/forum/tests/behat/basic_usage.feature b/src/addons/mod/forum/tests/behat/basic_usage.feature index afdb62ebc..eb5750689 100755 --- a/src/addons/mod/forum/tests/behat/basic_usage.feature +++ b/src/addons/mod/forum/tests/behat/basic_usage.feature @@ -96,7 +96,7 @@ Feature: Test basic usage of forum activity in app Then I should find "Reply" in the app When I press the back button in the app - And I switch offline mode to "true" + And I switch network connection to offline And I press "Initial discussion" in the app And I press "Reply" in the app And I set the field "Message" to "not sent reply" in the app @@ -110,7 +110,7 @@ Feature: Test basic usage of forum activity in app Then I should find "Not sent" in the app And I should find "This Discussion has offline data to be synchronised" in the app - When I switch offline mode to "false" + When I switch network connection to wifi And I press the back button in the app And I press "Initial discussion" in the app Then I should not find "Not sent" in the app @@ -118,7 +118,7 @@ Feature: Test basic usage of forum activity in app Scenario: Edit a not sent new discussion offline Given I entered the forum activity "Test forum name" on course "Course 1" as "student1" in the app - When I switch offline mode to "true" + 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: | Subject | Auto-test | @@ -129,7 +129,7 @@ Feature: Test basic usage of forum activity in app And I press "Post to forum" in the app Then I should find "This Forum has offline data to be synchronised." in the app - When I switch offline mode to "false" + When I switch network connection to wifi And I press "Auto-test" in the app Then I should find "Post to forum" in the app @@ -151,7 +151,7 @@ Feature: Test basic usage of forum activity in app Then I should find "Edit" in the app When I press "Edit" in the app - And I switch offline mode to "true" + And I switch network connection to offline And I set the field "Message" to "Auto-test message edited" in the app And I press "Save changes" in the app Then I should find "There was a problem connecting to the site. Please check your connection and try again." in the app @@ -163,7 +163,7 @@ Feature: Test basic usage of forum activity in app And I press "Edit" in the app Then I should find "There was a problem connecting to the site. Please check your connection and try again." in the app - When I switch offline mode to "false" + When I switch network connection to wifi And I press "OK" in the app And I press "Edit" in the app And I set the field "Message" to "Auto-test message edited" in the app @@ -183,7 +183,7 @@ Feature: Test basic usage of forum activity in app When I press "Delete" in the app And I press "Cancel" in the app - And I switch offline mode to "true" + And I switch network connection to offline And I press "Display options" near "Reply" in the app Then I should find "Delete" in the app @@ -192,7 +192,7 @@ Feature: Test basic usage of forum activity in app When I press "OK" in the app And I close the popup in the app - And I switch offline mode to "false" + And I switch network connection to wifi And I press "Display options" near "Reply" in the app And I press "Delete" in the app And I press "Delete" in the app @@ -215,14 +215,14 @@ Feature: Test basic usage of forum activity in app When I press "Auto-test" in the app And I press "None" near "Auto-test message" in the app And I press "1" near "Cancel" in the app - And I switch offline mode to "true" + And I switch network connection to offline And I press "None" near "test2" in the app And I press "0" near "Cancel" in the app Then I should find "Data stored in the device because it couldn't be sent. It will be sent automatically later." in the app And I should find "Average of ratings: -" in the app And I should find "Average of ratings: 1" in the app - When I switch offline mode to "false" + When I switch network connection to wifi And I press the back button in the app Then I should find "This Forum has offline data to be synchronised." in the app @@ -244,7 +244,7 @@ Feature: Test basic usage of forum activity in app Scenario: Reply a post offline Given I entered the forum activity "Test forum name" on course "Course 1" as "student1" in the app When I press "Initial discussion" in the app - And I switch offline mode to "true" + And I switch network connection to offline Then I should find "Reply" in the app When I press "Reply" in the app @@ -255,7 +255,7 @@ Feature: Test basic usage of forum activity in app And I should find "Not sent" in the app When I press the back button in the app - And I switch offline mode to "false" + And I switch network connection to wifi And I press "Initial discussion" in the app Then I should find "Initial discussion message" in the app And I should find "ReplyMessage" in the app @@ -263,7 +263,7 @@ Feature: Test basic usage of forum activity in 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 - When I switch offline mode to "true" + 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: | Subject | DiscussionSubject | @@ -273,7 +273,7 @@ Feature: Test basic usage of forum activity in app And I should find "Not sent" in the app And I should find "This Forum has offline data to be synchronised." in the app - When I switch offline mode to "false" + When I switch network connection to wifi And I press the back button in the app And I press "Test forum name" in the app And I press "Information" in the app @@ -286,7 +286,7 @@ Feature: Test basic usage of forum activity in app Scenario: New discussion offline & Auto-sync forum Given I entered the forum activity "Test forum name" on course "Course 1" as "student1" in the app - When I switch offline mode to "true" + 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: | Subject | DiscussionSubject | @@ -296,7 +296,7 @@ Feature: Test basic usage of forum activity in app And I should find "Not sent" in the app And I should find "This Forum has offline data to be synchronised." in the app - When I switch offline mode to "false" + When I switch network connection to wifi And I run cron tasks in the app And I wait loading to finish in the app Then I should not find "Not sent" in the app @@ -314,7 +314,7 @@ Feature: Test basic usage of forum activity in app Then I should find "Downloaded" within "Test forum name" "ion-item" in the app When I press the back button in the app - And I switch offline mode to "true" + And I switch network connection to offline And I press "Test forum name" in the app Then I should find "Initial discussion" in the app diff --git a/src/addons/mod/forum/tests/behat/navigation.feature b/src/addons/mod/forum/tests/behat/navigation.feature index 80bdef26b..89dae9dee 100644 --- a/src/addons/mod/forum/tests/behat/navigation.feature +++ b/src/addons/mod/forum/tests/behat/navigation.feature @@ -103,7 +103,7 @@ Feature: Test forum navigation # Offline When I press the back button in the app And I press "Add discussion topic" in the app - And I switch offline mode to "true" + And I switch network connection to offline And I set the following fields to these values in the app: | Subject | Offline discussion 1 | | Message | Offline discussion 1 message | @@ -199,7 +199,7 @@ Feature: Test forum navigation # Offline When I press "Add discussion topic" in the app - And I switch offline mode to "true" + And I switch network connection to offline And I set the following fields to these values in the app: | Subject | Offline discussion 1 | | Message | Offline discussion 1 message | diff --git a/src/addons/mod/glossary/tests/behat/basic_usage.feature b/src/addons/mod/glossary/tests/behat/basic_usage.feature index c3103b0a3..6a32f476c 100644 --- a/src/addons/mod/glossary/tests/behat/basic_usage.feature +++ b/src/addons/mod/glossary/tests/behat/basic_usage.feature @@ -100,6 +100,7 @@ Feature: Test basic usage of glossary in app # View comments as a student Given I entered the glossary activity "Test glossary" on course "Course 1" as "student1" in the app And I press "Eggplant" in the app + When I pull to refresh in the app Then I should find "Comments (2)" in the app When I press "Comments" in the app @@ -111,7 +112,7 @@ Feature: Test basic usage of glossary in app When I press "Course downloads" in the app When I press "Download" within "Test glossary" "ion-item" in the app And I press the back button in the app - And I switch offline mode to "true" + And I switch network connection to offline And I press "Test glossary" in the app Then the header should be "Test glossary" in the app And I should find "Cucumber" in the app @@ -156,7 +157,7 @@ Feature: Test basic usage of glossary in app Scenario: Sync Given I entered the glossary activity "Test glossary" on course "Course 1" as "student1" in the app And I press "Add a new entry" in the app - And I switch offline mode to "true" + And I switch network connection to offline And I set the following fields to these values in the app: | Concept | Broccoli | | Definition | Brassica oleracea var. italica | @@ -181,7 +182,7 @@ Feature: Test basic usage of glossary in app And I should find "Entries to be synced" in the app And I should find "This Glossary has offline data to be synchronised." in the app - When I switch offline mode to "false" + When I switch network connection to wifi And I press "Information" in the app And I press "Synchronise now" in the app Then the header should be "Test glossary" in the app @@ -211,13 +212,13 @@ Feature: Test basic usage of glossary in app # Rate entries as teacher2 Given I entered the glossary activity "Test glossary" on course "Course 1" as "teacher2" in the app And I press "Cucumber" in the app - And I switch offline mode to "true" + And I switch network connection to offline And I press "None" in the app And I press "0" in the app Then I should find "Data stored in the device because it couldn't be sent. It will be sent automatically later." in the app And I should find "Average of ratings: 1" in the app - When I switch offline mode to "false" + When I switch network connection to wifi And I press the back button in the app Then I should find "This Glossary has offline data to be synchronised." in the app diff --git a/src/addons/mod/glossary/tests/behat/navigation.feature b/src/addons/mod/glossary/tests/behat/navigation.feature index 04b988ecf..309c0a1f0 100644 --- a/src/addons/mod/glossary/tests/behat/navigation.feature +++ b/src/addons/mod/glossary/tests/behat/navigation.feature @@ -173,7 +173,7 @@ Feature: Test glossary navigation When I press the back button in the app And I press "Clear search" in the app And I press "Add a new entry" in the app - And I switch offline mode to "true" + And I switch network connection to offline And I set the following fields to these values in the app: | Concept | Tomato | | Definition | Tomato is a fruit | @@ -274,7 +274,7 @@ Feature: Test glossary navigation # Offline When I press "Clear search" in the app And I press "Add a new entry" in the app - And I switch offline mode to "true" + And I switch network connection to offline And I set the following fields to these values in the app: | Concept | Tomato | | Definition | Tomato is a fruit | diff --git a/src/addons/mod/survey/tests/behat/basic_usage.feature b/src/addons/mod/survey/tests/behat/basic_usage.feature index b6e0755c4..f12395a58 100755 --- a/src/addons/mod/survey/tests/behat/basic_usage.feature +++ b/src/addons/mod/survey/tests/behat/basic_usage.feature @@ -233,12 +233,12 @@ Feature: Test basic usage of survey activity in app | 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 offline mode to "true" + And 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." - When I switch offline mode to "false" + When I switch network connection to wifi And I press the back button in the app And I press "Test survey critical incidents" in the app And I press "Information" in the app @@ -255,7 +255,7 @@ Feature: Test basic usage of survey activity in app And I press "Course downloads" in the app And I press "Download" within "Test survey critical incidents" "ion-item" in the app And I press the back button in the app - And I switch offline mode to "true" + And I switch network connection to offline And I press "Test survey name" in the app Then I should see "There was a problem connecting to the site. Please check your connection and try again." @@ -266,7 +266,7 @@ Feature: Test basic usage of survey activity in app And I press "OK" in the app Then I should see "This Survey has offline data to be synchronised." - When I switch offline mode to "false" + When I switch network connection to wifi And I run cron tasks in the app Then I should not see "This Survey has offline data to be synchronised." And I should see "You have completed this survey." diff --git a/src/core/classes/errors/errors.ts b/src/core/classes/errors/errors.ts index 517d91910..9a4b21a23 100644 --- a/src/core/classes/errors/errors.ts +++ b/src/core/classes/errors/errors.ts @@ -23,7 +23,7 @@ import { CoreAjaxWSError } from './ajaxwserror'; import { CoreCaptureError } from './captureerror'; import { CoreNetworkError } from './network-error'; import { CoreSiteError } from './siteerror'; -import { CoreErrorWithTitle } from './errorwithtitle'; +import { CoreErrorWithOptions } from './errorwithtitle'; import { CoreHttpError } from './httperror'; export const CORE_ERRORS_CLASSES: Type[] = [ @@ -36,6 +36,6 @@ export const CORE_ERRORS_CLASSES: Type[] = [ CoreSilentError, CoreSiteError, CoreWSError, - CoreErrorWithTitle, + CoreErrorWithOptions, CoreHttpError, ]; diff --git a/src/core/classes/errors/errorwithtitle.ts b/src/core/classes/errors/errorwithtitle.ts index 0ff83d3b7..8a9e322b5 100644 --- a/src/core/classes/errors/errorwithtitle.ts +++ b/src/core/classes/errors/errorwithtitle.ts @@ -12,20 +12,24 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { AlertButton } from '@ionic/angular'; import { CoreError } from './error'; /** * Error with an explicit title describing the problem (instead of just "Error" or a generic message). * This title should be used to communicate the problem with users, and if it's undefined it should be omitted. + * The error also may contain customizable action buttons. */ -export class CoreErrorWithTitle extends CoreError { +export class CoreErrorWithOptions extends CoreError { title?: string; + buttons?: AlertButton[]; - constructor(message?: string, title?: string) { + constructor(message?: string, title?: string, buttons?: AlertButton[]) { super(message); this.title = title; + this.buttons = buttons; } } diff --git a/src/core/components/tabs-outlet/core-tabs-outlet.html b/src/core/components/tabs-outlet/core-tabs-outlet.html index ff4c3f825..8c92bb295 100644 --- a/src/core/components/tabs-outlet/core-tabs-outlet.html +++ b/src/core/components/tabs-outlet/core-tabs-outlet.html @@ -8,9 +8,10 @@ - + diff --git a/src/core/components/tabs/core-tabs.html b/src/core/components/tabs/core-tabs.html index 89e426368..928d55486 100644 --- a/src/core/components/tabs/core-tabs.html +++ b/src/core/components/tabs/core-tabs.html @@ -7,10 +7,11 @@ - + + (keyup)="tabAction.keyUp(tab.id, $event)" [layout]="layout" role="tab" [attr.aria-controls]="tab.id" + [attr.aria-selected]="selected == tab.id" [tabindex]="selected == tab.id ? 0 : -1"> {{ tab.title | translate}} diff --git a/src/core/features/comments/tests/behat/basic_usage.feature b/src/core/features/comments/tests/behat/basic_usage.feature index 9fef313be..f431b3e96 100644 --- a/src/core/features/comments/tests/behat/basic_usage.feature +++ b/src/core/features/comments/tests/behat/basic_usage.feature @@ -36,8 +36,9 @@ Feature: Test basic usage of comments in app | Field description | Test field description | And I press "Save" And I close the browser tab opened by the app - When I entered the course "Course 1" as "teacher1" in the app - And I press "Data" in the app + And I close the popup in the app + + When I pull to refresh in the app And I press "Add entries" in the app And I set the field "Test field name" to "Test" in the app And I press "Save" in the app @@ -75,7 +76,7 @@ Feature: Test basic usage of comments in app Scenario: Add comments offline & Delete comments offline & Sync comments (database) Given I entered the data activity "Data" on course "Course 1" as "teacher1" in the app - When I press "Information" in the app + And I press "Information" in the app And I press "Open in browser" in the app And I switch to the browser tab opened by the app And I log in as "teacher1" @@ -84,14 +85,15 @@ Feature: Test basic usage of comments in app | Field description | Test field description | And I press "Save" And I close the browser tab opened by the app + And I close the popup in the app - Given I entered the data activity "Data" on course "Course 1" as "teacher1" in the app - Then I press "Add entries" in the app + When I pull to refresh in the app + And I press "Add entries" in the app And I set the field "Test field name" to "Test" in the app And I press "Save" in the app And I press "More" in the app And I press "Comments (0)" in the app - And I switch offline mode to "true" + And I switch network connection to offline And I set the field "Add a comment..." to "comment test" in the app And I press "Send" in the app Then I should find "Data stored in the device because it couldn't be sent. It will be sent automatically later." in the app @@ -100,7 +102,7 @@ Feature: Test basic usage of comments in app When I press the back button in the app And I press "Comments (0)" in the app - And I switch offline mode to "false" + And I switch network connection to wifi And I press "Display options" in the app And I press "Synchronise now" in the app And I close the popup in the app @@ -109,7 +111,7 @@ Feature: Test basic usage of comments in app When I press the back button in the app And I press "Comments (1)" in the app - And I switch offline mode to "true" + And I switch network connection to offline And I press "Toggle delete buttons" in the app And I press "Delete" in the app And I press "Delete" near "Cancel" in the app @@ -120,7 +122,7 @@ Feature: Test basic usage of comments in app When I press the back button in the app And I press "Comments (1)" in the app - And I switch offline mode to "false" + And I switch network connection to wifi And I press "Display options" in the app And I press "Synchronise now" in the app And I close the popup in the app @@ -179,7 +181,7 @@ Feature: Test basic usage of comments in app And I press "Save" in the app And I press "potato" in the app And I press "Comments (0)" in the app - And I switch offline mode to "true" + And I switch network connection to offline And I set the field "Add a comment..." to "comment test" in the app And I press "Send" in the app Then I should find "Data stored in the device because it couldn't be sent. It will be sent automatically later." in the app @@ -188,7 +190,7 @@ Feature: Test basic usage of comments in app When I press the back button in the app And I press "Comments (0)" in the app - And I switch offline mode to "false" + And I switch network connection to wifi And I press "Display options" in the app And I press "Synchronise now" in the app And I close the popup in the app @@ -197,7 +199,7 @@ Feature: Test basic usage of comments in app When I press the back button in the app And I press "Comments (1)" in the app - And I switch offline mode to "true" + And I switch network connection to offline And I press "Toggle delete buttons" in the app And I press "Delete" in the app And I press "Delete" near "Cancel" in the app @@ -208,7 +210,7 @@ Feature: Test basic usage of comments in app When I press the back button in the app And I press "Comments (1)" in the app - And I switch offline mode to "false" + And I switch network connection to wifi And I press "Display options" in the app And I press "Synchronise now" in the app And I close the popup in the app @@ -262,7 +264,7 @@ Feature: Test basic usage of comments in app And I should find "Blog body" in the app When I press "Comments (0)" in the app - And I switch offline mode to "true" + And I switch network connection to offline And I set the field "Add a comment..." to "comment test" in the app And I press "Send" in the app Then I should find "Data stored in the device because it couldn't be sent. It will be sent automatically later." in the app @@ -271,7 +273,7 @@ Feature: Test basic usage of comments in app When I press the back button in the app And I press "Comments (0)" in the app - And I switch offline mode to "false" + And I switch network connection to wifi And I press "Display options" in the app And I press "Synchronise now" in the app And I close the popup in the app @@ -280,7 +282,7 @@ Feature: Test basic usage of comments in app When I press the back button in the app And I press "Comments (1)" in the app - And I switch offline mode to "true" + And I switch network connection to offline And I press "Toggle delete buttons" in the app And I press "Delete" in the app And I press "Delete" near "Cancel" in the app @@ -291,7 +293,7 @@ Feature: Test basic usage of comments in app When I press the back button in the app And I press "Comments (1)" in the app - And I switch offline mode to "false" + And I switch network connection to wifi And I press "Display options" in the app And I press "Synchronise now" in the app And I close the popup in the app diff --git a/src/core/features/course/services/course.ts b/src/core/features/course/services/course.ts index e58699c2a..0e987dc12 100644 --- a/src/core/features/course/services/course.ts +++ b/src/core/features/course/services/course.ts @@ -177,7 +177,9 @@ export class CoreCourseProvider { CorePlatform.resume.subscribe(() => { // Run the handler the app is open to keep user in online status. setTimeout(() => { - CoreCronDelegate.forceCronHandlerExecution(CoreCourseLogCronHandler.name); + CoreUtils.ignoreErrors( + CoreCronDelegate.forceCronHandlerExecution(CoreCourseLogCronHandler.name), + ); }, 1000); }); diff --git a/src/core/features/course/tests/behat/basic_usage.feature b/src/core/features/course/tests/behat/basic_usage.feature index 9be9bcf6d..078555930 100755 --- a/src/core/features/course/tests/behat/basic_usage.feature +++ b/src/core/features/course/tests/behat/basic_usage.feature @@ -411,6 +411,8 @@ Feature: Test basic usage of one course in app And I select "Enrolment methods" from the "jump" singleselect And I click on "Enable" "icon" in the "Self enrolment (Student)" "table_row" And I close the browser tab opened by the app + And I close the popup in the app + Given I entered the app as "student2" When I press "Site home" in the app And I press "Available courses" in the app diff --git a/src/core/features/courses/tests/behat/basic_usage.feature b/src/core/features/courses/tests/behat/basic_usage.feature index b61cbd65d..803f091fa 100755 --- a/src/core/features/courses/tests/behat/basic_usage.feature +++ b/src/core/features/courses/tests/behat/basic_usage.feature @@ -148,9 +148,12 @@ Feature: Test basic usage of courses in app # Grade assignment as teacher Given I entered the app as "teacher1" - When I press "Grade" in the app + When I pull to refresh in the app + And I press "Grade" in the app Then the header should be "assignment" in the app - And I should find "Test assignment description" in the app + + When I pull to refresh in the app + Then I should find "Test assignment description" in the app And I should find "Time remaining" in the app When I press "Needs grading" in the app diff --git a/src/core/features/login/tests/behat/basic_usage.feature b/src/core/features/login/tests/behat/basic_usage.feature index d03a08844..62ffc00fe 100755 --- a/src/core/features/login/tests/behat/basic_usage.feature +++ b/src/core/features/login/tests/behat/basic_usage.feature @@ -26,8 +26,7 @@ Feature: Test basic usage of login in app And I should find "Connect to Moodle" in the app Scenario: Add a new account in the app & Site name in displayed when adding a new account - When I enter the app - And I press the back button in the app + When I launch the app And I set the field "Your site" to "$WWWROOT" in the app And I press "Connect to your site" in the app Then I should find "Acceptance test site" in the app @@ -42,9 +41,7 @@ Feature: Test basic usage of login in app Scenario: Add a non existing account When I enter the app And I log in as "student1" - 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 + When I log out in the app And I press "Add" in the app And I set the field "Your site" to "Wrong Site Address" in the app And I press enter in the app @@ -63,11 +60,16 @@ Feature: Test basic usage of login in app Then I should find "Cannot connect" in the app And I should find "Wrong Site Address" 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 press the user menu button in the app - And I press "Log out" in the app - And I wait the app to restart + When I log out in the app Then I should find "Acceptance test site" in the app And I press "Edit accounts list" in the app And I press "Remove account" near "Acceptance test site" in the app @@ -75,14 +77,14 @@ Feature: Test basic usage of login in app Then I should find "Connect to Moodle" in the app But I should not find "Acceptance test site" in the app - Scenario: Require minium version of the app for a site - + Scenario: Require minium (previous) version of the app for a site # Log in with a previous required version Given the following config values are set as admin: | minimumversion | 3.8.1 | tool_mobile | When I enter the app Then I should not find "App update required" in the app + Scenario: Require minium (future) version of the app for a site # Log in with a future required version Given the following config values are set as admin: | minimumversion | 11.0.0 | tool_mobile | diff --git a/src/core/features/mainmenu/components/user-menu/user-menu.ts b/src/core/features/mainmenu/components/user-menu/user-menu.ts index c20d16bce..4b02fa22e 100644 --- a/src/core/features/mainmenu/components/user-menu/user-menu.ts +++ b/src/core/features/mainmenu/components/user-menu/user-menu.ts @@ -71,7 +71,14 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy { // Load the handlers. if (this.siteInfo) { - this.user = await CoreUser.getProfile(this.siteInfo.userid); + try { + this.user = await CoreUser.getProfile(this.siteInfo.userid); + } catch { + this.user = { + id: this.siteInfo.userid, + fullname: this.siteInfo.fullname, + }; + } this.subscription = CoreUserDelegate.getProfileHandlersFor(this.user, CoreUserDelegateContext.USER_MENU) .subscribe((handlers) => { diff --git a/src/core/features/pushnotifications/services/pushnotifications.ts b/src/core/features/pushnotifications/services/pushnotifications.ts index e817633bf..366ae942d 100644 --- a/src/core/features/pushnotifications/services/pushnotifications.ts +++ b/src/core/features/pushnotifications/services/pushnotifications.ts @@ -746,11 +746,7 @@ export class CorePushNotificationsProvider { CoreEvents.trigger(CoreEvents.DEVICE_REGISTERED_IN_MOODLE, {}, site.getId()); // Insert the device in the local DB. - try { - await this.registeredDevicesTables[site.getId()].insert(data); - } catch (err) { - // Ignore errors. - } + await CoreUtils.ignoreErrors(this.registeredDevicesTables[site.getId()].insert(data)); } } finally { // Remove pending unregisters for this site. diff --git a/src/core/features/settings/lang.json b/src/core/features/settings/lang.json index 42114fcd0..1a6550718 100644 --- a/src/core/features/settings/lang.json +++ b/src/core/features/settings/lang.json @@ -4,8 +4,8 @@ "appsettings": "App settings", "appversion": "App version", "cannotsyncloggedout": "This site cannot be synchronised because you've logged out. Please try again when you're logged in the site again.", - "cannotsyncoffline": "Cannot synchronise offline.", - "cannotsyncwithoutwifi": "Cannot synchronise because the current settings only allow to synchronise when connected to Wi-Fi. Please connect to a Wi-Fi network.", + "cannotsyncoffline": "Site synchronisation failed because your device is not connected to the internet.", + "cannotsyncwithoutwifi": "Your device is not connected to Wi-Fi. Connect to a Wi-Fi network or turn off Data Saver in the app settings.", "changelanguage": "Change to {{$a}}", "changelanguagealert": "Changing the language will restart the app.", "colorscheme-dark": "Dark", @@ -14,6 +14,8 @@ "colorscheme-system": "System default", "colorscheme": "Color Scheme", "compilationinfo": "Compilation info", + "connectwifitosync": "Connect to a Wi-Fi network or turn off Data saver to synchronise sites.", + "connecttosync": "Your device is offline. Connect to the internet to synchronise sites.", "copyinfo": "Copy device info on the clipboard", "cordovadevicemodel": "Cordova device model", "cordovadeviceosversion": "Cordova device OS version", @@ -35,9 +37,7 @@ "enablefirebaseanalyticsdescription": "If enabled, the app will collect anonymous data usage.", "enablerichtexteditor": "Enable text editor", "enablerichtexteditordescription": "If enabled, a text editor will be available when entering content.", - "enablesyncwifi": "Allow sync only when on Wi-Fi", "entriesincache": "{{$a}} entries in cache", - "errorsyncsite": "Error synchronising site data. Please check your Internet connection and try again.", "estimatedfreespace": "Estimated free space", "filesystemroot": "File system root", "fontsize": "Text size", @@ -54,6 +54,7 @@ "locationhref": "Web view URL", "loggedin": "Online", "loggedoff": "Offline", + "logintosync": "Log in to synchronise", "navigatorlanguage": "Navigator language", "navigatoruseragent": "Navigator userAgent", "networkstatus": "Internet connection status", @@ -68,7 +69,9 @@ "showdownloadoptions": "Show download options", "siteinfo": "Site info", "sites": "Sites", + "sitesyncfailed": "Site synchronisation failed", "spaceusage": "Space usage", + "syncdatasaver": "Data saver: Synchronise only when on Wi-Fi", "synchronization": "Synchronisation", "synchronizenow": "Synchronise now", "synchronizenowhelp": "Synchronising a site will send pending changes and all offline activity stored in the device and will synchronise some data like messages and notifications.", diff --git a/src/core/features/settings/pages/site/site.html b/src/core/features/settings/pages/site/site.html index 0470d5026..215d5f82e 100644 --- a/src/core/features/settings/pages/site/site.html +++ b/src/core/features/settings/pages/site/site.html @@ -37,11 +37,20 @@

- + + + + + + {{ 'core.settings.connectwifitosync' | translate }} + {{ 'core.settings.connecttosync' | translate }} + + diff --git a/src/core/features/settings/pages/site/site.ts b/src/core/features/settings/pages/site/site.ts index 57826c5ea..4a017185b 100644 --- a/src/core/features/settings/pages/site/site.ts +++ b/src/core/features/settings/pages/site/site.ts @@ -25,6 +25,11 @@ import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/ import { CoreSettingsHandlersSource } from '@features/settings/classes/settings-handlers-source'; import { CoreSettingsHelper } from '@features/settings/services/settings-helper'; import { CoreDomUtils } from '@services/utils/dom'; +import { CoreNetwork } from '@services/network'; +import { Subscription } from 'rxjs'; +import { NgZone } from '@singletons'; +import { CoreConstants } from '@/core/constants'; +import { CoreConfig } from '@services/config'; /** * Page that displays the list of site settings pages. @@ -39,8 +44,13 @@ export class CoreSitePreferencesPage implements AfterViewInit, OnDestroy { handlers: CoreListItemsManager; + dataSaver = false; + limitedConnection = false; + isOnline = true; + protected siteId: string; protected sitesObserver: CoreEventObserver; + protected networkObserver: Subscription; protected isDestroyed = false; constructor() { @@ -53,12 +63,25 @@ export class CoreSitePreferencesPage implements AfterViewInit, OnDestroy { this.sitesObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => { this.refreshData(); }, this.siteId); + + this.isOnline = CoreNetwork.isOnline(); + this.limitedConnection = this.isOnline && CoreNetwork.isNetworkAccessLimited(); + + this.networkObserver = CoreNetwork.onChange().subscribe(() => { + // Execute the callback in the Angular zone, so change detection doesn't stop working. + NgZone.run(() => { + this.isOnline = CoreNetwork.isOnline(); + this.limitedConnection = this.isOnline && CoreNetwork.isNetworkAccessLimited(); + }); + }); } /** - * View loaded. + * @inheritdoc */ async ngAfterViewInit(): Promise { + this.dataSaver = await CoreConfig.get(CoreConstants.SETTINGS_SYNC_ONLY_ON_WIFI, true); + const pageToOpen = CoreNavigator.getRouteParam('page'); try { @@ -94,7 +117,7 @@ export class CoreSitePreferencesPage implements AfterViewInit, OnDestroy { if (this.isDestroyed) { return; } - CoreDomUtils.showErrorModalDefault(error, 'core.settings.errorsyncsite', true); + CoreDomUtils.showErrorModalDefault(error, 'core.settings.sitesyncfailed', true); } } @@ -121,11 +144,12 @@ export class CoreSitePreferencesPage implements AfterViewInit, OnDestroy { } /** - * Page destroyed. + * @inheritdoc */ ngOnDestroy(): void { this.isDestroyed = true; - this.sitesObserver?.off(); + this.sitesObserver.off(); + this.networkObserver.unsubscribe(); this.handlers.destroy(); } diff --git a/src/core/features/settings/pages/synchronization/synchronization.html b/src/core/features/settings/pages/synchronization/synchronization.html index 7e4911b89..00c94086f 100644 --- a/src/core/features/settings/pages/synchronization/synchronization.html +++ b/src/core/features/settings/pages/synchronization/synchronization.html @@ -17,37 +17,97 @@ - +

{{ 'core.settings.syncsettings' | translate }}

- {{ 'core.settings.enablesyncwifi' | translate }} - - - - - -

{{ 'core.settings.sites' | translate }}

-
-
-

- + {{ 'core.settings.syncdatasaver' | translate }}

-

{{ site.fullName }}

-

{{ site.siteUrlWithoutProtocol }}

- - - - - + +
+ + + + + + + {{ 'core.settings.connectwifitosync' | translate }} + {{ 'core.settings.connecttosync' | translate }} + + + + + + + +

{{ 'core.accounts' | translate }}

+
+
+ + + + +

+ +

+

{{ + accountsList.currentSite.siteUrlWithoutProtocol }} +

+
+
+ + + + + + + + +
+ + + + +

+ +

+

{{ sites[0].siteUrlWithoutProtocol }}

+
+
+ + + + +
+
+ + + + + {{ 'core.pictureof' | translate:{$a: site.fullName} }} + + +

{{site.fullName}}

+

{{ 'core.settings.logintosync' | translate }}

+
+ + + + + + + + +
diff --git a/src/core/features/settings/pages/synchronization/synchronization.ts b/src/core/features/settings/pages/synchronization/synchronization.ts index 5b5b664c3..47e3b6f3f 100644 --- a/src/core/features/settings/pages/synchronization/synchronization.ts +++ b/src/core/features/settings/pages/synchronization/synchronization.ts @@ -16,11 +16,15 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { CoreConstants } from '@/core/constants'; import { CoreEventObserver, CoreEvents } from '@singletons/events'; -import { CoreSites, CoreSiteBasicInfo } from '@services/sites'; +import { CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreConfig } from '@services/config'; import { CoreSettingsHelper } from '@features/settings/services/settings-helper'; -import { Translate } from '@singletons'; +import { NgZone, Translate } from '@singletons'; +import { CoreAccountsList, CoreLoginHelper } from '@features/login/services/login-helper'; +import { CoreNetwork } from '@services/network'; +import { Subscription } from 'rxjs'; +import { CoreNavigator } from '@services/navigator'; /** * Page that displays the synchronization settings. @@ -28,61 +32,101 @@ import { Translate } from '@singletons'; @Component({ selector: 'page-core-app-settings-synchronization', templateUrl: 'synchronization.html', + styleUrls: ['../../../login/sitelist.scss'], }) export class CoreSettingsSynchronizationPage implements OnInit, OnDestroy { - sites: CoreSiteBasicInfo[] = []; + accountsList: CoreAccountsList = { + sameSite: [], + otherSites: [], + count: 0, + }; + sitesLoaded = false; - currentSiteId = ''; - syncOnlyOnWifi = false; + dataSaver = false; + limitedConnection = false; + isOnline = true; + protected isDestroyed = false; protected sitesObserver: CoreEventObserver; + protected networkObserver: Subscription; constructor() { - this.currentSiteId = CoreSites.getCurrentSiteId(); - this.sitesObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, async (data) => { - const site = await CoreSites.getSite(data.siteId); + const siteId = data.siteId; - const siteEntry = this.sites.find((siteEntry) => siteEntry.id == site.id); - if (siteEntry) { - const siteInfo = site.getInfo(); + let siteEntry = siteId === this.accountsList.currentSite?.id + ? this.accountsList.currentSite + : undefined; - siteEntry.siteName = site.getSiteName(); + if (!siteEntry) { + siteEntry = this.accountsList.sameSite.find((siteEntry) => siteEntry.id === siteId); + } - if (siteInfo) { - siteEntry.siteUrl = siteInfo.siteurl; - siteEntry.fullName = siteInfo.fullname; - } + if (!siteEntry) { + this.accountsList.otherSites.some((sites) => { + siteEntry = sites.find((siteEntry) => siteEntry.id === siteId); + + return siteEntry; + }); + } + + if (!siteEntry) { + return; + } + + const site = await CoreSites.getSite(siteId); + + const siteInfo = site.getInfo(); + + siteEntry.siteName = site.getSiteName(); + + if (siteInfo) { + siteEntry.siteUrl = siteInfo.siteurl; + siteEntry.fullName = siteInfo.fullname; } }); + + this.isOnline = CoreNetwork.isOnline(); + this.limitedConnection = this.isOnline && CoreNetwork.isNetworkAccessLimited(); + + this.networkObserver = CoreNetwork.onChange().subscribe(() => { + // Execute the callback in the Angular zone, so change detection doesn't stop working. + NgZone.run(() => { + this.isOnline = CoreNetwork.isOnline(); + this.limitedConnection = this.isOnline && CoreNetwork.isNetworkAccessLimited(); + }); + }); + } /** - * View loaded. + * @inheritdoc */ async ngOnInit(): Promise { + const currentSiteId = CoreSites.getCurrentSiteId(); + try { - this.sites = await CoreSites.getSortedSites(); + this.accountsList = await CoreLoginHelper.getAccountsList(currentSiteId); } catch { // Ignore errors. } this.sitesLoaded = true; - this.syncOnlyOnWifi = await CoreConfig.get(CoreConstants.SETTINGS_SYNC_ONLY_ON_WIFI, true); + this.dataSaver = await CoreConfig.get(CoreConstants.SETTINGS_SYNC_ONLY_ON_WIFI, true); } /** * Called when sync only on wifi setting is enabled or disabled. */ syncOnlyOnWifiChanged(): void { - CoreConfig.set(CoreConstants.SETTINGS_SYNC_ONLY_ON_WIFI, this.syncOnlyOnWifi ? 1 : 0); + CoreConfig.set(CoreConstants.SETTINGS_SYNC_ONLY_ON_WIFI, this.dataSaver ? 1 : 0); } /** - * Syncrhonizes a site. + * Synchronizes a site. * * @param siteId Site ID. */ @@ -95,10 +139,20 @@ export class CoreSettingsSynchronizationPage implements OnInit, OnDestroy { return; } - CoreDomUtils.showErrorModalDefault(error, 'core.settings.errorsyncsite', true); + CoreDomUtils.showErrorModalDefault(error, 'core.settings.sitesyncfailed', true); } } + /** + * Changes site. + * + * @param siteId Site ID. + */ + async login(siteId: string): Promise { + // This navigation will logout and navigate to the site home. + await CoreNavigator.navigateToSiteHome({ preferCurrentTab: false , siteId }); + } + /** * Returns true if site is beeing synchronized. * @@ -120,11 +174,12 @@ export class CoreSettingsSynchronizationPage implements OnInit, OnDestroy { } /** - * Page destroyed. + * @inheritdoc */ ngOnDestroy(): void { this.isDestroyed = true; - this.sitesObserver?.off(); + this.sitesObserver.off(); + this.networkObserver.unsubscribe(); } } diff --git a/src/core/features/settings/services/settings-helper.ts b/src/core/features/settings/services/settings-helper.ts index 1d1ee82ea..18e754265 100644 --- a/src/core/features/settings/services/settings-helper.ts +++ b/src/core/features/settings/services/settings-helper.ts @@ -29,6 +29,7 @@ import { CoreCourse } from '@features/course/services/course'; import { makeSingleton, Translate } from '@singletons'; import { CoreError } from '@classes/errors/error'; import { Observable, Subject } from 'rxjs'; +import { CoreTextUtils } from '@services/utils/text'; /** * Object with space usage and cache entries that can be erased. @@ -260,6 +261,7 @@ export class CoreSettingsHelperProvider { const site = await CoreSites.getSite(siteId); const hasSyncHandlers = CoreCronDelegate.hasManualSyncHandlers(); + // All these errors should not happen on manual sync because are prevented on UI. if (site.isLoggedOut()) { // Cannot sync logged out sites. throw new CoreError(Translate.instant('core.settings.cannotsyncloggedout')); @@ -286,6 +288,8 @@ export class CoreSettingsHelperProvider { try { await syncPromise; + } catch (error) { + throw CoreTextUtils.addTitleToError(error, Translate.instant('core.settings.sitesyncfailed')); } finally { delete this.syncPromises[siteId]; } diff --git a/src/core/features/settings/tests/behat/settings_navigation.feature b/src/core/features/settings/tests/behat/settings_navigation.feature index 9096b1138..5e14ec472 100644 --- a/src/core/features/settings/tests/behat/settings_navigation.feature +++ b/src/core/features/settings/tests/behat/settings_navigation.feature @@ -1,4 +1,4 @@ -@app @javascript +@app @javascript @core_settings Feature: It navigates properly within settings. Background: diff --git a/src/core/features/settings/tests/behat/sync.feature b/src/core/features/settings/tests/behat/sync.feature new file mode 100644 index 000000000..274706060 --- /dev/null +++ b/src/core/features/settings/tests/behat/sync.feature @@ -0,0 +1,150 @@ +@app @javascript @core_settings +Feature: It synchronise sites properly + + Background: + Given the following "courses" exist: + | fullname | shortname | category | + | Course 1 | C1 | 0 | + And the following "users" exist: + | username | firstname | lastname | + | student1 | david | student | + | student2 | pau | student2 | + And the following "course enrolments" exist: + | user | course | role | + | student1 | C1 | student | + | student2 | C1 | student | + And the following "activities" exist: + | activity | name | intro | course | idnumber | option | allowmultiple | allowupdate | showresults | + | choice | Sync choice | Intro | C1 | choice1 | Option 1, Option 2, Option 3 | 0 | 0 | 1 | + + 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 + When I switch network connection to offline + And I select "Option 1" in the app + And I press "Save my choice" in the app + And I press "OK" in the app + Then I should find "This Choice has offline data to be synchronised." in the app + + # Cannot sync in offline + When I press the back button in the app + And I press the back button in the app + And I press the user menu button in the app + And I press "Preferences" in the app + Then I should find "Your device is offline. Connect to the internet to synchronise sites." in the app + And I should not find "Connect to a Wi-Fi network or turn off Data saver to synchronise sites." in the app + + When I switch network connection to wifi + Then I should not find "Your device is offline. Connect to the internet to synchronise sites." in the app + And I should not find "Connect to a Wi-Fi network or turn off Data saver to synchronise sites." in the app + + # Check synced + When I press "Synchronise now" "button" in the app + And I wait loading to finish in the app + And I switch network connection to offline + And I press the back button in the app + And I entered the course "Course 1" in the app + And I press "Sync choice" in the app + Then I should not find "This Choice has offline data to be synchronised." in the app + + # Check limited sync. + When I switch network connection to cellular + And I press the back button in the app + And I press the back button in the app + And I press the user menu button in the app + And I press "Preferences" in the app + + # Cannot sync in cellular + Then I should find "Connect to a Wi-Fi network or turn off Data saver to synchronise sites." in the app + And I should not find "Your device is offline. Connect to the internet to synchronise sites." in the app + + Scenario: Sync sites messages with different network connections + Given I entered the app as "student1" + + # Wifi + data saver on. + When I press the more menu button in the app + And I press "App settings" in the app + And I press "Synchronisation" in the app + Then I should not find "Your device is offline. Connect to the internet to synchronise sites." in the app + And I should not find "Connect to a Wi-Fi network or turn off Data saver to synchronise sites." in the app + And I should find "Accounts" in the app + + # Limited + data saver on. + When I switch network connection to cellular + Then I should not find "Your device is offline. Connect to the internet to synchronise sites." in the app + And I should find "Connect to a Wi-Fi network or turn off Data saver to synchronise sites." in the app + And I should not find "Accounts" in the app + + # Offline + data saver on. + When I switch network connection to offline + Then I should find "Your device is offline. Connect to the internet to synchronise sites." in the app + And I should not find "Connect to a Wi-Fi network or turn off Data saver to synchronise sites." in the app + And I should not find "Accounts" in the app + + # Wifi + data saver off. + When I press "Data saver: Synchronise only when on Wi-Fi" in the app + And I switch network connection to wifi + Then I should not find "Your device is offline. Connect to the internet to synchronise sites." in the app + And I should not find "Connect to a Wi-Fi network or turn off Data saver to synchronise sites." in the app + And I should find "Accounts" in the app + + # Limited + data saver off. + When I switch network connection to cellular + Then I should not find "Your device is offline. Connect to the internet to synchronise sites." in the app + And I should not find "Connect to a Wi-Fi network or turn off Data saver to synchronise sites." in the app + And I should find "Accounts" in the app + + # Offline + data saver off. + When I switch network connection to offline + Then I should find "Your device is offline. Connect to the internet to synchronise sites." in the app + And I should not find "Connect to a Wi-Fi network or turn off Data saver to synchronise sites." in the app + And I should not find "Accounts" in the app + + Scenario: Sync logged in and logged out sites + Given I entered the app as "student1" + And I log out in the app + And I entered the choice activity "Sync choice" on course "Course 1" as "student2" in the app + + # Add something offline + When I switch network connection to offline + And I select "Option 1" in the app + And I press "Save my choice" in the app + And I press "OK" in the app + Then I should find "This Choice has offline data to be synchronised." in the app + + When I press the back button in the app + And I press the back button in the app + And I press the more menu button in the app + And I press "App settings" in the app + And I press "Synchronisation" in the app + And I switch network connection to wifi + Then I should find "Accounts" in the app + + # Check synced + When I press "Synchronise now" "button" in the app + And I wait loading to finish in the app + And I switch network connection to offline + And I press the back button in the app + And I entered the course "Course 1" in the app + And I press "Sync choice" in the app + Then I should not find "This Choice has offline data to be synchronised." in the app + + # Test log in to sync + When I press the back button in the app + And I press the back button in the app + And I press the more menu button in the app + And I press "App settings" in the app + And I press "Synchronisation" in the app + And I switch network connection to wifi + Then I should find "Accounts" in the app + And I should find "Log in to synchronise" in the app + + When I press "Log in" in the app + Then I should find "Reconnect" in the app + + When I set the field "Password" to "student1" in the app + And I press "Log in" in the app + And I press the more menu button in the app + And I press "App settings" in the app + And I press "Synchronisation" in the app + Then I should not find "Log in to synchronise" in the app diff --git a/src/core/features/usertours/components/user-tour/user-tour.scss b/src/core/features/usertours/components/user-tour/user-tour.scss index 2e37de273..812b509bc 100644 --- a/src/core/features/usertours/components/user-tour/user-tour.scss +++ b/src/core/features/usertours/components/user-tour/user-tour.scss @@ -1,7 +1,7 @@ :host { --popover-background: var(--ion-overlay-background-color, var(--ion-background-color, #fff)); - z-index: 99; + z-index: 105; // Main menu is 101. width: 100%; height: 100%; display: none; diff --git a/src/core/features/usertours/components/user-tour/user-tour.ts b/src/core/features/usertours/components/user-tour/user-tour.ts index 13a736198..cda15c4d2 100644 --- a/src/core/features/usertours/components/user-tour/user-tour.ts +++ b/src/core/features/usertours/components/user-tour/user-tour.ts @@ -62,6 +62,7 @@ export class CoreUserToursUserTourComponent implements AfterViewInit, OnDestroy @Output() afterDismiss = new EventEmitter(); @HostBinding('class.is-active') active = false; @HostBinding('class.is-popover') popover = false; + @HostBinding('class.backdrop') backdrop = true; @ViewChild('wrapper') wrapper?: ElementRef; focusStyles?: string; diff --git a/src/core/lang.json b/src/core/lang.json index a3e73bb03..c83d0aed7 100644 --- a/src/core/lang.json +++ b/src/core/lang.json @@ -96,6 +96,7 @@ "downloading": "Downloading", "edit": "Edit", "emptysplit": "This page will appear blank if the left panel is empty or is loading.", + "endonesteptour": "Got it", "error": "Error", "errorchangecompletion": "An error occurred while changing the completion status. Please try again.", "errordeletefile": "Error deleting the file. Please try again.", @@ -111,7 +112,9 @@ "erroropenfilenoextension": "Error opening file: the file doesn't have an extension.", "erroropenpopup": "This activity is trying to open a popup. This is not supported in the app.", "errorrenamefile": "Error renaming file. Please try again.", + "errorsitesupport": "If the problem persists, contact site support.", "errorsomedatanotdownloaded": "If you downloaded this activity, please notice that some data isn't downloaded during the download process for performance and data usage reasons.", + "errorsomethingwrong": "Something went wrong. Please try again.", "errorsync": "An error occurred while synchronising. Please try again.", "errorsyncblocked": "This {{$a}} cannot be synchronised right now because of an ongoing process. Please try again later. If the problem persists, try restarting the app.", "errorurlschemeinvalidscheme": "This URL is meant to be used in another app: {{$a}}.", @@ -248,8 +251,8 @@ "resources": "Resources", "restore": "Restore", "restricted": "Restricted", - "retry": "Retry", "resume": "Resume", + "retry": "Retry", "save": "Save", "savechanges": "Save changes", "scanqr": "Scan QR code", @@ -328,11 +331,10 @@ "user": "User", "userdeleted": "This user account has been deleted", "userdetails": "User details", - "usernotfullysetup": "User not fully set-up", "usernologin": "Authentication has been revoked for this account", - "usersuspended": "Registration suspended", - "endonesteptour": "Got it", + "usernotfullysetup": "User not fully set-up", "users": "Users", + "usersuspended": "Registration suspended", "view": "View", "viewcode": "View code", "vieweditor": "View editor", @@ -351,8 +353,8 @@ "year": "year", "years": "years", "yes": "Yes", - "youreoffline": "You are offline", - "youreonline": "You are back online", + "youreoffline": "Your device is offline", + "youreonline": "Your device is back online", "zoomin": "Zoom In", "zoomout": "Zoom Out" } diff --git a/src/core/services/app.ts b/src/core/services/app.ts index a734676e8..9a3632274 100644 --- a/src/core/services/app.ts +++ b/src/core/services/app.ts @@ -30,7 +30,7 @@ import { CoreDatabaseTable } from '@classes/database/database-table'; import { CorePromisedValue } from '@classes/promised-value'; import { Subscription } from 'rxjs'; import { CorePlatform } from '@services/platform'; -import { CoreNetwork } from '@services/network'; +import { CoreNetwork, CoreNetworkConnection } from '@services/network'; /** * Factory to provide some global functionalities, like access to the global app database. @@ -644,10 +644,10 @@ export class CoreAppProvider { * Set value of forceOffline flag. If true, the app will think the device is offline. * * @param value Value to set. - * @deprecated since 4.1.0. Use CoreNetwork instead. + * @deprecated since 4.1.0. Use CoreNetwork.setForceConnectionMode instead. */ setForceOffline(value: boolean): void { - CoreNetwork.setForceOffline(value); + CoreNetwork.setForceConnectionMode(value ? CoreNetworkConnection.NONE : CoreNetworkConnection.WIFI); } /** diff --git a/src/core/services/cron.ts b/src/core/services/cron.ts index df6ec5e33..32ec54823 100644 --- a/src/core/services/cron.ts +++ b/src/core/services/cron.ts @@ -21,7 +21,7 @@ import { CoreUtils } from '@services/utils/utils'; import { CoreConstants } from '@/core/constants'; import { CoreError } from '@classes/errors/error'; -import { makeSingleton } from '@singletons'; +import { makeSingleton, Translate } from '@singletons'; import { CoreLogger } from '@singletons/logger'; import { APP_SCHEMA, CRON_TABLE_NAME, CronDBEntry } from '@services/database/cron'; import { asyncInstance } from '../utils/async-instance'; @@ -81,10 +81,11 @@ export class CoreCronDelegateService { protected async checkAndExecuteHandler(name: string, force?: boolean, siteId?: string): Promise { if (!this.handlers[name] || !this.handlers[name].execute) { // Invalid handler. - const message = `Cannot execute handler because is invalid: ${name}`; - this.logger.debug(message); + this.logger.debug(`Cannot execute cron job because is invalid: ${name}`); - throw new CoreError(message); + throw new CoreError( + Translate.instant('core.errorsomethingwrong') + '
' + Translate.instant('core.errorsitesupport'), + ); } const usesNetwork = this.handlerUsesNetwork(name); @@ -92,11 +93,10 @@ export class CoreCronDelegateService { if (usesNetwork && !CoreNetwork.isOnline()) { // Offline, stop executing. - const message = `Cannot execute handler because device is offline: ${name}`; - this.logger.debug(message); + this.logger.debug(`Cron job failed because your device is not connected to the internet: ${name}`); this.stopHandler(name); - throw new CoreError(message); + throw new CoreError(Translate.instant('core.settings.cannotsyncoffline')); } if (isSync) { @@ -105,11 +105,10 @@ export class CoreCronDelegateService { if (syncOnlyOnWifi && !CoreNetwork.isWifi()) { // Cannot execute in this network connection, retry soon. - const message = `Cannot execute handler because device is using limited connection: ${name}`; - this.logger.debug(message); + this.logger.debug(`Cron job failed because your device has a limited internet connection: ${name}`); this.scheduleNextExecution(name, CoreCronDelegateService.MIN_INTERVAL); - throw new CoreError(message); + throw new CoreError(Translate.instant('core.settings.cannotsyncwithoutwifi')); } } @@ -118,7 +117,7 @@ export class CoreCronDelegateService { try { await this.executeHandler(name, force, siteId); - this.logger.debug(`Execution of handler '${name}' was a success.`); + this.logger.debug(`Cron job '${name}' was successfully executed.`); await CoreUtils.ignoreErrors(this.setHandlerLastExecutionTime(name, Date.now())); @@ -127,11 +126,10 @@ export class CoreCronDelegateService { return; } catch (error) { // Handler call failed. Retry soon. - const message = `Execution of handler '${name}' failed.`; - this.logger.error(message, error); + this.logger.error(`Cron job '${name}' failed.`, error); this.scheduleNextExecution(name, CoreCronDelegateService.MIN_INTERVAL); - throw new CoreError(message); + throw error; } }); diff --git a/src/core/services/network.ts b/src/core/services/network.ts index d8a2f9a64..72f9ec98f 100644 --- a/src/core/services/network.ts +++ b/src/core/services/network.ts @@ -18,6 +18,17 @@ import { Network } from '@ionic-native/network/ngx'; import { makeSingleton } from '@singletons'; import { Observable, Subject, merge } from 'rxjs'; +export enum CoreNetworkConnection { + UNKNOWN = 'unknown', + ETHERNET = 'ethernet', + WIFI = 'wifi', + CELL_2G = '2g', + CELL_3G = '3g', + CELL_4G = '4g', + CELL = 'cellular', + NONE = 'none', +}; + /** * Service to manage network connections. */ @@ -28,9 +39,21 @@ export class CoreNetworkService extends Network { protected connectObservable = new Subject<'connected'>(); protected disconnectObservable = new Subject<'disconnected'>(); - protected forceOffline = false; + protected forceConnectionMode?: CoreNetworkConnection; protected online = false; + get connectionType(): CoreNetworkConnection { + if (this.forceConnectionMode !== undefined) { + return this.forceConnectionMode; + } + + if (CorePlatform.isMobile()) { + return this.type as CoreNetworkConnection; + } + + return this.online ? CoreNetworkConnection.WIFI : CoreNetworkConnection.NONE; + } + /** * Initialize the service. */ @@ -38,20 +61,25 @@ export class CoreNetworkService extends Network { this.checkOnline(); if (CorePlatform.isMobile()) { - this.onChange().subscribe(() => { + // We cannot directly listen to onChange because it depends on + // onConnect and onDisconnect that have been already overriden. + super.onConnect().subscribe(() => { + this.fireObservable(); + }); + super.onDisconnect().subscribe(() => { this.fireObservable(); }); } else { // eslint-disable-next-line @typescript-eslint/no-explicit-any ( window).Connection = { - UNKNOWN: 'unknown', // eslint-disable-line @typescript-eslint/naming-convention - ETHERNET: 'ethernet', // eslint-disable-line @typescript-eslint/naming-convention - WIFI: 'wifi', // eslint-disable-line @typescript-eslint/naming-convention - CELL_2G: '2g', // eslint-disable-line @typescript-eslint/naming-convention - CELL_3G: '3g', // eslint-disable-line @typescript-eslint/naming-convention - CELL_4G: '4g', // eslint-disable-line @typescript-eslint/naming-convention - CELL: 'cellular', // eslint-disable-line @typescript-eslint/naming-convention - NONE: 'none', // eslint-disable-line @typescript-eslint/naming-convention + UNKNOWN: CoreNetworkConnection.UNKNOWN, // eslint-disable-line @typescript-eslint/naming-convention + ETHERNET: CoreNetworkConnection.ETHERNET, // eslint-disable-line @typescript-eslint/naming-convention + WIFI: CoreNetworkConnection.WIFI, // eslint-disable-line @typescript-eslint/naming-convention + CELL_2G: CoreNetworkConnection.CELL_2G, // eslint-disable-line @typescript-eslint/naming-convention + CELL_3G: CoreNetworkConnection.CELL_3G, // eslint-disable-line @typescript-eslint/naming-convention + CELL_4G: CoreNetworkConnection.CELL_4G, // eslint-disable-line @typescript-eslint/naming-convention + CELL: CoreNetworkConnection.CELL, // eslint-disable-line @typescript-eslint/naming-convention + NONE: CoreNetworkConnection.NONE, // eslint-disable-line @typescript-eslint/naming-convention }; window.addEventListener('online', () => { @@ -65,12 +93,13 @@ export class CoreNetworkService extends Network { } /** - * Set value of forceOffline flag. If true, the app will think the device is offline. + * Set value of forceConnectionMode flag. + * The app will think the device is offline or limited connection. * * @param value Value to set. */ - setForceOffline(value: boolean): void { - this.forceOffline = !!value; + setForceConnectionMode(value: CoreNetworkConnection): void { + this.forceConnectionMode = value; this.fireObservable(); } @@ -89,20 +118,15 @@ export class CoreNetworkService extends Network { * @return Whether the app is online. */ checkOnline(): void { - if (this.forceOffline) { + if (this.forceConnectionMode === CoreNetworkConnection.NONE) { this.online = false; return; } - if (!CorePlatform.isMobile()) { - this.online = navigator.onLine; + const type = this.connectionType; - return; - } - - let online = this.type !== null && this.type != this.Connection.NONE && - this.type != this.Connection.UNKNOWN; + let online = type !== null && type !== CoreNetworkConnection.NONE && type !== CoreNetworkConnection.UNKNOWN; // Double check we are not online because we cannot rely 100% in Cordova APIs. if (!online && navigator.onLine) { @@ -123,6 +147,7 @@ export class CoreNetworkService extends Network { /** * Returns an observable to notify when the app is connected. + * It will also be fired when connection type changes. * * @return Observable. */ @@ -143,12 +168,11 @@ export class CoreNetworkService extends Network { * Fires the correct observable depending on the connection status. */ protected fireObservable(): void { - const previousOnline = this.online; - this.checkOnline(); - if (this.online && !previousOnline) { + + if (this.online) { this.connectObservable.next('connected'); - } else if (!this.online && previousOnline) { + } else { this.disconnectObservable.next('disconnected'); } } @@ -159,18 +183,16 @@ export class CoreNetworkService extends Network { * @return Whether the device uses a limited connection. */ isNetworkAccessLimited(): boolean { - if (!CorePlatform.isMobile()) { - return false; - } - - const limited = [ - this.Connection.CELL_2G, - this.Connection.CELL_3G, - this.Connection.CELL_4G, - this.Connection.CELL, + const limited: CoreNetworkConnection[] = [ + CoreNetworkConnection.CELL_2G, + CoreNetworkConnection.CELL_3G, + CoreNetworkConnection.CELL_4G, + CoreNetworkConnection.CELL, ]; - return limited.indexOf(this.type) > -1; + const type = this.connectionType; + + return limited.indexOf(type) > -1; } /** diff --git a/src/core/services/sites.ts b/src/core/services/sites.ts index d508b06b3..15bce3bba 100644 --- a/src/core/services/sites.ts +++ b/src/core/services/sites.ts @@ -51,7 +51,7 @@ import { CoreRedirectPayload } from './navigator'; import { CoreSitesFactory } from './sites-factory'; import { CoreText } from '@singletons/text'; import { CoreLoginHelper } from '@features/login/services/login-helper'; -import { CoreErrorWithTitle } from '@classes/errors/errorwithtitle'; +import { CoreErrorWithOptions } from '@classes/errors/errorwithtitle'; import { CoreAjaxError } from '@classes/errors/ajaxerror'; import { CoreAjaxWSError } from '@classes/errors/ajaxwserror'; import { CoreSitePlugins } from '@features/siteplugins/services/siteplugins'; @@ -870,7 +870,7 @@ export class CoreSitesProvider { const siteUrlAllowed = await CoreLoginHelper.isSiteUrlAllowed(site.getURL(), false); if (!siteUrlAllowed) { - throw new CoreErrorWithTitle(Translate.instant('core.login.sitenotallowed')); + throw new CoreErrorWithOptions(Translate.instant('core.login.sitenotallowed')); } this.currentSite = site; @@ -1185,6 +1185,7 @@ export class CoreSitesProvider { siteName: CoreConstants.CONFIG.sitename == '' ? siteInfo?.sitename: CoreConstants.CONFIG.sitename, avatar: siteInfo?.userpictureurl, siteHomeId: siteInfo?.siteid || 1, + loggedOut: !!site.loggedOut, }; formattedSites.push(basicInfo); } @@ -1277,7 +1278,7 @@ export class CoreSitesProvider { /** * Logout the user. * - * @param forceLogout If true, site will be marked as logged out, no matter the value tool_mobile_forcelogout. + * @param options Logout options. * @return Promise resolved when the user is logged out. */ async logout(options: CoreSitesLogoutOptions = {}): Promise { @@ -1923,6 +1924,7 @@ export type CoreSiteBasicInfo = { avatar?: string; // User's avatar. badge?: number; // Badge to display in the site. siteHomeId?: number; // Site home ID. + loggedOut: boolean; // If Site is logged out. }; /** diff --git a/src/core/services/utils/dom.ts b/src/core/services/utils/dom.ts index 70cd4c47a..3ee12557a 100644 --- a/src/core/services/utils/dom.ts +++ b/src/core/services/utils/dom.ts @@ -46,7 +46,6 @@ import { CoreViewerImageComponent } from '@features/viewer/components/image/imag import { CoreFormFields, CoreForms } from '../../singletons/form'; import { CoreModalLateralTransitionEnter, CoreModalLateralTransitionLeave } from '@classes/modal-lateral-transition'; import { CoreZoomLevel } from '@features/settings/services/settings-helper'; -import { CoreErrorWithTitle } from '@classes/errors/errorwithtitle'; import { AddonFilterMultilangHandler } from '@addons/filter/multilang/services/handlers/multilang'; import { CoreSites } from '@services/sites'; import { NavigationStart } from '@angular/router'; @@ -1345,17 +1344,22 @@ export class CoreDomUtilsProvider { const alertOptions: AlertOptions = { message: message, - buttons: [Translate.instant('core.ok')], }; if (this.isNetworkError(message, error)) { alertOptions.cssClass = 'core-alert-network-error'; - } else if (error instanceof CoreErrorWithTitle) { + } else if (typeof error !== 'string' && 'title' in error) { alertOptions.header = error.title || undefined; } else { alertOptions.header = Translate.instant('core.error'); } + if (typeof error !== 'string' && 'buttons' in error && typeof error.buttons !== 'undefined') { + alertOptions.buttons = error.buttons; + } else { + alertOptions.buttons = [Translate.instant('core.ok')]; + } + return this.showAlertWithOptions(alertOptions, autocloseTime); } diff --git a/src/core/services/utils/text.ts b/src/core/services/utils/text.ts index 723b3574c..6c024c41a 100644 --- a/src/core/services/utils/text.ts +++ b/src/core/services/utils/text.ts @@ -26,6 +26,7 @@ import { CoreFileHelper } from '@services/file-helper'; import { CoreDomUtils } from './dom'; import { CoreText } from '@singletons/text'; import { CoreUrl } from '@singletons/url'; +import { AlertButton } from '@ionic/angular'; /** * Different type of errors the app can treat. @@ -37,6 +38,8 @@ export type CoreTextErrorObject = { body?: string; debuginfo?: string; backtrace?: string; + title?: string; + buttons?: AlertButton[]; }; /* @@ -149,6 +152,27 @@ export class CoreTextUtilsProvider { return error; } + /** + * Add some title to an error message. + * + * @param error Error message or object. + * @param title Title to add. + * @return Modified error. + */ + addTitleToError(error: string | CoreError | CoreTextErrorObject | undefined | null, title: string): CoreTextErrorObject { + let improvedError: CoreTextErrorObject = {}; + + if (typeof error === 'string') { + improvedError.message = error; + } else if (error && 'message' in error) { + improvedError = error; + } + + improvedError.title = improvedError.title || title; + + return improvedError; + } + /** * Given an address as a string, return a URL to open the address in maps. * diff --git a/src/testing/services/behat-dom.ts b/src/testing/services/behat-dom.ts index f872f3bac..fed631c74 100644 --- a/src/testing/services/behat-dom.ts +++ b/src/testing/services/behat-dom.ts @@ -18,9 +18,6 @@ import { CoreUtils } from '@services/utils/utils'; import { makeSingleton, NgZone } from '@singletons'; import { TestingBehatElementLocator, TestingBehatFindOptions } from './behat-runtime'; -// Containers that block containers behind them. -const blockingContainers = ['ION-ALERT', 'ION-POPOVER', 'ION-ACTION-SHEET', 'CORE-USER-TOURS-USER-TOUR', 'ION-PAGE']; - /** * Behat Dom Utils helper functions. */ @@ -331,13 +328,14 @@ export class TestingBehatDomUtilsService { } // Get containers until one blocks other views. - containers.find(container => { + containers.some(container => { if (container.tagName === 'ION-TOAST') { container = container.shadowRoot?.querySelector('.toast-container') || container; } topContainers.push(container); - return blockingContainers.includes(container.tagName); + // If container has backdrop it blocks the rest of the UI. + return container.querySelector(':scope > ion-backdrop') || container.classList.contains('backdrop'); }); return topContainers; diff --git a/src/testing/services/behat-runtime.ts b/src/testing/services/behat-runtime.ts index d8448e035..6161c0fd3 100644 --- a/src/testing/services/behat-runtime.ts +++ b/src/testing/services/behat-runtime.ts @@ -25,9 +25,8 @@ import { CoreCronDelegate, CoreCronDelegateService } from '@services/cron'; import { CoreLoadingComponent } from '@components/loading/loading'; import { CoreComponentsRegistry } from '@singletons/components-registry'; import { CoreDom } from '@singletons/dom'; -import { IonRefresher } from '@ionic/angular'; -import { CoreCoursesDashboardPage } from '@features/courses/pages/dashboard/dashboard'; import { Injectable } from '@angular/core'; +import { CoreSites, CoreSitesProvider } from '@services/sites'; /** * Behat runtime servive with public API. @@ -53,6 +52,10 @@ export class TestingBehatRuntimeService { return CorePushNotifications.instance; } + get sites(): CoreSitesProvider { + return CoreSites.instance; + } + /** * Init behat functions and set options like skipping onboarding. * @@ -348,23 +351,17 @@ export class TestingBehatRuntimeService { this.log('Action - pullToRefresh'); try { - // TODO We should generalize this to work with other pages. It's not possible to use - // an IonRefresher instance because it doesn't expose any methods to trigger refresh, - // so we'll have to find another way. - - const dashboard = this.getAngularInstance( - 'page-core-courses-dashboard', - 'CoreCoursesDashboardPage', + // 'el' is protected, but there's no other way to trigger refresh programatically. + const ionRefresher = this.getAngularInstance<{ el: HTMLIonRefresherElement }>( + 'ion-refresher', + 'IonRefresher', ); - if (!dashboard) { - return 'ERROR: It\'s not possible to pull to refresh the current page ' - + '(the dashboard page is the only one supported at the moment).'; + if (!ionRefresher) { + return 'ERROR: It\'s not possible to pull to refresh the current page.'; } - await new Promise(resolve => { - dashboard.refreshDashboard({ complete: resolve } as IonRefresher); - }); + ionRefresher.el.dispatchEvent(new CustomEvent('ionRefresh')); return 'OK'; } catch (error) { diff --git a/src/tests/behat/navigation_deeplinks.feature b/src/tests/behat/navigation_deeplinks.feature index 4394a477f..9c963164e 100644 --- a/src/tests/behat/navigation_deeplinks.feature +++ b/src/tests/behat/navigation_deeplinks.feature @@ -25,9 +25,7 @@ Feature: It navigates properly using deep links. Scenario: Receive a push notification Given I entered the app as "student2" - 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 + When I log out in the app And I press "Add" in the app And I set the field "Your site" to "$WWWROOT" in the app And I press "Connect to your site" in the app @@ -67,9 +65,7 @@ Feature: It navigates properly using deep links. Scenario: Open a link with a custom URL that calls WebServices for a logged out site Given I entered the app as "student2" - 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 + When I log out in the app And I open a custom link in the app for: | forum | | Test forum |