directives.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. /*
  2. * Only tests directives in `src/directives/index.js`
  3. * and the non-delegated case for `sd-on`
  4. *
  5. * The combination of `sd-each` and `sd-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 be empty with other stuff', function () {
  29. dir.update(null)
  30. assert.strictEqual(dir.el.textContent, '')
  31. dir.update(false)
  32. assert.strictEqual(dir.el.textContent, '')
  33. dir.update(true)
  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 be empty with other stuff', function () {
  56. dir.update(null)
  57. assert.strictEqual(dir.el.innerHTML, '')
  58. dir.update(false)
  59. assert.strictEqual(dir.el.innerHTML, '')
  60. dir.update(true)
  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('focus', function () {
  140. var dir = mockDirective('focus', 'input')
  141. it('should focus on the element', function (done) {
  142. var focused = false
  143. // the el needs to be in the dom and visible to actually
  144. // trigger the focus event
  145. document.body.appendChild(dir.el)
  146. dir.el.addEventListener('focus', function () {
  147. focused = true
  148. })
  149. dir.update(true)
  150. // the focus event has a 0ms timeout to make it async
  151. setTimeout(function () {
  152. assert.ok(focused)
  153. document.body.removeChild(dir.el)
  154. done()
  155. }, 0)
  156. })
  157. })
  158. describe('class', function () {
  159. it('should set class to the value if it has no arg', function () {
  160. var dir = mockDirective('class')
  161. dir.update('test')
  162. assert.ok(dir.el.classList.contains('test'))
  163. dir.update('hoho')
  164. assert.ok(!dir.el.classList.contains('test'))
  165. assert.ok(dir.el.classList.contains('hoho'))
  166. })
  167. it('should add/remove class based on truthy/falsy if it has an arg', function () {
  168. var dir = mockDirective('class')
  169. dir.arg = 'test'
  170. dir.update(true)
  171. assert.ok(dir.el.classList.contains('test'))
  172. dir.update(false)
  173. assert.ok(!dir.el.classList.contains('test'))
  174. })
  175. })
  176. describe('value', function () {
  177. var dir = mockDirective('value', 'input')
  178. dir.bind()
  179. before(function () {
  180. document.body.appendChild(dir.el)
  181. })
  182. it('should set the value on update()', function () {
  183. dir.update('foobar')
  184. assert.strictEqual(dir.el.value, 'foobar')
  185. })
  186. it('should trigger vm.$set when value is changed via keyup', function () {
  187. var triggered = false
  188. dir.key = 'foo'
  189. dir.vm = { $set: function (key, val) {
  190. assert.strictEqual(key, 'foo')
  191. assert.strictEqual(val, 'bar')
  192. triggered = true
  193. }}
  194. dir.el.value = 'bar'
  195. dir.el.dispatchEvent(mockKeyEvent('keyup'))
  196. assert.ok(triggered)
  197. })
  198. it('should remove event listener with unbind()', function () {
  199. var removed = true
  200. dir.vm.$set = function () {
  201. removed = false
  202. }
  203. dir.unbind()
  204. dir.el.dispatchEvent(mockKeyEvent('keyup'))
  205. assert.ok(removed)
  206. })
  207. after(function () {
  208. document.body.removeChild(dir.el)
  209. })
  210. })
  211. describe('checked', function () {
  212. var dir = mockDirective('checked', 'input', 'checkbox')
  213. dir.bind()
  214. before(function () {
  215. document.body.appendChild(dir.el)
  216. })
  217. it('should set checked on update()', function () {
  218. dir.update(true)
  219. assert.ok(dir.el.checked)
  220. dir.update(false)
  221. assert.ok(!dir.el.checked)
  222. })
  223. it('should trigger vm.$set on change event', function () {
  224. var triggered = false
  225. dir.key = 'foo'
  226. dir.vm = { $set: function (key, val) {
  227. assert.strictEqual(key, 'foo')
  228. assert.strictEqual(val, true)
  229. triggered = true
  230. }}
  231. dir.el.dispatchEvent(mockMouseEvent('click'))
  232. assert.ok(triggered)
  233. })
  234. it('should remove event listener with unbind()', function () {
  235. var removed = true
  236. dir.vm.$set = function () {
  237. removed = false
  238. }
  239. dir.unbind()
  240. dir.el.dispatchEvent(mockMouseEvent('click'))
  241. assert.ok(removed)
  242. })
  243. after(function () {
  244. document.body.removeChild(dir.el)
  245. })
  246. })
  247. describe('if', function () {
  248. it('should remain detached if it was detached during bind() and never attached', function () {
  249. var dir = mockDirective('if')
  250. dir.bind()
  251. dir.update(true)
  252. assert.notOk(dir.el.parentNode)
  253. dir.update(false)
  254. assert.notOk(dir.el.parentNode)
  255. })
  256. it('should remove el and insert ref when value is falsy', function () {
  257. var dir = mockDirective('if'),
  258. parent = document.createElement('div')
  259. parent.appendChild(dir.el)
  260. dir.bind()
  261. dir.update(false)
  262. assert.notOk(dir.el.parentNode)
  263. assert.notOk(parent.contains(dir.el))
  264. // phantomJS weird bug:
  265. // Node.contains() returns false when argument is a comment node.
  266. assert.strictEqual(dir.ref.parentNode, parent)
  267. })
  268. it('should append el and remove ref when value is truthy', function () {
  269. var dir = mockDirective('if'),
  270. parent = document.createElement('div')
  271. parent.appendChild(dir.el)
  272. dir.bind()
  273. dir.update(false)
  274. dir.update(true)
  275. assert.strictEqual(dir.el.parentNode, parent)
  276. assert.ok(parent.contains(dir.el))
  277. assert.notOk(parent.contains(dir.ref))
  278. })
  279. it('should work even if it was detached during bind()', function () {
  280. var dir = mockDirective('if')
  281. dir.bind()
  282. var parent = document.createElement('div')
  283. parent.appendChild(dir.el)
  284. dir.update(false)
  285. assert.strictEqual(dir.parent, parent)
  286. assert.notOk(dir.el.parentNode)
  287. assert.notOk(parent.contains(dir.el))
  288. assert.strictEqual(dir.ref.parentNode, parent)
  289. dir.update(true)
  290. assert.strictEqual(dir.el.parentNode, parent)
  291. assert.ok(parent.contains(dir.el))
  292. assert.notOk(parent.contains(dir.ref))
  293. })
  294. })
  295. describe('on (non-delegated only)', function () {
  296. var dir = mockDirective('on')
  297. dir.arg = 'click'
  298. it('should set the handler to be triggered by arg through update()', function () {
  299. var triggered = false
  300. dir.update(function () {
  301. triggered = true
  302. })
  303. dir.el.dispatchEvent(mockMouseEvent('click'))
  304. assert.ok(triggered)
  305. })
  306. it('should wrap the handler to supply expected args', function () {
  307. var vm = dir.binding.compiler.vm, // owner VM
  308. e = mockMouseEvent('click'), // original event
  309. triggered = false
  310. dir.update(function (ev) {
  311. assert.strictEqual(this, vm, 'handler should be called on owner VM')
  312. assert.strictEqual(ev, e, 'event should be passed in')
  313. assert.strictEqual(ev.vm, dir.vm)
  314. triggered = true
  315. })
  316. dir.el.dispatchEvent(e)
  317. assert.ok(triggered)
  318. })
  319. it('should remove previous handler when update() a new handler', function () {
  320. var triggered1 = false,
  321. triggered2 = false
  322. dir.update(function () {
  323. triggered1 = true
  324. })
  325. dir.update(function () {
  326. triggered2 = true
  327. })
  328. dir.el.dispatchEvent(mockMouseEvent('click'))
  329. assert.notOk(triggered1)
  330. assert.ok(triggered2)
  331. })
  332. it('should remove the handler in unbind()', function () {
  333. var triggered = false
  334. dir.update(function () {
  335. triggered = true
  336. })
  337. dir.unbind()
  338. dir.el.dispatchEvent(mockMouseEvent('click'))
  339. assert.notOk(triggered)
  340. assert.strictEqual(dir.handler, null, 'should remove reference to handler')
  341. assert.strictEqual(dir.el.sd_viewmodel, null, 'should remove reference to VM on the element')
  342. })
  343. it('should not use delegation if the event is blur or focus', function () {
  344. var dir = mockDirective('on', 'input'),
  345. triggerCount = 0,
  346. handler = function () {
  347. triggerCount++
  348. }
  349. document.body.appendChild(dir.el)
  350. dir.arg = 'focus'
  351. dir.update(handler)
  352. dir.el.focus()
  353. assert.strictEqual(triggerCount, 1)
  354. dir.arg = 'blur'
  355. dir.update(handler)
  356. dir.el.blur()
  357. assert.strictEqual(triggerCount, 2)
  358. document.body.removeChild(dir.el)
  359. })
  360. })
  361. })
  362. function mockDirective (dirName, tag, type) {
  363. var dir = seed.directive(dirName),
  364. ret = {
  365. binding: { compiler: { vm: {} } },
  366. compiler: { vm: {} },
  367. el: document.createElement(tag || 'div')
  368. }
  369. if (typeof dir === 'function') {
  370. ret.update = dir
  371. } else {
  372. for (var key in dir) {
  373. ret[key] = dir[key]
  374. }
  375. }
  376. if (tag === 'input') ret.el.type = type || 'text'
  377. return ret
  378. }
  379. function mockKeyEvent (type) {
  380. var e = document.createEvent('KeyboardEvent'),
  381. initMethod = e.initKeyboardEvent
  382. ? 'initKeyboardEvent'
  383. : 'initKeyEvent'
  384. e[initMethod](type, true, true, null, false, false, false, false, 9, 0)
  385. return e
  386. }
  387. function mockMouseEvent (type) {
  388. var e = document.createEvent('MouseEvent')
  389. e.initMouseEvent(type, true, true, null, 1, 0, 0, 0, 0, false, false, false, false, 0, null)
  390. return e
  391. }