diff --git a/core/src/components/nav/nav.tsx b/core/src/components/nav/nav.tsx index bf90a19e35..3f0efdcbfa 100644 --- a/core/src/components/nav/nav.tsx +++ b/core/src/components/nav/nav.tsx @@ -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 diff --git a/core/src/components/nav/test/modal-navigation/index.html b/core/src/components/nav/test/modal-navigation/index.html new file mode 100644 index 0000000000..a5dedf3a97 --- /dev/null +++ b/core/src/components/nav/test/modal-navigation/index.html @@ -0,0 +1,103 @@ + + + + + Nav - Modal Navigation + + + + + + + + + + + + + Modal Navigation + + + + Open Modal + + + + Modal + + Close + + + + + + + + + + + + + diff --git a/core/src/components/nav/test/modal-navigation/nav.e2e.ts b/core/src/components/nav/test/modal-navigation/nav.e2e.ts new file mode 100644 index 0000000000..71a7308c81 --- /dev/null +++ b/core/src/components/nav/test/modal-navigation/nav.e2e.ts @@ -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(); +};