directives.js 32 KB

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