diff --git a/packages/vue/src/components/IonRouterOutlet.ts b/packages/vue/src/components/IonRouterOutlet.ts index 81b4e0fc6e1..dc934000fd6 100644 --- a/packages/vue/src/components/IonRouterOutlet.ts +++ b/packages/vue/src/components/IonRouterOutlet.ts @@ -500,7 +500,21 @@ See https://ionicframework.com/docs/vue/navigation#ionpage for more information. if (!enteringViewItem.mount) { enteringViewItem.mount = true; enteringViewItem.registerCallback = () => { - handlePageTransition(); + const currentViewItem = viewStacks.findViewItemByRouteInfo( + ionRouter.getCurrentRouteInfo(), + id + ); + + /** + * If the route moved on while this page was mounting, + * the newer page runs the transition once it registers. + */ + if ( + currentViewItem === enteringViewItem || + !currentViewItem?.registerCallback + ) { + handlePageTransition(); + } enteringViewItem.registerCallback = undefined; }; } else { diff --git a/packages/vue/test/base/tests/unit/routing.spec.ts b/packages/vue/test/base/tests/unit/routing.spec.ts index ffa5985f4ea..2e03aed19ad 100644 --- a/packages/vue/test/base/tests/unit/routing.spec.ts +++ b/packages/vue/test/base/tests/unit/routing.spec.ts @@ -17,7 +17,7 @@ import { NavigationFailureType, onBeforeRouteLeave } from 'vue-router'; -import { inject } from 'vue'; +import { defineAsyncComponent, inject } from 'vue'; import { waitForRouter } from './utils'; enableAutoUnmount(afterEach); @@ -2323,4 +2323,57 @@ describe('Routing', () => { { id: 'list', hidden: false } ]); }); + + it('should not run the transition of a newer page when a superseded page mounts first', async () => { + const createLoadingPage = (id: string) => { + let load = () => {}; + const page = defineAsyncComponent( + () => new Promise>((resolve) => { + load = () => resolve(createPage(id)); + }) + ); + + return { page, load: () => load() }; + }; + + const list = createLoadingPage('list'); + const detail = createLoadingPage('detail'); + + const router = createRouter({ + history: createWebHistory(process.env.BASE_URL), + routes: [ + { path: '/', component: createPage('home') }, + { path: '/list', component: list.page }, + { path: '/detail', component: detail.page } + ] + }); + + router.push('/'); + await router.isReady(); + const wrapper = mount(IonRouterOutlet, { + global: { + plugins: [router, IonicVue] + } + }); + + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + router.push('/list'); + await waitForRouter(); + router.push('/detail'); + await waitForRouter(); + + // /list finishes loading after the route moved on to /detail. + list.load(); + await waitForRouter(); + detail.load(); + await waitForRouter(); + + expect(warnSpy).not.toHaveBeenCalledWith( + expect.stringContaining('does not have the required component') + ); + expect(viewStack(wrapper)).toContainEqual({ id: 'detail', hidden: false }); + + warnSpy.mockRestore(); + }); });