Skip to content

Commit

Permalink
Merge branch 'error_messages'
Browse files Browse the repository at this point in the history
  • Loading branch information
jnicklas committed Jul 9, 2012
2 parents 0a42b74 + 28aa12c commit 9935311
Show file tree
Hide file tree
Showing 24 changed files with 257 additions and 261 deletions.
2 changes: 2 additions & 0 deletions lib/capybara.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class CapybaraError < StandardError; end
class DriverNotFoundError < CapybaraError; end
class FrozenInTime < CapybaraError; end
class ElementNotFound < CapybaraError; end
class Ambiguous < ElementNotFound; end
class ExpectationNotMet < ElementNotFound; end
class FileNotFound < CapybaraError; end
class UnselectNotAllowed < CapybaraError; end
Expand Down Expand Up @@ -306,6 +307,7 @@ def session_pool
autoload :Session, 'capybara/session'
autoload :Selector, 'capybara/selector'
autoload :Query, 'capybara/query'
autoload :Result, 'capybara/result'
autoload :VERSION, 'capybara/version'

module Node
Expand Down
14 changes: 10 additions & 4 deletions lib/capybara/node/element.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,14 @@ module Node
#
class Element < Base

def initialize(session, base, parent, selector)
def initialize(session, base, parent, query)
super(session, base)
@parent = parent
@selector = selector
@query = query
end

def allow_reload!
@allow_reload = true
end

##
Expand Down Expand Up @@ -186,8 +190,10 @@ def all(*args)
end

def reload
reloaded = parent.reload.first(@selector.name, @selector.locator, @selector.options)
@base = reloaded.base if reloaded
if @allow_reload
reloaded = parent.reload.first(@query.name, @query.locator, @query.options)
@base = reloaded.base if reloaded
end
self
end

Expand Down
28 changes: 8 additions & 20 deletions lib/capybara/node/finders.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,7 @@ module Finders
# @raise [Capybara::ElementNotFound] If the element can't be found before time expires
#
def find(*args)
query = query(*args)
query.find = true
synchronize do
results = resolve(query)
query.verify!(results)
results.first
end
synchronize { all(*args).find! }.tap(&:allow_reload!)
end

##
Expand Down Expand Up @@ -115,7 +109,13 @@ def find_by_id(id)
# @return [Array[Capybara::Element]] The found elements
#
def all(*args)
resolve(query(*args))
query = Capybara::Query.new(*args)
elements = synchronize do
base.find(query.xpath).map do |node|
Capybara::Node::Element.new(session, node, self, query)
end
end
Capybara::Result.new(elements, query)
end

##
Expand All @@ -132,18 +132,6 @@ def all(*args)
def first(*args)
all(*args).first
end

def query(*args)
Capybara::Query.new(self, *args)
end

def resolve(query)
synchronize do
base.find(query.xpath).map do |node|
Capybara::Node::Element.new(session, node, self, query)
end.select { |node| query.matches_filters?(node) }
end
end
end
end
end
30 changes: 20 additions & 10 deletions lib/capybara/node/matchers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,19 @@ module Matchers
# @return [Boolean] If the expression exists
#
def has_selector?(*args)
synchronize do
results = all(*args)
query(*args).matches_count?(results) or raise Capybara::ExpectationNotMet
results
end
assert_selector(*args)
rescue Capybara::ExpectationNotMet
return false
end

def assert_selector(*args)
synchronize do
result = all(*args)
result.matches_count? or raise Capybara::ExpectationNotMet, result.failure_message
end
return true
end

##
#
# Checks if a given selector is not on the page or current node.
Expand All @@ -51,15 +55,19 @@ def has_selector?(*args)
# @return [Boolean]
#
def has_no_selector?(*args)
synchronize do
results = all(*args)
query(*args).matches_count?(results) and raise Capybara::ExpectationNotMet
results
end
assert_no_selector(*args)
rescue Capybara::ExpectationNotMet
return false
end

def assert_no_selector(*args)
synchronize do
result = all(*args)
result.matches_count? and raise Capybara::ExpectationNotMet, result.negative_failure_message
end
return true
end

##
#
# Checks if a given XPath expression is on the page or current node.
Expand Down Expand Up @@ -162,6 +170,7 @@ def has_text?(content)
normalize_whitespace(text).include?(normalized_content) or
raise ExpectationNotMet
end
return true
rescue Capybara::ExpectationNotMet
return false
end
Expand All @@ -185,6 +194,7 @@ def has_no_text?(content)
!normalize_whitespace(text).include?(normalized_content) or
raise ExpectationNotMet
end
return true
rescue Capybara::ExpectationNotMet
return false
end
Expand Down
12 changes: 9 additions & 3 deletions lib/capybara/node/simple.rb
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,16 @@ def synchronize
yield # simple nodes don't need to wait
end

