global.spec.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  1. import Vue from '@vue/compat'
  2. import { effect, isReactive } from '@vue/reactivity'
  3. import { h, nextTick } from '@vue/runtime-core'
  4. import {
  5. DeprecationTypes,
  6. deprecationData,
  7. toggleDeprecationWarning
  8. } from '../../runtime-core/src/compat/compatConfig'
  9. import { singletonApp } from '../../runtime-core/src/compat/global'
  10. import { createApp } from '../src/esm-index'
  11. beforeEach(() => {
  12. toggleDeprecationWarning(false)
  13. Vue.configureCompat({ MODE: 2 })
  14. })
  15. afterEach(() => {
  16. Vue.configureCompat({ MODE: 3 })
  17. toggleDeprecationWarning(false)
  18. })
  19. describe('GLOBAL_MOUNT', () => {
  20. test('new Vue() with el', () => {
  21. toggleDeprecationWarning(true)
  22. const el = document.createElement('div')
  23. el.innerHTML = `{{ msg }}`
  24. new Vue({
  25. el,
  26. compatConfig: { GLOBAL_MOUNT: true },
  27. data() {
  28. return {
  29. msg: 'hello'
  30. }
  31. }
  32. })
  33. expect(
  34. deprecationData[DeprecationTypes.GLOBAL_MOUNT].message
  35. ).toHaveBeenWarned()
  36. expect(el.innerHTML).toBe('hello')
  37. })
  38. test('new Vue() + $mount', () => {
  39. const el = document.createElement('div')
  40. el.innerHTML = `{{ msg }}`
  41. new Vue({
  42. data() {
  43. return {
  44. msg: 'hello'
  45. }
  46. }
  47. }).$mount(el)
  48. expect(el.innerHTML).toBe('hello')
  49. })
  50. })
  51. describe('GLOBAL_MOUNT_CONTAINER', () => {
  52. test('should warn', () => {
  53. toggleDeprecationWarning(true)
  54. const el = document.createElement('div')
  55. el.innerHTML = `test`
  56. el.setAttribute('v-bind:id', 'foo')
  57. new Vue().$mount(el)
  58. // warning only
  59. expect(
  60. deprecationData[DeprecationTypes.GLOBAL_MOUNT].message
  61. ).toHaveBeenWarned()
  62. expect(
  63. deprecationData[DeprecationTypes.GLOBAL_MOUNT_CONTAINER].message
  64. ).toHaveBeenWarned()
  65. })
  66. })
  67. describe('GLOBAL_EXTEND', () => {
  68. // https://github.com/vuejs/vue/blob/dev/test/unit/features/global-api/extend.spec.js
  69. it('should correctly merge options', () => {
  70. toggleDeprecationWarning(true)
  71. const Test = Vue.extend({
  72. name: 'test',
  73. a: 1,
  74. b: 2
  75. })
  76. expect(Test.options.a).toBe(1)
  77. expect(Test.options.b).toBe(2)
  78. expect(Test.super).toBe(Vue)
  79. const t = new Test({
  80. a: 2
  81. })
  82. expect(t.$options.a).toBe(2)
  83. expect(t.$options.b).toBe(2)
  84. // inheritance
  85. const Test2 = Test.extend({
  86. a: 2
  87. })
  88. expect(Test2.options.a).toBe(2)
  89. expect(Test2.options.b).toBe(2)
  90. const t2 = new Test2({
  91. a: 3
  92. })
  93. expect(t2.$options.a).toBe(3)
  94. expect(t2.$options.b).toBe(2)
  95. expect(
  96. deprecationData[DeprecationTypes.GLOBAL_MOUNT].message
  97. ).toHaveBeenWarned()
  98. expect(
  99. deprecationData[DeprecationTypes.GLOBAL_EXTEND].message
  100. ).toHaveBeenWarned()
  101. })
  102. it('should work when used as components', () => {
  103. const foo = Vue.extend({
  104. template: '<span>foo</span>'
  105. })
  106. const bar = Vue.extend({
  107. template: '<span>bar</span>'
  108. })
  109. const vm = new Vue({
  110. template: '<div><foo></foo><bar></bar></div>',
  111. components: { foo, bar }
  112. }).$mount()
  113. expect(vm.$el.innerHTML).toBe('<span>foo</span><span>bar</span>')
  114. })
  115. it('should merge lifecycle hooks', () => {
  116. const calls: number[] = []
  117. const A = Vue.extend({
  118. created() {
  119. calls.push(1)
  120. }
  121. })
  122. const B = A.extend({
  123. created() {
  124. calls.push(2)
  125. }
  126. })
  127. new B({
  128. created() {
  129. calls.push(3)
  130. }
  131. })
  132. expect(calls).toEqual([1, 2, 3])
  133. })
  134. it('should not merge nested mixins created with Vue.extend', () => {
  135. const a = jest.fn()
  136. const b = jest.fn()
  137. const c = jest.fn()
  138. const d = jest.fn()
  139. const A = Vue.extend({
  140. created: a
  141. })
  142. const B = Vue.extend({
  143. mixins: [A],
  144. created: b
  145. })
  146. const C = Vue.extend({
  147. extends: B,
  148. created: c
  149. })
  150. const D = Vue.extend({
  151. mixins: [C],
  152. created: d,
  153. render() {
  154. return null
  155. }
  156. })
  157. new D().$mount()
  158. expect(a.mock.calls.length).toStrictEqual(1)
  159. expect(b.mock.calls.length).toStrictEqual(1)
  160. expect(c.mock.calls.length).toStrictEqual(1)
  161. expect(d.mock.calls.length).toStrictEqual(1)
  162. })
  163. it('should merge methods', () => {
  164. const A = Vue.extend({
  165. methods: {
  166. a() {
  167. return this.n
  168. }
  169. }
  170. })
  171. const B = A.extend({
  172. methods: {
  173. b() {
  174. return this.n + 1
  175. }
  176. }
  177. })
  178. const b = new B({
  179. data: () => ({ n: 0 }),
  180. methods: {
  181. c() {
  182. return this.n + 2
  183. }
  184. }
  185. }) as any
  186. expect(b.a()).toBe(0)
  187. expect(b.b()).toBe(1)
  188. expect(b.c()).toBe(2)
  189. })
  190. it('should merge assets', () => {
  191. const A = Vue.extend({
  192. components: {
  193. aa: {
  194. template: '<div>A</div>'
  195. }
  196. }
  197. })
  198. const B = A.extend({
  199. components: {
  200. bb: {
  201. template: '<div>B</div>'
  202. }
  203. }
  204. })
  205. const b = new B({
  206. template: '<div><aa></aa><bb></bb></div>'
  207. }).$mount()
  208. expect(b.$el.innerHTML).toBe('<div>A</div><div>B</div>')
  209. })
  210. it('caching', () => {
  211. const options = {
  212. template: '<div></div>'
  213. }
  214. const A = Vue.extend(options)
  215. const B = Vue.extend(options)
  216. expect(A).toBe(B)
  217. })
  218. it('extended options should use different identify from parent', () => {
  219. const A = Vue.extend({ computed: {} })
  220. const B = A.extend()
  221. B.options.computed.b = () => 'foo'
  222. expect(B.options.computed).not.toBe(A.options.computed)
  223. expect(A.options.computed.b).toBeUndefined()
  224. })
  225. })
  226. describe('GLOBAL_PROTOTYPE', () => {
  227. test('plain properties', () => {
  228. toggleDeprecationWarning(true)
  229. Vue.prototype.$test = 1
  230. const vm = new Vue() as any
  231. expect(vm.$test).toBe(1)
  232. delete Vue.prototype.$test
  233. expect(
  234. deprecationData[DeprecationTypes.GLOBAL_MOUNT].message
  235. ).toHaveBeenWarned()
  236. expect(
  237. deprecationData[DeprecationTypes.GLOBAL_PROTOTYPE].message
  238. ).toHaveBeenWarned()
  239. })
  240. test('method this context', () => {
  241. Vue.prototype.$test = function () {
  242. return this.msg
  243. }
  244. const vm = new Vue({
  245. data() {
  246. return { msg: 'method' }
  247. }
  248. }) as any
  249. expect(vm.$test()).toBe('method')
  250. delete Vue.prototype.$test
  251. })
  252. test('defined properties', () => {
  253. Object.defineProperty(Vue.prototype, '$test', {
  254. configurable: true,
  255. get() {
  256. return this.msg
  257. }
  258. })
  259. const vm = new Vue({
  260. data() {
  261. return { msg: 'getter' }
  262. }
  263. }) as any
  264. expect(vm.$test).toBe('getter')
  265. delete Vue.prototype.$test
  266. })
  267. test('functions keeps additional properties', () => {
  268. function test(this: any) {
  269. return this.msg
  270. }
  271. test.additionalFn = () => {
  272. return 'additional fn'
  273. }
  274. Vue.prototype.$test = test
  275. const vm = new Vue({
  276. data() {
  277. return {
  278. msg: 'test'
  279. }
  280. }
  281. }) as any
  282. expect(typeof vm.$test).toBe('function')
  283. expect(typeof vm.$test.additionalFn).toBe('function')
  284. expect(vm.$test.additionalFn()).toBe('additional fn')
  285. delete Vue.prototype.$test
  286. })
  287. test('extended prototype', async () => {
  288. const Foo = Vue.extend()
  289. Foo.prototype.$test = 1
  290. const vm = new Foo() as any
  291. expect(vm.$test).toBe(1)
  292. const plain = new Vue() as any
  293. expect(plain.$test).toBeUndefined()
  294. })
  295. test('should affect apps created via createApp()', () => {
  296. Vue.prototype.$test = 1
  297. const vm = createApp({
  298. template: 'foo'
  299. }).mount(document.createElement('div')) as any
  300. expect(vm.$test).toBe(1)
  301. delete Vue.prototype.$test
  302. })
  303. })
  304. describe('GLOBAL_SET/DELETE', () => {
  305. test('set', () => {
  306. toggleDeprecationWarning(true)
  307. const obj: any = {}
  308. Vue.set(obj, 'foo', 1)
  309. expect(obj.foo).toBe(1)
  310. expect(
  311. deprecationData[DeprecationTypes.GLOBAL_SET].message
  312. ).toHaveBeenWarned()
  313. })
  314. test('delete', () => {
  315. toggleDeprecationWarning(true)
  316. const obj: any = { foo: 1 }
  317. Vue.delete(obj, 'foo')
  318. expect('foo' in obj).toBe(false)
  319. expect(
  320. deprecationData[DeprecationTypes.GLOBAL_DELETE].message
  321. ).toHaveBeenWarned()
  322. })
  323. })
  324. describe('GLOBAL_OBSERVABLE', () => {
  325. test('should work', () => {
  326. toggleDeprecationWarning(true)
  327. const obj = Vue.observable({})
  328. expect(isReactive(obj)).toBe(true)
  329. expect(
  330. deprecationData[DeprecationTypes.GLOBAL_OBSERVABLE].message
  331. ).toHaveBeenWarned()
  332. })
  333. })
  334. describe('GLOBAL_PRIVATE_UTIL', () => {
  335. test('defineReactive', () => {
  336. toggleDeprecationWarning(true)
  337. const obj: any = {}
  338. // @ts-ignore
  339. Vue.util.defineReactive(obj, 'test', 1)
  340. let n
  341. effect(() => {
  342. n = obj.test
  343. })
  344. expect(n).toBe(1)
  345. obj.test++
  346. expect(n).toBe(2)
  347. expect(
  348. deprecationData[DeprecationTypes.GLOBAL_PRIVATE_UTIL].message
  349. ).toHaveBeenWarned()
  350. })
  351. test('defineReactive on instance', async () => {
  352. const vm = new Vue({
  353. beforeCreate() {
  354. // @ts-ignore
  355. Vue.util.defineReactive(this, 'foo', 1)
  356. },
  357. template: `<div>{{ foo }}</div>`
  358. }).$mount() as any
  359. expect(vm.$el.textContent).toBe('1')
  360. vm.foo = 2
  361. await nextTick()
  362. expect(vm.$el.textContent).toBe('2')
  363. })
  364. test('defineReactive on instance with key that starts with $', async () => {
  365. const vm = new Vue({
  366. beforeCreate() {
  367. // @ts-ignore
  368. Vue.util.defineReactive(this, '$foo', 1)
  369. },
  370. template: `<div>{{ $foo }}</div>`
  371. }).$mount() as any
  372. expect(vm.$el.textContent).toBe('1')
  373. vm.$foo = 2
  374. await nextTick()
  375. expect(vm.$el.textContent).toBe('2')
  376. })
  377. test('defineReactive with object value', () => {
  378. const obj: any = {}
  379. const val = { a: 1 }
  380. // @ts-ignore
  381. Vue.util.defineReactive(obj, 'foo', val)
  382. let n
  383. effect(() => {
  384. n = obj.foo.a
  385. })
  386. expect(n).toBe(1)
  387. // mutating original
  388. val.a++
  389. expect(n).toBe(2)
  390. })
  391. test('defineReactive with array value', () => {
  392. const obj: any = {}
  393. const val = [1]
  394. // @ts-ignore
  395. Vue.util.defineReactive(obj, 'foo', val)
  396. let n
  397. effect(() => {
  398. n = obj.foo.length
  399. })
  400. expect(n).toBe(1)
  401. // mutating original
  402. val.push(2)
  403. expect(n).toBe(2)
  404. })
  405. })
  406. test('global asset registration should affect apps created via createApp', () => {
  407. Vue.component('foo', { template: 'foo' })
  408. const vm = createApp({
  409. template: '<foo/>'
  410. }).mount(document.createElement('div')) as any
  411. expect(vm.$el.textContent).toBe('foo')
  412. delete singletonApp._context.components.foo
  413. })
  414. test('post-facto global asset registration should affect apps created via createApp', () => {
  415. const app = createApp({
  416. template: '<foo/>'
  417. })
  418. Vue.component('foo', { template: 'foo' })
  419. const vm = app.mount(document.createElement('div')) as any
  420. expect(vm.$el.textContent).toBe('foo')
  421. delete singletonApp._context.components.foo
  422. })
  423. test('local asset registration should not affect other local apps', () => {
  424. const app1 = createApp({})
  425. const app2 = createApp({})
  426. app1.component('foo', {})
  427. app2.component('foo', {})
  428. expect(
  429. `Component "foo" has already been registered in target app`
  430. ).not.toHaveBeenWarned()
  431. })
  432. test('local app-level mixin registration should not affect other local apps', () => {
  433. const app1 = createApp({ render: () => h('div') })
  434. const app2 = createApp({})
  435. const mixin = { created: jest.fn() }
  436. app1.mixin(mixin)
  437. app2.mixin(mixin)
  438. expect(`Mixin has already been applied`).not.toHaveBeenWarned()
  439. app1.mount(document.createElement('div'))
  440. expect(mixin.created).toHaveBeenCalledTimes(1)
  441. })
  442. // #5699
  443. test('local app config should not affect other local apps in v3 mode', () => {
  444. Vue.configureCompat({ MODE: 3 })
  445. const app1 = createApp({
  446. render: () => h('div'),
  447. provide() {
  448. return {
  449. test: 123
  450. }
  451. }
  452. })
  453. app1.config.globalProperties.test = () => {}
  454. app1.mount(document.createElement('div'))
  455. const app2 = createApp({})
  456. expect(app2.config.globalProperties.test).toBe(undefined)
  457. })