Merge pull request #3961 from dpalou/MOBILE-3947

MOBILE-3947 ui: Display Cancel/Done buttons in datetime
main
Pau Ferrer Ocaña 2024-03-12 13:24:22 +01:00 committed by GitHub
commit 275089850e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 82 additions and 23 deletions

View File

@ -1211,4 +1211,13 @@ class behat_app extends behat_app_helper {
$this->resize_app_window($width, $height); $this->resize_app_window($width, $height);
} }
/**
* Wait until Toast disappears.
*
* @When I wait toast to dismiss in the app
*/
public function i_wait_toast_to_dismiss_in_the_app() {
$this->runtime_js('waitToastDismiss()');
}
} }

View File

@ -27,12 +27,13 @@
<!-- Date. --> <!-- Date. -->
<ion-item class="ion-text-wrap"> <ion-item class="ion-text-wrap">
<ion-label position="stacked"> <ion-label position="stacked">
<p class="item-heading" [core-mark-required]="true">{{ 'core.date' | translate }}</p> <label class="item-heading" [core-mark-required]="true" for="timestart-button">{{ 'core.date' | translate }}</label>
</ion-label> </ion-label>
<ion-datetime-button datetime="timestart" /> <ion-datetime-button datetime="timestart" id="timestart-button" />
<ion-modal [keepContentsMounted]="true"> <ion-modal [keepContentsMounted]="true">
<ng-template> <ng-template>
<ion-datetime id="timestart" formControlName="timestart" presentation="date-time" [max]="maxDate" [min]="minDate"> <ion-datetime id="timestart" formControlName="timestart" presentation="date-time" [max]="maxDate" [min]="minDate"
[showDefaultButtons]="true">
<span slot="title">{{'core.date' | translate}}</span> <span slot="title">{{'core.date' | translate}}</span>
</ion-datetime> </ion-datetime>
</ng-template> </ng-template>
@ -146,16 +147,16 @@
</ion-item> </ion-item>
<ion-item> <ion-item>
<ion-radio [value]="1"> <ion-radio [value]="1">
<p>{{ 'addon.calendar.durationuntil' | translate }}</p> <p><label for="timedurationuntil-button">{{ 'addon.calendar.durationuntil' | translate }}</label></p>
</ion-radio> </ion-radio>
</ion-item> </ion-item>
<ion-item *ngIf="form.controls.duration.value === 1"> <ion-item *ngIf="form.controls.duration.value === 1">
<ion-label position="stacked" /> <ion-label position="stacked" />
<ion-datetime-button datetime="timedurationuntil" /> <ion-datetime-button datetime="timedurationuntil" id="timedurationuntil-button" />
<ion-modal [keepContentsMounted]="true"> <ion-modal [keepContentsMounted]="true">
<ng-template> <ng-template>
<ion-datetime id="timedurationuntil" formControlName="timedurationuntil" [max]="maxDate" [min]="minDate" <ion-datetime id="timedurationuntil" formControlName="timedurationuntil" [max]="maxDate" [min]="minDate"
presentation="date-time"> presentation="date-time" [showDefaultButtons]="true">
<span slot="title">{{'addon.calendar.durationuntil' | translate}}</span> <span slot="title">{{'addon.calendar.durationuntil' | translate}}</span>
</ion-datetime> </ion-datetime>
</ng-template> </ng-template>
@ -163,11 +164,11 @@
</ion-item> </ion-item>
<ion-item> <ion-item>
<ion-radio [value]="2"> <ion-radio [value]="2">
<p id="durationinminutes">{{ 'addon.calendar.durationminutes' | translate }}</p> <p><label for="timedurationminutes">{{ 'addon.calendar.durationminutes' | translate }}</label></p>
</ion-radio> </ion-radio>
</ion-item> </ion-item>
<ion-item *ngIf="form.controls.duration.value === 2"> <ion-item *ngIf="form.controls.duration.value === 2">
<ion-input type="number" name="timedurationminutes" labelPlacement="start" aria-labelledby="durationinminutes" <ion-input type="number" name="timedurationminutes" labelPlacement="start" id="timedurationminutes"
[placeholder]="'addon.calendar.durationminutes' | translate" formControlName="timedurationminutes" /> [placeholder]="'addon.calendar.durationminutes' | translate" formControlName="timedurationminutes" />
</ion-item> </ion-item>
</ion-radio-group> </ion-radio-group>

