directives.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  1. /*
  2. * Only tests directives in `src/directives/index.js`
  3. * and the non-delegated case for `v-on`
  4. *
  5. * The combination of `v-repeat` and `v-on` are covered in
  6. * the E2E test case for repeated items.
  7. */
  8. describe('UNIT: Directives', function () {
  9. describe('attr', function () {
  10. var dir = mockDirective('attr')
  11. dir.arg = 'href'
  12. it('should set an attribute', function () {
  13. var url = 'http://a.b.com'
  14. dir.update(url)
  15. assert.strictEqual(dir.el.getAttribute('href'), url)
  16. })
  17. })
  18. describe('text', function () {
  19. var dir = mockDirective('text')
  20. it('should work with a string', function () {
  21. dir.update('hallo')
  22. assert.strictEqual(dir.el.textContent, 'hallo')
  23. })
  24. it('should work with a number', function () {
  25. dir.update(12345)
  26. assert.strictEqual(dir.el.textContent, '12345')
  27. })
  28. it('should work with booleans', function () {
  29. dir.update(true)
  30. assert.strictEqual(dir.el.textContent, 'true')
  31. })
  32. it('should be empty with other stuff', function () {
  33. dir.update(null)
  34. assert.strictEqual(dir.el.textContent, '')
  35. dir.update(undefined)
  36. assert.strictEqual(dir.el.textContent, '')
  37. dir.update({a:123})
  38. assert.strictEqual(dir.el.textContent, '')
  39. dir.update(function () {})
  40. assert.strictEqual(dir.el.textContent, '')
  41. })
  42. })
  43. describe('html', function () {
  44. var dir = mockDirective('html')
  45. it('should work with a string', function () {
  46. dir.update('hi!!!')
  47. assert.strictEqual(dir.el.innerHTML, 'hi!!!')
  48. dir.update('<span>haha</span><a>lol</a>')
  49. assert.strictEqual(dir.el.querySelector('span').textContent, 'haha')
  50. })
  51. it('should work with a number', function () {
  52. dir.update(12345)
  53. assert.strictEqual(dir.el.innerHTML, '12345')
  54. })
  55. it('should work with booleans', function () {
  56. dir.update(true)
  57. assert.strictEqual(dir.el.textContent, 'true')
  58. })
  59. it('should be empty with other stuff', function () {
  60. dir.update(null)
  61. assert.strictEqual(dir.el.innerHTML, '')
  62. dir.update(undefined)
  63. assert.strictEqual(dir.el.innerHTML, '')
  64. dir.update({a:123})
  65. assert.strictEqual(dir.el.innerHTML, '')
  66. dir.update(function () {})
  67. assert.strictEqual(dir.el.innerHTML, '')
  68. })
  69. })
  70. describe('style', function () {
  71. var dir = mockDirective('style')
  72. it('should convert the arg from dash style to camel case', function () {
  73. dir.arg = 'font-family'
  74. dir.bind()
  75. assert.strictEqual(dir.arg, 'fontFamily')
  76. dir.arg = '-webkit-transform'
  77. dir.bind()
  78. assert.strictEqual(dir.arg, 'webkitTransform')
  79. })
  80. it('should update the element style', function () {
  81. dir.update('rotate(20deg)')
  82. assert.strictEqual(dir.el.style.webkitTransform, 'rotate(20deg)')
  83. })
  84. })
  85. describe('show', function () {
  86. var dir = mockDirective('show')
  87. it('should be default value when value is truthy', function () {
  88. dir.update(1)
  89. assert.strictEqual(dir.el.style.display, '')
  90. dir.update('hi!')
  91. assert.strictEqual(dir.el.style.display, '')
  92. dir.update(true)
  93. assert.strictEqual(dir.el.style.display, '')
  94. dir.update({})
  95. assert.strictEqual(dir.el.style.display, '')
  96. dir.update(function () {})
  97. assert.strictEqual(dir.el.style.display, '')
  98. })
  99. it('should be none when value is falsy', function () {
  100. dir.update(0)
  101. assert.strictEqual(dir.el.style.display, 'none')
  102. dir.update('')
  103. assert.strictEqual(dir.el.style.display, 'none')
  104. dir.update(false)
  105. assert.strictEqual(dir.el.style.display, 'none')
  106. dir.update(null)
  107. assert.strictEqual(dir.el.style.display, 'none')
  108. dir.update(undefined)
  109. assert.strictEqual(dir.el.style.display, 'none')
  110. })
  111. })
  112. describe('visible', function () {
  113. var dir = mockDirective('visible')
  114. it('should be default value when value is truthy', function () {
  115. dir.update(1)
  116. assert.strictEqual(dir.el.style.visibility, '')
  117. dir.update('hi!')
  118. assert.strictEqual(dir.el.style.visibility, '')
  119. dir.update(true)
  120. assert.strictEqual(dir.el.style.visibility, '')
  121. dir.update({})
  122. assert.strictEqual(dir.el.style.visibility, '')
  123. dir.update(function () {})
  124. assert.strictEqual(dir.el.style.visibility, '')
  125. })
  126. it('should be hidden when value is falsy', function () {
  127. dir.update(0)
  128. assert.strictEqual(dir.el.style.visibility, 'hidden')
  129. dir.update('')
  130. assert.strictEqual(dir.el.style.visibility, 'hidden')
  131. dir.update(false)
  132. assert.strictEqual(dir.el.style.visibility, 'hidden')
  133. dir.update(null)
  134. assert.strictEqual(dir.el.style.visibility, 'hidden')
  135. dir.update(undefined)
  136. assert.strictEqual(dir.el.style.visibility, 'hidden')
  137. })
  138. })
  139. describe('class', function () {
  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(dir.el.classList.contains('test'))
  144. dir.update('hoho')
  145. assert.ok(!dir.el.classList.contains('test'))
  146. assert.ok(dir.el.classList.contains('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(dir.el.classList.contains('test'))
  153. dir.update(false)
  154. assert.ok(!dir.el.classList.contains('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.vm = { $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.vm.$set = function () {
  184. removed = false
  185. }
  186. dir.unbind()
  187. dir.el.dispatchEvent(mockMouseEvent('click'))
  188. assert.ok(removed)
  189. })
  190. after(function () {
  191. document.body.removeChild(dir.el)
  192. })
  193. })
  194. describe('input[radio]', function () {
  195. var dir1 = mockDirective('model', 'input', 'radio'),
  196. dir2 = mockDirective('model', 'input', 'radio')
  197. dir1.el.name = 'input-radio'
  198. dir2.el.name = 'input-radio'
  199. dir1.el.value = '12345'
  200. dir2.el.value = '54321'
  201. dir1.bind()
  202. dir2.bind()
  203. before(function () {
  204. document.body.appendChild(dir1.el)
  205. document.body.appendChild(dir2.el)
  206. })
  207. it('should set el.checked on update()', function () {
  208. assert.notOk(dir1.el.checked)
  209. dir1.update(12345)
  210. assert.ok(dir1.el.checked)
  211. })
  212. it('should trigger vm.$set when clicked', function () {
  213. var triggered = false
  214. dir2.key = 'radio'
  215. dir2.vm = { $set: function (key, val) {
  216. triggered = true
  217. assert.strictEqual(key, 'radio')
  218. assert.strictEqual(val, dir2.el.value)
  219. }}
  220. dir2.el.dispatchEvent(mockMouseEvent('click'))
  221. assert.ok(triggered)
  222. assert.ok(dir2.el.checked)
  223. assert.notOk(dir1.el.checked)
  224. })
  225. it('should remove listeners on unbind()', function () {
  226. var removed = true
  227. dir1.vm = { $set: function () {
  228. removed = false
  229. }}
  230. dir1.unbind()
  231. dir1.el.dispatchEvent(mockMouseEvent('click'))
  232. assert.ok(removed)
  233. })
  234. after(function () {
  235. document.body.removeChild(dir1.el)
  236. document.body.removeChild(dir2.el)
  237. })
  238. })
  239. describe('select', function () {
  240. var dir = mockDirective('model', 'select'),
  241. o1 = document.createElement('option'),
  242. o2 = document.createElement('option')
  243. o1.value = 0
  244. o2.value = 1
  245. dir.el.appendChild(o1)
  246. dir.el.appendChild(o2)
  247. dir.bind()
  248. before(function () {
  249. document.body.appendChild(dir.el)
  250. })
  251. it('should set value on update()', function () {
  252. dir.update(0)
  253. assert.strictEqual(dir.el.value, '0')
  254. })
  255. it('should trigger vm.$set when value is changed', function () {
  256. var triggered = false
  257. dir.key = 'select'
  258. dir.vm = { $set: function (key, val) {
  259. triggered = true
  260. assert.strictEqual(key, 'select')
  261. assert.equal(val, 1)
  262. }}
  263. dir.el.options.selectedIndex = 1
  264. dir.el.dispatchEvent(mockHTMLEvent('change'))
  265. assert.ok(triggered)
  266. })
  267. it('should remove listener on unbind()', function () {
  268. var removed = true
  269. dir.vm = { $set: function () {
  270. removed = false
  271. }}
  272. dir.unbind()
  273. dir.el.dispatchEvent(mockHTMLEvent('change'))
  274. assert.ok(removed)
  275. })
  276. after(function () {
  277. document.body.removeChild(dir.el)
  278. })
  279. })
  280. describe('input[text] and others', function () {
  281. var dir = mockDirective('model', 'input', 'text')
  282. dir.bind()
  283. before(function () {
  284. document.body.appendChild(dir.el)
  285. })
  286. it('should set the value on update()', function () {
  287. dir.update('foobar')
  288. assert.strictEqual(dir.el.value, 'foobar')
  289. })
  290. // `lazy` option is tested in the API suite
  291. it('should trigger vm.$set when value is changed via input', function () {
  292. var triggered = false
  293. dir.key = 'foo'
  294. dir.vm = { $set: function (key, val) {
  295. assert.ok(dir.lock, 'the directive should be locked if it has no filters')
  296. assert.strictEqual(key, 'foo')
  297. assert.strictEqual(val, 'bar')
  298. triggered = true
  299. }}
  300. dir.el.value = 'bar'
  301. dir.el.dispatchEvent(mockHTMLEvent('input'))
  302. assert.ok(triggered)
  303. })
  304. it('should remove event listener with unbind()', function () {
  305. var removed = true
  306. dir.vm.$set = function () {
  307. removed = false
  308. }
  309. dir.unbind()
  310. dir.el.dispatchEvent(mockHTMLEvent('input'))
  311. assert.ok(removed)
  312. })
  313. it('should not lock during vm.$set if it has filters', function (done) {
  314. var triggered = false
  315. var dir = mockDirective('model', 'input', 'text')
  316. dir.filters = []
  317. dir.bind()
  318. dir.vm = {$set:function () {
  319. assert.notOk(dir.lock)
  320. triggered = true
  321. }}
  322. dir.el.value = 'foo'
  323. document.body.appendChild(dir.el)
  324. dir.el.dispatchEvent(mockHTMLEvent('input'))
  325. // timeout becuase the update is async
  326. setTimeout(function () {
  327. assert.ok(triggered)
  328. document.body.removeChild(dir.el)
  329. done()
  330. }, 1)
  331. })
  332. after(function () {
  333. document.body.removeChild(dir.el)
  334. })
  335. })
  336. })
  337. describe('if', function () {
  338. it('should remain detached if it was detached during bind() and never attached', function () {
  339. var dir = mockDirective('if')
  340. dir.bind()
  341. dir.update(true)
  342. assert.notOk(dir.el.parentNode)
  343. dir.update(false)
  344. assert.notOk(dir.el.parentNode)
  345. })
  346. it('should remove el and insert ref when value is falsy', function () {
  347. var dir = mockDirective('if'),
  348. parent = document.createElement('div')
  349. parent.appendChild(dir.el)
  350. dir.bind()
  351. dir.update(false)
  352. assert.notOk(dir.el.parentNode)
  353. assert.notOk(parent.contains(dir.el))
  354. // phantomJS weird bug:
  355. // Node.contains() returns false when argument is a comment node.
  356. assert.strictEqual(dir.ref.parentNode, parent)
  357. })
  358. it('should append el and remove ref when value is truthy', function () {
  359. var dir = mockDirective('if'),
  360. parent = document.createElement('div')
  361. parent.appendChild(dir.el)
  362. dir.bind()
  363. dir.update(false)
  364. dir.update(true)
  365. assert.strictEqual(dir.el.parentNode, parent)
  366. assert.ok(parent.contains(dir.el))
  367. assert.notOk(parent.contains(dir.ref))
  368. })
  369. it('should work even if it was detached during bind()', function () {
  370. var dir = mockDirective('if')
  371. dir.bind()
  372. var parent = document.createElement('div')
  373. parent.appendChild(dir.el)
  374. dir.update(false)
  375. assert.strictEqual(dir.parent, parent)
  376. assert.notOk(dir.el.parentNode)
  377. assert.notOk(parent.contains(dir.el))
  378. assert.strictEqual(dir.ref.parentNode, parent)
  379. dir.update(true)
  380. assert.strictEqual(dir.el.parentNode, parent)
  381. assert.ok(parent.contains(dir.el))
  382. assert.notOk(parent.contains(dir.ref))
  383. })
  384. })
  385. describe('on (non-delegated only)', function () {
  386. var dir = mockDirective('on')
  387. dir.arg = 'click'
  388. before(function () {
  389. document.body.appendChild(dir.el)
  390. })
  391. it('should set the handler to be triggered by arg through update()', function () {
  392. var triggered = false
  393. dir.update(function () {
  394. triggered = true
  395. })
  396. dir.el.dispatchEvent(mockMouseEvent('click'))
  397. assert.ok(triggered)
  398. })
  399. it('should wrap the handler to supply expected args', function () {
  400. var vm = dir.binding.compiler.vm, // owner VM
  401. e = mockMouseEvent('click'), // original event
  402. triggered = false
  403. dir.update(function (ev) {
  404. assert.strictEqual(this, vm, 'handler should be called on owner VM')
  405. assert.strictEqual(ev, e, 'event should be passed in')
  406. assert.strictEqual(ev.vm, dir.vm)
  407. triggered = true
  408. })
  409. dir.el.dispatchEvent(e)
  410. assert.ok(triggered)
  411. })
  412. it('should remove previous handler when update() a new handler', function () {
  413. var triggered1 = false,
  414. triggered2 = false
  415. dir.update(function () {
  416. triggered1 = true
  417. })
  418. dir.update(function () {
  419. triggered2 = true
  420. })
  421. dir.el.dispatchEvent(mockMouseEvent('click'))
  422. assert.notOk(triggered1)
  423. assert.ok(triggered2)
  424. })
  425. it('should remove the handler in unbind()', function () {
  426. var triggered = false
  427. dir.update(function () {
  428. triggered = true
  429. })
  430. dir.unbind()
  431. dir.el.dispatchEvent(mockMouseEvent('click'))
  432. assert.notOk(triggered)
  433. assert.strictEqual(dir.handler, null, 'should remove reference to handler')
  434. assert.strictEqual(dir.el.vue_viewmodel, null, 'should remove reference to VM on the element')
  435. })
  436. it('should not use delegation if the event is blur or focus', function () {
  437. var dir = mockDirective('on', 'input'),
  438. triggerCount = 0,
  439. handler = function () {
  440. triggerCount++
  441. }
  442. document.body.appendChild(dir.el)
  443. dir.arg = 'focus'
  444. dir.update(handler)
  445. dir.el.dispatchEvent(mockHTMLEvent('focus'))
  446. assert.strictEqual(triggerCount, 1)
  447. dir.arg = 'blur'
  448. dir.update(handler)
  449. dir.el.dispatchEvent(mockHTMLEvent('blur'))
  450. assert.strictEqual(triggerCount, 2)
  451. document.body.removeChild(dir.el)
  452. })
  453. after(function () {
  454. document.body.removeChild(dir.el)
  455. })
  456. })
  457. describe('pre', function () {
  458. it('should skip compilation', function () {
  459. var testId = 'pre-test'
  460. mock(testId, '<span v-pre><strong>{{lol}}</strong><a v-text="hi"></a></span>')
  461. var t = new Vue({
  462. el: '#' + testId,
  463. scope: {
  464. lol: 'heyhey',
  465. hi: 'hohoho'
  466. }
  467. })
  468. assert.strictEqual(t.$el.querySelector('strong').textContent, '{{lol}}')
  469. assert.strictEqual(t.$el.querySelector('a').textContent, '')
  470. assert.ok(t.$el.querySelector('a').hasAttribute('v-text'))
  471. })
  472. })
  473. describe('component', function () {
  474. it('should work with no args', function () {
  475. assert.ok(false)
  476. })
  477. it('should work with arg (passed-in model from parent)', function () {
  478. assert.ok(false)
  479. })
  480. })
  481. describe('component-id', function () {
  482. it('should register a VM isntance on its parent\'s $', function () {
  483. var called = false
  484. var Child = Vue.extend({
  485. proto: {
  486. test: function () {
  487. called = true
  488. }
  489. }
  490. })
  491. var t = new Vue({
  492. template: '<div v-component="child" v-component-id="hihi"></div>',
  493. components: {
  494. child: Child
  495. }
  496. })
  497. assert.ok(t.$.hihi instanceof Child)
  498. t.$.hihi.test()
  499. assert.ok(called)
  500. t.$.hihi.$destroy()
  501. assert.notOk('hihi' in t.$)
  502. })
  503. })
  504. })
  505. function mockDirective (dirName, tag, type) {
  506. var dir = Vue.directive(dirName),
  507. ret = {
  508. binding: { compiler: { vm: {} } },
  509. compiler: { vm: {}, options: {}, execHook: function () {}},
  510. el: document.createElement(tag || 'div')
  511. }
  512. if (typeof dir === 'function') {
  513. ret.update = dir
  514. } else {
  515. for (var key in dir) {
  516. ret[key] = dir[key]
  517. }
  518. }
  519. if (tag === 'input') ret.el.type = type || 'text'
  520. return ret
  521. }