From b9245895a52626254f71f4a2b385dd0e0aeea30d Mon Sep 17 00:00:00 2001 From: ShaneK Date: Thu, 14 May 2026 09:01:52 -0700 Subject: [PATCH 1/2] fix(vue-router): invoke beforeRouteEnter next() callbacks with component instance --- packages/vue-router/src/viewStacks.ts | 22 +++++++++- .../vue/test/base/tests/unit/routing.spec.ts | 44 +++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/packages/vue-router/src/viewStacks.ts b/packages/vue-router/src/viewStacks.ts index 8c9f0cbefca..f0a49b4b212 100644 --- a/packages/vue-router/src/viewStacks.ts +++ b/packages/vue-router/src/viewStacks.ts @@ -34,9 +34,29 @@ export const createViewStacks = (router: Router) => { * and will not run route guards that * are written in the component. */ + const instance = viewItem.vueComponentRef.value; viewItem.matchedRoute.instances = { - default: viewItem.vueComponentRef.value, + default: instance, }; + + /** + * Run any callbacks passed to `next()` inside a + * `beforeRouteEnter` guard now that the component + * instance is available. Vue Router's official + * `` does this in a post-flush watcher; + * because IonRouterOutlet manages rendering itself, + * the wrapper must invoke them explicitly. + */ + const enterCallbacks = viewItem.matchedRoute.enterCallbacks?.default; + if (instance && enterCallbacks && enterCallbacks.length > 0) { + /** + * Clear before invoking so any synchronous navigation + * triggered by a callback can push fresh callbacks onto + * the array without being wiped by a post-iteration reset. + */ + viewItem.matchedRoute.enterCallbacks.default = []; + enterCallbacks.forEach((cb) => cb(instance)); + } }; const findViewItemByRouteInfo = (routeInfo: RouteInfo, outletId?: number) => { diff --git a/packages/vue/test/base/tests/unit/routing.spec.ts b/packages/vue/test/base/tests/unit/routing.spec.ts index 3d1f50a428f..d03d00ee250 100644 --- a/packages/vue/test/base/tests/unit/routing.spec.ts +++ b/packages/vue/test/base/tests/unit/routing.spec.ts @@ -464,6 +464,50 @@ describe('Routing', () => { expect(beforeRouteEnterSpy).toHaveBeenCalledTimes(2); }); + // Verifies fix for https://github.com/ionic-team/ionic-framework/issues/25013 + it('should run beforeRouteEnter next() callback with the component instance', async () => { + const enterCallbackSpy = vi.fn(); + const Page = { + data() { + return { member: 0 }; + }, + beforeRouteEnter(_to: any, _from: any, next: (cb: (vm: any) => void) => void) { + next((vm: any) => { + enterCallbackSpy(vm); + vm.member = 5; + }); + }, + name: 'PageWithEnterCb', + components: { IonPage }, + template: `{{ member }}` + }; + + const router = createRouter({ + history: createWebHistory(process.env.BASE_URL), + routes: [ + { path: '/', component: { template: '', components: { IonPage } } }, + { path: '/page', component: Page }, + ] + }); + + router.push('/'); + await router.isReady(); + const wrapper = mount(IonRouterOutlet, { + global: { + plugins: [router, IonicVue] + } + }); + + router.push('/page'); + await waitForRouter(); + + expect(enterCallbackSpy).toHaveBeenCalledTimes(1); + const instance = enterCallbackSpy.mock.calls[0][0]; + expect(instance).toBeDefined(); + expect(instance.member).toBe(5); + expect(wrapper.find('[data-test="member"]').text()).toBe('5'); + }); + // Verifies fix for https://github.com/ionic-team/ionic-framework/issues/24109 it('canGoBack() should return the correct value', async () => { const Page = { From 313388dc99c6765de40bcb30916f3c135f4e40ea Mon Sep 17 00:00:00 2001 From: ShaneK Date: Fri, 15 May 2026 09:09:23 -0700 Subject: [PATCH 2/2] chore(test): adding path for manual verification --- packages/vue/test/base/src/router/index.ts | 4 ++ packages/vue/test/base/src/views/Routing.vue | 4 ++ .../vue/test/base/src/views/RoutingGuards.vue | 69 +++++++++++++++++++ 3 files changed, 77 insertions(+) create mode 100644 packages/vue/test/base/src/views/RoutingGuards.vue diff --git a/packages/vue/test/base/src/router/index.ts b/packages/vue/test/base/src/router/index.ts index 2e9070e2861..687a1877be5 100644 --- a/packages/vue/test/base/src/router/index.ts +++ b/packages/vue/test/base/src/router/index.ts @@ -69,6 +69,10 @@ const routes: Array = [ path: '/routing/child', component: () => import('@/views/RoutingChild.vue') }, + { + path: '/routing/guards', + component: () => import('@/views/RoutingGuards.vue') + }, { path: '/routing/:id', component: () => import('@/views/RoutingParameter.vue'), diff --git a/packages/vue/test/base/src/views/Routing.vue b/packages/vue/test/base/src/views/Routing.vue index 78f50a5deaa..7b8ac4b84fa 100644 --- a/packages/vue/test/base/src/views/Routing.vue +++ b/packages/vue/test/base/src/views/Routing.vue @@ -24,6 +24,10 @@ Go to Child Page + + Go to Route Guards Page + + Go to Parameter Page ABC diff --git a/packages/vue/test/base/src/views/RoutingGuards.vue b/packages/vue/test/base/src/views/RoutingGuards.vue new file mode 100644 index 00000000000..6425d56dc97 --- /dev/null +++ b/packages/vue/test/base/src/views/RoutingGuards.vue @@ -0,0 +1,69 @@ + + +