модуль ActionDispatch::Assertions::SelectorAssertions
Добавляет метод assert_select для использования в функциональных тестах Rails, который может быть использован для утверждений о HTML-ответе действия контроллера. Вы также можете вызвать assert_select внутри другого assert_select, чтобы сделать утверждения об элементах, выбранных вложенным утверждением.
Используйте css_select для выбора элементов без утверждений, как из ответа HTML, так и из элементов, выбранных вложенным утверждением.
В дополнение к ответам в формате HTML вы можете сделать следующие утверждения:
-
assert_select_encoded— Утверждения на HTML, закодированном внутри XML, например, для работы с описаниями элементов ленты. -
assert_select_email— Утверждения на теле HTML-письма.
Также см. HTML::Selector, чтобы узнать, как использовать селекторы.
Публичные методы экземпляра
Утверждение, которое выбирает элементы и выполняет одно или несколько проверок равенства.
Если первый аргумент — элемент, выбирает все соответствующие элементы, начиная с (и включая) этот элемент и все его дочерние элементы в порядке обхода в глубину.
Если элемент не указан, вызов assert_select выбирает из HTML-ответа, если assert_select не вызывается внутри блока assert_select.
При вызове с блоком assert_select передаёт массив выбранных элементов в блок. Вызов assert_select из блока без указания элемента выполняет утверждение по всему набору элементов, выбранных вложенным утверждением. В качестве альтернативы, массив можно итерировать, чтобы assert_select можно было вызвать отдельно для каждого элемента.
Пример
Если ответ содержит два упорядоченных списка, каждый из которых содержит четыре элемента списка, то:
assert_select "ol" do |elements|
elements.each do |element|
assert_select element, "li", 4
end
end
будет выполнено, так же как и:
assert_select "ol" do assert_select "li", 8 end
Селектор может быть выражением селектора CSS (строка), выражением со значениями подстановки или объектом HTML::Selector.
Проверки равенства
Проверка равенства может быть одной из следующих:
-
true— Утверждение истинно, если выбрано хотя бы один элемент. -
false— Утверждение истинно, если не выбрано ни одного элемента. -
String/Regexp— Утверждение истинно, если текстовое значение хотя бы одного элемента соответствует строке или регулярному выражению. -
Integer— Утверждение истинно, если выбрано ровно указанное количество элементов. -
Range— Утверждение истинно, если количество выбранных элементов соответствует указанному диапазону.
Если проверка равенства не указана, утверждение истинно, если выбрано хотя бы один элемент.
Для выполнения нескольких проверок равенства используйте хеш со следующими ключами:
-
:text— Ограничить выбор элементами, имеющими это текстовое значение (строка или регулярное выражение). -
:html— Ограничить выбор элементами, содержащими это содержимое HTML (строка или регулярное выражение). -
:count— Утверждение истинно, если количество выбранных элементов равно этому значению. -
:minimum— Утверждение истинно, если количество выбранных элементов не меньше этого значения. -
:maximum— Утверждение истинно, если количество выбранных элементов не больше этого значения.
Если метод вызывается с блоком, после оценки всех проверок равенства вызывается блок с массивом всех совпавших элементов.
# At least one form element
assert_select "form"
# Form element includes four input fields
assert_select "form input", 4
# Page title is "Welcome"
assert_select "title", "Welcome"
# Page title is "Welcome" and there is only one title element
assert_select "title", {count: 1, text: "Welcome"},
"Wrong title or more than one title element"
# Page contains no forms
assert_select "form", false, "This page must contain no forms"
# Test the content and style
assert_select "body div.header ul.menu"
# Use substitution values
assert_select "ol>li#?", /item-\d+/
# All input fields in the form have a name
assert_select "form input" do
assert_select "[name=?]", /.+/ # Not empty
end
# File actionpack/lib/action_dispatch/testing/assertions/selector.rb, line 184
def assert_select(*args, &block)
# Start with optional element followed by mandatory selector.
arg = args.shift
@selected ||= nil
if arg.is_a?(HTML::Node)
# First argument is a node (tag or text, but also HTML root),
# so we know what we're selecting from.
root = arg
arg = args.shift
elsif arg == nil
# This usually happens when passing a node/element that
# happens to be nil.
raise ArgumentError, "First argument is either selector or element to select, but nil found. Perhaps you called assert_select with an element that does not exist?"
elsif @selected
root = HTML::Node.new(nil)
root.children.concat @selected
else
# Otherwise just operate on the response document.
root = response_from_page
end
# First or second argument is the selector: string and we pass
# all remaining arguments. Array and we pass the argument. Also
# accepts selector itself.
case arg
when String
selector = HTML::Selector.new(arg, args)
when Array
selector = HTML::Selector.new(*arg)
when HTML::Selector
selector = arg
else raise ArgumentError, "Expecting a selector as the first argument"
end
# Next argument is used for equality tests.
equals = {}
case arg = args.shift
when Hash
equals = arg
when String, Regexp
equals[:text] = arg
when Integer
equals[:count] = arg
when Range
equals[:minimum] = arg.begin
equals[:maximum] = arg.end
when FalseClass
equals[:count] = 0
when NilClass, TrueClass
equals[:minimum] = 1
else raise ArgumentError, "I don't understand what you're trying to match"
end
# By default we're looking for at least one match.
if equals[:count]
equals[:minimum] = equals[:maximum] = equals[:count]
else
equals[:minimum] = 1 unless equals[:minimum]
end
# Last argument is the message we use if the assertion fails.
message = args.shift
#- message = "No match made with selector #{selector.inspect}" unless message
if args.shift
raise ArgumentError, "Not expecting that last argument, you either have too many arguments, or they're the wrong type"
end
matches = selector.select(root)
# If text/html, narrow down to those elements that match it.
content_mismatch = nil
if match_with = equals[:text]
matches.delete_if do |match|
text = ""
stack = match.children.reverse
while node = stack.pop
if node.tag?
stack.concat node.children.reverse
else
content = node.content
text << content
end
end
text.strip! unless NO_STRIP.include?(match.name)
text.sub!(/\A\n/, '') if match.name == "textarea"
unless match_with.is_a?(Regexp) ? (text =~ match_with) : (text == match_with.to_s)
content_mismatch ||= sprintf("<%s> expected but was\n<%s>", match_with, text)
true
end
end
elsif match_with = equals[:html]
matches.delete_if do |match|
html = match.children.map(&:to_s).join
html.strip! unless NO_STRIP.include?(match.name)
unless match_with.is_a?(Regexp) ? (html =~ match_with) : (html == match_with.to_s)
content_mismatch ||= sprintf("<%s> expected but was\n<%s>", match_with, html)
true
end
end
end
# Expecting foo found bar element only if found zero, not if
# found one but expecting two.
message ||= content_mismatch if matches.empty?
# Test minimum/maximum occurrence.
min, max, count = equals[:minimum], equals[:maximum], equals[:count]
# FIXME: minitest provides messaging when we use assert_operator,
# so is this custom message really needed?
message = message || %Q(Expected #{count_description(min, max, count)} matching "#{selector.to_s}", found #{matches.size})
if count
assert_equal count, matches.size, message
else
assert_operator matches.size, :>=, min, message if min
assert_operator matches.size, :<=, max, message if max
end
# If a block is given call that block. Set @selected to allow
# nested assert_select, which can be nested several levels deep.
if block_given? && !matches.empty?
begin
in_scope, @selected = @selected, matches
yield matches
ensure
@selected = in_scope
end
end
# Returns all matches elements.
matches
end Извлекает тело письма и выполняет вложенные утверждения на нём.
Для работы этого утверждения необходимо включить доставку, используйте:
ActionMailer::Base.perform_deliveries = true
assert_select_email do
assert_select "h1", "Email alert"
end
assert_select_email do
items = assert_select "ol>li"
items.each do
# Work with items here...
end
end
# File actionpack/lib/action_dispatch/testing/assertions/selector.rb, line 409
def assert_select_email(&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 part["Content-Type"].to_s =~ /^text\/html\W/
root = HTML::Document.new(part.body.to_s).root
assert_select root, ":root", &block
end
end
end
end Извлекает содержимое элемента, обрабатывает его как закодированный HTML и выполняет на нём вложенное утверждение.
Обычно этот метод вызывается внутри другого утверждения для работы со всеми текущими выбранными элементами. Вы также можете передать элемент или массив элементов.
Содержимое каждого элемента раскодируется и оборачивается в корневой элемент encoded. Затем вызывается блок со всеми раскодированными элементами.
# Selects all bold tags from within the title of an Atom feed's entries (perhaps to nab a section name prefix)
assert_select "feed[xmlns='http://www.w3.org/2005/Atom']" do
# Select each entry item and then the title item
assert_select "entry>title" do
# Run assertions on the encoded title elements
assert_select_encoded do
assert_select "b"
end
end
end
# Selects all paragraph tags from within the description of an RSS feed
assert_select "rss[version=2.0]" do
# Select description element of each feed item.
assert_select "channel>item>description" do
# Run assertions on the encoded elements.
assert_select_encoded do
assert_select "p"
end
end
end
# File actionpack/lib/action_dispatch/testing/assertions/selector.rb, line 361
def assert_select_encoded(element = nil, &block)
case element
when Array
elements = element
when HTML::Node
elements = [element]
when nil
unless elements = @selected
raise ArgumentError, "First argument is optional, but must be called from a nested assert_select"
end
else
raise ArgumentError, "Argument is optional, and may be node or array of nodes"
end
fix_content = lambda do |node|
# Gets around a bug in the Rails 1.1 HTML parser.
node.content.gsub(/<!\[CDATA\[(.*)(\]\]>)?/m) { Rack::Utils.escapeHTML($1) }
end
selected = elements.map do |elem|
text = elem.children.select{ |c| not c.tag? }.map{ |c| fix_content[c] }.join
root = HTML::Document.new(CGI.unescapeHTML("<encoded>#{text}</encoded>")).root
css_select(root, "encoded:root", &block)[0]
end
begin
old_selected, @selected = @selected, selected
assert_select ":root", &block
ensure
@selected = old_selected
end
end Выбирает и возвращает все соответствующие элементы.
Если вызвана с одним аргументом, использует этот аргумент в качестве селектора для сопоставления всех элементов текущей страницы. Возвращает пустой массив, если совпадений не найдено.
Если вызвана с двумя аргументами, использует первый аргумент в качестве базового элемента, а второй — в качестве селектора. Попытка сопоставления с базовым элементом и любыми его дочерними элементами. Возвращает пустой массив, если совпадений не найдено.
Селектор может быть выражением селектора CSS (строка), выражением со значениями подстановки (массив) или объектом HTML::Selector.
# Selects all div tags
divs = css_select("div")
# Selects all paragraph tags and does something interesting
pars = css_select("p")
pars.each do |par|
# Do something fun with paragraphs here...
end
# Selects all list items in unordered lists
items = css_select("ul>li")
# Selects all form tags and then all inputs inside the form
forms = css_select("form")
forms.each do |form|
inputs = css_select(form, "input")
...
end # File actionpack/lib/action_dispatch/testing/assertions/selector.rb, line 60
def css_select(*args)
# See assert_select to understand what's going on here.
arg = args.shift
if arg.is_a?(HTML::Node)
root = arg
arg = args.shift
elsif arg == nil
raise ArgumentError, "First argument is either selector or element to select, but nil found. Perhaps you called assert_select with an element that does not exist?"
elsif defined?(@selected) && @selected
matches = []
@selected.each do |selected|
subset = css_select(selected, HTML::Selector.new(arg.dup, args.dup))
subset.each do |match|
matches << match unless matches.any? { |m| m.equal?(match) }
end
end
return matches
else
root = response_from_page
end
case arg
when String
selector = HTML::Selector.new(arg, args)
when Array
selector = HTML::Selector.new(*arg)
when HTML::Selector
selector = arg
else raise ArgumentError, "Expecting a selector as the first argument"
end
selector.select(root)
end Защищённые методы экземпляра
assert_select и css_select вызывают это для получения содержимого страницы HTML.
# File actionpack/lib/action_dispatch/testing/assertions/selector.rb, line 425 def response_from_page html_document.root end
© 2004–2016 David Heinemeier Hansson
Licensed under the MIT License.