Module: Dommy::Interaction::DomSummary
- Defined in:
- lib/dommy/interaction/dom_summary.rb
Overview
Summarizes the user-facing controls of a DOM scope (a document or any
element with query_selector_all): the buttons, fields, links, and forms
a person would see. Engine-independent and scope-general — it backs the
finder's "available …" failure messages, the Debug facade, and failure
artifacts, and can be called directly on any document (e.g. a request
spec's dom).
Dommy::Interaction::DomSummary.(document)
#=> [{label: "Save", type: "submit", selector: "button[type=submit]"}]
puts Dommy::Interaction::DomSummary.to_text(document)
Class Method Summary collapse
-
.button_label(el) ⇒ Object
--- label / descriptor helpers ---.
-
.button_labels(scope) ⇒ Object
One-line descriptors for failure messages:
"Save" button[type=submit]. -
.buttons(scope) ⇒ Object
Submit-capable and plain buttons, as type:, selector:.
-
.field_label(el, scope) ⇒ Object
Prefer an associated
- .field_labels(scope) ⇒ Object
- .field_type(el) ⇒ Object
-
.fields(scope) ⇒ Object
Form fields, as name:, id:, type:, selector:.
- .form_method(form) ⇒ Object
-
.forms(scope) ⇒ Object
Forms, as method:, fields: [names].
- .hidden_input?(el) ⇒ Boolean
- .label_caption(el, scope) ⇒ Object
- .link_labels(scope) ⇒ Object
-
.links(scope) ⇒ Object
Links with an href, as href:.
- .presence(value) ⇒ Object
- .quote(value) ⇒ Object
- .selector_for(el) ⇒ Object
- .squish(text) ⇒ Object
-
.text(scope) ⇒ Object
Collapsed visible text of a scope.
-
.to_text(scope) ⇒ Object
A readable, sectioned summary of the scope's controls (used by the
Debugfacade and failure artifacts).
Class Method Details
.button_label(el) ⇒ Object
--- label / descriptor helpers ---
77 78 79 80 81 82 83 |
# File 'lib/dommy/interaction/dom_summary.rb', line 77 def (el) if el.tag_name == "INPUT" presence(el.get_attribute("value")) || presence(el.get_attribute("alt")) || "" else squish(el.text_content) end end |
.button_labels(scope) ⇒ Object
One-line descriptors for failure messages: "Save" button[type=submit].
57 |
# File 'lib/dommy/interaction/dom_summary.rb', line 57 def (scope) = (scope).map { |b| "#{quote(b[:label])} #{b[:selector]}".strip } |
.buttons(scope) ⇒ Object
Submit-capable and plain buttons, as type:, selector:.
19 20 21 22 23 24 25 |
# File 'lib/dommy/interaction/dom_summary.rb', line 19 def (scope) return [] unless scope scope.query_selector_all("button, input[type='submit'], input[type='image'], input[type='button'], input[type='reset']").map do |el| {label: (el), type: presence(el.get_attribute("type")), selector: selector_for(el)} end end |
.field_label(el, scope) ⇒ Object
Prefer an associated
86 87 88 89 90 |
# File 'lib/dommy/interaction/dom_summary.rb', line 86 def field_label(el, scope) label_caption(el, scope) || presence(el.get_attribute("aria-label")) || presence(el.get_attribute("placeholder")) || presence(el.get_attribute("name")) || presence(el.get_attribute("id")) end |
.field_labels(scope) ⇒ Object
58 |
# File 'lib/dommy/interaction/dom_summary.rb', line 58 def field_labels(scope) = fields(scope).map { |f| "#{quote(f[:label] || f[:name])} #{f[:selector]}".strip } |
.field_type(el) ⇒ Object
99 100 101 102 103 |
# File 'lib/dommy/interaction/dom_summary.rb', line 99 def field_type(el) return el.tag_name.downcase unless el.tag_name == "INPUT" presence(el.get_attribute("type")) || "text" end |
.fields(scope) ⇒ Object
Form fields, as name:, id:, type:, selector:.
28 29 30 31 32 33 34 35 |
# File 'lib/dommy/interaction/dom_summary.rb', line 28 def fields(scope) return [] unless scope scope.query_selector_all("input, textarea, select").reject { |el| hidden_input?(el) }.map do |el| {label: field_label(el, scope), name: presence(el.get_attribute("name")), id: presence(el.get_attribute("id")), type: field_type(el), selector: selector_for(el)} end end |
.form_method(form) ⇒ Object
125 |
# File 'lib/dommy/interaction/dom_summary.rb', line 125 def form_method(form) = presence(form.get_attribute("method"))&.downcase |
.forms(scope) ⇒ Object
Forms, as method:, fields: [names].
47 48 49 50 51 52 53 54 |
# File 'lib/dommy/interaction/dom_summary.rb', line 47 def forms(scope) return [] unless scope scope.query_selector_all("form").map do |form| {action: presence(form.get_attribute("action")), method: form_method(form), fields: fields(form).map { |f| f[:name] }.compact} end end |
.hidden_input?(el) ⇒ Boolean
124 |
# File 'lib/dommy/interaction/dom_summary.rb', line 124 def hidden_input?(el) = el.tag_name == "INPUT" && el.get_attribute("type") == "hidden" |
.label_caption(el, scope) ⇒ Object
92 93 94 95 96 97 |
# File 'lib/dommy/interaction/dom_summary.rb', line 92 def label_caption(el, scope) id = presence(el.get_attribute("id")) wrapping = el.respond_to?(:closest) ? el.closest("label") : nil label = (id && scope.query_selector_all("label").find { |l| l.get_attribute("for") == id }) || wrapping label ? presence(squish(label.text_content)) : nil end |
.link_labels(scope) ⇒ Object
59 |
# File 'lib/dommy/interaction/dom_summary.rb', line 59 def link_labels(scope) = links(scope).map { |l| "#{quote(l[:text])} -> #{l[:href]}" } |
.links(scope) ⇒ Object
Links with an href, as href:.
38 39 40 41 42 43 44 |
# File 'lib/dommy/interaction/dom_summary.rb', line 38 def links(scope) return [] unless scope scope.query_selector_all("a[href]").map do |el| {text: squish(el.text_content), href: el.get_attribute("href").to_s} end end |
.presence(value) ⇒ Object
127 |
# File 'lib/dommy/interaction/dom_summary.rb', line 127 def presence(value) = (value.nil? || value.to_s.empty?) ? nil : value |
.quote(value) ⇒ Object
128 |
# File 'lib/dommy/interaction/dom_summary.rb', line 128 def quote(value) = value.to_s.inspect |
.selector_for(el) ⇒ Object
105 106 107 108 109 110 111 112 113 |
# File 'lib/dommy/interaction/dom_summary.rb', line 105 def selector_for(el) tag = el.tag_name.downcase type = presence(el.get_attribute("type")) name = presence(el.get_attribute("name")) descriptor = tag descriptor += "[type=#{type}]" if type descriptor += "[name=#{name}]" if name descriptor end |
.squish(text) ⇒ Object
126 |
# File 'lib/dommy/interaction/dom_summary.rb', line 126 def squish(text) = text.to_s.gsub(/\s+/, " ").strip |
.text(scope) ⇒ Object
Collapsed visible text of a scope. A Document has no text_content of its own, so read its
.
117 118 119 120 121 122 |
# File 'lib/dommy/interaction/dom_summary.rb', line 117 def text(scope) return "" unless scope node = scope.respond_to?(:body) ? scope.body : scope squish(node&.text_content) end |
.to_text(scope) ⇒ Object
A readable, sectioned summary of the scope's controls (used by the
Debug facade and failure artifacts).
63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/dommy/interaction/dom_summary.rb', line 63 def to_text(scope) sections = { "Forms" => forms(scope).map { |f| "#{(f[:method] || "get").upcase} #{f[:action] || "(self)"} [#{f[:fields].join(", ")}]" }, "Links" => link_labels(scope), "Buttons" => (scope), "Fields" => field_labels(scope) } sections.reject { |_, rows| rows.empty? } .map { |name, rows| "#{name}:\n#{rows.map { |r| " #{r}" }.join("\n")}" } .join("\n") end |