Skip to content

Commit

Permalink
Move expression builder methods to instance
Browse files Browse the repository at this point in the history
  • Loading branch information
twalpole committed Nov 28, 2018
1 parent cb89ff8 commit d5caddd
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 180 deletions.
6 changes: 3 additions & 3 deletions lib/capybara/queries/selector_query.rb
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ def filtered_expression(expr)
conditions = {}
conditions[:id] = options[:id] if use_default_id_filter?
conditions[:class] = options[:class] if use_default_class_filter?
builder.add_attribute_conditions(expr, conditions)
builder(expr).add_attribute_conditions(conditions)
end

def use_default_id_filter?
Expand Down Expand Up @@ -359,8 +359,8 @@ def default_visibility
@selector.default_visibility(session_options.ignore_hidden_elements, options)
end

def builder
selector.builder
def builder(expr)
selector.builder(expr)
end
end
end
Expand Down
22 changes: 7 additions & 15 deletions lib/capybara/selector.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
end

Capybara.add_selector(:id) do
xpath { |id| builder.add_attribute_conditions(XPath.descendant, id: id) }
xpath { |id| builder(XPath.descendant).add_attribute_conditions(id: id) }
locator_filter { |node, id| id.is_a?(Regexp) ? node[:id] =~ id : true }
end

Expand Down Expand Up @@ -92,8 +92,7 @@

Capybara.add_selector(:link) do
xpath do |locator, href: true, alt: nil, title: nil, **|
xpath = XPath.descendant(:a)
xpath = xpath[@href_conditions = builder.attribute_conditions(href: href)]
xpath = builder(XPath.descendant(:a)).add_attribute_conditions(href: href)

unless locator.nil?
locator = locator.to_s
Expand All @@ -119,25 +118,18 @@
end

expression_filter(:download, valid_values: [true, false, String]) do |expr, download|
builder.add_attribute_conditions(expr, download: download)
builder(expr).add_attribute_conditions(download: download)
end

describe_expression_filters do |**options|
desc = +''
if (href = options[:href])
if !href.is_a?(Regexp)
desc << " with href #{href.inspect}"
elsif @href_conditions
desc << " with href matching #{href.inspect}"
end
desc << " with href #{'matching ' if href.is_a? Regexp}#{href.inspect}"
elsif options.key?(:href) # is nil/false specified?
desc << ' with no href attribute'
end
desc << ' with no href attribute' if options.fetch(:href, true).nil?
desc
end

describe_node_filters do |href: nil, **|
" with href matching #{href.inspect}" if href.is_a?(Regexp) && @href_conditions.nil?
end
end

Capybara.add_selector(:button) do
Expand Down Expand Up @@ -489,7 +481,7 @@
end

expression_filter(:attributes, matcher: /.+/) do |xpath, name, val|
builder.add_attribute_conditions(xpath, name => val)
builder(xpath).add_attribute_conditions(name => val)
end

node_filter(:attributes, matcher: /.+/) do |node, name, val|
Expand Down
108 changes: 56 additions & 52 deletions lib/capybara/selector/builders/css_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,69 +6,73 @@ 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 initialize(expression)
@expression = expression || ''
end

def add_attribute_conditions(selector, **attributes)
attributes.inject(selector) do |css, (name, value)|
conditions = if name == :class
class_conditions(value)
elsif value.is_a? Regexp
Selector::RegexpDisassembler.new(value).alternated_substrings.map do |strs|
strs.map do |str|
"[#{name}*='#{str}'#{' i' if value.casefold?}]"
end.join
end
else
[attribute_conditions(name => value)]
attr_reader :expression

def add_attribute_conditions(**attributes)
@expression = attributes.inject(expression) do |css, (name, value)|
conditions = if name == :class
class_conditions(value)
elsif value.is_a? Regexp
Selector::RegexpDisassembler.new(value).alternated_substrings.map do |strs|
strs.map do |str|
"[#{name}*='#{str}'#{' i' if value.casefold?}]"
end.join
end
else
[attribute_conditions(name => value)]
end

::Capybara::Selector::CSS.split(css).map do |sel|
next sel if conditions.empty?
::Capybara::Selector::CSS.split(css).map do |sel|
next sel if conditions.empty?

conditions.map { |cond| sel + cond }.join(', ')
end.join(', ')
end
conditions.map { |cond| sel + cond }.join(', ')
end.join(', ')
end
end

private
private

