import { mount } from '@vue/test-utils'; import { describe, expect, it, vi } from 'vitest'; import { createRouter, createWebHistory } from '@ionic/vue-router'; import { IonicVue, IonRouterOutlet, IonTabs, IonPage } from '@ionic/vue'; import { defineComponent } from 'vue'; import { waitForRouter } from './utils'; const BasePage = { template: '', components: { IonPage }, } const Page1 = { ...BasePage, data() { return { name: 'page1' } }, ionViewWillEnter: vi.fn(), ionViewDidEnter: vi.fn(), ionViewWillLeave: vi.fn(), ionViewDidLeave: vi.fn(), } const Page2 = defineComponent({ ...BasePage, setup() { return { name: 'page2' } }, ionViewWillEnter: vi.fn(), ionViewDidEnter: vi.fn(), ionViewWillLeave: vi.fn(), ionViewDidLeave: vi.fn(), }); describe('Lifecycle Events', () => { 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(); const wrapper = mount(IonRouterOutlet, { global: { plugins: [router, IonicVue] } }); // Page 1 lifecycle hooks expect(Page1.ionViewWillEnter).toHaveBeenCalledWith(); expect(Page1.ionViewWillEnter.mock.instances[0]).toEqual(expect.objectContaining({ name: 'page1' })) expect(Page1.ionViewDidEnter).toHaveBeenCalled(); expect(Page1.ionViewDidEnter.mock.instances[0]).toEqual(expect.objectContaining({ name: 'page1' })) expect(Page1.ionViewWillLeave).not.toHaveBeenCalled(); expect(Page1.ionViewDidLeave).not.toHaveBeenCalled(); expect(wrapper.html()).toContain('page1'); // Page 2 lifecycle hooks expect(Page2.ionViewWillEnter).not.toHaveBeenCalled(); expect(Page2.ionViewDidEnter).not.toHaveBeenCalled(); expect(Page2.ionViewWillLeave).not.toHaveBeenCalled(); expect(Page2.ionViewDidLeave).not.toHaveBeenCalled(); // Navigate to 2nd page router.push('/2'); vi.resetAllMocks(); await waitForRouter(); // Page 1 lifecycle hooks expect(Page1.ionViewDidEnter).not.toHaveBeenCalled(); expect(Page1.ionViewWillEnter).not.toHaveBeenCalled(); expect(Page1.ionViewWillLeave).toHaveBeenCalled(); expect(Page1.ionViewWillLeave.mock.instances[0]).toEqual(expect.objectContaining({ name: 'page1' })) expect(Page1.ionViewDidLeave).toHaveBeenCalled(); expect(Page1.ionViewDidLeave.mock.instances[0]).toEqual(expect.objectContaining({ name: 'page1' })) // Page 2 lifecycle hooks expect(Page2.ionViewWillEnter).toHaveBeenCalled(); expect((Page2.ionViewWillEnter as vi.Mock).mock.instances[0]).toEqual(expect.objectContaining({ name: 'page2' })) expect(Page2.ionViewDidEnter).toHaveBeenCalled(); expect((Page2.ionViewDidEnter as vi.Mock).mock.instances[0]).toEqual(expect.objectContaining({ name: 'page2' })) expect(Page2.ionViewWillLeave).not.toHaveBeenCalled(); 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: vi.fn(), ionViewDidEnter: vi.fn(), ionViewWillLeave: vi.fn(), ionViewDidLeave: vi.fn(), } const Tab1Page = { ...BasePage, ionViewWillEnter: vi.fn(), ionViewDidEnter: vi.fn(), ionViewWillLeave: vi.fn(), ionViewDidLeave: vi.fn(), } const NonTabPage = { ...BasePage, ionViewWillEnter: vi.fn(), ionViewDidEnter: vi.fn(), ionViewWillLeave: vi.fn(), ionViewDidLeave: vi.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(IonRouterOutlet, { 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'); vi.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(); vi.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'); vi.resetAllMocks(); await waitForRouter(); expect(TabsPage.ionViewWillLeave).toHaveBeenCalled(); expect(TabsPage.ionViewDidLeave).toHaveBeenCalled(); expect(NonTabPage.ionViewWillEnter).toHaveBeenCalled(); expect(NonTabPage.ionViewDidEnter).toHaveBeenCalled(); // Go back again router.back(); vi.resetAllMocks(); await waitForRouter(); expect(TabsPage.ionViewWillEnter).toHaveBeenCalled(); expect(TabsPage.ionViewDidEnter).toHaveBeenCalled(); expect(NonTabPage.ionViewWillLeave).toHaveBeenCalled(); expect(NonTabPage.ionViewDidLeave).toHaveBeenCalled(); }) });