viewmodel.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. describe('UNIT: ViewModel', function () {
  2. mock('vm-test', '{{a.b.c}}')
  3. var data = {
  4. b: {
  5. c: 12345
  6. }
  7. },
  8. arr = [1, 2, 3],
  9. vm = new Vue({
  10. el: '#vm-test',
  11. data: {
  12. a: data,
  13. b: arr
  14. }
  15. })
  16. describe('.$set()', function () {
  17. vm.$set('a.b.c', 54321)
  18. it('should set correct value', function () {
  19. assert.strictEqual(data.b.c, 54321)
  20. })
  21. })
  22. describe('.$watch()', function () {
  23. it('should trigger callback when a plain value changes', function () {
  24. var val
  25. vm.$watch('a.b.c', function (newVal) {
  26. val = newVal
  27. })
  28. data.b.c = 'new value!'
  29. assert.strictEqual(val, data.b.c)
  30. })
  31. it('should trigger callback when an object value changes', function () {
  32. var val, subVal, rootVal,
  33. target = { c: 'hohoho' }
  34. vm.$watch('a.b', function (newVal) {
  35. val = newVal
  36. })
  37. vm.$watch('a.b.c', function (newVal) {
  38. subVal = newVal
  39. })
  40. vm.$watch('a', function (newVal) {
  41. rootVal = newVal
  42. })
  43. data.b = target
  44. assert.strictEqual(val, target)
  45. assert.strictEqual(subVal, target.c)
  46. vm.a = 'hehehe'
  47. assert.strictEqual(rootVal, 'hehehe')
  48. })
  49. it('should trigger callback when an array mutates', function () {
  50. var val, mut
  51. vm.$watch('b', function (array, mutation) {
  52. val = array
  53. mut = mutation
  54. })
  55. arr.push(4)
  56. assert.strictEqual(val, arr)
  57. assert.strictEqual(mut.method, 'push')
  58. assert.strictEqual(mut.args.length, 1)
  59. assert.strictEqual(mut.args[0], 4)
  60. })
  61. })
  62. describe('.$unwatch()', function () {
  63. it('should unwatch the stuff', function () {
  64. var triggered = false
  65. vm.$watch('a.b.c', function () {
  66. triggered = true
  67. })
  68. vm.$watch('a', function () {
  69. triggered = true
  70. })
  71. vm.$watch('b', function () {
  72. triggered = true
  73. })
  74. vm.$unwatch('a')
  75. vm.$unwatch('b')
  76. vm.$unwatch('a.b.c')
  77. vm.a = { b: { c:123123 }}
  78. vm.b.push(5)
  79. assert.notOk(triggered)
  80. })
  81. })
  82. describe('.$on', function () {
  83. it('should register listener on vm\'s compiler\'s emitter', function () {
  84. var t = new Vue(),
  85. triggered = false,
  86. msg = 'on test'
  87. t.$on('test', function (m) {
  88. assert.strictEqual(m, msg)
  89. triggered = true
  90. })
  91. t.$compiler.emitter.emit('test', msg)
  92. assert.ok(triggered)
  93. })
  94. })
  95. describe('.$once', function () {
  96. it('should invoke the listener only once', function () {
  97. var t = new Vue(),
  98. triggered = 0,
  99. msg = 'on once'
  100. t.$once('test', function (m) {
  101. assert.strictEqual(m, msg)
  102. triggered++
  103. })
  104. t.$compiler.emitter.emit('test', msg)
  105. t.$compiler.emitter.emit('test', msg)
  106. assert.strictEqual(triggered, 1)
  107. })
  108. })
  109. describe('$off', function () {
  110. it('should turn off the listener', function () {
  111. var t = new Vue(),
  112. triggered1 = false,
  113. triggered2 = false,
  114. f1 = function () {
  115. triggered1 = true
  116. },
  117. f2 = function () {
  118. triggered2 = true
  119. }
  120. t.$on('test', f1)
  121. t.$on('test', f2)
  122. t.$off('test', f1)
  123. t.$compiler.emitter.emit('test')
  124. assert.notOk(triggered1)
  125. assert.ok(triggered2)
  126. })
  127. })
  128. describe('.$broadcast()', function () {
  129. it('should notify all child VMs', function () {
  130. var triggered = 0,
  131. msg = 'broadcast test'
  132. var Child = Vue.extend({
  133. ready: function () {
  134. this.$on('hello', function (m) {
  135. assert.strictEqual(m, msg)
  136. triggered++
  137. })
  138. }
  139. })
  140. var Test = Vue.extend({
  141. template: '<div v-component="test"></div><div v-component="test"></div>',
  142. components: {
  143. test: Child
  144. }
  145. })
  146. var t = new Test()
  147. t.$broadcast('hello', msg)
  148. assert.strictEqual(triggered, 2)
  149. })
  150. })
  151. describe('.$emit', function () {
  152. it('should notify all ancestor VMs', function (done) {
  153. var topTriggered = false,
  154. midTriggered = false,
  155. msg = 'emit test'
  156. var Bottom = Vue.extend({
  157. ready: function () {
  158. var self = this
  159. setTimeout(function () {
  160. self.$emit('hello', msg)
  161. assert.ok(topTriggered)
  162. assert.ok(midTriggered)
  163. done()
  164. }, 0)
  165. }
  166. })
  167. var Middle = Vue.extend({
  168. template: '<div v-component="bottom"></div>',
  169. components: { bottom: Bottom },
  170. ready: function () {
  171. this.$on('hello', function (m) {
  172. assert.strictEqual(m, msg)
  173. midTriggered = true
  174. })
  175. }
  176. })
  177. var Top = Vue.extend({
  178. template: '<div v-component="middle"></div>',
  179. components: { middle: Middle },
  180. ready: function () {
  181. this.$on('hello', function (m) {
  182. assert.strictEqual(m, msg)
  183. topTriggered = true
  184. })
  185. }
  186. })
  187. new Top()
  188. })
  189. })
  190. describe('DOM methods', function () {
  191. var enterCalled,
  192. leaveCalled,
  193. callbackCalled,
  194. nextTick = require('vue/src/utils').nextTick
  195. var v = new Vue({
  196. attributes: {
  197. 'v-transition': 'test'
  198. },
  199. transitions: {
  200. test: {
  201. enter: function (el, change) {
  202. enterCalled = true
  203. change()
  204. },
  205. leave: function (el, change) {
  206. leaveCalled = true
  207. change()
  208. }
  209. }
  210. }
  211. })
  212. function reset () {
  213. enterCalled = false
  214. leaveCalled = false
  215. callbackCalled = false
  216. }
  217. function cb () {
  218. callbackCalled = true
  219. }
  220. it('$appendTo', function (done) {
  221. reset()
  222. var parent = document.createElement('div')
  223. v.$appendTo(parent, cb)
  224. assert.strictEqual(v.$el.parentNode, parent)
  225. assert.ok(enterCalled)
  226. nextTick(function () {
  227. assert.ok(callbackCalled)
  228. done()
  229. })
  230. })
  231. it('$before', function (done) {
  232. reset()
  233. var parent = document.createElement('div'),
  234. ref = document.createElement('div')
  235. parent.appendChild(ref)
  236. v.$before(ref, cb)
  237. assert.strictEqual(v.$el.parentNode, parent)
  238. assert.strictEqual(v.$el.nextSibling, ref)
  239. assert.ok(enterCalled)
  240. nextTick(function () {
  241. assert.ok(callbackCalled)
  242. done()
  243. })
  244. })
  245. it('$after', function (done) {
  246. reset()
  247. var parent = document.createElement('div'),
  248. ref1 = document.createElement('div'),
  249. ref2 = document.createElement('div')
  250. parent.appendChild(ref1)
  251. parent.appendChild(ref2)
  252. v.$after(ref1, cb)
  253. assert.strictEqual(v.$el.parentNode, parent)
  254. assert.strictEqual(v.$el.nextSibling, ref2)
  255. assert.strictEqual(ref1.nextSibling, v.$el)
  256. assert.ok(enterCalled)
  257. nextTick(function () {
  258. assert.ok(callbackCalled)
  259. next()
  260. })
  261. function next () {
  262. reset()
  263. v.$after(ref2, cb)
  264. assert.strictEqual(v.$el.parentNode, parent)
  265. assert.notOk(v.$el.nextSibling)
  266. assert.strictEqual(ref2.nextSibling, v.$el)
  267. assert.ok(enterCalled)
  268. nextTick(function () {
  269. assert.ok(callbackCalled)
  270. done()
  271. })
  272. }
  273. })
  274. it('$remove', function (done) {
  275. reset()
  276. var parent = document.createElement('div')
  277. v.$appendTo(parent)
  278. v.$remove(cb)
  279. assert.notOk(v.$el.parentNode)
  280. assert.ok(enterCalled)
  281. assert.ok(leaveCalled)
  282. nextTick(function () {
  283. assert.ok(callbackCalled)
  284. done()
  285. })
  286. })
  287. })
  288. describe('.$destroy', function () {
  289. // since this simply delegates to Compiler.prototype.destroy(),
  290. // that's what we are actually testing here.
  291. var destroy = require('vue/src/compiler').prototype.destroy
  292. var beforeDestroyCalled = false,
  293. afterDestroyCalled = false,
  294. observerOffCalled = false,
  295. emitterOffCalled = false,
  296. dirUnbindCalled = false,
  297. expUnbindCalled = false,
  298. bindingUnbindCalled = false,
  299. unobserveCalled = 0,
  300. elRemoved = false,
  301. externalBindingUnbindCalled = false
  302. var dirMock = {
  303. binding: {
  304. compiler: null,
  305. instances: []
  306. },
  307. unbind: function () {
  308. dirUnbindCalled = true
  309. }
  310. }
  311. dirMock.binding.instances.push(dirMock)
  312. var bindingsMock = Object.create({
  313. 'test2': {
  314. unbind: function () {
  315. externalBindingUnbindCalled = true
  316. }
  317. }
  318. })
  319. bindingsMock.test = {
  320. root: true,
  321. key: 'test',
  322. value: {
  323. __observer__: {
  324. off: function () {
  325. unobserveCalled++
  326. return this
  327. }
  328. }
  329. },
  330. unbind: function () {
  331. bindingUnbindCalled = true
  332. }
  333. }
  334. var compilerMock = {
  335. options: {
  336. beforeDestroy: function () {
  337. beforeDestroyCalled = true
  338. },
  339. afterDestroy: function () {
  340. afterDestroyCalled = true
  341. }
  342. },
  343. observer: {
  344. off: function () {
  345. observerOffCalled = true
  346. },
  347. proxies: {
  348. 'test.': {}
  349. }
  350. },
  351. emitter: {
  352. off: function () {
  353. emitterOffCalled = true
  354. }
  355. },
  356. dirs: [dirMock],
  357. exps: [{
  358. unbind: function () {
  359. expUnbindCalled = true
  360. }
  361. }],
  362. bindings: bindingsMock,
  363. childId: 'test',
  364. parentCompiler: {
  365. childCompilers: [],
  366. vm: {
  367. $: {
  368. 'test': true
  369. }
  370. }
  371. },
  372. vm: {
  373. $remove: function () {
  374. elRemoved = true
  375. }
  376. },
  377. execHook: function (id) {
  378. this.options[id].call(this)
  379. }
  380. }
  381. compilerMock.parentCompiler.childCompilers.push(compilerMock)
  382. destroy.call(compilerMock)
  383. it('should call the pre and post destroy hooks', function () {
  384. assert.ok(beforeDestroyCalled)
  385. assert.ok(afterDestroyCalled)
  386. })
  387. it('should turn observer and emitter off', function () {
  388. assert.ok(observerOffCalled)
  389. assert.ok(emitterOffCalled)
  390. })
  391. it('should unbind all directives', function () {
  392. assert.ok(dirUnbindCalled)
  393. })
  394. it('should remove directives from external bindings', function () {
  395. assert.strictEqual(dirMock.binding.instances.indexOf(dirMock), -1)
  396. })
  397. it('should unbind all expressions', function () {
  398. assert.ok(expUnbindCalled)
  399. })
  400. it('should unbind and unobserve own bindings', function () {
  401. assert.ok(bindingUnbindCalled)
  402. assert.strictEqual(unobserveCalled, 3)
  403. })
  404. it('should not unbind external bindings', function () {
  405. assert.notOk(externalBindingUnbindCalled)
  406. })
  407. it('should remove self from parentCompiler', function () {
  408. var parent = compilerMock.parentCompiler
  409. assert.ok(parent.childCompilers.indexOf(compilerMock), -1)
  410. assert.strictEqual(parent.vm.$[compilerMock.childId], undefined)
  411. })
  412. it('should remove the dom element', function () {
  413. assert.ok(elRemoved)
  414. })
  415. })
  416. })