global.spec.ts 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. import Vue from '@vue/compat'
  2. import { effect, isReactive } from '@vue/reactivity'
  3. import { 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('extended prototype', async () => {
  268. const Foo = Vue.extend()
  269. Foo.prototype.$test = 1
  270. const vm = new Foo() as any
  271. expect(vm.$test).toBe(1)
  272. const plain = new Vue() as any
  273. expect(plain.$test).toBeUndefined()
  274. })
  275. test('should affect apps created via createApp()', () => {
  276. Vue.prototype.$test = 1
  277. const vm = createApp({
  278. template: 'foo'
  279. }).mount(document.createElement('div')) as any
  280. expect(vm.$test).toBe(1)
  281. delete Vue.prototype.$test
  282. })
  283. })
  284. describe('GLOBAL_SET/DELETE', () => {
  285. test('set', () => {
  286. toggleDeprecationWarning(true)
  287. const obj: any = {}
  288. Vue.set(obj, 'foo', 1)
  289. expect(obj.foo).toBe(1)
  290. expect(
  291. deprecationData[DeprecationTypes.GLOBAL_SET].message
  292. ).toHaveBeenWarned()
  293. })
  294. test('delete', () => {
  295. toggleDeprecationWarning(true)
  296. const obj: any = { foo: 1 }
  297. Vue.delete(obj, 'foo')
  298. expect('foo' in obj).toBe(false)
  299. expect(
  300. deprecationData[DeprecationTypes.GLOBAL_DELETE].message
  301. ).toHaveBeenWarned()
  302. })
  303. })
  304. describe('GLOBAL_OBSERVABLE', () => {
  305. test('should work', () => {
  306. toggleDeprecationWarning(true)
  307. const obj = Vue.observable({})
  308. expect(isReactive(obj)).toBe(true)
  309. expect(
  310. deprecationData[DeprecationTypes.GLOBAL_OBSERVABLE].message
  311. ).toHaveBeenWarned()
  312. })
  313. })
  314. describe('GLOBAL_PRIVATE_UTIL', () => {
  315. test('defineReactive', () => {
  316. toggleDeprecationWarning(true)
  317. const obj: any = {}
  318. // @ts-ignore
  319. Vue.util.defineReactive(obj, 'test', 1)
  320. let n
  321. effect(() => {
  322. n = obj.test
  323. })
  324. expect(n).toBe(1)
  325. obj.test++
  326. expect(n).toBe(2)
  327. expect(
  328. deprecationData[DeprecationTypes.GLOBAL_PRIVATE_UTIL].message
  329. ).toHaveBeenWarned()
  330. })
  331. test('defineReactive on instance', async () => {
  332. const vm = new Vue({
  333. beforeCreate() {
  334. // @ts-ignore
  335. Vue.util.defineReactive(this, 'foo', 1)
  336. },
  337. template: `<div>{{ foo }}</div>`
  338. }).$mount() as any
  339. expect(vm.$el.textContent).toBe('1')
  340. vm.foo = 2
  341. await nextTick()
  342. expect(vm.$el.textContent).toBe('2')
  343. })
  344. test('defineReactive on instance with key that starts with $', async () => {
  345. const vm = new Vue({
  346. beforeCreate() {
  347. // @ts-ignore
  348. Vue.util.defineReactive(this, '$foo', 1)
  349. },
  350. template: `<div>{{ $foo }}</div>`
  351. }).$mount() as any
  352. expect(vm.$el.textContent).toBe('1')
  353. vm.$foo = 2
  354. await nextTick()
  355. expect(vm.$el.textContent).toBe('2')
  356. })
  357. test('defineReactive with object value', () => {
  358. const obj: any = {}
  359. const val = { a: 1 }
  360. // @ts-ignore
  361. Vue.util.defineReactive(obj, 'foo', val)
  362. let n
  363. effect(() => {
  364. n = obj.foo.a
  365. })
  366. expect(n).toBe(1)
  367. // mutating original
  368. val.a++
  369. expect(n).toBe(2)
  370. })
  371. test('defineReactive with array value', () => {
  372. const obj: any = {}
  373. const val = [1]
  374. // @ts-ignore
  375. Vue.util.defineReactive(obj, 'foo', val)
  376. let n
  377. effect(() => {
  378. n = obj.foo.length
  379. })
  380. expect(n).toBe(1)
  381. // mutating original
  382. val.push(2)
  383. expect(n).toBe(2)
  384. })
  385. })
  386. test('global asset registration should affect apps created via createApp', () => {
  387. Vue.component('foo', { template: 'foo' })
  388. const vm = createApp({
  389. template: '<foo/>'
  390. }).mount(document.createElement('div')) as any
  391. expect(vm.$el.textContent).toBe('foo')
  392. delete singletonApp._context.components.foo
  393. })