Module: Dommy::Internal::DomMatching

Defined in:
lib/dommy/internal/dom_matching.rb

Overview

Shared matching primitives used by both RSpec matchers and Minitest assertions. Centralizes selector / text / count interpretation so the two frameworks behave identically.

Class Method Summary collapse

Class Method Details

.count_matches?(actual, expected) ⇒ Boolean

Parameters:

  • actual (Integer)
  • expected (Integer, Range, nil)

    — nil means “at least one”

Returns:

  • (Boolean)


41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/dommy/internal/dom_matching.rb', line 41

def count_matches?(actual, expected)
  case expected
  when nil
    actual.positive?
  when Integer
    actual == expected
  when Range
    expected.cover?(actual)
  else
    false
  end
end

.filter(scope, selector, text: nil) ⇒ Array<Dommy::Element>

Find elements in scope matching selector, optionally filtered by text content.

Parameters:

  • scope (#query_selector_all)

    Document / Element / ShadowRoot / Fragment

  • selector (String)
  • text (String, Regexp, nil) (defaults to: nil)

Returns:



18
19
20
21
22
23
# File 'lib/dommy/internal/dom_matching.rb', line 18

def filter(scope, selector, text: nil)
  elements = scope.query_selector_all(selector).to_a
  return elements if text.nil?

  elements.select { |el| text_matches?(el.text_content, text) }
end

.filter_by_visibility(elements, visible) ⇒ Object

Filter elements by Capybara-style :visible option.

Parameters:

  • elements (Array)
  • visible (:visible, :all, :hidden, true, false, nil)


110
111
112
113
114
115
116
117
118
119
# File 'lib/dommy/internal/dom_matching.rb', line 110

def filter_by_visibility(elements, visible)
  case visible
  when nil, :all, false
    elements
  when :hidden
    elements.reject { |el| visible?(el) }
  else
    elements.select { |el| visible?(el) }
  end
end

.html_of(scope) ⇒ Object

Get the inner_html of a scope, falling back to body for Document.



76
77
78
79
80
81
82
83
84
# File 'lib/dommy/internal/dom_matching.rb', line 76

def html_of(scope)
  if scope.respond_to?(:inner_html)
    scope.inner_html.to_s
  elsif scope.respond_to?(:body) && scope.body
    scope.body.inner_html.to_s
  else
    scope.to_s
  end
end

.normalize_html(html) ⇒ Object

Normalize an HTML string for structural comparison. Re-parses through Nokogiri and re-serializes, which collapses whitespace differences and attribute ordering quirks.

Parameters:

  • html (String)


59
60
61
# File 'lib/dommy/internal/dom_matching.rb', line 59

def normalize_html(html)
  Nokogiri::HTML5.fragment(html.to_s).to_html.gsub(/\s+/, " ").strip
end

.text_matches?(actual, expected, exact: false) ⇒ Boolean

Parameters:

  • actual (String)
  • expected (String, Regexp)
  • exact (Boolean) (defaults to: false)

    when true, require exact equality (string) or full-string regexp match.

Returns:

  • (Boolean)


29
30
31
32
33
34
35
36
37
# File 'lib/dommy/internal/dom_matching.rb', line 29

def text_matches?(actual, expected, exact: false)
  actual = actual.to_s
  case expected
  when Regexp
    exact ? actual.match?(expected) && actual == actual[expected] : actual.match?(expected)
  else
    exact ? actual.strip == expected.to_s : actual.include?(expected.to_s)
  end
end

.text_of(scope) ⇒ Object

Get the text_content of a scope, handling Document (which has no text_content directly — its body does).



65
66
67
68
69
70
71
72
73
# File 'lib/dommy/internal/dom_matching.rb', line 65

def text_of(scope)
  if scope.respond_to?(:text_content)
    scope.text_content.to_s
  elsif scope.respond_to?(:body) && scope.body
    scope.body.text_content.to_s
  else
    scope.to_s
  end
end

.visible?(element) ⇒ Boolean

Best-effort visibility check using HTML-level signals only. Does NOT evaluate CSS stylesheets — ‘display: none` via class is NOT detected. See README for details and workarounds.

Detects: ‘hidden` attribute, `<input type=hidden>`, non-rendering ancestors (head/script/style/template), inline `display:none` / `visibility:hidden` on element or any ancestor.

Returns:

  • (Boolean)


93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/dommy/internal/dom_matching.rb', line 93

def visible?(element)
  return true unless element.respond_to?(:__node__)

  node = element.__node__
  return false if node_invisible_self?(node)

  NodeTraversal.each_ancestor(node) do |ancestor|
    return false if non_rendering_tag?(ancestor)
    return false if node_invisible_self?(ancestor)
  end

  true
end