diff --git a/packages/vue/src/components/IonRouterOutlet.ts b/packages/vue/src/components/IonRouterOutlet.ts index 4178c95285c..ebd52b25cb8 100644 --- a/packages/vue/src/components/IonRouterOutlet.ts +++ b/packages/vue/src/components/IonRouterOutlet.ts @@ -88,19 +88,20 @@ export const IonRouterOutlet = /*@__PURE__*/ defineComponent({ * all of the keys in the params object, we check the url path to * see if they are different after ensuring we are in a parameterized route. */ - if (currentMatchedRouteRef === undefined) { return; } - - const matchedDifferentRoutes = currentMatchedRouteRef !== previousMatchedRouteRef; - const matchedDifferentParameterRoutes = ( - currentRoute.matched[currentRoute.matched.length - 1] === currentMatchedRouteRef && - currentRoute.path !== previousMatchedPath - ); - - if (matchedDifferentRoutes || matchedDifferentParameterRoutes) { - setupViewItem(matchedRouteRef); - previousMatchedRouteRef = currentMatchedRouteRef; - previousMatchedPath = currentRoute.path; + if (currentMatchedRouteRef !== undefined) { + const matchedDifferentRoutes = currentMatchedRouteRef !== previousMatchedRouteRef; + const matchedDifferentParameterRoutes = ( + currentRoute.matched[currentRoute.matched.length - 1] === currentMatchedRouteRef && + currentRoute.path !== previousMatchedPath + ); + + if (matchedDifferentRoutes || matchedDifferentParameterRoutes) { + setupViewItem(matchedRouteRef); + } } + + previousMatchedRouteRef = currentMatchedRouteRef; + previousMatchedPath = currentRoute.path; }); const canStart = () => { @@ -274,13 +275,13 @@ See https://ionicframework.com/docs/vue/navigation#ionpage for more information. * return early for swipe to go back when * going from a non-tabs page to a tabs page. */ - if (isViewVisible(enteringEl) && leavingViewItem !== undefined && !isViewVisible(leavingViewItem.ionPageElement)) { + if (isViewVisible(enteringEl) && leavingViewItem?.ionPageElement !== undefined && !isViewVisible(leavingViewItem.ionPageElement)) { return; } fireLifecycle(enteringViewItem.vueComponent, enteringViewItem.vueComponentRef, LIFECYCLE_WILL_ENTER); - if (leavingViewItem && enteringViewItem !== leavingViewItem) { + if (leavingViewItem?.ionPageElement && enteringViewItem !== leavingViewItem) { let animationBuilder = routerAnimation; const leavingEl = leavingViewItem.ionPageElement; diff --git a/packages/vue/test-app/tests/unit/lifecycle.spec.ts b/packages/vue/test-app/tests/unit/lifecycle.spec.ts index d1999ae8dcc..37fee756c57 100644 --- a/packages/vue/test-app/tests/unit/lifecycle.spec.ts +++ b/packages/vue/test-app/tests/unit/lifecycle.spec.ts @@ -1,6 +1,6 @@ import { mount } from '@vue/test-utils'; import { createRouter, createWebHistory } from '@ionic/vue-router'; -import { IonicVue, IonApp, IonRouterOutlet, IonPage } from '@ionic/vue'; +import { IonicVue, IonApp, IonRouterOutlet, IonTabs, IonPage } from '@ionic/vue'; import { defineComponent } from 'vue'; import { waitForRouter } from './utils'; @@ -40,19 +40,19 @@ const Page2 = defineComponent({ ionViewDidLeave: jest.fn(), }); -const router = createRouter({ - history: createWebHistory(process.env.BASE_URL), - routes: [ - { path: '/', component: Page1 }, - { path: '/2', component: Page2 } - ] -}); - describe('Lifecycle Events', () => { beforeAll(() => { (HTMLElement.prototype as HTMLIonRouterOutletElement).commit = jest.fn(); }); it('Triggers lifecycle events', async () => { + const router = createRouter({ + history: createWebHistory(process.env.BASE_URL), + routes: [ + { path: '/', component: Page1 }, + { path: '/2', component: Page2 } + ] + }); + // Initial render router.push('/'); await router.isReady(); @@ -105,4 +105,117 @@ describe('Lifecycle Events', () => { expect(Page2.ionViewDidLeave).not.toHaveBeenCalled(); expect(wrapper.html()).toContain('page2'); }); + it('should fire lifecycle events on inner tab page', async () => { + const TabsPage = { + template: ` + + + + + + `, + components: { IonPage, IonTabs, IonRouterOutlet }, + ionViewWillEnter: jest.fn(), + ionViewDidEnter: jest.fn(), + ionViewWillLeave: jest.fn(), + ionViewDidLeave: jest.fn(), + } + const Tab1Page = { + ...BasePage, + ionViewWillEnter: jest.fn(), + ionViewDidEnter: jest.fn(), + ionViewWillLeave: jest.fn(), + ionViewDidLeave: jest.fn(), + } + + const NonTabPage = { + ...BasePage, + ionViewWillEnter: jest.fn(), + ionViewDidEnter: jest.fn(), + ionViewWillLeave: jest.fn(), + ionViewDidLeave: jest.fn(), + } + + + const router = createRouter({ + history: createWebHistory(process.env.BASE_URL), + routes: [ + { path: '/', component: TabsPage, children: [ + { path: 'tab1', component: Tab1Page } + ]}, + { path: '/non-tab', component: NonTabPage } + ] + }); + + // Initial render + router.push('/tab1'); + await router.isReady(); + const wrapper = mount(App, { + global: { + plugins: [router, IonicVue] + } + }); + + // Initial load + expect(TabsPage.ionViewWillEnter).toHaveBeenCalled(); + expect(TabsPage.ionViewDidEnter).toHaveBeenCalled(); + + expect(Tab1Page.ionViewWillEnter).toHaveBeenCalled(); + expect(Tab1Page.ionViewDidEnter).toHaveBeenCalled(); + + expect(NonTabPage.ionViewWillEnter).not.toHaveBeenCalled(); + expect(NonTabPage.ionViewDidEnter).not.toHaveBeenCalled(); + + // Navigate out of tabs + router.push('/non-tab'); + jest.resetAllMocks(); + await waitForRouter(); + + expect(TabsPage.ionViewWillLeave).toHaveBeenCalled(); + expect(TabsPage.ionViewDidLeave).toHaveBeenCalled(); + + // Tab1Page currently does not call leaving hooks + // when navigating out of tabs + //expect(Tab1Page.ionViewWillLeave).toHaveBeenCalled(); + //expect(Tab1Page.ionViewDidLeave).toHaveBeenCalled(); + + expect(NonTabPage.ionViewWillEnter).toHaveBeenCalled(); + expect(NonTabPage.ionViewDidEnter).toHaveBeenCalled(); + + // Go back + router.back(); + jest.resetAllMocks(); + await waitForRouter(); + + expect(TabsPage.ionViewWillEnter).toHaveBeenCalled(); + expect(TabsPage.ionViewDidEnter).toHaveBeenCalled(); + + expect(Tab1Page.ionViewWillEnter).toHaveBeenCalled(); + expect(Tab1Page.ionViewDidEnter).toHaveBeenCalled(); + + expect(NonTabPage.ionViewWillLeave).toHaveBeenCalled(); + expect(NonTabPage.ionViewDidLeave).toHaveBeenCalled(); + + // Navigate out of tabs again + router.push('/non-tab'); + jest.resetAllMocks(); + await waitForRouter(); + + expect(TabsPage.ionViewWillLeave).toHaveBeenCalled(); + expect(TabsPage.ionViewDidLeave).toHaveBeenCalled(); + + expect(NonTabPage.ionViewWillEnter).toHaveBeenCalled(); + expect(NonTabPage.ionViewDidEnter).toHaveBeenCalled(); + + // Go back again + router.back(); + jest.resetAllMocks(); + await waitForRouter(); + + expect(TabsPage.ionViewWillEnter).toHaveBeenCalled(); + expect(TabsPage.ionViewDidEnter).toHaveBeenCalled(); + + expect(NonTabPage.ionViewWillLeave).toHaveBeenCalled(); + expect(NonTabPage.ionViewDidLeave).toHaveBeenCalled(); + }) });