Move basic selector condition generation into builder classes
This commit is contained in:
parent
3778898a96
commit
3499480381
|
@ -222,15 +222,15 @@ module Capybara
|
|||
end
|
||||
|
||||
def filtered_xpath(expr)
|
||||
expr = "(#{expr})[#{xpath_from_id}]" if use_default_id_filter?
|
||||
expr = "(#{expr})[#{xpath_from_classes}]" if use_default_class_filter?
|
||||
expr = "(#{expr})[#{conditions_from_id}]" if use_default_id_filter?
|
||||
expr = "(#{expr})[#{conditions_from_classes}]" if use_default_class_filter?
|
||||
expr
|
||||
end
|
||||
|
||||
def filtered_css(expr)
|
||||
::Capybara::Selector::CSS.split(expr).map do |sel|
|
||||
sel += css_from_id if use_default_id_filter?
|
||||
sel += css_from_classes if use_default_class_filter?
|
||||
sel += conditions_from_id if use_default_id_filter?
|
||||
sel += conditions_from_classes if use_default_class_filter?
|
||||
sel
|
||||
end.join(', ')
|
||||
end
|
||||
|
@ -243,59 +243,12 @@ module Capybara
|
|||
options.key?(:class) && !custom_keys.include?(:class)
|
||||
end
|
||||
|
||||
def css_from_classes
|
||||
case options[:class]
|
||||
when XPath::Expression
|
||||
raise ArgumentError, 'XPath expressions are not supported for the :class filter with CSS based selectors'
|
||||
when Regexp
|
||||
strs = Selector::RegexpDisassembler.new(options[:class]).substrings
|
||||
strs.map { |str| "[class*='#{str}'#{' i' if options[:class].casefold?}]" }.join
|
||||
else
|
||||
classes = Array(options[:class]).group_by { |cl| cl.start_with? '!' }
|
||||
(classes[false].to_a.map { |cl| ".#{Capybara::Selector::CSS.escape(cl)}" } +
|
||||
classes[true].to_a.map { |cl| ":not(.#{Capybara::Selector::CSS.escape(cl.slice(1..-1))})" }).join
|
||||
end
|
||||
def conditions_from_classes
|
||||
builder.class_conditions(options[:class])
|
||||
end
|
||||
|
||||
def css_from_id
|
||||
case options[:id]
|
||||
when XPath::Expression
|
||||
raise ArgumentError, 'XPath expressions are not supported for the :id filter with CSS based selectors'
|
||||
when Regexp
|
||||
Selector::RegexpDisassembler.new(options[:id]).substrings.map do |str|
|
||||
"[id*='#{str}'#{' i' if options[:id].casefold?}]"
|
||||
end.join
|
||||
else
|
||||
"##{::Capybara::Selector::CSS.escape(options[:id])}"
|
||||
end
|
||||
end
|
||||
|
||||
def xpath_from_id
|
||||
case options[:id]
|
||||
when XPath::Expression
|
||||
XPath.attr(:id)[options[:id]]
|
||||
when Regexp
|
||||
XPath.attr(:id)[regexp_to_xpath_conditions(options[:id])]
|
||||
else
|
||||
XPath.attr(:id) == options[:id]
|
||||
end
|
||||
end
|
||||
|
||||
def xpath_from_classes
|
||||
case options[:class]
|
||||
when XPath::Expression
|
||||
XPath.attr(:class)[options[:class]]
|
||||
when Regexp
|
||||
XPath.attr(:class)[regexp_to_xpath_conditions(options[:class])]
|
||||
else
|
||||
Array(options[:class]).map do |klass|
|
||||
if klass.start_with?('!')
|
||||
!XPath.attr(:class).contains_word(klass.slice(1..-1))
|
||||
else
|
||||
XPath.attr(:class).contains_word(klass)
|
||||
end
|
||||
end.reduce(:&)
|
||||
end
|
||||
def conditions_from_id
|
||||
builder.attribute_conditions(id: options[:id])
|
||||
end
|
||||
|
||||
def apply_expression_filters(expression)
|
||||
|
@ -389,12 +342,8 @@ module Capybara
|
|||
!!node.text(text_visible, normalize_ws: normalize_ws).match(regexp)
|
||||
end
|
||||
|
||||
def regexp_to_xpath_conditions(regexp)
|
||||
condition = XPath.current
|
||||
condition = condition.uppercase if regexp.casefold?
|
||||
Selector::RegexpDisassembler.new(regexp).substrings.map do |str|
|
||||
condition.contains(str)
|
||||
end.reduce(:&)
|
||||
def builder
|
||||
selector.builder
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -90,18 +90,7 @@ end
|
|||
Capybara.add_selector(:link) do
|
||||
xpath(:title, :alt) do |locator, href: true, alt: nil, title: nil, **|
|
||||
xpath = XPath.descendant(:a)
|
||||
xpath = xpath[
|
||||
case href
|
||||
when nil, false
|
||||
!XPath.attr(:href)
|
||||
when true
|
||||
XPath.attr(:href)
|
||||
when Regexp
|
||||
XPath.attr(:href)[regexp_to_xpath_conditions(href)]
|
||||
else
|
||||
XPath.attr(:href) == href.to_s
|
||||
end
|
||||
]
|
||||
xpath = xpath[@href_conditions = builder.attribute_conditions(href: href)]
|
||||
|
||||
unless locator.nil?
|
||||
locator = locator.to_s
|
||||
|
@ -138,7 +127,7 @@ Capybara.add_selector(:link) do
|
|||
if (href = options[:href])
|
||||
if !href.is_a?(Regexp)
|
||||
desc << " with href #{href.inspect}"
|
||||
elsif regexp_to_xpath_conditions(href)
|
||||
elsif @href_conditions
|
||||
desc << " with href matching #{href.inspect}"
|
||||
end
|
||||
end
|
||||
|
@ -147,7 +136,7 @@ Capybara.add_selector(:link) do
|
|||
end
|
||||
|
||||
describe_node_filters do |href: nil, **|
|
||||
" with href matching #{href.inspect}" if href.is_a?(Regexp) && regexp_to_xpath_conditions(href).nil?
|
||||
" with href matching #{href.inspect}" if href.is_a?(Regexp) && @href_conditions.nil?
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -481,18 +470,7 @@ Capybara.add_selector(:element) do
|
|||
end
|
||||
|
||||
expression_filter(:attributes, matcher: /.+/) do |xpath, name, val|
|
||||
case val
|
||||
when Regexp
|
||||
xpath[XPath.attr(name)[regexp_to_xpath_conditions(val)]]
|
||||
when true
|
||||
xpath[XPath.attr(name)]
|
||||
when false
|
||||
xpath[!XPath.attr(name)]
|
||||
when XPath::Expression
|
||||
xpath[XPath.attr(name)[val]]
|
||||
else
|
||||
xpath[XPath.attr(name.to_sym) == val]
|
||||
end
|
||||
xpath[builder.attribute_conditions(name => val)]
|
||||
end
|
||||
|
||||
node_filter(:attributes, matcher: /.+/) do |node, name, val|
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'xpath'
|
||||
|
||||
module Capybara
|
||||
class Selector
|
||||
# @api private
|
||||
class CSSBuilder
|
||||
class << self
|
||||
def attribute_conditions(attributes)
|
||||
attributes.map do |attribute, value|
|
||||
case value
|
||||
when XPath::Expression
|
||||
raise ArgumentError, "XPath expressions are not supported for the :#{attribute} filter with CSS based selectors"
|
||||
when Regexp
|
||||
Selector::RegexpDisassembler.new(value).substrings.map do |str|
|
||||
"[#{attribute}*='#{str}'#{' i' if value.casefold?}]"
|
||||
end.join
|
||||
when true
|
||||
"[#{attribute}]"
|
||||
when false
|
||||
':not([attribute])'
|
||||
else
|
||||
if attribute == :id
|
||||
"##{::Capybara::Selector::CSS.escape(value)}"
|
||||
else
|
||||
"[#{attribute}='#{value}']"
|
||||
end
|
||||
end
|
||||
end.join
|
||||
end
|
||||
|
||||
def class_conditions(classes)
|
||||
case classes
|
||||
when XPath::Expression
|
||||
raise ArgumentError, 'XPath expressions are not supported for the :class filter with CSS based selectors'
|
||||
when Regexp
|
||||
strs = Selector::RegexpDisassembler.new(classes).substrings
|
||||
strs.map { |str| "[class*='#{str}'#{' i' if classes.casefold?}]" }.join
|
||||
else
|
||||
cls = Array(classes).group_by { |cl| cl.start_with? '!' }
|
||||
(cls[false].to_a.map { |cl| ".#{Capybara::Selector::CSS.escape(cl)}" } +
|
||||
cls[true].to_a.map { |cl| ":not(.#{Capybara::Selector::CSS.escape(cl.slice(1..-1))})" }).join
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,56 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'xpath'
|
||||
|
||||
module Capybara
|
||||
class Selector
|
||||
# @api private
|
||||
class XPathBuilder
|
||||
class << self
|
||||
def attribute_conditions(attributes)
|
||||
attributes.map do |attribute, value|
|
||||
case value
|
||||
when XPath::Expression
|
||||
XPath.attr(attribute)[value]
|
||||
when Regexp
|
||||
XPath.attr(attribute)[regexp_to_xpath_conditions(value)]
|
||||
when true
|
||||
XPath.attr(attribute)
|
||||
when false, nil
|
||||
!XPath.attr(attribute)
|
||||
else
|
||||
XPath.attr(attribute) == value.to_s
|
||||
end
|
||||
end.reduce(:&)
|
||||
end
|
||||
|
||||
def class_conditions(classes)
|
||||
case classes
|
||||
when XPath::Expression
|
||||
XPath.attr(:class)[classes]
|
||||
when Regexp
|
||||
XPath.attr(:class)[regexp_to_xpath_conditions(classes)]
|
||||
else
|
||||
Array(classes).map do |klass|
|
||||
if klass.start_with?('!')
|
||||
!XPath.attr(:class).contains_word(klass.slice(1..-1))
|
||||
else
|
||||
XPath.attr(:class).contains_word(klass)
|
||||
end
|
||||
end.reduce(:&)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def regexp_to_xpath_conditions(regexp)
|
||||
condition = XPath.current
|
||||
condition = condition.uppercase if regexp.casefold?
|
||||
Selector::RegexpDisassembler.new(regexp).substrings.map do |str|
|
||||
condition.contains(str)
|
||||
end.reduce(:&)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
module Capybara
|
||||
class Selector
|
||||
# @api private
|
||||
class RegexpDisassembler
|
||||
def initialize(regexp)
|
||||
@regexp = regexp
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
require 'capybara/selector/filter_set'
|
||||
require 'capybara/selector/css'
|
||||
require 'capybara/selector/regexp_disassembler'
|
||||
require 'capybara/selector/builders/xpath_builder'
|
||||
require 'capybara/selector/builders/css_builder'
|
||||
|
||||
module Capybara
|
||||
#
|
||||
|
@ -395,6 +397,18 @@ module Capybara
|
|||
vis.nil? ? fallback : vis
|
||||
end
|
||||
|
||||
# @api private
|
||||
def builder
|
||||
case format
|
||||
when :css
|
||||
Capybara::Selector::CSSBuilder
|
||||
when :xpath
|
||||
Capybara::Selector::XPathBuilder
|
||||
else
|
||||
raise NotImplementedError, "No builder exists for selector of type #{format}"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def enable_aria_label
|
||||
|
@ -445,14 +459,6 @@ module Capybara
|
|||
def find_by_class_attr(classes)
|
||||
Array(classes).map { |klass| XPath.attr(:class).contains_word(klass) }.reduce(:&)
|
||||
end
|
||||
|
||||
def regexp_to_xpath_conditions(regexp)
|
||||
condition = XPath.current
|
||||
condition = condition.uppercase if regexp.casefold?
|
||||
RegexpDisassembler.new(regexp).substrings.map do |str|
|
||||
condition.contains(str)
|
||||
end.reduce(:&)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in New Issue