Optimize attribute checking for :element selector

This commit is contained in:
Thomas Walpole 2018-09-21 12:26:55 -07:00
parent 318f595ca4
commit 907b9e269f
4 changed files with 44 additions and 5 deletions

View File

@ -97,7 +97,9 @@ Capybara.add_selector(:link) do
when true
XPath.attr(:href)
when Regexp
nil # needs to be handled in filter
regexp_to_substrings(href).map do |str|
XPath.attr(:href).contains(str)
end.reduce(&:&)
else
XPath.attr(:href) == href.to_s
end
@ -135,13 +137,19 @@ Capybara.add_selector(:link) do
describe_expression_filters do |**options|
desc = +''
desc << " with href #{options[:href].inspect}" if options[:href] && !options[:href].is_a?(Regexp)
if (href = options[:href])
if !href.is_a?(Regexp)
desc << " with href #{href.inspect}"
elsif regexp_to_substrings(href).any?
desc << " with href matching #{href.inspect}"
end
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
" with href matching #{href.inspect}" if href.is_a?(Regexp) && regexp_to_substrings(href).empty?
end
end
@ -477,7 +485,9 @@ Capybara.add_selector(:element) do
expression_filter(:attributes, matcher: /.+/) do |xpath, name, val|
case val
when Regexp
xpath
regexp_to_substrings(val).inject(xpath) do |xp, str|
xp[XPath.attr(name).contains(str)]
end
when true
xpath[XPath.attr(name)]
when false

View File

@ -444,6 +444,25 @@ module Capybara
def find_by_class_attr(classes)
Array(classes).map { |klass| XPath.attr(:class).contains_word(klass) }.reduce(:&)
end
def regexp_to_substrings(regexp)
return [] unless regexp.options.zero?
regexp.source.match(CONVERTIBLE_REGEXP) do |match|
match.captures.reject(&:empty?)
end || []
end
CONVERTIBLE_REGEXP = /
\A
\^? # start
([^\[\]\\^$.|?*+()]*) # leading literal characters
[^|]*? # do not try to convert expressions with alternates
(?<!\\) # skip metacharacters - ie has preceding slash
([^\[\]\\^$.|?*+()]*) # trailing literal characters
\$? # end
\z
/x
end
end

View File

@ -34,6 +34,6 @@ Capybara::SpecHelper.spec '#to_capybara_node' do
end.to raise_error(/^expected to find css "#second" within #<Capybara::Node::Element/)
expect do
expect(para).to have_link(href: %r{/without_simple_html})
end.to raise_error(%r{^expected to find visible link nil with href matching /\\/without_simple_html/ within #<Capybara::Node::Element})
end.to raise_error(%r{^expected to find link nil with href matching /\\/without_simple_html/ within #<Capybara::Node::Element})
end
end

View File

@ -257,6 +257,16 @@ RSpec.describe Capybara do
expect(string.find(:element, 'input', type: 'submit').value).to eq 'click me'
end
it 'supports regexp matching' do
expect(string.find(:element, 'input', type: /sub/).value).to eq 'click me'
expect(string.find(:element, 'input', title: /sub\w.*button/).value).to eq 'click me'
expect(string.find(:element, 'input', title: /sub.* b.*ton/).value).to eq 'click me'
expect(string.find(:element, 'input', title: /sub.*mit.*/).value).to eq 'click me'
expect(string.find(:element, 'input', title: /^submit button$/).value).to eq 'click me'
expect(string.find(:element, 'input', title: /^(?:submit|other) button$/).value).to eq 'click me'
expect(string.find(:element, 'input', title: /SuBmIt/i).value).to eq 'click me'
end
it 'still works with system keys' do
expect { string.all(:element, 'input', type: 'submit', count: 1) }.not_to raise_error
end