fix(vue): correctly remove old view when replacing route (#22566)

resolves #22492
This commit is contained in:
Liam DeBeasi
2020-11-30 11:00:14 -05:00
committed by GitHub
parent 9d04c127e8
commit 4f4f31b65e
6 changed files with 118 additions and 21 deletions

View File

@ -238,7 +238,11 @@ export const IonRouterOutlet = defineComponent({
leavingEl.classList.add('ion-page-hidden'); leavingEl.classList.add('ion-page-hidden');
leavingEl.setAttribute('aria-hidden', 'true'); 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); const shouldLeavingViewBeRemoved = routerDirection !== 'none' && leavingViewItem && (enteringViewItem !== leavingViewItem);
if (shouldLeavingViewBeRemoved) { if (shouldLeavingViewBeRemoved) {
leavingViewItem.mount = false; leavingViewItem.mount = false;

View File

@ -114,7 +114,7 @@ describe('Routing', () => {
cy.get('#replace').click(); cy.get('#replace').click();
cy.ionPageVisible('navigation'); cy.ionPageVisible('navigation');
cy.ionPageHidden('routing'); cy.ionPageDoesNotExist('routing');
cy.ionSwipeToGoBack(true); cy.ionSwipeToGoBack(true);
cy.ionPageVisible('navigation'); cy.ionPageVisible('navigation');

View File

@ -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 { createRouter, createWebHistory } from '@ionic/vue-router';
import { IonicVue, IonApp, IonRouterOutlet, IonPage } from '@ionic/vue'; import { IonicVue, IonApp, IonRouterOutlet, IonPage } from '@ionic/vue';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { waitForRouter } from './utils';
const App = { const App = {
components: { IonApp, IonRouterOutlet }, components: { IonApp, IonRouterOutlet },
@ -48,6 +49,9 @@ const router = createRouter({
}); });
describe('Lifecycle Events', () => { describe('Lifecycle Events', () => {
beforeAll(() => {
(HTMLElement.prototype as HTMLIonRouterOutletElement).commit = jest.fn();
});
it('Triggers lifecycle events', async () => { it('Triggers lifecycle events', async () => {
// Initial render // Initial render
router.push('/'); router.push('/');
@ -78,10 +82,7 @@ describe('Lifecycle Events', () => {
// Navigate to 2nd page // Navigate to 2nd page
router.push('/2'); router.push('/2');
jest.resetAllMocks(); jest.resetAllMocks();
await flushPromises(); await waitForRouter();
(HTMLElement.prototype as HTMLIonRouterOutletElement).commit = jest.fn();
await new Promise((r) => setTimeout(r, 100));
// Page 1 lifecycle hooks // Page 1 lifecycle hooks
expect(Page1.ionViewDidEnter).not.toHaveBeenCalled(); expect(Page1.ionViewDidEnter).not.toHaveBeenCalled();

View File

@ -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 { 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 { onBeforeRouteLeave } from 'vue-router';
import { waitForRouter } from './utils';
const App = { const App = {
components: { IonApp, IonRouterOutlet }, components: { IonApp, IonRouterOutlet },
@ -14,6 +24,9 @@ const BasePage = {
} }
describe('Routing', () => { describe('Routing', () => {
beforeAll(() => {
(HTMLElement.prototype as HTMLIonRouterOutletElement).commit = jest.fn();
});
it('should pass no props', async () => { it('should pass no props', async () => {
const Page1 = { const Page1 = {
...BasePage, ...BasePage,
@ -153,8 +166,83 @@ describe('Routing', () => {
// Navigate to 2nd page // Navigate to 2nd page
router.push('/page2'); router.push('/page2');
await flushPromises(); await waitForRouter();
expect(leaveHook).toBeCalled(); 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: `
<ion-page>
<ion-tabs>
<ion-tab-bar slot="top">
<ion-tab-button tab="tab1" href="/tabs/tab1">
<ion-label>Tab 1</ion-label>
</ion-tab-button>
<ion-tab-button tab="tab2" href="/tabs/tab2">
<ion-label>Tab 2</ion-label>
</ion-tab-button>
</ion-tab-bar>
</ion-tabs>
</ion-page>
`,
}
const Tab1 = {
components: { IonPage },
template: `<ion-page>Tab 1</ion-page>`
}
const Tab2 = {
components: { IonPage },
template: `<ion-page>Tab 2</ion-page>`
}
const Parent = {
...BasePage,
template: `<ion-page>Parent Page</ion-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);
});
}); });

View File

@ -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 { createRouter, createWebHistory } from '@ionic/vue-router';
import { IonicVue, IonApp, IonRouterOutlet, IonPage, IonTabs, IonTabBar, IonTabButton, IonLabel } from '@ionic/vue'; import { IonicVue, IonApp, IonRouterOutlet, IonPage, IonTabs, IonTabBar, IonTabButton, IonLabel } from '@ionic/vue';
import { waitForRouter } from './utils';
const App = { const App = {
components: { IonApp, IonRouterOutlet }, components: { IonApp, IonRouterOutlet },
@ -34,8 +35,9 @@ const Tab2 = {
} }
describe('ion-tabs', () => { 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 () => { it('should emit will change and did change events when changing tab', async () => {
const router = createRouter({ const router = createRouter({
history: createWebHistory(process.env.BASE_URL), history: createWebHistory(process.env.BASE_URL),
@ -76,7 +78,7 @@ describe('ion-tabs', () => {
expect(tabs.emitted().ionTabsDidChange[0]).toEqual([{ tab: 'tab1' }]); expect(tabs.emitted().ionTabsDidChange[0]).toEqual([{ tab: 'tab1' }]);
router.push('/tab2') router.push('/tab2')
await flushPromises() await waitForRouter()
expect(tabs.emitted().ionTabsWillChange.length).toEqual(2); expect(tabs.emitted().ionTabsWillChange.length).toEqual(2);
expect(tabs.emitted().ionTabsWillChange[1]).toEqual([{ tab: 'tab2' }]); expect(tabs.emitted().ionTabsWillChange[1]).toEqual([{ tab: 'tab2' }]);
@ -124,7 +126,7 @@ describe('ion-tabs', () => {
expect(tabs.emitted().ionTabsDidChange[0]).toEqual([{ tab: 'tab1' }]); expect(tabs.emitted().ionTabsDidChange[0]).toEqual([{ tab: 'tab1' }]);
router.push('/tab1') router.push('/tab1')
await flushPromises() await waitForRouter()
expect(tabs.emitted().ionTabsWillChange.length).toEqual(1); expect(tabs.emitted().ionTabsWillChange.length).toEqual(1);
expect(tabs.emitted().ionTabsDidChange.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' }]); expect(tabs.emitted().ionTabsDidChange[0]).toEqual([{ tab: 'tab1' }]);
router.push('/sibling'); router.push('/sibling');
await flushPromises(); await waitForRouter()
await new Promise((r) => setTimeout(r, 100));
expect(tabs.emitted().ionTabsWillChange.length).toEqual(1); expect(tabs.emitted().ionTabsWillChange.length).toEqual(1);
expect(tabs.emitted().ionTabsDidChange.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' }]); expect(tabs.emitted().ionTabsDidChange[0]).toEqual([{ tab: 'tab1' }]);
router.push('/tab1/child'); router.push('/tab1/child');
await flushPromises(); await waitForRouter()
await new Promise((r) => setTimeout(r, 100));
expect(tabs.emitted().ionTabsWillChange.length).toEqual(1); expect(tabs.emitted().ionTabsWillChange.length).toEqual(1);
expect(tabs.emitted().ionTabsDidChange.length).toEqual(1); expect(tabs.emitted().ionTabsDidChange.length).toEqual(1);

View File

@ -0,0 +1,6 @@
import { flushPromises } from '@vue/test-utils';
export const waitForRouter = async () => {
await flushPromises();
await new Promise((r) => setTimeout(r, 100));
}