Adding view matchers from Merb
This commit is contained in:
parent
5d3cb35370
commit
f6d95d34ae
@ -8,3 +8,4 @@ require "webrat/core/label"
|
|||||||
require "webrat/core/select_option"
|
require "webrat/core/select_option"
|
||||||
require "webrat/core/session"
|
require "webrat/core/session"
|
||||||
require "webrat/core/methods"
|
require "webrat/core/methods"
|
||||||
|
require "webrat/core/matchers"
|
||||||
|
234
lib/webrat/core/matchers.rb
Normal file
234
lib/webrat/core/matchers.rb
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
module Webrat
|
||||||
|
module Matchers
|
||||||
|
|
||||||
|
class HaveXpath
|
||||||
|
def initialize(expected, &block)
|
||||||
|
# Require nokogiri and fall back on rexml
|
||||||
|
begin
|
||||||
|
require "nokogiri"
|
||||||
|
rescue LoadError => e
|
||||||
|
if require "rexml/document"
|
||||||
|
require "merb-core/vendor/nokogiri/css"
|
||||||
|
warn("Standard REXML library is slow. Please consider installing nokogiri.\nUse \"sudo gem install nokogiri\"")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@expected = expected
|
||||||
|
@block = block
|
||||||
|
end
|
||||||
|
|
||||||
|
def matches?(stringlike)
|
||||||
|
if defined?(Nokogiri::XML)
|
||||||
|
matches_nokogiri?(stringlike)
|
||||||
|
else
|
||||||
|
matches_rexml?(stringlike)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def matches_rexml?(stringlike)
|
||||||
|
stringlike = stringlike.body.to_s if stringlike.respond_to?(:body)
|
||||||
|
|
||||||
|
@document = case stringlike
|
||||||
|
when REXML::Document
|
||||||
|
stringlike.root
|
||||||
|
when REXML::Node
|
||||||
|
stringlike
|
||||||
|
when StringIO, String
|
||||||
|
begin
|
||||||
|
REXML::Document.new(stringlike.to_s).root
|
||||||
|
rescue REXML::ParseException => e
|
||||||
|
if e.message.include?("second root element")
|
||||||
|
REXML::Document.new("<fake-root-element>#{stringlike}</fake-root-element>").root
|
||||||
|
else
|
||||||
|
raise e
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
query.all? do |q|
|
||||||
|
matched = REXML::XPath.match(@document, q)
|
||||||
|
matched.any? && (!block_given? || matched.all?(&@block))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def matches_nokogiri?(stringlike)
|
||||||
|
stringlike = stringlike.body.to_s if stringlike.respond_to?(:body)
|
||||||
|
|
||||||
|
@document = case stringlike
|
||||||
|
when Nokogiri::HTML::Document, Nokogiri::XML::NodeSet
|
||||||
|
stringlike
|
||||||
|
when StringIO
|
||||||
|
Nokogiri::HTML(stringlike.string)
|
||||||
|
else
|
||||||
|
Nokogiri::HTML(stringlike.to_s)
|
||||||
|
end
|
||||||
|
@document.xpath(*query).any?
|
||||||
|
end
|
||||||
|
|
||||||
|
def query
|
||||||
|
[@expected].flatten.compact
|
||||||
|
end
|
||||||
|
|
||||||
|
# ==== Returns
|
||||||
|
# String:: The failure message.
|
||||||
|
def failure_message
|
||||||
|
"expected following text to match xpath #{@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 xpath #{@expected}:\n#{@document}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class HaveSelector < HaveXpath
|
||||||
|
|
||||||
|
# ==== 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
|
||||||
|
|
||||||
|
def query
|
||||||
|
Nokogiri::CSS::Parser.parse(*super).map { |ast| ast.to_xpath }
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
class HaveTag < HaveSelector
|
||||||
|
|
||||||
|
# ==== Returns
|
||||||
|
# String:: The failure message.
|
||||||
|
def failure_message
|
||||||
|
"expected following output to contain a #{tag_inspect} 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_inspect}:\n#{@document}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def tag_inspect
|
||||||
|
options = @expected.last.dup
|
||||||
|
content = options.delete(:content)
|
||||||
|
|
||||||
|
html = "<#{@expected.first}"
|
||||||
|
options.each do |k,v|
|
||||||
|
html << " #{k}='#{v}'"
|
||||||
|
end
|
||||||
|
|
||||||
|
if content
|
||||||
|
html << ">#{content}</#{@expected.first}>"
|
||||||
|
else
|
||||||
|
html << "/>"
|
||||||
|
end
|
||||||
|
|
||||||
|
html
|
||||||
|
end
|
||||||
|
|
||||||
|
def query
|
||||||
|
options = @expected.last.dup
|
||||||
|
selector = @expected.first.to_s
|
||||||
|
|
||||||
|
selector << ":contains('#{options.delete(:content)}')" if options[:content]
|
||||||
|
|
||||||
|
options.each do |key, value|
|
||||||
|
selector << "[#{key}='#{value}']"
|
||||||
|
end
|
||||||
|
|
||||||
|
Nokogiri::CSS::Parser.parse(selector).map { |ast| ast.to_xpath }
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
class HasContent
|
||||||
|
def initialize(content)
|
||||||
|
@content = content
|
||||||
|
end
|
||||||
|
|
||||||
|
def matches?(element)
|
||||||
|
element = element.body.to_s if element.respond_to?(:body)
|
||||||
|
@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
|
||||||
|
|
||||||
|
# Matches HTML content against a CSS 3 selector.
|
||||||
|
#
|
||||||
|
# ==== Parameters
|
||||||
|
# expected<String>:: The CSS selector to look for.
|
||||||
|
#
|
||||||
|
# ==== Returns
|
||||||
|
# HaveSelector:: A new have selector matcher.
|
||||||
|
# ---
|
||||||
|
# @api public
|
||||||
|
def have_selector(expected)
|
||||||
|
HaveSelector.new(expected)
|
||||||
|
end
|
||||||
|
alias_method :match_selector, :have_selector
|
||||||
|
|
||||||
|
# Matches HTML content against an XPath query
|
||||||
|
#
|
||||||
|
# ==== Parameters
|
||||||
|
# expected<String>:: The XPath query to look for.
|
||||||
|
#
|
||||||
|
# ==== Returns
|
||||||
|
# HaveXpath:: A new have xpath matcher.
|
||||||
|
# ---
|
||||||
|
# @api public
|
||||||
|
def have_xpath(expected)
|
||||||
|
HaveXpath.new(expected)
|
||||||
|
end
|
||||||
|
alias_method :match_xpath, :have_xpath
|
||||||
|
|
||||||
|
def have_tag(name, attributes = {})
|
||||||
|
HaveTag.new([name, attributes])
|
||||||
|
end
|
||||||
|
alias_method :match_tag, :have_tag
|
||||||
|
|
||||||
|
# Matches the contents of an HTML document with
|
||||||
|
# whatever string is supplied
|
||||||
|
#
|
||||||
|
# ---
|
||||||
|
# @api public
|
||||||
|
def contain(content)
|
||||||
|
HasContent.new(content)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
108
spec/api/matchers_spec.rb
Normal file
108
spec/api/matchers_spec.rb
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
|
||||||
|
|
||||||
|
describe Webrat::Matchers do
|
||||||
|
include Webrat::Matchers
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
@body = <<-EOF
|
||||||
|
<div id='main'>
|
||||||
|
<div class='inner'>hello, world!</div>
|
||||||
|
</div>
|
||||||
|
EOF
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#have_selector" do
|
||||||
|
|
||||||
|
it "should be able to match a CSS selector" do
|
||||||
|
@body.should have_selector("div")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not match a CSS selector that does not exist" do
|
||||||
|
@body.should_not have_selector("p")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be able to loop over all the matched elements" do
|
||||||
|
@body.should have_selector("div") { |node| node.name.should == "div" }
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not match of any of the matchers in the block fail" do
|
||||||
|
lambda {
|
||||||
|
@body.should_not have_selector("div") { |node| node.name.should == "p" }
|
||||||
|
}.should raise_error(Spec::Expectations::ExpectationNotMetError)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#have_tag" do
|
||||||
|
|
||||||
|
it "should be able to match a tag" do
|
||||||
|
@body.should have_tag("div")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not match the tag when it should not match" do
|
||||||
|
@body.should_not have_tag("p")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be able to specify the content of the tag" do
|
||||||
|
@body.should have_tag("div", :content => "hello, world!")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be able to specify the attributes of the tag" do
|
||||||
|
@body.should have_tag("div", :class => "inner")
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
describe Webrat::Matchers::HasContent do
|
||||||
|
include Webrat::Matchers
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
@element = stub(:element)
|
||||||
|
@element.stub!(:inner_text).and_return <<-EOF
|
||||||
|
<div id='main'>
|
||||||
|
<div class='inner'>hello, world!</div>
|
||||||
|
</div>
|
||||||
|
EOF
|
||||||
|
|
||||||
|
@element.stub!(:contains?)
|
||||||
|
@element.stub!(:matches?)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#matches?" do
|
||||||
|
it "should call element#contains? when the argument is a string" do
|
||||||
|
@element.should_receive(:contains?)
|
||||||
|
|
||||||
|
Webrat::Matchers::HasContent.new("hello, world!").matches?(@element)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should call element#matches? when the argument is a regular expression" do
|
||||||
|
@element.should_receive(:matches?)
|
||||||
|
|
||||||
|
Webrat::Matchers::HasContent.new(/hello, world/).matches?(@element)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#failure_message" do
|
||||||
|
it "should include the content string" do
|
||||||
|
hc = Webrat::Matchers::HasContent.new("hello, world!")
|
||||||
|
hc.matches?(@element)
|
||||||
|
|
||||||
|
hc.failure_message.should include("\"hello, world!\"")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should include the content regular expresson" do
|
||||||
|
hc = Webrat::Matchers::HasContent.new(/hello,\sworld!/)
|
||||||
|
hc.matches?(@element)
|
||||||
|
|
||||||
|
hc.failure_message.should include("/hello,\\sworld!/")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should include the element's inner content" do
|
||||||
|
hc = Webrat::Matchers::HasContent.new(/hello,\sworld!/)
|
||||||
|
hc.matches?(@element)
|
||||||
|
|
||||||
|
hc.failure_message.should include(@element.inner_text)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue
Block a user