observer_spec.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. var Vue = require('src')
  2. var ob = require('src/observer')
  3. var Observer = ob.Observer
  4. var observe = ob.observe
  5. var Dep = require('src/observer/dep')
  6. var _ = require('src/util')
  7. describe('Observer', function () {
  8. it('create on non-observables', function () {
  9. // skip primitive value
  10. var ob = observe(1)
  11. expect(ob).toBeUndefined()
  12. // avoid vue instance
  13. ob = observe(new Vue())
  14. expect(ob).toBeUndefined()
  15. // avoid frozen objects
  16. ob = observe(Object.freeze({}))
  17. expect(ob).toBeUndefined()
  18. })
  19. it('create on object', function () {
  20. // on object
  21. var obj = {
  22. a: {},
  23. b: {}
  24. }
  25. var ob = observe(obj)
  26. expect(ob instanceof Observer).toBe(true)
  27. expect(ob.value).toBe(obj)
  28. expect(obj.__ob__).toBe(ob)
  29. // should've walked children
  30. expect(obj.a.__ob__ instanceof Observer).toBe(true)
  31. expect(obj.b.__ob__ instanceof Observer).toBe(true)
  32. // should return existing ob on already observed objects
  33. var ob2 = observe(obj)
  34. expect(ob2).toBe(ob)
  35. })
  36. it('create on null', function () {
  37. // on null
  38. var obj = Object.create(null)
  39. obj.a = {}
  40. obj.b = {}
  41. var ob = observe(obj)
  42. expect(ob instanceof Observer).toBe(true)
  43. expect(ob.value).toBe(obj)
  44. expect(obj.__ob__).toBe(ob)
  45. // should've walked children
  46. expect(obj.a.__ob__ instanceof Observer).toBe(true)
  47. expect(obj.b.__ob__ instanceof Observer).toBe(true)
  48. // should return existing ob on already observed objects
  49. var ob2 = observe(obj)
  50. expect(ob2).toBe(ob)
  51. })
  52. it('create on already observed object', function () {
  53. // on object
  54. var obj = {}
  55. var val = 0
  56. var getCount = 0
  57. Object.defineProperty(obj, 'a', {
  58. configurable: true,
  59. enumerable: true,
  60. get: function () {
  61. getCount++
  62. return val
  63. },
  64. set: function (v) {
  65. val = v
  66. }
  67. })
  68. var ob = observe(obj)
  69. expect(ob instanceof Observer).toBe(true)
  70. expect(ob.value).toBe(obj)
  71. expect(obj.__ob__).toBe(ob)
  72. getCount = 0
  73. // Each read of 'a' should result in only one get underlying get call
  74. obj.a
  75. expect(getCount).toBe(1)
  76. obj.a
  77. expect(getCount).toBe(2)
  78. // should return existing ob on already observed objects
  79. var ob2 = observe(obj)
  80. expect(ob2).toBe(ob)
  81. // should call underlying setter
  82. obj.a = 10
  83. expect(val).toBe(10)
  84. })
  85. it('create on property with only getter', function () {
  86. // on object
  87. var obj = {}
  88. Object.defineProperty(obj, 'a', {
  89. configurable: true,
  90. enumerable: true,
  91. get: function () {
  92. return 123
  93. }
  94. })
  95. var ob = observe(obj)
  96. expect(ob instanceof Observer).toBe(true)
  97. expect(ob.value).toBe(obj)
  98. expect(obj.__ob__).toBe(ob)
  99. // should be able to read
  100. expect(obj.a).toBe(123)
  101. // should return existing ob on already observed objects
  102. var ob2 = observe(obj)
  103. expect(ob2).toBe(ob)
  104. // since there is no setter, you shouldn't be able to write to it
  105. // PhantomJS throws when a property with no setter is set
  106. // but other real browsers don't
  107. try {
  108. obj.a = 101
  109. } catch (e) {}
  110. expect(obj.a).toBe(123)
  111. })
  112. it('create on property with only setter', function () {
  113. // on object
  114. var obj = {}
  115. var val = 10
  116. Object.defineProperty(obj, 'a', { // eslint-disable-line accessor-pairs
  117. configurable: true,
  118. enumerable: true,
  119. set: function (v) {
  120. val = v
  121. }
  122. })
  123. var ob = observe(obj)
  124. expect(ob instanceof Observer).toBe(true)
  125. expect(ob.value).toBe(obj)
  126. expect(obj.__ob__).toBe(ob)
  127. // reads should return undefined
  128. expect(obj.a).toBe(undefined)
  129. // should return existing ob on already observed objects
  130. var ob2 = observe(obj)
  131. expect(ob2).toBe(ob)
  132. // writes should call the set function
  133. obj.a = 100
  134. expect(val).toBe(100)
  135. })
  136. it('create on property which is marked not configurable', function () {
  137. // on object
  138. var obj = {}
  139. Object.defineProperty(obj, 'a', {
  140. configurable: false,
  141. enumerable: true,
  142. val: 10
  143. })
  144. var ob = observe(obj)
  145. expect(ob instanceof Observer).toBe(true)
  146. expect(ob.value).toBe(obj)
  147. expect(obj.__ob__).toBe(ob)
  148. })
  149. it('create on array', function () {
  150. // on object
  151. var arr = [{}, {}]
  152. var ob = observe(arr)
  153. expect(ob instanceof Observer).toBe(true)
  154. expect(ob.value).toBe(arr)
  155. expect(arr.__ob__).toBe(ob)
  156. // should've walked children
  157. expect(arr[0].__ob__ instanceof Observer).toBe(true)
  158. expect(arr[1].__ob__ instanceof Observer).toBe(true)
  159. })
  160. it('observing object prop change', function () {
  161. var obj = { a: { b: 2 } }
  162. observe(obj)
  163. // mock a watcher!
  164. var watcher = {
  165. deps: [],
  166. addDep: function (dep) {
  167. this.deps.push(dep)
  168. dep.addSub(this)
  169. },
  170. update: jasmine.createSpy()
  171. }
  172. // collect dep
  173. Dep.target = watcher
  174. obj.a.b
  175. Dep.target = null
  176. expect(watcher.deps.length).toBe(3) // obj.a + a.b + b
  177. obj.a.b = 3
  178. expect(watcher.update.calls.count()).toBe(1)
  179. // swap object
  180. obj.a = { b: 4 }
  181. expect(watcher.update.calls.count()).toBe(2)
  182. watcher.deps = []
  183. Dep.target = watcher
  184. obj.a.b
  185. Dep.target = null
  186. expect(watcher.deps.length).toBe(3)
  187. // set on the swapped object
  188. obj.a.b = 5
  189. expect(watcher.update.calls.count()).toBe(3)
  190. })
  191. it('observing object prop change on defined property', function () {
  192. var obj = { val: 2 }
  193. Object.defineProperty(obj, 'a', {
  194. configurable: true,
  195. enumerable: true,
  196. get: function () {
  197. return this.val
  198. },
  199. set: function (v) {
  200. this.val = v
  201. return this.val
  202. }
  203. })
  204. observe(obj)
  205. // mock a watcher!
  206. var watcher = {
  207. deps: [],
  208. addDep: function (dep) {
  209. this.deps.push(dep)
  210. dep.addSub(this)
  211. },
  212. update: jasmine.createSpy()
  213. }
  214. // collect dep
  215. Dep.target = watcher
  216. expect(obj.a).toBe(2) // Make sure 'this' is preserved
  217. Dep.target = null
  218. obj.a = 3
  219. expect(obj.val).toBe(3) // make sure 'setter' was called
  220. obj.val = 5
  221. expect(obj.a).toBe(5) // make sure 'getter' was called
  222. })
  223. it('observing set/delete', function () {
  224. var obj = { a: 1 }
  225. var ob = observe(obj)
  226. var dep = ob.dep
  227. spyOn(dep, 'notify')
  228. _.set(obj, 'b', 2)
  229. expect(obj.b).toBe(2)
  230. expect(dep.notify.calls.count()).toBe(1)
  231. _.del(obj, 'a')
  232. expect(_.hasOwn(obj, 'a')).toBe(false)
  233. expect(dep.notify.calls.count()).toBe(2)
  234. // set existing key, should be a plain set and not
  235. // trigger own ob's notify
  236. _.set(obj, 'b', 3)
  237. expect(obj.b).toBe(3)
  238. expect(dep.notify.calls.count()).toBe(2)
  239. // set non-existing key
  240. _.set(obj, 'c', 1)
  241. expect(obj.c).toBe(1)
  242. expect(dep.notify.calls.count()).toBe(3)
  243. // should ignore deleting non-existing key
  244. _.del(obj, 'a')
  245. expect(dep.notify.calls.count()).toBe(3)
  246. // should work on non-observed objects
  247. var obj2 = { a: 1 }
  248. _.del(obj2, 'a')
  249. expect(_.hasOwn(obj2, 'a')).toBe(false)
  250. // should work on Object.create(null)
  251. var obj3 = Object.create(null)
  252. obj3.a = 1
  253. var ob3 = observe(obj3)
  254. var dep3 = ob3.dep
  255. spyOn(dep3, 'notify')
  256. _.set(obj3, 'b', 2)
  257. expect(obj3.b).toBe(2)
  258. expect(dep3.notify.calls.count()).toBe(1)
  259. _.del(obj3, 'a')
  260. expect(_.hasOwn(obj3, 'a')).toBe(false)
  261. expect(dep3.notify.calls.count()).toBe(2)
  262. })
  263. it('observing array mutation', function () {
  264. var arr = []
  265. var ob = observe(arr)
  266. var dep = ob.dep
  267. spyOn(dep, 'notify')
  268. var objs = [{}, {}, {}]
  269. arr.push(objs[0])
  270. arr.pop()
  271. arr.unshift(objs[1])
  272. arr.shift()
  273. arr.splice(0, 0, objs[2])
  274. arr.sort()
  275. arr.reverse()
  276. expect(dep.notify.calls.count()).toBe(7)
  277. // inserted elements should be observed
  278. objs.forEach(function (obj) {
  279. expect(obj.__ob__ instanceof Observer).toBe(true)
  280. })
  281. })
  282. it('array $set', function () {
  283. var arr = [1]
  284. var ob = observe(arr)
  285. var dep = ob.dep
  286. spyOn(dep, 'notify')
  287. arr.$set(0, 2)
  288. expect(arr[0]).toBe(2)
  289. expect(dep.notify.calls.count()).toBe(1)
  290. // setting out of bound index
  291. arr.$set(2, 3)
  292. expect(arr[2]).toBe(3)
  293. expect(dep.notify.calls.count()).toBe(2)
  294. })
  295. it('array $remove', function () {
  296. var arr = [{}, {}]
  297. var obj1 = arr[0]
  298. var obj2 = arr[1]
  299. var ob = observe(arr)
  300. var dep = ob.dep
  301. spyOn(dep, 'notify')
  302. // remove by identity, not in array
  303. arr.$remove(obj1)
  304. expect(arr.length).toBe(1)
  305. expect(arr[0]).toBe(obj2)
  306. expect(dep.notify.calls.count()).toBe(1)
  307. // remove by identity, in array
  308. arr.$remove(obj2)
  309. expect(arr.length).toBe(0)
  310. expect(dep.notify.calls.count()).toBe(2)
  311. })
  312. it('no proto', function () {
  313. _.hasProto = false
  314. var arr = [1, 2, 3]
  315. var ob2 = observe(arr)
  316. expect(arr.$set).toBeTruthy()
  317. expect(arr.$remove).toBeTruthy()
  318. expect(arr.push).not.toBe([].push)
  319. var dep2 = ob2.dep
  320. spyOn(dep2, 'notify')
  321. arr.push(1)
  322. expect(dep2.notify).toHaveBeenCalled()
  323. _.hasProto = true
  324. })
  325. })