class RSpecHtmlMatchers::HaveTag

@api @private

Constants

DESCRIPTIONS
MESSAGES

Attributes

current_scope[R]
document[R]
failure_message[R]
failure_message_when_negated[R]
options[R]
tag[R]

Public Class Methods

new(tag, options = {}) click to toggle source
# File lib/rspec-html-matchers/have_tag.rb, line 45
def initialize tag, options = {}, &block
  @tag = tag.to_s
  @options = options
  @block = block

  if with_attrs = @options.delete(:with)
    if classes = with_attrs.delete(:class)
      @tag += '.' + classes_to_selector(classes)
    end
    selector = with_attrs.inject('') do |html_attrs_string, (k, v)|
      html_attrs_string += "[#{k}='#{v}']"
      html_attrs_string
    end
    @tag += selector
  end

  if without_attrs = @options.delete(:without)
    if classes = without_attrs.delete(:class)
      @tag += ":not(.#{classes_to_selector(classes)})"
    end
  end

  validate_options!
  organize_options!
end

Public Instance Methods

description() click to toggle source
# File lib/rspec-html-matchers/have_tag.rb, line 104
def description
  # TODO: should it be more complicated?
  if options.key?(:count)
    format(DESCRIPTIONS[:have_n], options[:count], tag)
  else
    DESCRIPTIONS[:have_at_least_1] % tag
  end
end
matches?(src, &block) click to toggle source
# File lib/rspec-html-matchers/have_tag.rb, line 75
def matches? src, &block
  @block = block if block

  src = src.html if defined?(Capybara::Session) && src.is_a?(Capybara::Session)

  case src
  when String
    parent_scope = Nokogiri::HTML(src)
    @document    = src
  else
    parent_scope  = src.current_scope
    @document     = parent_scope.to_html
  end

  @current_scope = begin
                     parent_scope.css(tag)
                     # on jruby this produce exception if css was not found:
                     # undefined method `decorate' for nil:NilClass
                   rescue NoMethodError
                     Nokogiri::XML::NodeSet.new(Nokogiri::XML::Document.new)
                   end
  if tag_presents? && proper_content? && count_right?
    @block.call(self) if @block
    true
  else
    false
  end
end

Private Instance Methods

classes_to_selector(classes) click to toggle source
# File lib/rspec-html-matchers/have_tag.rb, line 119
def classes_to_selector classes
  case classes
  when Array
    classes.join('.')
  when String
    classes.gsub(/\s+/, '.')
  end
end
count_is_range_but_no_min?() click to toggle source
# File lib/rspec-html-matchers/have_tag.rb, line 263
def count_is_range_but_no_min?
  options[:count].is_a?(Range) &&
    (options[:count].min.nil? || (options[:count].min < 0))
end
count_right?() click to toggle source
# File lib/rspec-html-matchers/have_tag.rb, line 137
def count_right? # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
  case options[:count]
  when Integer
    if @count == options[:count]
      match_succeeded! :unexpected_count, document, @count, tag
    else
      match_failed! :expected_count, document, options[:count], tag, @count
    end
  when Range
    if options[:count].member? @count
      match_succeeded! :unexpected_btw_count, document, options[:count].min, options[:count].max, tag, @count
    else
      match_failed! :expected_btw_count, document, options[:count].min, options[:count].max, tag, @count
    end
  when nil
    if options[:maximum]
      if @count <= options[:maximum]
        match_succeeded! :unexpected_at_most, document, options[:maximum], tag, @count
      else
        match_failed! :expected_at_most, document, options[:maximum], tag, @count
      end
    elsif options[:minimum]
      if @count >= options[:minimum]
        match_succeeded! :unexpected_at_least, document, options[:minimum], tag, @count
      else
        match_failed! :expected_at_least, document, options[:minimum], tag, @count
      end
    else
      true
    end
  end
end
match_failed!(message, *args) click to toggle source
# File lib/rspec-html-matchers/have_tag.rb, line 285
def match_failed! message, *args
  @failure_message = format MESSAGES[message], *args
  false
end
match_succeeded!(message, *args) click to toggle source
# File lib/rspec-html-matchers/have_tag.rb, line 280
def match_succeeded! message, *args
  @failure_message_when_negated = format MESSAGES[message], *args
  true
end
maybe_empty?() click to toggle source
# File lib/rspec-html-matchers/have_tag.rb, line 178
def maybe_empty?
  if options[:blank] && current_scope.children.empty?
    match_succeeded! :unexpected_blank, tag, document
  else
    match_failed! :expected_blank, tag, document
  end
end
organize_options!() click to toggle source
# File lib/rspec-html-matchers/have_tag.rb, line 268
def organize_options!
  @options[:minimum] ||= @options.delete(:min)
  @options[:maximum] ||= @options.delete(:max)

  @options[:text] = @options[:text].to_s if @options.key?(:text) && !@options[:text].is_a?(Regexp)

  if @options.key?(:seen) && !@options[:seen].is_a?(Regexp) # rubocop:disable Style/GuardClause
    @options[:text] = @options[:seen].to_s
    @options[:squeeze_text] = true
  end
