mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-20 04:14:21 +08:00
fix(nav): swipe to go back works inside card modal (#25333)
resolves #25327
This commit is contained in:
@ -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,
|
||||||
|
88
core/src/components/modal/test/card-nav/index.html
Normal file
88
core/src/components/modal/test/card-nav/index.html
Normal 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>
|
50
core/src/components/modal/test/card-nav/modal.e2e.ts
Normal file
50
core/src/components/modal/test/card-nav/modal.e2e.ts
Normal 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');
|
||||||
|
});
|
||||||
|
});
|
@ -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 }) => {
|
||||||
|
34
core/src/components/modal/test/fixtures.ts
Normal file
34
core/src/components/modal/test/fixtures.ts
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
Reference in New Issue
Block a user