directives.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011
  1. describe('Directives', function () {
  2. var nextTick = require('vue/src/utils').nextTick,
  3. VM = require('vue/src/viewmodel')
  4. describe('attr', function () {
  5. var dir = mockDirective('attr', 'input'),
  6. el = dir.el
  7. it('should set a truthy attribute value', function () {
  8. var value = 'Arrrrrr!'
  9. dir.arg = 'value'
  10. dir.update(value)
  11. assert.strictEqual(el.getAttribute('value'), value)
  12. })
  13. it('should set attribute value to `0`', function () {
  14. dir.arg = 'value'
  15. dir.update(0)
  16. assert.strictEqual(el.getAttribute('value'), '0')
  17. })
  18. it('should remove an attribute if value is `false`', function () {
  19. dir.arg = 'disabled'
  20. el.setAttribute('disabled', 'disabled')
  21. dir.update(false)
  22. assert.strictEqual(el.getAttribute('disabled'), null)
  23. })
  24. it('should remove an attribute if value is `null`', function () {
  25. dir.arg = 'disabled'
  26. el.setAttribute('disabled', 'disabled')
  27. dir.update(null)
  28. assert.strictEqual(el.getAttribute('disabled'), null)
  29. })
  30. it('should remove an attribute if value is `undefined`', function () {
  31. dir.arg = 'disabled'
  32. el.setAttribute('disabled', 'disabled')
  33. dir.update(undefined)
  34. assert.strictEqual(el.getAttribute('disabled'), null)
  35. })
  36. })
  37. describe('text', function () {
  38. var dir = mockDirective('text')
  39. dir.bind()
  40. it('should work with a string', function () {
  41. dir.update('hallo')
  42. assert.strictEqual(dir.el.textContent, 'hallo')
  43. })
  44. it('should work with a number', function () {
  45. dir.update(12345)
  46. assert.strictEqual(dir.el.textContent, '12345')
  47. })
  48. it('should work with booleans', function () {
  49. dir.update(true)
  50. assert.strictEqual(dir.el.textContent, 'true')
  51. })
  52. it('should work with objects', function () {
  53. dir.update({foo:"bar"})
  54. assert.strictEqual(dir.el.textContent, '{"foo":"bar"}')
  55. })
  56. it('should be empty with null & undefined', function () {
  57. dir.update(null)
  58. assert.strictEqual(dir.el.textContent, '')
  59. dir.update(undefined)
  60. assert.strictEqual(dir.el.textContent, '')
  61. })
  62. })
  63. describe('html', function () {
  64. var dir = mockDirective('html')
  65. it('should work with a string', function () {
  66. dir.update('hi!!!')
  67. assert.strictEqual(dir.el.innerHTML, 'hi!!!')
  68. dir.update('<span>haha</span><a>lol</a>')
  69. assert.strictEqual(dir.el.querySelector('span').textContent, 'haha')
  70. })
  71. it('should work with a number', function () {
  72. dir.update(12345)
  73. assert.strictEqual(dir.el.innerHTML, '12345')
  74. })
  75. it('should work with booleans', function () {
  76. dir.update(true)
  77. assert.strictEqual(dir.el.textContent, 'true')
  78. })
  79. it('should work with objects', function () {
  80. dir.update({foo:"bar"})
  81. assert.strictEqual(dir.el.textContent, '{"foo":"bar"}')
  82. })
  83. it('should be empty with with null & undefined', function () {
  84. dir.update(null)
  85. assert.strictEqual(dir.el.innerHTML, '')
  86. dir.update(undefined)
  87. assert.strictEqual(dir.el.innerHTML, '')
  88. })
  89. it('should swap html if el is a comment placeholder', function () {
  90. var dir = mockDirective('html'),
  91. comment = document.createComment('hi'),
  92. parent = dir.el
  93. parent.innerHTML = 'what!'
  94. parent.appendChild(comment)
  95. dir.el = comment
  96. dir.bind()
  97. assert.ok(dir.nodes)
  98. var pre = 'what!',
  99. after = '<!--hi-->',
  100. h1 = '<span>hello</span><span>world</span>',
  101. h2 = '<a>whats</a><a>up</a>'
  102. dir.update(h1)
  103. assert.strictEqual(parent.innerHTML, pre + h1 + after)
  104. dir.update(h2)
  105. assert.strictEqual(parent.innerHTML, pre + h2 + after)
  106. })
  107. })
  108. describe('show', function () {
  109. var dir = mockDirective('show')
  110. it('should be default value when value is truthy', function () {
  111. dir.update(1)
  112. assert.strictEqual(dir.el.style.display, '')
  113. dir.update('hi!')
  114. assert.strictEqual(dir.el.style.display, '')
  115. dir.update(true)
  116. assert.strictEqual(dir.el.style.display, '')
  117. dir.update({})
  118. assert.strictEqual(dir.el.style.display, '')
  119. dir.update(function () {})
  120. assert.strictEqual(dir.el.style.display, '')
  121. })
  122. it('should be none when value is falsy', function () {
  123. dir.update(0)
  124. assert.strictEqual(dir.el.style.display, 'none')
  125. dir.update('')
  126. assert.strictEqual(dir.el.style.display, 'none')
  127. dir.update(false)
  128. assert.strictEqual(dir.el.style.display, 'none')
  129. dir.update(null)
  130. assert.strictEqual(dir.el.style.display, 'none')
  131. dir.update(undefined)
  132. assert.strictEqual(dir.el.style.display, 'none')
  133. })
  134. })
  135. describe('class', function () {
  136. function contains (el, cls) {
  137. var cur = ' ' + el.className + ' '
  138. return cur.indexOf(' ' + cls + ' ') > -1
  139. }
  140. it('should set class to the value if it has no arg', function () {
  141. var dir = mockDirective('class')
  142. dir.update('test')
  143. assert.ok(contains(dir.el, 'test'))
  144. dir.update('hoho')
  145. assert.ok(!contains(dir.el, 'test'))
  146. assert.ok(contains(dir.el, 'hoho'))
  147. })
  148. it('should add/remove class based on truthy/falsy if it has an arg', function () {
  149. var dir = mockDirective('class')
  150. dir.arg = 'test'
  151. dir.update(true)
  152. assert.ok(contains(dir.el, 'test'))
  153. dir.update(false)
  154. assert.ok(!contains(dir.el, 'test'))
  155. })
  156. })
  157. describe('model', function () {
  158. describe('input[checkbox]', function () {
  159. var dir = mockDirective('model', 'input', 'checkbox')
  160. dir.bind()
  161. before(function () {
  162. document.body.appendChild(dir.el)
  163. })
  164. it('should set checked on update()', function () {
  165. dir.update(true)
  166. assert.ok(dir.el.checked)
  167. dir.update(false)
  168. assert.ok(!dir.el.checked)
  169. })
  170. it('should trigger vm.$set when clicked', function () {
  171. var triggered = false
  172. dir.key = 'foo'
  173. dir.ownerVM = { $set: function (key, val) {
  174. assert.strictEqual(key, 'foo')
  175. assert.strictEqual(val, true)
  176. triggered = true
  177. }}
  178. dir.el.dispatchEvent(mockMouseEvent('click'))
  179. assert.ok(triggered)
  180. })
  181. it('should remove event listener with unbind()', function () {
  182. var removed = true
  183. dir.ownerVM = {
  184. $set: function () {
  185. removed = false
  186. }
  187. }
  188. dir.unbind()
  189. dir.el.dispatchEvent(mockMouseEvent('click'))
  190. assert.ok(removed)
  191. })
  192. after(function () {
  193. document.body.removeChild(dir.el)
  194. })
  195. })
  196. describe('input[radio]', function () {
  197. var dir1 = mockDirective('model', 'input', 'radio'),
  198. dir2 = mockDirective('model', 'input', 'radio')
  199. dir1.el.name = 'input-radio'
  200. dir2.el.name = 'input-radio'
  201. dir1.el.value = '12345'
  202. dir2.el.value = '54321'
  203. dir1.bind()
  204. dir2.bind()
  205. before(function () {
  206. document.body.appendChild(dir1.el)
  207. document.body.appendChild(dir2.el)
  208. })
  209. it('should set el.checked on update()', function () {
  210. assert.notOk(dir1.el.checked)
  211. dir1.update(12345)
  212. assert.ok(dir1.el.checked)
  213. })
  214. it('should trigger vm.$set when clicked', function () {
  215. var triggered = false
  216. dir2.key = 'radio'
  217. dir2.ownerVM = { $set: function (key, val) {
  218. triggered = true
  219. assert.strictEqual(key, 'radio')
  220. assert.strictEqual(val, dir2.el.value)
  221. }}
  222. dir2.el.dispatchEvent(mockMouseEvent('click'))
  223. assert.ok(triggered)
  224. assert.ok(dir2.el.checked)
  225. assert.notOk(dir1.el.checked)
  226. })
  227. it('should remove listeners on unbind()', function () {
  228. var removed = true
  229. dir1.ownerVM = { $set: function () {
  230. removed = false
  231. }}
  232. dir1.unbind()
  233. dir1.el.dispatchEvent(mockMouseEvent('click'))
  234. assert.ok(removed)
  235. })
  236. after(function () {
  237. document.body.removeChild(dir1.el)
  238. document.body.removeChild(dir2.el)
  239. })
  240. })
  241. describe('select', function () {
  242. var dir = mockDirective('model', 'select'),
  243. o1 = document.createElement('option'),
  244. o2 = document.createElement('option')
  245. o1.value = 0
  246. o2.value = 1
  247. dir.el.appendChild(o1)
  248. dir.el.appendChild(o2)
  249. dir.bind()
  250. before(function () {
  251. document.body.appendChild(dir.el)
  252. })
  253. it('should set value on update()', function () {
  254. dir.update(0)
  255. assert.strictEqual(dir.el.value, '0')
  256. })
  257. it('should trigger vm.$set when value is changed', function () {
  258. var triggered = false
  259. dir.key = 'select'
  260. dir.ownerVM = { $set: function (key, val) {
  261. triggered = true
  262. assert.strictEqual(key, 'select')
  263. assert.equal(val, 1)
  264. }}
  265. dir.el.options.selectedIndex = 1
  266. dir.el.dispatchEvent(mockHTMLEvent('change'))
  267. assert.ok(triggered)
  268. })
  269. it('should remove listener on unbind()', function () {
  270. var removed = true
  271. dir.ownerVM = { $set: function () {
  272. removed = false
  273. }}
  274. dir.unbind()
  275. dir.el.dispatchEvent(mockHTMLEvent('change'))
  276. assert.ok(removed)
  277. })
  278. after(function () {
  279. document.body.removeChild(dir.el)
  280. })
  281. })
  282. describe('input[text] and others', function () {
  283. var dir = mockDirective('model', 'input', 'text')
  284. dir.bind()
  285. before(function () {
  286. document.body.appendChild(dir.el)
  287. })
  288. it('should set the value on update()', function () {
  289. dir.update('foobar')
  290. assert.strictEqual(dir.el.value, 'foobar')
  291. })
  292. // `lazy` option is tested in the API suite
  293. it('should trigger vm.$set when value is changed via input', function () {
  294. var triggered = false
  295. dir.key = 'foo'
  296. dir.ownerVM = { $set: function (key, val) {
  297. assert.ok(dir.lock, 'the directive should be locked if it has no filters')
  298. assert.strictEqual(key, 'foo')
  299. assert.strictEqual(val, 'bar')
  300. triggered = true
  301. }}
  302. dir.el.value = 'bar'
  303. dir.el.dispatchEvent(mockHTMLEvent('input'))
  304. assert.ok(triggered)
  305. })
  306. it('should remove event listener with unbind()', function () {
  307. var removed = true
  308. dir.ownerVM = {
  309. $set: function () {
  310. removed = false
  311. }
  312. }
  313. dir.unbind()
  314. dir.el.dispatchEvent(mockHTMLEvent('input'))
  315. assert.ok(removed)
  316. })
  317. it('should not lock during vm.$set if it has filters', function (done) {
  318. var triggered = false
  319. var dir = mockDirective('model', 'input', 'text')
  320. dir.filters = []
  321. dir.bind()
  322. dir.ownerVM = {$set:function () {
  323. assert.notOk(dir.lock)
  324. triggered = true
  325. }}
  326. dir.el.value = 'foo'
  327. document.body.appendChild(dir.el)
  328. dir.el.dispatchEvent(mockHTMLEvent('input'))
  329. // timeout becuase the update is async
  330. setTimeout(function () {
  331. assert.ok(triggered)
  332. document.body.removeChild(dir.el)
  333. done()
  334. }, 1)
  335. })
  336. after(function () {
  337. document.body.removeChild(dir.el)
  338. })
  339. })
  340. })
  341. describe('on', function () {
  342. var dir = mockDirective('on')
  343. dir.arg = 'click'
  344. dir.bind()
  345. before(function () {
  346. document.body.appendChild(dir.el)
  347. })
  348. it('should set the handler to be triggered by arg through update()', function () {
  349. var triggered = false
  350. dir.update(function () {
  351. triggered = true
  352. })
  353. dir.el.dispatchEvent(mockMouseEvent('click'))
  354. assert.ok(triggered)
  355. })
  356. it('should remove previous handler when update() a new handler', function () {
  357. var triggered1 = false,
  358. triggered2 = false
  359. dir.update(function () {
  360. triggered1 = true
  361. })
  362. dir.update(function () {
  363. triggered2 = true
  364. })
  365. dir.el.dispatchEvent(mockMouseEvent('click'))
  366. assert.notOk(triggered1)
  367. assert.ok(triggered2)
  368. })
  369. it('should wrap the handler to supply expected args', function () {
  370. var vm = dir.binding.compiler.vm, // owner VM
  371. e = mockMouseEvent('click'), // original event
  372. triggered = false
  373. dir.update(function (ev) {
  374. assert.strictEqual(this, vm, 'handler should be called on owner VM')
  375. assert.strictEqual(ev, e, 'event should be passed in')
  376. assert.strictEqual(ev.targetVM, dir.vm)
  377. triggered = true
  378. })
  379. dir.el.dispatchEvent(e)
  380. assert.ok(triggered)
  381. })
  382. it('should remove the handler in unbind()', function () {
  383. var triggered = false
  384. dir.update(function () {
  385. triggered = true
  386. })
  387. dir.unbind()
  388. dir.el.dispatchEvent(mockMouseEvent('click'))
  389. assert.notOk(triggered)
  390. })
  391. after(function () {
  392. document.body.removeChild(dir.el)
  393. })
  394. })
  395. describe('pre', function () {
  396. it('should skip compilation', function () {
  397. var testId = 'pre-test'
  398. mock(testId, '<span v-pre><strong>{{lol}}</strong><a v-text="hi"></a></span>')
  399. var t = new Vue({
  400. el: '#' + testId,
  401. data: {
  402. lol: 'heyhey',
  403. hi: 'hohoho'
  404. }
  405. })
  406. assert.strictEqual(t.$el.querySelector('strong').textContent, '{{lol}}')
  407. assert.strictEqual(t.$el.querySelector('a').textContent, '')
  408. assert.ok(t.$el.querySelector('a').hasAttribute('v-text'))
  409. })
  410. })
  411. describe('component', function () {
  412. it('should create a child viewmodel with given constructor', function () {
  413. var testId = 'component-test'
  414. mock(testId, '<div v-component="' + testId + '"></div>')
  415. var t = new Vue({
  416. el: '#' + testId,
  417. data: {
  418. msg: '123'
  419. },
  420. components: {
  421. 'component-test': {
  422. template: '<span>{{msg}}</span>'
  423. }
  424. }
  425. })
  426. assert.strictEqual(t.$el.querySelector('span').textContent, '123')
  427. })
  428. })
  429. describe('with', function () {
  430. it('should create a child viewmodel with given data', function () {
  431. var testId = 'with-test'
  432. mock(testId, '<span v-component v-with="test">{{msg}}</span>')
  433. var t = new Vue({
  434. el: '#' + testId,
  435. data: {
  436. test: {
  437. msg: testId
  438. }
  439. }
  440. })
  441. assert.strictEqual(t.$el.querySelector('span').textContent, testId)
  442. })
  443. it('should accept args and sync parent and child', function (done) {
  444. var t = new Vue({
  445. template:
  446. '<span>{{test.msg}} {{n}}</span>'
  447. + '<p v-component v-with="childMsg:test.msg, n:n" v-ref="child">{{childMsg}} {{n}}</p>',
  448. data: {
  449. n: 1,
  450. test: {
  451. msg: 'haha!'
  452. }
  453. }
  454. })
  455. nextTick(function () {
  456. assert.strictEqual(t.$el.querySelector('span').textContent, 'haha! 1')
  457. assert.strictEqual(t.$el.querySelector('p').textContent, 'haha! 1')
  458. testParentToChild()
  459. })
  460. function testParentToChild () {
  461. // test sync from parent to child
  462. t.test = { msg: 'hehe!' }
  463. nextTick(function () {
  464. assert.strictEqual(t.$el.querySelector('p').textContent, 'hehe! 1')
  465. testChildToParent()
  466. })
  467. }
  468. function testChildToParent () {
  469. // test sync back
  470. t.$.child.childMsg = 'hoho!'
  471. t.$.child.n = 2
  472. assert.strictEqual(t.test.msg, 'hoho!')
  473. assert.strictEqual(t.n, 2)
  474. nextTick(function () {
  475. assert.strictEqual(t.$el.querySelector('span').textContent, 'hoho! 2')
  476. assert.strictEqual(t.$el.querySelector('p').textContent, 'hoho! 2')
  477. done()
  478. })
  479. }
  480. })
  481. })
  482. describe('ref', function () {
  483. var t
  484. it('should register a VM isntance on its parent\'s $', function () {
  485. var called = false
  486. var Child = Vue.extend({
  487. methods: {
  488. test: function () {
  489. called = true
  490. }
  491. }
  492. })
  493. t = new Vue({
  494. template: '<div v-component="child" v-ref="hihi"></div>',
  495. components: {
  496. child: Child
  497. }
  498. })
  499. assert.ok(t.$.hihi instanceof Child)
  500. t.$.hihi.test()
  501. assert.ok(called)
  502. })
  503. it('should remove the reference if child is destroyed', function () {
  504. t.$.hihi.$destroy()
  505. assert.notOk('hihi' in t.$)
  506. })
  507. it('should register an Array of VMs with v-repeat', function () {
  508. t = new Vue({
  509. template: '<p v-repeat="list" v-ref="list"></p>',
  510. data: { list: [{a:1}, {a:2}, {a:3}] }
  511. })
  512. assert.equal(t.$.list.length, 3)
  513. assert.ok(t.$.list[0] instanceof Vue)
  514. assert.equal(t.$.list[1].a, 2)
  515. })
  516. it('should work with interpolation', function () {
  517. t = new Vue({
  518. template: '<div v-component v-with="obj" v-ref="{{ok ? \'a\' : \'b\'}}"></div>',
  519. data: { obj: { a: 123 } }
  520. })
  521. assert.equal(t.$.b.a, 123)
  522. })
  523. })
  524. describe('partial', function () {
  525. it('should replace the node\'s content', function () {
  526. var t = new Vue({
  527. template: '<div v-partial="test"></div>',
  528. partials: {
  529. test: '<a>ahahaha!</a>'
  530. }
  531. })
  532. assert.strictEqual(t.$el.innerHTML, '<div><a>ahahaha!</a></div>')
  533. })
  534. it('should work with interpolation', function () {
  535. var t = new Vue({
  536. template: '<div v-partial="{{ ready ? \'a\' : \'b\'}}"></div>',
  537. partials: {
  538. a: 'A',
  539. b: 'B'
  540. },
  541. data: {
  542. ready: true
  543. }
  544. })
  545. assert.strictEqual(t.$el.innerHTML, '<div>A</div>')
  546. })
  547. })
  548. describe('style', function () {
  549. it('should apply a normal style', function () {
  550. var d = mockDirective('style')
  551. d.arg = 'text-align'
  552. d.bind()
  553. assert.strictEqual(d.prop, 'text-align')
  554. d.update('center')
  555. assert.strictEqual(d.el.style.textAlign, 'center')
  556. })
  557. it('should apply style with !important priority', function () {
  558. var d = mockDirective('style')
  559. d.arg = 'font-size'
  560. d.bind()
  561. d.update('100px !important')
  562. assert.strictEqual(d.el.style.getPropertyPriority('font-size'), 'important')
  563. })
  564. it('should auto prefix styles', function () {
  565. var d = mockDirective('style')
  566. d.arg = '$transform'
  567. d.bind()
  568. assert.ok(d.prefixed)
  569. assert.strictEqual(d.prop, 'transform')
  570. // mock the el's CSSStyleDeclaration object
  571. // so we can test setProperty calls
  572. var results = []
  573. d.el = {
  574. style: {
  575. setProperty: function (prop, value) {
  576. results.push({
  577. prop: prop,
  578. value: value
  579. })
  580. }
  581. }
  582. }
  583. var val = 'scale(2)'
  584. d.update(val)
  585. assert.strictEqual(results.length, 4)
  586. assert.strictEqual(results[0].prop, 'transform')
  587. assert.strictEqual(results[1].prop, '-ms-transform')
  588. assert.strictEqual(results[2].prop, '-moz-transform')
  589. assert.strictEqual(results[3].prop, '-webkit-transform')
  590. assert.strictEqual(results[0].value, val)
  591. assert.strictEqual(results[1].value, val)
  592. assert.strictEqual(results[2].value, val)
  593. assert.strictEqual(results[3].value, val)
  594. })
  595. it('should set cssText if no arg', function () {
  596. var d = mockDirective('style')
  597. d.bind()
  598. var val = 'color:#fff'
  599. d.update(val)
  600. assert.strictEqual(d.el.style.color, 'rgb(255, 255, 255)')
  601. })
  602. })
  603. describe('cloak', function () {
  604. it('should remove itself after the instance is ready', function () {
  605. // it doesn't make sense to test with a mock for this one, so...
  606. var v = new Vue({
  607. template: '<div v-cloak></div>',
  608. replace: true,
  609. ready: function () {
  610. // this hook is attached before the v-cloak hook
  611. // so it should still have the attribute
  612. assert.ok(this.$el.hasAttribute('v-cloak'))
  613. }
  614. })
  615. assert.notOk(v.$el.hasAttribute('v-cloak'))
  616. })
  617. })
  618. describe('if', function () {
  619. var v
  620. it('should create and insert the childVM when value is truthy', function () {
  621. v = new Vue({
  622. template: '<div v-if="ok">{{msg}}</div>',
  623. data: {
  624. ok: true,
  625. msg: 'hello'
  626. }
  627. })
  628. assert.strictEqual(v.$el.innerHTML, '<div>hello</div><!--vue-if-->')
  629. })
  630. it('should destroy childVM and remove content when value is falsy', function (done) {
  631. v.ok = false
  632. nextTick(function () {
  633. assert.strictEqual(v.$el.innerHTML, '<!--vue-if-->')
  634. done()
  635. })
  636. })
  637. it('should work with v-component', function (done) {
  638. v = new Vue({
  639. template: '<div v-if="ok" v-component="test"></div>',
  640. data: {
  641. ok: true,
  642. msg: 'hello'
  643. },
  644. components: {
  645. test: {
  646. template: '{{msg}}'
  647. }
  648. }
  649. })
  650. assert.strictEqual(v.$el.innerHTML, '<div>hello</div><!--vue-if-->')
  651. v.ok = false
  652. nextTick(function () {
  653. assert.strictEqual(v.$el.innerHTML, '<!--vue-if-->')
  654. done()
  655. })
  656. })
  657. })
  658. describe('view', function () {
  659. it('should dynamically switch components', function (done) {
  660. var v = new Vue({
  661. template: '<div v-view="view" class="view"></div>',
  662. data: {
  663. view: 'a'
  664. },
  665. components: {
  666. a: { template: 'A' },
  667. b: { template: 'B' }
  668. }
  669. })
  670. assert.equal(
  671. v.$el.innerHTML,
  672. '<div class="view">A</div><!--v-view-->'
  673. )
  674. v.view = 'b'
  675. nextTick(function () {
  676. assert.equal(
  677. v.$el.innerHTML,
  678. '<div class="view">B</div><!--v-view-->'
  679. )
  680. done()
  681. })
  682. })
  683. })
  684. // More detailed testing for v-repeat can be found in functional tests.
  685. // this is mainly for code coverage
  686. describe('repeat', function () {
  687. it('should work', function (done) {
  688. var handlerCalled = false
  689. var v = new Vue({
  690. template: '<span v-repeat="items" v-on="click:check">{{title}}</span>',
  691. data: {
  692. items: [
  693. {title: 1},
  694. {title: 2}
  695. ]
  696. },
  697. methods: {
  698. check: function (e) {
  699. assert.ok(e.targetVM instanceof VM)
  700. assert.strictEqual(this, v)
  701. handlerCalled = true
  702. }
  703. }
  704. })
  705. nextTick(function () {
  706. assert.equal(v.$el.innerHTML, '<span>1</span><span>2</span><!--v-repeat-items-->')
  707. v.items.push({title:3})
  708. v.items.pop()
  709. v.items.unshift({title:0})
  710. v.items.shift()
  711. v.items.splice(0, 1, {title:-1})
  712. v.items.sort(function (a, b) {
  713. return a.title > b.title
  714. })
  715. v.items.reverse()
  716. nextTick(function () {
  717. assert.equal(v.$el.innerHTML, '<span>2</span><span>-1</span><!--v-repeat-items-->')
  718. testHandler()
  719. })
  720. })
  721. function testHandler () {
  722. document.getElementById('test').appendChild(v.$el)
  723. var span = v.$el.querySelector('span'),
  724. e = mockMouseEvent('click')
  725. span.dispatchEvent(e)
  726. nextTick(function () {
  727. assert.ok(handlerCalled)
  728. done()
  729. })
  730. }
  731. })
  732. it('should work with primitive values', function () {
  733. var v = new Vue({
  734. template: '<span v-repeat="tags">{{$value}}</span>',
  735. data: {
  736. tags: ['a', 'b', 'c']
  737. },
  738. computed: {
  739. concat: function () {
  740. return this.tags.join(',')
  741. }
  742. }
  743. })
  744. assert.strictEqual(v.concat, 'a,b,c')
  745. assert.strictEqual(v.$el.textContent, 'abc')
  746. })
  747. it('should diff and reuse existing VMs when reseting arrays', function (done) {
  748. var v = new Vue({
  749. template: '<span v-repeat="tags" v-ref="tags">{{$value}}</span>',
  750. data: {
  751. tags: ['a', 'b', 'c']
  752. }
  753. })
  754. var oldVMs = v.$.tags
  755. v.tags = v.tags.slice()
  756. nextTick(function () {
  757. assert.deepEqual(oldVMs, v.$.tags)
  758. done()
  759. })
  760. })
  761. it('should also work on objects', function (done) {
  762. var v = new Vue({
  763. template: '<span v-repeat="obj">{{$key}} {{msg}}</span>',
  764. data: {
  765. obj: {
  766. a: {
  767. msg: 'hi!'
  768. },
  769. b: {
  770. msg: 'ha!'
  771. }
  772. }
  773. }
  774. })
  775. assert.strictEqual(v.$el.textContent, 'a hi!b ha!')
  776. v.obj.a.msg = 'ho!'
  777. nextTick(function () {
  778. assert.strictEqual(v.$el.textContent, 'a ho!b ha!')
  779. testAddKey()
  780. })
  781. function testAddKey () {
  782. v.obj.$add('c', { msg: 'he!' })
  783. nextTick(function () {
  784. assert.strictEqual(v.$el.textContent, 'a ho!b ha!c he!')
  785. assert.strictEqual(v.obj.c.msg, 'he!')
  786. testRemoveKey()
  787. })
  788. }
  789. function testRemoveKey () {
  790. v.obj.$delete('a')
  791. nextTick(function () {
  792. assert.strictEqual(v.$el.textContent, 'b ha!c he!')
  793. assert.strictEqual(v.obj.a, undefined)
  794. testSwap()
  795. })
  796. }
  797. function testSwap () {
  798. v.obj.b = { msg: 'hehe' }
  799. nextTick(function () {
  800. assert.strictEqual(v.$el.textContent, 'b hehec he!')
  801. testRootSwap()
  802. })
  803. }
  804. function testRootSwap () {
  805. v.obj = { b: { msg: 'wa'}, c: {msg: 'wo'} }
  806. nextTick(function () {
  807. assert.strictEqual(v.$el.textContent, 'b wac wo')
  808. done()
  809. })
  810. }
  811. })
  812. it('should accept arg for aliasing on primitive arrays', function () {
  813. var v = new Vue({
  814. template: '<span v-repeat="item:items" >{{item}}</span>',
  815. data: {
  816. items: [1,2,3]
  817. }
  818. })
  819. assert.strictEqual(v.$el.textContent, '123')
  820. })
  821. it('should accept arg for aliasing on object arrays', function () {
  822. var v = new Vue({
  823. template: '<span v-repeat="item:items">{{item.id}}</span>',
  824. data: {
  825. items: [{id:1},{id:2},{id:3}]
  826. }
  827. })
  828. assert.strictEqual(v.$el.textContent, '123')
  829. })
  830. })
  831. })
  832. function mockDirective (dirName, tag, type) {
  833. var dir = Vue.directive(dirName),
  834. ret = {
  835. binding: { compiler: { vm: {} } },
  836. compiler: { vm: {}, options: {}, execHook: function () {}},
  837. el: document.createElement(tag || 'div')
  838. }
  839. if (typeof dir === 'function') {
  840. ret.update = dir
  841. } else {
  842. for (var key in dir) {
  843. ret[key] = dir[key]
  844. ret._update = dir.update
  845. ret._unbind = dir.unbind
  846. }
  847. }
  848. if (tag === 'input') ret.el.type = type || 'text'
  849. return ret
  850. }