module Rails::Dom::Testing::Assertions::SelectorAssertions

def assert_dom(*args, &block)

end
assert_dom ":match('name', ?)", /.+/ # Not empty
assert_dom "form input" do
# All input fields in the form have a name

assert_dom "ol>li:match('id', ?)", /item-\d+/
# Use substitution values

assert_dom "body div.header ul.menu"
# Test the content and style

assert_dom "form", false, "This page must contain no forms"
# Page contains no forms

"Wrong title or more than one title element"
assert_dom "title", {count: 1, text: "Welcome"},
# Page title is "Welcome" and there is only one title element

assert_dom "title", "Welcome"
# Page title is "Welcome"

assert_dom "form input", 4
# Form element includes four input fields

assert_dom "form"
# At least one form element

evaluated the block is called with an array of all matched elements.
If the method is called with a block, once all equality tests are

elements is at most this value.
* :maximum - Assertion is true if the number of selected
elements is at least this value.
* :minimum - Assertion is true if the number of selected
is equal to this value.
* :count - Assertion is true if the number of selected elements
content (string or regexp).
* :html - Narrow the selection to elements that have this HTML
value (string or regexp).
* :text - Narrow the selection to elements that have this text
To perform more than one equality tests, use a hash with the following keys:

element selected.
If no equality test specified, the assertion is true if at least one
elements fit the range.
* Range - Assertion is true if the number of selected
elements are selected.
* Integer - Assertion is true if exactly that number of
one element matches the string or regular expression.
* String/Regexp - Assertion is true if the text value of at least
* false - Assertion is true if no element selected.
* true - Assertion is true if at least one element selected.
The equality test may be one of the following:

=== Equality Tests

assert_dom "div:match('id', ?)", /\d+/
assert_dom "div:match('id', ?)", 1
assert_dom "div:match('id', ?)", :id_string
assert_dom "div:match('id', ?)", "id_string"

assert_dom returns nil if called with an invalid css selector.
Substitution uses a custom pseudo class match. Pass in whatever attribute you want to match (enclosed in quotes) and a ? for the substitution.
with substitution values (Array).
The selector may be a CSS selector expression (String, Symbol, or Numeric) or an expression

end
assert_dom "li", 8
assert_dom "ol" do
will pass, as will:

end
end
assert_dom element, "li", 4
elements.each do |element|
assert_dom "ol" do |elements|
If the response contains two ordered lists, each with four list elements then:
==== Example


separately for each element.
Alternatively the array may be iterated through so that +assert_dom+ can be called
runs the assertion on the complete set of elements selected by the enclosing assertion.
to the block. Calling +assert_dom+ from the block, with no element specified,
When called with a block +assert_dom+ passes an array of selected elements

The default implementation raises an exception explaining this.
Override +document_root_element+ to tell +assert_dom+ what to select from.
unless +assert_dom+ is called from within an +assert_dom+ block.
the element returned in +document_root_element+
If no element is specified +assert_dom+ selects from

depth-first order.
starting from (and including) that element and all its children in
If the first argument is an element, selects all matching elements

An assertion that selects elements and makes one or more equality tests.
def assert_dom(*args, &block)
  @selected ||= nil
  selector = HTMLSelector.new(args, @selected) { nodeset document_root_element }
  if selector.selecting_no_body?
    assert true
    return
  end
  selector.select.tap do |matches|
    assert_size_match!(matches.size, selector.tests,
      selector.css_selector, selector.message)
    nest_selection(matches, &block) if block_given? && !matches.empty?
  end
end

def assert_dom_email(html_version: nil, &block)


end
assert_dom "h1", "Email alert"
assert_dom_email(html_version: :html5) do

html_version: :html4 or html_version: :html5 keyword arguments:
If you want to specify the HTML parser just for a particular assertion, pass

+Rails.application.config.dom_testing_default_html_version+.
When testing in a Rails application, the parser default can also be set by setting

Rails::Dom::Testing.default_html_version (either :html4 or :html5).
The DOM is created using an HTML parser specified by

end
end
# Work with items here...
items.each do
items = assert_dom "ol>li"
assert_dom_email do

end
assert_dom "h1", "Email alert"
assert_dom_email do

Example usage:

ActionMailer::Base.perform_deliveries = true
You must enable deliveries for this assertion to work, use:

Extracts the body of an email and runs nested assertions on it.
def assert_dom_email(html_version: nil, &block)
  deliveries = ActionMailer::Base.deliveries
  assert !deliveries.empty?, "No e-mail in delivery list"
  deliveries.each do |delivery|
    (delivery.parts.empty? ? [delivery] : delivery.parts).each do |part|
      if /^text\/html\W/.match?(part["Content-Type"].to_s)
        root = Rails::Dom::Testing.html_document_fragment(html_version: html_version).parse(part.body.to_s)
        assert_dom root, ":root", &block
      end
    end
  end
end

def assert_dom_encoded(element = nil, html_version: nil, &block)


