global.spec.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  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).toBeInstanceOf(HTMLDivElement)
  114. expect(vm.$el.innerHTML).toBe('<span>foo</span><span>bar</span>')
  115. })
  116. it('should merge lifecycle hooks', () => {
  117. const calls: number[] = []
  118. const A = Vue.extend({
  119. created() {
  120. calls.push(1)
  121. },
  122. })
  123. const B = A.extend({
  124. created() {
  125. calls.push(2)
  126. },
  127. })
  128. new B({
  129. created() {
  130. calls.push(3)
  131. },
  132. })
  133. expect(calls).toEqual([1, 2, 3])
  134. })
  135. it('should not merge nested mixins created with Vue.extend', () => {
  136. const a = vi.fn()
  137. const b = vi.fn()
  138. const c = vi.fn()
  139. const d = vi.fn()
  140. const A = Vue.extend({
  141. created: a,
  142. })
  143. const B = Vue.extend({
  144. mixins: [A],
  145. created: b,
  146. })
  147. const C = Vue.extend({
  148. extends: B,
  149. created: c,
  150. })
  151. const D = Vue.extend({
  152. mixins: [C],
  153. created: d,
  154. render() {
  155. return null
  156. },
  157. })
  158. new D().$mount()
  159. expect(a.mock.calls.length).toStrictEqual(1)
  160. expect(b.mock.calls.length).toStrictEqual(1)
  161. expect(c.mock.calls.length).toStrictEqual(1)
  162. expect(d.mock.calls.length).toStrictEqual(1)
  163. })
  164. it('should merge methods', () => {
  165. const A = Vue.extend({
  166. methods: {
  167. a() {
  168. return this.n
  169. },
  170. },
  171. })
  172. const B = A.extend({
  173. methods: {
  174. b() {
  175. return this.n + 1
  176. },
  177. },
  178. })
  179. const b = new B({
  180. data: () => ({ n: 0 }),
  181. methods: {
  182. c() {
  183. return this.n + 2
  184. },
  185. },
  186. }) as any
  187. expect(b.a()).toBe(0)
  188. expect(b.b()).toBe(1)
  189. expect(b.c()).toBe(2)
  190. })
  191. it('should merge assets', () => {
  192. const A = Vue.extend({
  193. components: {
  194. aa: {
  195. template: '<div>A</div>',
  196. },
  197. },
  198. })
  199. const B = A.extend({
  200. components: {
  201. bb: {
  202. template: '<div>B</div>',
  203. },
  204. },
  205. })
  206. const b = new B({
  207. template: '<div><aa></aa><bb></bb></div>',
  208. }).$mount()
  209. expect(b.$el).toBeInstanceOf(HTMLDivElement)
  210. expect(b.$el.innerHTML).toBe('<div>A</div><div>B</div>')
  211. })
  212. it('caching', () => {
  213. const options = {
  214. template: '<div></div>',
  215. }
  216. const A = Vue.extend(options)
  217. const B = Vue.extend(options)
  218. expect(A).toBe(B)
  219. })
  220. it('extended options should use different identify from parent', () => {
  221. const A = Vue.extend({ computed: {} })
  222. const B = A.extend()
  223. B.options.computed.b = () => 'foo'
  224. expect(B.options.computed).not.toBe(A.options.computed)
  225. expect(A.options.computed.b).toBeUndefined()
  226. })
  227. })
  228. describe('GLOBAL_PROTOTYPE', () => {
  229. test('plain properties', () => {
  230. toggleDeprecationWarning(true)
  231. Vue.prototype.$test = 1
  232. const vm = new Vue() as any
  233. expect(vm.$test).toBe(1)
  234. delete Vue.prototype.$test
  235. expect(
  236. deprecationData[DeprecationTypes.GLOBAL_MOUNT].message,
  237. ).toHaveBeenWarned()
  238. expect(
  239. deprecationData[DeprecationTypes.GLOBAL_PROTOTYPE].message,
  240. ).toHaveBeenWarned()
  241. })
  242. test('method this context', () => {
  243. Vue.prototype.$test = function () {
  244. return this.msg
  245. }
  246. const vm = new Vue({
  247. data() {
  248. return { msg: 'method' }
  249. },
  250. }) as any
  251. expect(vm.$test()).toBe('method')
  252. delete Vue.prototype.$test
  253. })
  254. test('defined properties', () => {
  255. Object.defineProperty(Vue.prototype, '$test', {
  256. configurable: true,
  257. get() {
  258. return this.msg
  259. },
  260. })
  261. const vm = new Vue({
  262. data() {
  263. return { msg: 'getter' }
  264. },
  265. }) as any
  266. expect(vm.$test).toBe('getter')
  267. delete Vue.prototype.$test
  268. })
  269. test('functions keeps additional properties', () => {
  270. function test(this: any) {
  271. return this.msg
  272. }
  273. test.additionalFn = () => {
  274. return 'additional fn'
  275. }
  276. Vue.prototype.$test = test
  277. const vm = new Vue({
  278. data() {
  279. return {
  280. msg: 'test',
  281. }
  282. },
  283. }) as any
  284. expect(typeof vm.$test).toBe('function')
  285. expect(typeof vm.$test.additionalFn).toBe('function')
  286. expect(vm.$test.additionalFn()).toBe('additional fn')
  287. delete Vue.prototype.$test
  288. })
  289. test('extended prototype', async () => {
  290. const Foo = Vue.extend()
  291. Foo.prototype.$test = 1
  292. const vm = new Foo() as any
  293. expect(vm.$test).toBe(1)
  294. const plain = new Vue() as any
  295. expect(plain.$test).toBeUndefined()
  296. })
  297. test('should affect apps created via createApp()', () => {
  298. Vue.prototype.$test = 1
  299. const vm = createApp({
  300. template: 'foo',
  301. }).mount(document.createElement('div')) as any
  302. expect(vm.$test).toBe(1)
  303. delete Vue.prototype.$test
  304. })
  305. })
  306. describe('GLOBAL_SET/DELETE', () => {
  307. test('set', () => {
  308. toggleDeprecationWarning(true)
  309. const obj: any = {}
  310. Vue.set(obj, 'foo', 1)
  311. expect(obj.foo).toBe(1)
  312. expect(
  313. deprecationData[DeprecationTypes.GLOBAL_SET].message,
  314. ).toHaveBeenWarned()
  315. })
  316. test('delete', () => {
  317. toggleDeprecationWarning(true)
  318. const obj: any = { foo: 1 }
  319. Vue.delete(obj, 'foo')
  320. expect('foo' in obj).toBe(false)
  321. expect(
  322. deprecationData[DeprecationTypes.GLOBAL_DELETE].message,
  323. ).toHaveBeenWarned()
  324. })
  325. })
  326. describe('GLOBAL_OBSERVABLE', () => {
  327. test('should work', () => {
  328. toggleDeprecationWarning(true)
  329. const obj = Vue.observable({})
  330. expect(isReactive(obj)).toBe(true)
  331. expect(
  332. deprecationData[DeprecationTypes.GLOBAL_OBSERVABLE].message,
  333. ).toHaveBeenWarned()
  334. })
  335. })
  336. describe('GLOBAL_PRIVATE_UTIL', () => {
  337. test('defineReactive', () => {
  338. toggleDeprecationWarning(true)
  339. const obj: any = {}
  340. Vue.util.defineReactive(obj, 'test', 1)
  341. let n
  342. effect(() => {
  343. n = obj.test
  344. })
  345. expect(n).toBe(1)
  346. obj.test++
  347. expect(n).toBe(2)
  348. expect(
  349. deprecationData[DeprecationTypes.GLOBAL_PRIVATE_UTIL].message,
  350. ).toHaveBeenWarned()
  351. })
  352. test('defineReactive on instance', async () => {
  353. const vm = new Vue({
  354. beforeCreate() {
  355. Vue.util.defineReactive(this, 'foo', 1)
  356. },
  357. template: `<div>{{ foo }}</div>`,
  358. }).$mount() as any
  359. expect(vm.$el).toBeInstanceOf(HTMLDivElement)
  360. expect(vm.$el.textContent).toBe('1')
  361. vm.foo = 2
  362. await nextTick()
  363. expect(vm.$el.textContent).toBe('2')
  364. })
  365. test('defineReactive on instance with key that starts with $', async () => {
  366. const vm = new Vue({
  367. beforeCreate() {
  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. Vue.util.defineReactive(obj, 'foo', val)
  381. let n
  382. effect(() => {
  383. n = obj.foo.a
  384. })
  385. expect(n).toBe(1)
  386. // mutating original
  387. val.a++
  388. expect(n).toBe(2)
  389. })
  390. test('defineReactive with array value', () => {
  391. const obj: any = {}
  392. const val = [1]
  393. Vue.util.defineReactive(obj, 'foo', val)
  394. let n
  395. effect(() => {
  396. n = obj.foo.length
  397. })
  398. expect(n).toBe(1)
  399. // mutating original
  400. val.push(2)
  401. expect(n).toBe(2)
  402. })
  403. })
  404. test('global asset registration should affect apps created via createApp', () => {
  405. Vue.component('foo', { template: 'foo' })
  406. const vm = createApp({
  407. template: '<foo/>',
  408. }).mount(document.createElement('div')) as any
  409. expect(vm.$el.textContent).toBe('foo')
  410. delete singletonApp._context.components.foo
  411. })
  412. test('post-facto global asset registration should affect apps created via createApp', () => {
  413. const app = createApp({
  414. template: '<foo/>',
  415. })
  416. Vue.component('foo', { template: 'foo' })
  417. const vm = app.mount(document.createElement('div'))
  418. expect(vm.$el.textContent).toBe('foo')
  419. delete singletonApp._context.components.foo
  420. })
  421. test('local asset registration should not affect other local apps', () => {
  422. const app1 = createApp({})
  423. const app2 = createApp({})
  424. app1.component('foo', {})
  425. app2.component('foo', {})
  426. expect(
  427. `Component "foo" has already been registered in target app`,
  428. ).not.toHaveBeenWarned()
  429. })
  430. test('local app-level mixin registration should not affect other local apps', () => {
  431. const app1 = createApp({ render: () => h('div') })
  432. const app2 = createApp({})
  433. const mixin = { created: vi.fn() }
  434. app1.mixin(mixin)
  435. app2.mixin(mixin)
  436. expect(`Mixin has already been applied`).not.toHaveBeenWarned()
  437. app1.mount(document.createElement('div'))
  438. expect(mixin.created).toHaveBeenCalledTimes(1)
  439. })
  440. // #5699
  441. test('local app config should not affect other local apps in v3 mode', () => {
  442. Vue.configureCompat({ MODE: 3 })
  443. const app1 = createApp({
  444. render: () => h('div'),
  445. provide() {
  446. return {
  447. test: 123,
  448. }
  449. },
  450. })
  451. app1.config.globalProperties.test = () => {}
  452. app1.mount(document.createElement('div'))
  453. const app2 = createApp({})
  454. expect(app2.config.globalProperties.test).toBe(undefined)
  455. })