Class: RSpec::TagMatchers::HasTag
- Inherits:
-
Object
- Object
- RSpec::TagMatchers::HasTag
- Defined in:
- lib/rspec/tag_matchers/has_tag.rb
Overview
The base class for all tag matchers. HasTag is intended to provide facilities that are useful to subclasses. The subclasses of HasTag should define more expressive tag matchers. For example, to match a checkbox using HasTag directly, one would have to type:
it { has_tag(:input).with_attribute(:type => :checkbox) }
That can all be encapsulated into a subclass that provides a more expressive matcher:
it { has_checkbox }
Element Matching
The way tag matchers work is by counting how many elements match a set of filters. It starts by finding element that match the given tag name, e.g., a
, div
, or input
, and then filters the list of matching elements according to a list of criteria. In the end, the matcher is left with a set of elements that match all criteria. The matcher is said to match the input string if the set of elements that match all its criteria contains at least one element. The matched elements need not be the top-level element.
Example
In this example, the matcher looks for div
elements with and id
of “foo” and a class of “bar”. An element must match all three of those criteria in order for the matcher to consider it a successful match:
matcher = HasTag.new(:div).with_attribute(:id => :foo, :class => :bar)
matcher.matches?('<div id="foo" class="bar"></div>') # => true
matcher.matches?('<div id="foo"></div>') # => false
However, the all criteria must be matched by <strong>the same</strong> element. If one element matches half of the criteria and another element matches the other half of the criteria, it is not considered a successful match:
matcher = HasTag.new(:div).with_attribute(:id => :foo, :class => :bar)
matcher.matches?('<div id="foo"></div><div class="bar"></div>') # => false
Subclassing HasTag
In the most basic case, a subclass of HasTag will simply override the constructor to provide a default tag name that must be matched. For example, a matcher to match object
tags might look like this:
class HasObject < HasTag
def initialize
super(:object)
end
end
Also, one should provide a helper method to construct the matcher inside of a spec. For the above example, the helper method would look like this:
def have_object
HasObject.new
end
This allows the user to construct a HasObject matcher by calling have_object
in his spec, which provides for a more readable spec:
it { should have_object }
In some cases it might make sense to add additional criteria from within the constructor or to provide additional methods that can be chained from the matcher to provide tag-specific criteria. See #with_criteria for how to add custom criteria to a matcher.
Instance Method Summary collapse
-
#description ⇒ String
Returns a description of the matcher’s criteria.
-
#failure_message ⇒ String
Returns an explanation of why the matcher failed to match with
should
. -
#initialize(name) ⇒ HasTag
constructor
Constructs a matcher that matches HTML tags by
name
. -
#matches?(rendered) ⇒ Boolean
Answers whether or not the matcher matches any elements within
rendered
. -
#negative_failure_message ⇒ String
Returns an explanation of why the matcher failed to match with
should_not
. -
#test_attribute(attribute, expected) ⇒ Boolean
protected
Tests with
attribute
matchesexpected
according the the attribute matching rules described in #with_attribute. -
#with_attribute(attributes) ⇒ self
(also: #with_attributes)
Adds a constraint that the matched elements must match certain attributes.
-
#with_content(content) ⇒ self
Adds a constraint on the matched element’s content.
-
#with_count(count) ⇒ self
Adds a constraint that the matched elements appear a given number of times.
-
#with_criteria(method = nil, &block) ⇒ self
Adds an arbitrary criteria to the matched elements.
Methods included from RSpec::TagMatchers::Helpers::SentenceHelper
Constructor Details
#initialize(name) ⇒ HasTag
Constructs a matcher that matches HTML tags by name
.
107 108 109 110 111 |
# File 'lib/rspec/tag_matchers/has_tag.rb', line 107 def initialize(name) @name = name.to_s @attributes = {} @criteria = [] end |
Instance Method Details
#description ⇒ String
Returns a description of the matcher’s criteria. The description is used in RSpec’s output.
116 117 118 |
# File 'lib/rspec/tag_matchers/has_tag.rb', line 116 def description "have #{@name.inspect} tag #{extra_description}".strip end |
#failure_message ⇒ String
Returns an explanation of why the matcher failed to match with should
.
123 124 125 |
# File 'lib/rspec/tag_matchers/has_tag.rb', line 123 def "expected document to #{description}; got: #{@rendered}" end |
#matches?(rendered) ⇒ Boolean
Answers whether or not the matcher matches any elements within rendered
.
140 141 142 143 144 145 146 |
# File 'lib/rspec/tag_matchers/has_tag.rb', line 140 def matches?(rendered) @rendered = rendered matches = Nokogiri::HTML::Document.parse(@rendered.to_s).css(@name).select do |element| matches_attributes?(element) && matches_content?(element) && matches_criteria?(element) end matches_count?(matches) end |
#negative_failure_message ⇒ String
Returns an explanation of why the matcher failed to match with should_not
.
130 131 132 |
# File 'lib/rspec/tag_matchers/has_tag.rb', line 130 def "expected document to not #{description}; got: #{@rendered}" end |
#test_attribute(attribute, expected) ⇒ Boolean (protected)
The reason this method receives a Nokogiri::XML::Attr
object instead of the attribute’s value is that some attributes have meaning merely by existing, even if they don’t have a value. For example, the checked
attribute of a checkbox or radio button does not need to have a value. If it doesn’t have a value, element[:checked]
will return nil
in the JRuby implementation of Nokogiri, which would make with_attribute(:checked => true)
fail in JRuby.
Tests with attribute
matches expected
according the the attribute matching rules described in #with_attribute. This can be useful for testing attributes in subclasses.
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 |
# File 'lib/rspec/tag_matchers/has_tag.rb', line 233 def test_attribute(attribute, expected) actual = attribute && attribute.value case expected when String actual == expected when Symbol actual =~ /^#{expected}$/i when Regexp actual =~ expected when true !attribute.nil? when false attribute.nil? end end |
#with_attribute(attributes) ⇒ self Also known as: with_attributes
Adds a constraint that the matched elements must match certain attributes. The attributes
hash contains a set of key/value pairs. Each key is used for the attribute name (not case sensitive) and the value is used to determine if an attribute matches according to the rules in the following table:
- Attribute Matching Values
- String
-
The attribute’s value must match exactly.
- Symbol
-
The attribute’s value must match, but it is not case sensitive.
- Regexp
-
The attribute’s value must match the regular expression.
- true
-
The attribute must exist. The attribute’s value does not matter.
- false
-
The attribute must not exist.
164 165 166 167 |
# File 'lib/rspec/tag_matchers/has_tag.rb', line 164 def with_attribute(attributes) @attributes.merge!(attributes) self end |
#with_content(content) ⇒ self
Adds a constraint on the matched element’s content.
179 180 181 182 |
# File 'lib/rspec/tag_matchers/has_tag.rb', line 179 def with_content(content) @content = content self end |
#with_count(count) ⇒ self
Adds a constraint that the matched elements appear a given number of times. The criteria must be a Fixnum
209 210 211 212 |
# File 'lib/rspec/tag_matchers/has_tag.rb', line 209 def with_count(count) @count = count self end |
#with_criteria(method = nil, &block) ⇒ self
Adds an arbitrary criteria to the matched elements. The criteria can be a method name or a block. The method or block should accept a single Nokogiri::XML::Node object as its argument and return whether or not the element passed as an argument matches the criteria.
195 196 197 198 199 |
# File 'lib/rspec/tag_matchers/has_tag.rb', line 195 def with_criteria(method = nil, &block) @criteria << method unless method.nil? @criteria << block if block_given? self end |