observer_spec.js 9.0 KB

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