wip: move compat test cases + filter tests

This commit is contained in:
Evan You
2021-04-29 16:58:14 -04:00
parent bb76cfae12
commit b4c92ccf6b
12 changed files with 306 additions and 29 deletions

View File

@@ -0,0 +1,62 @@
import Vue from '@vue/compat'
import {
DeprecationTypes,
deprecationData,
toggleDeprecationWarning
} from '../../runtime-core/src/compat/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: `<div><comp/></div>`,
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: `<div><comp/></div>`,
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()
})
})

View File

@@ -0,0 +1,61 @@
import Vue from '@vue/compat'
import {
DeprecationTypes,
deprecationData,
toggleDeprecationWarning
} from '../../runtime-core/src/compat/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: `<func class="foo" x="foo">hello</func>`
}).$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(
`"<div id=\\"foo\\" class=\\"foo\\"><div class=\\"inject\\">123</div><div class=\\"slot\\">hello</div></div>"`
)
expect(
(deprecationData[DeprecationTypes.COMPONENT_FUNCTIONAL]
.message as Function)(func)
).toHaveBeenWarned()
})

View File

@@ -0,0 +1,85 @@
import Vue from '@vue/compat'
import { ComponentOptions } from '../../runtime-core/src/component'
import { nextTick } from '../../runtime-core/src/scheduler'
import {
DeprecationTypes,
deprecationData,
toggleDeprecationWarning
} from '../../runtime-core/src/compat/compatConfig'
import { triggerEvent } from './utils'
beforeEach(() => {
toggleDeprecationWarning(true)
Vue.configureCompat({
MODE: 2,
GLOBAL_MOUNT: 'suppress-warning'
})
})
afterEach(() => {
toggleDeprecationWarning(false)
Vue.configureCompat({ MODE: 3 })
})
describe('COMPONENT_V_MODEL', () => {
async function runTest(CustomInput: ComponentOptions) {
const vm = new Vue({
data() {
return {
text: 'foo'
}
},
components: { CustomInput },
template: `
<div>
<span>{{ text }}</span>
<custom-input v-model="text"></custom-input>
</div>
`
}).$mount() as any
const input = vm.$el.querySelector('input')
const span = vm.$el.querySelector('span')
expect(input.value).toBe('foo')
expect(span.textContent).toBe('foo')
expect(
(deprecationData[DeprecationTypes.COMPONENT_V_MODEL].message as Function)(
CustomInput
)
).toHaveBeenWarned()
input.value = 'bar'
triggerEvent(input, 'input')
await nextTick()
expect(input.value).toBe('bar')
expect(span.textContent).toBe('bar')
vm.text = 'baz'
await nextTick()
expect(input.value).toBe('baz')
expect(span.textContent).toBe('baz')
}
test('basic usage', async () => {
await runTest({
name: 'CustomInput',
props: ['value'],
template: `<input :value="value" @input="$emit('input', $event.target.value)">`
})
})
test('with model option', async () => {
await runTest({
name: 'CustomInput',
props: ['input'],
model: {
prop: 'input',
event: 'update'
},
template: `<input :value="input" @input="$emit('update', $event.target.value)">`
})
})
})

View File

