import { enableAutoUnmount, mount } from '@vue/test-utils'; import { afterEach, describe, expect, it, vi } from 'vitest'; import { createRouter, createWebHistory } from '@ionic/vue-router'; import { IonicVue, IonApp, IonRouterOutlet, IonPage, IonTabs, IonTabBar, IonTabButton, IonLabel, useIonRouter } from '@ionic/vue'; import { onBeforeRouteLeave } from 'vue-router'; import { waitForRouter } from './utils'; enableAutoUnmount(afterEach); const App = { components: { IonApp, IonRouterOutlet }, template: '', } const BasePage = { template: '', components: { IonPage }, } describe('Routing', () => { it('should pass no props', async () => { const Page1 = { ...BasePage, name: 'Page1', props: { title: { type: String, default: 'Default Title' } } }; const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes: [ { path: '/', component: Page1 } ] }); router.push('/'); await router.isReady(); const wrapper = mount(App, { global: { plugins: [router, IonicVue] } }); const cmp = wrapper.findComponent(Page1); expect(cmp.props()).toEqual({ title: 'Default Title' }); }); it('should pass route props as an object', async () => { const Page1 = { ...BasePage, name: 'Page1', props: { title: { type: String, default: 'Default Title' } } }; const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes: [ { path: '/', component: Page1, props: { title: 'Page 1 Title' } } ] }); router.push('/'); await router.isReady(); const wrapper = mount(App, { global: { plugins: [router, IonicVue] } }); const cmp = wrapper.findComponent(Page1); expect(cmp.props()).toEqual({ title: 'Page 1 Title' }); }); it('should pass route props as a function', async () => { const Page1 = { ...BasePage, name: 'Page1', props: { title: { type: String, default: 'Default Title' } } }; const propsFn = vi.fn((route) => { return { title: `${route.params.id} Title` } }); const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes: [ { path: '/myPath/:id', component: Page1, props: propsFn }, { path: '/otherPage', component: Page1 } ] }); router.push('/myPath/123'); await router.isReady(); const wrapper = mount(App, { global: { plugins: [router, IonicVue] } }); const cmp = wrapper.findComponent(Page1); expect(cmp.props()).toEqual({ title: '123 Title' }); router.push('/otherPage'); await waitForRouter(); expect(propsFn.mock.calls.length).toBe(1); router.back(); await waitForRouter(); expect(propsFn.mock.calls.length).toBe(1); expect(cmp.props()).toEqual({ title: '123 Title' }); }); it('should pass route params as props', async () => { const Page1 = { ...BasePage, name: 'Page1', props: { title: { type: String, default: 'Default Title' } } }; const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes: [ { path: '/:title', component: Page1, props: true } ] }); router.push('/myPath'); await router.isReady(); const wrapper = mount(App, { global: { plugins: [router, IonicVue] } }); const cmp = wrapper.findComponent(Page1); expect(cmp.props()).toEqual({ title: 'myPath' }); }); it('should call vue router hooks properly', async () => { const leaveHook = vi.fn(); const Page1 = { ...BasePage, name: 'Page1', setup() { onBeforeRouteLeave(leaveHook); } }; const Page2 = { ...BasePage, name: 'Page2', }; const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes: [ { path: '/', component: Page1 }, { path: '/page2', component: Page2 } ] }); router.push('/'); await router.isReady(); const wrapper = mount(App, { global: { plugins: [router, IonicVue] } }); // Navigate to 2nd page router.push('/page2'); 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, IonRouterOutlet }, name: 'Tabs', template: ` Tab 1 Tab 2 `, } const Tab1 = { components: { IonPage }, name: 'Tab1', template: `Tab 1` } const Tab2 = { components: { IonPage }, name: 'Tab2', template: `Tab 2` } const Parent = { ...BasePage, name: 'Parent', 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); }); // Verifies fix for https://github.com/ionic-team/ionic-framework/issues/23043 it('should show the latest props passed to a route', async () => { const Page1 = { ...BasePage, name: 'Page1', props: { title: { type: String, default: 'Default Title' } } }; const Home = { ...BasePage, name: 'Home', } const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes: [ { path: '/', component: Home }, { path: '/:title', component: Page1, props: true } ] }); router.push('/'); await router.isReady(); const wrapper = mount(App, { global: { plugins: [router, IonicVue] } }); router.push('/abc'); await waitForRouter(); const cmp = wrapper.findComponent(Page1); expect(cmp.props()).toEqual({ title: 'abc' }); router.back(); await waitForRouter(); router.push('/xyz'); await waitForRouter(); const cmpAgain = wrapper.findAllComponents(Page1); expect(cmpAgain.length).toEqual(1); expect(cmpAgain[0].props()).toEqual({ title: 'xyz' }); }); // Verifies fix for https://github.com/ionic-team/ionic-framework/issues/23043 it('should call the props function again when params change', async () => { const Page1 = { ...BasePage, name: 'Page1', props: { title: { type: String, default: 'Default Title' } } }; const Home = { ...BasePage, name: 'Home', } const propsFn = vi.fn((route) => { return { title: `${route.params.id} Title` } }); const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes: [ { path: '/myPath/:id', component: Page1, props: propsFn }, { path: '/', component: Home } ] }); router.push('/'); await router.isReady(); const wrapper = mount(App, { global: { plugins: [router, IonicVue] } }); router.push('/myPath/123'); await waitForRouter(); const cmp = wrapper.findComponent(Page1); expect(propsFn.mock.calls.length).toBe(1); expect(cmp.props()).toEqual({ title: '123 Title' }); router.back(); await waitForRouter(); router.push('/myPath/abc'); await waitForRouter(); expect(propsFn.mock.calls.length).toBe(2); const cmpAgain = wrapper.findAllComponents(Page1); expect(cmpAgain.length).toEqual(1); expect(cmpAgain[0].props()).toEqual({ title: 'abc Title' }); }); // Verifies fix for https://github.com/ionic-team/ionic-framework/pull/23189 it('should update props on a parameterized url', async () => { const Page = { name: 'Page', props: { id: { type: String, default: 'Default ID' } }, components: { IonPage }, template: `{{ $props.id }}` } const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes: [ { path: '/page/:id', component: Page, props: true }, { path: '/', redirect: '/page/1' } ] }); router.push('/'); await router.isReady(); const wrapper = mount(App, { global: { plugins: [router, IonicVue] } }); const page = wrapper.findComponent(Page); expect(page.props()).toEqual({ id: '1' }); router.push('/page/2'); await waitForRouter(); const pageAgain = wrapper.findAllComponents(Page); expect(pageAgain[0].props()).toEqual({ id: '1' }); expect(pageAgain[1].props()).toEqual({ id: '2' }); }); it('should fire guard written in a component', async () => { const beforeRouteEnterSpy = vi.fn(); const beforeRouteLeaveSpy = vi.fn(); const Page = { beforeRouteEnter() { beforeRouteEnterSpy(); }, beforeRouteLeave() { beforeRouteLeaveSpy(); }, name: 'Page', components: { IonPage }, template: `` } const Page2 = { components: { IonPage }, name: 'Page2', template: `` } const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes: [ { path: '/page', component: Page }, { path: '/page2', component: Page2 }, { path: '/', redirect: '/page' } ] }); router.push('/'); await router.isReady(); const wrapper = mount(App, { global: { plugins: [router, IonicVue] } }); expect(beforeRouteEnterSpy).toHaveBeenCalledTimes(1); router.push('/page2'); await waitForRouter(); expect(beforeRouteLeaveSpy).toHaveBeenCalledTimes(1); router.back(); await waitForRouter(); expect(beforeRouteEnterSpy).toHaveBeenCalledTimes(2); }); // Verifies fix for https://github.com/ionic-team/ionic-framework/issues/24109 it('canGoBack() should return the correct value', async () => { const Page = { components: { IonPage }, name: 'Page', template: `` } const Page2 = { components: { IonPage }, name: 'Page2', template: `` } const AppWithInject = { components: { IonApp, IonRouterOutlet }, name: 'AppWithInject', template: '', setup() { const ionRouter = useIonRouter(); return { ionRouter } } } const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes: [ { path: '/', component: Page }, { path: '/page2', component: Page2 } ] }); router.push('/'); await router.isReady(); const wrapper = mount(AppWithInject, { global: { plugins: [router, IonicVue] } }); const ionRouter = wrapper.vm.ionRouter; expect(ionRouter.canGoBack()).toEqual(false); router.push('/page2'); await waitForRouter(); expect(ionRouter.canGoBack()).toEqual(true); router.back(); await waitForRouter(); expect(ionRouter.canGoBack()).toEqual(false); }); // Verifies fix for https://github.com/ionic-team/ionic-framework/issues/24109 it('canGoBack() should return the correct value when using router.go', async () => { const Page = { components: { IonPage }, name: 'Page', template: `` } const Page2 = { components: { IonPage }, name: 'Page2', template: `` } const Page3 = { components: { IonPage }, name: 'Page3', template: `` } const AppWithInject = { components: { IonApp, IonRouterOutlet }, name: 'AppWithInject', template: '', setup() { const ionRouter = useIonRouter(); return { ionRouter } } } const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes: [ { path: '/', component: Page }, { path: '/page2', component: Page2 }, { path: '/page3', component: Page3 }, ] }); router.push('/'); await router.isReady(); const wrapper = mount(AppWithInject, { global: { plugins: [router, IonicVue] } }); const ionRouter = wrapper.vm.ionRouter; expect(ionRouter.canGoBack()).toEqual(false); router.push('/page2'); await waitForRouter(); expect(ionRouter.canGoBack()).toEqual(true); router.push('/page3'); await waitForRouter(); expect(ionRouter.canGoBack()).toEqual(true); router.go(-2); await waitForRouter(); expect(ionRouter.canGoBack()).toEqual(false); router.go(2); await waitForRouter(); expect(ionRouter.canGoBack()).toEqual(true); }); it('should not mount intermediary components when delta is 1', async () => { const Page = { components: { IonPage }, name: 'Page', template: `` } const Page2 = { components: { IonPage }, name: 'Page2', template: `` } const Page3 = { components: { IonPage }, name: 'Page3', template: `` } const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes: [ { path: '/page', component: Page }, { path: '/page2', component: Page2 }, { path: '/page3', component: Page3 }, { path: '/', redirect: '/page' } ] }); router.push('/'); await router.isReady(); const wrapper = mount(App, { global: { plugins: [router, IonicVue] } }); expect(wrapper.findComponent(Page).exists()).toBe(true); router.push('/page2'); await waitForRouter(); expect(wrapper.findComponent(Page2).exists()).toBe(true); router.back(); await waitForRouter(); expect(wrapper.findComponent(Page2).exists()).toBe(false); router.push('/page3'); await waitForRouter(); expect(wrapper.findComponent(Page2).exists()).toBe(false); expect(wrapper.findComponent(Page3).exists()).toBe(true); }); it('should unmount intermediary components when using router.go', async () => { const Page = { components: { IonPage }, name: 'Page', template: `` } const Page2 = { components: { IonPage }, name: 'Page2', template: `` } const Page3 = { components: { IonPage }, name: 'Page3', template: `` } const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes: [ { path: '/page', component: Page }, { path: '/page2', component: Page2 }, { path: '/page3', component: Page3 }, { path: '/', redirect: '/page' } ] }); router.push('/'); await router.isReady(); const wrapper = mount(App, { global: { plugins: [router, IonicVue] } }); router.push('/page2'); await waitForRouter(); router.push('/page3'); await waitForRouter(); expect(wrapper.findComponent(Page2).exists()).toBe(true); expect(wrapper.findComponent(Page3).exists()).toBe(true); router.go(-2); await waitForRouter(); expect(wrapper.findComponent(Page).exists()).toBe(true); expect(wrapper.findComponent(Page2).exists()).toBe(false); expect(wrapper.findComponent(Page3).exists()).toBe(false); }); });