diff --git a/packages/vue/src/components/IonRouterOutlet.ts b/packages/vue/src/components/IonRouterOutlet.ts index 6025cab342..2f3d1e373a 100644 --- a/packages/vue/src/components/IonRouterOutlet.ts +++ b/packages/vue/src/components/IonRouterOutlet.ts @@ -238,7 +238,11 @@ export const IonRouterOutlet = defineComponent({ leavingEl.classList.add('ion-page-hidden'); leavingEl.setAttribute('aria-hidden', 'true'); - if (!(routerAction === 'push' && routerDirection === 'forward')) { + if (routerAction === 'replace') { + leavingViewItem.mount = false; + leavingViewItem.ionPageElement = undefined; + leavingViewItem.ionRoute = false; + } else if (!(routerAction === 'push' && routerDirection === 'forward')) { const shouldLeavingViewBeRemoved = routerDirection !== 'none' && leavingViewItem && (enteringViewItem !== leavingViewItem); if (shouldLeavingViewBeRemoved) { leavingViewItem.mount = false; diff --git a/packages/vue/test-app/tests/e2e/specs/routing.js b/packages/vue/test-app/tests/e2e/specs/routing.js index 8505fa9d96..fa14197da0 100644 --- a/packages/vue/test-app/tests/e2e/specs/routing.js +++ b/packages/vue/test-app/tests/e2e/specs/routing.js @@ -114,7 +114,7 @@ describe('Routing', () => { cy.get('#replace').click(); cy.ionPageVisible('navigation'); - cy.ionPageHidden('routing'); + cy.ionPageDoesNotExist('routing'); cy.ionSwipeToGoBack(true); cy.ionPageVisible('navigation'); diff --git a/packages/vue/test-app/tests/unit/lifecycle.spec.ts b/packages/vue/test-app/tests/unit/lifecycle.spec.ts index 5a0ea158b0..d1999ae8dc 100644 --- a/packages/vue/test-app/tests/unit/lifecycle.spec.ts +++ b/packages/vue/test-app/tests/unit/lifecycle.spec.ts @@ -1,7 +1,8 @@ -import { mount, flushPromises } from '@vue/test-utils'; +import { mount } from '@vue/test-utils'; import { createRouter, createWebHistory } from '@ionic/vue-router'; import { IonicVue, IonApp, IonRouterOutlet, IonPage } from '@ionic/vue'; import { defineComponent } from 'vue'; +import { waitForRouter } from './utils'; const App = { components: { IonApp, IonRouterOutlet }, @@ -48,6 +49,9 @@ const router = createRouter({ }); describe('Lifecycle Events', () => { + beforeAll(() => { + (HTMLElement.prototype as HTMLIonRouterOutletElement).commit = jest.fn(); + }); it('Triggers lifecycle events', async () => { // Initial render router.push('/'); @@ -78,10 +82,7 @@ describe('Lifecycle Events', () => { // Navigate to 2nd page router.push('/2'); jest.resetAllMocks(); - await flushPromises(); - - (HTMLElement.prototype as HTMLIonRouterOutletElement).commit = jest.fn(); - await new Promise((r) => setTimeout(r, 100)); + await waitForRouter(); // Page 1 lifecycle hooks expect(Page1.ionViewDidEnter).not.toHaveBeenCalled(); diff --git a/packages/vue/test-app/tests/unit/routing.spec.ts b/packages/vue/test-app/tests/unit/routing.spec.ts index 20f5c7b695..edfb046a20 100644 --- a/packages/vue/test-app/tests/unit/routing.spec.ts +++ b/packages/vue/test-app/tests/unit/routing.spec.ts @@ -1,7 +1,17 @@ -import { mount, flushPromises } from '@vue/test-utils'; +import { mount } from '@vue/test-utils'; import { createRouter, createWebHistory } from '@ionic/vue-router'; -import { IonicVue, IonApp, IonRouterOutlet, IonPage, IonTabs, IonTabBar } from '@ionic/vue'; +import { + IonicVue, + IonApp, + IonRouterOutlet, + IonPage, + IonTabs, + IonTabBar, + IonTabButton, + IonLabel +} from '@ionic/vue'; import { onBeforeRouteLeave } from 'vue-router'; +import { waitForRouter } from './utils'; const App = { components: { IonApp, IonRouterOutlet }, @@ -14,6 +24,9 @@ const BasePage = { } describe('Routing', () => { + beforeAll(() => { + (HTMLElement.prototype as HTMLIonRouterOutletElement).commit = jest.fn(); + }); it('should pass no props', async () => { const Page1 = { ...BasePage, @@ -153,8 +166,83 @@ describe('Routing', () => { // Navigate to 2nd page router.push('/page2'); - await flushPromises(); + await waitForRouter(); expect(leaveHook).toBeCalled(); }); + + // Verifies fix for https://github.com/ionic-team/ionic-framework/issues/22492 + it('should show correct view when replacing', async () => { + const Tabs = { + components: { IonPage, IonTabs, IonTabBar, IonTabButton, IonLabel }, + template: ` + + + + + Tab 1 + + + Tab 2 + + + + + `, + } + const Tab1 = { + components: { IonPage }, + template: `Tab 1` + } + const Tab2 = { + components: { IonPage }, + template: `Tab 2` + } + const Parent = { + ...BasePage, + template: `Parent Page` + } + + const router = createRouter({ + history: createWebHistory(process.env.BASE_URL), + routes: [ + { path: '/', redirect: '/tabs/tab1' }, + { path: '/parent', component: Parent }, + { path: '/tabs/', component: Tabs, children: [ + { path: '/', redirect: 'tab1' }, + { path: 'tab1', component: Tab1 }, + { path: 'tab2', component: Tab2 } + ]} + ] + }); + + router.push('/'); + await router.isReady(); + const wrapper = mount(App, { + global: { + plugins: [router, IonicVue] + } + }); + + // Go to Tab 2 + const tabButtons = wrapper.findAllComponents(IonTabButton); + tabButtons[1].trigger('click'); + await waitForRouter(); + + expect(wrapper.findComponent(Tab2).exists()).toBe(true); + expect(wrapper.findComponent(Parent).exists()).toBe(false); + + router.replace('/parent') + await waitForRouter(); + + expect(wrapper.findComponent(Parent).exists()).toBe(true); + expect(wrapper.findComponent(Tabs).exists()).toBe(false); + + router.replace('/tabs/tab1'); + await waitForRouter(); + + expect(wrapper.findComponent(Parent).exists()).toBe(false); + expect(wrapper.findComponent(Tab1).exists()).toBe(true); + expect(wrapper.findComponent(Tab2).exists()).toBe(false); + }); }); diff --git a/packages/vue/test-app/tests/unit/tabs.spec.ts b/packages/vue/test-app/tests/unit/tabs.spec.ts index 0262da6d34..2c13e237dd 100644 --- a/packages/vue/test-app/tests/unit/tabs.spec.ts +++ b/packages/vue/test-app/tests/unit/tabs.spec.ts @@ -1,6 +1,7 @@ -import { mount, flushPromises } from '@vue/test-utils'; +import { mount } from '@vue/test-utils'; import { createRouter, createWebHistory } from '@ionic/vue-router'; import { IonicVue, IonApp, IonRouterOutlet, IonPage, IonTabs, IonTabBar, IonTabButton, IonLabel } from '@ionic/vue'; +import { waitForRouter } from './utils'; const App = { components: { IonApp, IonRouterOutlet }, @@ -34,8 +35,9 @@ const Tab2 = { } describe('ion-tabs', () => { - (HTMLElement.prototype as HTMLIonRouterOutletElement).commit = jest.fn(); - + beforeAll(() => { + (HTMLElement.prototype as HTMLIonRouterOutletElement).commit = jest.fn(); + }); it('should emit will change and did change events when changing tab', async () => { const router = createRouter({ history: createWebHistory(process.env.BASE_URL), @@ -76,7 +78,7 @@ describe('ion-tabs', () => { expect(tabs.emitted().ionTabsDidChange[0]).toEqual([{ tab: 'tab1' }]); router.push('/tab2') - await flushPromises() + await waitForRouter() expect(tabs.emitted().ionTabsWillChange.length).toEqual(2); expect(tabs.emitted().ionTabsWillChange[1]).toEqual([{ tab: 'tab2' }]); @@ -124,7 +126,7 @@ describe('ion-tabs', () => { expect(tabs.emitted().ionTabsDidChange[0]).toEqual([{ tab: 'tab1' }]); router.push('/tab1') - await flushPromises() + await waitForRouter() expect(tabs.emitted().ionTabsWillChange.length).toEqual(1); expect(tabs.emitted().ionTabsDidChange.length).toEqual(1); @@ -178,9 +180,7 @@ describe('ion-tabs', () => { expect(tabs.emitted().ionTabsDidChange[0]).toEqual([{ tab: 'tab1' }]); router.push('/sibling'); - await flushPromises(); - - await new Promise((r) => setTimeout(r, 100)); + await waitForRouter() expect(tabs.emitted().ionTabsWillChange.length).toEqual(1); expect(tabs.emitted().ionTabsDidChange.length).toEqual(1); @@ -236,9 +236,7 @@ describe('ion-tabs', () => { expect(tabs.emitted().ionTabsDidChange[0]).toEqual([{ tab: 'tab1' }]); router.push('/tab1/child'); - await flushPromises(); - - await new Promise((r) => setTimeout(r, 100)); + await waitForRouter() expect(tabs.emitted().ionTabsWillChange.length).toEqual(1); expect(tabs.emitted().ionTabsDidChange.length).toEqual(1); diff --git a/packages/vue/test-app/tests/unit/utils.ts b/packages/vue/test-app/tests/unit/utils.ts new file mode 100644 index 0000000000..89e52984c2 --- /dev/null +++ b/packages/vue/test-app/tests/unit/utils.ts @@ -0,0 +1,6 @@ +import { flushPromises } from '@vue/test-utils'; + +export const waitForRouter = async () => { + await flushPromises(); + await new Promise((r) => setTimeout(r, 100)); +}