MOBILE-3784 calendar: Add behat for create event
parent
e72cf7258e
commit
82e9331357
|
@ -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<month>\d+)" "(?P<year>\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)");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
<p class="item-heading" [core-mark-required]="true">{{ 'core.date' | translate }}</p>
|
||||
</ion-label>
|
||||
<ion-datetime formControlName="timestart" [placeholder]="'core.date' | translate" [displayFormat]="dateFormat"
|
||||
[max]="maxDate" [min]="minDate">
|
||||
[max]="maxDate" [min]="minDate" [displayTimezone]="displayTimezone">
|
||||
</ion-datetime>
|
||||
<core-input-errors [control]="form.controls.timestart" [errorMessages]="errors"></core-input-errors>
|
||||
</ion-item>
|
||||
|
@ -156,7 +156,8 @@
|
|||
<ion-item *ngIf="form.controls.duration.value === 1">
|
||||
<ion-label position="stacked"></ion-label>
|
||||
<ion-datetime formControlName="timedurationuntil" [max]="maxDate" [min]="minDate"
|
||||
[placeholder]="'addon.calendar.durationuntil' | translate" [displayFormat]="dateFormat">
|
||||
[placeholder]="'addon.calendar.durationuntil' | translate" [displayFormat]="dateFormat"
|
||||
[displayTimezone]="displayTimezone">
|
||||
</ion-datetime>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -253,6 +253,7 @@ export class AddonCalendarSyncProvider extends CoreSyncBaseProvider<AddonCalenda
|
|||
description: {
|
||||
text: event.description || '',
|
||||
format: 1,
|
||||
itemid: 0, // Files not supported yet.
|
||||
},
|
||||
},
|
||||
); // Clone the object because it will be modified in the submit function.
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
@core @core_calendar @app @javascript
|
||||
Feature: Test creation of calendar events in app
|
||||
In order to take advantage of all the calendar features while using the mobile app
|
||||
As a student
|
||||
I need basic to be able to create and edit calendar events in the app
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | teacher | teacher1@example.com |
|
||||
| student1 | Student1 | student1 | student1@example.com |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Course 1 | C1 | 0 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| student1 | C1 | student |
|
||||
|
||||
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
|
||||
And I should not be able to press "Save" in the app
|
||||
|
||||
# Check that student can only create User events.
|
||||
When I press "Type of event" in the app
|
||||
Then I should not find "Cancel" in the app
|
||||
And I should find "User" within "Type of event" "ion-item" in the 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 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
|
||||
And I press "Save" in the app
|
||||
Then I should find "Calendar events" in the app
|
||||
|
||||
# Verify that event was created right.
|
||||
When I open the calendar for "4" "2025" in the app
|
||||
And I press "Friday, 11 April 2025" in the app
|
||||
Then I should find "User Event 01" in the app
|
||||
|
||||
When I press "User Event 01" in the app
|
||||
Then I should find "Friday, 11 April" in the app
|
||||
And I should find "Starting time: 9:00 AM" in the app
|
||||
And I should find "User event" within "Event type" "ion-item" in the app
|
||||
And I should find "This is User Event 01 description." in the app
|
||||
And I should find "Barcelona" in the app
|
||||
But I should not find "Ending time" in the app
|
||||
|
||||
When I press "Display options" in the app
|
||||
Then I should find "Edit" in the app
|
||||
And I should find "Delete" in the app
|
||||
|
||||
When I close the popup in the app
|
||||
And I press "Barcelona" in the app
|
||||
And I press "OK" in the app
|
||||
Then the app should have opened a browser tab with url "google.com"
|
||||
|
||||
# @todo: Add more Scenarios to test teacher, different values, and creating events from other views (e.g. day view).
|
|
@ -1,7 +1,7 @@
|
|||
<span *ngIf="inputMode && form" [formGroup]="form">
|
||||
<span *ngIf="editMode" [core-mark-required]="field.required" class="core-mark-required"></span>
|
||||
<ion-datetime [formControlName]="'f_'+field.id" [placeholder]="'core.date' | translate" [max]="maxDate" [min]="minDate"
|
||||
[disabled]="searchMode && !searchFields!['f_'+field.id+'_z']" [displayFormat]="format">
|
||||
[disabled]="searchMode && !searchFields!['f_'+field.id+'_z']" [displayFormat]="format" [displayTimezone]="displayTimezone">
|
||||
</ion-datetime>
|
||||
<core-input-errors *ngIf="error && editMode" [control]="form.controls['f_'+field.id]" [errorText]="error"></core-input-errors>
|
||||
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<span [core-mark-required]="required">{{ field.name }}</span>
|
||||
</ion-label>
|
||||
<ion-datetime [formControlName]="modelName" [placeholder]="'core.choosedots' | translate" [displayFormat]="format" [max]="max"
|
||||
[min]="min" [monthNames]="monthNames">
|
||||
[min]="min" [monthNames]="monthNames" [displayTimezone]="displayTimezone">
|
||||
</ion-datetime>
|
||||
<core-input-errors [control]="form.controls[modelName]"></core-input-errors>
|
||||
</ion-item>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -192,9 +192,25 @@ export class TestingBehatBlockingService {
|
|||
*/
|
||||
protected async checkUIBlocked(): Promise<void> {
|
||||
await CoreUtils.nextTick();
|
||||
const blocked = document.querySelector<HTMLElement>('div.core-loading-container, ion-loading, .click-block-active');
|
||||
|
||||
if (blocked?.offsetParent) {
|
||||
const blockingElements = Array.from(
|
||||
document.querySelectorAll<HTMLElement>('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;
|
||||
|
|
|
@ -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<HTMLElement>('.datetime-text');
|
||||
if (dateTimeTextElement) {
|
||||
return dateTimeTextElement.innerText;
|
||||
}
|
||||
}
|
||||
|
||||
return 'value' in element ? element.value : element.innerText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an Angular component instance.
|
||||
*
|
||||
|
|
|
@ -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({
|
||||
|
|
Loading…
Reference in New Issue