viewmodel.js 16 KB

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