mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-18 03:00:58 +08:00
fix(nav): pop() will unmount views within a modal (#25638)
Resolves #25637, #21831
This commit is contained in:
@ -131,6 +131,10 @@ export class Nav implements NavOutlet {
|
||||
this.swipeGestureChanged();
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.destroyed = false;
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
for (const view of this.views) {
|
||||
lifecycle(view.element!, LIFECYCLE_WILL_UNLOAD);
|
||||
@ -879,9 +883,13 @@ export class Nav implements NavOutlet {
|
||||
leavingView: ViewController | undefined,
|
||||
opts: NavOptions
|
||||
): NavResult {
|
||||
const cleanupView = hasCompleted ? enteringView : leavingView;
|
||||
if (cleanupView) {
|
||||
this.cleanup(cleanupView);
|
||||
/**
|
||||
* If the transition did not complete, the leavingView will still be the active
|
||||
* view on the stack. Otherwise unmount all the views after the enteringView.
|
||||
*/
|
||||
const activeView = hasCompleted ? enteringView : leavingView;
|
||||
if (activeView) {
|
||||
this.unmountInactiveViews(activeView);
|
||||
}
|
||||
|
||||
return {
|
||||
@ -944,9 +952,13 @@ export class Nav implements NavOutlet {
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmounts all inactive views after the specified active view.
|
||||
*
|
||||
* DOM WRITE
|
||||
*
|
||||
* @param activeView The view that is actively visible in the stack. Used to calculate which views to unmount.
|
||||
*/
|
||||
private cleanup(activeView: ViewController) {
|
||||
private unmountInactiveViews(activeView: ViewController) {
|
||||
// ok, cleanup time!! Destroy all of the views that are
|
||||
// INACTIVE and come after the active view
|
||||
// only do this if the views exist, though
|
||||
|
103
core/src/components/nav/test/modal-navigation/index.html
Normal file
103
core/src/components/nav/test/modal-navigation/index.html
Normal file
@ -0,0 +1,103 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Nav - Modal Navigation</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>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Modal Navigation</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-button id="openModal">Open Modal</ion-button>
|
||||
<ion-modal trigger="openModal">
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Modal</ion-title>
|
||||
<ion-buttons slot="end">
|
||||
<ion-button onclick="dismiss()"> Close </ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-nav></ion-nav>
|
||||
</ion-content>
|
||||
</ion-modal>
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
|
||||
<script>
|
||||
const modal = document.querySelector('ion-modal');
|
||||
const nav = document.querySelector('ion-nav');
|
||||
|
||||
modal.addEventListener('willPresent', () => {
|
||||
nav.setRoot('page-one');
|
||||
});
|
||||
|
||||
const dismiss = () => modal.dismiss();
|
||||
|
||||
const navigate = (component, componentProps) => {
|
||||
nav.push(component, componentProps);
|
||||
};
|
||||
|
||||
const navigateBack = () => {
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
const navigateToRoot = () => {
|
||||
nav.popToRoot();
|
||||
};
|
||||
|
||||
class PageOne extends HTMLElement {
|
||||
connectedCallback() {
|
||||
this.innerHTML = `
|
||||
<ion-content class="ion-padding">
|
||||
<h1>Page One</h1>
|
||||
<ion-button id="goto-page-two" onclick="navigate('page-two')">Go to Page Two</ion-button>
|
||||
</ion-content>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
class PageTwo extends HTMLElement {
|
||||
connectedCallback() {
|
||||
this.innerHTML = `
|
||||
<ion-content class="ion-padding">
|
||||
<h1>Page Two</h1>
|
||||
<ion-button id="goto-page-three" onclick="navigate('page-three')">Go to Page Three</ion-button>
|
||||
</ion-content>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
class PageThree extends HTMLElement {
|
||||
connectedCallback() {
|
||||
this.innerHTML = `
|
||||
<ion-content class="ion-padding">
|
||||
<h1>Page Three</h1>
|
||||
<ion-button id="go-back" onclick="navigateBack()">Go Back</ion-button>
|
||||
<ion-button id="goto-root"onclick="navigateToRoot()">Go to Root</ion-button>
|
||||
</ion-content>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('page-one', PageOne);
|
||||
customElements.define('page-two', PageTwo);
|
||||
customElements.define('page-three', PageThree);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
77
core/src/components/nav/test/modal-navigation/nav.e2e.ts
Normal file
77
core/src/components/nav/test/modal-navigation/nav.e2e.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import type { E2EPage } from '@utils/test/playwright';
|
||||
import { test } from '@utils/test/playwright';
|
||||
|
||||
test.describe('nav: modal-navigation', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto(`/src/components/nav/test/modal-navigation`);
|
||||
await openModal(page);
|
||||
});
|
||||
|
||||
test('should render the root page', async ({ page }) => {
|
||||
const pageOne = page.locator('page-one');
|
||||
const pageOneHeading = page.locator('page-one h1');
|
||||
|
||||
await expect(pageOne).toBeVisible();
|
||||
await expect(pageOneHeading).toHaveText('Page One');
|
||||
});
|
||||
|
||||
test('should push to the next page', async ({ page }) => {
|
||||
await page.click('#goto-page-two');
|
||||
|
||||
const pageTwo = page.locator('page-two');
|
||||
const pageTwoHeading = page.locator('page-two h1');
|
||||
|
||||
await expect(pageTwo).toBeVisible();
|
||||
await expect(pageTwoHeading).toHaveText('Page Two');
|
||||
});
|
||||
|
||||
test('should pop to the previous page', async ({ page }) => {
|
||||
await page.click('#goto-page-two');
|
||||
await page.click('#goto-page-three');
|
||||
|
||||
const pageThree = page.locator('page-three');
|
||||
const pageThreeHeading = page.locator('page-three h1');
|
||||
|
||||
await expect(pageThree).toBeVisible();
|
||||
await expect(pageThreeHeading).toHaveText('Page Three');
|
||||
|
||||
await page.click('#go-back');
|
||||
|
||||
const pageTwo = page.locator('page-two');
|
||||
const pageTwoHeading = page.locator('page-two h1');
|
||||
|
||||
// Verifies the leavingView was unmounted
|
||||
await expect(pageThree).toHaveCount(0);
|
||||
await expect(pageTwo).toBeVisible();
|
||||
await expect(pageTwoHeading).toHaveText('Page Two');
|
||||
});
|
||||
|
||||
test.describe('popping to the root', () => {
|
||||
test('should render the root page', async ({ page }) => {
|
||||
const pageTwo = page.locator('page-two');
|
||||
const pageThree = page.locator('page-three');
|
||||
|
||||
await page.click('#goto-page-two');
|
||||
await page.click('#goto-page-three');
|
||||
|
||||
await page.click('#goto-root');
|
||||
|
||||
const pageOne = page.locator('page-one');
|
||||
const pageOneHeading = page.locator('page-one h1');
|
||||
|
||||
// Verifies all views besides the root were unmounted
|
||||
await expect(pageTwo).toHaveCount(0);
|
||||
await expect(pageThree).toHaveCount(0);
|
||||
|
||||
await expect(pageOne).toBeVisible();
|
||||
await expect(pageOneHeading).toHaveText('Page One');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const openModal = async (page: E2EPage) => {
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
await page.click('#openModal');
|
||||
await ionModalDidPresent.next();
|
||||
};
|
Reference in New Issue
Block a user