module Merb::Test::Rspec::ViewMatchers class HaveSelector # ==== Parameters # expected:: The string to look for. def initialize(expected) @expected = expected end # ==== Parameters # stringlike:: The thing to search in. # # ==== Returns # Boolean:: True if there was at least one match. def matches?(stringlike) @document = case stringlike when Hpricot::Elem stringlike when StringIO Hpricot.parse(stringlike.string) else Hpricot.parse(stringlike) end !@document.search(@expected).empty? end # ==== Returns # String:: The failure message. def failure_message "expected following text to match selector #{@expected}:\n#{@document}" end # ==== Returns # String:: The failure message to be displayed in negative matches. def negative_failure_message "expected following text to not match selector #{@expected}:\n#{@document}" end end class MatchTag # ==== Parameters # name<~to_s>:: The name of the tag to look for. # attrs:: Attributes to look for in the tag (see below). # # ==== Options (attrs) # :content:: Optional content to match. def initialize(name, attrs) @name, @attrs = name, attrs @content = @attrs.delete(:content) end # ==== Parameters # target:: The string to look for the tag in. # # ==== Returns # Boolean:: True if the tag matched. def matches?(target) @errors = [] unless target.include?("<#{@name}") @errors << "Expected a <#{@name}>, but was #{target}" end @attrs.each do |attr, val| unless target.include?("#{attr}=\"#{val}\"") @errors << "Expected #{attr}=\"#{val}\", but was #{target}" end end if @content unless target.include?(">#{@content}<") @errors << "Expected #{target} to include #{@content}" end end @errors.size == 0 end # ==== Returns # String:: The failure message. def failure_message @errors[0] end # ==== Returns # String:: The failure message to be displayed in negative matches. def negative_failure_message "Expected not to match against <#{@name} #{@attrs.map{ |a,v| "#{a}=\"#{v}\"" }.join(" ")}> tag, but it matched" end end class NotMatchTag # === Parameters # attrs:: A set of attributes that must not be matched. def initialize(attrs) @attrs = attrs end # ==== Parameters # target:: The target to look for the match in. # # ==== Returns # Boolean:: True if none of the attributes were matched. def matches?(target) @errors = [] @attrs.each do |attr, val| if target.include?("#{attr}=\"#{val}\"") @errors << "Should not include #{attr}=\"#{val}\", but was #{target}" end end @errors.size == 0 end # ==== Returns # String:: The failure message. def failure_message @errors[0] end end class HasTag # ==== Parameters # tag<~to_s>:: The tag to look for. # attributes:: Attributes for the tag (see below). def initialize(tag, attributes = {}, &blk) @tag, @attributes = tag, attributes @id, @class = @attributes.delete(:id), @attributes.delete(:class) @blk = blk end # ==== Parameters # stringlike:: The thing to search in. # &blk:: An optional block for searching in child elements using with_tag. # # ==== Returns # Boolean:: True if there was at least one match. def matches?(stringlike, &blk) @document = case stringlike when Hpricot::Elem stringlike when StringIO Hpricot.parse(stringlike.string) else Hpricot.parse(stringlike) end @blk = blk unless blk.nil? unless @blk.nil? !@document.search(selector).select do |ele| @blk.call ele true end.empty? else !@document.search(selector).empty? end end # ==== Returns # String:: The complete selector for element queries. def selector @selector = "//#{@tag}#{id_selector}#{class_selector}" @selector << @attributes.map{|a, v| "[@#{a}=\"#{v}\"]"}.join @selector << @inner_has_tag.selector unless @inner_has_tag.nil? @selector end # ==== Returns # String:: ID selector for use in element queries. def id_selector "##{@id}" if @id end # ==== Returns # String:: Class selector for use in element queries. def class_selector ".#{@class}" if @class end # ==== Returns # String:: The failure message. def failure_message "expected following output to contain a #{tag_for_error} tag:\n#{@document}" end # ==== Returns # String:: The failure message to be displayed in negative matches. def negative_failure_message "expected following output to omit a #{tag_for_error} tag:\n#{@document}" end # ==== Returns # String:: The tag used in failure messages. def tag_for_error "#{inner_failure_message}<#{@tag}#{id_for_error}#{class_for_error}#{attributes_for_error}>" end # ==== Returns # String:: # The failure message to be displayed in negative matches within the # have_tag block. def inner_failure_message "#{@inner_has_tag.tag_for_error} tag within a " unless @inner_has_tag.nil? end # ==== Returns # String:: ID for the error tag. def id_for_error " id=\"#{@id}\"" unless @id.nil? end # ==== Returns # String:: Class for the error tag. def class_for_error " class=\"#{@class}\"" unless @class.nil? end # ==== Returns # String:: Class for the error tag. def attributes_for_error @attributes.map{|a,v| " #{a}=\"#{v}\""}.join end # Search for a child tag within a have_tag block. # # ==== Parameters # tag<~to_s>:: The tag to look for. # attributes:: Attributes for the tag (see below). def with_tag(name, attrs={}) @inner_has_tag = HasTag.new(name, attrs) end end class HasContent def initialize(content) @content = content end def matches?(element) @element = element case @content when String @element.contains?(@content) when Regexp @element.matches?(@content) end end # ==== Returns # String:: The failure message. def failure_message "expected the following element's content to #{content_message}:\n#{@element.inner_text}" end # ==== Returns # String:: The failure message to be displayed in negative matches. def negative_failure_message "expected the following element's content to not #{content_message}:\n#{@element.inner_text}" end def content_message case @content when String "include \"#{@content}\"" when Regexp "match #{@content.inspect}" end end end # ==== Parameters # name<~to_s>:: The name of the tag to look for. # attrs:: Attributes to look for in the tag (see below). # # ==== Options (attrs) # :content:: Optional content to match. # # ==== Returns # MatchTag:: A new match tag matcher. def match_tag(name, attrs={}) MatchTag.new(name, attrs) end # ==== Parameters # attrs:: A set of attributes that must not be matched. # # ==== Returns # NotMatchTag:: A new not match tag matcher. def not_match_tag(attrs) NotMatchTag.new(attrs) end # ==== Parameters # expected:: The string to look for. # # ==== Returns # HaveSelector:: A new have selector matcher. def have_selector(expected) HaveSelector.new(expected) end alias_method :match_selector, :have_selector # RSpec matcher to test for the presence of tags. # # ==== Parameters # tag<~to_s>:: The name of the tag. # attributes:: Tag attributes. # # ==== Returns # HasTag:: A new has tag matcher. # # ==== Examples # # Check for
# body.should have_tag("div") # # # Check for # body.should have_tag("span", :id => :notice) # # # Check for

# body.should have_tag(:h2, :class => "bar", :id => "foo") # # # Check for
# body.should have_tag(:div, :attr => :val) def have_tag(tag, attributes = {}, &blk) HasTag.new(tag, attributes, &blk) end alias_method :with_tag, :have_tag def contain(content) HasContent.new(content) end end