Class: RSpec::TagMatchers::HasTag

Inherits:
Object
  • Object
show all
Includes:
RSpec::TagMatchers::Helpers::SentenceHelper
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.

Direct Known Subclasses

HasForm, HasInput

Instance Method Summary collapse

Methods included from RSpec::TagMatchers::Helpers::SentenceHelper

#make_sentence

Constructor Details

#initialize(name) ⇒ HasTag

Constructs a matcher that matches HTML tags by name.

Parameters:

  • name (String, Symbol)

    The type of tag to match.



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

#descriptionString

Returns a description of the matcher’s criteria. The description is used in RSpec’s output.

Returns:

  • (String)


116
117
118
# File 'lib/rspec/tag_matchers/has_tag.rb', line 116

def description
  "have #{@name.inspect} tag #{extra_description}".strip
end

#failure_messageString

Returns an explanation of why the matcher failed to match with should.

Returns:

  • (String)


123
124
125
# File 'lib/rspec/tag_matchers/has_tag.rb', line 123

def failure_message
  "expected document to #{description}; got: #{@rendered}"
end

#matches?(rendered) ⇒ Boolean

Answers whether or not the matcher matches any elements within rendered.

Parameters:

  • rendered (String)

    A string of HTML or an Object whose to_s method returns HTML. (That includes Nokogiri classes.)

Returns:

  • (Boolean)


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_messageString

Returns an explanation of why the matcher failed to match with should_not.

Returns:

  • (String)


130
131
132
# File 'lib/rspec/tag_matchers/has_tag.rb', line 130

def negative_failure_message
  "expected document to not #{description}; got: #{@rendered}"
end

#test_attribute(attribute, expected) ⇒ Boolean (protected)

Note:

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.

Parameters:

  • attribute (Nokogiri::XML::Attr)

    The attribute to be tested.

  • expected (String, Symbol, Regexp, Boolean)

    The expected value of attribute.

Returns:

  • (Boolean)

See Also:



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.

Parameters:

  • attributes (Hash)

    A hash of attribute key/value pairs. The keys are the attribute names.

Returns:

  • (self)


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.

Examples:

have_tag(:p).with_content("Welcome!")
have_tag(:p).with_content(/^Hello, \w+$/)

Parameters:

  • content (String, Regexp)

    The expected contents of the matched element.

Returns:

  • (self)


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

Examples:

have_div.with_count(2)

Parameters:

  • method (Fixnum)

    The name of the method to be called.

Returns:

  • (self)


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.

Examples:

have_div.with_criteria { |element| element[:id].to_i % 2 == 0 }

Parameters:

  • method (Symbol) (defaults to: nil)

    The name of the method to be called.

  • block (Proc)

    A block to be calle => d.

Returns:

  • (self)


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