forked from EVOgeek/Vmeda.Online
		
	Merge pull request #3877 from NoelDeMartin/MOBILE-3947
MOBILE-3947: Fix application startup and behat gestures
This commit is contained in:
		
						commit
						3b1f1fb638
					
				
							
								
								
									
										4
									
								
								.github/workflows/acceptance.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/acceptance.yml
									
									
									
									
										vendored
									
									
								
							| @ -16,7 +16,7 @@ on: | |||||||
|         required: true |         required: true | ||||||
|         default: 'https://github.com/moodle/moodle' |         default: 'https://github.com/moodle/moodle' | ||||||
|   pull_request: |   pull_request: | ||||||
|     branches: [ main, v*.x ] |     branches: [ main, ionic7, v*.x ] | ||||||
| 
 | 
 | ||||||
| jobs: | jobs: | ||||||
|   behat: |   behat: | ||||||
| @ -24,7 +24,7 @@ jobs: | |||||||
|     env: |     env: | ||||||
|       MOODLE_DOCKER_DB: pgsql |       MOODLE_DOCKER_DB: pgsql | ||||||
|       MOODLE_DOCKER_BROWSER: chrome |       MOODLE_DOCKER_BROWSER: chrome | ||||||
|       MOODLE_DOCKER_PHP_VERSION: '8.0' |       MOODLE_DOCKER_PHP_VERSION: '8.1' | ||||||
|       MOODLE_BRANCH: ${{ github.event.inputs.moodle_branch || 'master' }} |       MOODLE_BRANCH: ${{ github.event.inputs.moodle_branch || 'master' }} | ||||||
|       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' }} |       BEHAT_TAGS: ${{ github.event.inputs.behat_tags || '~@performance' }} | ||||||
|  | |||||||
| @ -214,10 +214,7 @@ class behat_app extends behat_app_helper { | |||||||
|             return true; |             return true; | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         $this->wait_for_pending_js(); |         $this->wait_animations_done(); | ||||||
| 
 |  | ||||||
|         // Wait scroll animation to finish.
 |  | ||||||
|         $this->getSession()->wait(300); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -263,10 +260,7 @@ class behat_app extends behat_app_helper { | |||||||
|             throw new DriverException('Error when swiping - ' . $result); |             throw new DriverException('Error when swiping - ' . $result); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         $this->wait_for_pending_js(); |         $this->wait_animations_done(); | ||||||
| 
 |  | ||||||
|         // Wait swipe animation to finish.
 |  | ||||||
|         $this->getSession()->wait(300); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -689,10 +683,7 @@ class behat_app extends behat_app_helper { | |||||||
|                 return true; |                 return true; | ||||||
|             }); |             }); | ||||||
| 
 | 
 | ||||||
|             $this->wait_for_pending_js(); |             $this->wait_animations_done(); | ||||||
| 
 |  | ||||||
|             // Wait for UI to settle after refreshing.
 |  | ||||||
|             $this->getSession()->wait(300); |  | ||||||
| 
 | 
 | ||||||
