From: Evan You Date: Thu, 29 Apr 2021 19:51:37 +0000 (-0400) Subject: wip: test for legacy component compat X-Git-Tag: v3.1.0-beta.1~55 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6db7c00b42b3c8406b28c8bb31940e3ceabe761b;p=thirdparty%2Fvuejs%2Fcore.git wip: test for legacy component compat --- diff --git a/packages/runtime-core/src/compat/__tests__/componentAsync.spec.ts b/packages/runtime-core/src/compat/__tests__/componentAsync.spec.ts new file mode 100644 index 0000000000..1f6303332d --- /dev/null +++ b/packages/runtime-core/src/compat/__tests__/componentAsync.spec.ts @@ -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: `
`, + 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: `
`, + 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() + }) +}) diff --git a/packages/runtime-core/src/compat/__tests__/componentFunctional.spec.ts b/packages/runtime-core/src/compat/__tests__/componentFunctional.spec.ts new file mode 100644 index 0000000000..5eceaa1d1f --- /dev/null +++ b/packages/runtime-core/src/compat/__tests__/componentFunctional.spec.ts @@ -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: `hello` + }).$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( + `"
123
hello
"` + ) + + expect( + (deprecationData[DeprecationTypes.COMPONENT_FUNCTIONAL] + .message as Function)(func) + ).toHaveBeenWarned() +}) diff --git a/packages/runtime-core/src/compat/__tests__/vModel.spec.ts b/packages/runtime-core/src/compat/__tests__/vModel.spec.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/runtime-core/src/compat/component.ts b/packages/runtime-core/src/compat/component.ts index 3e89924f35..b09d579a59 100644 --- a/packages/runtime-core/src/compat/component.ts +++ b/packages/runtime-core/src/compat/component.ts @@ -1,22 +1,12 @@ -import { isArray, isFunction, isObject, isPromise } from '@vue/shared' -import { defineAsyncComponent } from '../apiAsyncComponent' -import { - Component, - ComponentInternalInstance, - ComponentOptions, - FunctionalComponent, - getCurrentInstance -} from '../component' -import { resolveInjections } from '../componentOptions' -import { InternalSlots } from '../componentSlots' -import { isVNode } from '../vnode' +import { isFunction, isObject } from '@vue/shared' +import { Component, ComponentInternalInstance } from '../component' import { checkCompatEnabled, - softAssertCompatEnabled, - DeprecationTypes + DeprecationTypes, + softAssertCompatEnabled } from './compatConfig' -import { getCompatListeners } from './instanceListeners' -import { compatH } from './renderFn' +import { convertLegacyAsyncComponent } from './componentAsync' +import { convertLegacyFunctionalComponent } from './componentFunctional' export function convertLegacyComponent( comp: any, @@ -56,109 +46,3 @@ export function convertLegacyComponent( return comp } - -interface LegacyAsyncOptions { - component: Promise - loading?: Component - error?: Component - delay?: number - timeout?: number -} - -type LegacyAsyncReturnValue = Promise | LegacyAsyncOptions - -type LegacyAsyncComponent = ( - resolve?: (res: LegacyAsyncReturnValue) => void, - reject?: (reason?: any) => void -) => LegacyAsyncReturnValue | undefined - -const normalizedAsyncComponentMap = new Map() - -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((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 = { - 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 -} diff --git a/packages/runtime-core/src/compat/componentAsync.ts b/packages/runtime-core/src/compat/componentAsync.ts new file mode 100644 index 0000000000..0a0dee72b6 --- /dev/null +++ b/packages/runtime-core/src/compat/componentAsync.ts @@ -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 + loading?: Component + error?: Component + delay?: number + timeout?: number +} + +type LegacyAsyncReturnValue = Promise | LegacyAsyncOptions + +type LegacyAsyncComponent = ( + resolve?: (res: LegacyAsyncReturnValue) => void, + reject?: (reason?: any) => void +) => LegacyAsyncReturnValue | undefined + +const normalizedAsyncComponentMap = new Map() + +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((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 +} diff --git a/packages/runtime-core/src/compat/componentFunctional.ts b/packages/runtime-core/src/compat/componentFunctional.ts new file mode 100644 index 0000000000..80af32a1da --- /dev/null +++ b/packages/runtime-core/src/compat/componentFunctional.ts @@ -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 = { + 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 +} diff --git a/packages/runtime-core/src/compat/instance.ts b/packages/runtime-core/src/compat/instance.ts index 46e3ef6313..065e357bec 100644 --- a/packages/runtime-core/src/compat/instance.ts +++ b/packages/runtime-core/src/compat/instance.ts @@ -19,7 +19,7 @@ import { import { off, on, once } from './instanceEventEmitter' import { getCompatListeners } from './instanceListeners' import { shallowReadonly } from '@vue/reactivity' -import { legacySlotProxyHandlers } from './component' +import { legacySlotProxyHandlers } from './componentFunctional' import { compatH } from './renderFn' import { createCommentVNode, createTextVNode } from '../vnode' import { renderList } from '../helpers/renderList'