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

- (HasTag) initialize(name)

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

- (String) description

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

- (String) failure_message

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 failure_message
  "expected document to #{description}; got: #{@rendered}"
end

- (Boolean) matches?(rendered)

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

- (String) negative_failure_message

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 negative_failure_message
  "expected document to not #{description}; got: #{@rendered}"
end

- (self) with_attribute(attributes) 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

- (self) with_content(content)

Adds a constraint on the matched element's content.

Examples:

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


179
180
181
182
# File 'lib/rspec/tag_matchers/has_tag.rb', line 179

def with_content(content)
  @content = content
  self
end

- (self) with_count(count)

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)


209
210
211
212
# File 'lib/rspec/tag_matchers/has_tag.rb', line 209

def with_count(count)
  @count = count
  self
end

- (self) with_criteria(method = nil, &block)

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 }


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