module Rails::Dom::Testing::Assertions::SelectorAssertions
def assert_dom(*args, &block)
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)
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)
...
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