wip: more tests for v2 compat

This commit is contained in:
Evan You 2021-04-29 14:45:22 -04:00
parent d3d9355c5a
commit 3963f2e963
5 changed files with 289 additions and 14 deletions

View File

@ -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()
})

View 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)

View 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
)
) {

View 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

View 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
}