@@ -0,0 +1,237 @@
import Vue from '@vue/compat'
import { CompilerDeprecationTypes } from '../../compiler-core/src'
import { toggleDeprecationWarning } from '../../runtime-core/src/compat/compatConfig'
beforeEach(() => {
toggleDeprecationWarning(false)
Vue.configureCompat({ MODE: 2 })
})
afterEach(() => {
Vue.configureCompat({ MODE: 3 })
toggleDeprecationWarning(false)
})
describe('FILTERS', () => {
function upper(v: string) {
return v.toUpperCase()
}
function lower(v: string) {
return v.toLowerCase()
}
function reverse(v: string) {
return v
.split('')
.reverse()
.join('')
}
function double(v: number) {
return v * 2
}
it('basic usage', () => {
const vm = new Vue({
template: '<div>{{ msg | upper }}</div>',
data: () => ({
msg: 'hi'
}),
filters: {
upper
}
}).$mount()
expect(vm.$el.textContent).toBe('HI')
expect(CompilerDeprecationTypes.COMPILER_FILTERS).toHaveBeenWarned()
})
it('chained usage', () => {
const vm = new Vue({
template: '<div>{{ msg | upper | reverse }}</div>',
data: () => ({
msg: 'hi'
}),
filters: {
upper,
reverse
}
}).$mount()
expect(vm.$el.textContent).toBe('IH')
expect(CompilerDeprecationTypes.COMPILER_FILTERS).toHaveBeenWarned()
})
it('in v-bind', () => {
const vm = new Vue({
template: `
<div
v-bind:id="id | upper | reverse"
:class="cls | reverse"
:ref="ref | lower">
</div>
`,
filters: {
upper,
reverse,
lower
},
data: () => ({
id: 'abc',
cls: 'foo',
ref: 'BAR'
})
}).$mount()
expect(vm.$el.id).toBe('CBA')
expect(vm.$el.className).toBe('oof')
expect(vm.$refs.bar).toBe(vm.$el)
expect(CompilerDeprecationTypes.COMPILER_FILTERS).toHaveBeenWarned()
})
it('handle regex with pipe', () => {
const vm = new Vue({
template: `<test ref="test" :pattern="/a|b\\// | identity"></test>`,
filters: { identity: (v: any) => v },
components: {
test: {
props: ['pattern'],
template: '<div></div>'
}
}
}).$mount() as any
expect(vm.$refs.test.pattern instanceof RegExp).toBe(true)
expect(vm.$refs.test.pattern.toString()).toBe('/a|b\\//')
expect(CompilerDeprecationTypes.COMPILER_FILTERS).toHaveBeenWarned()
})
it('handle division', () => {
const vm = new Vue({
data: () => ({ a: 2 }),
template: `<div>{{ 1/a / 4 | double }}</div>`,
filters: { double }
}).$mount()
expect(vm.$el.textContent).toBe(String(1 / 4))
expect(CompilerDeprecationTypes.COMPILER_FILTERS).toHaveBeenWarned()
})
it('handle division with parenthesis', () => {
const vm = new Vue({
data: () => ({ a: 20 }),
template: `<div>{{ (a*2) / 5 | double }}</div>`,
filters: { double }
}).$mount()
expect(vm.$el.textContent).toBe(String(16))
expect(CompilerDeprecationTypes.COMPILER_FILTERS).toHaveBeenWarned()
})
it('handle division with dot', () => {
const vm = new Vue({
template: `<div>{{ 20. / 5 | double }}</div>`,
filters: { double }
}).$mount()
expect(vm.$el.textContent).toBe(String(8))
expect(CompilerDeprecationTypes.COMPILER_FILTERS).toHaveBeenWarned()
})
it('handle division with array values', () => {
const vm = new Vue({
data: () => ({ a: [20] }),
template: `<div>{{ a[0] / 5 | double }}</div>`,
filters: { double }
}).$mount()
expect(vm.$el.textContent).toBe(String(8))
expect(CompilerDeprecationTypes.COMPILER_FILTERS).toHaveBeenWarned()
})
it('handle division with hash values', () => {
const vm = new Vue({
data: () => ({ a: { n: 20 } }),
template: `<div>{{ a['n'] / 5 | double }}</div>`,
filters: { double }
}).$mount()
expect(vm.$el.textContent).toBe(String(8))
expect(CompilerDeprecationTypes.COMPILER_FILTERS).toHaveBeenWarned()
})
it('handle division with variable_', () => {
const vm = new Vue({
data: () => ({ a_: 8 }),
template: `<div>{{ a_ / 2 | double }}</div>`,
filters: { double }
}).$mount()
expect(vm.$el.textContent).toBe(String(8))
expect(CompilerDeprecationTypes.COMPILER_FILTERS).toHaveBeenWarned()
})
it('arguments', () => {
const vm = new Vue({
template: `<div>{{ msg | add(a, 3) }}</div>`,
data: () => ({
msg: 1,
a: 2
}),
filters: {
add: (v: number, arg1: number, arg2: number) => v + arg1 + arg2
}
}).$mount()
expect(vm.$el.textContent).toBe('6')
expect(CompilerDeprecationTypes.COMPILER_FILTERS).toHaveBeenWarned()
})
it('quotes', () => {
const vm = new Vue({
template: `<div>{{ msg + "b | c" + 'd' | upper }}</div>`,
data: () => ({
msg: 'a'
}),
filters: {
upper
}
}).$mount()
expect(vm.$el.textContent).toBe('AB | CD')
expect(CompilerDeprecationTypes.COMPILER_FILTERS).toHaveBeenWarned()
})
it('double pipe', () => {
const vm = new Vue({
template: `<div>{{ b || msg | upper }}</div>`,
data: () => ({
b: false,
msg: 'a'
}),
filters: {
upper
}
}).$mount()
expect(vm.$el.textContent).toBe('A')
expect(CompilerDeprecationTypes.COMPILER_FILTERS).toHaveBeenWarned()
})
it('object literal', () => {
const vm = new Vue({
template: `<div>{{ { a: 123 } | pick('a') }}</div>`,
filters: {
pick: (v: any, key: string) => v[key]
}
}).$mount()
expect(vm.$el.textContent).toBe('123')
expect(CompilerDeprecationTypes.COMPILER_FILTERS).toHaveBeenWarned()
})
it('array literal', () => {
const vm = new Vue({
template: `<div>{{ [1, 2, 3] | reverse }}</div>`,
filters: {
reverse: (arr: any[]) => arr.reverse().join(',')
}
}).$mount()
expect(vm.$el.textContent).toBe('3,2,1')
expect(CompilerDeprecationTypes.COMPILER_FILTERS).toHaveBeenWarned()
})
it('bigint support', () => {
const vm = new Vue({
template: `<div>{{ BigInt(BigInt(10000000)) + BigInt(2000000000n) * 3000000n }}</div>`
}).$mount()
expect(vm.$el.textContent).toBe('6000000010000000')
})
})

