observer_spec.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. var Observer = require('../../../../src/observer')
  2. var Dep = require('../../../../src/observer/dep')
  3. var _ = require('../../../../src/util')
  4. var config = require('../../../../src/config')
  5. describe('Observer', function () {
  6. beforeEach(function () {
  7. spyOn(_, 'warn')
  8. })
  9. it('create on non-observables', function () {
  10. // skip primitive value
  11. var ob = Observer.create(1)
  12. expect(ob).toBeUndefined()
  13. // avoid vue instance
  14. ob = Observer.create(new _.Vue())
  15. expect(ob).toBeUndefined()
  16. // avoid frozen objects
  17. ob = Observer.create(Object.freeze({}))
  18. expect(ob).toBeUndefined()
  19. })
  20. it('create on object', function () {
  21. // on object
  22. var obj = {
  23. a: {},
  24. b: {}
  25. }
  26. var ob = Observer.create(obj)
  27. expect(ob instanceof Observer).toBe(true)
  28. expect(ob.value).toBe(obj)
  29. expect(obj.__ob__).toBe(ob)
  30. // should've walked children
  31. expect(obj.a.__ob__ instanceof Observer).toBe(true)
  32. expect(obj.b.__ob__ instanceof Observer).toBe(true)
  33. // should return existing ob on already observed objects
  34. var ob2 = Observer.create(obj)
  35. expect(ob2).toBe(ob)
  36. })
  37. it('create on already observed object', function () {
  38. var previousConvertAllProperties = config.convertAllProperties
  39. config.convertAllProperties = true
  40. // on object
  41. var obj = {}
  42. var val = 0
  43. Object.defineProperty(obj, 'a', {
  44. configurable: true,
  45. enumerable: true,
  46. get: function () {
  47. return val
  48. },
  49. set: function (v) {
  50. val = v
  51. }
  52. })
  53. var ob = Observer.create(obj)
  54. expect(ob instanceof Observer).toBe(true)
  55. expect(ob.value).toBe(obj)
  56. expect(obj.__ob__).toBe(ob)
  57. // should return existing ob on already observed objects
  58. var ob2 = Observer.create(obj)
  59. expect(ob2).toBe(ob)
  60. // should call underlying setter
  61. obj.a = 10
  62. expect(val).toBe(10)
  63. config.convertAllProperties = previousConvertAllProperties
  64. })
  65. it('create on array', function () {
  66. // on object
  67. var arr = [{}, {}]
  68. var ob = Observer.create(arr)
  69. expect(ob instanceof Observer).toBe(true)
  70. expect(ob.value).toBe(arr)
  71. expect(arr.__ob__).toBe(ob)
  72. // should've walked children
  73. expect(arr[0].__ob__ instanceof Observer).toBe(true)
  74. expect(arr[1].__ob__ instanceof Observer).toBe(true)
  75. })
  76. it('observing object prop change', function () {
  77. var obj = { a: { b: 2 } }
  78. Observer.create(obj)
  79. // mock a watcher!
  80. var watcher = {
  81. deps: [],
  82. addDep: function (dep) {
  83. this.deps.push(dep)
  84. dep.addSub(this)
  85. },
  86. update: jasmine.createSpy()
  87. }
  88. // collect dep
  89. Dep.target = watcher
  90. obj.a.b
  91. Dep.target = null
  92. expect(watcher.deps.length).toBe(3) // obj.a + a.b + b
  93. obj.a.b = 3
  94. expect(watcher.update.calls.count()).toBe(1)
  95. // swap object
  96. obj.a = { b: 4 }
  97. expect(watcher.update.calls.count()).toBe(2)
  98. watcher.deps = []
  99. Dep.target = watcher
  100. obj.a.b
  101. Dep.target = null
  102. expect(watcher.deps.length).toBe(3)
  103. // set on the swapped object
  104. obj.a.b = 5
  105. expect(watcher.update.calls.count()).toBe(3)
  106. })
  107. it('observing object prop change on defined property', function () {
  108. var previousConvertAllProperties = config.convertAllProperties
  109. config.convertAllProperties = true
  110. var obj = { val: 2 }
  111. Object.defineProperty(obj, 'a', {
  112. configurable: true,
  113. enumerable: true,
  114. get: function () {
  115. return this.val
  116. },
  117. set: function (v) {
  118. this.val = v
  119. return this.val
  120. }
  121. })
  122. Observer.create(obj)
  123. // mock a watcher!
  124. var watcher = {
  125. deps: [],
  126. addDep: function (dep) {
  127. this.deps.push(dep)
  128. dep.addSub(this)
  129. },
  130. update: jasmine.createSpy()
  131. }
  132. // collect dep
  133. Dep.target = watcher
  134. expect(obj.a).toBe(2) // Make sure 'this' is preserved
  135. Dep.target = null
  136. obj.a = 3
  137. expect(obj.val).toBe(3) // make sure 'setter' was called
  138. obj.val = 5
  139. expect(obj.a).toBe(5) // make sure 'getter' was called
  140. config.convertAllProperties = previousConvertAllProperties
  141. })
  142. it('observing set/delete', function () {
  143. var obj = { a: 1 }
  144. var ob = Observer.create(obj)
  145. var dep = ob.dep
  146. spyOn(dep, 'notify')
  147. _.set(obj, 'b', 2)
  148. expect(obj.b).toBe(2)
  149. expect(dep.notify.calls.count()).toBe(1)
  150. _.delete(obj, 'a')
  151. expect(obj.hasOwnProperty('a')).toBe(false)
  152. expect(dep.notify.calls.count()).toBe(2)
  153. // set existing key, should be a plain set and not
  154. // trigger own ob's notify
  155. _.set(obj, 'b', 3)
  156. expect(obj.b).toBe(3)
  157. expect(dep.notify.calls.count()).toBe(2)
  158. // set non-existing key
  159. _.set(obj, 'c', 1)
  160. expect(obj.c).toBe(1)
  161. expect(dep.notify.calls.count()).toBe(3)
  162. // should ignore deleting non-existing key
  163. _.delete(obj, 'a')
  164. expect(dep.notify.calls.count()).toBe(3)
  165. // should work on non-observed objects
  166. var obj2 = { a: 1 }
  167. _.delete(obj2, 'a')
  168. expect(obj2.hasOwnProperty('a')).toBe(false)
  169. })
  170. it('observing array mutation', function () {
  171. var arr = []
  172. var ob = Observer.create(arr)
  173. var dep = ob.dep
  174. spyOn(dep, 'notify')
  175. var objs = [{}, {}, {}]
  176. arr.push(objs[0])
  177. arr.pop()
  178. arr.unshift(objs[1])
  179. arr.shift()
  180. arr.splice(0, 0, objs[2])
  181. arr.sort()
  182. arr.reverse()
  183. expect(dep.notify.calls.count()).toBe(7)
  184. // inserted elements should be observed
  185. objs.forEach(function (obj) {
  186. expect(obj.__ob__ instanceof Observer).toBe(true)
  187. })
  188. })
  189. it('array $set', function () {
  190. var arr = [1]
  191. var ob = Observer.create(arr)
  192. var dep = ob.dep
  193. spyOn(dep, 'notify')
  194. arr.$set(0, 2)
  195. expect(arr[0]).toBe(2)
  196. expect(dep.notify.calls.count()).toBe(1)
  197. // setting out of bound index
  198. arr.$set(2, 3)
  199. expect(arr[2]).toBe(3)
  200. expect(dep.notify.calls.count()).toBe(2)
  201. })
  202. it('array $remove', function () {
  203. var arr = [{}, {}]
  204. var obj1 = arr[0]
  205. var obj2 = arr[1]
  206. var ob = Observer.create(arr)
  207. var dep = ob.dep
  208. spyOn(dep, 'notify')
  209. // remove by identity, not in array
  210. arr.$remove(obj1)
  211. expect(arr.length).toBe(1)
  212. expect(arr[0]).toBe(obj2)
  213. expect(dep.notify.calls.count()).toBe(1)
  214. // remove by identity, in array
  215. arr.$remove(obj2)
  216. expect(arr.length).toBe(0)
  217. expect(dep.notify.calls.count()).toBe(2)
  218. })
  219. it('no proto', function () {
  220. _.hasProto = false
  221. var arr = [1, 2, 3]
  222. var ob2 = Observer.create(arr)
  223. expect(arr.$set).toBeTruthy()
  224. expect(arr.$remove).toBeTruthy()
  225. expect(arr.push).not.toBe([].push)
  226. var dep2 = ob2.dep
  227. spyOn(dep2, 'notify')
  228. arr.push(1)
  229. expect(dep2.notify).toHaveBeenCalled()
  230. _.hasProto = true
  231. })
  232. })