2016-03-07 19:52:19 -05:00
|
|
|
# frozen_string_literal: true
|
2018-01-08 15:23:54 -05:00
|
|
|
|
2012-08-09 20:38:07 -04:00
|
|
|
require 'forwardable'
|
|
|
|
|
2012-06-08 10:47:01 -04:00
|
|
|
module Capybara
|
2013-01-28 18:58:15 -05:00
|
|
|
##
|
2015-02-25 23:16:38 -05:00
|
|
|
# A {Capybara::Result} represents a collection of {Capybara::Node::Element} on the page. It is possible to interact with this
|
2013-01-28 18:58:15 -05:00
|
|
|
# collection similar to an Array because it implements Enumerable and offers the following Array methods through delegation:
|
|
|
|
#
|
2018-10-31 12:07:25 -04:00
|
|
|
# * \[\]
|
2013-01-28 18:58:15 -05:00
|
|
|
# * each()
|
|
|
|
# * at()
|
|
|
|
# * size()
|
|
|
|
# * count()
|
|
|
|
# * length()
|
|
|
|
# * first()
|
|
|
|
# * last()
|
|
|
|
# * empty?()
|
2018-11-01 12:08:35 -04:00
|
|
|
# * values_at()
|
|
|
|
# * sample()
|
2013-01-28 18:58:15 -05:00
|
|
|
#
|
2015-02-25 23:16:38 -05:00
|
|
|
# @see Capybara::Node::Element
|
2013-01-28 18:58:15 -05:00
|
|
|
#
|
2012-06-08 10:47:01 -04:00
|
|
|
class Result
|
|
|
|
include Enumerable
|
2012-08-09 20:38:07 -04:00
|
|
|
extend Forwardable
|
2012-06-08 10:47:01 -04:00
|
|
|
|
|
|
|
def initialize(elements, query)
|
2012-07-09 08:47:11 -04:00
|
|
|
@elements = elements
|
2016-08-05 17:44:33 -04:00
|
|
|
@result_cache = []
|
2018-11-09 19:30:43 -05:00
|
|
|
@filter_errors = []
|
|
|
|
@results_enum = lazy_select_elements { |node| query.matches_filters?(node, @filter_errors) }
|
2012-06-08 10:47:01 -04:00
|
|
|
@query = query
|
2019-07-05 17:12:38 -04:00
|
|
|
@allow_reload = false
|
2012-06-08 10:47:01 -04:00
|
|
|
end
|
|
|
|
|
2016-08-05 17:44:33 -04:00
|
|
|
def_delegators :full_results, :size, :length, :last, :values_at, :inspect, :sample
|
|
|
|
|
2018-10-12 19:21:53 -04:00
|
|
|
alias index find_index
|
2016-08-05 17:44:33 -04:00
|
|
|
|
|
|
|
def each(&block)
|
2016-10-18 11:54:57 -04:00
|
|
|
return enum_for(:each) unless block_given?
|
|
|
|
|
2016-08-05 17:44:33 -04:00
|
|
|
@result_cache.each(&block)
|
|
|
|
loop do
|
|
|
|
next_result = @results_enum.next
|
2019-07-05 17:12:38 -04:00
|
|
|
add_to_cache(next_result)
|
2018-01-08 15:23:54 -05:00
|
|
|
yield next_result
|
2016-08-05 17:44:33 -04:00
|
|
|
end
|
|
|
|
self
|
|
|
|
end
|
|
|
|
|
|
|
|
def [](*args)
|
2018-05-11 18:14:51 -04:00
|
|
|
idx, length = args
|
|
|
|
max_idx = case idx
|
|
|
|
when Integer
|
2018-05-10 16:20:23 -04:00
|
|
|
if !idx.negative?
|
2018-05-11 18:14:51 -04:00
|
|
|
length.nil? ? idx : idx + length - 1
|
|
|
|
else
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
when Range
|
2020-01-25 20:18:07 -05:00
|
|
|
# idx.max is broken with beginless ranges
|
|
|
|
# idx.end && idx.max # endless range will have end == nil
|
|
|
|
max = idx.end
|
2020-02-03 20:48:36 -05:00
|
|
|
max = nil if max&.negative?
|
2020-01-25 20:18:07 -05:00
|
|
|
max -= 1 if max && idx.exclude_end?
|
|
|
|
max
|
2018-05-11 18:14:51 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
if max_idx.nil?
|
2016-08-05 17:44:33 -04:00
|
|
|
full_results[*args]
|
2018-05-11 18:14:51 -04:00
|
|
|
else
|
2018-08-17 16:57:12 -04:00
|
|
|
load_up_to(max_idx + 1)
|
2018-05-11 18:14:51 -04:00
|
|
|
@result_cache[*args]
|
2016-08-05 17:44:33 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
alias :at :[]
|
|
|
|
|
|
|
|
def empty?
|
|
|
|
!any?
|
|
|
|
end
|
2012-06-08 10:47:01 -04:00
|
|
|
|
2017-11-14 15:14:24 -05:00
|
|
|
def compare_count
|
2019-05-28 15:20:38 -04:00
|
|
|
return 0 unless @query
|
|
|
|
|
2018-10-11 13:59:17 -04:00
|
|
|
count, min, max, between = @query.options.values_at(:count, :minimum, :maximum, :between)
|
|
|
|
|
2016-08-31 17:55:03 -04:00
|
|
|
# Only check filters for as many elements as necessary to determine result
|
2018-10-11 13:59:17 -04:00
|
|
|
if count && (count = Integer(count))
|
2018-08-17 16:57:12 -04:00
|
|
|
return load_up_to(count + 1) <=> count
|
2016-08-31 17:55:03 -04:00
|
|
|
end
|
2016-08-05 17:44:33 -04:00
|
|
|
|
2018-10-11 13:59:17 -04:00
|
|
|
if min && (min = Integer(min))
|
2018-08-17 16:57:12 -04:00
|
|
|
return -1 if load_up_to(min) < min
|
2016-08-05 17:44:33 -04:00
|
|
|
end
|
|
|
|
|
2018-10-11 13:59:17 -04:00
|
|
|
if max && (max = Integer(max))
|
2018-08-17 16:57:12 -04:00
|
|
|
return 1 if load_up_to(max + 1) > max
|
2016-08-05 17:44:33 -04:00
|
|
|
end
|
|
|
|
|
2018-10-11 13:59:17 -04:00
|
|
|
if between
|
2020-01-25 20:18:07 -05:00
|
|
|
min, max = (between.begin && between.min) || 1, between.end
|
|
|
|
max -= 1 if max && between.exclude_end?
|
|
|
|
|
2018-11-16 15:42:41 -05:00
|
|
|
size = load_up_to(max ? max + 1 : min)
|
2018-11-18 15:52:39 -05:00
|
|
|
return size <=> min unless between.include?(size)
|
2016-08-31 17:55:03 -04:00
|
|
|
end
|
|
|
|
|
2018-05-14 17:30:34 -04:00
|
|
|
0
|
2012-06-08 10:47:01 -04:00
|
|
|
end
|
|
|
|
|
2017-11-14 15:14:24 -05:00
|
|
|
def matches_count?
|
2018-01-08 15:23:54 -05:00
|
|
|
compare_count.zero?
|
2017-11-14 15:14:24 -05:00
|
|
|
end
|
|
|
|
|
2012-07-09 07:21:44 -04:00
|
|
|
def failure_message
|
2016-08-30 14:37:54 -04:00
|
|
|
message = @query.failure_message
|
2018-01-13 16:06:03 -05:00
|
|
|
if count.zero?
|
2018-07-10 17:18:39 -04:00
|
|
|
message << ' but there were no matches'
|
2018-01-13 16:06:03 -05:00
|
|
|
else
|
2018-11-02 14:38:58 -04:00
|
|
|
message << ", found #{count} #{Capybara::Helpers.declension('match', 'matches', count)}: " \
|
|
|
|
<< full_results.map(&:text).map(&:inspect).join(', ')
|
2012-07-09 08:47:11 -04:00
|
|
|
end
|
2016-08-05 17:44:33 -04:00
|
|
|
unless rest.empty?
|
2018-07-10 17:18:39 -04:00
|
|
|
elements = rest.map { |el| el.text rescue '<<ERROR>>' }.map(&:inspect).join(', ') # rubocop:disable Style/RescueModifier
|
2018-11-09 19:30:43 -05:00
|
|
|
message << '. Also found ' << elements << ', which matched the selector but not all filters. '
|
|
|
|
message << @filter_errors.join('. ') if (rest.size == 1) && count.zero?
|
2012-07-09 08:47:11 -04:00
|
|
|
end
|
|
|
|
message
|
2012-06-08 10:47:01 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def negative_failure_message
|
2014-02-16 12:13:58 -05:00
|
|
|
failure_message.sub(/(to find)/, 'not \1')
|
2012-06-08 10:47:01 -04:00
|
|
|
end
|
2016-08-05 17:44:33 -04:00
|
|
|
|
2018-07-12 15:32:41 -04:00
|
|
|
def unfiltered_size
|
|
|
|
@elements.length
|
|
|
|
end
|
|
|
|
|
2019-07-05 17:12:38 -04:00
|
|
|
##
|
|
|
|
# @api private
|
|
|
|
#
|
|
|
|
def allow_reload!
|
|
|
|
@allow_reload = true
|
|
|
|
self
|
|
|
|
end
|
|
|
|
|
2018-01-09 17:05:50 -05:00
|
|
|
private
|
2016-08-05 17:44:33 -04:00
|
|
|
|
2019-07-05 17:12:38 -04:00
|
|
|
def add_to_cache(elem)
|
|
|
|
elem.allow_reload!(@result_cache.size) if @allow_reload
|
|
|
|
@result_cache << elem
|
|
|
|
end
|
|
|
|
|
2018-08-17 16:57:12 -04:00
|
|
|
def load_up_to(num)
|
|
|
|
loop do
|
|
|
|
break if @result_cache.size >= num
|
2018-09-24 12:43:46 -04:00
|
|
|
|
2019-07-05 17:12:38 -04:00
|
|
|
add_to_cache(@results_enum.next)
|
2018-08-17 16:57:12 -04:00
|
|
|
end
|
|
|
|
@result_cache.size
|
|
|
|
end
|
|
|
|
|
2016-08-05 17:44:33 -04:00
|
|
|
def full_results
|
2016-08-31 17:55:03 -04:00
|
|
|
loop { @result_cache << @results_enum.next }
|
2016-08-05 17:44:33 -04:00
|
|
|
@result_cache
|
|
|
|
end
|
|
|
|
|
|
|
|
def rest
|
|
|
|
@rest ||= @elements - full_results
|
|
|
|
end
|
|
|
|
|
2019-08-14 18:56:06 -04:00
|
|
|
if (RUBY_PLATFORM == 'java') && (Gem::Version.new(JRUBY_VERSION) < Gem::Version.new('9.2.8.0'))
|
2019-04-11 18:01:59 -04:00
|
|
|
# JRuby < 9.2.8.0 has an issue with lazy enumerators which
|
2017-02-16 15:31:33 -05:00
|
|
|
# causes a concurrency issue with network requests here
|
|
|
|
# https://github.com/jruby/jruby/issues/4212
|
2019-08-14 18:56:06 -04:00
|
|
|
def lazy_select_elements(&block)
|
2018-01-09 17:05:50 -05:00
|
|
|
@elements.select(&block).to_enum # non-lazy evaluation
|
2019-08-14 18:56:06 -04:00
|
|
|
end
|
|
|
|
else
|
|
|
|
def lazy_select_elements(&block)
|
2016-08-17 14:41:40 -04:00
|
|
|
@elements.lazy.select(&block)
|
2016-08-05 17:44:33 -04:00
|
|
|
end
|
|
|
|
end
|
2012-06-08 10:47:01 -04:00
|
|
|
end
|
|
|
|
end
|