event-handler.spec.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. import EventHandler from '../../../src/dom/event-handler'
  2. /** Test helpers */
  3. import { getFixture, clearFixture } from '../../helpers/fixture'
  4. describe('EventHandler', () => {
  5. let fixtureEl
  6. beforeAll(() => {
  7. fixtureEl = getFixture()
  8. })
  9. afterEach(() => {
  10. clearFixture()
  11. })
  12. describe('on', () => {
  13. it('should not add event listener if the event is not a string', () => {
  14. fixtureEl.innerHTML = '<div></div>'
  15. const div = fixtureEl.querySelector('div')
  16. EventHandler.on(div, null, () => {})
  17. EventHandler.on(null, 'click', () => {})
  18. expect().nothing()
  19. })
  20. it('should add event listener', done => {
  21. fixtureEl.innerHTML = '<div></div>'
  22. const div = fixtureEl.querySelector('div')
  23. EventHandler.on(div, 'click', () => {
  24. expect().nothing()
  25. done()
  26. })
  27. div.click()
  28. })
  29. it('should add namespaced event listener', done => {
  30. fixtureEl.innerHTML = '<div></div>'
  31. const div = fixtureEl.querySelector('div')
  32. EventHandler.on(div, 'bs.namespace', () => {
  33. expect().nothing()
  34. done()
  35. })
  36. EventHandler.trigger(div, 'bs.namespace')
  37. })
  38. it('should add native namespaced event listener', done => {
  39. fixtureEl.innerHTML = '<div></div>'
  40. const div = fixtureEl.querySelector('div')
  41. EventHandler.on(div, 'click.namespace', () => {
  42. expect().nothing()
  43. done()
  44. })
  45. EventHandler.trigger(div, 'click')
  46. })
  47. it('should handle event delegation', done => {
  48. EventHandler.on(document, 'click', '.test', () => {
  49. expect().nothing()
  50. done()
  51. })
  52. fixtureEl.innerHTML = '<div class="test"></div>'
  53. const div = fixtureEl.querySelector('div')
  54. div.click()
  55. })
  56. it('should handle mouseenter/mouseleave like the native counterpart', done => {
  57. fixtureEl.innerHTML = [
  58. '<div class="outer">',
  59. '<div class="inner">',
  60. '<div class="nested">',
  61. '<div class="deep"></div>',
  62. '</div>',
  63. '</div>',
  64. '<div class="sibling"></div>',
  65. '</div>'
  66. ]
  67. const outer = fixtureEl.querySelector('.outer')
  68. const inner = fixtureEl.querySelector('.inner')
  69. const nested = fixtureEl.querySelector('.nested')
  70. const deep = fixtureEl.querySelector('.deep')
  71. const sibling = fixtureEl.querySelector('.sibling')
  72. const enterSpy = jasmine.createSpy('mouseenter')
  73. const leaveSpy = jasmine.createSpy('mouseleave')
  74. const delegateEnterSpy = jasmine.createSpy('mouseenter')
  75. const delegateLeaveSpy = jasmine.createSpy('mouseleave')
  76. EventHandler.on(inner, 'mouseenter', enterSpy)
  77. EventHandler.on(inner, 'mouseleave', leaveSpy)
  78. EventHandler.on(outer, 'mouseenter', '.inner', delegateEnterSpy)
  79. EventHandler.on(outer, 'mouseleave', '.inner', delegateLeaveSpy)
  80. EventHandler.on(sibling, 'mouseenter', () => {
  81. expect(enterSpy.calls.count()).toBe(2)
  82. expect(leaveSpy.calls.count()).toBe(2)
  83. expect(delegateEnterSpy.calls.count()).toBe(2)
  84. expect(delegateLeaveSpy.calls.count()).toBe(2)
  85. done()
  86. })
  87. const moveMouse = (from, to) => {
  88. from.dispatchEvent(new MouseEvent('mouseout', {
  89. bubbles: true,
  90. relatedTarget: to
  91. }))
  92. to.dispatchEvent(new MouseEvent('mouseover', {
  93. bubbles: true,
  94. relatedTarget: from
  95. }))
  96. }
  97. // from outer to deep and back to outer (nested)
  98. moveMouse(outer, inner)
  99. moveMouse(inner, nested)
  100. moveMouse(nested, deep)
  101. moveMouse(deep, nested)
  102. moveMouse(nested, inner)
  103. moveMouse(inner, outer)
  104. setTimeout(() => {
  105. expect(enterSpy.calls.count()).toBe(1)
  106. expect(leaveSpy.calls.count()).toBe(1)
  107. expect(delegateEnterSpy.calls.count()).toBe(1)
  108. expect(delegateLeaveSpy.calls.count()).toBe(1)
  109. // from outer to inner to sibling (adjacent)
  110. moveMouse(outer, inner)
  111. moveMouse(inner, sibling)
  112. }, 20)
  113. })
  114. })
  115. describe('one', () => {
  116. it('should call listener just once', done => {
  117. fixtureEl.innerHTML = '<div></div>'
  118. let called = 0
  119. const div = fixtureEl.querySelector('div')
  120. const obj = {
  121. oneListener() {
  122. called++
  123. }
  124. }
  125. EventHandler.one(div, 'bootstrap', obj.oneListener)
  126. EventHandler.trigger(div, 'bootstrap')
  127. EventHandler.trigger(div, 'bootstrap')
  128. setTimeout(() => {
  129. expect(called).toEqual(1)
  130. done()
  131. }, 20)
  132. })
  133. it('should call delegated listener just once', done => {
  134. fixtureEl.innerHTML = '<div></div>'
  135. let called = 0
  136. const div = fixtureEl.querySelector('div')
  137. const obj = {
  138. oneListener() {
  139. called++
  140. }
  141. }
  142. EventHandler.one(fixtureEl, 'bootstrap', 'div', obj.oneListener)
  143. EventHandler.trigger(div, 'bootstrap')
  144. EventHandler.trigger(div, 'bootstrap')
  145. setTimeout(() => {
  146. expect(called).toEqual(1)
  147. done()
  148. }, 20)
  149. })
  150. })
  151. describe('off', () => {
  152. it('should not remove a listener', () => {
  153. fixtureEl.innerHTML = '<div></div>'
  154. const div = fixtureEl.querySelector('div')
  155. EventHandler.off(div, null, () => {})
  156. EventHandler.off(null, 'click', () => {})
  157. expect().nothing()
  158. })
  159. it('should remove a listener', done => {
  160. fixtureEl.innerHTML = '<div></div>'
  161. const div = fixtureEl.querySelector('div')
  162. let called = 0
  163. const handler = () => {
  164. called++
  165. }
  166. EventHandler.on(div, 'foobar', handler)
  167. EventHandler.trigger(div, 'foobar')
  168. EventHandler.off(div, 'foobar', handler)
  169. EventHandler.trigger(div, 'foobar')
  170. setTimeout(() => {
  171. expect(called).toEqual(1)
  172. done()
  173. }, 20)
  174. })
  175. it('should remove all the events', done => {
  176. fixtureEl.innerHTML = '<div></div>'
  177. const div = fixtureEl.querySelector('div')
  178. let called = 0
  179. EventHandler.on(div, 'foobar', () => {
  180. called++
  181. })
  182. EventHandler.on(div, 'foobar', () => {
  183. called++
  184. })
  185. EventHandler.trigger(div, 'foobar')
  186. EventHandler.off(div, 'foobar')
  187. EventHandler.trigger(div, 'foobar')
  188. setTimeout(() => {
  189. expect(called).toEqual(2)
  190. done()
  191. }, 20)
  192. })
  193. it('should remove all the namespaced listeners if namespace is passed', done => {
  194. fixtureEl.innerHTML = '<div></div>'
  195. const div = fixtureEl.querySelector('div')
  196. let called = 0
  197. EventHandler.on(div, 'foobar.namespace', () => {
  198. called++
  199. })
  200. EventHandler.on(div, 'foofoo.namespace', () => {
  201. called++
  202. })
  203. EventHandler.trigger(div, 'foobar.namespace')
  204. EventHandler.trigger(div, 'foofoo.namespace')
  205. EventHandler.off(div, '.namespace')
  206. EventHandler.trigger(div, 'foobar.namespace')
  207. EventHandler.trigger(div, 'foofoo.namespace')
  208. setTimeout(() => {
  209. expect(called).toEqual(2)
  210. done()
  211. }, 20)
  212. })
  213. it('should remove the namespaced listeners', done => {
  214. fixtureEl.innerHTML = '<div></div>'
  215. const div = fixtureEl.querySelector('div')
  216. let calledCallback1 = 0
  217. let calledCallback2 = 0
  218. EventHandler.on(div, 'foobar.namespace', () => {
  219. calledCallback1++
  220. })
  221. EventHandler.on(div, 'foofoo.namespace', () => {
  222. calledCallback2++
  223. })
  224. EventHandler.trigger(div, 'foobar.namespace')
  225. EventHandler.off(div, 'foobar.namespace')
  226. EventHandler.trigger(div, 'foobar.namespace')
  227. EventHandler.trigger(div, 'foofoo.namespace')
  228. setTimeout(() => {
  229. expect(calledCallback1).toEqual(1)
  230. expect(calledCallback2).toEqual(1)
  231. done()
  232. }, 20)
  233. })
  234. it('should remove the all the namespaced listeners for native events', done => {
  235. fixtureEl.innerHTML = '<div></div>'
  236. const div = fixtureEl.querySelector('div')
  237. let called = 0
  238. EventHandler.on(div, 'click.namespace', () => {
  239. called++
  240. })
  241. EventHandler.on(div, 'click.namespace2', () => {
  242. called++
  243. })
  244. EventHandler.trigger(div, 'click')
  245. EventHandler.off(div, 'click')
  246. EventHandler.trigger(div, 'click')
  247. setTimeout(() => {
  248. expect(called).toEqual(2)
  249. done()
  250. }, 20)
  251. })
  252. it('should remove the specified namespaced listeners for native events', done => {
  253. fixtureEl.innerHTML = '<div></div>'
  254. const div = fixtureEl.querySelector('div')
  255. let called1 = 0
  256. let called2 = 0
  257. EventHandler.on(div, 'click.namespace', () => {
  258. called1++
  259. })
  260. EventHandler.on(div, 'click.namespace2', () => {
  261. called2++
  262. })
  263. EventHandler.trigger(div, 'click')
  264. EventHandler.off(div, 'click.namespace')
  265. EventHandler.trigger(div, 'click')
  266. setTimeout(() => {
  267. expect(called1).toEqual(1)
  268. expect(called2).toEqual(2)
  269. done()
  270. }, 20)
  271. })
  272. it('should remove a listener registered by .one', done => {
  273. fixtureEl.innerHTML = '<div></div>'
  274. const div = fixtureEl.querySelector('div')
  275. const handler = () => {
  276. throw new Error('called')
  277. }
  278. EventHandler.one(div, 'foobar', handler)
  279. EventHandler.off(div, 'foobar', handler)
  280. EventHandler.trigger(div, 'foobar')
  281. setTimeout(() => {
  282. expect().nothing()
  283. done()
  284. }, 20)
  285. })
  286. it('should remove the correct delegated event listener', () => {
  287. const element = document.createElement('div')
  288. const subelement = document.createElement('span')
  289. element.appendChild(subelement)
  290. const anchor = document.createElement('a')
  291. element.appendChild(anchor)
  292. let i = 0
  293. const handler = () => {
  294. i++
  295. }
  296. EventHandler.on(element, 'click', 'a', handler)
  297. EventHandler.on(element, 'click', 'span', handler)
  298. fixtureEl.appendChild(element)
  299. EventHandler.trigger(anchor, 'click')
  300. EventHandler.trigger(subelement, 'click')
  301. // first listeners called
  302. expect(i).toEqual(2)
  303. EventHandler.off(element, 'click', 'span', handler)
  304. EventHandler.trigger(subelement, 'click')
  305. // removed listener not called
  306. expect(i).toEqual(2)
  307. EventHandler.trigger(anchor, 'click')
  308. // not removed listener called
  309. expect(i).toEqual(3)
  310. EventHandler.on(element, 'click', 'span', handler)
  311. EventHandler.trigger(anchor, 'click')
  312. EventHandler.trigger(subelement, 'click')
  313. // listener re-registered
  314. expect(i).toEqual(5)
  315. EventHandler.off(element, 'click', 'span')
  316. EventHandler.trigger(subelement, 'click')
  317. // listener removed again
  318. expect(i).toEqual(5)
  319. })
  320. })
  321. })