lifecycle.spec.ts 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. import Vue from 'vue'
  2. describe('Options lifecycle hooks', () => {
  3. let spy
  4. beforeEach(() => {
  5. spy = vi.fn()
  6. })
  7. describe('beforeCreate', () => {
  8. it('should allow modifying options', () => {
  9. const vm = new Vue({
  10. data: {
  11. a: 1
  12. },
  13. beforeCreate() {
  14. spy()
  15. expect(this.a).toBeUndefined()
  16. this.$options.computed = {
  17. b() {
  18. return this.a + 1
  19. }
  20. }
  21. }
  22. })
  23. expect(spy).toHaveBeenCalled()
  24. expect(vm.b).toBe(2)
  25. })
  26. })
  27. describe('created', () => {
  28. it('should have completed observation', () => {
  29. new Vue({
  30. data: {
  31. a: 1
  32. },
  33. created() {
  34. expect(this.a).toBe(1)
  35. spy()
  36. }
  37. })
  38. expect(spy).toHaveBeenCalled()
  39. })
  40. })
  41. describe('beforeMount', () => {
  42. it('should not have mounted', () => {
  43. const vm = new Vue({
  44. render() {},
  45. beforeMount() {
  46. spy()
  47. expect(this._isMounted).toBe(false)
  48. expect(this.$el).toBeUndefined() // due to empty mount
  49. expect(this._vnode).toBeNull()
  50. expect(this._watcher).toBeNull()
  51. }
  52. })
  53. expect(spy).not.toHaveBeenCalled()
  54. vm.$mount()
  55. expect(spy).toHaveBeenCalled()
  56. })
  57. })
  58. describe('mounted', () => {
  59. it('should have mounted', () => {
  60. const vm = new Vue({
  61. template: '<div></div>',
  62. mounted() {
  63. spy()
  64. expect(this._isMounted).toBe(true)
  65. expect(this.$el.tagName).toBe('DIV')
  66. expect(this._vnode.tag).toBe('div')
  67. }
  68. })
  69. expect(spy).not.toHaveBeenCalled()
  70. vm.$mount()
  71. expect(spy).toHaveBeenCalled()
  72. })
  73. // #3898
  74. it('should call for manually mounted instance with parent', () => {
  75. const parent = new Vue()
  76. expect(spy).not.toHaveBeenCalled()
  77. new Vue({
  78. parent,
  79. template: '<div></div>',
  80. mounted() {
  81. spy()
  82. }
  83. }).$mount()
  84. expect(spy).toHaveBeenCalled()
  85. })
  86. it('should mount child parent in correct order', () => {
  87. const calls: any[] = []
  88. new Vue({
  89. template: '<div><test></test></div>',
  90. mounted() {
  91. calls.push('parent')
  92. },
  93. components: {
  94. test: {
  95. template: '<nested></nested>',
  96. mounted() {
  97. expect(this.$el.parentNode).toBeTruthy()
  98. calls.push('child')
  99. },
  100. components: {
  101. nested: {
  102. template: '<div></div>',
  103. mounted() {
  104. expect(this.$el.parentNode).toBeTruthy()
  105. calls.push('nested')
  106. }
  107. }
  108. }
  109. }
  110. }
  111. }).$mount()
  112. expect(calls).toEqual(['nested', 'child', 'parent'])
  113. })
  114. })
  115. describe('beforeUpdate', () => {
  116. it('should be called before update', done => {
  117. const vm = new Vue({
  118. template: '<div>{{ msg }}</div>',
  119. data: { msg: 'foo' },
  120. beforeUpdate() {
  121. spy()
  122. expect(this.$el.textContent).toBe('foo')
  123. }
  124. }).$mount()
  125. expect(spy).not.toHaveBeenCalled()
  126. vm.msg = 'bar'
  127. expect(spy).not.toHaveBeenCalled() // should be async
  128. waitForUpdate(() => {
  129. expect(spy).toHaveBeenCalled()
  130. }).then(done)
  131. })
  132. it('should be called before render and allow mutating state', done => {
  133. const vm = new Vue({
  134. template: '<div>{{ msg }}</div>',
  135. data: { msg: 'foo' },
  136. beforeUpdate() {
  137. this.msg += '!'
  138. }
  139. }).$mount()
  140. expect(vm.$el.textContent).toBe('foo')
  141. vm.msg = 'bar'
  142. waitForUpdate(() => {
  143. expect(vm.$el.textContent).toBe('bar!')
  144. }).then(done)
  145. })
  146. // #8076
  147. it('should not be called after destroy', done => {
  148. const beforeUpdate = vi.fn()
  149. const destroyed = vi.fn()
  150. Vue.component('todo', {
  151. template: '<div>{{todo.done}}</div>',
  152. props: ['todo'],
  153. destroyed,
  154. beforeUpdate
  155. })
  156. const vm = new Vue({
  157. template: `
  158. <div>
  159. <todo v-for="t in pendingTodos" :todo="t" :key="t.id"></todo>
  160. </div>
  161. `,
  162. data() {
  163. return {
  164. todos: [{ id: 1, done: false }]
  165. }
  166. },
  167. computed: {
  168. pendingTodos() {
  169. return this.todos.filter(t => !t.done)
  170. }
  171. }
  172. }).$mount()
  173. vm.todos[0].done = true
  174. waitForUpdate(() => {
  175. expect(destroyed).toHaveBeenCalled()
  176. expect(beforeUpdate).not.toHaveBeenCalled()
  177. }).then(done)
  178. })
  179. })
  180. describe('updated', () => {
  181. it('should be called after update', done => {
  182. const vm = new Vue({
  183. template: '<div>{{ msg }}</div>',
  184. data: { msg: 'foo' },
  185. updated() {
  186. spy()
  187. expect(this.$el.textContent).toBe('bar')
  188. }
  189. }).$mount()
  190. expect(spy).not.toHaveBeenCalled()
  191. vm.msg = 'bar'
  192. expect(spy).not.toHaveBeenCalled() // should be async
  193. waitForUpdate(() => {
  194. expect(spy).toHaveBeenCalled()
  195. }).then(done)
  196. })
  197. it('should be called after children are updated', done => {
  198. const calls: any[] = []
  199. const vm = new Vue({
  200. template: '<div><test ref="child">{{ msg }}</test></div>',
  201. data: { msg: 'foo' },
  202. components: {
  203. test: {
  204. template: `<div><slot></slot></div>`,
  205. updated() {
  206. expect(this.$el.textContent).toBe('bar')
  207. calls.push('child')
  208. }
  209. }
  210. },
  211. updated() {
  212. expect(this.$el.textContent).toBe('bar')
  213. calls.push('parent')
  214. }
  215. }).$mount()
  216. expect(calls).toEqual([])
  217. vm.msg = 'bar'
  218. expect(calls).toEqual([])
  219. waitForUpdate(() => {
  220. expect(calls).toEqual(['child', 'parent'])
  221. }).then(done)
  222. })
  223. // #8076
  224. it('should not be called after destroy', done => {
  225. const updated = vi.fn()
  226. const destroyed = vi.fn()
  227. Vue.component('todo', {
  228. template: '<div>{{todo.done}}</div>',
  229. props: ['todo'],
  230. destroyed,
  231. updated
  232. })
  233. const vm = new Vue({
  234. template: `
  235. <div>
  236. <todo v-for="t in pendingTodos" :todo="t" :key="t.id"></todo>
  237. </div>
  238. `,
  239. data() {
  240. return {
  241. todos: [{ id: 1, done: false }]
  242. }
  243. },
  244. computed: {
  245. pendingTodos() {
  246. return this.todos.filter(t => !t.done)
  247. }
  248. }
  249. }).$mount()
  250. vm.todos[0].done = true
  251. waitForUpdate(() => {
  252. expect(destroyed).toHaveBeenCalled()
  253. expect(updated).not.toHaveBeenCalled()
  254. }).then(done)
  255. })
  256. })
  257. describe('beforeDestroy', () => {
  258. it('should be called before destroy', () => {
  259. const vm = new Vue({
  260. render() {},
  261. beforeDestroy() {
  262. spy()
  263. expect(this._isBeingDestroyed).toBe(false)
  264. expect(this._isDestroyed).toBe(false)
  265. }
  266. }).$mount()
  267. expect(spy).not.toHaveBeenCalled()
  268. vm.$destroy()
  269. vm.$destroy()
  270. expect(spy).toHaveBeenCalled()
  271. expect(spy.mock.calls.length).toBe(1)
  272. })
  273. })
  274. describe('destroyed', () => {
  275. it('should be called after destroy', () => {
  276. const vm = new Vue({
  277. render() {},
  278. destroyed() {
  279. spy()
  280. expect(this._isBeingDestroyed).toBe(true)
  281. expect(this._isDestroyed).toBe(true)
  282. }
  283. }).$mount()
  284. expect(spy).not.toHaveBeenCalled()
  285. vm.$destroy()
  286. vm.$destroy()
  287. expect(spy).toHaveBeenCalled()
  288. expect(spy.mock.calls.length).toBe(1)
  289. })
  290. })
  291. it('should emit hook events', () => {
  292. const created = vi.fn()
  293. const mounted = vi.fn()
  294. const destroyed = vi.fn()
  295. const vm = new Vue({
  296. render() {},
  297. beforeCreate() {
  298. this.$on('hook:created', created)
  299. this.$on('hook:mounted', mounted)
  300. this.$on('hook:destroyed', destroyed)
  301. }
  302. })
  303. expect(created).toHaveBeenCalled()
  304. expect(mounted).not.toHaveBeenCalled()
  305. expect(destroyed).not.toHaveBeenCalled()
  306. vm.$mount()
  307. expect(mounted).toHaveBeenCalled()
  308. expect(destroyed).not.toHaveBeenCalled()
  309. vm.$destroy()
  310. expect(destroyed).toHaveBeenCalled()
  311. })
  312. })