global.spec.ts 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  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 = Vue.extend({
  136. created: () => {}
  137. })
  138. const B = Vue.extend({
  139. mixins: [A],
  140. created: () => {}
  141. })
  142. const C = Vue.extend({
  143. extends: B,
  144. created: () => {}
  145. })
  146. const D = Vue.extend({
  147. mixins: [C],
  148. created: () => {}
  149. })
  150. expect(D.options.created!.length).toBe(4)
  151. })
  152. it('should merge methods', () => {
  153. const A = Vue.extend({
  154. methods: {
  155. a() {
  156. return this.n
  157. }
  158. }
  159. })
  160. const B = A.extend({
  161. methods: {
  162. b() {
  163. return this.n + 1
  164. }
  165. }
  166. })
  167. const b = new B({
  168. data: () => ({ n: 0 }),
  169. methods: {
  170. c() {
  171. return this.n + 2
  172. }
  173. }
  174. }) as any
  175. expect(b.a()).toBe(0)
  176. expect(b.b()).toBe(1)
  177. expect(b.c()).toBe(2)
  178. })
  179. it('should merge assets', () => {
  180. const A = Vue.extend({
  181. components: {
  182. aa: {
  183. template: '<div>A</div>'
  184. }
  185. }
  186. })
  187. const B = A.extend({
  188. components: {
  189. bb: {
  190. template: '<div>B</div>'
  191. }
  192. }
  193. })
  194. const b = new B({
  195. template: '<div><aa></aa><bb></bb></div>'
  196. }).$mount()
  197. expect(b.$el.innerHTML).toBe('<div>A</div><div>B</div>')
  198. })
  199. it('caching', () => {
  200. const options = {
  201. template: '<div></div>'
  202. }
  203. const A = Vue.extend(options)
  204. const B = Vue.extend(options)
  205. expect(A).toBe(B)
  206. })
  207. it('extended options should use different identify from parent', () => {
  208. const A = Vue.extend({ computed: {} })
  209. const B = A.extend()
  210. B.options.computed.b = () => 'foo'
  211. expect(B.options.computed).not.toBe(A.options.computed)
  212. expect(A.options.computed.b).toBeUndefined()
  213. })
  214. })
  215. describe('GLOBAL_PROTOTYPE', () => {
  216. test('plain properties', () => {
  217. toggleDeprecationWarning(true)
  218. Vue.prototype.$test = 1
  219. const vm = new Vue() as any
  220. expect(vm.$test).toBe(1)
  221. delete Vue.prototype.$test
  222. expect(
  223. deprecationData[DeprecationTypes.GLOBAL_MOUNT].message
  224. ).toHaveBeenWarned()
  225. expect(
  226. deprecationData[DeprecationTypes.GLOBAL_PROTOTYPE].message
  227. ).toHaveBeenWarned()
  228. })
  229. test('method this context', () => {
  230. Vue.prototype.$test = function() {
  231. return this.msg
  232. }
  233. const vm = new Vue({
  234. data() {
  235. return { msg: 'method' }
  236. }
  237. }) as any
  238. expect(vm.$test()).toBe('method')
  239. delete Vue.prototype.$test
  240. })
  241. test('defined properties', () => {
  242. Object.defineProperty(Vue.prototype, '$test', {
  243. configurable: true,
  244. get() {
  245. return this.msg
  246. }
  247. })
  248. const vm = new Vue({
  249. data() {
  250. return { msg: 'getter' }
  251. }
  252. }) as any
  253. expect(vm.$test).toBe('getter')
  254. delete Vue.prototype.$test
  255. })
  256. test('extended prototype', async () => {
  257. const Foo = Vue.extend()
  258. Foo.prototype.$test = 1
  259. const vm = new Foo() as any
  260. expect(vm.$test).toBe(1)
  261. const plain = new Vue() as any
  262. expect(plain.$test).toBeUndefined()
  263. })
  264. test('should affect apps created via createApp()', () => {
  265. Vue.prototype.$test = 1
  266. const vm = createApp({
  267. template: 'foo'
  268. }).mount(document.createElement('div')) as any
  269. expect(vm.$test).toBe(1)
  270. delete Vue.prototype.$test
  271. })
  272. })
  273. describe('GLOBAL_SET/DELETE', () => {
  274. test('set', () => {
  275. toggleDeprecationWarning(true)
  276. const obj: any = {}
  277. Vue.set(obj, 'foo', 1)
  278. expect(obj.foo).toBe(1)
  279. expect(
  280. deprecationData[DeprecationTypes.GLOBAL_SET].message
  281. ).toHaveBeenWarned()
  282. })
  283. test('delete', () => {
  284. toggleDeprecationWarning(true)
  285. const obj: any = { foo: 1 }
  286. Vue.delete(obj, 'foo')
  287. expect('foo' in obj).toBe(false)
  288. expect(
  289. deprecationData[DeprecationTypes.GLOBAL_DELETE].message
  290. ).toHaveBeenWarned()
  291. })
  292. })
  293. describe('GLOBAL_OBSERVABLE', () => {
  294. test('should work', () => {
  295. toggleDeprecationWarning(true)
  296. const obj = Vue.observable({})
  297. expect(isReactive(obj)).toBe(true)
  298. expect(
  299. deprecationData[DeprecationTypes.GLOBAL_OBSERVABLE].message
  300. ).toHaveBeenWarned()
  301. })
  302. })
  303. describe('GLOBAL_PRIVATE_UTIL', () => {
  304. test('defineReactive', () => {
  305. toggleDeprecationWarning(true)
  306. const obj: any = {}
  307. // @ts-ignore
  308. Vue.util.defineReactive(obj, 'test', 1)
  309. let n
  310. effect(() => {
  311. n = obj.test
  312. })
  313. expect(n).toBe(1)
  314. obj.test++
  315. expect(n).toBe(2)
  316. expect(
  317. deprecationData[DeprecationTypes.GLOBAL_PRIVATE_UTIL].message
  318. ).toHaveBeenWarned()
  319. })
  320. test('defineReactive on instance', async () => {
  321. const vm = new Vue({
  322. beforeCreate() {
  323. // @ts-ignore
  324. Vue.util.defineReactive(this, 'foo', 1)
  325. },
  326. template: `<div>{{ foo }}</div>`
  327. }).$mount() as any
  328. expect(vm.$el.textContent).toBe('1')
  329. vm.foo = 2
  330. await nextTick()
  331. expect(vm.$el.textContent).toBe('2')
  332. })
  333. test('defineReactive on instance with key that starts with $', async () => {
  334. const vm = new Vue({
  335. beforeCreate() {
  336. // @ts-ignore
  337. Vue.util.defineReactive(this, '$foo', 1)
  338. },
  339. template: `<div>{{ $foo }}</div>`
  340. }).$mount() as any
  341. expect(vm.$el.textContent).toBe('1')
  342. vm.$foo = 2
  343. await nextTick()
  344. expect(vm.$el.textContent).toBe('2')
  345. })
  346. test('defineReactive with object value', () => {
  347. const obj: any = {}
  348. const val = { a: 1 }
  349. // @ts-ignore
  350. Vue.util.defineReactive(obj, 'foo', val)
  351. let n
  352. effect(() => {
  353. n = obj.foo.a
  354. })
  355. expect(n).toBe(1)
  356. // mutating original
  357. val.a++
  358. expect(n).toBe(2)
  359. })
  360. test('defineReactive with array value', () => {
  361. const obj: any = {}
  362. const val = [1]
  363. // @ts-ignore
  364. Vue.util.defineReactive(obj, 'foo', val)
  365. let n
  366. effect(() => {
  367. n = obj.foo.length
  368. })
  369. expect(n).toBe(1)
  370. // mutating original
  371. val.push(2)
  372. expect(n).toBe(2)
  373. })
  374. })
  375. test('global asset registration should affect apps created via createApp', () => {
  376. Vue.component('foo', { template: 'foo' })
  377. const vm = createApp({
  378. template: '<foo/>'
  379. }).mount(document.createElement('div')) as any
  380. expect(vm.$el.textContent).toBe('foo')
  381. delete singletonApp._context.components.foo
  382. })