wip: more tests for v2 compat
This commit is contained in:
parent
d3d9355c5a
commit
3963f2e963
249
packages/runtime-core/src/compat/__tests__/misc.spec.ts
Normal file
249
packages/runtime-core/src/compat/__tests__/misc.spec.ts
Normal 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()
|
||||||
|
})
|
@ -32,13 +32,13 @@ export function mapCompatDirectiveHook(
|
|||||||
if (mappedName) {
|
if (mappedName) {
|
||||||
if (isArray(mappedName)) {
|
if (isArray(mappedName)) {
|
||||||
const hook: DirectiveHook[] = []
|
const hook: DirectiveHook[] = []
|
||||||
mappedName.forEach(name => {
|
mappedName.forEach(mapped => {
|
||||||
const mappedHook = dir[name]
|
const mappedHook = dir[mapped]
|
||||||
if (mappedHook) {
|
if (mappedHook) {
|
||||||
softAssertCompatEnabled(
|
softAssertCompatEnabled(
|
||||||
DeprecationTypes.CUSTOM_DIR,
|
DeprecationTypes.CUSTOM_DIR,
|
||||||
instance,
|
instance,
|
||||||
mappedName,
|
mapped,
|
||||||
name
|
name
|
||||||
)
|
)
|
||||||
hook.push(mappedHook)
|
hook.push(mappedHook)
|
||||||
|
@ -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'
|
export const xlinkNS = 'http://www.w3.org/1999/xlink'
|
||||||
|
|
||||||
@ -6,7 +11,8 @@ export function patchAttr(
|
|||||||
el: Element,
|
el: Element,
|
||||||
key: string,
|
key: string,
|
||||||
value: any,
|
value: any,
|
||||||
isSVG: boolean
|
isSVG: boolean,
|
||||||
|
instance?: ComponentInternalInstance | null
|
||||||
) {
|
) {
|
||||||
if (isSVG && key.startsWith('xlink:')) {
|
if (isSVG && key.startsWith('xlink:')) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
@ -15,7 +21,7 @@ export function patchAttr(
|
|||||||
el.setAttributeNS(xlinkNS, key, value)
|
el.setAttributeNS(xlinkNS, key, value)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (__COMPAT__ && compatCoerceAttr(el, key, value)) {
|
if (__COMPAT__ && compatCoerceAttr(el, key, value, instance)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,9 +37,6 @@ export function patchAttr(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2.x compat
|
// 2.x compat
|
||||||
import { makeMap, NOOP } from '@vue/shared'
|
|
||||||
import { compatUtils, DeprecationTypes } from '@vue/runtime-core'
|
|
||||||
|
|
||||||
const isEnumeratedAttr = __COMPAT__
|
const isEnumeratedAttr = __COMPAT__
|
||||||
? /*#__PURE__*/ makeMap('contenteditable,draggable,spellcheck')
|
? /*#__PURE__*/ makeMap('contenteditable,draggable,spellcheck')
|
||||||
: NOOP
|
: NOOP
|
||||||
@ -41,7 +44,8 @@ const isEnumeratedAttr = __COMPAT__
|
|||||||
export function compatCoerceAttr(
|
export function compatCoerceAttr(
|
||||||
el: Element,
|
el: Element,
|
||||||
key: string,
|
key: string,
|
||||||
value: unknown
|
value: unknown,
|
||||||
|
instance: ComponentInternalInstance | null = null
|
||||||
): boolean {
|
): boolean {
|
||||||
if (isEnumeratedAttr(key)) {
|
if (isEnumeratedAttr(key)) {
|
||||||
const v2CocercedValue =
|
const v2CocercedValue =
|
||||||
@ -54,7 +58,7 @@ export function compatCoerceAttr(
|
|||||||
v2CocercedValue &&
|
v2CocercedValue &&
|
||||||
compatUtils.softAssertCompatEnabled(
|
compatUtils.softAssertCompatEnabled(
|
||||||
DeprecationTypes.ATTR_ENUMERATED_COERSION,
|
DeprecationTypes.ATTR_ENUMERATED_COERSION,
|
||||||
null,
|
instance,
|
||||||
key,
|
key,
|
||||||
value,
|
value,
|
||||||
v2CocercedValue
|
v2CocercedValue
|
||||||
@ -68,7 +72,7 @@ export function compatCoerceAttr(
|
|||||||
!isSpecialBooleanAttr(key) &&
|
!isSpecialBooleanAttr(key) &&
|
||||||
compatUtils.softAssertCompatEnabled(
|
compatUtils.softAssertCompatEnabled(
|
||||||
DeprecationTypes.ATTR_FALSE_VALUE,
|
DeprecationTypes.ATTR_FALSE_VALUE,
|
||||||
null,
|
instance,
|
||||||
key
|
key
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
// Reason: potentially setting innerHTML.
|
// Reason: potentially setting innerHTML.
|
||||||
// This can come from explicit usage of v-html or innerHTML as a prop in render
|
// 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.
|
// functions. The user is responsible for using them with only trusted content.
|
||||||
export function patchDOMProp(
|
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
|
// some properties perform value validation and throw
|
||||||
try {
|
try {
|
||||||
el[key] = value
|
el[key] = value
|
||||||
|
@ -58,7 +58,7 @@ export const patchProp: DOMRendererOptions['patchProp'] = (
|
|||||||
} else if (key === 'false-value') {
|
} else if (key === 'false-value') {
|
||||||
;(el as any)._falseValue = nextValue
|
;(el as any)._falseValue = nextValue
|
||||||
}
|
}
|
||||||
patchAttr(el, key, nextValue, isSVG)
|
patchAttr(el, key, nextValue, isSVG, parentComponent)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user