viewmodel.js 16 KB

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