View File

@@ -0,0 +1,336 @@
import Vue from '@vue/compat'
import { effect, isReactive } from '@vue/reactivity'
import {
DeprecationTypes,
deprecationData,
toggleDeprecationWarning
} from '../../runtime-core/src/compat/compatConfig'
beforeEach(() => {
toggleDeprecationWarning(false)
Vue.configureCompat({ MODE: 2 })
})
afterEach(() => {
Vue.configureCompat({ MODE: 3 })
toggleDeprecationWarning(false)
})
describe('GLOBAL_MOUNT', () => {
test('new Vue() with el', () => {
toggleDeprecationWarning(true)
const el = document.createElement('div')
el.innerHTML = `{{ msg }}`
new Vue({
el,
compatConfig: { GLOBAL_MOUNT: true },
data() {
return {
msg: 'hello'
}
}
})
expect(
deprecationData[DeprecationTypes.GLOBAL_MOUNT].message
).toHaveBeenWarned()
expect(el.innerHTML).toBe('hello')
})
test('new Vue() + $mount', () => {
const el = document.createElement('div')
el.innerHTML = `{{ msg }}`
new Vue({
data() {
return {
msg: 'hello'
}
}
}).$mount(el)
expect(el.innerHTML).toBe('hello')
})
})
describe('GLOBAL_MOUNT_CONTAINER', () => {
test('should warn', () => {
toggleDeprecationWarning(true)
const el = document.createElement('div')
el.innerHTML = `test`
el.setAttribute('v-bind:id', 'foo')
new Vue().$mount(el)
// warning only
expect(
deprecationData[DeprecationTypes.GLOBAL_MOUNT].message
).toHaveBeenWarned()
expect(
deprecationData[DeprecationTypes.GLOBAL_MOUNT_CONTAINER].message
).toHaveBeenWarned()
})
})
describe('GLOBAL_EXTEND', () => {
// https://github.com/vuejs/vue/blob/dev/test/unit/features/global-api/extend.spec.js
it('should correctly merge options', () => {
toggleDeprecationWarning(true)
const Test = Vue.extend({
name: 'test',
a: 1,
b: 2
})
expect(Test.options.a).toBe(1)
expect(Test.options.b).toBe(2)
expect(Test.super).toBe(Vue)
const t = new Test({
a: 2
})
expect(t.$options.a).toBe(2)
expect(t.$options.b).toBe(2)
// inheritance
const Test2 = Test.extend({
a: 2
})
expect(Test2.options.a).toBe(2)
expect(Test2.options.b).toBe(2)
const t2 = new Test2({
a: 3
})
expect(t2.$options.a).toBe(3)
expect(t2.$options.b).toBe(2)
expect(
deprecationData[DeprecationTypes.GLOBAL_MOUNT].message
).toHaveBeenWarned()
expect(
deprecationData[DeprecationTypes.GLOBAL_EXTEND].message
).toHaveBeenWarned()
})
it('should work when used as components', () => {
const foo = Vue.extend({
template: '<span>foo</span>'
})
const bar = Vue.extend({
template: '<span>bar</span>'
})
const vm = new Vue({
template: '<div><foo></foo><bar></bar></div>',
components: { foo, bar }
}).$mount()
expect(vm.$el.innerHTML).toBe('<span>foo</span><span>bar</span>')
})
it('should merge lifecycle hooks', () => {
const calls: number[] = []
const A = Vue.extend({
created() {
calls.push(1)
}
})
const B = A.extend({
created() {
calls.push(2)
}
})
new B({
created() {
calls.push(3)
}
})
expect(calls).toEqual([1, 2, 3])
})
it('should not merge nested mixins created with Vue.extend', () => {
const A = Vue.extend({
created: () => {}
})
const B = Vue.extend({
mixins: [A],
created: () => {}
})
const C = Vue.extend({
extends: B,
created: () => {}
})
const D = Vue.extend({
mixins: [C],
created: () => {}
})
expect(D.options.created!.length).toBe(4)
})
it('should merge methods', () => {
const A = Vue.extend({
methods: {
a() {
return this.n
}
}
})
const B = A.extend({
methods: {
b() {
return this.n + 1
}
}
})
const b = new B({
data: () => ({ n: 0 }),
methods: {
c() {
return this.n + 2
}
}
}) as any
expect(b.a()).toBe(0)
expect(b.b()).toBe(1)
expect(b.c()).toBe(2)
})
it('should merge assets', () => {
const A = Vue.extend({
components: {
aa: {
template: '<div>A</div>'
}
}
})
const B = A.extend({
components: {
bb: {
template: '<div>B</div>'
}
}
})
const b = new B({
template: '<div><aa></aa><bb></bb></div>'
}).$mount()
expect(b.$el.innerHTML).toBe('<div>A</div><div>B</div>')
})
it('caching', () => {
const options = {
template: '<div></div>'
}
const A = Vue.extend(options)
const B = Vue.extend(options)
expect(A).toBe(B)
})
it('extended options should use different identify from parent', () => {
const A = Vue.extend({ computed: {} })
const B = A.extend()
B.options.computed.b = () => 'foo'
expect(B.options.computed).not.toBe(A.options.computed)
expect(A.options.computed.b).toBeUndefined()
})
})
describe('GLOBAL_PROTOTYPE', () => {
test('plain properties', () => {
toggleDeprecationWarning(true)
Vue.prototype.$test = 1
const vm = new Vue() as any
expect(vm.$test).toBe(1)
delete Vue.prototype.$test
expect(
deprecationData[DeprecationTypes.GLOBAL_MOUNT].message
).toHaveBeenWarned()
expect(
deprecationData[DeprecationTypes.GLOBAL_PROTOTYPE].message
).toHaveBeenWarned()
})
test('method this context', () => {
Vue.prototype.$test = function() {
return this.msg
}
const vm = new Vue({
data() {
return { msg: 'method' }
}
}) as any
expect(vm.$test()).toBe('method')
delete Vue.prototype.$test
})
test('defined properties', () => {
Object.defineProperty(Vue.prototype, '$test', {
configurable: true,
get() {
return this.msg
}
})
const vm = new Vue({
data() {
return { msg: 'getter' }
}
}) as any
expect(vm.$test).toBe('getter')
delete Vue.prototype.$test
})
test('extended prototype', async () => {
const Foo = Vue.extend()
Foo.prototype.$test = 1
const vm = new Foo() as any
expect(vm.$test).toBe(1)
const plain = new Vue() as any
expect(plain.$test).toBeUndefined()
})
})
describe('GLOBAL_SET/DELETE', () => {
test('set', () => {
toggleDeprecationWarning(true)
const obj: any = {}
Vue.set(obj, 'foo', 1)
expect(obj.foo).toBe(1)
expect(
deprecationData[DeprecationTypes.GLOBAL_SET].message
).toHaveBeenWarned()
})
test('delete', () => {
toggleDeprecationWarning(true)
const obj: any = { foo: 1 }
Vue.delete(obj, 'foo')
expect('foo' in obj).toBe(false)
expect(
deprecationData[DeprecationTypes.GLOBAL_DELETE].message
).toHaveBeenWarned()
})
})
describe('GLOBAL_OBSERVABLE', () => {
test('should work', () => {
toggleDeprecationWarning(true)
const obj = Vue.observable({})
expect(isReactive(obj)).toBe(true)
expect(
deprecationData[DeprecationTypes.GLOBAL_OBSERVABLE].message
).toHaveBeenWarned()
})
})
describe('GLOBAL_PRIVATE_UTIL', () => {
test('defineReactive', () => {
toggleDeprecationWarning(true)
const obj: any = {}
// @ts-ignore
Vue.util.defineReactive(obj, 'test', 1)
let n
effect(() => {
n = obj.test
})
expect(n).toBe(1)
obj.test++
expect(n).toBe(2)
expect(
deprecationData[DeprecationTypes.GLOBAL_PRIVATE_UTIL].message
).toHaveBeenWarned()
})
})

