From 1a1e03bdd4b9496de1e5182c86e3ca89f8f7be5a Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 11 Nov 2022 08:40:09 +0100 Subject: [PATCH 1/5] MOBILE-3784 time: Fix time zone usage in ion-datetime --- .../calendar/pages/edit-event/edit-event.html | 4 ++-- .../calendar/pages/edit-event/edit-event.page.ts | 5 +++-- .../date/component/addon-mod-data-field-date.html | 2 +- .../datetime/services/handlers/datetime.ts | 4 ++-- src/core/services/utils/time.ts | 15 +++------------ 5 files changed, 11 insertions(+), 19 deletions(-) diff --git a/src/addons/calendar/pages/edit-event/edit-event.html b/src/addons/calendar/pages/edit-event/edit-event.html index 912194c76..c5b628d9d 100644 --- a/src/addons/calendar/pages/edit-event/edit-event.html +++ b/src/addons/calendar/pages/edit-event/edit-event.html @@ -31,7 +31,7 @@

{{ 'core.date' | translate }}

+ [max]="maxDate" [min]="minDate"> @@ -156,7 +156,7 @@ + [placeholder]="'addon.calendar.durationuntil' | translate" [displayFormat]="dateFormat"> diff --git a/src/addons/calendar/pages/edit-event/edit-event.page.ts b/src/addons/calendar/pages/edit-event/edit-event.page.ts index 79190818a..f5a21a733 100644 --- a/src/addons/calendar/pages/edit-event/edit-event.page.ts +++ b/src/addons/calendar/pages/edit-event/edit-event.page.ts @@ -45,6 +45,7 @@ import { CanLeave } from '@guards/can-leave'; import { CoreForms } from '@singletons/form'; import { CoreReminders, CoreRemindersService, CoreRemindersUnits } from '@features/reminders/services/reminders'; import { CoreRemindersSetReminderMenuComponent } from '@features/reminders/components/set-reminder-menu/set-reminder-menu'; +import moment from 'moment-timezone'; /** * Page that displays a form to create/edit an event. @@ -454,8 +455,8 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave { async submit(): Promise { // Validate data. const formData = this.form.value; - const timeStartDate = CoreTimeUtils.convertToTimestamp(formData.timestart, true); - const timeUntilDate = CoreTimeUtils.convertToTimestamp(formData.timedurationuntil, true); + const timeStartDate = moment(formData.timestart).unix(); + const timeUntilDate = moment(formData.timedurationuntil).unix(); const timeDurationMinutes = parseInt(formData.timedurationminutes || '', 10); let error: string | undefined; diff --git a/src/addons/mod/data/fields/date/component/addon-mod-data-field-date.html b/src/addons/mod/data/fields/date/component/addon-mod-data-field-date.html index d0acc0ca8..f6bcd38db 100644 --- a/src/addons/mod/data/fields/date/component/addon-mod-data-field-date.html +++ b/src/addons/mod/data/fields/date/component/addon-mod-data-field-date.html @@ -1,7 +1,7 @@ + [disabled]="searchMode && !searchFields!['f_'+field.id+'_z']" [displayFormat]="format"> diff --git a/src/addons/userprofilefield/datetime/services/handlers/datetime.ts b/src/addons/userprofilefield/datetime/services/handlers/datetime.ts index 413ad1ccd..96f3deaba 100644 --- a/src/addons/userprofilefield/datetime/services/handlers/datetime.ts +++ b/src/addons/userprofilefield/datetime/services/handlers/datetime.ts @@ -18,9 +18,9 @@ import { AuthEmailSignupProfileField } from '@features/login/services/login-help import { CoreUserProfileField } from '@features/user/services/user'; import { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from '@features/user/services/user-profile-field-delegate'; import { CoreFormFields } from '@singletons/form'; -import { CoreTimeUtils } from '@services/utils/time'; import { makeSingleton } from '@singletons'; import { AddonUserProfileFieldDatetimeComponent } from '../../component/datetime'; +import moment from 'moment-timezone'; /** * Datetime user profile field handlers. @@ -61,7 +61,7 @@ export class AddonUserProfileFieldDatetimeHandlerService implements CoreUserProf return { type: 'datetime', name: 'profile_field_' + field.shortname, - value: CoreTimeUtils.convertToTimestamp( formValues[name]), + value: moment( formValues[name]).unix(), }; } } diff --git a/src/core/services/utils/time.ts b/src/core/services/utils/time.ts index fd44ad144..b22b5b44b 100644 --- a/src/core/services/utils/time.ts +++ b/src/core/services/utils/time.ts @@ -261,23 +261,14 @@ export class CoreTimeUtilsProvider { /** * Convert a text into user timezone timestamp. * - * @todo The `applyOffset` argument is only used as a workaround, it should be removed once - * MOBILE-3784 is resolved. - * * @param date To convert to timestamp. * @param applyOffset Whether to apply offset to date or not. * @return Converted timestamp. + * @deprecated since 4.1. Use moment(date).unix() instead. */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars convertToTimestamp(date: string, applyOffset?: boolean): number { - const timestamp = moment(date).unix(); - - if (applyOffset !== undefined) { - return applyOffset ? timestamp - moment().utcOffset() * 60 : timestamp; - } - - return typeof date == 'string' && date.slice(-1) == 'Z' - ? timestamp - moment().utcOffset() * 60 - : timestamp; + return moment(date).unix(); } /** From ee4e8f6b054d383a3e2e3a11acf2ce3e6c13b619 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 15 Nov 2022 16:07:57 +0100 Subject: [PATCH 2/5] MOBILE-3784 calendar: Support opening any month with page params --- .../calendar/components/calendar/calendar.ts | 55 ++++++++++++------- src/addons/calendar/pages/index/index.page.ts | 4 ++ .../components/swipe-slides/swipe-slides.ts | 51 +++++++++++++++-- 3 files changed, 84 insertions(+), 26 deletions(-) diff --git a/src/addons/calendar/components/calendar/calendar.ts b/src/addons/calendar/components/calendar/calendar.ts index 0c8ef0178..81b65c10e 100644 --- a/src/addons/calendar/components/calendar/calendar.ts +++ b/src/addons/calendar/components/calendar/calendar.ts @@ -257,27 +257,9 @@ export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestro * Go to current month. */ async goToCurrentMonth(): Promise { - const manager = this.manager; - const slides = this.slides; - if (!manager || !slides) { - return; - } + const currentMoment = moment(); - const currentMonth = { - moment: moment(), - }; - this.loaded = false; - - try { - // Make sure the day is loaded. - await manager.getSource().loadItem(currentMonth); - - slides.slideToItem(currentMonth); - } catch (error) { - CoreDomUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true); - } finally { - this.loaded = true; - } + await this.viewMonth(currentMoment.month() + 1, currentMoment.year()); } /** @@ -319,6 +301,39 @@ export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestro }); } + /** + * View a certain month and year. + * + * @param month Month. + * @param year Year. + */ + async viewMonth(month: number, year: number): Promise { + const manager = this.manager; + const slides = this.slides; + if (!manager || !slides) { + return; + } + + this.loaded = false; + const item = { + moment: moment({ + year, + month: month - 1, + }), + }; + + try { + // Make sure the day is loaded. + await manager.getSource().loadItem(item); + + slides.slideToItem(item); + } catch (error) { + CoreDomUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true); + } finally { + this.loaded = true; + } + } + /** * Component destroyed. */ diff --git a/src/addons/calendar/pages/index/index.page.ts b/src/addons/calendar/pages/index/index.page.ts index fa59a8f87..cba2727e8 100644 --- a/src/addons/calendar/pages/index/index.page.ts +++ b/src/addons/calendar/pages/index/index.page.ts @@ -173,6 +173,10 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { this.filter.filtered = !!this.filter.courseId; this.fetchData(true, false); + + if (this.year !== undefined && this.month !== undefined && this.calendarComponent) { + this.calendarComponent.viewMonth(this.month, this.year); + } }); const deepLinkManager = new CoreMainMenuDeepLinkManager(); diff --git a/src/core/components/swipe-slides/swipe-slides.ts b/src/core/components/swipe-slides/swipe-slides.ts index 27e6587b3..bb25cfe22 100644 --- a/src/core/components/swipe-slides/swipe-slides.ts +++ b/src/core/components/swipe-slides/swipe-slides.ts @@ -18,6 +18,7 @@ import { import { CoreSwipeSlidesItemsManager } from '@classes/items-management/swipe-slides-items-manager'; import { IonContent, IonSlides } from '@ionic/angular'; import { CoreDomUtils, VerticalPoint } from '@services/utils/dom'; +import { CoreUtils } from '@services/utils/utils'; import { CoreDom } from '@singletons/dom'; import { CoreEventObserver } from '@singletons/events'; import { CoreMath } from '@singletons/math'; @@ -43,6 +44,7 @@ export class CoreSwipeSlidesComponent implements OnChanges, OnDe protected hostElement: HTMLElement; protected unsubscribe?: () => void; protected resizeListener: CoreEventObserver; + protected updateSlidesPromise?: Promise; constructor( elementRef: ElementRef, @@ -51,7 +53,7 @@ export class CoreSwipeSlidesComponent implements OnChanges, OnDe this.hostElement = elementRef.nativeElement; this.resizeListener = CoreDom.onWindowResize(() => { - this.slides?.update(); + this.updateSlidesComponent(); }); } @@ -118,7 +120,22 @@ 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. + await this.updateSlidesPromise; + + const slides = this.slides; + if (!slides) { + return; + } + + // Verify that the number of slides matches the number of items. + const slidesLength = await slides.length(); + if (slidesLength !== this.items.length) { + // Number doesn't match, do a new update to try to match them. + await this.updateSlidesComponent(); + } + this.slides?.slideTo(index, speed, runCallbacks); } @@ -132,7 +149,7 @@ export class CoreSwipeSlidesComponent implements OnChanges, OnDe slideToItem(item: Item, speed?: number, runCallbacks?: boolean): void { const index = this.manager?.getSource().getItemIndex(item) ?? -1; if (index != -1) { - this.slides?.slideTo(index, speed, runCallbacks); + this.slideToIndex(index, speed, runCallbacks); } } @@ -158,10 +175,14 @@ export class CoreSwipeSlidesComponent implements OnChanges, OnDe /** * Called when items list has been updated. - * - * @param items New items. */ - protected onItemsUpdated(): void { + protected async onItemsUpdated(): Promise { + // Wait for slides to be added in DOM. + await CoreUtils.nextTick(); + + // Update the slides component so the slides list reflects the new items. + await this.updateSlidesComponent(); + const currentItem = this.manager?.getSelectedItem(); if (!currentItem || !this.manager) { @@ -249,6 +270,24 @@ export class CoreSwipeSlidesComponent implements OnChanges, OnDe }; } + /** + * Update slides component. + */ + protected async updateSlidesComponent(): Promise { + if (!this.slides) { + return; + } + + const promise = this.slides.update(); + this.updateSlidesPromise = promise; + + await promise; + + if (this.updateSlidesPromise === promise) { + delete this.updateSlidesPromise; + } + } + /** * @inheritdoc */ From e2d2e04f41114615bbdd8e5dba87f1bf1398afca Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 15 Nov 2022 16:11:01 +0100 Subject: [PATCH 3/5] MOBILE-3784 calendar: Fix a11y title of days --- src/addons/calendar/components/calendar/calendar.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/addons/calendar/components/calendar/calendar.ts b/src/addons/calendar/components/calendar/calendar.ts index 81b65c10e..1e8bf8d73 100644 --- a/src/addons/calendar/components/calendar/calendar.ts +++ b/src/addons/calendar/components/calendar/calendar.ts @@ -526,6 +526,7 @@ class AddonCalendarMonthSlidesItemsManagerSource extends CoreSwipeSlidesDynamicI const weeks = result.weeks as AddonCalendarWeek[]; const currentDay = moment().date(); const currentTime = CoreTimeUtils.timestamp(); + const dayMoment = moment(month.moment); const preloadedMonth: PreloadedMonth = { ...month, @@ -538,7 +539,7 @@ class AddonCalendarMonthSlidesItemsManagerSource extends CoreSwipeSlidesDynamicI await Promise.all(weeks.map(async (week) => { await Promise.all(week.days.map(async (day) => { day.periodName = CoreTimeUtils.userDate( - month.moment.unix() * 1000, + dayMoment.date(day.mday).unix() * 1000, 'core.strftimedaydate', ); day.eventsFormated = day.eventsFormated || []; From e72cf7258e443f9caf73651a75037d60b1e158ef Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 16 Nov 2022 12:15:13 +0100 Subject: [PATCH 4/5] MOBILE-3874 calendar: Fix event time and improve a11y --- scripts/langindex.json | 2 ++ src/addons/calendar/pages/event/event.html | 2 +- src/addons/calendar/services/calendar.ts | 17 ++++++++++++----- src/core/lang.json | 2 ++ 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/scripts/langindex.json b/scripts/langindex.json index eafbc0172..82e052166 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -1695,6 +1695,7 @@ "core.editor.underline": "atto_underline/pluginname", "core.editor.unorderedlist": "atto_unorderedlist/pluginname", "core.emptysplit": "local_moodlemobileapp", + "core.endingtime": "local_moodlemobileapp", "core.endonesteptour": "tool_usertours", "core.error": "moodle", "core.errorchangecompletion": "local_moodlemobileapp", @@ -2304,6 +2305,7 @@ "core.sort": "moodle", "core.sortby": "moodle", "core.start": "local_moodlemobileapp", + "core.startingtime": "local_moodlemobileapp", "core.storingfiles": "local_moodlemobileapp", "core.strftimedate": "langconfig", "core.strftimedatefullshort": "langconfig", diff --git a/src/addons/calendar/pages/event/event.html b/src/addons/calendar/pages/event/event.html index add0b9a28..e41c029cc 100644 --- a/src/addons/calendar/pages/event/event.html +++ b/src/addons/calendar/pages/event/event.html @@ -58,7 +58,7 @@ - +

{{ 'addon.calendar.when' | translate }}

{ + const getTimeHtml = (time: string, a11yLangKey: string): string => + `${time}`; + const getStartTimeHtml = (time: string): string => getTimeHtml(time, 'core.startingtime'); + const getEndTimeHtml = (time: string): string => getTimeHtml(time, 'core.endingtime'); + const start = event.timestart * 1000; const end = (event.timestart + event.timeduration) * 1000; let time: string; - if (!event.timeduration) { + if (event.timeduration) { if (moment(start).isSame(end, 'day')) { // Event starts and ends the same day. if (event.timeduration == CoreConstants.SECONDS_DAY) { time = Translate.instant('addon.calendar.allday'); } else { - time = CoreTimeUtils.userDate(start, format) + ' » ' + - CoreTimeUtils.userDate(end, format); + time = getStartTimeHtml(CoreTimeUtils.userDate(start, format)) + ' » ' + + getEndTimeHtml(CoreTimeUtils.userDate(end, format)); } } else { @@ -388,11 +393,12 @@ export class AddonCalendarProvider { await Promise.all(promises); - return dayStart + timeStart + ' » ' + dayEnd + timeEnd; + return getStartTimeHtml(dayStart + timeStart) + ' » ' + + getEndTimeHtml(dayEnd + timeEnd); } } else { // There is no time duration. - time = CoreTimeUtils.userDate(start, format); + time = getStartTimeHtml(CoreTimeUtils.userDate(start, format)); } if (showTime) { @@ -2173,6 +2179,7 @@ export type AddonCalendarSubmitCreateUpdateFormDataWSParams = Omit Date: Wed, 16 Nov 2022 12:20:48 +0100 Subject: [PATCH 5/5] MOBILE-3784 calendar: Add behat for create event --- .../tests/behat/behat_app.php | 18 ++++++ .../calendar/pages/edit-event/edit-event.html | 5 +- .../pages/edit-event/edit-event.page.ts | 4 ++ src/addons/calendar/services/calendar-sync.ts | 1 + .../tests/behat/create_events.feature | 64 +++++++++++++++++++ .../component/addon-mod-data-field-date.html | 2 +- .../mod/data/fields/date/component/date.ts | 3 + .../addon-user-profile-field-datetime.html | 2 +- .../datetime/component/datetime.ts | 3 + src/core/services/app.ts | 12 ++++ src/testing/services/behat-blocking.ts | 20 +++++- src/testing/services/behat-runtime.ts | 25 +++++++- src/testing/testing.module.ts | 4 +- 13 files changed, 154 insertions(+), 9 deletions(-) create mode 100755 src/addons/calendar/tests/behat/create_events.feature diff --git a/local_moodleappbehat/tests/behat/behat_app.php b/local_moodleappbehat/tests/behat/behat_app.php index 0b09375b5..a5af4a3ca 100644 --- a/local_moodleappbehat/tests/behat/behat_app.php +++ b/local_moodleappbehat/tests/behat/behat_app.php @@ -1012,4 +1012,22 @@ class behat_app extends behat_app_helper { return true; } + /** + * View a specific month in the calendar in the app. + * + * @When /^I open the calendar for "(?P\d+)" "(?P\d+)" in the app$/ + * @param int $month the month selected as a number + * @param int $year the four digit year + */ + public function i_open_the_calendar_for($month, $year) { + $options = json_encode([ + 'params' => [ + 'month' => $month, + 'year' => $year, + ], + ]); + + $this->zone_js("navigator.navigateToSitePath('/calendar/index', $options)"); + } + } diff --git a/src/addons/calendar/pages/edit-event/edit-event.html b/src/addons/calendar/pages/edit-event/edit-event.html index c5b628d9d..23cea6789 100644 --- a/src/addons/calendar/pages/edit-event/edit-event.html +++ b/src/addons/calendar/pages/edit-event/edit-event.html @@ -31,7 +31,7 @@

{{ 'core.date' | translate }}

+ [max]="maxDate" [min]="minDate" [displayTimezone]="displayTimezone">
@@ -156,7 +156,8 @@ + [placeholder]="'addon.calendar.durationuntil' | translate" [displayFormat]="dateFormat" + [displayTimezone]="displayTimezone"> diff --git a/src/addons/calendar/pages/edit-event/edit-event.page.ts b/src/addons/calendar/pages/edit-event/edit-event.page.ts index f5a21a733..e4fb34416 100644 --- a/src/addons/calendar/pages/edit-event/edit-event.page.ts +++ b/src/addons/calendar/pages/edit-event/edit-event.page.ts @@ -46,6 +46,7 @@ import { CoreForms } from '@singletons/form'; import { CoreReminders, CoreRemindersService, CoreRemindersUnits } from '@features/reminders/services/reminders'; import { CoreRemindersSetReminderMenuComponent } from '@features/reminders/components/set-reminder-menu/set-reminder-menu'; import moment from 'moment-timezone'; +import { CoreAppProvider } from '@services/app'; /** * Page that displays a form to create/edit an event. @@ -78,6 +79,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave { eventId?: number; maxDate: string; minDate: string; + displayTimezone?: string; // Form variables. form: FormGroup; @@ -109,6 +111,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave { // Calculate format to use. ion-datetime doesn't support escaping characters ([]), so we remove them. this.dateFormat = CoreTimeUtils.convertPHPToMoment(Translate.instant('core.strftimedatetimeshort')) .replace(/[[\]]/g, ''); + this.displayTimezone = CoreAppProvider.getForcedTimezone(); this.form = new FormGroup({}); @@ -489,6 +492,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave { description: { text: formData.description || '', format: 1, + itemid: 0, // Files not supported yet. }, location: formData.location, duration: formData.duration, diff --git a/src/addons/calendar/services/calendar-sync.ts b/src/addons/calendar/services/calendar-sync.ts index c2a351e87..59ad3e571 100644 --- a/src/addons/calendar/services/calendar-sync.ts +++ b/src/addons/calendar/services/calendar-sync.ts @@ -253,6 +253,7 @@ export class AddonCalendarSyncProvider extends CoreSyncBaseProvider + [disabled]="searchMode && !searchFields!['f_'+field.id+'_z']" [displayFormat]="format" [displayTimezone]="displayTimezone"> diff --git a/src/addons/mod/data/fields/date/component/date.ts b/src/addons/mod/data/fields/date/component/date.ts index 761953f89..91e9c4041 100644 --- a/src/addons/mod/data/fields/date/component/date.ts +++ b/src/addons/mod/data/fields/date/component/date.ts @@ -13,6 +13,7 @@ // limitations under the License. import { Component } from '@angular/core'; +import { CoreAppProvider } from '@services/app'; import { CoreTimeUtils } from '@services/utils/time'; import { Translate } from '@singletons'; import moment, { Moment } from 'moment-timezone'; @@ -31,6 +32,7 @@ export class AddonModDataFieldDateComponent extends AddonModDataFieldPluginBaseC displayDate?: number; maxDate?: string; minDate?: string; + displayTimezone?: string; /** * @inheritdoc @@ -52,6 +54,7 @@ export class AddonModDataFieldDateComponent extends AddonModDataFieldPluginBaseC )); this.maxDate = CoreTimeUtils.getDatetimeDefaultMax(); this.minDate = CoreTimeUtils.getDatetimeDefaultMin(); + this.displayTimezone = CoreAppProvider.getForcedTimezone(); if (this.searchMode) { this.addControl('f_' + this.field.id + '_z'); diff --git a/src/addons/userprofilefield/datetime/component/addon-user-profile-field-datetime.html b/src/addons/userprofilefield/datetime/component/addon-user-profile-field-datetime.html index 77f12bae2..bf48afb57 100644 --- a/src/addons/userprofilefield/datetime/component/addon-user-profile-field-datetime.html +++ b/src/addons/userprofilefield/datetime/component/addon-user-profile-field-datetime.html @@ -12,7 +12,7 @@ {{ field.name }} + [min]="min" [monthNames]="monthNames" [displayTimezone]="displayTimezone"> diff --git a/src/addons/userprofilefield/datetime/component/datetime.ts b/src/addons/userprofilefield/datetime/component/datetime.ts index c26355ce0..eac0d6ec2 100644 --- a/src/addons/userprofilefield/datetime/component/datetime.ts +++ b/src/addons/userprofilefield/datetime/component/datetime.ts @@ -22,6 +22,7 @@ import { CoreUserProfileField } from '@features/user/services/user'; import { Translate } from '@singletons'; import { CoreUserProfileFieldBaseComponent } from '@features/user/classes/base-profilefield-component'; import { CoreLang } from '@services/lang'; +import { CoreAppProvider } from '@services/app'; /** * Directive to render a datetime user profile field. @@ -37,6 +38,7 @@ export class AddonUserProfileFieldDatetimeComponent extends CoreUserProfileField max?: string; valueNumber = 0; monthNames?: string[]; + displayTimezone?: string; /** * Init the data when the field is meant to be displayed without editing. @@ -56,6 +58,7 @@ export class AddonUserProfileFieldDatetimeComponent extends CoreUserProfileField super.initForEdit(field); this.monthNames = CoreLang.getMonthNames(); + this.displayTimezone = CoreAppProvider.getForcedTimezone(); // Check if it's only date or it has time too. const hasTime = CoreUtils.isTrueOrOne(field.param3); diff --git a/src/core/services/app.ts b/src/core/services/app.ts index ed55f1e32..4971fe65b 100644 --- a/src/core/services/app.ts +++ b/src/core/services/app.ts @@ -70,6 +70,18 @@ export class CoreAppProvider { return !!navigator.webdriver; } + /** + * Returns the forced timezone to use. Timezone is forced for automated tests. + * + * @return Timezone. Undefined to use the user's timezone. + */ + static getForcedTimezone(): string | undefined { + if (CoreAppProvider.isAutomated()) { + // Use the same timezone forced for LMS in tests. + return 'Australia/Perth'; + } + } + /** * Initialize database. */ diff --git a/src/testing/services/behat-blocking.ts b/src/testing/services/behat-blocking.ts index 92cb315cf..345dba3c9 100644 --- a/src/testing/services/behat-blocking.ts +++ b/src/testing/services/behat-blocking.ts @@ -192,9 +192,25 @@ export class TestingBehatBlockingService { */ protected async checkUIBlocked(): Promise { await CoreUtils.nextTick(); - const blocked = document.querySelector('div.core-loading-container, ion-loading, .click-block-active'); - if (blocked?.offsetParent) { + const blockingElements = Array.from( + document.querySelectorAll('div.core-loading-container, ion-loading, .click-block-active'), + ); + + const isBlocked = blockingElements.some(element => { + if (!element.offsetParent) { + return false; + } + + const slide = element.closest('ion-slide'); + if (slide && !slide.classList.contains('swiper-slide-active')) { + return false; + } + + return true; + }); + + if (isBlocked) { if (!this.waitingBlocked) { this.block('blocked'); this.waitingBlocked = true; diff --git a/src/testing/services/behat-runtime.ts b/src/testing/services/behat-runtime.ts index a9d012ad4..878df655b 100644 --- a/src/testing/services/behat-runtime.ts +++ b/src/testing/services/behat-runtime.ts @@ -27,6 +27,7 @@ import { CoreComponentsRegistry } from '@singletons/components-registry'; import { CoreDom } from '@singletons/dom'; import { Injectable } from '@angular/core'; import { CoreSites, CoreSitesProvider } from '@services/sites'; +import { CoreNavigator, CoreNavigatorService } from '@services/navigator'; /** * Behat runtime servive with public API. @@ -56,6 +57,10 @@ export class TestingBehatRuntimeService { return CoreSites.instance; } + get navigator(): CoreNavigatorService { + return CoreNavigator.instance; + } + /** * Init behat functions and set options like skipping onboarding. * @@ -436,7 +441,7 @@ export class TestingBehatRuntimeService { return 'ERROR: No element matches field to set.'; } - const foundValue = 'value' in found ? found.value : found.innerText; + const foundValue = this.getFieldValue(found); if (value !== foundValue) { return `ERROR: Expecting value "${value}", found "${foundValue}" instead.`; } @@ -457,6 +462,24 @@ export class TestingBehatRuntimeService { ); } + /** + * Get the value of a certain field. + * + * @param element Field to get the value. + * @return Value. + */ + protected getFieldValue(element: HTMLElement | HTMLInputElement): string { + 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; + } + } + + return 'value' in element ? element.value : element.innerText; + } + /** * Get an Angular component instance. * diff --git a/src/testing/testing.module.ts b/src/testing/testing.module.ts index bc9171833..479df360d 100644 --- a/src/testing/testing.module.ts +++ b/src/testing/testing.module.ts @@ -28,8 +28,8 @@ function initializeAutomatedTests(window: AutomatedTestsWindow) { window.behat = TestingBehatRuntime.instance; - // Force timezone for automated tests. Use the same timezone forced for LMS in tests. - moment.tz.setDefault('Australia/Perth'); + // Force timezone for automated tests. + moment.tz.setDefault(CoreAppProvider.getForcedTimezone()); } @NgModule({