|             if (is_null($locator)) { |             if (is_null($locator)) { | ||||||
|                 return; |                 return; | ||||||
|  | |||||||
| @ -641,4 +641,15 @@ EOF; | |||||||
|             return $text; |             return $text; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Wait until animations have finished. | ||||||
|  |      */ | ||||||
|  |     protected function wait_animations_done() { | ||||||
|  |         $this->wait_for_pending_js(); | ||||||
|  | 
 | ||||||
|  |         // Ideally, we wouldn't wait a fixed amount of time. But it is not straightforward to wait for animations
 | ||||||
|  |         // to finish, so for now we'll just wait 300ms.
 | ||||||
|  |         usleep(300000); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -27,6 +27,8 @@ import { | |||||||
| 
 | 
 | ||||||
| import { CoreArray } from '@singletons/array'; | import { CoreArray } from '@singletons/array'; | ||||||
| 
 | 
 | ||||||
|  | const modulesRoutes: WeakMap<InjectionToken<unknown>, ModuleRoutes> = new WeakMap(); | ||||||
|  | 
 | ||||||
| /** | /** | ||||||
|  * Build app routes. |  * Build app routes. | ||||||
|  * |  * | ||||||
| @ -175,6 +177,10 @@ export function conditionalRoutes(routes: Routes, condition: () => boolean): Rou | |||||||
|  * @returns Routes. |  * @returns Routes. | ||||||
|  */ |  */ | ||||||
| export function resolveModuleRoutes(injector: Injector, token: InjectionToken<ModuleRoutesConfig[]>): ModuleRoutes { | export function resolveModuleRoutes(injector: Injector, token: InjectionToken<ModuleRoutesConfig[]>): ModuleRoutes { | ||||||
|  |     if (modulesRoutes.has(token)) { | ||||||
|  |         return modulesRoutes.get(token) as ModuleRoutes; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     const configs = injector.get(token, []); |     const configs = injector.get(token, []); | ||||||
|     const routes = configs.map(config => { |     const routes = configs.map(config => { | ||||||
|         if (Array.isArray(config)) { |         if (Array.isArray(config)) { | ||||||
| @ -190,10 +196,14 @@ export function resolveModuleRoutes(injector: Injector, token: InjectionToken<Mo | |||||||
|         }; |         }; | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     return { |     const moduleRoutes = { | ||||||
|         children: CoreArray.flatten(routes.map(r => r.children)), |         children: CoreArray.flatten(routes.map(r => r.children)), | ||||||
|         siblings: CoreArray.flatten(routes.map(r => r.siblings)), |         siblings: CoreArray.flatten(routes.map(r => r.siblings)), | ||||||
|     }; |     }; | ||||||
|  | 
 | ||||||
|  |     modulesRoutes.set(token, moduleRoutes); | ||||||
|  | 
 | ||||||
|  |     return moduleRoutes; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const APP_ROUTES = new InjectionToken('APP_ROUTES'); | export const APP_ROUTES = new InjectionToken('APP_ROUTES'); | ||||||
|  | |||||||
| @ -1,5 +1,4 @@ | |||||||
| <swiper-container #swiperRef *ngIf="loaded" (slidechangetransitionstart)="slideWillChange()" (slidechangetransitionend)="slideDidChange()" | <swiper-container #swiperRef *ngIf="loaded"> | ||||||
|     [initialSlide]="options.initialSlide" [runCallbacksOnInit]="options.runCallbacksOnInit"> |  | ||||||
|     <swiper-slide *ngFor="let item of items; index as index" [attr.aria-hidden]="!isActive(index)"> |     <swiper-slide *ngFor="let item of items; index as index" [attr.aria-hidden]="!isActive(index)"> | ||||||
|         <ng-container *ngIf="template" [ngTemplateOutlet]="template" [ngTemplateOutletContext]="{item: item, active: isActive(index)}" /> |         <ng-container *ngIf="template" [ngTemplateOutlet]="template" [ngTemplateOutletContext]="{item: item, active: isActive(index)}" /> | ||||||
|     </swiper-slide> |     </swiper-slide> | ||||||
|  | |||||||
| @ -50,6 +50,13 @@ export class CoreSwipeSlidesComponent<Item = unknown> implements OnChanges, OnDe | |||||||
|             if (swiperRef?.nativeElement?.swiper) { |             if (swiperRef?.nativeElement?.swiper) { | ||||||
|                 this.swiper = swiperRef.nativeElement.swiper as Swiper; |                 this.swiper = swiperRef.nativeElement.swiper as Swiper; | ||||||
| 
 | 
 | ||||||
|  |                 if (this.options.initialSlide) { | ||||||
|  |                     this.swiper.slideTo(this.options.initialSlide, 0, this.options.runCallbacksOnInit); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 this.swiper.on('slideChangeTransitionStart', () => this.slideWillChange()); | ||||||
|  |                 this.swiper.on('slideChangeTransitionEnd', () => this.slideDidChange()); | ||||||
|  | 
 | ||||||
|                 Object.keys(this.options).forEach((key) => { |                 Object.keys(this.options).forEach((key) => { | ||||||
|                     if (this.swiper) { |                     if (this.swiper) { | ||||||
|                         this.swiper.params[key] = this.options[key]; |                         this.swiper.params[key] = this.options[key]; | ||||||
|  | |||||||
| @ -14,8 +14,15 @@ | |||||||
| 
 | 
 | ||||||
| import envJson from '@/assets/env.json'; | import envJson from '@/assets/env.json'; | ||||||
| import { EnvironmentConfig } from '@/types/config'; | import { EnvironmentConfig } from '@/types/config'; | ||||||
|  | import { InjectionToken } from '@angular/core'; | ||||||
| import { CoreBrowser } from '@singletons/browser'; | import { CoreBrowser } from '@singletons/browser'; | ||||||
| 
 | 
 | ||||||
|  | /** | ||||||
|  |  * Injection token used for dependencies marked as optional that will never | ||||||
|  |  * be resolved by Angular injectors. | ||||||
|  |  */ | ||||||
|  | export const NULL_INJECTION_TOKEN = new InjectionToken('null'); | ||||||
|  | 
 | ||||||
| /** | /** | ||||||
|  * Context levels enumeration. |  * Context levels enumeration. | ||||||
|  */ |  */ | ||||||
|  | |||||||
| @ -12,6 +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 { CoreConstants } from '@/core/constants'; | ||||||
| import {  AfterViewInit, Directive, ElementRef, Input, OnDestroy } from '@angular/core'; | import {  AfterViewInit, Directive, ElementRef, Input, OnDestroy } from '@angular/core'; | ||||||
| import { CoreSwipeNavigationItemsManager } from '@classes/items-management/swipe-navigation-items-manager'; | import { CoreSwipeNavigationItemsManager } from '@classes/items-management/swipe-navigation-items-manager'; | ||||||
| import { CoreSwipeNavigationTourComponent } from '@components/swipe-navigation-tour/swipe-navigation-tour'; | import { CoreSwipeNavigationTourComponent } from '@components/swipe-navigation-tour/swipe-navigation-tour'; | ||||||
| @ -42,6 +43,11 @@ export class CoreSwipeNavigationDirective implements AfterViewInit, OnDestroy { | |||||||
| 
 | 
 | ||||||
|     constructor(el: ElementRef) { |     constructor(el: ElementRef) { | ||||||
|         this.element = el.nativeElement; |         this.element = el.nativeElement; | ||||||
|  | 
 | ||||||
|  |         if (CoreConstants.enableDevTools()) { | ||||||
|  |             this.element['swipeNavigation'] = this; | ||||||
|  |             this.element.classList.add('uses-swipe-navigation'); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     get enabled(): boolean { |     get enabled(): boolean { | ||||||
|  | |||||||
| @ -36,10 +36,6 @@ function buildRoutes(injector: Injector): Routes { | |||||||
|             path: '', |             path: '', | ||||||
|             component: CoreMainMenuPage, |             component: CoreMainMenuPage, | ||||||
|             children: [ |             children: [ | ||||||
|                 { |  | ||||||
|                     path: '', |  | ||||||
|                     pathMatch: 'full', |  | ||||||
|                 }, |  | ||||||
|                 { |                 { | ||||||
|                     path: CoreMainMenuHomeHandlerService.PAGE_NAME, |                     path: CoreMainMenuHomeHandlerService.PAGE_NAME, | ||||||
|                     loadChildren: () => import('./mainmenu-home-lazy.module').then(m => m.CoreMainMenuHomeLazyModule), |                     loadChildren: () => import('./mainmenu-home-lazy.module').then(m => m.CoreMainMenuHomeLazyModule), | ||||||
|  | |||||||
| @ -18,24 +18,64 @@ import { Route, Routes } from '@angular/router'; | |||||||
| import { ModuleRoutesConfig, resolveModuleRoutes } from '@/app/app-routing.module'; | import { ModuleRoutesConfig, 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>> = {}; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Get the name of the module the injector belongs to. | ||||||
|  |  * | ||||||
|  |  * @param injector Injector. | ||||||
|  |  * @returns Injector module name. | ||||||
|  |  */ | ||||||
|  | function getInjectorModule(injector: Injector): string | null { | ||||||
|  |     if (!('source' in injector) || typeof injector.source !== 'string') { | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Get module name from R3Injector source.
 | ||||||
|  |     // See https://github.com/angular/angular/blob/16.2.0/packages/core/src/di/r3_injector.ts#L161C8
 | ||||||
|  |     return injector.source; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Get module paths. | ||||||
|  |  * | ||||||
|  |  * @param injector Injector. | ||||||
|  |  * @returns Module paths. | ||||||
|  |  */ | ||||||
|  | function getModulePaths(injector: Injector): Set<string> | null { | ||||||
|  |     const module = getInjectorModule(injector); | ||||||
|  | 
 | ||||||
|  |     if (!module) { | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return modulesPaths[module] ??= new Set(); | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Build module routes. |  * Build module routes. | ||||||
|  * |  * | ||||||
|  * @param injector Injector. |  * @param injector Injector. | ||||||
|  |  * @param mainRoute Main route. | ||||||
|  * @returns Routes. |  * @returns Routes. | ||||||
|  */ |  */ | ||||||
| export function buildTabMainRoutes(injector: Injector, mainRoute: Route): Routes { | export function buildTabMainRoutes(injector: Injector, mainRoute: Route): Routes { | ||||||
|  |     const path = mainRoute.path ?? ''; | ||||||
|  |     const modulePaths = getModulePaths(injector); | ||||||
|  |     const isRootRoute = modulePaths && !modulePaths.has(path); | ||||||
|     const routes = resolveModuleRoutes(injector, MAIN_MENU_TAB_ROUTES); |     const routes = resolveModuleRoutes(injector, MAIN_MENU_TAB_ROUTES); | ||||||
| 
 | 
 | ||||||
|     mainRoute.path = mainRoute.path || ''; |     mainRoute.path = path; | ||||||
|     mainRoute.children = mainRoute.children || []; |     modulePaths?.add(path); | ||||||
|     mainRoute.children = mainRoute.children.concat(routes.children); |  | ||||||
| 
 | 
 | ||||||
|     return [ |     if (isRootRoute && !('redirectTo' in mainRoute)) { | ||||||
|         mainRoute, |         mainRoute.children = mainRoute.children || []; | ||||||
|         ...routes.siblings, |         mainRoute.children = mainRoute.children.concat(routes.children); | ||||||
|     ]; |     } | ||||||
|  | 
 | ||||||
|  |     return isRootRoute | ||||||
|  |         ? [mainRoute, ...routes.siblings] | ||||||
|  |         : [mainRoute]; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @NgModule() | @NgModule() | ||||||
|  | |||||||
| @ -213,7 +213,7 @@ export class CoreSettingsDeviceInfoPage implements OnDestroy { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const showDevOptionsOnConfig = await CoreConfig.get('showDevOptions', 0); |         const showDevOptionsOnConfig = await CoreConfig.get('showDevOptions', 0); | ||||||
|         this.devOptionsForced = CoreConstants.BUILD.isDevelopment || CoreConstants.BUILD.isTesting; |         this.devOptionsForced = CoreConstants.enableDevTools(); | ||||||
|         this.showDevOptions = this.devOptionsForced || showDevOptionsOnConfig == 1; |         this.showDevOptions = this.devOptionsForced || showDevOptionsOnConfig == 1; | ||||||
| 
 | 
 | ||||||
|         const publicKey = this.deviceInfo.pushId ? |         const publicKey = this.deviceInfo.pushId ? | ||||||
|  | |||||||
| @ -24,6 +24,7 @@ import { SQLiteDB } from '@classes/sqlitedb'; | |||||||
| import { APP_SCHEMA, CoreStorageRecord, TABLE_NAME } from './database/storage'; | import { APP_SCHEMA, CoreStorageRecord, TABLE_NAME } from './database/storage'; | ||||||
| import { CoreSites } from './sites'; | import { CoreSites } from './sites'; | ||||||
| import { CoreSite } from '@classes/sites/site'; | import { CoreSite } from '@classes/sites/site'; | ||||||
|  | import { NULL_INJECTION_TOKEN } from '@/core/constants'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Service to store data using key-value pairs. |  * Service to store data using key-value pairs. | ||||||
| @ -38,7 +39,7 @@ export class CoreStorageService { | |||||||
| 
 | 
 | ||||||
|     table: AsyncInstance<CoreStorageTable>; |     table: AsyncInstance<CoreStorageTable>; | ||||||
| 
 | 
 | ||||||
|     constructor(@Optional() @Inject(null) lazyTableConstructor?: () => Promise<CoreStorageTable>) { |     constructor(@Optional() @Inject(NULL_INJECTION_TOKEN) lazyTableConstructor?: () => Promise<CoreStorageTable>) { | ||||||
|         this.table = asyncInstance(lazyTableConstructor); |         this.table = asyncInstance(lazyTableConstructor); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -404,17 +404,13 @@ export class TestingBehatRuntimeService { | |||||||
|         this.log('Action - pullToRefresh'); |         this.log('Action - pullToRefresh'); | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             // 'el' is protected, but there's no other way to trigger refresh programatically.
 |             const ionRefresher = this.getElement('ion-refresher'); | ||||||
|             const ionRefresher = this.getAngularInstance<{ el: HTMLIonRefresherElement }>( |  | ||||||
|                 'ion-refresher', |  | ||||||
|                 'IonRefresher', |  | ||||||
|             ); |  | ||||||
| 
 | 
 | ||||||
|             if (!ionRefresher) { |             if (!ionRefresher) { | ||||||
|                 return 'ERROR: It\'s not possible to pull to refresh the current page.'; |                 return 'ERROR: It\'s not possible to pull to refresh the current page.'; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             ionRefresher.el.dispatchEvent(new CustomEvent('ionRefresh')); |             ionRefresher.dispatchEvent(new CustomEvent('ionRefresh')); | ||||||
| 
 | 
 | ||||||
|             return 'OK'; |             return 'OK'; | ||||||
|         } catch (error) { |         } catch (error) { | ||||||
| @ -521,20 +517,13 @@ export class TestingBehatRuntimeService { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Get an Angular component instance. |      * Get element instance. | ||||||
|      * |      * | ||||||
|      * @param selector Element selector |      * @param selector Element selector. | ||||||
|      * @param className Constructor class name |  | ||||||
|      * @param referenceLocator The locator to the reference element to start looking for. If not specified, document body. |      * @param referenceLocator The locator to the reference element to start looking for. If not specified, document body. | ||||||
|      * @returns Component instance |      * @returns Element instance. | ||||||
|      */ |      */ | ||||||
|     getAngularInstance<T = unknown>( |     private getElement<T = Element>(selector: string, referenceLocator?: TestingBehatElementLocator): T | null { | ||||||
|         selector: string, |  | ||||||
|         className: string, |  | ||||||
|         referenceLocator?: TestingBehatElementLocator, |  | ||||||
|     ): T | null { |  | ||||||
|         this.log('Action - Get Angular instance ' + selector + ', ' + className, referenceLocator); |  | ||||||
| 
 |  | ||||||
|         let startingElement: HTMLElement | undefined = document.body; |         let startingElement: HTMLElement | undefined = document.body; | ||||||
|         let queryPrefix = ''; |         let queryPrefix = ''; | ||||||
| 
 | 
 | ||||||
| @ -552,15 +541,8 @@ export class TestingBehatRuntimeService { | |||||||
|             queryPrefix = '.ion-page:not(.ion-page-hidden) '; |             queryPrefix = '.ion-page:not(.ion-page-hidden) '; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // eslint-disable-next-line @typescript-eslint/no-explicit-any
 |         return Array.from(startingElement.querySelectorAll(`${queryPrefix}${selector}`)).pop() as T | ||||||
|         const activeElement = Array.from(startingElement.querySelectorAll<any>(`${queryPrefix}${selector}`)).pop() ?? |             ?? startingElement.closest(selector) as T; | ||||||
|             startingElement.closest(selector); |  | ||||||
| 
 |  | ||||||
|         if (!activeElement || !activeElement.__ngContext__) { |  | ||||||
|             return null; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return activeElement.__ngContext__.find(node => node?.constructor?.name === className); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -632,26 +614,26 @@ export class TestingBehatRuntimeService { | |||||||
| 
 | 
 | ||||||
|         if (locator) { |         if (locator) { | ||||||
|             // Locator specified, try to find swiper-container first.
 |             // Locator specified, try to find swiper-container first.
 | ||||||
|             const instance = this.getAngularInstance<Swiper>('swiper-container', 'Swiper', locator); |             const swiperContainer = this.getElement<{ swiper: Swiper }>('swiper-container', locator); | ||||||
|             if (instance) { | 
 | ||||||
|                 direction === 'left' ? instance.slideNext() : instance.slidePrev(); |             if (swiperContainer) { | ||||||
|  |                 direction === 'left' ? swiperContainer.swiper.slideNext() : swiperContainer.swiper.slidePrev(); | ||||||
| 
 | 
 | ||||||
|                 return 'OK'; |                 return 'OK'; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // No locator specified or swiper-container not found, search swipe navigation now.
 |         // No locator specified or swiper-container not found, search swipe navigation now.
 | ||||||
|         const instance = this.getAngularInstance<CoreSwipeNavigationDirective>( |         const ionContent = this.getElement<{ swipeNavigation: CoreSwipeNavigationDirective }>( | ||||||
|             'ion-content', |             'ion-content.uses-swipe-navigation', | ||||||
|             'CoreSwipeNavigationDirective', |  | ||||||
|             locator, |             locator, | ||||||
|         ); |         ); | ||||||
| 
 | 
 | ||||||
|         if (!instance) { |         if (!ionContent) { | ||||||
|             return 'ERROR: Element to swipe not found.'; |             return 'ERROR: Element to swipe not found.'; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         direction === 'left' ? instance.swipeLeft() : instance.swipeRight(); |         direction === 'left' ? ionContent.swipeNavigation.swipeLeft() : ionContent.swipeNavigation.swipeRight(); | ||||||
| 
 | 
 | ||||||
|         return 'OK'; |         return 'OK'; | ||||||
|     } |     } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user