]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: more tests for v2 compat
authorEvan You <yyx990803@gmail.com>
Thu, 29 Apr 2021 18:45:22 +0000 (14:45 -0400)
committerEvan You <yyx990803@gmail.com>
Thu, 29 Apr 2021 18:45:22 +0000 (14:45 -0400)
packages/runtime-core/src/compat/__tests__/misc.spec.ts [new file with mode: 0644]
packages/runtime-core/src/compat/customDirective.ts
packages/runtime-dom/src/modules/attrs.ts
packages/runtime-dom/src/modules/props.ts
packages/runtime-dom/src/patchProp.ts

diff --git a/packages/runtime-core/src/compat/__tests__/misc.spec.ts b/packages/runtime-core/src/compat/__tests__/misc.spec.ts
new file mode 100644 (file)
index 0000000..21574ca
--- /dev/null
@@ -0,0 +1,249 @@
+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 })
+})
+
+function triggerEvent(
+  target: Element,
+  event: string,
+  process?: (e: any) => any
+) {
+  const e = document.createEvent('HTMLEvents')
+  e.initEvent(event, true, true)
+  if (process) process(e)
+  target.dispatchEvent(e)
+  return e
+}
+
+test('WATCH_ARRAY', async () => {
+  const spy = jest.fn()
+  const vm = new Vue({
+    data() {
+      return {
+        foo: []
+      }
+    },
+    watch: {
+      foo: spy
+    }
+  }) as any
+  expect(
+    deprecationData[DeprecationTypes.WATCH_ARRAY].message
+  ).toHaveBeenWarned()
+
+  expect(spy).not.toHaveBeenCalled()
+  vm.foo.push(1)
+  await nextTick()
+  expect(spy).toHaveBeenCalledTimes(1)
+})
+
+test('PROPS_DEFAULT_THIS', () => {
+  let thisCtx: any
+  const Child = {
+    customOption: 1,
+    inject: ['provided'],
+    props: {
+      foo: null,
+      bar: {
+        default(this: any) {
+          // copy values since injection must be sync
+          thisCtx = {
+            foo: this.foo,
+            $options: this.$options,
+            provided: this.provided
+          }
+          return this.foo + 1
+        }
+      }
+    },
+    template: `{{ bar }}`
+  }
+
+  const vm = new Vue({
+    components: { Child },
+    provide: {
+      provided: 2
+    },
+    template: `<child :foo="0" />`
+  }).$mount()
+
+  expect(vm.$el.textContent).toBe('1')
+  // other props
+  expect(thisCtx.foo).toBe(0)
+  // $options
+  expect(thisCtx.$options.customOption).toBe(1)
+  // injections
+  expect(thisCtx.provided).toBe(2)
+
+  expect(
+    (deprecationData[DeprecationTypes.PROPS_DEFAULT_THIS].message as Function)(
+      'bar'
+    )
+  ).toHaveBeenWarned()
+})
+
+test('V_FOR_REF', async () => {
+  const vm = new Vue({
+    data() {
+      return {
+        ok: true,
+        list: [1, 2, 3]
+      }
+    },
+    template: `
+    <template v-if="ok">
+      <li v-for="i in list" ref="list">{{ i }}</li>
+    </template>
+    `
+  }).$mount() as any
+
+  const mapRefs = () => vm.$refs.list.map((el: HTMLElement) => el.textContent)
+  expect(mapRefs()).toMatchObject(['1', '2', '3'])
+
+  expect(deprecationData[DeprecationTypes.V_FOR_REF].message).toHaveBeenWarned()
+
+  vm.list.push(4)
+  await nextTick()
+  expect(mapRefs()).toMatchObject(['1', '2', '3', '4'])
+
+  vm.list.shift()
+  await nextTick()
+  expect(mapRefs()).toMatchObject(['2', '3', '4'])
+
+  vm.ok = !vm.ok
+  await nextTick()
+  expect(mapRefs()).toMatchObject([])
+
+  vm.ok = !vm.ok
+  await nextTick()
+  expect(mapRefs()).toMatchObject(['2', '3', '4'])
+})
+
+test('V_ON_KEYCODE_MODIFIER', () => {
+  const spy = jest.fn()
+  const vm = new Vue({
+    template: `<input @keyup.1="spy">`,
+    methods: { spy }
+  }).$mount()
+  triggerEvent(vm.$el, 'keyup', e => {
+    e.key = '_'
+    e.keyCode = 1
+  })
+  expect(spy).toHaveBeenCalled()
+  expect(
+    deprecationData[DeprecationTypes.V_ON_KEYCODE_MODIFIER].message
+  ).toHaveBeenWarned()
+})
+
+test('CUSTOM_DIR', async () => {
+  const myDir = {
+    bind: jest.fn(),
+    inserted: jest.fn(),
+    update: jest.fn(),
+    componentUpdated: jest.fn(),
+    unbind: jest.fn()
+  } as any
+
+  const getCalls = () =>
+    Object.keys(myDir).map(key => myDir[key].mock.calls.length)
+
+  const vm = new Vue({
+    data() {
+      return {
+        ok: true,
+        foo: 1
+      }
+    },
+    template: `<div v-if="ok" v-my-dir="foo"/>`,
+    directives: {
+      myDir
+    }
+  }).$mount() as any
+
+  expect(getCalls()).toMatchObject([1, 1, 0, 0, 0])
+
+  expect(
+    (deprecationData[DeprecationTypes.CUSTOM_DIR].message as Function)(
+      'bind',
+      'beforeMount'
+    )
+  ).toHaveBeenWarned()
+  expect(
+    (deprecationData[DeprecationTypes.CUSTOM_DIR].message as Function)(
+      'inserted',
+      'mounted'
+    )
+  ).toHaveBeenWarned()
+
+  vm.foo++
+  await nextTick()
+  expect(getCalls()).toMatchObject([1, 1, 1, 1, 0])
+
+  expect(
+    (deprecationData[DeprecationTypes.CUSTOM_DIR].message as Function)(
+      'update',
+      'updated'
+    )
+  ).toHaveBeenWarned()
+  expect(
+    (deprecationData[DeprecationTypes.CUSTOM_DIR].message as Function)(
+      'componentUpdated',
+      'updated'
+    )
+  ).toHaveBeenWarned()
+})
+
+test('ATTR_FALSE_VALUE', () => {
+  const vm = new Vue({
+    template: `<div :id="false" :foo="false"/>`
+  }).$mount()
+  expect(vm.$el.hasAttribute('id')).toBe(false)
+  expect(vm.$el.hasAttribute('foo')).toBe(false)
+  expect(
+    (deprecationData[DeprecationTypes.ATTR_FALSE_VALUE].message as Function)(
+      'id'
+    )
+  ).toHaveBeenWarned()
+  expect(
+    (deprecationData[DeprecationTypes.ATTR_FALSE_VALUE].message as Function)(
+      'foo'
+    )
+  ).toHaveBeenWarned()
+})
+
+test('ATTR_ENUMERATED_COERSION', () => {
+  const vm = new Vue({
+    template: `<div :draggable="null" :spellcheck="0" contenteditable="foo" />`
+  }).$mount()
+  expect(vm.$el.getAttribute('draggable')).toBe('false')
+  expect(vm.$el.getAttribute('spellcheck')).toBe('true')
+  expect(vm.$el.getAttribute('contenteditable')).toBe('true')
+  expect(
+    (deprecationData[DeprecationTypes.ATTR_ENUMERATED_COERSION]
+      .message as Function)('draggable', null, 'false')
+  ).toHaveBeenWarned()
+  expect(
+    (deprecationData[DeprecationTypes.ATTR_ENUMERATED_COERSION]
+      .message as Function)('spellcheck', 0, 'true')
+  ).toHaveBeenWarned()
+  expect(
+    (deprecationData[DeprecationTypes.ATTR_ENUMERATED_COERSION]
+      .message as Function)('contenteditable', 'foo', 'true')
+  ).toHaveBeenWarned()
+})
index dc854dbcaaf968c6352a79929c8763bf11440e6f..da351eb0812cce3cf034eb558a38782868a0626f 100644 (file)
@@ -32,13 +32,13 @@ export function mapCompatDirectiveHook(
   if (mappedName) {
     if (isArray(mappedName)) {
       const hook: DirectiveHook[] = []
-      mappedName.forEach(name => {
-        const mappedHook = dir[name]
+      mappedName.forEach(mapped => {
+        const mappedHook = dir[mapped]
         if (mappedHook) {
           softAssertCompatEnabled(
             DeprecationTypes.CUSTOM_DIR,
             instance,
-            mappedName,
+            mapped,
             name
           )
           hook.push(mappedHook)
index fc3ead5266a0cd6fb146e3947e07240241b48f84..4e1621e92c3f3f463c74886058461dacc852f995 100644 (file)
@@ -1,4 +1,9 @@
-import { isSpecialBooleanAttr } from '@vue/shared'
+import { isSpecialBooleanAttr, makeMap, NOOP } from '@vue/shared'
+import {
+  compatUtils,
+  ComponentInternalInstance,
+  DeprecationTypes
+} from '@vue/runtime-core'
 
 export const xlinkNS = 'http://www.w3.org/1999/xlink'
 
@@ -6,7 +11,8 @@ export function patchAttr(
   el: Element,
   key: string,
   value: any,
-  isSVG: boolean
+  isSVG: boolean,
+  instance?: ComponentInternalInstance | null
 ) {
   if (isSVG && key.startsWith('xlink:')) {
     if (value == null) {
@@ -15,7 +21,7 @@ export function patchAttr(
       el.setAttributeNS(xlinkNS, key, value)
     }
   } else {
-    if (__COMPAT__ && compatCoerceAttr(el, key, value)) {
+    if (__COMPAT__ && compatCoerceAttr(el, key, value, instance)) {
       return
     }
 
@@ -31,9 +37,6 @@ export function patchAttr(
 }
 
 // 2.x compat
-import { makeMap, NOOP } from '@vue/shared'
-import { compatUtils, DeprecationTypes } from '@vue/runtime-core'
-
 const isEnumeratedAttr = __COMPAT__
   ? /*#__PURE__*/ makeMap('contenteditable,draggable,spellcheck')
   : NOOP
@@ -41,7 +44,8 @@ const isEnumeratedAttr = __COMPAT__
 export function compatCoerceAttr(
   el: Element,
   key: string,
-  value: unknown
+  value: unknown,
+  instance: ComponentInternalInstance | null = null
 ): boolean {
   if (isEnumeratedAttr(key)) {
     const v2CocercedValue =
@@ -54,7 +58,7 @@ export function compatCoerceAttr(
       v2CocercedValue &&
       compatUtils.softAssertCompatEnabled(
         DeprecationTypes.ATTR_ENUMERATED_COERSION,
-        null,
+        instance,
         key,
         value,
         v2CocercedValue
@@ -68,7 +72,7 @@ export function compatCoerceAttr(
     !isSpecialBooleanAttr(key) &&
     compatUtils.softAssertCompatEnabled(
       DeprecationTypes.ATTR_FALSE_VALUE,
-      null,
+      instance,
       key
     )
   ) {
index 068701642f8bd28d752c8e61096fd43c8213fbef..01a16a58cba6a400c7944d16186833f7077a459f 100644 (file)
@@ -2,7 +2,7 @@
 // Reason: potentially setting innerHTML.
 // This can come from explicit usage of v-html or innerHTML as a prop in render
 
-import { warn } from '@vue/runtime-core'
+import { warn, DeprecationTypes, compatUtils } from '@vue/runtime-core'
 
 // functions. The user is responsible for using them with only trusted content.
 export function patchDOMProp(
@@ -55,6 +55,28 @@ export function patchDOMProp(
     }
   }
 
+  if (
+    __COMPAT__ &&
+    value === false &&
+    compatUtils.isCompatEnabled(
+      DeprecationTypes.ATTR_FALSE_VALUE,
+      parentComponent
+    )
+  ) {
+    const type = typeof el[key]
+    if (type === 'string' || type === 'number') {
+      __DEV__ &&
+        compatUtils.warnDeprecation(
+          DeprecationTypes.ATTR_FALSE_VALUE,
+          parentComponent,
+          key
+        )
+      el[key] = type === 'number' ? 0 : ''
+      el.removeAttribute(key)
+      return
+    }
+  }
+
   // some properties perform value validation and throw
   try {
     el[key] = value
index b7ae61e214450aa52312a8085c4f3a90980449b6..2754f7426e159fbb35222843584af7949a40ecc2 100644 (file)
@@ -58,7 +58,7 @@ export const patchProp: DOMRendererOptions['patchProp'] = (
         } else if (key === 'false-value') {
           ;(el as any)._falseValue = nextValue
         }
-        patchAttr(el, key, nextValue, isSVG)
+        patchAttr(el, key, nextValue, isSVG, parentComponent)
       }
       break
   }