wip: test for legacy component compat
This commit is contained in:
parent
3963f2e963
commit
6db7c00b42
@ -0,0 +1,62 @@
|
|||||||
|
import Vue from '@vue/compat'
|
||||||
|
import {
|
||||||
|
DeprecationTypes,
|
||||||
|
deprecationData,
|
||||||
|
toggleDeprecationWarning
|
||||||
|
} from '../compatConfig'
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
toggleDeprecationWarning(true)
|
||||||
|
Vue.configureCompat({
|
||||||
|
MODE: 2,
|
||||||
|
GLOBAL_MOUNT: 'suppress-warning'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
toggleDeprecationWarning(false)
|
||||||
|
Vue.configureCompat({ MODE: 3 })
|
||||||
|
})
|
||||||
|
|
||||||
|
const timeout = (n: number) => new Promise(r => setTimeout(r, n))
|
||||||
|
|
||||||
|
describe('COMPONENT_ASYNC', () => {
|
||||||
|
test('resolve/reject', async () => {
|
||||||
|
let resolve: any
|
||||||
|
const comp = (r: any) => {
|
||||||
|
resolve = r
|
||||||
|
}
|
||||||
|
const vm = new Vue({
|
||||||
|
template: `<div><comp/></div>`,
|
||||||
|
components: { comp }
|
||||||
|
}).$mount()
|
||||||
|
expect(vm.$el.innerHTML).toBe(`<!---->`)
|
||||||
|
|
||||||
|
resolve({ template: 'foo' })
|
||||||
|
await timeout(0)
|
||||||
|
expect(vm.$el.innerHTML).toBe(`foo`)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
(deprecationData[DeprecationTypes.COMPONENT_ASYNC].message as Function)(
|
||||||
|
comp
|
||||||
|
)
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Promise', async () => {
|
||||||
|
const comp = () => Promise.resolve({ template: 'foo' })
|
||||||
|
const vm = new Vue({
|
||||||
|
template: `<div><comp/></div>`,
|
||||||
|
components: { comp }
|
||||||
|
}).$mount()
|
||||||
|
expect(vm.$el.innerHTML).toBe(`<!---->`)
|
||||||
|
await timeout(0)
|
||||||
|
expect(vm.$el.innerHTML).toBe(`foo`)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
(deprecationData[DeprecationTypes.COMPONENT_ASYNC].message as Function)(
|
||||||
|
comp
|
||||||
|
)
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
})
|
@ -0,0 +1,61 @@
|
|||||||
|
import Vue from '@vue/compat'
|
||||||
|
import {
|
||||||
|
DeprecationTypes,
|
||||||
|
deprecationData,
|
||||||
|
toggleDeprecationWarning
|
||||||
|
} from '../compatConfig'
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
toggleDeprecationWarning(true)
|
||||||
|
Vue.configureCompat({
|
||||||
|
MODE: 2,
|
||||||
|
GLOBAL_MOUNT: 'suppress-warning'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
toggleDeprecationWarning(false)
|
||||||
|
Vue.configureCompat({ MODE: 3 })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('COMPONENT_FUNCTIONAL', async () => {
|
||||||
|
const func = {
|
||||||
|
name: 'Func',
|
||||||
|
functional: true,
|
||||||
|
props: {
|
||||||
|
x: String
|
||||||
|
},
|
||||||
|
inject: ['foo'],
|
||||||
|
render: (h: any, { data, props, injections, slots }: any) => {
|
||||||
|
return h('div', { id: props.x, class: data.class }, [
|
||||||
|
h('div', { class: 'inject' }, injections.foo),
|
||||||
|
h('div', { class: 'slot' }, slots().default)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const vm = new Vue({
|
||||||
|
provide() {
|
||||||
|
return {
|
||||||
|
foo: 123
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
func
|
||||||
|
},
|
||||||
|
template: `<func class="foo" x="foo">hello</func>`
|
||||||
|
}).$mount()
|
||||||
|
|
||||||
|
expect(vm.$el.id).toBe('foo')
|
||||||
|
expect(vm.$el.className).toBe('foo')
|
||||||
|
expect(vm.$el.querySelector('.inject').textContent).toBe('123')
|
||||||
|
expect(vm.$el.querySelector('.slot').textContent).toBe('hello')
|
||||||
|
expect(vm.$el.outerHTML).toMatchInlineSnapshot(
|
||||||
|
`"<div id=\\"foo\\" class=\\"foo\\"><div class=\\"inject\\">123</div><div class=\\"slot\\">hello</div></div>"`
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
(deprecationData[DeprecationTypes.COMPONENT_FUNCTIONAL]
|
||||||
|
.message as Function)(func)
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
})
|
@ -1,22 +1,12 @@
|
|||||||
import { isArray, isFunction, isObject, isPromise } from '@vue/shared'
|
import { isFunction, isObject } from '@vue/shared'
|
||||||
import { defineAsyncComponent } from '../apiAsyncComponent'
|
import { Component, ComponentInternalInstance } from '../component'
|
||||||
import {
|
|
||||||
Component,
|
|
||||||
ComponentInternalInstance,
|
|
||||||
ComponentOptions,
|
|
||||||
FunctionalComponent,
|
|
||||||
getCurrentInstance
|
|
||||||
} from '../component'
|
|
||||||
import { resolveInjections } from '../componentOptions'
|
|
||||||
import { InternalSlots } from '../componentSlots'
|
|
||||||
import { isVNode } from '../vnode'
|
|
||||||
import {
|
import {
|
||||||
checkCompatEnabled,
|
checkCompatEnabled,
|
||||||
softAssertCompatEnabled,
|
DeprecationTypes,
|
||||||
DeprecationTypes
|
softAssertCompatEnabled
|
||||||
} from './compatConfig'
|
} from './compatConfig'
|
||||||
import { getCompatListeners } from './instanceListeners'
|
import { convertLegacyAsyncComponent } from './componentAsync'
|
||||||
import { compatH } from './renderFn'
|
import { convertLegacyFunctionalComponent } from './componentFunctional'
|
||||||
|
|
||||||
export function convertLegacyComponent(
|
export function convertLegacyComponent(
|
||||||
comp: any,
|
comp: any,
|
||||||
@ -56,109 +46,3 @@ export function convertLegacyComponent(
|
|||||||
|
|
||||||
return comp
|
return comp
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LegacyAsyncOptions {
|
|
||||||
component: Promise<Component>
|
|
||||||
loading?: Component
|
|
||||||
error?: Component
|
|
||||||
delay?: number
|
|
||||||
timeout?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
type LegacyAsyncReturnValue = Promise<Component> | LegacyAsyncOptions
|
|
||||||
|
|
||||||
type LegacyAsyncComponent = (
|
|
||||||
resolve?: (res: LegacyAsyncReturnValue) => void,
|
|
||||||
reject?: (reason?: any) => void
|
|
||||||
) => LegacyAsyncReturnValue | undefined
|
|
||||||
|
|
||||||
const normalizedAsyncComponentMap = new Map<LegacyAsyncComponent, Component>()
|
|
||||||
|
|
||||||
function convertLegacyAsyncComponent(comp: LegacyAsyncComponent) {
|
|
||||||
if (normalizedAsyncComponentMap.has(comp)) {
|
|
||||||
return normalizedAsyncComponentMap.get(comp)!
|
|
||||||
}
|
|
||||||
|
|
||||||
// we have to call the function here due to how v2's API won't expose the
|
|
||||||
// options until we call it
|
|
||||||
let resolve: (res: LegacyAsyncReturnValue) => void
|
|
||||||
let reject: (reason?: any) => void
|
|
||||||
const fallbackPromise = new Promise<Component>((r, rj) => {
|
|
||||||
;(resolve = r), (reject = rj)
|
|
||||||
})
|
|
||||||
|
|
||||||
const res = comp(resolve!, reject!)
|
|
||||||
|
|
||||||
let converted: Component
|
|
||||||
if (isPromise(res)) {
|
|
||||||
converted = defineAsyncComponent(() => res)
|
|
||||||
} else if (isObject(res) && !isVNode(res) && !isArray(res)) {
|
|
||||||
converted = defineAsyncComponent({
|
|
||||||
loader: () => res.component,
|
|
||||||
loadingComponent: res.loading,
|
|
||||||
errorComponent: res.error,
|
|
||||||
delay: res.delay,
|
|
||||||
timeout: res.timeout
|
|
||||||
})
|
|
||||||
} else if (res == null) {
|
|
||||||
converted = defineAsyncComponent(() => fallbackPromise)
|
|
||||||
} else {
|
|
||||||
converted = comp as any // probably a v3 functional comp
|
|
||||||
}
|
|
||||||
normalizedAsyncComponentMap.set(comp, converted)
|
|
||||||
return converted
|
|
||||||
}
|
|
||||||
|
|
||||||
const normalizedFunctionalComponentMap = new Map<
|
|
||||||
ComponentOptions,
|
|
||||||
FunctionalComponent
|
|
||||||
>()
|
|
||||||
|
|
||||||
export const legacySlotProxyHandlers: ProxyHandler<InternalSlots> = {
|
|
||||||
get(target, key: string) {
|
|
||||||
const slot = target[key]
|
|
||||||
return slot && slot()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function convertLegacyFunctionalComponent(comp: ComponentOptions) {
|
|
||||||
if (normalizedFunctionalComponentMap.has(comp)) {
|
|
||||||
return normalizedFunctionalComponentMap.get(comp)!
|
|
||||||
}
|
|
||||||
|
|
||||||
const legacyFn = comp.render as any
|
|
||||||
|
|
||||||
const Func: FunctionalComponent = (props, ctx) => {
|
|
||||||
const instance = getCurrentInstance()!
|
|
||||||
|
|
||||||
const legacyCtx = {
|
|
||||||
props,
|
|
||||||
children: instance.vnode.children || [],
|
|
||||||
data: instance.vnode.props || {},
|
|
||||||
scopedSlots: ctx.slots,
|
|
||||||
parent: instance.parent && instance.parent.proxy,
|
|
||||||
slots() {
|
|
||||||
return new Proxy(ctx.slots, legacySlotProxyHandlers)
|
|
||||||
},
|
|
||||||
get listeners() {
|
|
||||||
return getCompatListeners(instance)
|
|
||||||
},
|
|
||||||
get injections() {
|
|
||||||
if (comp.inject) {
|
|
||||||
const injections = {}
|
|
||||||
resolveInjections(comp.inject, {})
|
|
||||||
return injections
|
|
||||||
}
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return legacyFn(compatH, legacyCtx)
|
|
||||||
}
|
|
||||||
Func.props = comp.props
|
|
||||||
Func.displayName = comp.name
|
|
||||||
// v2 functional components do not inherit attrs
|
|
||||||
Func.inheritAttrs = false
|
|
||||||
|
|
||||||
normalizedFunctionalComponentMap.set(comp, Func)
|
|
||||||
return Func
|
|
||||||
}
|
|
||||||
|
56
packages/runtime-core/src/compat/componentAsync.ts
Normal file
56
packages/runtime-core/src/compat/componentAsync.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { isArray, isObject, isPromise } from '@vue/shared'
|
||||||
|
import { defineAsyncComponent } from '../apiAsyncComponent'
|
||||||
|
import { Component } from '../component'
|
||||||
|
import { isVNode } from '../vnode'
|
||||||
|
|
||||||
|
interface LegacyAsyncOptions {
|
||||||
|
component: Promise<Component>
|
||||||
|
loading?: Component
|
||||||
|
error?: Component
|
||||||
|
delay?: number
|
||||||
|
timeout?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
type LegacyAsyncReturnValue = Promise<Component> | LegacyAsyncOptions
|
||||||
|
|
||||||
|
type LegacyAsyncComponent = (
|
||||||
|
resolve?: (res: LegacyAsyncReturnValue) => void,
|
||||||
|
reject?: (reason?: any) => void
|
||||||
|
) => LegacyAsyncReturnValue | undefined
|
||||||
|
|
||||||
|
const normalizedAsyncComponentMap = new Map<LegacyAsyncComponent, Component>()
|
||||||
|
|
||||||
|
export function convertLegacyAsyncComponent(comp: LegacyAsyncComponent) {
|
||||||
|
if (normalizedAsyncComponentMap.has(comp)) {
|
||||||
|
return normalizedAsyncComponentMap.get(comp)!
|
||||||
|
}
|
||||||
|
|
||||||
|
// we have to call the function here due to how v2's API won't expose the
|
||||||
|
// options until we call it
|
||||||
|
let resolve: (res: LegacyAsyncReturnValue) => void
|
||||||
|
let reject: (reason?: any) => void
|
||||||
|
const fallbackPromise = new Promise<Component>((r, rj) => {
|
||||||
|
;(resolve = r), (reject = rj)
|
||||||
|
})
|
||||||
|
|
||||||
|
const res = comp(resolve!, reject!)
|
||||||
|
|
||||||
|
let converted: Component
|
||||||
|
if (isPromise(res)) {
|
||||||
|
converted = defineAsyncComponent(() => res)
|
||||||
|
} else if (isObject(res) && !isVNode(res) && !isArray(res)) {
|
||||||
|
converted = defineAsyncComponent({
|
||||||
|
loader: () => res.component,
|
||||||
|
loadingComponent: res.loading,
|
||||||
|
errorComponent: res.error,
|
||||||
|
delay: res.delay,
|
||||||
|
timeout: res.timeout
|
||||||
|
})
|
||||||
|
} else if (res == null) {
|
||||||
|
converted = defineAsyncComponent(() => fallbackPromise)
|
||||||
|
} else {
|
||||||
|
converted = comp as any // probably a v3 functional comp
|
||||||
|
}
|
||||||
|
normalizedAsyncComponentMap.set(comp, converted)
|
||||||
|
return converted
|
||||||
|
}
|
63
packages/runtime-core/src/compat/componentFunctional.ts
Normal file
63
packages/runtime-core/src/compat/componentFunctional.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import {
|
||||||
|
ComponentOptions,
|
||||||
|
FunctionalComponent,
|
||||||
|
getCurrentInstance
|
||||||
|
} from '../component'
|
||||||
|
import { resolveInjections } from '../componentOptions'
|
||||||
|
import { InternalSlots } from '../componentSlots'
|
||||||
|
import { getCompatListeners } from './instanceListeners'
|
||||||
|
import { compatH } from './renderFn'
|
||||||
|
|
||||||
|
const normalizedFunctionalComponentMap = new Map<
|
||||||
|
ComponentOptions,
|
||||||
|
FunctionalComponent
|
||||||
|
>()
|
||||||
|
|
||||||
|
export const legacySlotProxyHandlers: ProxyHandler<InternalSlots> = {
|
||||||
|
get(target, key: string) {
|
||||||
|
const slot = target[key]
|
||||||
|
return slot && slot()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function convertLegacyFunctionalComponent(comp: ComponentOptions) {
|
||||||
|
if (normalizedFunctionalComponentMap.has(comp)) {
|
||||||
|
return normalizedFunctionalComponentMap.get(comp)!
|
||||||
|
}
|
||||||
|
|
||||||
|
const legacyFn = comp.render as any
|
||||||
|
|
||||||
|
const Func: FunctionalComponent = (props, ctx) => {
|
||||||
|
const instance = getCurrentInstance()!
|
||||||
|
|
||||||
|
const legacyCtx = {
|
||||||
|
props,
|
||||||
|
children: instance.vnode.children || [],
|
||||||
|
data: instance.vnode.props || {},
|
||||||
|
scopedSlots: ctx.slots,
|
||||||
|
parent: instance.parent && instance.parent.proxy,
|
||||||
|
slots() {
|
||||||
|
return new Proxy(ctx.slots, legacySlotProxyHandlers)
|
||||||
|
},
|
||||||
|
get listeners() {
|
||||||
|
return getCompatListeners(instance)
|
||||||
|
},
|
||||||
|
get injections() {
|
||||||
|
if (comp.inject) {
|
||||||
|
const injections = {}
|
||||||
|
resolveInjections(comp.inject, injections)
|
||||||
|
return injections
|
||||||
|
}
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return legacyFn(compatH, legacyCtx)
|
||||||
|
}
|
||||||
|
Func.props = comp.props
|
||||||
|
Func.displayName = comp.name
|
||||||
|
// v2 functional components do not inherit attrs
|
||||||
|
Func.inheritAttrs = false
|
||||||
|
|
||||||
|
normalizedFunctionalComponentMap.set(comp, Func)
|
||||||
|
return Func
|
||||||
|
}
|
@ -19,7 +19,7 @@ import {
|
|||||||
import { off, on, once } from './instanceEventEmitter'
|
import { off, on, once } from './instanceEventEmitter'
|
||||||
import { getCompatListeners } from './instanceListeners'
|
import { getCompatListeners } from './instanceListeners'
|
||||||
import { shallowReadonly } from '@vue/reactivity'
|
import { shallowReadonly } from '@vue/reactivity'
|
||||||
import { legacySlotProxyHandlers } from './component'
|
import { legacySlotProxyHandlers } from './componentFunctional'
|
||||||
import { compatH } from './renderFn'
|
import { compatH } from './renderFn'
|
||||||
import { createCommentVNode, createTextVNode } from '../vnode'
|
import { createCommentVNode, createTextVNode } from '../vnode'
|
||||||
import { renderList } from '../helpers/renderList'
|
import { renderList } from '../helpers/renderList'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user