viewmodel.js 16 KB

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