def resolve(query)
native.xpath(query.xpath).map do |node|
def allow_reload!
# no op
end

def all(*args)
query = Capybara::Query.new(*args)
elements = native.xpath(query.xpath).map do |node|
self.class.new(node)
end.select { |node| query.matches_filters?(node) }
end
Capybara::Result.new(elements, query)
end
end
end
Expand Down
51 changes: 11 additions & 40 deletions lib/capybara/query.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
module Capybara
class Query
attr_accessor :node, :selector, :locator, :options, :xpath, :find, :negative
attr_accessor :selector, :locator, :options, :xpath, :find, :negative

def initialize(node, *args)
@node = node
def initialize(*args)
@options = if args.last.is_a?(Hash) then args.pop.dup else {} end
unless options.has_key?(:visible)
@options[:visible] = Capybara.ignore_hidden_elements
Expand All @@ -21,25 +20,11 @@ def initialize(node, *args)
@xpath = @selector.call(@locator).to_s
end

def failure_message
message = selector.failure_message.call(node, self) if selector.failure_message
message ||= options[:message]
if find
message ||= "Unable to find #{description}"
else
message ||= "expected #{description} to return something"
end
message
end

def negative_failure_message
"expected #{description} not to return anything"
end

def name; selector.name; end
def label; selector.label or selector.name; end

def description
@description = "#{name} #{locator.inspect}"
@description = "#{label} #{locator.inspect}"
@description << " with text #{options[:text].inspect}" if options[:text]
@description
end
Expand All @@ -56,34 +41,20 @@ def matches_filters?(node)
true
end

def verify!(results)
if find and results.length != 1
raise Capybara::ElementNotFound, failure_message
end
end

def error(results)
if negative
negative_failure_message
else
failure_message
end
end

def matches_count?(nodes)
def matches_count?(count)
case
when nodes.empty?
when count.zero?
false
when options[:between]
options[:between] === nodes.size
options[:between] === count
when options[:count]
options[:count].to_i == nodes.size
options[:count].to_i == count
when options[:maximum]
options[:maximum].to_i >= nodes.size
options[:maximum].to_i >= count
when options[:minimum]
options[:minimum].to_i <= nodes.size
options[:minimum].to_i <= count
else
nodes.size > 0
count > 0
end
end
end
Expand Down
84 changes: 84 additions & 0 deletions lib/capybara/result.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
module Capybara
class Result
include Enumerable

def initialize(elements, query)
@elements = elements
@result = elements.select { |node| query.matches_filters?(node) }
@rest = @elements - @result
@query = query
end

def each(&block)
@result.each(&block)
end

def first
@result.first
end

def matches_count?
@query.matches_count?(@result.size)
end

def find!
raise find_error if @result.count != 1
@result.first
end

def size; @result.size; end
alias_method :length, :size
alias_method :count, :size

def find_error
if @result.count == 0
Capybara::ElementNotFound.new("Unable to find #{@query.description}")
elsif @result.count > 1
Capybara::Ambiguous.new("Ambiguous match, found #{size} elements matching #{@query.description}")
end
end

def failure_message
message = if @query.options[:count]
"expected #{@query.description} to be found #{@query.options[:count]} #{declension("time", "times", @query.options[:count])}"
elsif @query.options[:between]
"expected #{@query.description} to be found between #{@query.options[:between].first} and #{@query.options[:between].last} times"
elsif @query.options[:maximum]
"expected #{@query.description} to be found at most #{@query.options[:maximum]} #{declension("time", "times", @query.options[:maximum])}"
elsif @query.options[:minimum]
"expected #{@query.description} to be found at least #{@query.options[:minimum]} #{declension("time", "times", @query.options[:minimum])}"
else
"expected to find #{@query.description}"
end
if count > 0
message << ", found #{count} #{declension("match", "matches")}: " << @result.map(&:text).map(&:inspect).join(", ")
else
message << " but there were no matches"
end
unless @rest.empty?
elements = @rest.map(&:text).map(&:inspect).join(", ")
message << ". Also found " << elements << ", which matched the selector but not all filters."
end
message
end

def negative_failure_message
"expected not to find #{@query.description}, but there #{declension("was", "were")} #{count} #{declension("match", "matches")}"
end

def empty?
@result.empty?
end
def [](key); @result[key]; end

private

def declension(singular, plural, count=count)
if count == 1
singular
else
plural
end
end
end
end
Loading

0 comments on commit 9935311

Please sign in to comment.