mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
fix(modal): dismiss top-most overlay when multiple IDs match (#30883)
Issue number: resolves #30030 --------- ## What is the current behavior? When modals are presented one after another with matching IDs and then dismissed by ID it will dismiss the first presented modal. ## What is the new behavior? - When modals are presented one after another with matching IDs and then dismissed by ID it will dismiss the last (top-most) presented modal. - Added e2e tests to verify this behavior works the same as the default dismiss (not passing an ID). ## Does this introduce a breaking change? - [ ] Yes - [x] No ## Other information [Modal: Dismiss Behavior](https://ionic-framework-git-fw-7016-ionic1.vercel.app/src/components/modal/test/dismiss-behavior) --------- Co-authored-by: Brandy Smith <6577830+brandyscarney@users.noreply.github.com>
This commit is contained in:
97
core/src/components/modal/test/dismiss-behavior/index.html
Normal file
97
core/src/components/modal/test/dismiss-behavior/index.html
Normal file
@@ -0,0 +1,97 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Modal - Dismiss Behavior</title>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="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 nomodule src="../../../../../dist/ionic/ionic.js"></script>
|
||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<ion-app>
|
||||
<div class="ion-page">
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Modal - Dismiss Behavior</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding">
|
||||
<button id="present-first-modal" onclick="presentFirstModal()">Present Modal</button>
|
||||
</ion-content>
|
||||
</div>
|
||||
</ion-app>
|
||||
|
||||
<script type="module">
|
||||
import { modalController } from '../../../../../dist/ionic/index.esm.js';
|
||||
|
||||
window.modalController = modalController;
|
||||
|
||||
const sharedId = 'shared-modal-id';
|
||||
const maxModals = 5;
|
||||
let modalCount = 0;
|
||||
|
||||
function createModalComponent(modalNumber) {
|
||||
const element = document.createElement('div');
|
||||
const canPresentNext = modalNumber < maxModals;
|
||||
const presentNextButton = canPresentNext
|
||||
? `<ion-button id="present-next-modal" onclick="presentNextModal(${modalNumber + 1})">Present Modal ${
|
||||
modalNumber + 1
|
||||
}</ion-button>`
|
||||
: '';
|
||||
element.innerHTML = `
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Modal ${modalNumber}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content class="ion-padding">
|
||||
<p>This is modal number ${modalNumber}</p>
|
||||
${presentNextButton}
|
||||
<ion-button class="dismiss-by-id">Dismiss By ID</ion-button>
|
||||
<ion-button class="dismiss-default">Dismiss Default</ion-button>
|
||||
</ion-content>
|
||||
`;
|
||||
return element;
|
||||
}
|
||||
|
||||
window.presentFirstModal = async () => {
|
||||
modalCount = 0;
|
||||
await presentNextModal(1);
|
||||
};
|
||||
|
||||
window.presentNextModal = async (modalNumber) => {
|
||||
if (modalNumber > maxModals) {
|
||||
return;
|
||||
}
|
||||
modalCount = Math.max(modalCount, modalNumber);
|
||||
const element = createModalComponent(modalNumber);
|
||||
const modal = await modalController.create({
|
||||
component: element,
|
||||
htmlAttributes: {
|
||||
id: sharedId,
|
||||
'data-testid': `modal-${modalNumber}`,
|
||||
},
|
||||
});
|
||||
await modal.present();
|
||||
|
||||
const dismissByIdButton = element.querySelector('ion-button.dismiss-by-id');
|
||||
dismissByIdButton.addEventListener('click', () => {
|
||||
modalController.dismiss(undefined, undefined, sharedId);
|
||||
});
|
||||
|
||||
const dismissDefaultButton = element.querySelector('ion-button.dismiss-default');
|
||||
dismissDefaultButton.addEventListener('click', () => {
|
||||
modalController.dismiss();
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
58
core/src/components/modal/test/dismiss-behavior/modal.e2e.ts
Normal file
58
core/src/components/modal/test/dismiss-behavior/modal.e2e.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { configs, test } from '@utils/test/playwright';
|
||||
|
||||
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe(title('modal: dismiss behavior'), () => {
|
||||
test.describe(title('modal: default dismiss'), () => {
|
||||
test('should dismiss the last presented modal when the default dismiss button is clicked', async ({ page }) => {
|
||||
await page.goto('/src/components/modal/test/dismiss-behavior', config);
|
||||
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
|
||||
|
||||
await page.click('#present-first-modal');
|
||||
await ionModalDidPresent.next();
|
||||
const firstModal = page.locator('ion-modal[data-testid="modal-1"]');
|
||||
await expect(firstModal).toBeVisible();
|
||||
|
||||
await page.click('#present-next-modal');
|
||||
await ionModalDidPresent.next();
|
||||
const secondModal = page.locator('ion-modal[data-testid="modal-2"]');
|
||||
await expect(secondModal).toBeVisible();
|
||||
|
||||
await page.click('ion-modal[data-testid="modal-2"] ion-button.dismiss-default');
|
||||
await ionModalDidDismiss.next();
|
||||
await secondModal.waitFor({ state: 'detached' });
|
||||
|
||||
await expect(firstModal).toBeVisible();
|
||||
await expect(secondModal).toBeHidden();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe(title('modal: dismiss by id'), () => {
|
||||
test('should dismiss the last presented modal when the dismiss by id button is clicked', async ({ page }) => {
|
||||
await page.goto('/src/components/modal/test/dismiss-behavior', config);
|
||||
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
|
||||
|
||||
await page.click('#present-first-modal');
|
||||
await ionModalDidPresent.next();
|
||||
const firstModal = page.locator('ion-modal[data-testid="modal-1"]');
|
||||
await expect(firstModal).toBeVisible();
|
||||
|
||||
await page.click('#present-next-modal');
|
||||
await ionModalDidPresent.next();
|
||||
const secondModal = page.locator('ion-modal[data-testid="modal-2"]');
|
||||
await expect(secondModal).toBeVisible();
|
||||
|
||||
await page.click('ion-modal[data-testid="modal-2"] ion-button.dismiss-by-id');
|
||||
await ionModalDidDismiss.next();
|
||||
await secondModal.waitFor({ state: 'detached' });
|
||||
|
||||
await expect(firstModal).toBeVisible();
|
||||
await expect(secondModal).toBeHidden();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
import { h } from '@stencil/core';
|
||||
import { newSpecPage } from '@stencil/core/testing';
|
||||
|
||||
import { Modal } from '../modal';
|
||||
import { h } from '@stencil/core';
|
||||
|
||||
describe('modal: id', () => {
|
||||
it('modal should be assigned an incrementing id', async () => {
|
||||
@@ -52,4 +52,21 @@ describe('modal: id', () => {
|
||||
const alert = page.body.querySelector('ion-modal')!;
|
||||
expect(alert.id).toBe(id);
|
||||
});
|
||||
|
||||
it('should allow multiple modals with the same id', async () => {
|
||||
const sharedId = 'shared-modal-id';
|
||||
|
||||
const page = await newSpecPage({
|
||||
components: [Modal],
|
||||
template: () => [
|
||||
<ion-modal id={sharedId} overlayIndex={1} is-open={true}></ion-modal>,
|
||||
<ion-modal id={sharedId} overlayIndex={2} is-open={true}></ion-modal>,
|
||||
],
|
||||
});
|
||||
|
||||
const modals = page.body.querySelectorAll('ion-modal');
|
||||
expect(modals.length).toBe(2);
|
||||
expect(modals[0].id).toBe(sharedId);
|
||||
expect(modals[1].id).toBe(sharedId);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -473,7 +473,9 @@ export const getPresentedOverlay = (
|
||||
id?: string
|
||||
): HTMLIonOverlayElement | undefined => {
|
||||
const overlays = getPresentedOverlays(doc, overlayTag);
|
||||
return id === undefined ? overlays[overlays.length - 1] : overlays.find((o) => o.id === id);
|
||||
// If no id is provided, return the last presented overlay
|
||||
// Otherwise, return the last overlay with the given id
|
||||
return (id === undefined ? overlays : overlays.filter((o: HTMLIonOverlayElement) => o.id === id)).slice(-1)[0];
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user