diff --git a/packages/vue/src/components/IonRouterOutlet.ts b/packages/vue/src/components/IonRouterOutlet.ts
index 6025cab342..2f3d1e373a 100644
--- a/packages/vue/src/components/IonRouterOutlet.ts
+++ b/packages/vue/src/components/IonRouterOutlet.ts
@@ -238,7 +238,11 @@ export const IonRouterOutlet = defineComponent({
leavingEl.classList.add('ion-page-hidden');
leavingEl.setAttribute('aria-hidden', 'true');
- if (!(routerAction === 'push' && routerDirection === 'forward')) {
+ if (routerAction === 'replace') {
+ leavingViewItem.mount = false;
+ leavingViewItem.ionPageElement = undefined;
+ leavingViewItem.ionRoute = false;
+ } else if (!(routerAction === 'push' && routerDirection === 'forward')) {
const shouldLeavingViewBeRemoved = routerDirection !== 'none' && leavingViewItem && (enteringViewItem !== leavingViewItem);
if (shouldLeavingViewBeRemoved) {
leavingViewItem.mount = false;
diff --git a/packages/vue/test-app/tests/e2e/specs/routing.js b/packages/vue/test-app/tests/e2e/specs/routing.js
index 8505fa9d96..fa14197da0 100644
--- a/packages/vue/test-app/tests/e2e/specs/routing.js
+++ b/packages/vue/test-app/tests/e2e/specs/routing.js
@@ -114,7 +114,7 @@ describe('Routing', () => {
cy.get('#replace').click();
cy.ionPageVisible('navigation');
- cy.ionPageHidden('routing');
+ cy.ionPageDoesNotExist('routing');
cy.ionSwipeToGoBack(true);
cy.ionPageVisible('navigation');
diff --git a/packages/vue/test-app/tests/unit/lifecycle.spec.ts b/packages/vue/test-app/tests/unit/lifecycle.spec.ts
index 5a0ea158b0..d1999ae8dc 100644
--- a/packages/vue/test-app/tests/unit/lifecycle.spec.ts
+++ b/packages/vue/test-app/tests/unit/lifecycle.spec.ts
@@ -1,7 +1,8 @@
-import { mount, flushPromises } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
import { createRouter, createWebHistory } from '@ionic/vue-router';
import { IonicVue, IonApp, IonRouterOutlet, IonPage } from '@ionic/vue';
import { defineComponent } from 'vue';
+import { waitForRouter } from './utils';
const App = {
components: { IonApp, IonRouterOutlet },
@@ -48,6 +49,9 @@ const router = createRouter({
});
describe('Lifecycle Events', () => {
+ beforeAll(() => {
+ (HTMLElement.prototype as HTMLIonRouterOutletElement).commit = jest.fn();
+ });
it('Triggers lifecycle events', async () => {
// Initial render
router.push('/');
@@ -78,10 +82,7 @@ describe('Lifecycle Events', () => {
// Navigate to 2nd page
router.push('/2');
jest.resetAllMocks();
- await flushPromises();
-
- (HTMLElement.prototype as HTMLIonRouterOutletElement).commit = jest.fn();
- await new Promise((r) => setTimeout(r, 100));
+ await waitForRouter();
// Page 1 lifecycle hooks
expect(Page1.ionViewDidEnter).not.toHaveBeenCalled();
diff --git a/packages/vue/test-app/tests/unit/routing.spec.ts b/packages/vue/test-app/tests/unit/routing.spec.ts
index 20f5c7b695..edfb046a20 100644
--- a/packages/vue/test-app/tests/unit/routing.spec.ts
+++ b/packages/vue/test-app/tests/unit/routing.spec.ts
@@ -1,7 +1,17 @@
-import { mount, flushPromises } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
import { createRouter, createWebHistory } from '@ionic/vue-router';
-import { IonicVue, IonApp, IonRouterOutlet, IonPage, IonTabs, IonTabBar } from '@ionic/vue';
+import {
+ IonicVue,
+ IonApp,
+ IonRouterOutlet,
+ IonPage,
+ IonTabs,
+ IonTabBar,
+ IonTabButton,
+ IonLabel
+} from '@ionic/vue';
import { onBeforeRouteLeave } from 'vue-router';
+import { waitForRouter } from './utils';
const App = {
components: { IonApp, IonRouterOutlet },
@@ -14,6 +24,9 @@ const BasePage = {
}
describe('Routing', () => {
+ beforeAll(() => {
+ (HTMLElement.prototype as HTMLIonRouterOutletElement).commit = jest.fn();
+ });
it('should pass no props', async () => {
const Page1 = {
...BasePage,
@@ -153,8 +166,83 @@ describe('Routing', () => {
// Navigate to 2nd page
router.push('/page2');
- await flushPromises();
+ 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 },
+ template: `
+
+
+
+
+ Tab 1
+
+
+ Tab 2
+
+
+
+
+ `,
+ }
+ const Tab1 = {
+ components: { IonPage },
+ template: `Tab 1`
+ }
+ const Tab2 = {
+ components: { IonPage },
+ template: `Tab 2`
+ }
+ const Parent = {
+ ...BasePage,
+ 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);
+ });
});
diff --git a/packages/vue/test-app/tests/unit/tabs.spec.ts b/packages/vue/test-app/tests/unit/tabs.spec.ts
index 0262da6d34..2c13e237dd 100644
--- a/packages/vue/test-app/tests/unit/tabs.spec.ts
+++ b/packages/vue/test-app/tests/unit/tabs.spec.ts
@@ -1,6 +1,7 @@
-import { mount, flushPromises } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
import { createRouter, createWebHistory } from '@ionic/vue-router';
import { IonicVue, IonApp, IonRouterOutlet, IonPage, IonTabs, IonTabBar, IonTabButton, IonLabel } from '@ionic/vue';
+import { waitForRouter } from './utils';
const App = {
components: { IonApp, IonRouterOutlet },
@@ -34,8 +35,9 @@ const Tab2 = {
}
describe('ion-tabs', () => {
- (HTMLElement.prototype as HTMLIonRouterOutletElement).commit = jest.fn();
-
+ beforeAll(() => {
+ (HTMLElement.prototype as HTMLIonRouterOutletElement).commit = jest.fn();
+ });
it('should emit will change and did change events when changing tab', async () => {
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
@@ -76,7 +78,7 @@ describe('ion-tabs', () => {
expect(tabs.emitted().ionTabsDidChange[0]).toEqual([{ tab: 'tab1' }]);
router.push('/tab2')
- await flushPromises()
+ await waitForRouter()
expect(tabs.emitted().ionTabsWillChange.length).toEqual(2);
expect(tabs.emitted().ionTabsWillChange[1]).toEqual([{ tab: 'tab2' }]);
@@ -124,7 +126,7 @@ describe('ion-tabs', () => {
expect(tabs.emitted().ionTabsDidChange[0]).toEqual([{ tab: 'tab1' }]);
router.push('/tab1')
- await flushPromises()
+ await waitForRouter()
expect(tabs.emitted().ionTabsWillChange.length).toEqual(1);
expect(tabs.emitted().ionTabsDidChange.length).toEqual(1);
@@ -178,9 +180,7 @@ describe('ion-tabs', () => {
expect(tabs.emitted().ionTabsDidChange[0]).toEqual([{ tab: 'tab1' }]);
router.push('/sibling');
- await flushPromises();
-
- await new Promise((r) => setTimeout(r, 100));
+ await waitForRouter()
expect(tabs.emitted().ionTabsWillChange.length).toEqual(1);
expect(tabs.emitted().ionTabsDidChange.length).toEqual(1);
@@ -236,9 +236,7 @@ describe('ion-tabs', () => {
expect(tabs.emitted().ionTabsDidChange[0]).toEqual([{ tab: 'tab1' }]);
router.push('/tab1/child');
- await flushPromises();
-
- await new Promise((r) => setTimeout(r, 100));
+ await waitForRouter()
expect(tabs.emitted().ionTabsWillChange.length).toEqual(1);
expect(tabs.emitted().ionTabsDidChange.length).toEqual(1);
diff --git a/packages/vue/test-app/tests/unit/utils.ts b/packages/vue/test-app/tests/unit/utils.ts
new file mode 100644
index 0000000000..89e52984c2
--- /dev/null
+++ b/packages/vue/test-app/tests/unit/utils.ts
@@ -0,0 +1,6 @@
+import { flushPromises } from '@vue/test-utils';
+
+export const waitForRouter = async () => {
+ await flushPromises();
+ await new Promise((r) => setTimeout(r, 100));
+}