View File

@@ -0,0 +1,66 @@
import Vue from '@vue/compat'
import { toggleDeprecationWarning } from '../../runtime-core/src/compat/compatConfig'
import { triggerEvent } from './utils'
beforeEach(() => {
toggleDeprecationWarning(false)
Vue.configureCompat({ MODE: 2 })
})
afterEach(() => {
Vue.configureCompat({ MODE: 3 })
toggleDeprecationWarning(false)
})
// only testing config options that affect runtime behavior.
test('GLOBAL_KEY_CODES', () => {
Vue.config.keyCodes = {
foo: 86,
bar: [38, 87]
}
const onFoo = jest.fn()
const onBar = jest.fn()
const el = document.createElement('div')
new Vue({
el,
template: `<input type="text" @keyup.foo="onFoo" @keyup.bar="onBar">`,
methods: {
onFoo,
onBar
}
})
triggerEvent(el.children[0], 'keyup', e => {
e.key = '_'
e.keyCode = 86
})
expect(onFoo).toHaveBeenCalledTimes(1)
expect(onBar).toHaveBeenCalledTimes(0)
triggerEvent(el.children[0], 'keyup', e => {
e.key = '_'
e.keyCode = 38
})
expect(onFoo).toHaveBeenCalledTimes(1)
expect(onBar).toHaveBeenCalledTimes(1)
triggerEvent(el.children[0], 'keyup', e => {
e.key = '_'
e.keyCode = 87
})
expect(onFoo).toHaveBeenCalledTimes(1)
expect(onBar).toHaveBeenCalledTimes(2)
})
test('GLOBAL_IGNORED_ELEMENTS', () => {
Vue.config.ignoredElements = [/^v-/, 'foo']
const el = document.createElement('div')
new Vue({
el,
template: `<v-foo/><foo/>`
})
expect(el.innerHTML).toBe(`<v-foo></v-foo><foo></foo>`)
})

