Allow "select" of datalist values from input element
This commit is contained in:
parent
09924678e8
commit
da5e5aae98
|
@ -174,8 +174,42 @@ module Capybara
|
||||||
#
|
#
|
||||||
# @return [Capybara::Node::Element] The option element selected
|
# @return [Capybara::Node::Element] The option element selected
|
||||||
def select(value = nil, from: nil, **options)
|
def select(value = nil, from: nil, **options)
|
||||||
scope = from ? find(:select, from, options) : self
|
scope = if from
|
||||||
scope.find(:option, value, options).select_option
|
synchronize(Capybara::Queries::BaseQuery.wait(options, session_options.default_max_wait_time)) do
|
||||||
|
begin
|
||||||
|
find(:select, from, options)
|
||||||
|
rescue Capybara::ElementNotFound => select_error
|
||||||
|
raise if %i[selected with_selected multiple].any? { |option| options.key?(option) }
|
||||||
|
begin
|
||||||
|
find(:datalist_input, from, options)
|
||||||
|
rescue Capybara::ElementNotFound => dlinput_error
|
||||||
|
raise Capybara::ElementNotFound, "#{select_error.message} and #{dlinput_error.message}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
if scope.respond_to?(:tag_name) && scope.tag_name == "input"
|
||||||
|
begin
|
||||||
|
# TODO: this is a more efficient but won't work with non-JS drivers
|
||||||
|
# datalist_options = session.evaluate_script('Array.prototype.slice.call((arguments[0].list||{}).options || []).filter(function(el){ return !el.disabled }).map(function(el){ return { "value": el.value, "label": el.label} })', scope)
|
||||||
|
datalist_options = session.evaluate_script(DATALIST_OPTIONS_SCRIPT, scope)
|
||||||
|
if (option = datalist_options.find { |o| o['value'] == value || o['label'] == value })
|
||||||
|
scope.set(option["value"])
|
||||||
|
else
|
||||||
|
raise ::Capybara::ElementNotFound, "Unable to find datalist option \"#{value}\""
|
||||||
|
end
|
||||||
|
rescue ::Capybara::NotSupportedByDriverError
|
||||||
|
# Implement for drivers that don't support JS
|
||||||
|
datalist = find(:xpath, XPath.descendant(:datalist)[XPath.attr(:id) == scope[:list]], visible: false)
|
||||||
|
option = datalist.find(:datalist_option, value, disabled: false)
|
||||||
|
scope.set(option.value)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
scope.find(:option, value, options).select_option
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -291,6 +325,12 @@ module Capybara
|
||||||
delete el.capybara_style_cache;
|
delete el.capybara_style_cache;
|
||||||
}
|
}
|
||||||
JS
|
JS
|
||||||
|
|
||||||
|
DATALIST_OPTIONS_SCRIPT = <<-'JS'.freeze
|
||||||
|
Array.prototype.slice.call((arguments[0].list||{}).options || []).
|
||||||
|
filter(function(el){ return !el.disabled }).
|
||||||
|
map(function(el){ return { "value": el.value, "label": el.label} })
|
||||||
|
JS
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -90,7 +90,7 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
|
||||||
return true if string_node.disabled?
|
return true if string_node.disabled?
|
||||||
|
|
||||||
if %w[option optgroup].include? tag_name
|
if %w[option optgroup].include? tag_name
|
||||||
find_xpath("parent::*[self::optgroup or self::select]")[0].disabled?
|
find_xpath("parent::*[self::optgroup or self::select or self::datalist]")[0].disabled?
|
||||||
else
|
else
|
||||||
!find_xpath("parent::fieldset[@disabled] | ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]").empty?
|
!find_xpath("parent::fieldset[@disabled] | ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]").empty?
|
||||||
end
|
end
|
||||||
|
|
|
@ -407,6 +407,34 @@ Capybara.add_selector(:select) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Capybara.add_selector(:datalist_input) do
|
||||||
|
label "input box with datalist completion"
|
||||||
|
|
||||||
|
xpath do |locator, **options|
|
||||||
|
xpath = XPath.descendant(:input)[XPath.attr(:list)]
|
||||||
|
locate_field(xpath, locator, options)
|
||||||
|
end
|
||||||
|
|
||||||
|
filter_set(:_field, %i[disabled name placeholder])
|
||||||
|
|
||||||
|
filter(:options) do |node, options|
|
||||||
|
actual = node.find("//datalist[@id=#{node[:list]}]", visible: :all).all(:datalist_option, wait: false).map(&:value)
|
||||||
|
options.sort == actual.sort
|
||||||
|
end
|
||||||
|
|
||||||
|
filter(:with_options) do |node, options|
|
||||||
|
options.all? { |option| node.find("//datalist[@id=#{node[:list]}]", visible: :all).first(:datalist_option, option) }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe do |options: nil, with_options: nil, **opts|
|
||||||
|
desc = "".dup
|
||||||
|
desc << " with options #{options.inspect}" if options
|
||||||
|
desc << " with at least options #{with_options.inspect}" if with_options
|
||||||
|
desc << describe_all_expression_filters(opts)
|
||||||
|
desc
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
##
|
##
|
||||||
#
|
#
|
||||||
# Find option elements
|
# Find option elements
|
||||||
|
@ -433,6 +461,25 @@ Capybara.add_selector(:option) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Capybara.add_selector(:datalist_option) do
|
||||||
|
label "datalist option"
|
||||||
|
visible(:all)
|
||||||
|
|
||||||
|
xpath do |locator|
|
||||||
|
xpath = XPath.descendant(:option)
|
||||||
|
xpath = xpath[XPath.string.n.is(locator.to_s) | (XPath.attr(:value) == locator.to_s)] unless locator.nil?
|
||||||
|
xpath
|
||||||
|
end
|
||||||
|
|
||||||
|
filter(:disabled, :boolean) { |node, value| !(value ^ node.disabled?) }
|
||||||
|
|
||||||
|
describe do |**options|
|
||||||
|
desc = "".dup
|
||||||
|
desc << " that is#{' not' unless options[:disabled]} disabled" if options.key?(:disabled)
|
||||||
|
desc
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
##
|
##
|
||||||
#
|
#
|
||||||
# Find file input elements
|
# Find file input elements
|
||||||
|
|
|
@ -84,9 +84,35 @@ Capybara::SpecHelper.spec "#select" do
|
||||||
expect(@session.find_field('Title').value).to eq('Miss')
|
expect(@session.find_field('Title').value).to eq('Miss')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "input with datalist" do
|
||||||
|
it "should select an option" do
|
||||||
|
@session.select("Audi", from: 'manufacturer')
|
||||||
|
@session.click_button('awesome')
|
||||||
|
expect(extract_results(@session)['manufacturer']).to eq('Audi')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not find an input without a datalist" do
|
||||||
|
expect do
|
||||||
|
@session.select("Thomas", from: 'form_first_name')
|
||||||
|
end.to raise_error(/Unable to find visible input box with datalist completion "form_first_name" that is not disabled/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not select an option that doesn't exist" do
|
||||||
|
expect do
|
||||||
|
@session.select("Tata", from: 'manufacturer')
|
||||||
|
end.to raise_error(/Unable to find datalist option "Tata"/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not select a disabled option" do
|
||||||
|
expect do
|
||||||
|
@session.select("Mercedes", from: 'manufacturer')
|
||||||
|
end.to raise_error(/Unable to find datalist option "Mercedes"/)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context "with a locator that doesn't exist" do
|
context "with a locator that doesn't exist" do
|
||||||
it "should raise an error" do
|
it "should raise an error" do
|
||||||
msg = "Unable to find visible select box \"does not exist\" that is not disabled"
|
msg = /Unable to find visible select box "does not exist" that is not disabled/
|
||||||
expect do
|
expect do
|
||||||
@session.select('foo', from: 'does not exist')
|
@session.select('foo', from: 'does not exist')
|
||||||
end.to raise_error(Capybara::ElementNotFound, msg)
|
end.to raise_error(Capybara::ElementNotFound, msg)
|
||||||
|
|
|
@ -197,6 +197,15 @@ New line after and before textarea tag
|
||||||
</label>
|
</label>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<input type="text" name="form[manufacturer]" list="manufacturers" id="manufacturer"/>
|
||||||
|
<datalist id="manufacturers">
|
||||||
|
<option value="Jaguar">J</option>
|
||||||
|
<option value="Audi">
|
||||||
|
<option value="Mercedes" disabled>
|
||||||
|
</datalist>
|
||||||
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<input type="checkbox" name="form[valueless_checkbox]" id="valueless_checkbox" checked="checked"/>
|
<input type="checkbox" name="form[valueless_checkbox]" id="valueless_checkbox" checked="checked"/>
|
||||||
<label for="valueless_checkbox">Valueless Checkbox</label>
|
<label for="valueless_checkbox">Valueless Checkbox</label>
|
||||||
|
|
Loading…
Reference in New Issue