observer_spec.js 10 KB

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