View File

@@ -0,0 +1,299 @@
import Vue from '@vue/compat'
import { Slots } from '../../runtime-core/src/componentSlots'
import { Text } from '../../runtime-core/src/vnode'
import {
DeprecationTypes,
deprecationData,
toggleDeprecationWarning
} from '../../runtime-core/src/compat/compatConfig'
import { LegacyPublicInstance } from '../../runtime-core/src/compat/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()
})

View File

@@ -0,0 +1,238 @@
import Vue from '@vue/compat'
import { nextTick } from '../../runtime-core/src/scheduler'
import {
DeprecationTypes,
deprecationData,
toggleDeprecationWarning
} from '../../runtime-core/src/compat/compatConfig'
import { triggerEvent } from './utils'
beforeEach(() => {
toggleDeprecationWarning(true)
Vue.configureCompat({
MODE: 2,
GLOBAL_MOUNT: 'suppress-warning'
})
})
afterEach(() => {
toggleDeprecationWarning(false)
Vue.configureCompat({ MODE: 3 })
})
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

@@ -0,0 +1,92 @@
import Vue from '@vue/compat'
import { nextTick } from '../../runtime-core/src/scheduler'
import {
DeprecationTypes,
deprecationData,
toggleDeprecationWarning
} from '../../runtime-core/src/compat/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()
})

View File

