]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: test for instance and options compat 3680/head
authorEvan You <yyx990803@gmail.com>
Wed, 28 Apr 2021 21:21:02 +0000 (17:21 -0400)
committerEvan You <yyx990803@gmail.com>
Wed, 28 Apr 2021 21:21:05 +0000 (17:21 -0400)
packages/runtime-core/src/compat/__tests__/instance.spec.ts [new file with mode: 0644]
packages/runtime-core/src/compat/__tests__/options.spec.ts [new file with mode: 0644]
packages/runtime-core/src/compat/compatConfig.ts
packages/runtime-core/src/compat/global.ts
packages/runtime-core/src/compat/instance.ts
packages/runtime-core/src/compat/instanceEventEmitter.ts
packages/runtime-core/src/renderer.ts

diff --git a/packages/runtime-core/src/compat/__tests__/instance.spec.ts b/packages/runtime-core/src/compat/__tests__/instance.spec.ts
new file mode 100644 (file)
index 0000000..bddc77e
--- /dev/null
@@ -0,0 +1,299 @@
+import Vue from '@vue/compat'
+import { Slots } from '../../componentSlots'
+import { Text } from '../../vnode'
+import {
+  DeprecationTypes,
+  deprecationData,
+  toggleDeprecationWarning
+} from '../compatConfig'
+import { LegacyPublicInstance } from '../instance'
+
+beforeEach(() => {
+  toggleDeprecationWarning(true)
+  Vue.configureCompat({
+    MODE: 2,
+    GLOBAL_MOUNT: 'suppress-warning'
+  })
+})
+
+afterEach(() => {
+  toggleDeprecationWarning(false)
+  Vue.configureCompat({ MODE: 3 })
+})
+
+test('INSTANCE_SET', () => {
+  const obj: any = {}
+  new Vue().$set(obj, 'foo', 1)
+  expect(obj.foo).toBe(1)
+  expect(
+    deprecationData[DeprecationTypes.INSTANCE_SET].message
+  ).toHaveBeenWarned()
+})
+
+test('INSTANCE_DELETE', () => {
+  const obj: any = { foo: 1 }
+  new Vue().$delete(obj, 'foo')
+  expect('foo' in obj).toBe(false)
+  expect(
+    deprecationData[DeprecationTypes.INSTANCE_DELETE].message
+  ).toHaveBeenWarned()
+})
+
+test('INSTANCE_DESTROY', () => {
+  new Vue({ template: 'foo' }).$mount().$destroy()
+  expect(
+    deprecationData[DeprecationTypes.INSTANCE_DESTROY].message
+  ).toHaveBeenWarned()
+})
+
+// https://github.com/vuejs/vue/blob/dev/test/unit/features/instance/methods-events.spec.js
+describe('INSTANCE_EVENT_EMITTER', () => {
+  let vm: LegacyPublicInstance
+  let spy: jest.Mock
+
+  beforeEach(() => {
+    vm = new Vue()
+    spy = jest.fn()
+  })
+
+  it('$on', () => {
+    vm.$on('test', function(this: any) {
+      // expect correct context
+      expect(this).toBe(vm)
+      spy.apply(this, arguments)
+    })
+    vm.$emit('test', 1, 2, 3, 4)
+    expect(spy).toHaveBeenCalledTimes(1)
+    expect(spy).toHaveBeenCalledWith(1, 2, 3, 4)
+    expect(
+      deprecationData[DeprecationTypes.INSTANCE_EVENT_EMITTER].message
+    ).toHaveBeenWarned()
+  })
+
+  it('$on multi event', () => {
+    vm.$on(['test1', 'test2'], function(this: any) {
+      expect(this).toBe(vm)
+      spy.apply(this, arguments)
+    })
+    vm.$emit('test1', 1, 2, 3, 4)
+    expect(spy).toHaveBeenCalledTimes(1)
+    expect(spy).toHaveBeenCalledWith(1, 2, 3, 4)
+    vm.$emit('test2', 5, 6, 7, 8)
+    expect(spy).toHaveBeenCalledTimes(2)
+    expect(spy).toHaveBeenCalledWith(5, 6, 7, 8)
+    expect(
+      deprecationData[DeprecationTypes.INSTANCE_EVENT_EMITTER].message
+    ).toHaveBeenWarned()
+  })
+
+  it('$off multi event', () => {
+    vm.$on(['test1', 'test2', 'test3'], spy)
+    vm.$off(['test1', 'test2'], spy)
+    vm.$emit('test1')
+    vm.$emit('test2')
+    expect(spy).not.toHaveBeenCalled()
+    vm.$emit('test3', 1, 2, 3, 4)
+    expect(spy).toHaveBeenCalledTimes(1)
+    expect(
+      deprecationData[DeprecationTypes.INSTANCE_EVENT_EMITTER].message
+    ).toHaveBeenWarned()
+  })
+
+  it('$off multi event without callback', () => {
+    vm.$on(['test1', 'test2'], spy)
+    vm.$off(['test1', 'test2'])
+    vm.$emit('test1')
+    expect(spy).not.toHaveBeenCalled()
+    expect(
+      deprecationData[DeprecationTypes.INSTANCE_EVENT_EMITTER].message
+    ).toHaveBeenWarned()
+  })
+
+  it('$once', () => {
+    vm.$once('test', spy)
+    vm.$emit('test', 1, 2, 3)
+    vm.$emit('test', 2, 3, 4)
+    expect(spy).toHaveBeenCalledTimes(1)
+    expect(spy).toHaveBeenCalledWith(1, 2, 3)
+    expect(
+      deprecationData[DeprecationTypes.INSTANCE_EVENT_EMITTER].message
+    ).toHaveBeenWarned()
+  })
+
+  it('$off event added by $once', () => {
+    vm.$once('test', spy)
+    vm.$off('test', spy) // test off event and this event added by once
+    vm.$emit('test', 1, 2, 3)
+    expect(spy).not.toHaveBeenCalled()
+    expect(
+      deprecationData[DeprecationTypes.INSTANCE_EVENT_EMITTER].message
+    ).toHaveBeenWarned()
+  })
+
+  it('$off', () => {
+    vm.$on('test1', spy)
+    vm.$on('test2', spy)
+    vm.$off()
+    vm.$emit('test1')
+    vm.$emit('test2')
+    expect(spy).not.toHaveBeenCalled()
+    expect(
+      deprecationData[DeprecationTypes.INSTANCE_EVENT_EMITTER].message
+    ).toHaveBeenWarned()
+  })
+
+  it('$off event', () => {
+    vm.$on('test1', spy)
+    vm.$on('test2', spy)
+    vm.$off('test1')
+    vm.$off('test1') // test off something that's already off
+    vm.$emit('test1', 1)
+    vm.$emit('test2', 2)
+    expect(spy).toHaveBeenCalledTimes(1)
+    expect(spy).toHaveBeenCalledWith(2)
+    expect(
+      deprecationData[DeprecationTypes.INSTANCE_EVENT_EMITTER].message
+    ).toHaveBeenWarned()
+  })
+
+  it('$off event + fn', () => {
+    const spy2 = jasmine.createSpy('emitter')
+    vm.$on('test', spy)
+    vm.$on('test', spy2)
+    vm.$off('test', spy)
+    vm.$emit('test', 1, 2, 3)
+    expect(spy).not.toHaveBeenCalled()
+    expect(spy2).toHaveBeenCalledTimes(1)
+    expect(spy2).toHaveBeenCalledWith(1, 2, 3)
+    expect(
+      deprecationData[DeprecationTypes.INSTANCE_EVENT_EMITTER].message
+    ).toHaveBeenWarned()
+  })
+})
+
+describe('INSTANCE_EVENT_HOOKS', () => {
+  test('instance API', () => {
+    const spy = jest.fn()
+    const vm = new Vue({ template: 'foo' })
+    vm.$on('hook:mounted', spy)
+    vm.$mount()
+    expect(spy).toHaveBeenCalled()
+    expect(
+      (deprecationData[DeprecationTypes.INSTANCE_EVENT_HOOKS]
+        .message as Function)('hook:mounted')
+    ).toHaveBeenWarned()
+  })
+
+  test('via template', () => {
+    const spy = jest.fn()
+    new Vue({
+      template: `<child @hook:mounted="spy"/>`,
+      methods: { spy },
+      components: {
+        child: {
+          template: 'foo'
+        }
+      }
+    }).$mount()
+    expect(spy).toHaveBeenCalled()
+    expect(
+      (deprecationData[DeprecationTypes.INSTANCE_EVENT_HOOKS]
+        .message as Function)('hook:mounted')
+    ).toHaveBeenWarned()
+  })
+})
+
+test('INSTANCE_EVENT_CHILDREN', () => {
+  const vm = new Vue({
+    template: `<child/><div><child v-for="i in 3"/></div>`,
+    components: {
+      child: {
+        template: 'foo',
+        data() {
+          return { n: 1 }
+        }
+      }
+    }
+  }).$mount()
+  expect(vm.$children.length).toBe(4)
+  vm.$children.forEach((c: any) => {
+    expect(c.n).toBe(1)
+  })
+  expect(
+    deprecationData[DeprecationTypes.INSTANCE_CHILDREN].message
+  ).toHaveBeenWarned()
+})
+
+test('INSTANCE_LISTENERS', () => {
+  const foo = () => 'foo'
+  const bar = () => 'bar'
+  let listeners: Record<string, Function>
+
+  new Vue({
+    template: `<child @click="foo" @custom="bar" />`,
+    methods: { foo, bar },
+    components: {
+      child: {
+        template: `<div/>`,
+        mounted() {
+          listeners = this.$listeners
+        }
+      }
+    }
+  }).$mount()
+
+  expect(Object.keys(listeners!)).toMatchObject(['click', 'custom'])
+  expect(listeners!.click()).toBe('foo')
+  expect(listeners!.custom()).toBe('bar')
+
+  expect(
+    deprecationData[DeprecationTypes.INSTANCE_LISTENERS].message
+  ).toHaveBeenWarned()
+})
+
+test('INSTANCE_SCOPED_SLOTS', () => {
+  let slots: Slots
+  new Vue({
+    template: `<child v-slot="{ msg }">{{ msg }}</child>`,
+    components: {
+      child: {
+        compatConfig: { RENDER_FUNCTION: false },
+        render() {
+          slots = this.$scopedSlots
+        }
+      }
+    }
+  }).$mount()
+
+  expect(slots!.default!({ msg: 'hi' })).toMatchObject([
+    {
+      type: Text,
+      children: 'hi'
+    }
+  ])
+
+  expect(
+    deprecationData[DeprecationTypes.INSTANCE_SCOPED_SLOTS].message
+  ).toHaveBeenWarned()
+})
+
+test('INSTANCE_ATTR_CLASS_STYLE', () => {
+  const vm = new Vue({
+    template: `<child class="foo" style="color:red" id="ok" />`,
+    components: {
+      child: {
+        inheritAttrs: false,
+        template: `<div><div v-bind="$attrs" /></div>`
+      }
+    }
+  }).$mount()
+
+  expect(vm.$el.outerHTML).toBe(
+    `<div class="foo" style="color: red;"><div id="ok"></div></div>`
+  )
+
+  expect(
+    (deprecationData[DeprecationTypes.INSTANCE_ATTRS_CLASS_STYLE]
+      .message as Function)('Anonymous')
+  ).toHaveBeenWarned()
+})
diff --git a/packages/runtime-core/src/compat/__tests__/options.spec.ts b/packages/runtime-core/src/compat/__tests__/options.spec.ts
new file mode 100644 (file)
index 0000000..a10d357
--- /dev/null
@@ -0,0 +1,92 @@
+import Vue from '@vue/compat'
+import { nextTick } from '../../scheduler'
+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('root data plain object', () => {
+  const vm = new Vue({
+    data: { foo: 1 } as any,
+    template: `{{ foo }}`
+  }).$mount()
+  expect(vm.$el.textContent).toBe('1')
+  expect(
+    deprecationData[DeprecationTypes.OPTIONS_DATA_FN].message
+  ).toHaveBeenWarned()
+})
+
+test('data deep merge', () => {
+  const mixin = {
+    data() {
+      return {
+        foo: {
+          baz: 2
+        }
+      }
+    }
+  }
+
+  const vm = new Vue({
+    mixins: [mixin],
+    data: () => ({
+      foo: {
+        bar: 1
+      }
+    }),
+    template: `{{ foo }}`
+  }).$mount()
+
+  expect(vm.$el.textContent).toBe(JSON.stringify({ baz: 2, bar: 1 }, null, 2))
+  expect(
+    (deprecationData[DeprecationTypes.OPTIONS_DATA_MERGE].message as Function)(
+      'foo'
+    )
+  ).toHaveBeenWarned()
+})
+
+test('beforeDestroy/destroyed', async () => {
+  const beforeDestroy = jest.fn()
+  const destroyed = jest.fn()
+
+  const child = {
+    template: `foo`,
+    beforeDestroy,
+    destroyed
+  }
+
+  const vm = new Vue({
+    template: `<child v-if="ok"/>`,
+    data() {
+      return { ok: true }
+    },
+    components: { child }
+  }).$mount() as any
+
+  vm.ok = false
+  await nextTick()
+  expect(beforeDestroy).toHaveBeenCalled()
+  expect(destroyed).toHaveBeenCalled()
+
+  expect(
+    deprecationData[DeprecationTypes.OPTIONS_BEFORE_DESTROY].message
+  ).toHaveBeenWarned()
+
+  expect(
+    deprecationData[DeprecationTypes.OPTIONS_DESTROYED].message
+  ).toHaveBeenWarned()
+})
index e59bdb0a2330d3d9a093211e392b61223cac4bba..d715d97040f8f6f022f4f382002394bf4d34f9ab 100644 (file)
@@ -230,7 +230,8 @@ export const deprecationData: Record<DeprecationTypes, DeprecationData> = {
 
   [DeprecationTypes.INSTANCE_ATTRS_CLASS_STYLE]: {
     message: componentName =>
-      `Component <${componentName}> has \`inheritAttrs: false\` but is ` +
+      `Component <${componentName ||
+        'Anonymous'}> has \`inheritAttrs: false\` but is ` +
       `relying on class/style fallthrough from parent. In Vue 3, class/style ` +
       `are now included in $attrs and will no longer fallthrough when ` +
       `inheritAttrs is false. If you are already using v-bind="$attrs" on ` +
index 5dba5f4b1cfc4192113bea2faeaf973ef97e6f89..dbb5217328405d914bda3bca3df33f96a5cc1994 100644 (file)
@@ -36,7 +36,7 @@ import {
 } from '../component'
 import { RenderFunction, mergeOptions } from '../componentOptions'
 import { ComponentPublicInstance } from '../componentPublicInstance'
-import { devtoolsInitApp } from '../devtools'
+import { devtoolsInitApp, devtoolsUnmountApp } from '../devtools'
 import { Directive } from '../directives'
 import { nextTick } from '../scheduler'
 import { version } from '..'
@@ -456,7 +456,17 @@ export function installCompatMount(
       return instance.proxy!
     }
 
-    instance.ctx._compat_destroy = app.unmount
+    instance.ctx._compat_destroy = () => {
+      if (isMounted) {
+        render(null, app._container)
+        if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
+          devtoolsUnmountApp(app)
+        }
+        delete app._container.__vue_app__
+      } else if (__DEV__) {
+        warn(`Cannot unmount an app that is not mounted.`)
+      }
+    }
 
     return instance.proxy!
   }
