viewmodel.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  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. })
  77. describe('.$unwatch()', function () {
  78. it('should unwatch the stuff', function (done) {
  79. var triggered = false
  80. vm.$watch('a.b.c', function () {
  81. triggered = true
  82. })
  83. vm.$watch('a', function () {
  84. triggered = true
  85. })
  86. vm.$watch('b', function () {
  87. triggered = true
  88. })
  89. vm.$unwatch('a')
  90. vm.$unwatch('b')
  91. vm.$unwatch('a.b.c')
  92. vm.a = { b: { c:123123 }}
  93. vm.b.push(5)
  94. nextTick(function () {
  95. assert.notOk(triggered)
  96. done()
  97. })
  98. })
  99. })
  100. describe('.$on', function () {
  101. it('should register listener on vm\'s compiler\'s emitter', function () {
  102. var t = new Vue(),
  103. triggered = false,
  104. msg = 'on test'
  105. t.$on('test', function (m) {
  106. assert.strictEqual(m, msg)
  107. triggered = true
  108. })
  109. t.$compiler.emitter.emit('test', msg)
  110. assert.ok(triggered)
  111. })
  112. })
  113. describe('.$once', function () {
  114. it('should invoke the listener only once', function () {
  115. var t = new Vue(),
  116. triggered = 0,
  117. msg = 'on once'
  118. t.$once('test', function (m) {
  119. assert.strictEqual(m, msg)
  120. triggered++
  121. })
  122. t.$compiler.emitter.emit('test', msg)
  123. t.$compiler.emitter.emit('test', msg)
  124. assert.strictEqual(triggered, 1)
  125. })
  126. })
  127. describe('$off', function () {
  128. it('should turn off the listener', function () {
  129. var t = new Vue(),
  130. triggered1 = false,
  131. triggered2 = false,
  132. f1 = function () {
  133. triggered1 = true
  134. },
  135. f2 = function () {
  136. triggered2 = true
  137. }
  138. t.$on('test', f1)
  139. t.$on('test', f2)
  140. t.$off('test', f1)
  141. t.$compiler.emitter.emit('test')
  142. assert.notOk(triggered1)
  143. assert.ok(triggered2)
  144. })
  145. })
  146. describe('$emit', function () {
  147. it('should trigger the event', function () {
  148. var t = new Vue(),
  149. triggered = false
  150. t.$compiler.emitter.on('test', function (m) {
  151. triggered = m
  152. })
  153. t.$emit('test', 'hi')
  154. assert.strictEqual(triggered, 'hi')
  155. })
  156. })
  157. describe('.$broadcast()', function () {
  158. it('should notify all child VMs', function () {
  159. var triggered = 0,
  160. msg = 'broadcast test'
  161. var Child = Vue.extend({
  162. ready: function () {
  163. this.$on('hello', function (m) {
  164. assert.strictEqual(m, msg)
  165. triggered++
  166. })
  167. }
  168. })
  169. var Test = Vue.extend({
  170. template: '<div v-component="test"></div><div v-component="test"></div>',
  171. components: {
  172. test: Child
  173. }
  174. })
  175. var t = new Test()
  176. t.$broadcast('hello', msg)
  177. assert.strictEqual(triggered, 2)
  178. })
  179. })
  180. describe('.$dispatch', function () {
  181. it('should notify all ancestor VMs', function (done) {
  182. var topTriggered = false,
  183. midTriggered = false,
  184. msg = 'emit test'
  185. var Bottom = Vue.extend({
  186. ready: function () {
  187. var self = this
  188. nextTick(function () {
  189. self.$dispatch('hello', msg)
  190. assert.ok(topTriggered)
  191. assert.ok(midTriggered)
  192. done()
  193. })
  194. }
  195. })
  196. var Middle = Vue.extend({
  197. template: '<div v-component="bottom"></div>',
  198. components: { bottom: Bottom },
  199. ready: function () {
  200. this.$on('hello', function (m) {
  201. assert.strictEqual(m, msg)
  202. midTriggered = true
  203. })
  204. }
  205. })
  206. var Top = Vue.extend({
  207. template: '<div v-component="middle"></div>',
  208. components: { middle: Middle },
  209. ready: function () {
  210. this.$on('hello', function (m) {
  211. assert.strictEqual(m, msg)
  212. topTriggered = true
  213. })
  214. }
  215. })
  216. new Top()
  217. })
  218. })
  219. describe('DOM methods', function () {
  220. var enterCalled,
  221. leaveCalled,
  222. callbackCalled
  223. var v = new Vue({
  224. attributes: {
  225. 'v-transition': 'test'
  226. },
  227. transitions: {
  228. test: {
  229. enter: function (el, change) {
  230. enterCalled = true
  231. change()
  232. },
  233. leave: function (el, change) {
  234. leaveCalled = true
  235. change()
  236. }
  237. }
  238. }
  239. })
  240. function reset () {
  241. enterCalled = false
  242. leaveCalled = false
  243. callbackCalled = false
  244. }
  245. function cb () {
  246. callbackCalled = true
  247. }
  248. it('$appendTo', function (done) {
  249. reset()
  250. var parent = document.createElement('div')
  251. v.$appendTo(parent, cb)
  252. assert.strictEqual(v.$el.parentNode, parent)
  253. assert.ok(enterCalled)
  254. nextTick(function () {
  255. assert.ok(callbackCalled)
  256. done()
  257. })
  258. })
  259. it('$before', function (done) {
  260. reset()
  261. var parent = document.createElement('div'),
  262. ref = document.createElement('div')
  263. parent.appendChild(ref)
  264. v.$before(ref, cb)
  265. assert.strictEqual(v.$el.parentNode, parent)
  266. assert.strictEqual(v.$el.nextSibling, ref)
  267. assert.ok(enterCalled)
  268. nextTick(function () {
  269. assert.ok(callbackCalled)
  270. done()
  271. })
  272. })
  273. it('$after', function (done) {
  274. reset()
  275. var parent = document.createElement('div'),
  276. ref1 = document.createElement('div'),
  277. ref2 = document.createElement('div')
  278. parent.appendChild(ref1)
  279. parent.appendChild(ref2)
  280. v.$after(ref1, cb)
  281. assert.strictEqual(v.$el.parentNode, parent)
  282. assert.strictEqual(v.$el.nextSibling, ref2)
  283. assert.strictEqual(ref1.nextSibling, v.$el)
  284. assert.ok(enterCalled)
  285. nextTick(function () {
  286. assert.ok(callbackCalled)
  287. next()
  288. })
  289. function next () {
  290. reset()
  291. v.$after(ref2, cb)
  292. assert.strictEqual(v.$el.parentNode, parent)
  293. assert.notOk(v.$el.nextSibling)
  294. assert.strictEqual(ref2.nextSibling, v.$el)
  295. assert.ok(enterCalled)
  296. nextTick(function () {
  297. assert.ok(callbackCalled)
  298. done()
  299. })
  300. }
  301. })
  302. it('$remove', function (done) {
  303. reset()
  304. var parent = document.createElement('div')
  305. v.$appendTo(parent)
  306. v.$remove(cb)
  307. assert.notOk(v.$el.parentNode)
  308. assert.ok(enterCalled)
  309. assert.ok(leaveCalled)
  310. nextTick(function () {
  311. assert.ok(callbackCalled)
  312. done()
  313. })
  314. })
  315. })
  316. describe('.$destroy', function () {
  317. // since this simply delegates to Compiler.prototype.destroy(),
  318. // that's what we are actually testing here.
  319. var destroy = require('vue/src/compiler').prototype.destroy
  320. var beforeDestroyCalled = false,
  321. afterDestroyCalled = false,
  322. observerOffCalled = false,
  323. emitterOffCalled = false,
  324. dirUnbindCalled = false,
  325. expUnbindCalled = false,
  326. bindingUnbindCalled = false,
  327. unobserveCalled = false,
  328. elRemoved = false
  329. var dirMock = {
  330. binding: {
  331. compiler: null,
  332. dirs: []
  333. },
  334. unbind: function () {
  335. dirUnbindCalled = true
  336. }
  337. }
  338. dirMock.binding.dirs.push(dirMock)
  339. var bindingsMock = {
  340. test: {
  341. root: true,
  342. key: 'test',
  343. unbind: function () {
  344. bindingUnbindCalled = true
  345. }
  346. }
  347. }
  348. var compilerMock = {
  349. options: {
  350. beforeDestroy: function () {
  351. beforeDestroyCalled = true
  352. },
  353. afterDestroy: function () {
  354. afterDestroyCalled = true
  355. }
  356. },
  357. data: {
  358. __emitter__: {
  359. off: function () {
  360. unobserveCalled = true
  361. return this
  362. }
  363. }
  364. },
  365. observer: {
  366. off: function () {
  367. observerOffCalled = true
  368. },
  369. proxies: {
  370. 'test.': {},
  371. '': {}
  372. }
  373. },
  374. emitter: {
  375. off: function () {
  376. emitterOffCalled = true
  377. }
  378. },
  379. dirs: [dirMock],
  380. exps: [{
  381. unbind: function () {
  382. expUnbindCalled = true
  383. }
  384. }],
  385. bindings: bindingsMock,
  386. childId: 'test',
  387. parentCompiler: {
  388. childCompilers: [],
  389. vm: {
  390. $: {
  391. 'test': true
  392. }
  393. }
  394. },
  395. vm: {
  396. $remove: function () {
  397. elRemoved = true
  398. }
  399. },
  400. execHook: function (id) {
  401. this.options[id].call(this)
  402. }
  403. }
  404. compilerMock.parentCompiler.childCompilers.push(compilerMock)
  405. destroy.call(compilerMock)
  406. it('should call the pre and post destroy hooks', function () {
  407. assert.ok(beforeDestroyCalled)
  408. assert.ok(afterDestroyCalled)
  409. })
  410. it('should turn observer and emitter off', function () {
  411. assert.ok(observerOffCalled)
  412. assert.ok(emitterOffCalled)
  413. })
  414. it('should unobserve the data', function () {
  415. assert.ok(unobserveCalled)
  416. })
  417. it('should unbind all directives', function () {
  418. assert.ok(dirUnbindCalled)
  419. })
  420. it('should remove directives from external bindings', function () {
  421. assert.strictEqual(dirMock.binding.dirs.indexOf(dirMock), -1)
  422. })
  423. it('should unbind all expressions', function () {
  424. assert.ok(expUnbindCalled)
  425. })
  426. it('should unbind and unobserve own bindings', function () {
  427. assert.ok(bindingUnbindCalled)
  428. })
  429. it('should remove self from parentCompiler', function () {
  430. var parent = compilerMock.parentCompiler
  431. assert.ok(parent.childCompilers.indexOf(compilerMock), -1)
  432. assert.strictEqual(parent.vm.$[compilerMock.childId], undefined)
  433. })
  434. it('should remove the dom element', function () {
  435. assert.ok(elRemoved)
  436. })
  437. })
  438. describe('$data', function () {
  439. it('should be the same data', function () {
  440. var data = {},
  441. vm = new Vue({data:data})
  442. assert.strictEqual(vm.$data, data)
  443. })
  444. it('should be able to be swapped', function (done) {
  445. var data1 = { a: 1 },
  446. data2 = { a: 2 },
  447. vm = new Vue({data: data1}),
  448. emittedChange = false
  449. vm.$watch('a', function (v) {
  450. assert.equal(v, 2)
  451. emittedChange = true
  452. })
  453. vm.$data = data2
  454. assert.equal(vm.a, 2)
  455. nextTick(function () {
  456. assert.ok(emittedChange)
  457. done()
  458. })
  459. })
  460. })
  461. })