fix(vue): improve swipe to go back reliability (#22288)

resolves #22237
This commit is contained in:
Liam DeBeasi
2020-10-12 11:32:28 -04:00
committed by GitHub
parent 16cf98ffbd
commit c74fd4147b
7 changed files with 149 additions and 40 deletions

View File

@ -10,14 +10,13 @@ import {
InjectionKey InjectionKey
} from 'vue'; } from 'vue';
import { AnimationBuilder } from '@ionic/core'; import { AnimationBuilder } from '@ionic/core';
import { useRoute, useRouter } from 'vue-router'; import { useRoute } from 'vue-router';
import { fireLifecycle, generateId, LIFECYCLE_DID_ENTER, LIFECYCLE_DID_LEAVE, LIFECYCLE_WILL_ENTER, LIFECYCLE_WILL_LEAVE } from '../utils'; import { fireLifecycle, generateId, LIFECYCLE_DID_ENTER, LIFECYCLE_DID_LEAVE, LIFECYCLE_WILL_ENTER, LIFECYCLE_WILL_LEAVE } from '../utils';
let viewDepthKey: InjectionKey<0> = Symbol(0); let viewDepthKey: InjectionKey<0> = Symbol(0);
export const IonRouterOutlet = defineComponent({ export const IonRouterOutlet = defineComponent({
name: 'IonRouterOutlet', name: 'IonRouterOutlet',
setup(_, { attrs }) { setup(_, { attrs }) {
const vueRouter = useRouter();
const route = useRoute(); const route = useRoute();
const depth = inject(viewDepthKey, 0); const depth = inject(viewDepthKey, 0);
@ -57,13 +56,22 @@ export const IonRouterOutlet = defineComponent({
const canStart = () => { const canStart = () => {
const stack = viewStacks.getViewStack(id); const stack = viewStacks.getViewStack(id);
return stack && stack.length > 1; if (!stack || stack.length <= 1) return false;
/**
* We only want to outlet of the entering view
* to respond to this gesture, so check
* to make sure the view is in the outlet we want.
*/
const routeInfo = ionRouter.getCurrentRouteInfo();
const enteringViewItem = viewStacks.findViewItemByRouteInfo({ pathname: routeInfo.pushedByRoute }, id);
return !!enteringViewItem;
} }
const onStart = async () => { const onStart = async () => {
const routeInfo = ionRouter.getCurrentRouteInfo(); const routeInfo = ionRouter.getCurrentRouteInfo();
const { routerAnimation } = routeInfo; const { routerAnimation } = routeInfo;
const items = viewStacks.getViewStack(id); const enteringViewItem = viewStacks.findViewItemByRouteInfo({ pathname: routeInfo.pushedByRoute }, id);
const enteringViewItem = items[items.length - 2];
const leavingViewItem = viewStacks.findViewItemByRouteInfo(routeInfo, id); const leavingViewItem = viewStacks.findViewItemByRouteInfo(routeInfo, id);
if (leavingViewItem) { if (leavingViewItem) {
@ -105,7 +113,23 @@ export const IonRouterOutlet = defineComponent({
const onEnd = (shouldContinue: boolean) => { const onEnd = (shouldContinue: boolean) => {
if (shouldContinue) { if (shouldContinue) {
skipTransition = true; skipTransition = true;
vueRouter.back();
/**
* Use the same logic as clicking
* ion-back-button to determine where
* to go back to.
*/
ionRouter.handleNavigateBack();
} else {
/**
* In the event that the swipe
* gesture was aborted, we should
* re-hide the page that was going to enter.
*/
const routeInfo = ionRouter.getCurrentRouteInfo();
const enteringViewItem = viewStacks.findViewItemByRouteInfo({ pathname: routeInfo.pushedByRoute }, id);
enteringViewItem.ionPageElement.setAttribute('aria-hidden', 'true');
enteringViewItem.ionPageElement.classList.add('ion-page-hidden');
} }
} }

View File

@ -1306,27 +1306,27 @@
} }
}, },
"@ionic/core": { "@ionic/core": {
"version": "5.4.0-dev.202010081632.d371af4", "version": "5.4.0-dev.202010091911.bfc0b25",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-5.4.0-dev.202010081632.d371af4.tgz", "resolved": "https://registry.npmjs.org/@ionic/core/-/core-5.4.0-dev.202010091911.bfc0b25.tgz",
"integrity": "sha512-Umy3OIbolByO9a4ZpETLxQ904TmaxD8grUnF3j7sfRFji4jXnzHAaVz6FteogWeZHjUa4hxJwrD3oBMh3OPKJQ==", "integrity": "sha512-gDWwMcgV+kPeTfBToZ8oLoSI7FygkRYEgi/DEAIHye3W8T/Z/NmqrxaNqXrXU0J0RKvfzrIlrw6uB67Xz1xRcw==",
"requires": { "requires": {
"ionicons": "^5.1.2", "ionicons": "^5.1.2",
"tslib": "^1.10.0" "tslib": "^1.10.0"
} }
}, },
"@ionic/vue": { "@ionic/vue": {
"version": "5.4.0-dev.202010081632.d371af4", "version": "5.4.0-dev.202010091911.bfc0b25",
"resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-5.4.0-dev.202010081632.d371af4.tgz", "resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-5.4.0-dev.202010091911.bfc0b25.tgz",
"integrity": "sha512-fPvnbWLxrP4wCSFlQepH/J++IkOE0wDvZlPAjl+B/xXGW8jEp0cr8HVY+cXevgPGiUbE0eUexBlOtmYLZ2yIEQ==", "integrity": "sha512-zrF/u3sMq4k3mLMtlA/VcS+RFeWEXrhOnzJAo8uPuDGT4ZcttJv0sVIFJMqZF7zM+6+xxep5B5mMoB1cigwG3A==",
"requires": { "requires": {
"@ionic/core": "5.4.0-dev.202010081632.d371af4", "@ionic/core": "5.4.0-dev.202010091911.bfc0b25",
"ionicons": "^5.1.2" "ionicons": "^5.1.2"
} }
}, },
"@ionic/vue-router": { "@ionic/vue-router": {
"version": "5.4.0-dev.202010081632.d371af4", "version": "5.4.0-dev.202010091911.bfc0b25",
"resolved": "https://registry.npmjs.org/@ionic/vue-router/-/vue-router-5.4.0-dev.202010081632.d371af4.tgz", "resolved": "https://registry.npmjs.org/@ionic/vue-router/-/vue-router-5.4.0-dev.202010091911.bfc0b25.tgz",
"integrity": "sha512-/ZrfEppL1UeA3Eb+QSa4D05AjPPwcloevpPOoXW8tkJ0q8A9yb7n/29IlsnPdp0m/zSJKyJAFlO2cxy5RVQSRw==" "integrity": "sha512-moa388WPxJjHBcIVILoijS+xrTcS7jaK4T/rP3U9Lri1bSYrDpYNWmd/0sktGt80m3gaa8VXS5E995zDiyLAfQ=="
}, },
"@jest/console": { "@jest/console": {
"version": "24.9.0", "version": "24.9.0",

View File

@ -11,8 +11,8 @@
"cypress": "node_modules/.bin/cypress run --headless --browser chrome" "cypress": "node_modules/.bin/cypress run --headless --browser chrome"
}, },
"dependencies": { "dependencies": {
"@ionic/vue": "5.4.0-dev.202010081632.d371af4", "@ionic/vue": "5.4.0-dev.202010091911.bfc0b25",
"@ionic/vue-router": "5.4.0-dev.202010081632.d371af4", "@ionic/vue-router": "5.4.0-dev.202010091911.bfc0b25",
"core-js": "^3.6.5", "core-js": "^3.6.5",
"vue": "^3.0.0-0", "vue": "^3.0.0-0",
"vue-router": "^4.0.0-0" "vue-router": "^4.0.0-0"

View File

@ -1,7 +1,7 @@
<template> <template>
<ion-page data-pageid="tabs"> <ion-page data-pageid="tabs">
<ion-content> <ion-content>
<ion-tabs> <ion-tabs id="tabs">
<ion-tab-bar slot="bottom"> <ion-tab-bar slot="bottom">
<ion-tab-button tab="tab1" href="/tabs/tab1"> <ion-tab-button tab="tab1" href="/tabs/tab1">
<ion-icon :icon="triangle" /> <ion-icon :icon="triangle" />

View File

@ -1,20 +1,4 @@
describe('Navigation', () => { describe('Navigation', () => {
/*
// TODO move these to separate describe block
it.skip('should swipe and abort', () => {
cy.ionPageInvisible('home');
cy.ionSwipeToGoBack();
cy.ionPageInvisible('home');
cy.ionPageVisible('navigation');
});
it.skip('should swipe and complete', () => {
cy.ionSwipeToGoBack(true);
cy.ionPageVisible('home');
});
*/
it('should go to sibling page', () => { it('should go to sibling page', () => {
cy.visit('http://localhost:8080'); cy.visit('http://localhost:8080');
cy.get('ion-item#navigation').click(); cy.get('ion-item#navigation').click();
@ -64,3 +48,27 @@ describe('Navigation', () => {
cy.ionPageDoesNotExist('navigationchild') cy.ionPageDoesNotExist('navigationchild')
}) })
}); });
describe('Navigation - Swipe to Go Back', () => {
beforeEach(() => {
cy.viewport(320, 568);
cy.visit('http://localhost:8080?ionic:mode=ios');
cy.get('#navigation').click();
cy.ionPageHidden('home');
cy.ionPageVisible('navigation')
});
it('should swipe and abort', () => {
cy.ionSwipeToGoBack();
cy.ionPageHidden('home');
cy.ionPageVisible('navigation');
});
it('should swipe and complete', () => {
cy.ionSwipeToGoBack(true);
cy.ionPageVisible('home');
// TODO: Vue router does not go back in cypress with router.back()
//cy.ionPageDoesNotExist('navigation');
});
})

View File

@ -66,3 +66,71 @@ describe('Tabs', () => {
cy.ionPageDoesNotExist('tabs'); cy.ionPageDoesNotExist('tabs');
}); });
}) })
describe('Tabs - Swipe to Go Back', () => {
beforeEach(() => {
cy.viewport(320, 568);
cy.visit('http://localhost:8080?ionic:mode=ios');
cy.get('#tabs').click();
cy.ionPageHidden('home');
cy.ionPageVisible('tab1')
});
it('should swipe and abort', () => {
cy.ionSwipeToGoBack();
cy.ionPageHidden('home');
cy.ionPageVisible('tab1');
});
it('should swipe and go back to home', () => {
cy.ionSwipeToGoBack(true);
cy.ionPageVisible('home');
// TODO: Vue router does not go back in cypress with router.back()
//cy.ionPageDoesNotExist('tabs1');
});
it('should swipe and abort within a tab', () => {
cy.get('#child-one').click();
cy.ionPageVisible('tab1childone');
cy.ionPageHidden('tab1');
cy.ionSwipeToGoBack(false, 'ion-tabs#tabs ion-router-outlet');
cy.ionPageHidden('tab1');
cy.ionPageVisible('tab1childone');
});
it('should swipe and go back within a tab', () => {
cy.get('#child-one').click();
cy.ionPageVisible('tab1childone');
cy.ionPageHidden('tab1');
cy.ionSwipeToGoBack(true, 'ion-tabs#tabs ion-router-outlet');
cy.ionPageVisible('tab1');
cy.ionPageDoesNotExist('tab1childone');
});
it('should swipe and go back to correct tab after switching tabs', () => {
cy.get('#child-one').click();
cy.ionPageVisible('tab1childone');
cy.ionPageHidden('tab1');
cy.get('ion-tab-button#tab-button-tab2').click();
cy.ionPageVisible('tab2');
cy.ionPageHidden('tab1childone');
cy.get('ion-tab-button#tab-button-tab1').click();
cy.ionPageVisible('tab1childone');
cy.ionPageHidden('tab2');
cy.ionSwipeToGoBack(true, 'ion-tabs#tabs ion-router-outlet');
cy.ionPageVisible('tab1');
cy.ionPageDoesNotExist('tab1childone');
cy.ionSwipeToGoBack(true, 'ion-tabs#tabs ion-router-outlet');
cy.ionPageVisible('home');
});
})

View File

@ -24,11 +24,20 @@
// -- This is will overwrite an existing command -- // -- This is will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
Cypress.Commands.add('ionSwipeToGoBack', (complete = false) => { Cypress.Commands.add('ionSwipeToGoBack', (complete = false, selector = 'ion-router-outlet') => {
cy.get('ion-router-outlet') const increment = (complete) ? 60 : 25;
.trigger('mousedown', { position: "left" }) cy.get(selector)
.trigger('mousemove', { clientX: (complete) ? 100 : 300, clientY: 275 }) .first()
.trigger('mouseup', { force: true }) .trigger('mousedown', 0, 275, { which: 1, force: true })
.trigger('mousemove', increment * 1, 275, { which: 1, force: true })
.wait(50)
.trigger('mousemove', increment * 2, 275, { which: 1, force: true })
.wait(50)
.trigger('mousemove', increment * 3, 275, { which: 1, force: true })
.wait(50)
.trigger('mousemove', increment * 4, 275, { which: 1, force: true })
.wait(50)
.trigger('mouseup', increment * 4, 275, { which: 1, force: true })
cy.wait(150); cy.wait(150);
}) })