directives.js 34 KB

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