PageRenderTime 44ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/test/rdom/events_test.rb

https://gitlab.com/Blueprint-Marketing/rdom
Ruby | 500 lines | 347 code | 94 blank | 59 comment | 4 complexity | 6b3a04f1ad7bd41806e357e6e48fe8f0 MD5 | raw file
  1. require File.expand_path('../../test_helper', __FILE__)
  2. class EventsTest < Test::Unit::TestCase
  3. attr_reader :window, :document
  4. def setup
  5. @window = RDom::Window.new('<html><body><a id="foo" href="#"></a></body></html>')
  6. @document = window.document
  7. end
  8. # DOM-Level-2-Events
  9. # http://www.w3.org/TR/2000/REC-DOM-Level-2-Events-20001113/events.html
  10. # Each event has an EventTarget toward which the event is directed by the DOM
  11. # implementation. This EventTarget is specified in the Event's target
  12. # attribute.
  13. test "ruby: event target is the original target node, currentTarget is the currently processing node", :ruby do
  14. foo = document.getElementById('foo')
  15. targets, current_targets = [], []
  16. listener = lambda do |event|
  17. targets << event.target.nodeName
  18. current_targets << event.currentTarget.nodeName
  19. end
  20. document.addEventListener('click', listener)
  21. foo.addEventListener('click', listener)
  22. event = document.createEvent('MouseEvents').initEvent('click')
  23. foo.dispatchEvent(event)
  24. assert_equal %w(A A), targets
  25. assert_equal %w(A #document), current_targets
  26. end
  27. test "js: event target is the original target node, currentTarget is the currently processing node", :js do
  28. window.evaluate <<-js
  29. foo = document.getElementById('foo')
  30. targets = [], current_targets = []
  31. listener = {
  32. handleEvent: function(event) {
  33. targets.push(event.target.nodeName)
  34. current_targets.push(event.currentTarget.nodeName)
  35. return true
  36. }
  37. }
  38. document.addEventListener('click', listener)
  39. foo.addEventListener('click', listener)
  40. event = document.createEvent('MouseEvents').initEvent('click')
  41. foo.dispatchEvent(event)
  42. js
  43. assert_equal %w(A A), window.evaluate("targets").to_a
  44. assert_equal %w(A #document), window.evaluate("current_targets").to_a
  45. end
  46. # When the event reaches the target, any event listeners registered on the
  47. # EventTarget are triggered.
  48. #
  49. # A capturing EventListener will not be triggered by events dispatched
  50. # directly to the EventTarget upon which it is registered.
  51. test "ruby: event triggers multiple listeners on multiple targets during capturing, capturing not triggered on dispatching node", :ruby do
  52. foo = document.getElementById('foo')
  53. triggered = []
  54. document.addEventListener('click', lambda { triggered << 'doc:capture' }, true)
  55. document.addEventListener('click', lambda { triggered << 'doc:bubble' })
  56. foo.addEventListener('click', lambda { triggered << 'foo:capture' }, true)
  57. foo.addEventListener('click', lambda { triggered << 'foo:bubble' })
  58. event = document.createEvent('MouseEvents').initEvent('click')
  59. foo.dispatchEvent(event)
  60. assert_equal %w(doc:capture foo:bubble doc:bubble), triggered
  61. end
  62. test "js: event triggers multiple listeners on multiple targets during capturing, capturing not triggered on dispatching node", :js do
  63. window.evaluate <<-js
  64. foo = document.getElementById('foo')
  65. triggered = []
  66. listener = function(msg) {
  67. return { handleEvent: function(event) { triggered.push(msg); return true } };
  68. }
  69. document.addEventListener('click', listener('doc:capture'), true)
  70. document.addEventListener('click', listener('doc:bubble'))
  71. foo.addEventListener('click', listener('foo:capture'), true)
  72. foo.addEventListener('click', listener('foo:bubble'))
  73. event = document.createEvent('MouseEvents').initEvent('click')
  74. foo.dispatchEvent(event)
  75. js
  76. assert_equal %w(doc:capture foo:bubble doc:bubble), window.evaluate("triggered").to_a
  77. end
  78. # If neither event capture or event bubbling are in use for that particular
  79. # event, the event flow process will complete after all listeners have been
  80. # triggered.
  81. test 'ruby: an event initialized as not bubbling does not bubble', :ruby do
  82. foo = document.getElementById('foo')
  83. triggered = []
  84. foo.addEventListener('click', lambda { triggered << 'foo:bubble' }, false)
  85. document.addEventListener('click', lambda { triggered << 'doc:bubble' }, false)
  86. event = document.createEvent('MouseEvents').initEvent('click', false) # doesn't bubble
  87. foo.dispatchEvent(event)
  88. assert_equal %w(foo:bubble), triggered
  89. end
  90. test 'js: an event initialized as not bubbling does not bubble', :js do
  91. window.evaluate <<-js
  92. foo = document.getElementById('foo')
  93. triggered = []
  94. listener = function(msg) {
  95. return { handleEvent: function(event) { triggered.push(msg); return true } };
  96. }
  97. foo.addEventListener('click', listener('foo:bubble'), false)
  98. document.addEventListener('click', listener('doc:bubble'), false)
  99. event = document.createEvent('MouseEvents').initEvent('click', false)
  100. foo.dispatchEvent(event)
  101. js
  102. assert_equal %w(foo:bubble), window.evaluate("triggered").to_a
  103. end
  104. # Any exceptions thrown inside an EventListener will not stop propagation of
  105. # the event. It will continue processing any additional EventListener in the
  106. # described manner.
  107. # TODO
  108. # It is expected that actions taken by EventListeners may cause additional
  109. # events to fire. Additional events should be handled in a synchronous manner
  110. # and may cause reentrancy into the event model.
  111. test 'ruby: events can fire additional events and are handled in a synchronous manner', :ruby do
  112. foo = document.getElementById('foo')
  113. triggered = []
  114. over = document.createEvent('MouseEvents').initEvent('mouseOver')
  115. out = document.createEvent('MouseEvents').initEvent('mouseOut')
  116. foo.addEventListener('mouseOver', lambda { |event| triggered << event.type; foo.dispatchEvent(out) })
  117. foo.addEventListener('mouseOut', lambda { |event| triggered << event.type })
  118. foo.dispatchEvent(over)
  119. assert_equal %w(mouseOver mouseOut), triggered
  120. end
  121. test 'js: events can fire additional events and are handled in a synchronous manner', :js do
  122. window.evaluate <<-js
  123. foo = document.getElementById('foo')
  124. triggered = []
  125. over = document.createEvent('MouseEvents').initEvent('mouseOver')
  126. out = document.createEvent('MouseEvents').initEvent('mouseOut')
  127. foo.addEventListener('mouseOver', {
  128. handleEvent: function(event) {
  129. triggered.push(event.type)
  130. foo.dispatchEvent(out)
  131. return true
  132. }
  133. })
  134. foo.addEventListener('mouseOut', {
  135. handleEvent: function(event) {
  136. triggered.push(event.type);
  137. return true
  138. }
  139. })
  140. foo.dispatchEvent(over)
  141. js
  142. assert_equal %w(mouseOver mouseOut), window.evaluate("triggered").to_a
  143. end
  144. # If an EventListener is added to an EventTarget while it is processing an
  145. # event, it will not be triggered by the current actions but may be triggered
  146. # during a later stage of event flow, such as the bubbling phase.
  147. test 'ruby: listeners registered during dispatch are triggered when registered to a node dispatched later on', :ruby do
  148. foo = document.getElementById('foo')
  149. triggered = []
  150. listener = lambda { |event|
  151. triggered << 'doc:capturing'
  152. document.addEventListener('click', lambda { |event| triggered << 'does not happen' }, true)
  153. document.addEventListener('click', lambda { |event| triggered << 'doc:bubbling' })
  154. }
  155. document.addEventListener('click', listener, true)
  156. event = document.createEvent('MouseEvents').initEvent('click')
  157. foo.dispatchEvent(event)
  158. assert_equal %w(doc:capturing doc:bubbling), triggered
  159. end
  160. test 'js: listeners registered during dispatch are triggered when registered to a node dispatched later on', :js do
  161. window.evaluate <<-js
  162. foo = document.getElementById('foo')
  163. triggered = []
  164. listener = function(msg) {
  165. return { handleEvent: function(event) { triggered.push(msg); return true } };
  166. }
  167. document.addEventListener('click', {
  168. handleEvent: function(event) {
  169. triggered.push('doc:capturing')
  170. document.addEventListener('click', listener('does not happen'), true)
  171. document.addEventListener('click', listener('doc:bubbling'))
  172. return true
  173. }
  174. }, true)
  175. event = document.createEvent('MouseEvents').initEvent('click')
  176. foo.dispatchEvent(event)
  177. js
  178. assert_equal %w(doc:capturing doc:bubbling), window.evaluate("triggered").to_a
  179. end
  180. # If multiple identical EventListeners are registered on the same EventTarget
  181. # with the same parameters the duplicate instances are discarded. They do not
  182. # cause the EventListener to be called twice
  183. test 'ruby: a listener registered to the same target multiple times is only called once', :ruby do
  184. foo = document.getElementById('foo')
  185. triggered = []
  186. listener = lambda { |event| triggered << event.type }
  187. foo.addEventListener('click', listener)
  188. foo.addEventListener('click', listener)
  189. event = document.createEvent('MouseEvents').initEvent('click')
  190. foo.dispatchEvent(event)
  191. assert_equal %w(click), triggered
  192. end
  193. test 'js: a listener registered to the same target multiple times is only called once', :js do
  194. window.evaluate <<-js
  195. foo = document.getElementById('foo')
  196. triggered = []
  197. listener = { handleEvent: function(event) { triggered.push(event.type); return true } }
  198. foo.addEventListener('click', listener)
  199. foo.addEventListener('click', listener)
  200. event = document.createEvent('MouseEvents').initEvent('click')
  201. foo.dispatchEvent(event)
  202. js
  203. assert_equal %w(click), window.evaluate("triggered").to_a
  204. end
  205. # The chain of EventTargets from the top of the tree to the event's target is
  206. # determined before the initial dispatch of the event. If modifications occur
  207. # to the tree during event processing, event flow will proceed based on the
  208. # initial state of the tree.
  209. test "ruby: event targets for capturing are determined before the initial dispatch", :ruby do
  210. foo = document.getElementById('foo')
  211. triggered = []
  212. listener = lambda do |event|
  213. triggered << event.currentTarget.nodeName
  214. div = document.createElement('div')
  215. document.body.appendChild(div)
  216. div.appendChild(foo)
  217. div.addEventListener('click', lambda { triggered << event.currentTarget.nodeName }, true)
  218. end
  219. document.body.addEventListener('click', listener, true)
  220. event = document.createEvent('MouseEvents').initEvent('click')
  221. foo.dispatchEvent(event)
  222. assert_equal %w(BODY), triggered
  223. end
  224. test "js: event targets for capturing are determined before the initial dispatch", :js do
  225. window.evaluate <<-js
  226. foo = document.getElementById('foo')
  227. triggered = []
  228. document.body.addEventListener('click', {
  229. handleEvent: function(event) {
  230. triggered.push(event.currentTarget.nodeName)
  231. div = document.createElement('div')
  232. document.body.appendChild(div)
  233. div.appendChild(foo)
  234. div.addEventListener('click', { handleEvent: function(event) { triggered.push(event.currentTarget.nodeName) } }, true)
  235. }
  236. }, true)
  237. event = document.createEvent('MouseEvents').initEvent('click')
  238. foo.dispatchEvent(event)
  239. js
  240. assert_equal %w(BODY), window.evaluate("triggered").to_a
  241. end
  242. # The chain of EventTargets from the event target to the top of the tree is
  243. # determined before the initial dispatch of the event. If modifications occur
  244. # to the tree during event processing, event flow will proceed based on the
  245. # initial state of the tree.
  246. test "ruby: event targets for bubbling are determined before the initial dispatch", :ruby do
  247. foo = document.getElementById('foo')
  248. triggered = []
  249. listener = lambda do |event|
  250. triggered << event.currentTarget.nodeName
  251. div = document.createElement('div')
  252. document.body.appendChild(div)
  253. div.appendChild(foo)
  254. div.addEventListener('click', lambda { triggered << event.currentTarget.nodeName })
  255. end
  256. document.body.addEventListener('click', listener)
  257. event = document.createEvent('MouseEvents').initEvent('click')
  258. foo.dispatchEvent(event)
  259. assert_equal %w(BODY), triggered
  260. end
  261. test "js: event targets for bubbling are determined before the initial dispatch", :js do
  262. window.evaluate <<-js
  263. foo = document.getElementById('foo')
  264. triggered = []
  265. document.body.addEventListener('click', {
  266. handleEvent: function(event) {
  267. triggered.push(event.currentTarget.nodeName)
  268. div = document.createElement('div')
  269. document.body.appendChild(div)
  270. div.appendChild(foo)
  271. div.addEventListener('click', { handleEvent: function(event) { triggered.push(event.currentTarget.nodeName) } })
  272. }
  273. })
  274. event = document.createEvent('MouseEvents').initEvent('click')
  275. foo.dispatchEvent(event)
  276. js
  277. assert_equal %w(BODY), window.evaluate("triggered").to_a
  278. end
  279. # Both capturing and bubbling:
  280. # Any event handler may choose to prevent further event propagation by
  281. # calling the stopPropagation method of the Event interface. This will
  282. # prevent further dispatch of the event, although additional EventListeners
  283. # registered at the same hierarchy level will still receive the event.
  284. test 'ruby: stopping event propagation during capturing phase', :ruby do
  285. foo = document.getElementById('foo')
  286. triggered = []
  287. event = document.createEvent('MouseEvents').initEvent('click')
  288. document.addEventListener('click', lambda { triggered << 'doc:capture'; event.stopPropagation }, true)
  289. document.addEventListener('click', lambda { triggered << 'doc:bubble' })
  290. foo.addEventListener('click', lambda { triggered << 'body:capture' }, true)
  291. foo.addEventListener('click', lambda { triggered << 'body:bubble' })
  292. foo.dispatchEvent(event)
  293. assert_equal %w(doc:capture), triggered
  294. end
  295. test 'js: stopping event propagation during capturing phase', :js do
  296. window.evaluate <<-js
  297. foo = document.getElementById('foo')
  298. triggered = []
  299. listener = function(msg) {
  300. return { handleEvent: function(event) { triggered.push(msg); return true } };
  301. }
  302. event = document.createEvent('MouseEvents').initEvent('click')
  303. document.addEventListener('click', {
  304. handleEvent: function(event) { triggered.push('doc:capture'); event.stopPropagation(); return true }
  305. }, true)
  306. document.addEventListener('click', listener('doc:bubble'))
  307. foo.addEventListener('click', listener('body:capture'), true)
  308. foo.addEventListener('click', listener('body:bubble'))
  309. foo.dispatchEvent(event)
  310. js
  311. assert_equal %w(doc:capture), window.evaluate("triggered").to_a
  312. end
  313. test 'ruby: stopping event propagation during bubbling phase', :ruby do
  314. foo = document.getElementById('foo')
  315. triggered = []
  316. event = document.createEvent('MouseEvents').initEvent('click')
  317. document.addEventListener('click', lambda { triggered << 'doc:capture' }, true)
  318. document.addEventListener('click', lambda { triggered << 'doc:bubble' })
  319. foo.addEventListener('click', lambda { triggered << 'foo:bubble'; event.stopPropagation })
  320. foo.dispatchEvent(event)
  321. assert_equal %w(doc:capture foo:bubble), triggered
  322. end
  323. test 'js: stopping event propagation during bubbling phase', :js do
  324. window.evaluate <<-js
  325. foo = document.getElementById('foo')
  326. triggered = []
  327. listener = function(msg) {
  328. return { handleEvent: function(event) { triggered.push(msg); return true } };
  329. }
  330. event = document.createEvent('MouseEvents').initEvent('click')
  331. document.addEventListener('click', listener('doc:capture'), true)
  332. document.addEventListener('click', listener('doc:bubble'))
  333. foo.addEventListener('click', {
  334. handleEvent: function(event) { triggered.push('foo:bubble'); event.stopPropagation(); return true }
  335. })
  336. foo.dispatchEvent(event)
  337. js
  338. assert_equal %w(doc:capture foo:bubble), window.evaluate("triggered").to_a
  339. end
  340. # When a Node is copied using the cloneNode method the EventListeners attached
  341. # to the source Node are not attached to the copied Node.
  342. test 'ruby: cloneNode does not clone any attached listeners', :ruby do
  343. foo = document.getElementById('foo')
  344. triggered = []
  345. foo.addEventListener('click', lambda { |event| triggered << 'does not happen' })
  346. bar = foo.cloneNode(true)
  347. foo.parentNode.appendChild(bar)
  348. event = document.createEvent('MouseEvents').initEvent('click')
  349. bar.dispatchEvent(event)
  350. assert triggered.empty?
  351. end
  352. test 'js: cloneNode does not clone any attached listeners', :js do
  353. window.evaluate <<-js
  354. foo = document.getElementById('foo')
  355. triggered = []
  356. foo.addEventListener('click', { handleEvent: function(event) { triggered.push('does not happen'); return true } })
  357. bar = foo.cloneNode(true)
  358. foo.parentNode.appendChild(bar)
  359. event = document.createEvent('MouseEvents').initEvent('click')
  360. bar.dispatchEvent(event)
  361. js
  362. assert window.evaluate("triggered").to_a.empty?
  363. end
  364. # The return value of dispatchEvent indicates whether any of the listeners
  365. # which handled the event called preventDefault. If preventDefault was called
  366. # the value is false, else the value is true.
  367. test 'ruby: dispatchEvent returns true if event.preventDefault was called', :ruby do
  368. foo = document.getElementById('foo')
  369. event = document.createEvent('MouseEvents')
  370. event.initEvent('click', false)
  371. foo.addEventListener('click', lambda { })
  372. assert !foo.dispatchEvent(event)
  373. event = document.createEvent('MouseEvents')
  374. event.initEvent('click', false)
  375. document.body.addEventListener('click', lambda { |event| event.preventDefault })
  376. assert document.body.dispatchEvent(event)
  377. end
  378. test 'js: dispatchEvent returns true if event.preventDefault was called', :js do
  379. window.evaluate <<-js
  380. foo = document.getElementById('foo')
  381. event = document.createEvent('MouseEvents').initEvent('click', false)
  382. foo.addEventListener('click', { handleEvent: function() { } })
  383. js
  384. assert !window.evaluate("foo.dispatchEvent(event)")
  385. window.evaluate <<-js
  386. event = document.createEvent('MouseEvents').initEvent('click', false)
  387. document.body.addEventListener('click', { handleEvent: function(event) { event.preventDefault() } })
  388. js
  389. assert window.evaluate("document.body.dispatchEvent(event)")
  390. end
  391. # TODO
  392. # Some events are specified as cancelable. For these events, the DOM
  393. # implementation generally has a default action associated with the event.
  394. # (...) Listeners then have the option of canceling the implementation's
  395. # default action or allowing the default action to proceed. In the case of the
  396. # hyperlink in the browser, canceling the action would have the result of not
  397. # activating the hyperlink. Cancelation is accomplished by calling the Event's
  398. # preventDefault method. If one or more EventListeners call preventDefault
  399. # during any phase of event flow the default action will be canceled.
  400. # In order to achieve compatibility with HTML 4.0, implementors may view the
  401. # setting of attributes which represent event handlers as the creation and
  402. # registration of an EventListener on the EventTarget.
  403. # If the attribute representing the event listener is changed, this may be
  404. # viewed as the removal of the previously registered EventListener and the
  405. # registration of a new one.
  406. end