instance.spec.ts 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. import type { Mock } from 'vitest'
  2. import Vue from '@vue/compat'
  3. import type { Slots } from '../../runtime-core/src/componentSlots'
  4. import { Text } from '../../runtime-core/src/vnode'
  5. import {
  6. DeprecationTypes,
  7. deprecationData,
  8. toggleDeprecationWarning,
  9. } from '../../runtime-core/src/compat/compatConfig'
  10. import type { LegacyPublicInstance } from '../../runtime-core/src/compat/instance'
  11. beforeEach(() => {
  12. toggleDeprecationWarning(true)
  13. Vue.configureCompat({
  14. MODE: 2,
  15. GLOBAL_MOUNT: 'suppress-warning',
  16. PRIVATE_APIS: 'suppress-warning',
  17. })
  18. })
  19. afterEach(() => {
  20. toggleDeprecationWarning(false)
  21. Vue.configureCompat({ MODE: 3 })
  22. })
  23. test('INSTANCE_SET', () => {
  24. const obj: any = {}
  25. new Vue().$set(obj, 'foo', 1)
  26. expect(obj.foo).toBe(1)
  27. expect(
  28. deprecationData[DeprecationTypes.INSTANCE_SET].message,
  29. ).toHaveBeenWarned()
  30. })
  31. test('INSTANCE_DELETE', () => {
  32. const obj: any = { foo: 1 }
  33. new Vue().$delete(obj, 'foo')
  34. expect('foo' in obj).toBe(false)
  35. expect(
  36. deprecationData[DeprecationTypes.INSTANCE_DELETE].message,
  37. ).toHaveBeenWarned()
  38. })
  39. test('INSTANCE_DESTROY', () => {
  40. new Vue({ template: 'foo' }).$mount().$destroy()
  41. expect(
  42. deprecationData[DeprecationTypes.INSTANCE_DESTROY].message,
  43. ).toHaveBeenWarned()
  44. })
  45. // https://github.com/vuejs/vue/blob/dev/test/unit/features/instance/methods-events.spec.js
  46. describe('INSTANCE_EVENT_EMITTER', () => {
  47. let vm: LegacyPublicInstance
  48. let spy: Mock
  49. beforeEach(() => {
  50. vm = new Vue()
  51. spy = vi.fn()
  52. })
  53. it('$on', () => {
  54. vm.$on('test', function (this: any) {
  55. // expect correct context
  56. expect(this).toBe(vm)
  57. spy.apply(this, arguments as unknown as any[])
  58. })
  59. vm.$emit('test', 1, 2, 3, 4)
  60. expect(spy).toHaveBeenCalledTimes(1)
  61. expect(spy).toHaveBeenCalledWith(1, 2, 3, 4)
  62. expect(
  63. deprecationData[DeprecationTypes.INSTANCE_EVENT_EMITTER].message,
  64. ).toHaveBeenWarned()
  65. })
  66. it('$on multi event', () => {
  67. vm.$on(['test1', 'test2'], function (this: any) {
  68. expect(this).toBe(vm)
  69. spy.apply(this, arguments as unknown as any[])
  70. })
  71. vm.$emit('test1', 1, 2, 3, 4)
  72. expect(spy).toHaveBeenCalledTimes(1)
  73. expect(spy).toHaveBeenCalledWith(1, 2, 3, 4)
  74. vm.$emit('test2', 5, 6, 7, 8)
  75. expect(spy).toHaveBeenCalledTimes(2)
  76. expect(spy).toHaveBeenCalledWith(5, 6, 7, 8)
  77. expect(
  78. deprecationData[DeprecationTypes.INSTANCE_EVENT_EMITTER].message,
  79. ).toHaveBeenWarned()
  80. })
  81. it('$off multi event', () => {
  82. vm.$on(['test1', 'test2', 'test3'], spy)
  83. vm.$off(['test1', 'test2'], spy)
  84. vm.$emit('test1')
  85. vm.$emit('test2')
  86. expect(spy).not.toHaveBeenCalled()
  87. vm.$emit('test3', 1, 2, 3, 4)
  88. expect(spy).toHaveBeenCalledTimes(1)
  89. expect(
  90. deprecationData[DeprecationTypes.INSTANCE_EVENT_EMITTER].message,
  91. ).toHaveBeenWarned()
  92. })
  93. it('$off multi event without callback', () => {
  94. vm.$on(['test1', 'test2'], spy)
  95. vm.$off(['test1', 'test2'])
  96. vm.$emit('test1')
  97. expect(spy).not.toHaveBeenCalled()
  98. expect(
  99. deprecationData[DeprecationTypes.INSTANCE_EVENT_EMITTER].message,
  100. ).toHaveBeenWarned()
  101. })
  102. it('$once', () => {
  103. vm.$once('test', spy)
  104. vm.$emit('test', 1, 2, 3)
  105. vm.$emit('test', 2, 3, 4)
  106. expect(spy).toHaveBeenCalledTimes(1)
  107. expect(spy).toHaveBeenCalledWith(1, 2, 3)
  108. expect(
  109. deprecationData[DeprecationTypes.INSTANCE_EVENT_EMITTER].message,
  110. ).toHaveBeenWarned()
  111. })
  112. it('$off event added by $once', () => {
  113. vm.$once('test', spy)
  114. vm.$off('test', spy) // test off event and this event added by once
  115. vm.$emit('test', 1, 2, 3)
  116. expect(spy).not.toHaveBeenCalled()
  117. expect(
  118. deprecationData[DeprecationTypes.INSTANCE_EVENT_EMITTER].message,
  119. ).toHaveBeenWarned()
  120. })
  121. it('$off', () => {
  122. vm.$on('test1', spy)
  123. vm.$on('test2', spy)
  124. vm.$off()
  125. vm.$emit('test1')
  126. vm.$emit('test2')
  127. expect(spy).not.toHaveBeenCalled()
  128. expect(
  129. deprecationData[DeprecationTypes.INSTANCE_EVENT_EMITTER].message,
  130. ).toHaveBeenWarned()
  131. })
  132. it('$off event', () => {
  133. vm.$on('test1', spy)
  134. vm.$on('test2', spy)
  135. vm.$off('test1')
  136. vm.$off('test1') // test off something that's already off
  137. vm.$emit('test1', 1)
  138. vm.$emit('test2', 2)
  139. expect(spy).toHaveBeenCalledTimes(1)
  140. expect(spy).toHaveBeenCalledWith(2)
  141. expect(
  142. deprecationData[DeprecationTypes.INSTANCE_EVENT_EMITTER].message,
  143. ).toHaveBeenWarned()
  144. })
  145. it('$off event + fn', () => {
  146. const spy2 = vi.fn()
  147. vm.$on('test', spy)
  148. vm.$on('test', spy2)
  149. vm.$off('test', spy)
  150. vm.$emit('test', 1, 2, 3)
  151. expect(spy).not.toHaveBeenCalled()
  152. expect(spy2).toHaveBeenCalledTimes(1)
  153. expect(spy2).toHaveBeenCalledWith(1, 2, 3)
  154. expect(
  155. deprecationData[DeprecationTypes.INSTANCE_EVENT_EMITTER].message,
  156. ).toHaveBeenWarned()
  157. })
  158. })
  159. describe('INSTANCE_EVENT_HOOKS', () => {
  160. test('instance API', () => {
  161. const spy = vi.fn()
  162. const vm = new Vue({ template: 'foo' })
  163. vm.$on('hook:mounted', spy)
  164. vm.$mount()
  165. expect(spy).toHaveBeenCalled()
  166. expect(
  167. (
  168. deprecationData[DeprecationTypes.INSTANCE_EVENT_HOOKS]
  169. .message as Function
  170. )('hook:mounted'),
  171. ).toHaveBeenWarned()
  172. })
  173. test('via template', () => {
  174. const spy = vi.fn()
  175. new Vue({
  176. template: `<child @hook:mounted="spy"/>`,
  177. methods: { spy },
  178. components: {
  179. child: {
  180. template: 'foo',
  181. },
  182. },
  183. }).$mount()
  184. expect(spy).toHaveBeenCalled()
  185. expect(
  186. (
  187. deprecationData[DeprecationTypes.INSTANCE_EVENT_HOOKS]
  188. .message as Function
  189. )('hook:mounted'),
  190. ).toHaveBeenWarned()
  191. })
  192. })
  193. test('INSTANCE_EVENT_CHILDREN', () => {
  194. const vm = new Vue({
  195. template: `<child/><div><child v-for="i in 3"/></div>`,
  196. components: {
  197. child: {
  198. template: 'foo',
  199. data() {
  200. return { n: 1 }
  201. },
  202. },
  203. },
  204. }).$mount()
  205. expect(vm.$children.length).toBe(4)
  206. vm.$children.forEach((c: any) => {
  207. expect(c.n).toBe(1)
  208. })
  209. expect(
  210. deprecationData[DeprecationTypes.INSTANCE_CHILDREN].message,
  211. ).toHaveBeenWarned()
  212. })
  213. test('INSTANCE_LISTENERS', () => {
  214. const foo = () => 'foo'
  215. const bar = () => 'bar'
  216. let listeners: Record<string, Function>
  217. new Vue({
  218. template: `<child @click="foo" @custom="bar" />`,
  219. methods: { foo, bar },
  220. components: {
  221. child: {
  222. template: `<div/>`,
  223. mounted() {
  224. listeners = this.$listeners
  225. },
  226. },
  227. },
  228. }).$mount()
  229. expect(Object.keys(listeners!)).toMatchObject(['click', 'custom'])
  230. expect(listeners!.click()).toBe('foo')
  231. expect(listeners!.custom()).toBe('bar')
  232. expect(
  233. deprecationData[DeprecationTypes.INSTANCE_LISTENERS].message,
  234. ).toHaveBeenWarned()
  235. })
  236. describe('INSTANCE_SCOPED_SLOTS', () => {
  237. test('explicit usage', () => {
  238. let slots: Slots
  239. new Vue({
  240. template: `<child v-slot="{ msg }">{{ msg }}</child>`,
  241. components: {
  242. child: {
  243. compatConfig: { RENDER_FUNCTION: false },
  244. render() {
  245. slots = this.$scopedSlots
  246. },
  247. },
  248. },
  249. }).$mount()
  250. expect(slots!.default!({ msg: 'hi' })).toMatchObject([
  251. {
  252. type: Text,
  253. children: 'hi',
  254. },
  255. ])
  256. expect(
  257. deprecationData[DeprecationTypes.INSTANCE_SCOPED_SLOTS].message,
  258. ).toHaveBeenWarned()
  259. })
  260. test('should include legacy slot usage in $scopedSlots', () => {
  261. let normalSlots: Slots
  262. let scopedSlots: Slots
  263. new Vue({
  264. template: `<child><div>default</div></child>`,
  265. components: {
  266. child: {
  267. compatConfig: { RENDER_FUNCTION: false },
  268. render(this: LegacyPublicInstance) {
  269. normalSlots = this.$slots
  270. scopedSlots = this.$scopedSlots
  271. },
  272. },
  273. },
  274. }).$mount()
  275. expect('default' in normalSlots!).toBe(true)
  276. expect('default' in scopedSlots!).toBe(true)
  277. expect(
  278. deprecationData[DeprecationTypes.INSTANCE_SCOPED_SLOTS].message,
  279. ).toHaveBeenWarned()
  280. })
  281. })
  282. test('INSTANCE_ATTR_CLASS_STYLE', () => {
  283. const vm = new Vue({
  284. template: `<child class="foo" style="color:red" id="ok" />`,
  285. components: {
  286. child: {
  287. inheritAttrs: false,
  288. template: `<div><div v-bind="$attrs" /></div>`,
  289. },
  290. },
  291. }).$mount()
  292. expect(vm.$el).toBeInstanceOf(HTMLDivElement)
  293. expect(vm.$el.outerHTML).toBe(
  294. `<div class="foo" style="color: red;"><div id="ok"></div></div>`,
  295. )
  296. expect(
  297. (
  298. deprecationData[DeprecationTypes.INSTANCE_ATTRS_CLASS_STYLE]
  299. .message as Function
  300. )('Anonymous'),
  301. ).toHaveBeenWarned()
  302. })
  303. test('$options mutation', () => {
  304. const Comp = {
  305. props: ['id'],
  306. template: '<div/>',
  307. data() {
  308. return {
  309. foo: '',
  310. }
  311. },
  312. created(this: any) {
  313. expect(this.$options.parent).toBeDefined()
  314. expect(this.$options.test).toBeUndefined()
  315. this.$options.test = this.id
  316. expect(this.$options.test).toBe(this.id)
  317. },
  318. }
  319. new Vue({
  320. template: `<div><Comp id="1"/><Comp id="2"/></div>`,
  321. components: { Comp },
  322. }).$mount()
  323. })
  324. test('other private APIs', () => {
  325. new Vue({
  326. created() {
  327. expect(this.$createElement).toBeTruthy()
  328. },
  329. })
  330. new Vue({
  331. compatConfig: {
  332. PRIVATE_APIS: false,
  333. },
  334. created() {
  335. expect(this.$createElement).toBeUndefined()
  336. },
  337. })
  338. })