commit
97df9fb152
|
@ -6,7 +6,7 @@ on:
|
||||||
behat_tags:
|
behat_tags:
|
||||||
description: 'Behat tags to execute'
|
description: 'Behat tags to execute'
|
||||||
required: true
|
required: true
|
||||||
default: '~@performance&&~@ionic7_failure'
|
default: '~@performance'
|
||||||
moodle_branch:
|
moodle_branch:
|
||||||
description: 'Moodle branch'
|
description: 'Moodle branch'
|
||||||
required: true
|
required: true
|
||||||
|
@ -27,7 +27,7 @@ jobs:
|
||||||
MOODLE_DOCKER_PHP_VERSION: '8.1'
|
MOODLE_DOCKER_PHP_VERSION: '8.1'
|
||||||
MOODLE_BRANCH: ${{ github.event.inputs.moodle_branch || 'main' }}
|
MOODLE_BRANCH: ${{ github.event.inputs.moodle_branch || 'main' }}
|
||||||
MOODLE_REPOSITORY: ${{ github.event.inputs.moodle_repository || 'https://github.com/moodle/moodle' }}
|
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:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- 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.
|
* 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$/
|
* @Given /^I set the field "((?:[^"]|\\")+)" to "((?:[^"]|\\")*)" in the app$/
|
||||||
* @param string $field Text identifying field
|
* @param string $field Text identifying the field.
|
||||||
* @param string $value Value for 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
|
* @throws DriverException If the field set doesn't work.
|
||||||
*/
|
*/
|
||||||
public function i_set_the_field_in_the_app(string $field, string $value) {
|
public function i_set_the_field_in_the_app(string $field, string $value) {
|
||||||
$field = addslashes_js($field);
|
$field = addslashes_js($field);
|
||||||
|
|
|
@ -639,8 +639,10 @@ class AddonCalendarEventsSwipeItemsManager extends CoreSwipeNavigationItemsManag
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot): string | null {
|
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot | ActivatedRoute): string | null {
|
||||||
return route.params.id;
|
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 { CoreSwipeNavigationItemsManager } from '@classes/items-management/swipe-navigation-items-manager';
|
||||||
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
|
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
|
||||||
import { AddonCompetencyPlanCompetenciesSource } from '@addons/competency/classes/competency-plan-competencies-source';
|
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 { AddonCompetencyCourseCompetenciesSource } from '@addons/competency/classes/competency-course-competencies-source';
|
||||||
import { CoreTime } from '@singletons/time';
|
import { CoreTime } from '@singletons/time';
|
||||||
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
|
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
|
||||||
|
@ -350,8 +350,10 @@ class AddonCompetencyCompetenciesSwipeManager
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot): string | null {
|
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot | ActivatedRoute): string | null {
|
||||||
return route.params.competencyId;
|
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
|
Then I should find "Teacher teacher" in the app
|
||||||
And I should find "Student1 student1" in the app
|
And I should find "Student1 student1" in the app
|
||||||
|
|
||||||
@ionic7_failure
|
|
||||||
Scenario: User blocking feature
|
Scenario: User blocking feature
|
||||||
Given I entered the course "Course 1" as "student2" in the app
|
Given I entered the course "Course 1" as "student2" in the app
|
||||||
When I press "Participants" 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
|
Then I should find "test message" in the app
|
||||||
But I should not find "You are unable to message this user" in the app
|
But I should not find "You are unable to message this user" in the app
|
||||||
|
|
||||||
@ionic7_failure
|
|
||||||
Scenario: Mute Unmute conversations
|
Scenario: Mute Unmute conversations
|
||||||
Given I entered the course "Course 1" as "student1" in the app
|
Given I entered the course "Course 1" as "student1" in the app
|
||||||
When I press "Participants" in the app
|
When I press "Participants" in the app
|
||||||
|
|
|
@ -245,8 +245,10 @@ class AddonModAssignSubmissionSwipeItemsManager extends CoreSwipeNavigationItems
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot): string | null {
|
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot | ActivatedRoute): string | null {
|
||||||
return route.params.submitId;
|
const snapshot = route instanceof ActivatedRouteSnapshot ? route : route.snapshot;
|
||||||
|
|
||||||
|
return snapshot.params.submitId;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
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 { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
|
||||||
import { CoreSwipeNavigationItemsManager } from '@classes/items-management/swipe-navigation-items-manager';
|
import { CoreSwipeNavigationItemsManager } from '@classes/items-management/swipe-navigation-items-manager';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
@ -187,8 +187,10 @@ class AddonModFeedbackAttemptsSwipeManager extends CoreSwipeNavigationItemsManag
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot): string | null {
|
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot | ActivatedRoute): string | null {
|
||||||
return route.params.attemptId;
|
const snapshot = route instanceof ActivatedRouteSnapshot ? route : route.snapshot;
|
||||||
|
|
||||||
|
return snapshot.params.attemptId;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -893,8 +893,10 @@ class AddonModForumDiscussionDiscussionsSwipeManager extends AddonModForumDiscus
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot): string | null {
|
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot | ActivatedRoute): string | null {
|
||||||
return this.getSource().DISCUSSIONS_PATH_PREFIX + route.params.discussionId;
|
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
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot): string | null {
|
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot | ActivatedRoute): string | null {
|
||||||
return `${this.getSource().DISCUSSIONS_PATH_PREFIX}new/${route.params.timeCreated}`;
|
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
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot): string | null {
|
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot | ActivatedRoute): string | null {
|
||||||
return `${this.getSource().GLOSSARY_PATH_PREFIX}entry/${route.params.entrySlug}`;
|
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 "Cashew" in the app
|
||||||
And I should find "Acerola" in the app
|
And I should find "Acerola" in the app
|
||||||
|
|
||||||
@ci_jenkins_skip @ionic7_failure
|
|
||||||
Scenario: Tablet navigation on glossary
|
Scenario: Tablet navigation on glossary
|
||||||
Given I entered the course "Course 1" as "student1" in the app
|
Given I entered the course "Course 1" as "student1" in the app
|
||||||
And I change viewport size to "1200x640" in the app
|
And I change viewport size to "1200x640" in the app
|
||||||
|
|
|
@ -20,7 +20,7 @@ import {
|
||||||
AddonNotificationsHelper,
|
AddonNotificationsHelper,
|
||||||
} from '@addons/notifications/services/notifications-helper';
|
} from '@addons/notifications/services/notifications-helper';
|
||||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
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 { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
|
||||||
import { CoreSwipeNavigationItemsManager } from '@classes/items-management/swipe-navigation-items-manager';
|
import { CoreSwipeNavigationItemsManager } from '@classes/items-management/swipe-navigation-items-manager';
|
||||||
import { CoreContentLinksAction, CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate';
|
import { CoreContentLinksAction, CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate';
|
||||||
|
@ -211,8 +211,10 @@ class AddonNotificationSwipeItemsManager extends CoreSwipeNavigationItemsManager
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot): string | null {
|
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot | ActivatedRoute): string | null {
|
||||||
return route.params.id;
|
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.
|
* Resolve module routes.
|
||||||
*
|
*
|
||||||
|
|
|
@ -239,13 +239,15 @@ export class CoreListItemsManager<
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot): string | null {
|
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot | ActivatedRoute): string | null {
|
||||||
const segments: UrlSegment[] = [];
|
const segments: UrlSegment[] = [];
|
||||||
|
|
||||||
while (route.firstChild) {
|
while (route.firstChild) {
|
||||||
route = 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;
|
return segments.map(segment => segment.path).join('/').replace(/\/+/, '/').trim() || null;
|
||||||
|
|
|
@ -55,7 +55,7 @@ export abstract class CoreRoutedItemsManager<
|
||||||
* @param route Page route.
|
* @param route Page route.
|
||||||
* @returns Path of the selected item in the given 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.
|
* Get the path of the selected item.
|
||||||
|
@ -63,7 +63,7 @@ export abstract class CoreRoutedItemsManager<
|
||||||
* @param route Page route, if any.
|
* @param route Page route, if any.
|
||||||
* @returns Path of the selected item.
|
* @returns Path of the selected item.
|
||||||
*/
|
*/
|
||||||
protected getSelectedItemPath(route?: ActivatedRouteSnapshot | null): string | null {
|
protected getSelectedItemPath(route?: ActivatedRouteSnapshot | ActivatedRoute | null): string | null {
|
||||||
if (!route) {
|
if (!route) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -76,14 +76,12 @@ export abstract class CoreRoutedItemsManager<
|
||||||
*
|
*
|
||||||
* @param route Current route.
|
* @param route Current route.
|
||||||
*/
|
*/
|
||||||
protected updateSelectedItem(route: ActivatedRouteSnapshot | null = null): void {
|
protected updateSelectedItem(route: ActivatedRouteSnapshot | ActivatedRoute | null = null): void {
|
||||||
route = route ?? this.getCurrentPageRoute()?.snapshot ?? null;
|
route = route ?? this.getCurrentPageRoute() ?? null;
|
||||||
|
|
||||||
const selectedItemPath = this.getSelectedItemPath(route);
|
const selectedItemPath = this.getSelectedItemPath(route);
|
||||||
|
const selectedItem = selectedItemPath ? (this.itemsMap?.[selectedItemPath] ?? null) : null;
|
||||||
|
|
||||||
const selectedItem = selectedItemPath
|
|
||||||
? this.itemsMap?.[selectedItemPath] ?? null
|
|
||||||
: null;
|
|
||||||
this.setSelectedItem(selectedItem);
|
this.setSelectedItem(selectedItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,7 +104,7 @@ export abstract class CoreRoutedItemsManager<
|
||||||
|
|
||||||
// If this item is already selected, do nothing.
|
// If this item is already selected, do nothing.
|
||||||
const itemPath = this.getSource().getItemPath(item);
|
const itemPath = this.getSource().getItemPath(item);
|
||||||
const selectedItemPath = this.getSelectedItemPath(route.snapshot);
|
const selectedItemPath = this.getSelectedItemPath(route);
|
||||||
|
|
||||||
if (selectedItemPath === itemPath) {
|
if (selectedItemPath === itemPath) {
|
||||||
return;
|
return;
|
||||||
|
@ -135,7 +133,7 @@ export abstract class CoreRoutedItemsManager<
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the current page is already the index, do nothing.
|
// If the current page is already the index, do nothing.
|
||||||
const selectedItemPath = this.getSelectedItemPath(route.snapshot);
|
const selectedItemPath = this.getSelectedItemPath(route);
|
||||||
|
|
||||||
if (selectedItemPath === null) {
|
if (selectedItemPath === null) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -81,11 +81,13 @@ export class CoreSwipeNavigationItemsManager<
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot): string | null {
|
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot | ActivatedRoute): string | null {
|
||||||
const segments: UrlSegment[] = [];
|
const segments: UrlSegment[] = [];
|
||||||
|
|
||||||
while (route) {
|
while (route) {
|
||||||
segments.push(...route.url);
|
const snapshot = route instanceof ActivatedRouteSnapshot ? route : route.snapshot;
|
||||||
|
|
||||||
|
segments.push(...snapshot.url);
|
||||||
|
|
||||||
if (!route.firstChild) {
|
if (!route.firstChild) {
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -330,8 +330,10 @@ class CoreGradesCourseParticipantsSwipeManager extends CoreSwipeNavigationItemsM
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot): string | null {
|
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot | ActivatedRoute): string | null {
|
||||||
return route.params.userId;
|
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 { InjectionToken, Injector, ModuleWithProviders, NgModule } from '@angular/core';
|
||||||
import { Route, Routes } from '@angular/router';
|
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 MAIN_MENU_TAB_ROUTES = new InjectionToken('MAIN_MENU_TAB_ROUTES');
|
||||||
const modulesPaths: Record<string, Set<string>> = {};
|
const modulesPaths: Record<string, Set<string>> = {};
|
||||||
|
@ -71,6 +71,8 @@ export function buildTabMainRoutes(injector: Injector, mainRoute: Route): Routes
|
||||||
if (isRootRoute && !('redirectTo' in mainRoute)) {
|
if (isRootRoute && !('redirectTo' in mainRoute)) {
|
||||||
mainRoute.children = mainRoute.children || [];
|
mainRoute.children = mainRoute.children || [];
|
||||||
mainRoute.children = mainRoute.children.concat(routes.children);
|
mainRoute.children = mainRoute.children.concat(routes.children);
|
||||||
|
} else if (isEmptyRoute(mainRoute)) {
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return isRootRoute
|
return isRootRoute
|
||||||
|
|
|
@ -4,4 +4,4 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<core-reminders-set-button *ngIf="showReminderButton" slot="end" [component]="component" [instanceId]="instanceId" [type]="type"
|
<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)"
|
<ion-button fill="clear" size="small" (click)="setReminder($event)"
|
||||||
[attr.aria-label]="'core.reminders.setareminderfor' | translate : { title: title, label: labelClean }"
|
[attr.aria-label]="'core.reminders.setareminderfor' | translate : { title: title, label: labelClean }">
|
||||||
[attr.aria-checked]="timebefore !== undefined">
|
|
||||||
<ion-icon name="fas-bell" slot="icon-only" *ngIf="timebefore !== undefined" aria-hidden="true" />
|
<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-icon name="far-bell-slash" slot="icon-only" *ngIf="timebefore === undefined" aria-hidden="true" />
|
||||||
</ion-button>
|
</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() instanceId?: number;
|
||||||
@Input() type?: string;
|
@Input() type?: string;
|
||||||
@Input() label = '';
|
@Input() label = '';
|
||||||
@Input() timebefore?: number;
|
@Input() initialTimebefore?: number;
|
||||||
@Input() time = -1;
|
@Input() time = -1;
|
||||||
@Input() title = '';
|
@Input() title = '';
|
||||||
@Input() url = '';
|
@Input() url = '';
|
||||||
|
|
||||||
labelClean = '';
|
labelClean = '';
|
||||||
|
timebefore?: number;
|
||||||
|
reminderMessage?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.labelClean = this.label.replace(':', '');
|
this.labelClean = this.label.replace(':', '');
|
||||||
|
|
||||||
|
this.setTimebefore(this.initialTimebefore);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -86,6 +90,23 @@ export class CoreRemindersSetButtonComponent implements OnInit {
|
||||||
this.saveReminder(reminderTime.timeBefore);
|
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.
|
* Save reminder.
|
||||||
*
|
*
|
||||||
|
@ -105,18 +126,18 @@ export class CoreRemindersSetButtonComponent implements OnInit {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (timebefore === undefined || timebefore === CoreRemindersService.DISABLED) {
|
if (timebefore === undefined || timebefore === CoreRemindersService.DISABLED) {
|
||||||
this.timebefore = undefined;
|
this.setTimebefore(undefined);
|
||||||
CoreDomUtils.showToast('core.reminders.reminderunset', true);
|
CoreDomUtils.showToast('core.reminders.reminderunset', true);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.timebefore = timebefore;
|
this.setTimebefore(timebefore);
|
||||||
|
|
||||||
const reminder: CoreReminderData = {
|
const reminder: CoreReminderData = {
|
||||||
|
timebefore,
|
||||||
component: this.component,
|
component: this.component,
|
||||||
instanceId: this.instanceId,
|
instanceId: this.instanceId,
|
||||||
timebefore: this.timebefore,
|
|
||||||
type: this.type,
|
type: this.type,
|
||||||
title: this.label + ' ' + this.title,
|
title: this.label + ' ' + this.title,
|
||||||
url: this.url,
|
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 | assign01 | Assignment 01 | ## yesterday ## | ## now +70 minutes ## |
|
||||||
| assign | C1 | assign02 | Assignment 02 | ## yesterday ## | ## 1 January 2050 ## |
|
| assign | C1 | assign02 | Assignment 02 | ## yesterday ## | ## 1 January 2050 ## |
|
||||||
|
|
||||||
@ionic7_failure
|
|
||||||
Scenario: Add, delete and update reminder on activity
|
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
|
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
|
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 I should not find "Reminder set for" in the app
|
||||||
And "Set a reminder for \"Assignment 01\" (Due)" should not be selected in the app
|
But I should find "Set a reminder for \"Assignment 01\" (Due)" in the app
|
||||||
|
|
||||||
# Default set
|
# Default set
|
||||||
When I press "Set a reminder for \"Assignment 01\" (Due)" 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
|
||||||
And "Set a reminder for \"Assignment 01\" (Due)" should be selected in the app
|
|
||||||
|
|
||||||
# Set from list
|
# Set from list
|
||||||
When I press "Set a reminder for \"Assignment 01\" (Due)" in the app
|
When I press "Set a reminder for \"Assignment 01\" (Due)" 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
|
||||||
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
|
When I press "1 hour 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 "Set a reminder for \"Assignment 01\" (Due)" should be selected in the app
|
|
||||||
|
|
||||||
# Custom set
|
# Custom set
|
||||||
When I press "Set a reminder for \"Assignment 01\" (Due)" in the app
|
When I press "Set a reminder for \"Assignment 01\" (Due)" 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 "1 hour before" should 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
|
When I press "Custom..." in the app
|
||||||
Then I should find "Custom reminder" in the app
|
Then I should find "Custom reminder" in the app
|
||||||
When I set the following fields to these values in the app:
|
When I set the following fields to these values in the app:
|
||||||
| Value | 4 |
|
| Value | 4 |
|
||||||
| Units | minutes |
|
| Units | minutes |
|
||||||
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 "Set a reminder for \"Assignment 01\" (Due)" should be selected in the app
|
|
||||||
|
|
||||||
# Remove
|
# Remove
|
||||||
When I press "Set a reminder for \"Assignment 01\" (Due)" in the app
|
When I press "Set a reminder for \"Assignment 01\" (Due)" in the app
|
||||||
Then "4 minutes before" should be selected in the app
|
Then "4 minutes 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
|
||||||
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
|
# Set and check reminder
|
||||||
When I press "Set a reminder for \"Assignment 01\" (Due)" 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 "Set a reminder for \"Assignment 01\" (Due)" in the app
|
When I press "Set a reminder for \"Assignment 01\" (Due)" in the app
|
||||||
And I press "Custom..." in the app
|
And I press "Custom..." in the app
|
||||||
Then I should find "Custom reminder" 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 |
|
| Value | 69 |
|
||||||
| Units | minutes |
|
| Units | minutes |
|
||||||
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
|
||||||
When I wait "50" seconds
|
When I wait "50" seconds
|
||||||
Then a notification with title "Due: Assignment 01" is present in the app
|
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
|
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 |
|
| Value | 68 |
|
||||||
| Units | minutes |
|
| Units | minutes |
|
||||||
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
|
||||||
When I press "Set a reminder for \"Assignment 01\" (Due)" 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
|
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
|
||||||
When I wait "50" seconds
|
When I wait "50" seconds
|
||||||
|
|
|
@ -12,34 +12,33 @@ Feature: Set a new reminder on course
|
||||||
| user | course | role |
|
| user | course | role |
|
||||||
| student1 | C1 | student |
|
| student1 | C1 | student |
|
||||||
|
|
||||||
@ionic7_failure
|
|
||||||
Scenario: Add, delete and update reminder on course
|
Scenario: Add, delete and update reminder on course
|
||||||
Given I entered the course "Course 1" as "student1" in the app
|
Given I entered the course "Course 1" as "student1" in the app
|
||||||
And I press "Course summary" 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
|
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 I should not find "Reminder set for" in the app
|
||||||
And "Set a reminder for \"Course 1\" (Course end date)" should not be selected in the app
|
But I should find "Set a reminder for \"Course 1\" (Course end date)" in the app
|
||||||
|
|
||||||
# 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 "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
|
# Set from list
|
||||||
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 "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
|
||||||
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
|
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 "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
|
# Custom 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 "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
|
||||||
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
|
When I press "Custom..." in the app
|
||||||
Then I should find "Custom reminder" in the app
|
Then I should find "Custom reminder" in the app
|
||||||
When I set the following fields to these values 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 |
|
| 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 "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
|
# Remove
|
||||||
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 "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
|
||||||
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
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot): string | null {
|
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot | ActivatedRoute): string | null {
|
||||||
return route.params.userId;
|
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.
|
* 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.
|
* @param elements Elements list.
|
||||||
* @returns Top ancestors.
|
* @returns Top ancestors.
|
||||||
|
@ -480,6 +480,34 @@ export class TestingBehatDomUtilsService {
|
||||||
return this.findElementsBasedOnText(locator, options)[0];
|
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.
|
* Function to find elements based on their text or Aria label.
|
||||||
*
|
*
|
||||||
|
@ -515,7 +543,7 @@ export class TestingBehatDomUtilsService {
|
||||||
protected findElementsBasedOnTextInContainer(
|
protected findElementsBasedOnTextInContainer(
|
||||||
locator: TestingBehatElementLocator,
|
locator: TestingBehatElementLocator,
|
||||||
topContainer: HTMLElement,
|
topContainer: HTMLElement,
|
||||||
options: TestingBehatFindOptions,
|
options: TestingBehatFindOptions = {},
|
||||||
): HTMLElement[] {
|
): HTMLElement[] {
|
||||||
let container: HTMLElement | null = topContainer;
|
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 element Input element.
|
||||||
* @param value Value to be set.
|
* @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 () => {
|
await NgZone.run(async () => {
|
||||||
const promise = new CorePromisedValue<void>();
|
|
||||||
|
|
||||||
// Functions to get/set value depending on field type.
|
// Functions to get/set value depending on field type.
|
||||||
const setValue = (text: string) => {
|
const setValue = async (text: string) => {
|
||||||
if (! ('value' in element)) {
|
|
||||||
element.innerHTML = text;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (element.tagName === 'ION-SELECT') {
|
if (element.tagName === 'ION-SELECT') {
|
||||||
value = value.trim();
|
this.setIonSelectInputValue(element, value);
|
||||||
const optionValue = Array.from(element.querySelectorAll('ion-select-option'))
|
} else if ('value' in element) {
|
||||||
.find((option) => option.innerHTML.trim() === value);
|
|
||||||
|
|
||||||
if (optionValue) {
|
|
||||||
element.value = optionValue.value;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
element.value = text;
|
element.value = text;
|
||||||
|
} else {
|
||||||
|
element.innerHTML = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
element.dispatchEvent(new Event('ionChange'));
|
element.dispatchEvent(new Event('ionChange'));
|
||||||
};
|
};
|
||||||
|
|
||||||
const getValue = () => {
|
const getValue = () => {
|
||||||
if ('value' in element) {
|
if ('value' in element) {
|
||||||
return element.value;
|
return element.value;
|
||||||
|
@ -707,38 +724,79 @@ export class TestingBehatDomUtilsService {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Pretend we have cut and pasted the new text.
|
// Pretend we have cut and pasted the new text.
|
||||||
let event: InputEvent;
|
if (element.tagName !== 'ION-SELECT' && getValue() !== '') {
|
||||||
if (getValue() !== '') {
|
await CoreUtils.nextTick();
|
||||||
event = new InputEvent('input', {
|
await setValue('');
|
||||||
|
|
||||||
|
element.dispatchEvent(new InputEvent('input', {
|
||||||
bubbles: true,
|
bubbles: true,
|
||||||
view: window,
|
view: window,
|
||||||
cancelable: true,
|
cancelable: true,
|
||||||
inputType: 'deleteByCut',
|
inputType: 'deleteByCut',
|
||||||
});
|
}));
|
||||||
|
|
||||||
await CoreUtils.nextTick();
|
|
||||||
setValue('');
|
|
||||||
element.dispatchEvent(event);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value !== '') {
|
if (value !== '') {
|
||||||
event = new InputEvent('input', {
|
await CoreUtils.nextTick();
|
||||||
|
await setValue(value);
|
||||||
|
|
||||||
|
element.dispatchEvent(new InputEvent('input', {
|
||||||
bubbles: true,
|
bubbles: true,
|
||||||
view: window,
|
view: window,
|
||||||
cancelable: true,
|
cancelable: true,
|
||||||
inputType: 'insertFromPaste',
|
inputType: 'insertFromPaste',
|
||||||
data: value,
|
data: value,
|
||||||
});
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
await CoreUtils.nextTick();
|
/**
|
||||||
setValue(value);
|
* Select an option in an ion-select element.
|
||||||
element.dispatchEvent(event);
|
*
|
||||||
|
* @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();
|
await TestingBehatDomUtils.pressElement(submitButton);
|
||||||
|
}
|
||||||
return promise;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { TestingBehatDomUtils } from './behat-dom';
|
import { TestingBehatDomUtils, TestingBehatDomUtilsService } from './behat-dom';
|
||||||
import { TestingBehatBlocking } from './behat-blocking';
|
import { TestingBehatBlocking } from './behat-blocking';
|
||||||
import { CoreCustomURLSchemes, CoreCustomURLSchemesProvider } from '@services/urlschemes';
|
import { CoreCustomURLSchemes, CoreCustomURLSchemesProvider } from '@services/urlschemes';
|
||||||
import { ONBOARDING_DONE } from '@features/login/constants';
|
import { ONBOARDING_DONE } from '@features/login/constants';
|
||||||
|
@ -63,6 +63,10 @@ export class TestingBehatRuntimeService {
|
||||||
return CoreNavigator.instance;
|
return CoreNavigator.instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get domUtils(): TestingBehatDomUtilsService {
|
||||||
|
return TestingBehatDomUtils.instance;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Init behat functions and set options like skipping onboarding.
|
* 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 === value)?.value
|
||||||
?? options.find(option => option.text.includes(value))?.value
|
?? options.find(option => option.text.includes(value))?.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