Skip to content

Commit

Permalink
feat(compat): support legacy functional components (#703)
Browse files Browse the repository at this point in the history
  • Loading branch information
xanf authored Jun 30, 2021
1 parent 63e1c6d commit 16df366
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 4 deletions.
6 changes: 5 additions & 1 deletion src/mount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { attachEmitListener } from './emit'
import { createDataMixin } from './dataMixin'
import { MOUNT_COMPONENT_REF, MOUNT_PARENT_NAME } from './constants'
import { createStub, stubComponents } from './stubs'
import { isLegacyFunctionalComponent } from './utils/vueCompatSupport'

// NOTE this should come from `vue`
type PublicProps = VNodeProps & AllowedComponentProps & ComponentCustomProps
Expand Down Expand Up @@ -224,7 +225,10 @@ export function mount(
// normalise the incoming component
let component: ConcreteComponent

if (isFunctionalComponent(originalComponent)) {
if (
isFunctionalComponent(originalComponent) ||
isLegacyFunctionalComponent(originalComponent)
) {
component = defineComponent({
setup:
(_, { attrs, slots }) =>
Expand Down
7 changes: 7 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,10 @@ export function textContent(element: Element): string {
? element.textContent?.trim() ?? ''
: ''
}

export function hasOwnProperty<O extends {}, P extends PropertyKey>(
obj: O,
prop: P
): obj is O & Record<P, unknown> {
return obj.hasOwnProperty(prop)
}
21 changes: 19 additions & 2 deletions src/utils/vueCompatSupport.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as Vue from 'vue'
import type { ComponentOptions } from 'vue'
import { FindAllComponentsSelector } from '../types'
import { hasOwnProperty } from '../utils'

function isCompatEnabled(key: string): boolean {
return (Vue as any).compatUtils?.isCompatEnabled(key) ?? false
Expand All @@ -16,6 +17,22 @@ export function convertLegacyVueExtendSelector(
// @ts-ignore Vue.extend is part of Vue2 compat API, types are missing
const fakeCmp = Vue.extend({})

// @ts-ignore TypeScript does not allow access of properties on functions
return fakeCmp.super === selector.super ? selector.options : selector
return hasOwnProperty(selector, 'super') &&
hasOwnProperty(selector, 'options') &&
fakeCmp.super === selector.super
? (selector.options as FindAllComponentsSelector)
: selector
}

export function isLegacyFunctionalComponent(component: unknown) {
if (!isCompatEnabled('COMPONENT_FUNCTIONAL')) {
return false
}

return (
component &&
typeof component === 'object' &&
hasOwnProperty(component, 'functional') &&
component.functional
)
}
30 changes: 29 additions & 1 deletion tests-compat/compat.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as Vue from '@vue/compat'
import { mount } from '../src'

const { configureCompat, extend, defineComponent } = Vue as any
const { configureCompat, extend, defineComponent, h } = Vue as any

describe('@vue/compat build', () => {
it.each([true, false])(
Expand Down Expand Up @@ -35,4 +35,32 @@ describe('@vue/compat build', () => {

expect(wrapper.findComponent(LegacyComponent).exists()).toBe(true)
})

it('correctly mounts legacy functional component', () => {
configureCompat({ MODE: 3, COMPONENT_FUNCTIONAL: true })

const Component = defineComponent({
functional: true,
render: () => h('div', 'test')
})
const wrapper = mount(Component)

expect(wrapper.html()).toBe('<div>test</div>')
})

it('correctly mounts legacy functional component wrapped in Vue.extend', () => {
configureCompat({
MODE: 3,
GLOBAL_EXTEND: true,
COMPONENT_FUNCTIONAL: true
})

const Component = extend({
functional: true,
render: () => h('div', 'test')
})
const wrapper = mount(Component)

expect(wrapper.html()).toBe('<div>test</div>')
})
})

0 comments on commit 16df366

Please sign in to comment.