Skip to content

Commit

Permalink
fix(nav): swipe to go back works inside card modal (#25333)
Browse files Browse the repository at this point in the history
resolves #25327
  • Loading branch information
liamdebeasi committed May 23, 2022
1 parent 311c634 commit 0156be6
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 39 deletions.
2 changes: 1 addition & 1 deletion core/src/components/modal/gestures/swipe-to-close.ts
Expand Up @@ -277,7 +277,7 @@ export const createSwipeToCloseGesture = (el: HTMLIonModalElement, animation: An
const gesture = createGesture({
el,
gestureName: 'modalSwipeToClose',
gesturePriority: 40,
gesturePriority: 39,
direction: 'y',
threshold: 10,
canStart,
Expand Down
88 changes: 88 additions & 0 deletions core/src/components/modal/test/card-nav/index.html
@@ -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>
50 changes: 50 additions & 0 deletions core/src/components/modal/test/card-nav/modal.e2e.ts
@@ -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');
});
});
39 changes: 4 additions & 35 deletions core/src/components/modal/test/card/modal.e2e.ts
@@ -1,46 +1,15 @@
import { expect } from '@playwright/test';
import { dragElementBy, test, Viewports } from '@utils/test/playwright';
import type { E2EPage, EventSpy } from '@utils/test/playwright';

class CardModalPage {
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();
}
}
}
import { test, Viewports } from '@utils/test/playwright';

import { CardModalPage } from '../fixtures';

test.describe('card modal', () => {
let cardModalPage: CardModalPage;
test.beforeEach(async ({ page }, testInfo) => {
test.skip(testInfo.project.metadata.mode !== 'ios', 'Card style modal is only available on iOS');

cardModalPage = new CardModalPage(page);
await cardModalPage.navigate();
await cardModalPage.navigate('/src/components/modal/test/card');
});
test.describe('card modal: rendering', () => {
test('should not have visual regressions', async ({ page }) => {
Expand Down
34 changes: 34 additions & 0 deletions core/src/components/modal/test/fixtures.ts
@@ -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();
}
}
}
8 changes: 5 additions & 3 deletions core/src/utils/test/playwright/drag-element.ts
Expand Up @@ -6,7 +6,9 @@ export const dragElementBy = async (
el: Locator | ElementHandle<SVGElement | HTMLElement>,
page: E2EPage,
dragByX = 0,
dragByY = 0
dragByY = 0,
startXCoord?: number,
startYCoord?: number
) => {
const boundingBox = await el.boundingBox();

Expand All @@ -16,8 +18,8 @@ export const dragElementBy = async (
);
}

const startX = boundingBox.x + boundingBox.width / 2;
const startY = boundingBox.y + boundingBox.height / 2;
const startX = startXCoord === undefined ? boundingBox.x + boundingBox.width / 2 : startXCoord;
const startY = startYCoord === undefined ? boundingBox.y + boundingBox.height / 2 : startYCoord;

const midX = startX + dragByX / 2;
const midY = startY + dragByY / 2;
Expand Down

0 comments on commit 0156be6

Please sign in to comment.