fix(nav): swipe to go back works inside card modal (#25333)

resolves #25327
This commit is contained in:
Liam DeBeasi
2022-05-23 16:49:36 -04:00
committed by GitHub
parent 311c634d20
commit 0156be61cb
6 changed files with 181 additions and 38 deletions

View File

@ -277,7 +277,7 @@ export const createSwipeToCloseGesture = (el: HTMLIonModalElement, animation: An
const gesture = createGesture({ const gesture = createGesture({
el, el,
gestureName: 'modalSwipeToClose', gestureName: 'modalSwipeToClose',
gesturePriority: 40, gesturePriority: 39,
direction: 'y', direction: 'y',
threshold: 10, threshold: 10,
canStart, canStart,

View File

@ -0,0 +1,88 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Modal - Card + Nav</title>
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta
name="viewport"
content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
<script src="../../../../../scripts/testing/scripts.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
</head>
<body>
<script>
class AppNav extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<ion-content>
<ion-nav root="page-one"></ion-nav>
</ion-content>
`;
}
}
class PageOne extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<ion-header>
<ion-toolbar>
<ion-title>Page One</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<h1>Page One</h1>
<ion-nav-link router-direction="forward" component="page-two">
<ion-button id="go-page-two">Go to Page Two</ion-button>
</ion-nav-link>
</ion-content>
`;
}
}
class PageTwo extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
</ion-buttons>
<ion-title>Page Two</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding page-two-content"></ion-content>
`;
}
}
customElements.define('page-one', PageOne);
customElements.define('page-two', PageTwo);
customElements.define('app-nav', AppNav);
</script>
<ion-app>
<div class="ion-page">
<ion-header>
<ion-toolbar>
<ion-title>Card</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<ion-button id="open-modal">Open Modal</ion-button>
<ion-modal trigger="open-modal" component="app-nav"></ion-modal>
</ion-content>
</div>
</ion-app>
<script>
const modal = document.querySelector('ion-modal');
const nav = document.querySelector('ion-nav');
modal.canDismiss = true;
modal.presentingElement = document.querySelector('.ion-page');
</script>
</body>
</html>

View File

@ -0,0 +1,50 @@
import { expect } from '@playwright/test';
import { test, dragElementBy } from '@utils/test/playwright';
import { CardModalPage } from '../fixtures';
test.describe('card modal - nav', () => {
let cardModalPage: CardModalPage;
test.beforeEach(async ({ page, browserName }, testInfo) => {
test.skip(testInfo.project.metadata.mode !== 'ios', 'Card style modal is only available on iOS');
test.skip(
testInfo.project.metadata.rtl === true,
'This test only verifies that the gesture activates inside of a modal.'
);
test.skip(browserName !== 'chromium', 'dragElementBy is flaky outside of Chrome browsers.');
cardModalPage = new CardModalPage(page);
await cardModalPage.navigate('/src/components/modal/test/card-nav?ionic:_testing=false');
});
test('it should swipe to go back', async ({ page }) => {
await cardModalPage.openModalByTrigger('#open-modal');
const nav = page.locator('ion-nav') as any;
const ionNavDidChange = await nav.spyOnEvent('ionNavDidChange');
await page.click('#go-page-two');
await ionNavDidChange.next();
const pageOne = page.locator('page-one');
expect(pageOne).toHaveClass(/ion-page-hidden/);
const content = page.locator('.page-two-content');
await dragElementBy(content, page, 1000, 0, 10);
await ionNavDidChange.next();
});
test('should swipe to close', async ({ page }) => {
await cardModalPage.openModalByTrigger('#open-modal');
const nav = page.locator('ion-nav') as any;
const ionNavDidChange = await nav.spyOnEvent('ionNavDidChange');
await page.click('#go-page-two');
await ionNavDidChange.next();
await cardModalPage.swipeToCloseModal('ion-modal ion-content.page-two-content');
});
});

View File

@ -1,38 +1,7 @@
import { expect } from '@playwright/test'; import { expect } from '@playwright/test';
import { dragElementBy, test, Viewports } from '@utils/test/playwright'; import { test, Viewports } from '@utils/test/playwright';
import type { E2EPage, EventSpy } from '@utils/test/playwright';
class CardModalPage { import { CardModalPage } from '../fixtures';
private ionModalDidPresent!: EventSpy;
private ionModalDidDismiss!: EventSpy;
private page: E2EPage;
constructor(page: E2EPage) {
this.page = page;
}
async navigate() {
const { page } = this;
await page.goto('/src/components/modal/test/card');
this.ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
this.ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
}
async openModalByTrigger(selector: string) {
await this.page.click(selector);
await this.ionModalDidPresent.next();
return this.page.locator('ion-modal');
}
async swipeToCloseModal(selector: string, waitForDismiss = true, swipeY = 500) {
const { page } = this;
const elementRef = await page.locator(selector);
await dragElementBy(elementRef, page, 0, swipeY);
if (waitForDismiss) {
await this.ionModalDidDismiss.next();
}
}
}
test.describe('card modal', () => { test.describe('card modal', () => {
let cardModalPage: CardModalPage; let cardModalPage: CardModalPage;
@ -40,7 +9,7 @@ test.describe('card modal', () => {
test.skip(testInfo.project.metadata.mode !== 'ios', 'Card style modal is only available on iOS'); test.skip(testInfo.project.metadata.mode !== 'ios', 'Card style modal is only available on iOS');
cardModalPage = new CardModalPage(page); cardModalPage = new CardModalPage(page);
await cardModalPage.navigate(); await cardModalPage.navigate('/src/components/modal/test/card');
}); });
test.describe('card modal: rendering', () => { test.describe('card modal: rendering', () => {
test('should not have visual regressions', async ({ page }) => { test('should not have visual regressions', async ({ page }) => {

View File

@ -0,0 +1,34 @@
import { dragElementBy } from '@utils/test/playwright';
import type { E2EPage, EventSpy } from '@utils/test/playwright';
export class CardModalPage {
private ionModalDidPresent!: EventSpy;
private ionModalDidDismiss!: EventSpy;
private page: E2EPage;
constructor(page: E2EPage) {
this.page = page;
}
async navigate(url: string) {
const { page } = this;
await page.goto(url);
this.ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
this.ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
}
async openModalByTrigger(selector: string) {
await this.page.click(selector);
await this.ionModalDidPresent.next();
return this.page.locator('ion-modal');
}
async swipeToCloseModal(selector: string, waitForDismiss = true, swipeY = 500) {
const { page } = this;
const elementRef = await page.locator(selector);
await dragElementBy(elementRef, page, 0, swipeY);
if (waitForDismiss) {
await this.ionModalDidDismiss.next();
}
}
}

View File

@ -6,7 +6,9 @@ export const dragElementBy = async (
el: Locator | ElementHandle<SVGElement | HTMLElement>, el: Locator | ElementHandle<SVGElement | HTMLElement>,
page: E2EPage, page: E2EPage,
dragByX = 0, dragByX = 0,
dragByY = 0 dragByY = 0,
startXCoord?: number,
startYCoord?: number
) => { ) => {
const boundingBox = await el.boundingBox(); const boundingBox = await el.boundingBox();
@ -16,8 +18,8 @@ export const dragElementBy = async (
); );
} }
const startX = boundingBox.x + boundingBox.width / 2; const startX = startXCoord === undefined ? boundingBox.x + boundingBox.width / 2 : startXCoord;
const startY = boundingBox.y + boundingBox.height / 2; const startY = startYCoord === undefined ? boundingBox.y + boundingBox.height / 2 : startYCoord;
const midX = startX + dragByX / 2; const midX = startX + dragByX / 2;
const midY = startY + dragByY / 2; const midY = startY + dragByY / 2;