@@ -0,0 +1,219 @@
import { ShapeFlags } from '@vue/shared'
import Vue from '@vue/compat'
import { createComponentInstance } from '../../runtime-core/src/component'
import { setCurrentRenderingInstance } from '../../runtime-core/src/componentRenderContext'
import { DirectiveBinding } from '../../runtime-core/src/directives'
import { createVNode } from '../../runtime-core/src/vnode'
import {
deprecationData,
DeprecationTypes,
toggleDeprecationWarning
} from '../../runtime-core/src/compat/compatConfig'
import { compatH as h } from '../../runtime-core/src/compat/renderFn'
beforeEach(() => {
toggleDeprecationWarning(false)
Vue.configureCompat({
MODE: 2,
GLOBAL_MOUNT: 'suppress-warning'
})
})
afterEach(() => {
toggleDeprecationWarning(false)
Vue.configureCompat({ MODE: 3 })
})
describe('compat: render function', () => {
const mockDir = {}
const mockChildComp = {}
const mockComponent = {
directives: {
mockDir
},
components: {
foo: mockChildComp
}
}
const mockInstance = createComponentInstance(
createVNode(mockComponent),
null,
null
)
beforeEach(() => {
setCurrentRenderingInstance(mockInstance)
})
afterEach(() => {
setCurrentRenderingInstance(null)
})
test('string component lookup', () => {
expect(h('foo')).toMatchObject({
type: mockChildComp
})
})
test('class / style / attrs / domProps / props', () => {
expect(
h('div', {
class: 'foo',
style: { color: 'red' },
attrs: {
id: 'foo'
},
domProps: {
innerHTML: 'hi'
},
props: {
myProp: 'foo'
}
})
).toMatchObject({
props: {
class: 'foo',
style: { color: 'red' },
id: 'foo',
innerHTML: 'hi',
myProp: 'foo'
}
})
})
test('staticClass + class', () => {
expect(
h('div', {
class: { foo: true },
staticClass: 'bar'
})
).toMatchObject({
props: {
class: 'bar foo'
}
})
})
test('staticStyle + style', () => {
expect(
h('div', {
style: { color: 'red' },
staticStyle: { fontSize: '14px' }
})
).toMatchObject({
props: {
style: {
color: 'red',
fontSize: '14px'
}
}
})
})
test('on / nativeOn', () => {
const fn = () => {}
expect(
h('div', {
on: {
click: fn,
fooBar: fn
},
nativeOn: {
click: fn,
'bar-baz': fn
}
})
).toMatchObject({
props: {
onClick: fn, // should dedupe
onFooBar: fn,
'onBar-baz': fn
}
})
})
test('directives', () => {
expect(
h('div', {
directives: [
{
name: 'mock-dir',
value: '2',
// expression: '1 + 1',
arg: 'foo',
modifiers: {
bar: true
}
}
]
})
).toMatchObject({
dirs: [
{
dir: mockDir,
instance: mockInstance.proxy,
value: '2',
oldValue: void 0,
arg: 'foo',
modifiers: {
bar: true
}
}
] as DirectiveBinding[]
})
})
test('scopedSlots', () => {
const scopedSlots = {
default() {}
}
const vnode = h(mockComponent, {
scopedSlots
})
expect(vnode).toMatchObject({
children: scopedSlots
})
expect('scopedSlots' in vnode.props!).toBe(false)
expect(vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN).toBeTruthy()
})
test('legacy named slot', () => {
const vnode = h(mockComponent, [
'text',
h('div', { slot: 'foo' }, 'one'),
h('div', { slot: 'bar' }, 'two'),
h('div', { slot: 'foo' }, 'three'),
h('div', 'four')
])
expect(vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN).toBeTruthy()
const slots = vnode.children as any
// default
expect(slots.default()).toMatchObject(['text', { children: 'four' }])
expect(slots.foo()).toMatchObject([
{ children: 'one' },
{ children: 'three' }
])
expect(slots.bar()).toMatchObject([{ children: 'two' }])
})
test('in component usage', () => {
toggleDeprecationWarning(true)
const vm = new Vue({
render(h: any) {
return h(
'div',
{
class: 'foo',
attrs: { id: 'bar' }
},
'hello'
)
}
}).$mount()
expect(vm.$el.outerHTML).toBe(`<div class="foo" id="bar">hello</div>`)
expect(
deprecationData[DeprecationTypes.RENDER_FUNCTION].message
).toHaveBeenWarned()
})
})

View File

@@ -0,0 +1,11 @@
export 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
}