import Vue from '@vue/compat' import { effect, isReactive } from '@vue/reactivity' import { h, nextTick } from '@vue/runtime-core' import { DeprecationTypes, deprecationData, toggleDeprecationWarning } from '../../runtime-core/src/compat/compatConfig' import { singletonApp } from '../../runtime-core/src/compat/global' import { createApp } from '../src/esm-index' 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: 'foo' }) const bar = Vue.extend({ template: 'bar' }) const vm = new Vue({ template: '
', components: { foo, bar } }).$mount() expect(vm.$el.innerHTML).toBe('foobar') }) 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 = jest.fn() const b = jest.fn() const c = jest.fn() const d = jest.fn() const A = Vue.extend({ created: a }) const B = Vue.extend({ mixins: [A], created: b }) const C = Vue.extend({ extends: B, created: c }) const D = Vue.extend({ mixins: [C], created: d, render() { return null } }) new D().$mount() expect(a.mock.calls.length).toStrictEqual(1) expect(b.mock.calls.length).toStrictEqual(1) expect(c.mock.calls.length).toStrictEqual(1) expect(d.mock.calls.length).toStrictEqual(1) }) 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: '
A
' } } }) const B = A.extend({ components: { bb: { template: '
B
' } } }) const b = new B({ template: '
' }).$mount() expect(b.$el.innerHTML).toBe('
A
B
') }) it('caching', () => { const options = { template: '
' } 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('functions keeps additional properties', () => { function test(this: any) { return this.msg } test.additionalFn = () => { return 'additional fn' } Vue.prototype.$test = test const vm = new Vue({ data() { return { msg: 'test' } } }) as any expect(typeof vm.$test).toBe('function') expect(typeof vm.$test.additionalFn).toBe('function') expect(vm.$test.additionalFn()).toBe('additional fn') 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() }) test('should affect apps created via createApp()', () => { Vue.prototype.$test = 1 const vm = createApp({ template: 'foo' }).mount(document.createElement('div')) as any expect(vm.$test).toBe(1) delete Vue.prototype.$test }) }) 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() }) test('defineReactive on instance', async () => { const vm = new Vue({ beforeCreate() { // @ts-ignore Vue.util.defineReactive(this, 'foo', 1) }, template: `
{{ foo }}
` }).$mount() as any expect(vm.$el.textContent).toBe('1') vm.foo = 2 await nextTick() expect(vm.$el.textContent).toBe('2') }) test('defineReactive on instance with key that starts with $', async () => { const vm = new Vue({ beforeCreate() { // @ts-ignore Vue.util.defineReactive(this, '$foo', 1) }, template: `
{{ $foo }}
` }).$mount() as any expect(vm.$el.textContent).toBe('1') vm.$foo = 2 await nextTick() expect(vm.$el.textContent).toBe('2') }) test('defineReactive with object value', () => { const obj: any = {} const val = { a: 1 } // @ts-ignore Vue.util.defineReactive(obj, 'foo', val) let n effect(() => { n = obj.foo.a }) expect(n).toBe(1) // mutating original val.a++ expect(n).toBe(2) }) test('defineReactive with array value', () => { const obj: any = {} const val = [1] // @ts-ignore Vue.util.defineReactive(obj, 'foo', val) let n effect(() => { n = obj.foo.length }) expect(n).toBe(1) // mutating original val.push(2) expect(n).toBe(2) }) }) test('global asset registration should affect apps created via createApp', () => { Vue.component('foo', { template: 'foo' }) const vm = createApp({ template: '' }).mount(document.createElement('div')) as any expect(vm.$el.textContent).toBe('foo') delete singletonApp._context.components.foo }) test('post-facto global asset registration should affect apps created via createApp', () => { const app = createApp({ template: '' }) Vue.component('foo', { template: 'foo' }) const vm = app.mount(document.createElement('div')) as any expect(vm.$el.textContent).toBe('foo') delete singletonApp._context.components.foo }) test('local asset registration should not affect other local apps', () => { const app1 = createApp({}) const app2 = createApp({}) app1.component('foo', {}) app2.component('foo', {}) expect( `Component "foo" has already been registered in target app` ).not.toHaveBeenWarned() }) test('local app-level mixin registration should not affect other local apps', () => { const app1 = createApp({ render: () => h('div') }) const app2 = createApp({}) const mixin = { created: jest.fn() } app1.mixin(mixin) app2.mixin(mixin) expect(`Mixin has already been applied`).not.toHaveBeenWarned() app1.mount(document.createElement('div')) expect(mixin.created).toHaveBeenCalledTimes(1) }) // #5699 test('local app config should not affect other local apps in v3 mode', () => { Vue.configureCompat({ MODE: 3 }) const app1 = createApp({ render: () => h('div'), provide() { return { test: 123 } } }) app1.config.globalProperties.test = () => {} app1.mount(document.createElement('div')) const app2 = createApp({}) expect(app2.config.globalProperties.test).toBe(undefined) })