diff --git a/packages/vue/src/components/IonRouterOutlet.ts b/packages/vue/src/components/IonRouterOutlet.ts index 4b4c634e946..1c32de13c01 100644 --- a/packages/vue/src/components/IonRouterOutlet.ts +++ b/packages/vue/src/components/IonRouterOutlet.ts @@ -115,7 +115,15 @@ export const IonRouterOutlet = /*@__PURE__*/ defineComponent({ previousMatchedRouteRef = currentMatchedRouteRef; previousMatchedPath = currentRoute.path; - } + }, + /** + * Future versions of Vue may default watching nested + * reactive objects to "deep: false". + * We explicitly set this watcher to "deep: true" to + * account for that. + * https://github.com/vuejs/core/issues/9965#issuecomment-1875067499 + */ + { deep: true } ); const canStart = () => { diff --git a/packages/vue/src/components/IonTabs.ts b/packages/vue/src/components/IonTabs.ts index a2333695a30..432c2479b59 100644 --- a/packages/vue/src/components/IonTabs.ts +++ b/packages/vue/src/components/IonTabs.ts @@ -6,6 +6,28 @@ const DID_CHANGE = "ionTabsDidChange"; // TODO(FW-2969): types +/** + * Vue 3.2.38 fixed an issue where Web Component + * names are respected using kebab case instead of pascal case. + * As a result, we need to account for both here since we support + * versions of Vue < 3.2.38. + */ +// TODO FW-5904 +const isRouterOutlet = (node: VNode) => { + return ( + node.type && + ((node.type as any).name === "IonRouterOutlet" || + node.type === "ion-router-outlet") + ); +}; + +const isTabBar = (node: VNode) => { + return ( + node.type && + ((node.type as any).name === "IonTabBar" || node.type === "ion-tab-bar") + ); +}; + export const IonTabs = /*@__PURE__*/ defineComponent({ name: "IonTabs", emits: [WILL_CHANGE, DID_CHANGE], @@ -19,9 +41,8 @@ export const IonTabs = /*@__PURE__*/ defineComponent({ * inside of ion-tabs. */ if (slottedContent && slottedContent.length > 0) { - routerOutlet = slottedContent.find( - (child: VNode) => - child.type && (child.type as any).name === "IonRouterOutlet" + routerOutlet = slottedContent.find((child: VNode) => + isRouterOutlet(child) ); } @@ -57,13 +78,11 @@ export const IonTabs = /*@__PURE__*/ defineComponent({ * since that needs to be inside of `.tabs-inner`. */ const filteredContent = slottedContent.filter( - (child: VNode) => - !child.type || - (child.type && (child.type as any).name !== "IonRouterOutlet") + (child: VNode) => !child.type || !isRouterOutlet(child) ); - const slottedTabBar = filteredContent.find( - (child: VNode) => child.type && (child.type as any).name === "IonTabBar" + const slottedTabBar = filteredContent.find((child: VNode) => + isTabBar(child) ); const hasTopSlotTabBar = slottedTabBar && slottedTabBar.props?.slot === "top"; diff --git a/packages/vue/test/apps/vue3/package.json b/packages/vue/test/apps/vue3/package.json index 669d1600d8b..09e9b8fcdf6 100644 --- a/packages/vue/test/apps/vue3/package.json +++ b/packages/vue/test/apps/vue3/package.json @@ -19,8 +19,8 @@ "@ionic/vue": "^6.0.12", "@ionic/vue-router": "^6.0.12", "ionicons": "^6.0.4", - "vue": "^3.2.31", - "vue-router": "^4.0.14" + "vue": "^3.4.14", + "vue-router": "^4.2.5" }, "devDependencies": { "@typescript-eslint/eslint-plugin": "^5.4.0", @@ -35,7 +35,7 @@ "jsdom": "^20.0.0", "typescript": "~4.5.5", "vite": "^3.1.4", - "vitest": "^0.23.4", + "vitest": "^1.2.1", "wait-on": "^5.3.0" }, "engines": { diff --git a/packages/vue/test/base/tests/unit/hooks.spec.ts b/packages/vue/test/base/tests/unit/hooks.spec.ts index b0ef23bed7a..d6657faa6bc 100644 --- a/packages/vue/test/base/tests/unit/hooks.spec.ts +++ b/packages/vue/test/base/tests/unit/hooks.spec.ts @@ -1,14 +1,9 @@ import { mount } from '@vue/test-utils'; import { describe, expect, it } from 'vitest'; import { createRouter, createWebHistory } from '@ionic/vue-router'; -import { IonicVue, IonApp, IonRouterOutlet, IonPage, useIonRouter, createAnimation } from '@ionic/vue'; +import { IonicVue, IonRouterOutlet, IonPage, useIonRouter } from '@ionic/vue'; import { mockAnimation, waitForRouter } from './utils'; -const App = { - components: { IonApp, IonRouterOutlet }, - template: '', -} - const BasePage = { template: '', components: { IonPage }, @@ -40,7 +35,7 @@ describe('useIonRouter', () => { router.push('/'); await router.isReady(); - const wrapper = mount(App, { + const wrapper = mount(IonRouterOutlet, { global: { plugins: [router, IonicVue] } @@ -85,7 +80,7 @@ describe('useIonRouter', () => { router.push('/'); await router.isReady(); - const wrapper = mount(App, { + const wrapper = mount(IonRouterOutlet, { global: { plugins: [router, IonicVue] } @@ -137,7 +132,7 @@ describe('useIonRouter', () => { router.push('/'); await router.isReady(); - const wrapper = mount(App, { + const wrapper = mount(IonRouterOutlet, { global: { plugins: [router, IonicVue] } @@ -181,7 +176,7 @@ describe('useIonRouter', () => { router.push('/'); await router.isReady(); - const wrapper = mount(App, { + const wrapper = mount(IonRouterOutlet, { global: { plugins: [router, IonicVue] } @@ -229,7 +224,7 @@ describe('useIonRouter', () => { router.push('/'); await router.isReady(); - const wrapper = mount(App, { + const wrapper = mount(IonRouterOutlet, { global: { plugins: [router, IonicVue] } diff --git a/packages/vue/test/base/tests/unit/lifecycle.spec.ts b/packages/vue/test/base/tests/unit/lifecycle.spec.ts index f82106ccbe8..3c1996d5e74 100644 --- a/packages/vue/test/base/tests/unit/lifecycle.spec.ts +++ b/packages/vue/test/base/tests/unit/lifecycle.spec.ts @@ -1,15 +1,10 @@ import { mount } from '@vue/test-utils'; import { describe, expect, it, vi } from 'vitest'; import { createRouter, createWebHistory } from '@ionic/vue-router'; -import { IonicVue, IonApp, IonRouterOutlet, IonTabs, IonPage } from '@ionic/vue'; +import { IonicVue, IonRouterOutlet, IonTabs, IonPage } from '@ionic/vue'; import { defineComponent } from 'vue'; import { waitForRouter } from './utils'; -const App = { - components: { IonApp, IonRouterOutlet }, - template: '', -} - const BasePage = { template: '', components: { IonPage }, @@ -54,7 +49,7 @@ describe('Lifecycle Events', () => { // Initial render router.push('/'); await router.isReady(); - const wrapper = mount(App, { + const wrapper = mount(IonRouterOutlet, { global: { plugins: [router, IonicVue] } @@ -108,7 +103,7 @@ describe('Lifecycle Events', () => { template: ` - + `, @@ -148,7 +143,7 @@ describe('Lifecycle Events', () => { // Initial render router.push('/tab1'); await router.isReady(); - const wrapper = mount(App, { + const wrapper = mount(IonRouterOutlet, { global: { plugins: [router, IonicVue] } diff --git a/packages/vue/test/base/tests/unit/page.spec.ts b/packages/vue/test/base/tests/unit/page.spec.ts index cd8917c30bd..4fb0e985f25 100644 --- a/packages/vue/test/base/tests/unit/page.spec.ts +++ b/packages/vue/test/base/tests/unit/page.spec.ts @@ -1,12 +1,7 @@ import { mount } from '@vue/test-utils'; import { describe, expect, it } from 'vitest'; import { createRouter, createWebHistory } from '@ionic/vue-router'; -import { IonicVue, IonApp, IonRouterOutlet, IonPage } from '@ionic/vue'; - -const App = { - components: { IonApp, IonRouterOutlet }, - template: '', -} +import { IonicVue, IonRouterOutlet, IonPage } from '@ionic/vue'; describe('IonPage', () => { it('should add ion-page class', async () => { @@ -25,7 +20,7 @@ describe('IonPage', () => { router.push('/'); await router.isReady(); - const wrapper = mount(App, { + const wrapper = mount(IonRouterOutlet, { global: { plugins: [router, IonicVue] } @@ -50,7 +45,7 @@ describe('IonPage', () => { router.push('/'); await router.isReady(); - const wrapper = mount(App, { + const wrapper = mount(IonRouterOutlet, { global: { plugins: [router, IonicVue] } diff --git a/packages/vue/test/base/tests/unit/router-outlet.spec.ts b/packages/vue/test/base/tests/unit/router-outlet.spec.ts index 89ee04bc559..f25490c2a21 100644 --- a/packages/vue/test/base/tests/unit/router-outlet.spec.ts +++ b/packages/vue/test/base/tests/unit/router-outlet.spec.ts @@ -3,22 +3,14 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; import { createRouter, createWebHistory } from '@ionic/vue-router'; import { IonicVue, - IonApp, IonRouterOutlet, IonPage, useIonRouter, - createAnimation } from '@ionic/vue'; -import { onBeforeRouteLeave } from 'vue-router'; import { mockAnimation, waitForRouter } from './utils'; enableAutoUnmount(afterEach); -const App = { - components: { IonApp, IonRouterOutlet }, - template: '', -} - const BasePage = { template: '', components: { IonPage }, @@ -60,7 +52,7 @@ describe('Routing', () => { router.push('/'); await router.isReady(); - const wrapper = mount(App, { + const wrapper = mount(IonRouterOutlet, { global: { plugins: [router, IonicVue] } @@ -122,7 +114,7 @@ describe('Routing', () => { router.push('/'); await router.isReady(); - const wrapper = mount(App, { + const wrapper = mount(IonRouterOutlet, { global: { plugins: [router, IonicVue] } diff --git a/packages/vue/test/base/tests/unit/routing.spec.ts b/packages/vue/test/base/tests/unit/routing.spec.ts index a4a8f55521a..3d1f50a428f 100644 --- a/packages/vue/test/base/tests/unit/routing.spec.ts +++ b/packages/vue/test/base/tests/unit/routing.spec.ts @@ -17,11 +17,6 @@ import { waitForRouter } from './utils'; enableAutoUnmount(afterEach); -const App = { - components: { IonApp, IonRouterOutlet }, - template: '', -} - const BasePage = { template: '', components: { IonPage }, @@ -46,7 +41,7 @@ describe('Routing', () => { router.push('/'); await router.isReady(); - const wrapper = mount(App, { + const wrapper = mount(IonRouterOutlet, { global: { plugins: [router, IonicVue] } @@ -74,7 +69,7 @@ describe('Routing', () => { router.push('/'); await router.isReady(); - const wrapper = mount(App, { + const wrapper = mount(IonRouterOutlet, { global: { plugins: [router, IonicVue] } @@ -107,7 +102,7 @@ describe('Routing', () => { router.push('/myPath/123'); await router.isReady(); - const wrapper = mount(App, { + const wrapper = mount(IonRouterOutlet, { global: { plugins: [router, IonicVue] } @@ -147,7 +142,7 @@ describe('Routing', () => { router.push('/myPath'); await router.isReady(); - const wrapper = mount(App, { + const wrapper = mount(IonRouterOutlet, { global: { plugins: [router, IonicVue] } @@ -182,7 +177,7 @@ describe('Routing', () => { router.push('/'); await router.isReady(); - const wrapper = mount(App, { + const wrapper = mount(IonRouterOutlet, { global: { plugins: [router, IonicVue] } @@ -203,7 +198,7 @@ describe('Routing', () => { template: ` - + Tab 1 @@ -247,9 +242,9 @@ describe('Routing', () => { ] }); - router.push('/'); + router.push('/tabs/tab1'); await router.isReady(); - const wrapper = mount(App, { + const wrapper = mount(IonRouterOutlet, { global: { plugins: [router, IonicVue] } @@ -302,7 +297,7 @@ describe('Routing', () => { router.push('/'); await router.isReady(); - const wrapper = mount(App, { + const wrapper = mount(IonRouterOutlet, { global: { plugins: [router, IonicVue] } @@ -355,7 +350,7 @@ describe('Routing', () => { router.push('/'); await router.isReady(); - const wrapper = mount(App, { + const wrapper = mount(IonRouterOutlet, { global: { plugins: [router, IonicVue] } @@ -402,7 +397,7 @@ describe('Routing', () => { router.push('/'); await router.isReady(); - const wrapper = mount(App, { + const wrapper = mount(IonRouterOutlet, { global: { plugins: [router, IonicVue] } @@ -450,7 +445,7 @@ describe('Routing', () => { router.push('/'); await router.isReady(); - const wrapper = mount(App, { + const wrapper = mount(IonRouterOutlet, { global: { plugins: [router, IonicVue] } @@ -618,7 +613,7 @@ describe('Routing', () => { router.push('/'); await router.isReady(); - const wrapper = mount(App, { + const wrapper = mount(IonRouterOutlet, { global: { plugins: [router, IonicVue] } @@ -672,7 +667,7 @@ describe('Routing', () => { router.push('/'); await router.isReady(); - const wrapper = mount(App, { + const wrapper = mount(IonRouterOutlet, { global: { plugins: [router, IonicVue] } diff --git a/packages/vue/test/base/tests/unit/tab-bar.spec.ts b/packages/vue/test/base/tests/unit/tab-bar.spec.ts index d1c1037a924..00ded7020b4 100644 --- a/packages/vue/test/base/tests/unit/tab-bar.spec.ts +++ b/packages/vue/test/base/tests/unit/tab-bar.spec.ts @@ -1,12 +1,7 @@ import { mount } from '@vue/test-utils'; import { describe, expect, it } from 'vitest'; import { createRouter, createWebHistory } from '@ionic/vue-router'; -import { IonicVue, IonApp, IonRouterOutlet, IonPage, IonTabs, IonTabBar } from '@ionic/vue'; - -const App = { - components: { IonApp, IonRouterOutlet }, - template: '', -} +import { IonicVue, IonRouterOutlet, IonPage, IonTabs, IonTabBar } from '@ionic/vue'; describe('ion-tab-bar', () => { it('should render in the top slot', async () => { @@ -31,7 +26,7 @@ describe('ion-tab-bar', () => { router.push('/'); await router.isReady(); - const wrapper = mount(App, { + const wrapper = mount(Tabs, { global: { plugins: [router, IonicVue] } @@ -67,7 +62,7 @@ describe('ion-tab-bar', () => { router.push('/'); await router.isReady(); - const wrapper = mount(App, { + const wrapper = mount(Tabs, { global: { plugins: [router, IonicVue] } @@ -102,7 +97,7 @@ describe('ion-tab-bar', () => { router.push('/'); await router.isReady(); - const wrapper = mount(App, { + const wrapper = mount(Tabs, { global: { plugins: [router, IonicVue] } @@ -141,16 +136,15 @@ describe('ion-tab-bar', () => { router.push('/'); await router.isReady(); - const wrapper = mount(App, { + const wrapper = mount(Tabs, { global: { plugins: [router, IonicVue] } }); - const innerHTML = wrapper.find('ion-tabs').html(); - - const tabs = wrapper.findComponent(IonTabBar); - const children = tabs.vm.$el.childNodes; + const tabs = wrapper.findComponent(IonTabs); + const tabbar = tabs.vm.$el.children[1]; + const children = tabbar.childNodes; // 8 is a comment node: https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType expect(children[0].nodeType).toEqual(8); diff --git a/packages/vue/test/base/tests/unit/tabs.spec.ts b/packages/vue/test/base/tests/unit/tabs.spec.ts index 528386b8963..73a82bd8748 100644 --- a/packages/vue/test/base/tests/unit/tabs.spec.ts +++ b/packages/vue/test/base/tests/unit/tabs.spec.ts @@ -1,14 +1,9 @@ import { mount } from '@vue/test-utils'; import { describe, expect, it } from 'vitest'; import { createRouter, createWebHistory } from '@ionic/vue-router'; -import { IonicVue, IonApp, IonRouterOutlet, IonPage, IonTabs, IonTabBar, IonTabButton, IonLabel } from '@ionic/vue'; +import { IonicVue, IonRouterOutlet, IonPage, IonTabs, IonTabBar, IonTabButton, IonLabel } from '@ionic/vue'; import { waitForRouter } from './utils'; -const App = { - components: { IonApp, IonRouterOutlet }, - template: '', -} - const Tabs = { components: { IonPage, IonTabs, IonTabBar, IonTabButton, IonLabel, IonRouterOutlet }, template: ` @@ -64,7 +59,7 @@ describe('ion-tabs', () => { router.push('/'); await router.isReady(); - const wrapper = mount(App, { + const wrapper = mount(IonRouterOutlet, { global: { plugins: [router, IonicVue] } @@ -112,7 +107,7 @@ describe('ion-tabs', () => { router.push('/'); await router.isReady(); - const wrapper = mount(App, { + const wrapper = mount(IonRouterOutlet, { global: { plugins: [router, IonicVue] } @@ -166,7 +161,7 @@ describe('ion-tabs', () => { router.push('/'); await router.isReady(); - const wrapper = mount(App, { + const wrapper = mount(IonRouterOutlet, { global: { plugins: [router, IonicVue] } @@ -222,7 +217,7 @@ describe('ion-tabs', () => { router.push('/'); await router.isReady(); - const wrapper = mount(App, { + const wrapper = mount(IonRouterOutlet, { global: { plugins: [router, IonicVue] }