watcher.spec.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. import Vue from 'vue'
  2. import Watcher from 'core/observer/watcher'
  3. describe('Watcher', () => {
  4. let vm, spy
  5. beforeEach(() => {
  6. vm = new Vue({
  7. template: '<div></div>',
  8. data: {
  9. a: 1,
  10. b: {
  11. c: 2,
  12. d: 4
  13. },
  14. c: 'c',
  15. msg: 'yo'
  16. }
  17. }).$mount()
  18. spy = jasmine.createSpy('watcher')
  19. })
  20. it('path', done => {
  21. const watcher = new Watcher(vm, 'b.c', spy)
  22. expect(watcher.value).toBe(2)
  23. vm.b.c = 3
  24. waitForUpdate(() => {
  25. expect(watcher.value).toBe(3)
  26. expect(spy).toHaveBeenCalledWith(3, 2)
  27. vm.b = { c: 4 } // swapping the object
  28. }).then(() => {
  29. expect(watcher.value).toBe(4)
  30. expect(spy).toHaveBeenCalledWith(4, 3)
  31. }).then(done)
  32. })
  33. it('non-existent path, set later', done => {
  34. const watcher1 = new Watcher(vm, 'b.e', spy)
  35. expect(watcher1.value).toBeUndefined()
  36. // check $add should not affect isolated children
  37. const child2 = new Vue({ parent: vm })
  38. const watcher2 = new Watcher(child2, 'b.e', spy)
  39. expect(watcher2.value).toBeUndefined()
  40. Vue.set(vm.b, 'e', 123)
  41. waitForUpdate(() => {
  42. expect(watcher1.value).toBe(123)
  43. expect(watcher2.value).toBeUndefined()
  44. expect(spy.calls.count()).toBe(1)
  45. expect(spy).toHaveBeenCalledWith(123, undefined)
  46. }).then(done)
  47. })
  48. it('delete', done => {
  49. const watcher = new Watcher(vm, 'b.c', spy)
  50. expect(watcher.value).toBe(2)
  51. Vue.delete(vm.b, 'c')
  52. waitForUpdate(() => {
  53. expect(watcher.value).toBeUndefined()
  54. expect(spy).toHaveBeenCalledWith(undefined, 2)
  55. }).then(done)
  56. })
  57. it('path containing $data', done => {
  58. const watcher = new Watcher(vm, '$data.b.c', spy)
  59. expect(watcher.value).toBe(2)
  60. vm.b = { c: 3 }
  61. waitForUpdate(() => {
  62. expect(watcher.value).toBe(3)
  63. expect(spy).toHaveBeenCalledWith(3, 2)
  64. vm.$data.b.c = 4
  65. }).then(() => {
  66. expect(watcher.value).toBe(4)
  67. expect(spy).toHaveBeenCalledWith(4, 3)
  68. }).then(done)
  69. })
  70. it('deep watch', done => {
  71. let oldB
  72. new Watcher(vm, 'b', spy, {
  73. deep: true
  74. })
  75. vm.b.c = { d: 4 }
  76. waitForUpdate(() => {
  77. expect(spy).toHaveBeenCalledWith(vm.b, vm.b)
  78. oldB = vm.b
  79. vm.b = { c: [{ a: 1 }] }
  80. }).then(() => {
  81. expect(spy).toHaveBeenCalledWith(vm.b, oldB)
  82. expect(spy.calls.count()).toBe(2)
  83. vm.b.c[0].a = 2
  84. }).then(() => {
  85. expect(spy).toHaveBeenCalledWith(vm.b, vm.b)
  86. expect(spy.calls.count()).toBe(3)
  87. }).then(done)
  88. })
  89. it('deep watch $data', done => {
  90. new Watcher(vm, '$data', spy, {
  91. deep: true
  92. })
  93. vm.b.c = 3
  94. waitForUpdate(() => {
  95. expect(spy).toHaveBeenCalledWith(vm.$data, vm.$data)
  96. }).then(done)
  97. })
  98. it('deep watch with circular references', done => {
  99. new Watcher(vm, 'b', spy, {
  100. deep: true
  101. })
  102. Vue.set(vm.b, '_', vm.b)
  103. waitForUpdate(() => {
  104. expect(spy).toHaveBeenCalledWith(vm.b, vm.b)
  105. expect(spy.calls.count()).toBe(1)
  106. vm.b._.c = 1
  107. }).then(() => {
  108. expect(spy).toHaveBeenCalledWith(vm.b, vm.b)
  109. expect(spy.calls.count()).toBe(2)
  110. }).then(done)
  111. })
  112. it('fire change for prop addition/deletion in non-deep mode', done => {
  113. new Watcher(vm, 'b', spy)
  114. Vue.set(vm.b, 'e', 123)
  115. waitForUpdate(() => {
  116. expect(spy).toHaveBeenCalledWith(vm.b, vm.b)
  117. expect(spy.calls.count()).toBe(1)
  118. Vue.delete(vm.b, 'e')
  119. }).then(() => {
  120. expect(spy.calls.count()).toBe(2)
  121. }).then(done)
  122. })
  123. it('watch function', done => {
  124. const watcher = new Watcher(vm, function () {
  125. return this.a + this.b.d
  126. }, spy)
  127. expect(watcher.value).toBe(5)
  128. vm.a = 2
  129. waitForUpdate(() => {
  130. expect(spy).toHaveBeenCalledWith(6, 5)
  131. vm.b = { d: 2 }
  132. }).then(() => {
  133. expect(spy).toHaveBeenCalledWith(4, 6)
  134. }).then(done)
  135. })
  136. it('computed mode, lazy', done => {
  137. let getterCallCount = 0
  138. const watcher = new Watcher(vm, function () {
  139. getterCallCount++
  140. return this.a + this.b.d
  141. }, null, { computed: true })
  142. expect(getterCallCount).toBe(0)
  143. expect(watcher.computed).toBe(true)
  144. expect(watcher.value).toBeUndefined()
  145. expect(watcher.dirty).toBe(true)
  146. expect(watcher.dep).toBeTruthy()
  147. const value = watcher.evaluate()
  148. expect(getterCallCount).toBe(1)
  149. expect(value).toBe(5)
  150. expect(watcher.value).toBe(5)
  151. expect(watcher.dirty).toBe(false)
  152. // should not get again if not dirty
  153. watcher.evaluate()
  154. expect(getterCallCount).toBe(1)
  155. vm.a = 2
  156. waitForUpdate(() => {
  157. expect(getterCallCount).toBe(1)
  158. expect(watcher.value).toBe(5)
  159. expect(watcher.dirty).toBe(true)
  160. const value = watcher.evaluate()
  161. expect(getterCallCount).toBe(2)
  162. expect(value).toBe(6)
  163. expect(watcher.value).toBe(6)
  164. expect(watcher.dirty).toBe(false)
  165. }).then(done)
  166. })
  167. it('computed mode, activated', done => {
  168. let getterCallCount = 0
  169. const watcher = new Watcher(vm, function () {
  170. getterCallCount++
  171. return this.a + this.b.d
  172. }, null, { computed: true })
  173. // activate by mocking a subscriber
  174. const subMock = jasmine.createSpyObj('sub', ['update'])
  175. watcher.dep.addSub(subMock)
  176. const value = watcher.evaluate()
  177. expect(getterCallCount).toBe(1)
  178. expect(value).toBe(5)
  179. vm.a = 2
  180. waitForUpdate(() => {
  181. expect(getterCallCount).toBe(2)
  182. expect(subMock.update).toHaveBeenCalled()
  183. // since already computed, calling evaluate again should not trigger
  184. // getter
  185. watcher.evaluate()
  186. expect(getterCallCount).toBe(2)
  187. }).then(done)
  188. })
  189. it('teardown', done => {
  190. const watcher = new Watcher(vm, 'b.c', spy)
  191. watcher.teardown()
  192. vm.b.c = 3
  193. waitForUpdate(() => {
  194. expect(watcher.active).toBe(false)
  195. expect(spy).not.toHaveBeenCalled()
  196. }).then(done)
  197. })
  198. it('warn not support path', () => {
  199. new Watcher(vm, 'd.e + c', spy)
  200. expect('Failed watching path:').toHaveBeenWarned()
  201. })
  202. })