commit
97df9fb152
|
@ -6,7 +6,7 @@ on:
|
|||
behat_tags:
|
||||
description: 'Behat tags to execute'
|
||||
required: true
|
||||
default: '~@performance&&~@ionic7_failure'
|
||||
default: '~@performance'
|
||||
moodle_branch:
|
||||
description: 'Moodle branch'
|
||||
required: true
|
||||
|
@ -27,7 +27,7 @@ jobs:
|
|||
MOODLE_DOCKER_PHP_VERSION: '8.1'
|
||||
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' }}
|
||||
BEHAT_TAGS: ${{ github.event.inputs.behat_tags || '~@performance' }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
|
|
@ -781,13 +781,10 @@ class behat_app extends behat_app_helper {
|
|||
/**
|
||||
* Sets a field to the given text value in the app.
|
||||
*
|
||||
* Currently this only works for input fields which must be identified using a partial or
|
||||
* exact match on the placeholder text.
|
||||
*
|
||||
* @Given /^I set the field "((?:[^"]|\\")+)" to "((?:[^"]|\\")*)" in the app$/
|
||||
* @param string $field Text identifying field
|
||||
* @param string $value Value for field
|
||||
* @throws DriverException If the field set doesn't work
|
||||
* @param string $field Text identifying the field.
|
||||
* @param string $value Value to set. In select fields, this can be either the value or text included in the select option.
|
||||
* @throws DriverException If the field set doesn't work.
|
||||
*/
|
||||
public function i_set_the_field_in_the_app(string $field, string $value) {
|
||||
$field = addslashes_js($field);
|
||||
|
|
|
@ -639,8 +639,10 @@ class AddonCalendarEventsSwipeItemsManager extends CoreSwipeNavigationItemsManag
|
|||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot): string | null {
|
||||
return route.params.id;
|
||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot | ActivatedRoute): string | null {
|
||||
const snapshot = route instanceof ActivatedRouteSnapshot ? route : route.snapshot;
|
||||
|
||||
return snapshot.params.id;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ import { ADDON_COMPETENCY_SUMMARY_PAGE } from '@addons/competency/competency.mod
|
|||
import { CoreSwipeNavigationItemsManager } from '@classes/items-management/swipe-navigation-items-manager';
|
||||
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
|
||||
import { AddonCompetencyPlanCompetenciesSource } from '@addons/competency/classes/competency-plan-competencies-source';
|
||||
import { ActivatedRouteSnapshot } from '@angular/router';
|
||||
import { ActivatedRoute, ActivatedRouteSnapshot } from '@angular/router';
|
||||
import { AddonCompetencyCourseCompetenciesSource } from '@addons/competency/classes/competency-course-competencies-source';
|
||||
import { CoreTime } from '@singletons/time';
|
||||
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
|
||||
|
@ -350,8 +350,10 @@ class AddonCompetencyCompetenciesSwipeManager
|
|||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot): string | null {
|
||||
return route.params.competencyId;
|
||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot | ActivatedRoute): string | null {
|
||||
const snapshot = route instanceof ActivatedRouteSnapshot ? route : route.snapshot;
|
||||
|
||||
return snapshot.params.competencyId;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -280,7 +280,6 @@ Feature: Test basic usage of messages in app
|
|||
Then I should find "Teacher teacher" in the app
|
||||
And I should find "Student1 student1" in the app
|
||||
|
||||
@ionic7_failure
|
||||
Scenario: User blocking feature
|
||||
Given I entered the course "Course 1" as "student2" in the app
|
||||
When I press "Participants" in the app
|
||||
|
@ -318,7 +317,6 @@ Feature: Test basic usage of messages in app
|
|||
Then I should find "test message" in the app
|
||||
But I should not find "You are unable to message this user" in the app
|
||||
|
||||
@ionic7_failure
|
||||
Scenario: Mute Unmute conversations
|
||||
Given I entered the course "Course 1" as "student1" in the app
|
||||
When I press "Participants" in the app
|
||||
|
|
|
@ -245,8 +245,10 @@ class AddonModAssignSubmissionSwipeItemsManager extends CoreSwipeNavigationItems
|
|||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot): string | null {
|
||||
return route.params.submitId;
|
||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot | ActivatedRoute): string | null {
|
||||
const snapshot = route instanceof ActivatedRouteSnapshot ? route : route.snapshot;
|
||||
|
||||
return snapshot.params.submitId;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot } from '@angular/router';
|
||||
import { ActivatedRoute, ActivatedRouteSnapshot } from '@angular/router';
|
||||
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
|
||||
import { CoreSwipeNavigationItemsManager } from '@classes/items-management/swipe-navigation-items-manager';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
|
@ -187,8 +187,10 @@ class AddonModFeedbackAttemptsSwipeManager extends CoreSwipeNavigationItemsManag
|
|||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot): string | null {
|
||||
return route.params.attemptId;
|
||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot | ActivatedRoute): string | null {
|
||||
const snapshot = route instanceof ActivatedRouteSnapshot ? route : route.snapshot;
|
||||
|
||||
return snapshot.params.attemptId;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -893,8 +893,10 @@ class AddonModForumDiscussionDiscussionsSwipeManager extends AddonModForumDiscus
|
|||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot): string | null {
|
||||
return this.getSource().DISCUSSIONS_PATH_PREFIX + route.params.discussionId;
|
||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot | ActivatedRoute): string | null {
|
||||
const snapshot = route instanceof ActivatedRouteSnapshot ? route : route.snapshot;
|
||||
|
||||
return this.getSource().DISCUSSIONS_PATH_PREFIX + snapshot.params.discussionId;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -699,8 +699,10 @@ class AddonModForumNewDiscussionDiscussionsSwipeManager extends AddonModForumDis
|
|||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot): string | null {
|
||||
return `${this.getSource().DISCUSSIONS_PATH_PREFIX}new/${route.params.timeCreated}`;
|
||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot | ActivatedRoute): string | null {
|
||||
const snapshot = route instanceof ActivatedRouteSnapshot ? route : route.snapshot;
|
||||
|
||||
return `${this.getSource().DISCUSSIONS_PATH_PREFIX}new/${snapshot.params.timeCreated}`;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -367,8 +367,10 @@ class AddonModGlossaryEntryEntriesSwipeManager
|
|||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot): string | null {
|
||||
return `${this.getSource().GLOSSARY_PATH_PREFIX}entry/${route.params.entrySlug}`;
|
||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot | ActivatedRoute): string | null {
|
||||
const snapshot = route instanceof ActivatedRouteSnapshot ? route : route.snapshot;
|
||||
|
||||
return `${this.getSource().GLOSSARY_PATH_PREFIX}entry/${snapshot.params.entrySlug}`;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -211,7 +211,6 @@ Feature: Test glossary navigation
|
|||
And I should find "Cashew" in the app
|
||||
And I should find "Acerola" in the app
|
||||
|
||||
@ci_jenkins_skip @ionic7_failure
|
||||
Scenario: Tablet navigation on glossary
|
||||
Given I entered the course "Course 1" as "student1" in the app
|
||||
And I change viewport size to "1200x640" in the app
|
||||
|
|
|
@ -20,7 +20,7 @@ import {
|
|||
AddonNotificationsHelper,
|
||||
} from '@addons/notifications/services/notifications-helper';
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot } from '@angular/router';
|
||||
import { ActivatedRoute, ActivatedRouteSnapshot } from '@angular/router';
|
||||
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
|
||||
import { CoreSwipeNavigationItemsManager } from '@classes/items-management/swipe-navigation-items-manager';
|
||||
import { CoreContentLinksAction, CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate';
|
||||
|
@ -211,8 +211,10 @@ class AddonNotificationSwipeItemsManager extends CoreSwipeNavigationItemsManager
|
|||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot): string | null {
|
||||
return route.params.id;
|
||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot | ActivatedRoute): string | null {
|
||||
const snapshot = route instanceof ActivatedRouteSnapshot ? route : route.snapshot;
|
||||
|
||||
return snapshot.params.id;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -169,6 +169,20 @@ export function conditionalRoutes(routes: Routes, condition: () => boolean): Rou
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a route does not have any content.
|
||||
*
|
||||
* @param route Route.
|
||||
* @returns Whether the route doesn't have any content.
|
||||
*/
|
||||
export function isEmptyRoute(route: Route): boolean {
|
||||
return !('component' in route)
|
||||
&& !('loadComponent' in route)
|
||||
&& !('children' in route)
|
||||
&& !('loadChildren' in route)
|
||||
&& !('redirectTo' in route);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve module routes.
|
||||
*
|
||||
|
|
|
@ -239,13 +239,15 @@ export class CoreListItemsManager<
|
|||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot): string | null {
|
||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot | ActivatedRoute): string | null {
|
||||
const segments: UrlSegment[] = [];
|
||||
|
||||
while (route.firstChild) {
|
||||
route = route.firstChild;
|
||||
|
||||
segments.push(...route.url);
|
||||
const snapshot = route instanceof ActivatedRouteSnapshot ? route : route.snapshot;
|
||||
|
||||
segments.push(...snapshot.url);
|
||||
}
|
||||
|
||||
return segments.map(segment => segment.path).join('/').replace(/\/+/, '/').trim() || null;
|
||||
|
|
|
@ -55,7 +55,7 @@ export abstract class CoreRoutedItemsManager<
|
|||
* @param route Page route.
|
||||
* @returns Path of the selected item in the given route.
|
||||
*/
|
||||
protected abstract getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot): string | null;
|
||||
protected abstract getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot | ActivatedRoute): string | null;
|
||||
|
||||
/**
|
||||
* Get the path of the selected item.
|
||||
|
@ -63,7 +63,7 @@ export abstract class CoreRoutedItemsManager<
|
|||
* @param route Page route, if any.
|
||||
* @returns Path of the selected item.
|
||||
*/
|
||||
protected getSelectedItemPath(route?: ActivatedRouteSnapshot | null): string | null {
|
||||
protected getSelectedItemPath(route?: ActivatedRouteSnapshot | ActivatedRoute | null): string | null {
|
||||
if (!route) {
|
||||
return null;
|
||||
}
|
||||
|
@ -76,14 +76,12 @@ export abstract class CoreRoutedItemsManager<
|
|||
*
|
||||
* @param route Current route.
|
||||
*/
|
||||
protected updateSelectedItem(route: ActivatedRouteSnapshot | null = null): void {
|
||||
route = route ?? this.getCurrentPageRoute()?.snapshot ?? null;
|
||||
protected updateSelectedItem(route: ActivatedRouteSnapshot | ActivatedRoute | null = null): void {
|
||||
route = route ?? this.getCurrentPageRoute() ?? null;
|
||||
|
||||
const selectedItemPath = this.getSelectedItemPath(route);
|
||||
const selectedItem = selectedItemPath ? (this.itemsMap?.[selectedItemPath] ?? null) : null;
|
||||
|
||||
const selectedItem = selectedItemPath
|
||||
? this.itemsMap?.[selectedItemPath] ?? null
|
||||
: null;
|
||||
this.setSelectedItem(selectedItem);
|
||||
}
|
||||
|
||||
|
@ -106,7 +104,7 @@ export abstract class CoreRoutedItemsManager<
|
|||
|
||||
// If this item is already selected, do nothing.
|
||||
const itemPath = this.getSource().getItemPath(item);
|
||||
const selectedItemPath = this.getSelectedItemPath(route.snapshot);
|
||||
const selectedItemPath = this.getSelectedItemPath(route);
|
||||
|
||||
if (selectedItemPath === itemPath) {
|
||||
return;
|
||||
|
@ -135,7 +133,7 @@ export abstract class CoreRoutedItemsManager<
|
|||
}
|
||||
|
||||
// If the current page is already the index, do nothing.
|
||||
const selectedItemPath = this.getSelectedItemPath(route.snapshot);
|
||||
const selectedItemPath = this.getSelectedItemPath(route);
|
||||
|
||||
if (selectedItemPath === null) {
|
||||
return;
|
||||
|
|
|
@ -81,11 +81,13 @@ export class CoreSwipeNavigationItemsManager<
|
|||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot): string | null {
|
||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot | ActivatedRoute): string | null {
|
||||
const segments: UrlSegment[] = [];
|
||||
|
||||
while (route) {
|
||||
segments.push(...route.url);
|
||||
const snapshot = route instanceof ActivatedRouteSnapshot ? route : route.snapshot;
|
||||
|
||||
segments.push(...snapshot.url);
|
||||
|
||||
if (!route.firstChild) {
|
||||
break;
|
||||
|
|
|
@ -330,8 +330,10 @@ class CoreGradesCourseParticipantsSwipeManager extends CoreSwipeNavigationItemsM
|
|||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot): string | null {
|
||||
return route.params.userId;
|
||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot | ActivatedRoute): string | null {
|
||||
const snapshot = route instanceof ActivatedRouteSnapshot ? route : route.snapshot;
|
||||
|
||||
return snapshot.params.userId;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
import { InjectionToken, Injector, ModuleWithProviders, NgModule } from '@angular/core';
|
||||
import { Route, Routes } from '@angular/router';
|
||||
|
||||
import { ModuleRoutesConfig, resolveModuleRoutes } from '@/app/app-routing.module';
|
||||
import { ModuleRoutesConfig, isEmptyRoute, resolveModuleRoutes } from '@/app/app-routing.module';
|
||||
|
||||
const MAIN_MENU_TAB_ROUTES = new InjectionToken('MAIN_MENU_TAB_ROUTES');
|
||||
const modulesPaths: Record<string, Set<string>> = {};
|
||||
|
@ -71,6 +71,8 @@ export function buildTabMainRoutes(injector: Injector, mainRoute: Route): Routes
|
|||
if (isRootRoute && !('redirectTo' in mainRoute)) {
|
||||
mainRoute.children = mainRoute.children || [];
|
||||
mainRoute.children = mainRoute.children.concat(routes.children);
|
||||
} else if (isEmptyRoute(mainRoute)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return isRootRoute
|
||||
|
|
|
@ -4,4 +4,4 @@
|
|||
</div>
|
||||
|
||||
<core-reminders-set-button *ngIf="showReminderButton" slot="end" [component]="component" [instanceId]="instanceId" [type]="type"
|
||||
[label]="label" [timebefore]="timebefore" [time]="time" [title]="title" [url]="url" />
|
||||
[label]="label" [initialTimebefore]="timebefore" [time]="time" [title]="title" [url]="url" />
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<ion-button fill="clear" size="small" (click)="setReminder($event)"
|
||||
[attr.aria-label]="'core.reminders.setareminderfor' | translate : { title: title, label: labelClean }"
|
||||
[attr.aria-checked]="timebefore !== undefined">
|
||||
[attr.aria-label]="'core.reminders.setareminderfor' | translate : { title: title, label: labelClean }">
|
||||
<ion-icon name="fas-bell" slot="icon-only" *ngIf="timebefore !== undefined" aria-hidden="true" />
|
||||
<ion-icon name="far-bell-slash" slot="icon-only" *ngIf="timebefore === undefined" aria-hidden="true" />
|
||||
</ion-button>
|
||||
|
||||
<span class="sr-only" role="status" *ngIf="reminderMessage">{{ reminderMessage }}</span>
|
||||
|
|
|
@ -32,18 +32,22 @@ export class CoreRemindersSetButtonComponent implements OnInit {
|
|||
@Input() instanceId?: number;
|
||||
@Input() type?: string;
|
||||
@Input() label = '';
|
||||
@Input() timebefore?: number;
|
||||
@Input() initialTimebefore?: number;
|
||||
@Input() time = -1;
|
||||
@Input() title = '';
|
||||
@Input() url = '';
|
||||
|
||||
labelClean = '';
|
||||
timebefore?: number;
|
||||
reminderMessage?: string;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.labelClean = this.label.replace(':', '');
|
||||
|
||||
this.setTimebefore(this.initialTimebefore);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -86,6 +90,23 @@ export class CoreRemindersSetButtonComponent implements OnInit {
|
|||
this.saveReminder(reminderTime.timeBefore);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update time before.
|
||||
*/
|
||||
setTimebefore(timebefore: number | undefined): void {
|
||||
this.timebefore = timebefore;
|
||||
|
||||
if (this.timebefore !== undefined) {
|
||||
const reminderTime = this.time - this.timebefore;
|
||||
|
||||
this.reminderMessage = Translate.instant('core.reminders.reminderset', {
|
||||
$a: CoreTimeUtils.userDate(reminderTime * 1000),
|
||||
});
|
||||
} else {
|
||||
this.reminderMessage = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save reminder.
|
||||
*
|
||||
|
@ -105,18 +126,18 @@ export class CoreRemindersSetButtonComponent implements OnInit {
|
|||
});
|
||||
|
||||
if (timebefore === undefined || timebefore === CoreRemindersService.DISABLED) {
|
||||
this.timebefore = undefined;
|
||||
this.setTimebefore(undefined);
|
||||
CoreDomUtils.showToast('core.reminders.reminderunset', true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.timebefore = timebefore;
|
||||
this.setTimebefore(timebefore);
|
||||
|
||||
const reminder: CoreReminderData = {
|
||||
timebefore,
|
||||
component: this.component,
|
||||
instanceId: this.instanceId,
|
||||
timebefore: this.timebefore,
|
||||
type: this.type,
|
||||
title: this.label + ' ' + this.title,
|
||||
url: this.url,
|
||||
|
|
|
@ -16,52 +16,48 @@ Feature: Set a new reminder on activity
|
|||
| assign | C1 | assign01 | Assignment 01 | ## yesterday ## | ## now +70 minutes ## |
|
||||
| assign | C1 | assign02 | Assignment 02 | ## yesterday ## | ## 1 January 2050 ## |
|
||||
|
||||
@ionic7_failure
|
||||
Scenario: Add, delete and update reminder on activity
|
||||
Given I entered the assign activity "Assignment 01" on course "Course 1" as "student1" in the app
|
||||
|
||||
Then I should not find "Set a reminder for \"Assignment 01\" (Opened)" in the app
|
||||
And I should find "Set a reminder for \"Assignment 01\" (Due)" in the app
|
||||
And "Set a reminder for \"Assignment 01\" (Due)" should not be selected in the app
|
||||
And I should not find "Reminder set for" in the app
|
||||
But I should find "Set a reminder for \"Assignment 01\" (Due)" in the app
|
||||
|
||||
# Default set
|
||||
When I press "Set a reminder for \"Assignment 01\" (Due)" in the app
|
||||
Then I should find "Reminder set for " in the app
|
||||
And "Set a reminder for \"Assignment 01\" (Due)" should be selected in the app
|
||||
Then I should find "Reminder set for" in the app
|
||||
|
||||
# Set from list
|
||||
When I press "Set a reminder for \"Assignment 01\" (Due)" 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 "1 hour before" should not be selected in the app
|
||||
But "1 hour before" should not be selected in the app
|
||||
When I press "1 hour before" in the app
|
||||
Then I should find "Reminder set for " in the app
|
||||
And "Set a reminder for \"Assignment 01\" (Due)" should be selected in the app
|
||||
Then I should find "Reminder set for" in the app
|
||||
|
||||
# Custom set
|
||||
When I press "Set a reminder for \"Assignment 01\" (Due)" 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 "1 hour before" should be selected in the app
|
||||
But "At the time of the event" should not be selected in the app
|
||||
When I press "Custom..." in the app
|
||||
Then I should find "Custom reminder" in the app
|
||||
When I set the following fields to these values in the app:
|
||||
| Value | 4 |
|
||||
| Units | minutes |
|
||||
And I press "Set reminder" in the app
|
||||
Then I should find "Reminder set for " in the app
|
||||
And "Set a reminder for \"Assignment 01\" (Due)" should be selected in the app
|
||||
Then I should find "Reminder set for" in the app
|
||||
|
||||
# Remove
|
||||
When I press "Set a reminder for \"Assignment 01\" (Due)" in the app
|
||||
Then "4 minutes before" should be selected in the app
|
||||
When I press "Delete reminder" in the app
|
||||
Then I should find "Reminder deleted" in the app
|
||||
And "Set a reminder for \"Assignment 01\" (Due)" should not be selected in the app
|
||||
But I should not find "Reminder set for" in the app
|
||||
|
||||
# Set and check reminder
|
||||
When I press "Set a reminder for \"Assignment 01\" (Due)" in the app
|
||||
Then I should find "Reminder set for " in the app
|
||||
Then I should find "Reminder set for" in the app
|
||||
When I press "Set a reminder for \"Assignment 01\" (Due)" in the app
|
||||
And I press "Custom..." in the app
|
||||
Then I should find "Custom reminder" in the app
|
||||
|
@ -69,7 +65,7 @@ Feature: Set a new reminder on activity
|
|||
| Value | 69 |
|
||||
| Units | minutes |
|
||||
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
|
||||
When I wait "50" seconds
|
||||
Then a notification with title "Due: Assignment 01" is present in the app
|
||||
And I close a notification with title "Due: Assignment 01" in the app
|
||||
|
@ -82,9 +78,9 @@ Feature: Set a new reminder on activity
|
|||
| Value | 68 |
|
||||
| Units | minutes |
|
||||
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
|
||||
When I press "Set a reminder for \"Assignment 01\" (Due)" in the app
|
||||
Then I should find "Reminder set for " in the app
|
||||
Then I should find "Reminder set for" in the app
|
||||
When I press "Delete reminder" in the app
|
||||
Then I should find "Reminder deleted" in the app
|
||||
When I wait "50" seconds
|
||||
|
|
|
@ -12,34 +12,33 @@ Feature: Set a new reminder on course
|
|||
| user | course | role |
|
||||
| student1 | C1 | student |
|
||||
|
||||
@ionic7_failure
|
||||
Scenario: Add, delete and update reminder on course
|
||||
Given I entered the course "Course 1" as "student1" in the app
|
||||
And I press "Course summary" in the app
|
||||
|
||||
Then I should not find "Set a reminder for \"Course 1\" (Course start date)" in the app
|
||||
And I should find "Set a reminder for \"Course 1\" (Course end date)" in the app
|
||||
And "Set a reminder for \"Course 1\" (Course end date)" should not be selected in the app
|
||||
And I should not find "Reminder set for" in the app
|
||||
But I should find "Set a reminder for \"Course 1\" (Course end date)" in the app
|
||||
|
||||
# Default set
|
||||
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
|
||||
And "Set a reminder for \"Course 1\" (Course end date)" should be selected in the app
|
||||
And I should find "Reminder set for" in the app
|
||||
|
||||
# Set from list
|
||||
When I press "Set a reminder for \"Course 1\" (Course end date)" 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 "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
|
||||
Then I should find "Reminder set for " in the app
|
||||
And "Set a reminder for \"Course 1\" (Course end date)" should be selected in the app
|
||||
And I should find "Reminder set for" in the app
|
||||
|
||||
# Custom set
|
||||
When I press "Set a reminder for \"Course 1\" (Course end date)" 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 "12 hours before" should be selected in the app
|
||||
But "12 hours before" should be selected in the app
|
||||
When I press "Custom..." in the app
|
||||
Then I should find "Custom reminder" in the app
|
||||
When I set the following fields to these values in the app:
|
||||
|
@ -47,11 +46,11 @@ Feature: Set a new reminder on course
|
|||
| Units | hours |
|
||||
And I press "Set reminder" in the app
|
||||
Then I should find "Reminder set for " in the app
|
||||
And "Set a reminder for \"Course 1\" (Course end date)" should be selected in the app
|
||||
And I should find "Reminder set for" in the app
|
||||
|
||||
# Remove
|
||||
When I press "Set a reminder for \"Course 1\" (Course end date)" in the app
|
||||
Then "2 hours before" should be selected in the app
|
||||
When I press "Delete reminder" in the app
|
||||
Then I should find "Reminder deleted" in the app
|
||||
And "Set a reminder for \"Course 1\" (Course end date)" should not be selected in the app
|
||||
But I should not find "Reminder set for" in the app
|
||||
|
|
|
@ -253,8 +253,10 @@ class CoreUserSwipeItemsManager extends CoreSwipeNavigationItemsManager {
|
|||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot): string | null {
|
||||
return route.params.userId;
|
||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot | ActivatedRoute): string | null {
|
||||
const snapshot = route instanceof ActivatedRouteSnapshot ? route : route.snapshot;
|
||||
|
||||
return snapshot.params.userId;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -276,7 +276,7 @@ export class TestingBehatDomUtilsService {
|
|||
/**
|
||||
* Given a list of elements, get the top ancestors among all of them.
|
||||
*
|
||||
* This will remote duplicates and drop any elements nested within each other.
|
||||
* This will remove duplicates and drop any elements nested within each other.
|
||||
*
|
||||
* @param elements Elements list.
|
||||
* @returns Top ancestors.
|
||||
|
@ -480,6 +480,34 @@ export class TestingBehatDomUtilsService {
|
|||
return this.findElementsBasedOnText(locator, options)[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until an element with the given selector is found.
|
||||
*
|
||||
* @param selector Element selector.
|
||||
* @param timeout Timeout after which an error is thrown.
|
||||
* @param retryFrequency Frequency for retries when the element is not found.
|
||||
* @returns Element.
|
||||
*/
|
||||
async waitForElement<T extends HTMLElement = HTMLElement>(
|
||||
selector: string,
|
||||
timeout: number = 2000,
|
||||
retryFrequency: number = 100,
|
||||
): Promise<T> {
|
||||
const element = document.querySelector<T>(selector);
|
||||
|
||||
if (!element) {
|
||||
if (timeout < retryFrequency) {
|
||||
throw new Error(`Element with '${selector}' selector not found`);
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, retryFrequency));
|
||||
|
||||
return this.waitForElement<T>(selector, timeout - retryFrequency, retryFrequency);
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to find elements based on their text or Aria label.
|
||||
*
|
||||
|
@ -515,7 +543,7 @@ export class TestingBehatDomUtilsService {
|
|||
protected findElementsBasedOnTextInContainer(
|
||||
locator: TestingBehatElementLocator,
|
||||
topContainer: HTMLElement,
|
||||
options: TestingBehatFindOptions,
|
||||
options: TestingBehatFindOptions = {},
|
||||
): HTMLElement[] {
|
||||
let container: HTMLElement | null = topContainer;
|
||||
|
||||
|
@ -667,37 +695,26 @@ export class TestingBehatDomUtilsService {
|
|||
}
|
||||
|
||||
/**
|
||||
* Set an element value.
|
||||
* Set an input element value.
|
||||
*
|
||||
* @param element HTML to set.
|
||||
* @param value Value to be set.
|
||||
* @param element Input element.
|
||||
* @param value Value.
|
||||
*/
|
||||
async setElementValue(element: HTMLInputElement | HTMLElement, value: string): Promise<void> {
|
||||
async setInputValue(element: HTMLInputElement | HTMLElement, value: string): Promise<void> {
|
||||
await NgZone.run(async () => {
|
||||
const promise = new CorePromisedValue<void>();
|
||||
|
||||
// Functions to get/set value depending on field type.
|
||||
const setValue = (text: string) => {
|
||||
if (! ('value' in element)) {
|
||||
element.innerHTML = text;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const setValue = async (text: string) => {
|
||||
if (element.tagName === 'ION-SELECT') {
|
||||
value = value.trim();
|
||||
const optionValue = Array.from(element.querySelectorAll('ion-select-option'))
|
||||
.find((option) => option.innerHTML.trim() === value);
|
||||
|
||||
if (optionValue) {
|
||||
element.value = optionValue.value;
|
||||
}
|
||||
} else {
|
||||
this.setIonSelectInputValue(element, value);
|
||||
} else if ('value' in element) {
|
||||
element.value = text;
|
||||
} else {
|
||||
element.innerHTML = text;
|
||||
}
|
||||
|
||||
element.dispatchEvent(new Event('ionChange'));
|
||||
};
|
||||
|
||||
const getValue = () => {
|
||||
if ('value' in element) {
|
||||
return element.value;
|
||||
|
@ -707,38 +724,79 @@ export class TestingBehatDomUtilsService {
|
|||
};
|
||||
|
||||
// Pretend we have cut and pasted the new text.
|
||||
let event: InputEvent;
|
||||
if (getValue() !== '') {
|
||||
event = new InputEvent('input', {
|
||||
if (element.tagName !== 'ION-SELECT' && getValue() !== '') {
|
||||
await CoreUtils.nextTick();
|
||||
await setValue('');
|
||||
|
||||
element.dispatchEvent(new InputEvent('input', {
|
||||
bubbles: true,
|
||||
view: window,
|
||||
cancelable: true,
|
||||
inputType: 'deleteByCut',
|
||||
});
|
||||
|
||||
await CoreUtils.nextTick();
|
||||
setValue('');
|
||||
element.dispatchEvent(event);
|
||||
}));
|
||||
}
|
||||
|
||||
if (value !== '') {
|
||||
event = new InputEvent('input', {
|
||||
await CoreUtils.nextTick();
|
||||
await setValue(value);
|
||||
|
||||
element.dispatchEvent(new InputEvent('input', {
|
||||
bubbles: true,
|
||||
view: window,
|
||||
cancelable: true,
|
||||
inputType: 'insertFromPaste',
|
||||
data: value,
|
||||
});
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await CoreUtils.nextTick();
|
||||
setValue(value);
|
||||
element.dispatchEvent(event);
|
||||
/**
|
||||
* Select an option in an ion-select element.
|
||||
*
|
||||
* @param element IonSelect element.
|
||||
* @param value Value.
|
||||
*/
|
||||
protected async setIonSelectInputValue(element: HTMLElement, value: string): Promise<void> {
|
||||
// Press select.
|
||||
await TestingBehatDomUtils.pressElement(element);
|
||||
|
||||
// Press option.
|
||||
type IonSelectInterface = 'alert' | 'action-sheet' | 'popover';
|
||||
const selectInterface = element.getAttribute('interface') as IonSelectInterface ?? 'alert';
|
||||
const containerSelector = ({
|
||||
'alert': 'ion-alert.select-alert',
|
||||
'action-sheet': 'ion-action-sheet.select-action-sheet',
|
||||
'popover': 'ion-popover.select-popover',
|
||||
})[selectInterface];
|
||||
const optionSelector = ({
|
||||
'alert': 'button',
|
||||
'action-sheet': 'button',
|
||||
'popover': 'ion-radio',
|
||||
})[selectInterface] ?? '';
|
||||
const optionsContainer = await TestingBehatDomUtils.waitForElement(containerSelector);
|
||||
const options = this.findElementsBasedOnTextInContainer(
|
||||
{ text: value, selector: optionSelector },
|
||||
optionsContainer,
|
||||
{},
|
||||
);
|
||||
|
||||
if (options.length === 0) {
|
||||
throw new Error('Couldn\'t find ion-select option.');
|
||||
}
|
||||
|
||||
await TestingBehatDomUtils.pressElement(options[0]);
|
||||
|
||||
// Press options submit.
|
||||
if (selectInterface === 'alert') {
|
||||
const submitButton = optionsContainer.querySelector<HTMLElement>('.alert-button-group button:last-child');
|
||||
|
||||
if (!submitButton) {
|
||||
throw new Error('Couldn\'t find ion-select submit button.');
|
||||
}
|
||||
|
||||
promise.resolve();
|
||||
|
||||
return promise;
|
||||
});
|
||||
await TestingBehatDomUtils.pressElement(submitButton);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { TestingBehatDomUtils } from './behat-dom';
|
||||
import { TestingBehatDomUtils, TestingBehatDomUtilsService } from './behat-dom';
|
||||
import { TestingBehatBlocking } from './behat-blocking';
|
||||
import { CoreCustomURLSchemes, CoreCustomURLSchemesProvider } from '@services/urlschemes';
|
||||
import { ONBOARDING_DONE } from '@features/login/constants';
|
||||
|
@ -63,6 +63,10 @@ export class TestingBehatRuntimeService {
|
|||
return CoreNavigator.instance;
|
||||
}
|
||||
|
||||
get domUtils(): TestingBehatDomUtilsService {
|
||||
return TestingBehatDomUtils.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init behat functions and set options like skipping onboarding.
|
||||
*
|
||||
|
@ -468,11 +472,22 @@ export class TestingBehatRuntimeService {
|
|||
?? options.find(option => option.text === value)?.value
|
||||
?? options.find(option => option.text.includes(value))?.value
|
||||
?? value;
|
||||
} else if (input.tagName === 'ION-SELECT') {
|
||||
const options = Array.from(input.querySelectorAll('ion-select-option'));
|
||||
|
||||
value = options.find(option => option.value?.toString() === value)?.textContent?.trim()
|
||||
?? options.find(option => option.textContent?.trim() === value)?.textContent?.trim()
|
||||
?? options.find(option => option.textContent?.includes(value))?.textContent?.trim()
|
||||
?? value;
|
||||
}
|
||||
|
||||
await TestingBehatDomUtils.setElementValue(input, value);
|
||||
try {
|
||||
await TestingBehatDomUtils.setInputValue(input, value);
|
||||
|
||||
return 'OK';
|
||||
return 'OK';
|
||||
} catch (error) {
|
||||
return `ERROR: ${error.message ?? 'Unknown error'}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue