diff --git a/packages/vue-router/src/router.ts b/packages/vue-router/src/router.ts
index 7045dad5947..940f4c2aa42 100644
--- a/packages/vue-router/src/router.ts
+++ b/packages/vue-router/src/router.ts
@@ -776,6 +776,16 @@ export const createIonRouter = (
const routeInfo = locationHistory.getFirstRouteInfoForTab(tab);
if (routeInfo) {
const delta = routeInfo.position! - currentHistoryPosition;
+ /**
+ * Memory history doesn't store a position in `history.state`, so
+ * `delta` is NaN and there's nothing to traverse. Replace instead.
+ */
+ if (Number.isNaN(delta)) {
+ if (originalHref) {
+ handleNavigate(originalHref, "pop", "back", undefined, tab);
+ }
+ return;
+ }
if (delta !== 0) {
router.go(delta);
return;
diff --git a/packages/vue/test/base/tests/unit/memory.spec.ts b/packages/vue/test/base/tests/unit/memory.spec.ts
index 5a109446f8e..a1a482d482d 100644
--- a/packages/vue/test/base/tests/unit/memory.spec.ts
+++ b/packages/vue/test/base/tests/unit/memory.spec.ts
@@ -10,6 +10,9 @@ import {
IonApp,
IonRouterOutlet,
IonPage,
+ IonTabs,
+ IonTabBar,
+ IonTabButton,
} from '@ionic/vue';
import { waitForRouter } from './utils';
@@ -112,4 +115,115 @@ describe('createMemoryHistory', () => {
await waitForRouter();
expect(router.currentRoute.value.path).toBe('/page3');
});
+
+ // Verifies fix for https://github.com/ionic-team/ionic-framework/issues/29785
+ describe('tapping the active tab', () => {
+ const Tabs = {
+ components: { IonPage, IonTabs, IonTabBar, IonTabButton, IonRouterOutlet },
+ template: `
+
+
+
+
+ Tab 1
+ Tab 2
+
+
+
+ `,
+ };
+ const Tab1 = {
+ components: { IonPage },
+ template: 'Tab 1',
+ };
+ const Tab1Child = {
+ components: { IonPage },
+ template: 'Tab 1 Child',
+ };
+ const Tab2 = {
+ components: { IonPage },
+ template: 'Tab 2',
+ };
+
+ const createTabsRouter = () =>
+ createRouter({
+ history: createMemoryHistory(process.env.BASE_URL),
+ routes: [
+ { path: '/', redirect: '/tabs/tab1' },
+ {
+ path: '/tabs/',
+ component: Tabs,
+ children: [
+ { path: 'tab1', component: Tab1 },
+ { path: 'tab1/child', component: Tab1Child },
+ { path: 'tab2', component: Tab2 },
+ ],
+ },
+ ],
+ });
+
+ const tapTab = async (wrapper: ReturnType, tab: string) => {
+ const button = wrapper
+ .findAllComponents(IonTabButton)
+ .find((b) => b.props('tab') === tab)!;
+ await button.trigger('click');
+ await waitForRouter();
+ };
+
+ it('should return to the tab root from a child page', async () => {
+ const router = createTabsRouter();
+
+ router.push('/tabs/tab1');
+ await router.isReady();
+ const wrapper = mount(App, {
+ global: { plugins: [router, IonicVue] },
+ });
+ await waitForRouter();
+
+ router.push('/tabs/tab1/child');
+ await waitForRouter();
+ expect(router.currentRoute.value.path).toBe('/tabs/tab1/child');
+
+ await tapTab(wrapper, 'tab1');
+
+ expect(router.currentRoute.value.path).toBe('/tabs/tab1');
+ });
+
+ it('should return to the tab root when the app started on a child page', async () => {
+ const router = createTabsRouter();
+
+ router.push('/tabs/tab1/child');
+ await router.isReady();
+ const wrapper = mount(App, {
+ global: { plugins: [router, IonicVue] },
+ });
+ await waitForRouter();
+
+ await tapTab(wrapper, 'tab1');
+
+ expect(router.currentRoute.value.path).toBe('/tabs/tab1');
+ });
+
+ it('should replace the child page entry rather than push over it', async () => {
+ const router = createTabsRouter();
+
+ router.push('/tabs/tab1');
+ await router.isReady();
+ const wrapper = mount(App, {
+ global: { plugins: [router, IonicVue] },
+ });
+ await waitForRouter();
+
+ router.push('/tabs/tab1/child');
+ await waitForRouter();
+
+ await tapTab(wrapper, 'tab1');
+ expect(router.currentRoute.value.path).toBe('/tabs/tab1');
+
+ router.back();
+ await waitForRouter();
+
+ expect(router.currentRoute.value.path).toBe('/tabs/tab1');
+ });
+ });
})