Module: Dommy::Interaction::EventSynthesis
- Defined in:
- lib/dommy/interaction/event_synthesis.rb
Overview
Synthesizes and dispatches the DOM event sequences a real browser fires for user interactions, in the right order, so JS handlers (Stimulus actions, React synthetic events, …) run exactly as they would in a browser. Dispatch is Ruby-side; a JS-registered listener is invoked synchronously through the bridge (CallableInvoker → js_call).
Constant Summary collapse
- BUBBLES =
{"bubbles" => true, "cancelable" => true, "composed" => true}.freeze
- NAMED_KEYS =
Named keys for Driver#send_keys, mapped to their KeyboardEvent key/code pairs. Aliases (:up for :arrow_up, …) match Capybara's.
{ enter: ["Enter", "Enter"], tab: ["Tab", "Tab"], escape: ["Escape", "Escape"], space: [" ", "Space"], backspace: ["Backspace", "Backspace"], delete: ["Delete", "Delete"], arrow_up: ["ArrowUp", "ArrowUp"], arrow_down: ["ArrowDown", "ArrowDown"], arrow_left: ["ArrowLeft", "ArrowLeft"], arrow_right: ["ArrowRight", "ArrowRight"], up: ["ArrowUp", "ArrowUp"], down: ["ArrowDown", "ArrowDown"], left: ["ArrowLeft", "ArrowLeft"], right: ["ArrowRight", "ArrowRight"], home: ["Home", "Home"], end: ["End", "End"], page_up: ["PageUp", "PageUp"], page_down: ["PageDown", "PageDown"], }.freeze
Class Method Summary collapse
-
.beforeinput(element, data, input_type) ⇒ Object
beforeinput precedes the value mutation and is cancelable (the input event that follows the mutation is not).
- .blur(element) ⇒ Object
- .change(element) ⇒ Object
-
.char_code(char) ⇒ Object
The KeyboardEvent code for a typed character ("a" -> "KeyA").
-
.click(element) ⇒ Object
Full primary-button click: pointerdown → mousedown → focus → pointerup → mouseup → click.
-
.composition_input(element, data) ⇒ Object
The input pair during composition: beforeinput then input, both with inputType insertCompositionText and isComposing true.
- .compositionend(element, data) ⇒ Object
-
.compositionstart(element, data = "") ⇒ Object
IME composition events.
- .compositionupdate(element, data) ⇒ Object
- .dispatch(element, event) ⇒ Object
-
.focus(element) ⇒ Object
Run the element's focusing steps (Element#focus): moves document.activeElement and fires blur/focusout on the previously focused element plus focus/focusin here — a no-op when the element already holds focus, like a real browser.
- .input(element, data = nil, input_type = "insertText") ⇒ Object
- .key_init(key, code, extra = nil) ⇒ Object
-
.keydown(element, key, code, extra = nil) ⇒ Object
keydown is cancelable: a prevented keydown suppresses the key's default action (typing, implicit submission).
-
.keypress(element, key, code) ⇒ Object
keypress fires only for keys that produce a character (legacy but still widely handled).
- .keyup(element, key, code, extra = nil) ⇒ Object
- .mouse_init ⇒ Object
Class Method Details
.beforeinput(element, data, input_type) ⇒ Object
beforeinput precedes the value mutation and is cancelable (the input event that follows the mutation is not).
134 135 136 137 138 139 140 |
# File 'lib/dommy/interaction/event_synthesis.rb', line 134 def beforeinput(element, data, input_type) event = Dommy::InputEvent.new( "beforeinput", BUBBLES.merge("data" => data, "inputType" => input_type) ) element.dispatch_event(event) event.default_prevented? end |
.blur(element) ⇒ Object
44 45 46 47 |
# File 'lib/dommy/interaction/event_synthesis.rb', line 44 def blur(element) element.blur if element.respond_to?(:blur) nil end |
.change(element) ⇒ Object
53 54 55 |
# File 'lib/dommy/interaction/event_synthesis.rb', line 53 def change(element) dispatch(element, Dommy::Event.new("change", "bubbles" => true)) end |
.char_code(char) ⇒ Object
The KeyboardEvent code for a typed character ("a" -> "KeyA"). Best-effort: unknown characters get an empty code, like a real browser does for keys it can't map to a physical position.
145 146 147 148 149 150 151 152 |
# File 'lib/dommy/interaction/event_synthesis.rb', line 145 def char_code(char) case char when /\A[a-zA-Z]\z/ then "Key#{char.upcase}" when /\A[0-9]\z/ then "Digit#{char}" when " " then "Space" else "" end end |
.click(element) ⇒ Object
Full primary-button click: pointerdown → mousedown → focus → pointerup →
mouseup → click. Returns true when the click default was prevented (the
caller suppresses any follow-on navigation / submission).
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
# File 'lib/dommy/interaction/event_synthesis.rb', line 18 def click(element) dispatch(element, Dommy::PointerEvent.new("pointerdown", mouse_init)) dispatch(element, Dommy::MouseEvent.new("mousedown", mouse_init)) focus(element) dispatch(element, Dommy::PointerEvent.new("pointerup", mouse_init)) dispatch(element, Dommy::MouseEvent.new("mouseup", mouse_init)) event = Dommy::MouseEvent.new("click", mouse_init) element.dispatch_event(event) prevented = event.default_prevented? # Run the click's activation behavior (hyperlink navigation, …) the same # way Element#click does, so a synthetic/user click triggers the default # action too — unless it was prevented. Checkbox/radio toggling is handled # by the field interactor, not here (their activation_target is nil). element.__run_click_activation_behavior__(event) if !prevented && element.respond_to?(:__run_click_activation_behavior__) prevented end |
.composition_input(element, data) ⇒ Object
The input pair during composition: beforeinput then input, both with inputType insertCompositionText and isComposing true. Unlike ordinary typing, the composition beforeinput is NOT cancelable (spec).
125 126 127 128 129 130 |
# File 'lib/dommy/interaction/event_synthesis.rb', line 125 def composition_input(element, data) init = {"bubbles" => true, "composed" => true, "data" => data, "inputType" => "insertCompositionText", "isComposing" => true} dispatch(element, Dommy::InputEvent.new("beforeinput", init)) dispatch(element, Dommy::InputEvent.new("input", init)) end |
.compositionend(element, data) ⇒ Object
117 118 119 120 |
# File 'lib/dommy/interaction/event_synthesis.rb', line 117 def compositionend(element, data) dispatch(element, Dommy::CompositionEvent.new("compositionend", "bubbles" => true, "composed" => true, "data" => data)) end |
.compositionstart(element, data = "") ⇒ Object
IME composition events. compositionstart is cancelable per spec; update / end are not.
108 109 110 |
# File 'lib/dommy/interaction/event_synthesis.rb', line 108 def compositionstart(element, data = "") dispatch(element, Dommy::CompositionEvent.new("compositionstart", BUBBLES.merge("data" => data))) end |
.compositionupdate(element, data) ⇒ Object
112 113 114 115 |
# File 'lib/dommy/interaction/event_synthesis.rb', line 112 def compositionupdate(element, data) dispatch(element, Dommy::CompositionEvent.new("compositionupdate", "bubbles" => true, "composed" => true, "data" => data)) end |
.dispatch(element, event) ⇒ Object
57 58 59 |
# File 'lib/dommy/interaction/event_synthesis.rb', line 57 def dispatch(element, event) element.dispatch_event(event) end |
.focus(element) ⇒ Object
Run the element's focusing steps (Element#focus): moves document.activeElement and fires blur/focusout on the previously focused element plus focus/focusin here — a no-op when the element already holds focus, like a real browser.
39 40 41 42 |
# File 'lib/dommy/interaction/event_synthesis.rb', line 39 def focus(element) element.focus if element.respond_to?(:focus) nil end |
.input(element, data = nil, input_type = "insertText") ⇒ Object
49 50 51 |
# File 'lib/dommy/interaction/event_synthesis.rb', line 49 def input(element, data = nil, input_type = "insertText") dispatch(element, Dommy::InputEvent.new("input", BUBBLES.merge("data" => data, "inputType" => input_type))) end |
.key_init(key, code, extra = nil) ⇒ Object
154 155 156 157 |
# File 'lib/dommy/interaction/event_synthesis.rb', line 154 def key_init(key, code, extra = nil) init = BUBBLES.merge("key" => key, "code" => code) extra ? init.merge(extra) : init end |
.keydown(element, key, code, extra = nil) ⇒ Object
keydown is cancelable: a prevented keydown suppresses the key's default action (typing, implicit submission). Returns whether it was prevented, like #click.
87 88 89 90 91 |
# File 'lib/dommy/interaction/event_synthesis.rb', line 87 def keydown(element, key, code, extra = nil) event = Dommy::KeyboardEvent.new("keydown", key_init(key, code, extra)) element.dispatch_event(event) event.default_prevented? end |
.keypress(element, key, code) ⇒ Object
keypress fires only for keys that produce a character (legacy but still widely handled). Also cancelable; a prevented keypress suppresses the character insertion.
96 97 98 99 100 |
# File 'lib/dommy/interaction/event_synthesis.rb', line 96 def keypress(element, key, code) event = Dommy::KeyboardEvent.new("keypress", key_init(key, code)) element.dispatch_event(event) event.default_prevented? end |
.keyup(element, key, code, extra = nil) ⇒ Object
102 103 104 |
# File 'lib/dommy/interaction/event_synthesis.rb', line 102 def keyup(element, key, code, extra = nil) dispatch(element, Dommy::KeyboardEvent.new("keyup", key_init(key, code, extra))) end |
.mouse_init ⇒ Object
159 160 161 |
# File 'lib/dommy/interaction/event_synthesis.rb', line 159 def mouse_init BUBBLES.merge("button" => 0, "clientX" => 0, "clientY" => 0) end |