View File

@ -26,8 +26,9 @@ Feature: Test creation of calendar events in app
When I press "More" in the app When I press "More" in the app
And I press "Calendar" in the app And I press "Calendar" in the app
And I press "New event" in the app And I press "New event" in the app
Then I should find "## now ##%b %e, %Y##" in the app
# Flaky step, sometimes it fails due to minute change when checking. # 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 find "## now ##%l:%M %p##" in the app
And I should not be able to press "Save" in the app And I should not be able to press "Save" in the app
# Check that student can only create User events. # Check that student can only create User events.

View File

@ -5,7 +5,7 @@
<ion-modal [keepContentsMounted]="true"> <ion-modal [keepContentsMounted]="true">
<ng-template> <ng-template>
<ion-datetime id="datetime" [formControlName]="'f_'+field.id" [max]="maxDate" [min]="minDate" <ion-datetime id="datetime" [formControlName]="'f_'+field.id" [max]="maxDate" [min]="minDate"
[disabled]="searchMode && !searchFields['f_'+field.id+'_z']" presentation="date" /> [disabled]="searchMode && !searchFields['f_'+field.id+'_z']" presentation="date" [showDefaultButtons]="true" />
</ng-template> </ng-template>
</ion-modal> </ion-modal>
<core-input-errors *ngIf="error && editMode" [control]="form.controls['f_'+field.id]" [errorText]="error" /> <core-input-errors *ngIf="error && editMode" [control]="form.controls['f_'+field.id]" [errorText]="error" />

View File

