Skip to content

Commit

Permalink
Play with idea of plugins to support actions with common UI libraries
Browse files Browse the repository at this point in the history
  • Loading branch information
twalpole committed Jul 6, 2018
1 parent d3d57d7 commit ebd240b
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 1 deletion.
8 changes: 8 additions & 0 deletions lib/capybara.rb
Expand Up @@ -132,6 +132,10 @@ def register_server(name, &block)
servers[name.to_sym] = block
end

def register_plugin(name, plugin)
plugins[name.to_sym] = plugin
end

##
#
# Add a new selector to Capybara. Selectors can be used by various methods in Capybara
Expand Down Expand Up @@ -190,6 +194,10 @@ def servers
@servers ||= {}
end

def plugins
@plugins ||= {}
end

# Wraps the given string, which should contain an HTML document or fragment
# in a {Capybara::Node::Simple} which exposes all {Capybara::Node::Matchers},
# {Capybara::Node::Finders} and {Capybara::Node::DocumentMatchers}. This allows you to query
Expand Down
8 changes: 7 additions & 1 deletion lib/capybara/node/actions.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true

require 'capybara/node/pluginify'

module Capybara
module Node
module Actions
Expand Down Expand Up @@ -173,7 +175,9 @@ def uncheck(locator = nil, **options)
# @param from: [String] The id, Capybara.test_id atrtribute, name or label of the select box
#
# @return [Capybara::Node::Element] The option element selected
def select(value = nil, from: nil, **options)
def select(value = nil, from: nil, using: nil, **options)
return Capybara.plugins[using].select(self, value, from: from, **options) if using

el = from ? find_select_or_datalist_input(from, options) : self

if el.respond_to?(:tag_name) && (el.tag_name == "input")
Expand Down Expand Up @@ -237,6 +241,8 @@ def attach_file(locator = nil, path, make_visible: nil, **options) # rubocop:dis
end
end

prepend ::Capybara::Node::Pluginify

private

def find_select_or_datalist_input(from, options)
Expand Down
22 changes: 22 additions & 0 deletions lib/capybara/node/pluginify.rb
@@ -0,0 +1,22 @@
# frozen_string_literal: true

module Capybara
module Node
module Pluginify
def self.prepended(mod)
mod.public_instance_methods.each do |method_name|
define_method method_name do |*args, using: nil, **options|
if using
plugin = Capybara.plugins[using]
raise ArgumentError, "Plugin not loaded: #{using}" unless plugin
raise NoMethodError, "Action not implemented in plugin: #{using}:#{method_name}" unless plugin.respond_to?(method_name)
plugin.send(method_name, self, *args, **options)
else
super(*args, **options)
end
end
end
end
end
end
end
38 changes: 38 additions & 0 deletions lib/capybara/plugins/select2.rb
@@ -0,0 +1,38 @@
# frozen_string_literal: true

module Capybara
module Plugins
class Select2
def select(scope, value, from: nil, **options)
select2 = if from
scope.find(:select2, from, options.merge(visible: false))
else
select = scope.find(:option, value, options).ancestor(:css, 'select', visible: false)
select.find(:xpath, XPath.next_sibling(:span)[XPath.attr(:class).contains_word('select2')][XPath.attr(:class).contains_word('select2-container')])
end
select2.click
scope.find(:select2_option, value).click
end
end
end
end

Capybara.add_selector(:select2) do
xpath do |locator, **options|
xpath = XPath.descendant(:select)
xpath = locate_field(xpath, locator, options)
xpath = xpath.next_sibling(:span)[XPath.attr(:class).contains_word('select2')][XPath.attr(:class).contains_word('select2-container')]
xpath
end
end

Capybara.add_selector(:select2_option) do
xpath do |locator|
xpath = XPath.anywhere(:ul)[XPath.attr(:class).contains_word('select2-results__options')][XPath.attr(:id)]
xpath = xpath.descendant(:li)[XPath.attr(:role) == 'treeitem']
xpath = xpath[XPath.string.n.is(locator.to_s)] unless locator.nil?
xpath
end
end

Capybara.register_plugin(:select2, Capybara::Plugins::Select2.new)
46 changes: 46 additions & 0 deletions lib/capybara/spec/session/plugin_spec.rb
@@ -0,0 +1,46 @@
# frozen_string_literal: true

require 'capybara/plugins/select2'

Capybara::SpecHelper.spec "Plugin", requires: [:js] do
before do
@session.visit('https://select2.org/appearance')
end

it "should raise if wrong plugin specified" do
expect do
@session.select 'Florida', from: 'Click this to focus the single select element', using: :select3
end.to raise_error(ArgumentError, /Plugin not loaded/)
end

it "should raise if non-implemented action is called" do
expect do
@session.click_on('blah', using: :select2)
end.to raise_error(NoMethodError, /Action not implemented/)
end

it "should select an option" do
@session.select 'Florida', from: 'Click this to focus the single select element', using: :select2

expect(@session).to have_field(type: 'select', with: 'FL', visible: false)
end

it "should work with multiple select" do
@session.select 'Pennsylvania', from: 'Click this to focus the multiple select element', using: :select2
@session.select 'California', from: 'Click this to focus the multiple select element', using: :select2

expect(@session).to have_select(multiple: true, selected: %w[Pennsylvania California], visible: false)
end

it "should work with id" do
@session.select 'Florida', from: 'id_label_single', using: :select2
expect(@session).to have_field(type: 'select', with: 'FL', visible: false)
end

it "works without :from" do
@session.within(:css, 'div.s2-example:nth-of-type(2) p:first-child') do
@session.select 'Florida', using: :select2
expect(@session).to have_field(type: 'select', with: 'FL', visible: false)
end
end
end

0 comments on commit ebd240b

Please sign in to comment.