end
end
end
assert_dom "b"
assert_dom_encoded(html_version: :html5) do
assert_dom "entry>title" do
assert_dom "feed[xmlns='http://www.w3.org/2005/Atom']" do

html_version: :html4 or html_version: :html5 keyword arguments:
If you want to specify the HTML parser just for a particular assertion, pass

+Rails.application.config.dom_testing_default_html_version+.
When testing in a Rails application, the parser default can also be set by setting

Rails::Dom::Testing.default_html_version (either :html4 or :html5).
The DOM is created using an HTML parser specified by

end
end
end
assert_dom "p"
assert_dom_encoded do
# Run assertions on the encoded elements.
assert_dom "channel>item>description" do
# Select description element of each feed item.
assert_dom "rss[version=2.0]" do
# Selects all paragraph tags from within the description of an RSS feed


end
end
end
assert_dom "b"
assert_dom_encoded do
# Run assertions on the encoded title elements
assert_dom "entry>title" do
# Select each entry item and then the title item
assert_dom "feed[xmlns='http://www.w3.org/2005/Atom']" do
# Selects all bold tags from within the title of an Atom feed's entries (perhaps to nab a section name prefix)

element +encoded+. It then calls the block with all un-encoded elements.
The content of each element is un-encoded, and wrapped in the root

of elements.
all currently selected elements. You can also pass an element or array
You typically call this method within another assertion to operate on

nested assertion on it.
Extracts the content of an element, treats it as encoded HTML and runs
def assert_dom_encoded(element = nil, html_version: nil, &block)
  if !element && !@selected
    raise ArgumentError, "Element is required when called from a nonnested assert_dom"
  end
  content = nodeset(element || @selected).map do |elem|
    elem.children.select do |child|
      child.cdata? || (child.text? && !child.blank?)
    end.map(&:content)
  end.join
  selected = Rails::Dom::Testing.html_document_fragment(html_version: html_version).parse(content)
  nest_selection(selected) do
    if content.empty?
      yield selected
    else
      assert_dom ":root", &block
    end
  end
end

def assert_size_match!(size, equals, css_selector, message = nil)

+equals+ must contain :minimum, :maximum and :count keys
def assert_size_match!(size, equals, css_selector, message = nil)
  min, max, count = equals[:minimum], equals[:maximum], equals[:count]
  message ||= %(Expected #{count_description(min, max, count)} matching #{css_selector.inspect}, found #{size})
  if count
    assert_equal count, size, message
  else
    assert_operator size, :>=, min, message if min
    assert_operator size, :<=, max, message if max
  end
end

def count_description(min, max, count)

def count_description(min, max, count)
  if min && max && (max != min)
    "between #{min} and #{max} elements"
  elsif min && max && max == min && count
    "exactly #{count} #{pluralize_element(min)}"
  elsif min && !(min == 1 && max == 1)
    "at least #{min} #{pluralize_element(min)}"
  elsif max
    "at most #{max} #{pluralize_element(max)}"
  end
end

def css_select(*args)

end
...
inputs = css_select(form, "input")
forms.each do |form|
forms = css_select("form")
# Selects all form tags and then all inputs inside the form

items = css_select("ul>li")
# Selects all list items in unordered lists

end
# Do something fun with paragraphs here...
pars.each do |par|
pars = css_select("p")
# Selects all paragraph tags and does something interesting

divs = css_select("div")
# Selects all div tags

css_select returns nil if called with an invalid css selector.
The selector may be a CSS selector expression (String).

Returns an empty Nokogiri::XML::NodeSet if no match is found.
root element and any of its children.
element and the second argument as the selector. Attempts to match the
If called with two arguments, uses the first argument as the root

Returns an empty Nokogiri::XML::NodeSet if no match is found.

The default implementation of +document_root_element+ raises an exception explaining this.

the element returned in +document_root_element+
Called without an element +css_select+ selects from
If called with a single argument, uses that argument as a selector.

Select and return all matching elements.
def css_select(*args)
  raise ArgumentError, "you at least need a selector argument" if args.empty?
  root = args.size == 1 ? document_root_element : args.shift
  nodeset(root).css(args.first)
end

def document_root_element

def document_root_element
  raise NotImplementedError, "Implementing document_root_element makes " \
    "assert_dom work without needing to specify an element to select from."
end

def nest_selection(selection)

def nest_selection(selection)
  # Set @selected to allow nested assert_dom.
  # Can be nested several levels deep.
  old_selected, @selected = @selected, selection
  yield @selected
ensure
  @selected = old_selected
end

def nodeset(node)

def nodeset(node)
  if node.is_a?(Nokogiri::XML::NodeSet)
    node
  else
    node ||= Nokogiri::HTML::Document.new
    Nokogiri::XML::NodeSet.new(node.document, [node])
  end
end

def pluralize_element(quantity)

def pluralize_element(quantity)
  quantity == 1 ? "element" : "elements"
end