end
proper_content?() click to toggle source
# File lib/rspec-html-matchers/have_tag.rb, line 170
def proper_content?
  if options.key?(:blank)
    maybe_empty?
  else
    text_right?
  end
end
tag_presents?() click to toggle source
# File lib/rspec-html-matchers/have_tag.rb, line 128
def tag_presents?
  if current_scope.first
    @count = current_scope.count
    match_succeeded! :unexpected_tag, document, tag, @count
  else
    match_failed! :expected_tag, document, tag
  end
end
text_right?() click to toggle source
# File lib/rspec-html-matchers/have_tag.rb, line 186
def text_right?
  return true unless options[:text]

  case text = options[:text]
  when Regexp
    new_scope = current_scope.css(':regexp()', NokogiriRegexpHelper.new(text))
    if new_scope.empty?
      match_failed! :expected_regexp, text.inspect, tag, document
    else
      @count = new_scope.count
      match_succeeded! :unexpected_regexp, text.inspect, tag, document
    end
  else
    new_scope = current_scope.css(':content()', NokogiriTextHelper.new(text, options[:squeeze_text]))
    if new_scope.empty?
      match_failed! :expected_text, text, tag, document
    else
      @count = new_scope.count
      match_succeeded! :unexpected_text, text, tag, document
    end
  end
end
validate_count_presence!() click to toggle source
# File lib/rspec-html-matchers/have_tag.rb, line 239
def validate_count_presence!
  raise 'wrong :count specified' unless [Range, NilClass].include?(options[:count].class) || options[:count].is_a?(Integer)

  [:min, :minimum, :max, :maximum].each do |key|
    raise MESSAGES[:wrong_count_error] if options.key?(key) && options.key?(:count)
  end
end
validate_count_when_set_min_max!() click to toggle source
# File lib/rspec-html-matchers/have_tag.rb, line 247
def validate_count_when_set_min_max!
  raise MESSAGES[:min_max_error] if options[:minimum] > options[:maximum]
rescue NoMethodError # nil > 4 # rubocop:disable Lint/HandleExceptions
rescue ArgumentError # 2 < nil # rubocop:disable Lint/HandleExceptions
end
validate_count_when_set_range!() click to toggle source
# File lib/rspec-html-matchers/have_tag.rb, line 253
def validate_count_when_set_range!
  begin
    raise format(MESSAGES[:bad_range_error], options[:count].to_s) if count_is_range_but_no_min?
  rescue ArgumentError, 'comparison of String with' # if options[:count] == 'a'..'z' # rubocop:disable Lint/RescueType
    raise format(MESSAGES[:bad_range_error], options[:count].to_s)
  end
rescue TypeError # fix for 1.8.7 for 'rescue ArgumentError, "comparison of String with"' stroke
  raise format(MESSAGES[:bad_range_error], options[:count].to_s)
end
validate_html_body_tags!() click to toggle source

here is a demo:

irb(main):009:0> Nokogiri::HTML('<p>asd</p>').xpath('//html')
=> [#<Nokogiri::XML::Element:0x3fea02cd3f58 name="html" children=[#<Nokogiri::XML::Element:0x3fea02cd37c4 name="body" children=[#<Nokogiri::XML::Element:0x3fea02cd34e0 name="p" children=[#<Nokogiri::XML::Text:0x3fea02cd3134 "asd">]>]>]>]
irb(main):010:0> Nokogiri::HTML('<p>asd</p>').xpath('//body')
=> [#<Nokogiri::XML::Element:0x3fea02ce3df4 name="body" children=[#<Nokogiri::XML::Element:0x3fea02ce3a70 name="p" children=[#<Nokogiri::XML::Text:0x3fea02ce350c "asd">]>]>]
irb(main):011:0> Nokogiri::HTML('<p>asd</p>').xpath('//p')
=> [#<Nokogiri::XML::Element:0x3fea02cf3754 name="p" children=[#<Nokogiri::XML::Text:0x3fea02cf2f98 "asd">]>]
irb(main):012:0> Nokogiri::HTML('<p>asd</p>').xpath('//a')
=> []
# File lib/rspec-html-matchers/have_tag.rb, line 226
def validate_html_body_tags!
  if %w[html body].include?(tag) && options.empty?
    raise ArgumentError, 'matching <html> and <body> tags without specifying additional options does not work, see: https://github.com/kucaahbe/rspec-html-matchers/pull/75'
  end
end
validate_options!() click to toggle source
# File lib/rspec-html-matchers/have_tag.rb, line 209
def validate_options!
  validate_html_body_tags!
  validate_text_options!
  validate_count_presence!
  validate_count_when_set_min_max!
  validate_count_when_set_range!
end
validate_text_options!() click to toggle source
# File lib/rspec-html-matchers/have_tag.rb, line 232
def validate_text_options!
  # TODO: test these options validations
  if options.key?(:blank) && options[:blank] && options.key?(:text) # rubocop:disable Style/GuardClause, Style/IfUnlessModifier
    raise ':text option is not accepted when :blank => true'
  end
end