@ -13,16 +13,22 @@
<!-- Edit. --> <!-- Edit. -->
<ion-item *ngIf="edit && field && field.shortname && form" class="ion-text-wrap" [formGroup]="form"> <ion-item *ngIf="edit && field && field.shortname && form" class="ion-text-wrap" [formGroup]="form">
<ion-label position="stacked"> <ion-label position="stacked">
<span [core-mark-required]="required"> <label [core-mark-required]="required" for="profile-field-datetime-{{field.shortname}}-button">
<core-format-text [text]="field.name" [contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" <core-format-text [text]="field.name" [contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId"
[courseId]="courseId" [wsNotFiltered]="true" /> [courseId]="courseId" [wsNotFiltered]="true" />
</span> </label>
</ion-label> </ion-label>
<ion-datetime-button datetime="datetime" /> <ion-datetime-button datetime="profile-field-datetime-{{field.shortname}}" id="profile-field-datetime-{{field.shortname}}-button">
<ng-container *ngIf="control?.value === undefined">
<span slot="date-target">{{ 'core.choosedots' | translate }}</span>
<span slot="time-target">{{ 'core.choosedots' | translate }}</span>
</ng-container>
</ion-datetime-button>
<ion-modal [keepContentsMounted]="true"> <ion-modal [keepContentsMounted]="true">
<ng-template> <ng-template>
<ion-datetime id="datetime" [formControlName]="modelName" [presentation]="ionDateTimePresentation" [max]="max" [min]="min"> <ion-datetime id="profile-field-datetime-{{field.shortname}}" [formControlName]="modelName"
[presentation]="ionDateTimePresentation" [max]="max" [min]="min" [showDefaultButtons]="true">
<span slot="title"> <span slot="title">
<core-format-text [text]="field.name" [contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" <core-format-text [text]="field.name" [contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId"
[courseId]="courseId" [wsNotFiltered]="true" /> [courseId]="courseId" [wsNotFiltered]="true" />

View File

@ -86,7 +86,7 @@ export class AddonUserProfileFieldDatetimeComponent extends CoreUserProfileField
*/ */
protected createFormControl(field: AuthEmailSignupProfileField): FormControl<string | undefined> { protected createFormControl(field: AuthEmailSignupProfileField): FormControl<string | undefined> {
const formData = { const formData = {
value: field.defaultdata != '0' ? field.defaultdata : undefined, value: field.defaultdata && field.defaultdata !== '0' ? field.defaultdata : undefined,
disabled: this.disabled, disabled: this.disabled,
}; };

View File

@ -23,19 +23,19 @@ Feature: Set a new reminder on course
# Default set # Default set
When I press "Set a reminder for \"Course 1\" (Course end date)" in the app When I press "Set a reminder for \"Course 1\" (Course end date)" in the app
Then I should find "Reminder set for " in the app Then I should find "Reminder set for " in the app
And I should find "Reminder set for" in the app
# Set from list # Set from list
When I press "Set a reminder for \"Course 1\" (Course end date)" in the app When I wait toast to dismiss in the app
And I press "Set a reminder for \"Course 1\" (Course end date)" in the app
Then I should find "Set a reminder" in the app Then I should find "Set a reminder" in the app
And "At the time of the event" should be selected in the app And "At the time of the event" should be selected in the app
But "12 hours before" should not be selected in the app But "12 hours before" should not be selected in the app
When I press "12 hours before" in the app When I press "12 hours before" in the app
Then I should find "Reminder set for " in the app Then I should find "Reminder set for " in the app
And I should find "Reminder set for" in the app
# Custom set # Custom set
When I press "Set a reminder for \"Course 1\" (Course end date)" in the app When I wait toast to dismiss in the app
And I press "Set a reminder for \"Course 1\" (Course end date)" in the app
Then I should find "Set a reminder" in the app Then I should find "Set a reminder" in the app
And "At the time of the event" should not be selected in the app And "At the time of the event" should not be selected in the app
But "12 hours before" should be selected in the app But "12 hours before" should be selected in the app
@ -46,10 +46,10 @@ Feature: Set a new reminder on course
| Units | hours | | Units | hours |
And I press "Set reminder" in the app And I press "Set reminder" in the app
Then I should find "Reminder set for " in the app Then I should find "Reminder set for " in the app
And I should find "Reminder set for" in the app
# Remove # Remove
When I press "Set a reminder for \"Course 1\" (Course end date)" in the app When I wait toast to dismiss in the app
And I press "Set a reminder for \"Course 1\" (Course end date)" in the app
Then "2 hours before" should be selected in the app Then "2 hours before" should be selected in the app
When I press "Delete reminder" in the app When I press "Delete reminder" in the app
Then I should find "Reminder deleted" in the app Then I should find "Reminder deleted" in the app

View File

@ -376,6 +376,11 @@ export class TestingBehatDomUtilsService {
containers = containers containers = containers
.filter(container => { .filter(container => {
if (!this.isElementVisible(container)) {
// Ignore containers not visible.
return false;
}
if (container.tagName === 'ION-ALERT') { if (container.tagName === 'ION-ALERT') {
// For some reason, in Behat sometimes alerts aren't removed from DOM, the close animation doesn't finish. // For some reason, in Behat sometimes alerts aren't removed from DOM, the close animation doesn't finish.
// Filter alerts with pointer-events none since that style is set before the close animation starts. // Filter alerts with pointer-events none since that style is set before the close animation starts.
@ -453,7 +458,16 @@ export class TestingBehatDomUtilsService {
const inputId = label.getAttribute('for'); const inputId = label.getAttribute('for');
if (inputId) { if (inputId) {
return document.getElementById(inputId) || undefined; const element = document.getElementById(inputId) || undefined;
if (element?.tagName !== 'ION-DATETIME-BUTTON') {
return element;
}
// Search the ion-datetime associated with the button.
const datetimeId = (<HTMLIonDatetimeButtonElement> element).datetime;
const datetime = document.querySelector<HTMLElement>(`ion-datetime#${datetimeId}`);
return datetime || undefined;
} }
input = this.getShadowDOMHost(label) || undefined; input = this.getShadowDOMHost(label) || undefined;
@ -477,6 +491,19 @@ export class TestingBehatDomUtilsService {
locator: TestingBehatElementLocator, locator: TestingBehatElementLocator,
options: TestingBehatFindOptions = {}, options: TestingBehatFindOptions = {},
): HTMLElement | undefined { ): HTMLElement | undefined {
// Remove extra spaces.
const treatedText = locator.text.trim().replace(/\s\s+/g, ' ');
if (treatedText !== locator.text) {
const element = this.findElementsBasedOnText({
...locator,
text: treatedText,
}, options)[0];
if (element) {
return element;
}
}
return this.findElementsBasedOnText(locator, options)[0]; return this.findElementsBasedOnText(locator, options)[0];
} }

View File

@ -18,7 +18,7 @@ import { CoreCustomURLSchemes, CoreCustomURLSchemesProvider } from '@services/ur
import { ONBOARDING_DONE } from '@features/login/constants'; import { ONBOARDING_DONE } from '@features/login/constants';
import { CoreConfig } from '@services/config'; import { CoreConfig } from '@services/config';
import { EnvironmentConfig } from '@/types/config'; import { EnvironmentConfig } from '@/types/config';
import { LocalNotifications, makeSingleton, NgZone } from '@singletons'; import { LocalNotifications, makeSingleton, NgZone, ToastController } from '@singletons';
import { CoreNetwork, CoreNetworkService } from '@services/network'; import { CoreNetwork, CoreNetworkService } from '@services/network';
import { CorePushNotifications, CorePushNotificationsProvider } from '@features/pushnotifications/services/pushnotifications'; import { CorePushNotifications, CorePushNotificationsProvider } from '@features/pushnotifications/services/pushnotifications';
import { CoreCronDelegate, CoreCronDelegateService } from '@services/cron'; import { CoreCronDelegate, CoreCronDelegateService } from '@services/cron';
@ -33,6 +33,7 @@ import { Swiper } from 'swiper';
import { LocalNotificationsMock } from '@features/emulator/services/local-notifications'; import { LocalNotificationsMock } from '@features/emulator/services/local-notifications';
import { GetClosureArgs } from '@/core/utils/types'; import { GetClosureArgs } from '@/core/utils/types';
import { CoreIframeComponent } from '@components/iframe/iframe'; import { CoreIframeComponent } from '@components/iframe/iframe';
import { CoreUtils } from '@services/utils/utils';
/** /**
* Behat runtime servive with public API. * Behat runtime servive with public API.
@ -745,6 +746,15 @@ export class TestingBehatRuntimeService {
return 'OK'; return 'OK';
} }
/**
* Wait for toast to be dismissed in the app.
*
* @returns Promise resolved when toast has been dismissed.
*/
async waitToastDismiss(): Promise<void> {
await CoreUtils.ignoreErrors(ToastController.dismiss());
}
} }
export const TestingBehatRuntime = makeSingleton(TestingBehatRuntimeService); export const TestingBehatRuntime = makeSingleton(TestingBehatRuntimeService);

View File

@ -2015,6 +2015,11 @@ ion-item.item-label-stacked ion-datetime-button {
align-self: self-end; align-self: self-end;
} }
ion-datetime-button p {
margin-top: 4px;
margin-bottom: 4px;
}
// Table App styles // Table App styles
table.core-table { table.core-table {
border-collapse: collapse; border-collapse: collapse;