From 06e7a0c97a094480bd96bee4f78272bb7264068e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 10 Jan 2024 10:00:54 +0100 Subject: [PATCH 01/15] MOBILE-3947 behat: Fix behat tests on ionic7 upgrade --- scripts/langindex.json | 1 + .../components/timeline/addon-block-timeline.html | 3 ++- src/addons/block/timeline/lang.json | 1 + src/addons/block/timeline/tests/behat/basic_usage.feature | 8 ++++---- src/addons/messages/tests/behat/basic_usage.feature | 1 - src/core/components/combobox/core-combobox.html | 2 +- src/theme/theme.base.scss | 2 +- 7 files changed, 10 insertions(+), 8 deletions(-) diff --git a/scripts/langindex.json b/scripts/langindex.json index 3c3c3e1f9..c9c894df6 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -72,6 +72,7 @@ "addon.block_starredcourses.nocourses": "block_starredcourses", "addon.block_starredcourses.pluginname": "block_starredcourses", "addon.block_tags.pluginname": "block_tags", + "addon.block_timeline.ariadayfilter": "block_timeline", "addon.block_timeline.duedate": "block_timeline", "addon.block_timeline.next30days": "block_timeline", "addon.block_timeline.next3months": "block_timeline", diff --git a/src/addons/block/timeline/components/timeline/addon-block-timeline.html b/src/addons/block/timeline/components/timeline/addon-block-timeline.html index a5bc3a312..4aca795d0 100644 --- a/src/addons/block/timeline/components/timeline/addon-block-timeline.html +++ b/src/addons/block/timeline/components/timeline/addon-block-timeline.html @@ -14,7 +14,8 @@ - + {{ option.name | translate }} diff --git a/src/addons/block/timeline/lang.json b/src/addons/block/timeline/lang.json index b4cb56414..efd22154f 100644 --- a/src/addons/block/timeline/lang.json +++ b/src/addons/block/timeline/lang.json @@ -1,4 +1,5 @@ { + "ariadayfilter": "Filter timeline by date", "duedate": "Due date", "next30days": "Next 30 days", "next3months": "Next 3 months", diff --git a/src/addons/block/timeline/tests/behat/basic_usage.feature b/src/addons/block/timeline/tests/behat/basic_usage.feature index 34b002473..e8591a04f 100644 --- a/src/addons/block/timeline/tests/behat/basic_usage.feature +++ b/src/addons/block/timeline/tests/behat/basic_usage.feature @@ -46,7 +46,7 @@ Feature: Timeline block. | assign | C3 | assign24 | Assignment 24 | ##+1 year## | | assign | C3 | assign25 | Assignment 25 | ##+1 year## | - @lms_from4.0 @ionic7_failure + @lms_from4.0 Scenario: See courses inside block Given I entered the app as "student1" Then I should find "Assignment 00" within "Timeline" "ion-card" in the app @@ -57,7 +57,7 @@ Feature: Timeline block. But I should not find "Assignment 01" within "Timeline" "ion-card" in the app And I should not find "Course 3" within "Timeline" "ion-card" in the app - When I press "Next 30 days" in the app + When I press "Filter timeline by date" in the app And I press "Overdue" in the app Then I should find "Assignment 01" within "Timeline" "ion-card" in the app And I should find "Course 2" within "Timeline" "ion-card" in the app @@ -66,7 +66,7 @@ Feature: Timeline block. And I should not find "Course 1" within "Timeline" "ion-card" in the app And I should not find "Course 3" within "Timeline" "ion-card" in the app - When I press "Overdue" in the app + When I press "Filter timeline by date" in the app And I press "All" in the app Then I should find "Assignment 19" within "Timeline" "ion-card" in the app And I should find "Course 3" within "Timeline" "ion-card" in the app @@ -76,7 +76,7 @@ Feature: Timeline block. Then I should find "Assignment 21" within "Timeline" "ion-card" in the app And I should find "Assignment 25" within "Timeline" "ion-card" in the app - When I press "All" in the app + When I press "Filter timeline by date" in the app And I press "Next 7 days" in the app And I press "Sort by" in the app And I press "Sort by courses" in the app diff --git a/src/addons/messages/tests/behat/basic_usage.feature b/src/addons/messages/tests/behat/basic_usage.feature index 828a310f5..37d0ec60a 100755 --- a/src/addons/messages/tests/behat/basic_usage.feature +++ b/src/addons/messages/tests/behat/basic_usage.feature @@ -19,7 +19,6 @@ Feature: Test basic usage of messages in app | student1 | C1 | student | | student2 | C1 | student | - @ionic7_failure Scenario: View recent conversations and contacts Given I entered the app as "teacher1" When I press "Messages" in the app diff --git a/src/core/components/combobox/core-combobox.html b/src/core/components/combobox/core-combobox.html index 1ffd3db43..8d0868bc5 100644 --- a/src/core/components/combobox/core-combobox.html +++ b/src/core/components/combobox/core-combobox.html @@ -5,7 +5,7 @@ + [interface]="interface" [attr.aria-label]="label" [disabled]="disabled" [hidden]="!!icon"> diff --git a/src/theme/theme.base.scss b/src/theme/theme.base.scss index f362be48a..37c3e2adf 100644 --- a/src/theme/theme.base.scss +++ b/src/theme/theme.base.scss @@ -1161,7 +1161,7 @@ ion-select-popover { ion-item.core-select-option-title { cursor: pointer; - ion-radio { + ion-radio::part(container) { display: none; } } From 036e9323505391a65ee6cf9b5beb4d0df03614df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 10 Jan 2024 10:08:59 +0100 Subject: [PATCH 02/15] MOBILE-3947 chore: Change master to main branch names on moodle repos --- .github/workflows/acceptance.yml | 6 +++--- .github/workflows/performance.yml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/acceptance.yml b/.github/workflows/acceptance.yml index 286cb4f1a..2cbc29c28 100644 --- a/.github/workflows/acceptance.yml +++ b/.github/workflows/acceptance.yml @@ -10,7 +10,7 @@ on: moodle_branch: description: 'Moodle branch' required: true - default: 'master' + default: 'main' moodle_repository: description: 'Moodle repository' required: true @@ -25,7 +25,7 @@ jobs: MOODLE_DOCKER_DB: pgsql MOODLE_DOCKER_BROWSER: chrome MOODLE_DOCKER_PHP_VERSION: '8.1' - MOODLE_BRANCH: ${{ github.event.inputs.moodle_branch || 'master' }} + MOODLE_BRANCH: ${{ github.event.inputs.moodle_branch || 'main' }} MOODLE_REPOSITORY: ${{ github.event.inputs.moodle_repository || 'https://github.com/moodle/moodle' }} BEHAT_TAGS: ${{ github.event.inputs.behat_tags || '~@performance&&~@ionic7_failure' }} @@ -37,7 +37,7 @@ jobs: - name: Additional checkouts run: | git clone --branch $MOODLE_BRANCH --depth 1 $MOODLE_REPOSITORY $GITHUB_WORKSPACE/moodle - git clone --branch master --depth 1 https://github.com/moodlehq/moodle-docker $GITHUB_WORKSPACE/moodle-docker + git clone --branch main --depth 1 https://github.com/moodlehq/moodle-docker $GITHUB_WORKSPACE/moodle-docker - name: Install npm packages run: npm ci --no-audit - name: Create Behat faildumps folder diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml index ce6475d27..025956e75 100644 --- a/.github/workflows/performance.yml +++ b/.github/workflows/performance.yml @@ -16,8 +16,8 @@ jobs: node-version-file: '.nvmrc' - name: Additional checkouts run: | - git clone --branch master --depth 1 https://github.com/moodle/moodle $GITHUB_WORKSPACE/moodle - git clone --branch master --depth 1 https://github.com/moodlehq/moodle-docker $GITHUB_WORKSPACE/moodle-docker + git clone --branch main --depth 1 https://github.com/moodle/moodle $GITHUB_WORKSPACE/moodle + git clone --branch main --depth 1 https://github.com/moodlehq/moodle-docker $GITHUB_WORKSPACE/moodle-docker - name: Install npm packages run: npm ci --no-audit - name: Generate Behat tests plugin From 210b3a75a3f0891b869b04d8141d778cb73b1399 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 10 Jan 2024 12:07:19 +0100 Subject: [PATCH 03/15] MOBILE-3947 behat: Fix Datetime value --- .../calendar/tests/behat/create_events.feature | 7 ++++--- src/testing/services/behat-dom.ts | 2 +- src/testing/services/behat-runtime.ts | 13 ++++++++----- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/addons/calendar/tests/behat/create_events.feature b/src/addons/calendar/tests/behat/create_events.feature index afde42680..6f995d079 100755 --- a/src/addons/calendar/tests/behat/create_events.feature +++ b/src/addons/calendar/tests/behat/create_events.feature @@ -20,13 +20,14 @@ Feature: Test creation of calendar events in app | teacher1 | C1 | editingteacher | | student1 | C1 | student | - @ionic7_failure + # This test is flaky due to timestamp. Scenario: Create user event as student from monthly view Given I entered the app as "student1" When I press "More" in the app And I press "Calendar" in the app And I press "New event" in the app - Then the field "Date" matches value "## now ##%d/%m/%y, %H:%M##" in the app + # Flaky step, sometimes it fails due to minute change when checking. + Then the field "Date" matches value "## now ##%Y-%m-%dT%H:%M##" in the app And I should not be able to press "Save" in the app # Check that student can only create User events. @@ -36,7 +37,7 @@ Feature: Test creation of calendar events in app # Create the event. When I set the field "Event title" to "User Event 01" in the app - And I set the field "Date" to "2025-04-11T09:00+08:00" in the app + And I set the field "Date" to "2025-04-11T09:00" in the app And I press "Without duration" in the app And I set the field "Description" to "This is User Event 01 description." in the app And I set the field "Location" to "Barcelona" in the app diff --git a/src/testing/services/behat-dom.ts b/src/testing/services/behat-dom.ts index fe32cb01c..c199918ab 100644 --- a/src/testing/services/behat-dom.ts +++ b/src/testing/services/behat-dom.ts @@ -401,7 +401,7 @@ export class TestingBehatDomUtilsService { */ findField(field: string): HTMLElement | HTMLInputElement | undefined { const input = this.findElementBasedOnText( - { text: field, selector: 'input, textarea, [contenteditable="true"], ion-select, ion-datetime' }, + { text: field, selector: 'input, textarea, [contenteditable="true"], ion-select, ion-datetime-button, ion-datetime' }, { onlyClickable: false, containerName: '' }, ); diff --git a/src/testing/services/behat-runtime.ts b/src/testing/services/behat-runtime.ts index 0b938c175..fc32e4d50 100644 --- a/src/testing/services/behat-runtime.ts +++ b/src/testing/services/behat-runtime.ts @@ -508,12 +508,15 @@ export class TestingBehatRuntimeService { * @returns Value. */ protected getFieldValue(element: HTMLElement | HTMLInputElement): string { + if (element.tagName === 'ION-DATETIME-BUTTON' && element.shadowRoot) { + return Array.from(element.shadowRoot.querySelectorAll('button')).map(button => button.innerText).join(' '); + } + if (element.tagName === 'ION-DATETIME') { - // ion-datetime's value is a timestamp in ISO format. Use the text displayed to the user instead. - const dateTimeTextElement = element.shadowRoot?.querySelector('.datetime-text'); - if (dateTimeTextElement) { - return dateTimeTextElement.innerText; - } + const value = 'value' in element ? element.value : element.innerText; + + // Remove seconds from the value to ensure stability on tests. It could be improved using moment parsing if needed. + return value.substring(0, value.length - 3); } return 'value' in element ? element.value : element.innerText; From 522d1e2c79b65b7d8159027fc1169109d9f31bb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 9 Jan 2024 08:54:59 +0100 Subject: [PATCH 04/15] MOBILE-3947 swipe: Fix Swipe slides update component --- .../calendar/components/calendar/calendar.ts | 6 +- .../components/swipe-slides/swipe-slides.ts | 105 ++++++++++++------ 2 files changed, 73 insertions(+), 38 deletions(-) diff --git a/src/addons/calendar/components/calendar/calendar.ts b/src/addons/calendar/components/calendar/calendar.ts index 3561366a8..6fac088f5 100644 --- a/src/addons/calendar/components/calendar/calendar.ts +++ b/src/addons/calendar/components/calendar/calendar.ts @@ -142,7 +142,7 @@ export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestro } /** - * Component loaded. + * @inheritdoc */ ngOnInit(): void { this.canNavigate = typeof this.canNavigate == 'undefined' ? true : CoreUtils.isTrueOrOne(this.canNavigate); @@ -164,7 +164,7 @@ export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestro } /** - * Detect and act upon changes that Angular can’t or won’t detect on its own (objects and arrays). + * @inheritdoc */ ngDoCheck(): void { const items = this.manager?.getSource().getItems(); @@ -368,7 +368,7 @@ export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestro } /** - * Component destroyed. + * @inheritdoc */ ngOnDestroy(): void { this.undeleteEventObserver?.off(); diff --git a/src/core/components/swipe-slides/swipe-slides.ts b/src/core/components/swipe-slides/swipe-slides.ts index f063ffd72..82c856af3 100644 --- a/src/core/components/swipe-slides/swipe-slides.ts +++ b/src/core/components/swipe-slides/swipe-slides.ts @@ -15,7 +15,9 @@ import { Component, ContentChild, ElementRef, EventEmitter, Input, OnChanges, OnDestroy, Output, SimpleChange, TemplateRef, ViewChild, } from '@angular/core'; +import { AsyncDirective } from '@classes/async-directive'; import { CoreSwipeSlidesItemsManager } from '@classes/items-management/swipe-slides-items-manager'; +import { CorePromisedValue } from '@classes/promised-value'; import { IonContent } from '@ionic/angular'; import { CoreDomUtils, VerticalPoint } from '@services/utils/dom'; import { CoreUtils } from '@services/utils/utils'; @@ -32,7 +34,7 @@ import { SwiperOptions } from 'swiper/types'; templateUrl: 'swipe-slides.html', styleUrls: ['swipe-slides.scss'], }) -export class CoreSwipeSlidesComponent implements OnChanges, OnDestroy { +export class CoreSwipeSlidesComponent implements OnChanges, OnDestroy, AsyncDirective { @Input() manager?: CoreSwipeSlidesItemsManager; @Input() options: CoreSwipeSlidesOptions = {}; @@ -46,22 +48,21 @@ export class CoreSwipeSlidesComponent implements OnChanges, OnDe * This setTimeout waits for Ionic's async initialization to complete. * Otherwise, an outdated swiper reference will be used. */ - setTimeout(() => { + setTimeout(async () => { if (swiperRef?.nativeElement?.swiper) { this.swiper = swiperRef.nativeElement.swiper as Swiper; + await this.initialize(); + if (this.options.initialSlide) { this.swiper.slideTo(this.options.initialSlide, 0, this.options.runCallbacksOnInit); } + this.updateOptions(); + this.swiper.on('slideChangeTransitionStart', () => this.slideWillChange()); this.swiper.on('slideChangeTransitionEnd', () => this.slideDidChange()); - Object.keys(this.options).forEach((key) => { - if (this.swiper) { - this.swiper.params[key] = this.options[key]; - } - }); } }, 0); } @@ -72,6 +73,7 @@ export class CoreSwipeSlidesComponent implements OnChanges, OnDe protected unsubscribe?: () => void; protected resizeListener: CoreEventObserver; protected activeSlideIndexes: number[] = []; + protected onReadyPromise = new CorePromisedValue(); constructor( elementRef: ElementRef, @@ -87,17 +89,11 @@ export class CoreSwipeSlidesComponent implements OnChanges, OnDe /** * @inheritdoc */ - ngOnChanges(changes: { [name: string]: SimpleChange }): void { - if (!this.unsubscribe && this.manager) { - this.initialize(this.manager); - } + async ngOnChanges(changes: { [name: string]: SimpleChange }): Promise { + await this.initialize(); if (changes.options) { - Object.keys(this.options).forEach((key) => { - if (this.swiper) { - this.swiper.params[key] = this.options[key]; - } - }); + this.updateOptions(); } } @@ -122,8 +118,12 @@ export class CoreSwipeSlidesComponent implements OnChanges, OnDe /** * Initialize some properties based on the manager. */ - protected async initialize(manager: CoreSwipeSlidesItemsManager): Promise { - this.unsubscribe = manager.getSource().addListener({ + protected async initialize(): Promise { + if (this.unsubscribe || !this.swiper || !this.manager) { + return; + } + + this.unsubscribe = this.manager.getSource().addListener({ onItemsUpdated: () => this.onItemsUpdated(), }); @@ -131,16 +131,16 @@ export class CoreSwipeSlidesComponent implements OnChanges, OnDe // This is because default callbacks aren't triggered for index 0, and to prevent auto scroll on init. this.options.runCallbacksOnInit = false; - await manager.getSource().waitForLoaded(); + await this.manager.getSource().waitForLoaded(); if (this.options.initialSlide === undefined) { // Calculate the initial slide. - const index = manager.getSource().getInitialItemIndex(); + const index = this.manager.getSource().getInitialItemIndex(); this.options.initialSlide = Math.max(index, 0); } // Emit change events with the initial item. - const items = manager.getSource().getItems(); + const items = this.manager.getSource().getItems(); if (!items || !items.length) { return; } @@ -155,9 +155,11 @@ export class CoreSwipeSlidesComponent implements OnChanges, OnDe this.activeSlideIndexes = [initialIndex]; - manager.setSelectedItem(items[initialIndex]); + this.manager.setSelectedItem(items[initialIndex]); this.onWillChange.emit(initialItemData); this.onDidChange.emit(initialItemData); + + this.onReadyPromise.resolve(); } /** @@ -167,19 +169,20 @@ export class CoreSwipeSlidesComponent implements OnChanges, OnDe * @param speed Animation speed. * @param runCallbacks Whether to run callbacks. */ - slideToIndex(index: number, speed?: number, runCallbacks?: boolean): void { + async slideToIndex(index: number, speed?: number, runCallbacks?: boolean): Promise { // If slides are being updated, wait for the update to finish. - if (!this.swiper) { - return; - } + await this.ready(); // Verify that the number of slides matches the number of items. - const slidesLength = this.swiper.slides.length; + const slidesLength = this.swiper?.slides?.length || 0; if (slidesLength !== this.items.length) { // Number doesn't match, do a new update to try to match them. - this.updateSlidesComponent(); + await this.updateSlidesComponent(); } + if (!this.swiper?.slides) { + return; + } this.swiper?.slideTo(index, speed, runCallbacks); } @@ -190,10 +193,10 @@ export class CoreSwipeSlidesComponent implements OnChanges, OnDe * @param speed Animation speed. * @param runCallbacks Whether to run callbacks. */ - slideToItem(item: Item, speed?: number, runCallbacks?: boolean): void { + async slideToItem(item: Item, speed?: number, runCallbacks?: boolean): Promise { const index = this.manager?.getSource().getItemIndex(item) ?? -1; if (index != -1) { - this.slideToIndex(index, speed, runCallbacks); + await this.slideToIndex(index, speed, runCallbacks); } } @@ -225,7 +228,7 @@ export class CoreSwipeSlidesComponent implements OnChanges, OnDe await CoreUtils.nextTick(); // Update the slides component so the slides list reflects the new items. - this.updateSlidesComponent(); + await this.updateSlidesComponent(); const currentItem = this.manager?.getSelectedItem(); @@ -234,10 +237,26 @@ export class CoreSwipeSlidesComponent implements OnChanges, OnDe } // Keep the same slide in case the list has changed. - const newIndex = this.manager.getSource().getItemIndex(currentItem) ?? -1; - if (newIndex != -1) { - this.swiper?.slideTo(newIndex, 0, false); + this.slideToItem(currentItem, 0, false); + } + + /** + * Update Swiper params from options. + */ + protected updateOptions(): void { + if (!this.swiper) { + return; } + + if (this.swiper.params === undefined) { + this.swiper.params = {}; + } + + Object.keys(this.options).forEach((key) => { + if (this.swiper) { + this.swiper.params[key] = this.options[key]; + } + }); } /** @@ -322,8 +341,24 @@ export class CoreSwipeSlidesComponent implements OnChanges, OnDe /** * Update slides component. */ - updateSlidesComponent(): void { + async updateSlidesComponent(): Promise { + await this.ready(); + + if (!this.swiper) { + return; + } + this.swiper?.update(); + + // We need to ensure the slides are updated before continuing. + await CoreUtils.nextTicks(2); + } + + /** + * @inheritdoc + */ + async ready(): Promise { + return this.onReadyPromise; } /** From eb704d42039126a049ee45a1a31166c391add548 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 22 Dec 2023 15:29:26 +0100 Subject: [PATCH 05/15] MOBILE-3947 calendar: Simplify observers management --- src/addons/calendar/pages/day/day.ts | 54 +++++++++++----------------- 1 file changed, 20 insertions(+), 34 deletions(-) diff --git a/src/addons/calendar/pages/day/day.ts b/src/addons/calendar/pages/day/day.ts index 6097d8af7..2a57565f9 100644 --- a/src/addons/calendar/pages/day/day.ts +++ b/src/addons/calendar/pages/day/day.ts @@ -65,15 +65,8 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { protected currentSiteId: string; // Observers. - protected newEventObserver: CoreEventObserver; - protected discardedObserver: CoreEventObserver; - protected editEventObserver: CoreEventObserver; - protected deleteEventObserver: CoreEventObserver; - protected undeleteEventObserver: CoreEventObserver; - protected syncObserver: CoreEventObserver; - protected manualSyncObserver: CoreEventObserver; + protected eventObservers: CoreEventObserver[] = []; protected onlineObserver: Subscription; - protected filterChangedObserver: CoreEventObserver; protected managerUnsubscribe?: () => void; protected logView: () => void; @@ -97,7 +90,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { this.currentSiteId = CoreSites.getCurrentSiteId(); // Listen for events added. When an event is added, reload the data. - this.newEventObserver = CoreEvents.on( + this.eventObservers.push(CoreEvents.on( AddonCalendarProvider.NEW_EVENT_EVENT, (data) => { if (data && data.eventId) { @@ -106,16 +99,16 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { } }, this.currentSiteId, - ); + )); // Listen for new event discarded event. When it does, reload the data. - this.discardedObserver = CoreEvents.on(AddonCalendarProvider.NEW_EVENT_DISCARDED_EVENT, () => { + this.eventObservers.push(CoreEvents.on(AddonCalendarProvider.NEW_EVENT_DISCARDED_EVENT, () => { this.manager?.getSource().markAllItemsUnloaded(); this.refreshData(true, true); - }, this.currentSiteId); + }, this.currentSiteId)); // Listen for events edited. When an event is edited, reload the data. - this.editEventObserver = CoreEvents.on( + this.eventObservers.push(CoreEvents.on( AddonCalendarProvider.EDIT_EVENT_EVENT, (data) => { if (data && data.eventId) { @@ -124,25 +117,25 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { } }, this.currentSiteId, - ); + )); // Refresh data if calendar events are synchronized automatically. - this.syncObserver = CoreEvents.on(AddonCalendarSyncProvider.AUTO_SYNCED, () => { + this.eventObservers.push(CoreEvents.on(AddonCalendarSyncProvider.AUTO_SYNCED, () => { this.manager?.getSource().markAllItemsUnloaded(); this.refreshData(false, true); - }, this.currentSiteId); + }, this.currentSiteId)); // Refresh data if calendar events are synchronized manually but not by this page. - this.manualSyncObserver = CoreEvents.on(AddonCalendarSyncProvider.MANUAL_SYNCED, (data) => { + this.eventObservers.push(CoreEvents.on(AddonCalendarSyncProvider.MANUAL_SYNCED, (data) => { const selectedDay = this.manager?.getSelectedItem(); if (data && (data.source != 'day' || !selectedDay || !data.moment || !selectedDay.moment.isSame(data.moment, 'day'))) { this.manager?.getSource().markAllItemsUnloaded(); this.refreshData(false, true); } - }, this.currentSiteId); + }, this.currentSiteId)); // Update the events when an event is deleted. - this.deleteEventObserver = CoreEvents.on( + this.eventObservers.push(CoreEvents.on( AddonCalendarProvider.DELETED_EVENT_EVENT, (data) => { if (data && !data.sent) { @@ -154,10 +147,10 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { } }, this.currentSiteId, - ); + )); // Listen for events "undeleted" (offline). - this.undeleteEventObserver = CoreEvents.on( + this.eventObservers.push(CoreEvents.on( AddonCalendarProvider.UNDELETED_EVENT_EVENT, (data) => { if (!data || !data.eventId) { @@ -168,9 +161,9 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { this.manager?.getSource().markAsDeleted(data.eventId, false); }, this.currentSiteId, - ); + )); - this.filterChangedObserver = CoreEvents.on( + this.eventObservers.push(CoreEvents.on( AddonCalendarProvider.FILTER_CHANGED_EVENT, async (data) => { this.filter = data; @@ -180,7 +173,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { this.manager?.getSource().filterAllDayEvents(this.filter); }, - ); + )); // Refresh online status when changes. this.onlineObserver = CoreNetwork.onChange().subscribe(() => { @@ -214,7 +207,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { } /** - * View loaded. + * @inheritdoc */ ngOnInit(): void { const types: string[] = []; @@ -470,18 +463,11 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { } /** - * Page destroyed. + * @inheritdoc */ ngOnDestroy(): void { - this.newEventObserver?.off(); - this.discardedObserver?.off(); - this.editEventObserver?.off(); - this.deleteEventObserver?.off(); - this.undeleteEventObserver?.off(); - this.syncObserver?.off(); - this.manualSyncObserver?.off(); + this.eventObservers.forEach((observer) => observer.off()); this.onlineObserver?.unsubscribe(); - this.filterChangedObserver?.off(); this.manager?.getSource().forgetRelatedSources(); this.manager?.destroy(); this.managerUnsubscribe?.(); From b523757e81b17baf11a57666a3f35fa3abfd753c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 9 Jan 2024 11:18:23 +0100 Subject: [PATCH 06/15] MOBILE-3947 behat: Dispatch ionChange event after setting a field --- .../mod/survey/tests/behat/basic_usage.feature | 4 ---- src/testing/services/behat-dom.ts | 14 ++++++++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/addons/mod/survey/tests/behat/basic_usage.feature b/src/addons/mod/survey/tests/behat/basic_usage.feature index 1355b6f7a..f12395a58 100755 --- a/src/addons/mod/survey/tests/behat/basic_usage.feature +++ b/src/addons/mod/survey/tests/behat/basic_usage.feature @@ -20,7 +20,6 @@ Feature: Test basic usage of survey activity in app | activity | name | intro | course | idnumber | groupmode | | survey | Test survey name | Test survey | C1 | survey | 0 | - @ionic7_failure Scenario: Answer a survey & View results (ATTLS) Given I entered the survey activity "Test survey name" on course "Course 1" as "student1" in the app And I set the following fields to these values in the app: @@ -79,7 +78,6 @@ Feature: Test basic usage of survey activity in app And I should see "4th answer" And I should see "5th answer" - @ionic7_failure Scenario: Answer a survey & View results (Colles actual) Given the following "activities" exist: | activity | name | intro | template |course | idnumber | groupmode | @@ -122,7 +120,6 @@ Feature: Test basic usage of survey activity in app Then I should see "You've completed this survey. The graph below shows a summary of your results compared to the class averages." And I should see "1 people have completed this survey so far" - @ionic7_failure Scenario: Answer a survey & View results (Colles preferred) Given the following "activities" exist: | activity | name | intro | template | course | idnumber | groupmode | @@ -165,7 +162,6 @@ Feature: Test basic usage of survey activity in app Then I should see "You've completed this survey. The graph below shows a summary of your results compared to the class averages." And I should see "1 people have completed this survey so far" - @ionic7_failure Scenario: Answer a survey & View results (Colles preferred and actual) Given the following "activities" exist: | activity | name | intro | template | course | idnumber | groupmode | diff --git a/src/testing/services/behat-dom.ts b/src/testing/services/behat-dom.ts index c199918ab..dc2806780 100644 --- a/src/testing/services/behat-dom.ts +++ b/src/testing/services/behat-dom.ts @@ -631,7 +631,13 @@ export class TestingBehatDomUtilsService { // Functions to get/set value depending on field type. const setValue = (text: string) => { - if (element.tagName === 'ION-SELECT' && 'value' in element) { + if (! ('value' in element)) { + element.innerHTML = text; + + return; + } + + if (element.tagName === 'ION-SELECT') { value = value.trim(); const optionValue = Array.from(element.querySelectorAll('ion-select-option')) .find((option) => option.innerHTML.trim() === value); @@ -639,11 +645,11 @@ export class TestingBehatDomUtilsService { if (optionValue) { element.value = optionValue.value; } - } else if ('value' in element) { - element.value = text; } else { - element.innerHTML = text; + element.value = text; } + + element.dispatchEvent(new Event('ionChange')); }; const getValue = () => { if ('value' in element) { From 6cca2a953b4a8e0a38c68dd120dafee652d1eb3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 9 Jan 2024 13:18:07 +0100 Subject: [PATCH 07/15] MOBILE-3947 behat: Check shadowDom on getTopAncestors --- .../competency/tests/behat/navigation.feature | 1 - .../messages/tests/behat/basic_usage.feature | 1 - src/testing/services/behat-dom.ts | 32 ++++++++++++++++--- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/addons/competency/tests/behat/navigation.feature b/src/addons/competency/tests/behat/navigation.feature index c891c52e8..b3996466c 100644 --- a/src/addons/competency/tests/behat/navigation.feature +++ b/src/addons/competency/tests/behat/navigation.feature @@ -297,7 +297,6 @@ Feature: Test competency navigation Then I should find "Desserts are important" in the app But I should not find "Cakes" in the app - @ionic7_failure Scenario: Tablet navigation (student) Given I entered the course "Course 1" as "student1" in the app And I change viewport size to "1200x640" in the app diff --git a/src/addons/messages/tests/behat/basic_usage.feature b/src/addons/messages/tests/behat/basic_usage.feature index 37d0ec60a..b09b72646 100755 --- a/src/addons/messages/tests/behat/basic_usage.feature +++ b/src/addons/messages/tests/behat/basic_usage.feature @@ -216,7 +216,6 @@ Feature: Test basic usage of messages in app Then I should find "heeey student" in the app And I should find "byee" in the app - @ionic7_failure Scenario: Search for messages Given I entered the app as "teacher1" When I press "Messages" in the app diff --git a/src/testing/services/behat-dom.ts b/src/testing/services/behat-dom.ts index dc2806780..7d988d3ab 100644 --- a/src/testing/services/behat-dom.ts +++ b/src/testing/services/behat-dom.ts @@ -286,7 +286,18 @@ export class TestingBehatDomUtilsService { continue; } - if (element.contains(otherElement)) { + let documentPosition = element.compareDocumentPosition(otherElement); + // eslint-disable-next-line no-bitwise + if (documentPosition & Node.DOCUMENT_POSITION_DISCONNECTED) { + // Check if they are inside shadow DOM so we can compare their hosts. + const elementHost = this.getShadowDOMHost(element) || element; + const otherElementHost = this.getShadowDOMHost(otherElement) || otherElement; + + documentPosition = elementHost.compareDocumentPosition(otherElementHost); + } + + // eslint-disable-next-line no-bitwise + if (documentPosition & Node.DOCUMENT_POSITION_CONTAINS) { uniqueElements.delete(otherElement); } } @@ -302,9 +313,22 @@ export class TestingBehatDomUtilsService { * @returns Parent element. */ protected getParentElement(element: HTMLElement): HTMLElement | null { - return element.parentElement || - (element.getRootNode() && (element.getRootNode() as ShadowRoot).host as HTMLElement) || - null; + return element.parentElement || this.getShadowDOMHost(element); + } + + /** + * Get shadow DOM host element. + * + * @param element Element. + * @returns Shadow DOM host element. + */ + protected getShadowDOMHost(element: HTMLElement): HTMLElement | null { + const node = element.getRootNode(); + if (node instanceof ShadowRoot) { + return node.host as HTMLElement; + } + + return null; } /** From 1f667da2463e7f2cc7adc90cc08f741f7a538ff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 9 Jan 2024 13:38:48 +0100 Subject: [PATCH 08/15] MOBILE-3947 competency: Narrow selector on press Competencies --- src/addons/competency/tests/behat/navigation.feature | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/addons/competency/tests/behat/navigation.feature b/src/addons/competency/tests/behat/navigation.feature index b3996466c..484278102 100644 --- a/src/addons/competency/tests/behat/navigation.feature +++ b/src/addons/competency/tests/behat/navigation.feature @@ -382,7 +382,6 @@ Feature: Test competency navigation Then I should find "Desserts are important" in the app But I should not find "Cakes" in the app - @ionic7_failure Scenario: Tablet navigation (teacher) Given I entered the course "Course 1" as "teacher1" in the app And I change viewport size to "1200x640" in the app @@ -390,7 +389,7 @@ Feature: Test competency navigation # Participant competencies When I press "Participants" in the app And I press "Student first" in the app - And I press "Competencies" in the app + And I press "Competencies" within "Student first" "page-core-user-participants" in the app Then I should find "Student first" in the app And I should find "Salads are important" in the app And I should find "Good" within "salads" "ion-item" in the app From 90a47b24416f75929ff83e30d0d1e43ff03c16c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 9 Jan 2024 14:38:17 +0100 Subject: [PATCH 09/15] MOBILE-3947 behat: Fix ion-toggle press --- .../messages/tests/behat/basic_usage.feature | 1 - .../core-context-menu-popover.html | 21 ++++++++++++------- src/testing/services/behat-dom.ts | 4 +++- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/addons/messages/tests/behat/basic_usage.feature b/src/addons/messages/tests/behat/basic_usage.feature index b09b72646..eb3621b0f 100755 --- a/src/addons/messages/tests/behat/basic_usage.feature +++ b/src/addons/messages/tests/behat/basic_usage.feature @@ -346,7 +346,6 @@ Feature: Test basic usage of messages in app Then I should find "test message" in the app And I should find "Muted conversation" in the app - @ionic7_failure Scenario: Self conversations Given I entered the app as "student1" When I press "Messages" in the app diff --git a/src/core/components/context-menu/core-context-menu-popover.html b/src/core/components/context-menu/core-context-menu-popover.html index 1e2accd45..9e844194a 100644 --- a/src/core/components/context-menu/core-context-menu-popover.html +++ b/src/core/components/context-menu/core-context-menu-popover.html @@ -7,17 +7,22 @@ [href]="item.href" (click)="itemClicked($event, item)" [attr.aria-label]="item.ariaAction" [hidden]="item.hidden" [detail]="!!(item.href && !item.iconAction)" role="menuitem" [button]="!!(item.href && !item.iconAction)" [showBrowserWarning]="item.showBrowserWarning"> - +

-
- -