def class_conditions(classes)
case classes
def attribute_conditions(attributes)
attributes.map do |attribute, value|
case value
when XPath::Expression
raise ArgumentError, 'XPath expressions are not supported for the :class filter with CSS based selectors'
raise ArgumentError, "XPath expressions are not supported for the :#{attribute} filter with CSS based selectors"
when Regexp
Selector::RegexpDisassembler.new(classes).alternated_substrings.map do |strs|
strs.map do |str|
"[class*='#{str}'#{' i' if classes.casefold?}]"
end.join
end
Selector::RegexpDisassembler.new(value).substrings.map do |str|
"[#{attribute}*='#{str}'#{' i' if value.casefold?}]"
end.join
when true
"[#{attribute}]"
when false
':not([attribute])'
else
cls = Array(classes).group_by { |cl| cl.start_with?('!') && !cl.start_with?('!!!') }
[(cls[false].to_a.map { |cl| ".#{Capybara::Selector::CSS.escape(cl.sub(/^!!/, ''))}" } +
cls[true].to_a.map { |cl| ":not(.#{Capybara::Selector::CSS.escape(cl.slice(1..-1))})" }).join]
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
Selector::RegexpDisassembler.new(classes).alternated_substrings.map do |strs|
strs.map do |str|
"[class*='#{str}'#{' i' if classes.casefold?}]"
end.join
end
else
cls = Array(classes).group_by { |cl| cl.start_with?('!') && !cl.start_with?('!!!') }
[(cls[false].to_a.map { |cl| ".#{Capybara::Selector::CSS.escape(cl.sub(/^!!/, ''))}" } +
cls[true].to_a.map { |cl| ":not(.#{Capybara::Selector::CSS.escape(cl.slice(1..-1))})" }).join]
end
end
end
Expand Down
92 changes: 48 additions & 44 deletions lib/capybara/selector/builders/xpath_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,60 +6,64 @@ 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 initialize(expression)
@expression = expression || ''
end

def add_attribute_conditions(xpath, **conditions)
conditions.inject(xpath) do |xp, (name, value)|
conditions = name == :class ? class_conditions(value) : attribute_conditions(name => value)
if xp.is_a? XPath::Expression
xp[conditions]
else
"(#{xp})[#{conditions}]"
end
attr_reader :expression

def add_attribute_conditions(**conditions)
@expression = conditions.inject(expression) do |xp, (name, value)|
conditions = name == :class ? class_conditions(value) : attribute_conditions(name => value)
if xp.is_a? XPath::Expression
xp[conditions]
else
"(#{xp})[#{conditions}]"
end
end
end

private
private

def class_conditions(classes)
case classes
when XPath::Expression, Regexp
attribute_conditions(class: classes)
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
Array(classes).map do |klass|
if klass.start_with?('!') && !klass.start_with?('!!!')
!XPath.attr(:class).contains_word(klass.slice(1..-1))
else
XPath.attr(:class).contains_word(klass.sub(/^!!/, ''))
end
end.reduce(:&)
XPath.attr(attribute) == value.to_s
end
end
end.reduce(:&)
end

def regexp_to_xpath_conditions(regexp)
condition = XPath.current
condition = condition.uppercase if regexp.casefold?
Selector::RegexpDisassembler.new(regexp).alternated_substrings.map do |strs|
strs.map { |str| condition.contains(str) }.reduce(:&)
end.reduce(:|)
def class_conditions(classes)
case classes
when XPath::Expression, Regexp
attribute_conditions(class: classes)
else
Array(classes).map do |klass|
if klass.start_with?('!') && !klass.start_with?('!!!')
!XPath.attr(:class).contains_word(klass.slice(1..-1))
else
XPath.attr(:class).contains_word(klass.sub(/^!!/, ''))
end
end.reduce(:&)
end
end

def regexp_to_xpath_conditions(regexp)
condition = XPath.current
condition = condition.uppercase if regexp.casefold?
Selector::RegexpDisassembler.new(regexp).alternated_substrings.map do |strs|
strs.map { |str| condition.contains(str) }.reduce(:&)
end.reduce(:|)
end
end
end
end
4 changes: 2 additions & 2 deletions lib/capybara/selector/selector.rb
Original file line number Diff line number Diff line change
Expand Up @@ -409,15 +409,15 @@ def add_error(error_msg)
end

# @api private
def builder
def builder(expr = nil)
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.new(expr)
end

# @api private
Expand Down
10 changes: 5 additions & 5 deletions lib/capybara/spec/session/click_link_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
end

it "should raise error if link wasn't found" do
expect { @session.click_link('labore', href: 'invalid_href') }.to raise_error(Capybara::ElementNotFound)
expect { @session.click_link('labore', href: 'invalid_href') }.to raise_error(Capybara::ElementNotFound, /with href "invalid_href/)
end
end

Expand All @@ -104,8 +104,8 @@
end

it "should raise an error if no link's href matched the pattern" do
expect { @session.click_link('labore', href: /invalid_pattern/) }.to raise_error(Capybara::ElementNotFound)
expect { @session.click_link('labore', href: /.+d+/) }.to raise_error(Capybara::ElementNotFound)
expect { @session.click_link('labore', href: /invalid_pattern/) }.to raise_error(Capybara::ElementNotFound, %r{with href matching /invalid_pattern/})
expect { @session.click_link('labore', href: /.+d+/) }.to raise_error(Capybara::ElementNotFound, /#{Regexp.quote "with href matching /.+d+/"}/)
end

context 'href: nil' do
Expand All @@ -114,8 +114,8 @@
end

it 'should raise an error if href attribute exists' do
expect { @session.click_link('Blank Href', href: nil) }.to raise_error(Capybara::ElementNotFound)
expect { @session.click_link('Normal Anchor', href: nil) }.to raise_error(Capybara::ElementNotFound)
expect { @session.click_link('Blank Href', href: nil) }.to raise_error(Capybara::ElementNotFound, /with no href attribute/)
expect { @session.click_link('Normal Anchor', href: nil) }.to raise_error(Capybara::ElementNotFound, /with no href attribute/)
end
end
end
Expand Down
Loading

0 comments on commit d5caddd

Please sign in to comment.