observer_spec.js 9.9 KB

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