2016-01-29 19:31:35 -05:00
# frozen_string_literal: true
module Capybara
module Queries
class SelectorQuery < Queries :: BaseQuery
attr_accessor :selector , :locator , :options , :expression , :find , :negative
2016-12-23 15:17:45 -05:00
VALID_KEYS = COUNT_KEYS + [ :text , :id , :class , :visible , :exact , :exact_text , :match , :wait , :filter_set ]
2016-01-29 19:31:35 -05:00
VALID_MATCH = [ :first , :smart , :prefer_exact , :one ]
2016-09-23 18:03:36 -04:00
def initialize ( * args , & filter_block )
2016-01-29 19:31:35 -05:00
@options = if args . last . is_a? ( Hash ) then args . pop . dup else { } end
2016-09-23 18:03:36 -04:00
@filter_block = filter_block
2016-01-29 19:31:35 -05:00
if args [ 0 ] . is_a? ( Symbol )
2016-09-26 14:20:15 -04:00
@selector = Selector . all . fetch ( args . shift ) do | selector_type |
warn " Unknown selector type (: #{ selector_type } ), defaulting to : #{ Capybara . default_selector } - This will raise an exception in a future version of Capybara "
nil
end
2016-04-04 18:36:43 -04:00
@locator = args . shift
2016-01-29 19:31:35 -05:00
else
@selector = Selector . all . values . find { | s | s . match? ( args [ 0 ] ) }
2016-04-04 18:36:43 -04:00
@locator = args . shift
2016-01-29 19:31:35 -05:00
end
@selector || = Selector . all [ Capybara . default_selector ]
2016-04-04 18:36:43 -04:00
warn " Unused parameters passed to #{ self . class . name } : #{ args . to_s } " unless args . empty?
2016-01-29 19:31:35 -05:00
# for compatibility with Capybara 2.0
if Capybara . exact_options and @selector == Selector . all [ :option ]
@options [ :exact ] = true
end
2016-08-18 16:27:35 -04:00
@expression = @selector . call ( @locator , @options )
2016-08-24 17:07:05 -04:00
2016-08-25 13:07:45 -04:00
warn_exact_usage
2016-08-24 17:07:05 -04:00
2016-01-29 19:31:35 -05:00
assert_valid_keys
end
def name ; selector . name ; end
def label ; selector . label or selector . name ; end
def description
@description = String . new ( " #{ label } #{ locator . inspect } " )
2017-01-26 19:10:51 -05:00
@description << " with #{ " exact " if exact_text == true } text #{ options [ :text ] . inspect } " if options [ :text ]
2016-12-23 15:17:45 -05:00
@description << " with exact text #{ options [ :exact_text ] } " if options [ :exact_text ] . is_a? ( String )
2016-09-22 19:55:54 -04:00
@description << " with id #{ options [ :id ] } " if options [ :id ]
@description << " with classes #{ Array ( options [ :class ] ) . join ( ',' ) } ] " if options [ :class ]
2016-01-29 19:31:35 -05:00
@description << selector . description ( options )
2016-09-23 18:03:36 -04:00
@description << " that also matches the custom filter block " if @filter_block
2016-01-29 19:31:35 -05:00
@description
end
def matches_filters? ( node )
if options [ :text ]
2016-12-23 15:17:45 -05:00
regexp = if options [ :text ] . is_a? ( Regexp )
options [ :text ]
else
2017-01-26 19:10:51 -05:00
if exact_text == true
2017-03-01 12:45:47 -05:00
/ \ A #{ Regexp . escape ( options [ :text ] . to_s ) } \ z /
2016-12-23 15:17:45 -05:00
else
Regexp . escape ( options [ :text ] . to_s )
end
end
text_visible = visible
text_visible = :all if text_visible == :hidden
return false if not node . text ( text_visible ) . match ( regexp )
end
if exact_text . is_a? ( String )
2017-03-01 12:45:47 -05:00
regexp = / \ A #{ Regexp . escape ( options [ :exact_text ] ) } \ z /
2016-11-14 12:51:01 -05:00
text_visible = visible
text_visible = :all if text_visible == :hidden
return false if not node . text ( text_visible ) . match ( regexp )
2016-01-29 19:31:35 -05:00
end
2016-08-05 17:44:33 -04:00
2016-01-29 19:31:35 -05:00
case visible
when :visible then return false unless node . visible?
when :hidden then return false if node . visible?
end
2016-08-05 17:44:33 -04:00
2016-09-23 18:03:36 -04:00
res = query_filters . all? do | name , filter |
2016-01-29 19:31:35 -05:00
if options . has_key? ( name )
2016-08-05 17:44:33 -04:00
filter . matches? ( node , options [ name ] )
2016-01-29 19:31:35 -05:00
elsif filter . default?
2016-08-05 17:44:33 -04:00
filter . matches? ( node , filter . default )
else
true
2016-01-29 19:31:35 -05:00
end
end
2016-09-23 18:03:36 -04:00
2016-11-16 12:59:10 -05:00
res && = Capybara . using_wait_time ( 0 ) { @filter_block . call ( node ) } unless @filter_block . nil?
2016-09-23 18:03:36 -04:00
res
2016-01-29 19:31:35 -05:00
end
def visible
2016-10-02 21:28:52 -04:00
case ( vis = options . fetch ( :visible ) { @selector . default_visibility } )
when true then :visible
when false then :all
else vis
2016-01-29 19:31:35 -05:00
end
end
def exact?
2016-08-24 17:07:05 -04:00
return false if ! supports_exact?
2016-08-31 13:21:22 -04:00
options . fetch ( :exact , Capybara . exact )
2016-01-29 19:31:35 -05:00
end
def match
2016-08-31 13:21:22 -04:00
options . fetch ( :match , Capybara . match )
2016-01-29 19:31:35 -05:00
end
def xpath ( exact = nil )
2016-08-31 13:21:22 -04:00
exact = self . exact? if exact . nil?
2016-09-22 19:55:54 -04:00
expr = if @expression . respond_to? ( :to_xpath ) and exact
2016-01-29 19:31:35 -05:00
@expression . to_xpath ( :exact )
else
@expression . to_s
end
2016-09-22 19:55:54 -04:00
filtered_xpath ( expr )
2016-01-29 19:31:35 -05:00
end
def css
2016-09-22 19:55:54 -04:00
filtered_css ( @expression )
2016-01-29 19:31:35 -05:00
end
# @api private
def resolve_for ( node , exact = nil )
node . synchronize do
children = if selector . format == :css
node . find_css ( self . css )
else
node . find_xpath ( self . xpath ( exact ) )
end . map do | child |
if node . is_a? ( Capybara :: Node :: Base )
Capybara :: Node :: Element . new ( node . session , child , node , self )
else
Capybara :: Node :: Simple . new ( child )
end
end
Capybara :: Result . new ( children , self )
end
end
2016-08-24 17:07:05 -04:00
# @api private
def supports_exact?
@expression . respond_to? :to_xpath
end
2016-01-29 19:31:35 -05:00
private
def valid_keys
2016-09-01 18:19:48 -04:00
VALID_KEYS + custom_keys
2015-01-26 17:50:59 -05:00
end
def query_filters
if options . has_key? ( :filter_set )
Capybara :: Selector :: FilterSet . all [ options [ :filter_set ] ] . filters
else
@selector . custom_filters
end
end
def custom_keys
2016-09-22 19:55:54 -04:00
@custom_keys || = query_filters . keys + @selector . expression_filters
2016-01-29 19:31:35 -05:00
end
def assert_valid_keys
super
unless VALID_MATCH . include? ( match )
raise ArgumentError , " invalid option #{ match . inspect } for :match, should be one of #{ VALID_MATCH . map ( & :inspect ) . join ( " , " ) } "
end
end
2016-08-24 17:07:05 -04:00
2016-09-22 19:55:54 -04:00
def filtered_xpath ( expr )
if options . has_key? ( :id ) || options . has_key? ( :class )
expr = " ( #{ expr } ) "
expr = " #{ expr } [ #{ XPath . attr ( :id ) == options [ :id ] } ] " if options . has_key? ( :id ) && ! custom_keys . include? ( :id )
if options . has_key? ( :class ) && ! custom_keys . include? ( :class )
class_xpath = Array ( options [ :class ] ) . map do | klass |
" contains(concat(' ',normalize-space(@class),' '),' #{ klass } ') "
end . join ( " and " )
expr = " #{ expr } [ #{ class_xpath } ] "
end
end
expr
end
def filtered_css ( expr )
if options . has_key? ( :id ) || options . has_key? ( :class )
css_selectors = expr . split ( ',' ) . map ( & :rstrip )
expr = css_selectors . map do | sel |
sel += " # #{ Capybara :: Selector :: CSS . escape ( options [ :id ] ) } " if options . has_key? ( :id ) && ! custom_keys . include? ( :id )
sel += Array ( options [ :class ] ) . map { | k | " . #{ Capybara :: Selector :: CSS . escape ( k ) } " } . join if options . has_key? ( :class ) && ! custom_keys . include? ( :class )
sel
end . join ( " , " )
end
expr
end
2016-08-25 13:07:45 -04:00
def warn_exact_usage
2016-08-24 17:07:05 -04:00
if options . has_key? ( :exact ) && ! supports_exact?
2016-08-25 13:07:45 -04:00
warn " The :exact option only has an effect on queries using the XPath # is method. Using it with the query \" #{ expression . to_s } \" has no effect. "
2016-08-24 17:07:05 -04:00
end
end
2016-12-23 15:17:45 -05:00
def exact_text
2017-01-26 19:10:51 -05:00
options . fetch ( :exact_text , Capybara . exact_text )
2016-12-23 15:17:45 -05:00
end
2016-01-29 19:31:35 -05:00
end
end
end