index 10c59cebb68a3f52ea46ff12399715311e038032..46e3ef6313a72ed06b76ce424ffa7549638a7fb2 100644 (file)
@@ -49,7 +49,7 @@ export interface LegacyPublicProperties {
   $scopedSlots: Slots
   $on(event: string | string[], fn: Function): this
   $once(event: string, fn: Function): this
-  $off(event?: string, fn?: Function): this
+  $off(event?: string | string[], fn?: Function): this
   $children: LegacyPublicProperties[]
   $listeners: Record<string, Function | Function[]>
 }
index 64237e91654c630105a9b36dd8d041d3a9db7fbe..b314f686c4eeed738c4694e825d4489639af9f8c 100644 (file)
@@ -61,13 +61,13 @@ export function once(
 
 export function off(
   instance: ComponentInternalInstance,
-  event?: string,
+  event?: string | string[],
   fn?: Function
 ) {
   assertCompatEnabled(DeprecationTypes.INSTANCE_EVENT_EMITTER, instance)
   const vm = instance.proxy
   // all
-  if (!arguments.length) {
+  if (!event) {
     eventRegistryMap.set(instance, Object.create(null))
     return vm
   }
@@ -93,12 +93,12 @@ export function off(
 export function emit(
   instance: ComponentInternalInstance,
   event: string,
-  ...args: any[]
+  args: any[]
 ) {
   const cbs = getRegistry(instance)[event]
   if (cbs) {
     callWithAsyncErrorHandling(
-      cbs,
+      cbs.map(cb => cb.bind(instance.proxy)),
       instance,
       ErrorCodes.COMPONENT_EVENT_HANDLER,
       args
index 4555b9fbc0bb171617c51cd8a8abeffe3998b96f..c075b3e5abe86e8bc72e81e550549f5b8d5040c1 100644 (file)
@@ -446,9 +446,6 @@ function baseCreateRenderer(
   options: RendererOptions,
   createHydrationFns?: typeof createHydrationFunctions
 ): any {
-  const isHookEventCompatEnabled =
-    __COMPAT__ && isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, null)
-
   // compile-time feature flags check
   if (__ESM_BUNDLER__ && !__TEST__) {
     initFeatureFlags()
@@ -1426,7 +1423,10 @@ function baseCreateRenderer(
         if ((vnodeHook = props && props.onVnodeBeforeMount)) {
           invokeVNodeHook(vnodeHook, parent, initialVNode)
         }
-        if (__COMPAT__ && isHookEventCompatEnabled) {
+        if (
+          __COMPAT__ &&
+          isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
+        ) {
           instance.emit('hook:beforeMount')
         }
 
@@ -1484,7 +1484,10 @@ function baseCreateRenderer(
             parentSuspense
           )
         }
-        if (__COMPAT__ && isHookEventCompatEnabled) {
+        if (
+          __COMPAT__ &&
+          isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
+        ) {
           queuePostRenderEffect(
             () => instance.emit('hook:mounted'),
             parentSuspense
@@ -1496,7 +1499,10 @@ function baseCreateRenderer(
         // since the hook may be injected by a child keep-alive
         if (initialVNode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {
           instance.a && queuePostRenderEffect(instance.a, parentSuspense)
-          if (__COMPAT__ && isHookEventCompatEnabled) {
+          if (
+            __COMPAT__ &&
+            isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
+          ) {
             queuePostRenderEffect(
               () => instance.emit('hook:activated'),
               parentSuspense
@@ -1537,7 +1543,10 @@ function baseCreateRenderer(
         if ((vnodeHook = next.props && next.props.onVnodeBeforeUpdate)) {
           invokeVNodeHook(vnodeHook, parent, next, vnode)
         }
-        if (__COMPAT__ && isHookEventCompatEnabled) {
+        if (
+          __COMPAT__ &&
+          isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
+        ) {
           instance.emit('hook:beforeUpdate')
         }
 
@@ -1587,7 +1596,10 @@ function baseCreateRenderer(
             parentSuspense
           )
         }
-        if (__COMPAT__ && isHookEventCompatEnabled) {
+        if (
+          __COMPAT__ &&
+          isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
+        ) {
           queuePostRenderEffect(
             () => instance.emit('hook:updated'),
             parentSuspense
@@ -2253,7 +2265,10 @@ function baseCreateRenderer(
     if (bum) {
       invokeArrayFns(bum)
     }
-    if (__COMPAT__ && isHookEventCompatEnabled) {
+    if (
+      __COMPAT__ &&
+      isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
+    ) {
       instance.emit('hook:beforeDestroy')
     }
 
@@ -2272,7 +2287,10 @@ function baseCreateRenderer(
     if (um) {
       queuePostRenderEffect(um, parentSuspense)
     }
-    if (__COMPAT__ && isHookEventCompatEnabled) {
+    if (
+      __COMPAT__ &&
+      isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
+    ) {
       queuePostRenderEffect(
         () => instance.emit('hook:destroyed'),
         parentSuspense