Skip to content

Commit

Permalink
Add support for plugins to support actions with common UI libraries
Browse files Browse the repository at this point in the history
  • Loading branch information
twalpole committed Oct 2, 2018
1 parent 54a368b commit ab6135a
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 1 deletion.
8 changes: 8 additions & 0 deletions lib/capybara.rb
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,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 @@ -192,6 +196,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
4 changes: 4 additions & 0 deletions lib/capybara/node/actions.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# frozen_string_literal: true

require 'capybara/node/pluginify'

module Capybara
module Node
module Actions
Expand Down Expand Up @@ -266,6 +268,8 @@ def attach_file(locator = nil, paths, make_visible: nil, **options) # rubocop:di
end
end

prepend ::Capybara::Node::Pluginify

private

def find_select_or_datalist_input(from, options)
Expand Down
23 changes: 23 additions & 0 deletions lib/capybara/node/pluginify.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# 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, **options|
plugin_name = options.delete(:using) { |_using| Capybara.default_plugin[method_name] }
if plugin_name
plugin = Capybara.plugins[plugin_name]
raise ArgumentError, "Plugin not loaded: #{plugin_name}" unless plugin
raise NoMethodError, "Action not implemented in plugin: #{plugin_name}:#{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
Original file line number Diff line number Diff line change
@@ -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)
7 changes: 6 additions & 1 deletion lib/capybara/session/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class SessionConfig
automatic_reload match exact exact_text raise_server_errors visible_text_only
automatic_label_click enable_aria_label save_path asset_host default_host app_host
server_host server_port server_errors default_set_options disable_animation test_id
predicates_wait default_normalize_ws].freeze
predicates_wait default_normalize_ws default_plugin].freeze

attr_accessor(*OPTIONS)

Expand Down Expand Up @@ -108,6 +108,11 @@ def test_id=(id)
@test_id = id&.to_sym
end

remove_method :default_plugin
def default_plugin
@default_plugin ||= {}
end

def initialize_copy(other)
super
@server_errors = @server_errors.dup
Expand Down
68 changes: 68 additions & 0 deletions lib/capybara/spec/session/plugin_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# frozen_string_literal: true

require 'capybara/plugins/select2'

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

after do
Capybara.default_plugin = nil
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

it 'works when called on the select box' do
el = @session.find(:css, 'select#id_label_single', visible: false)
el.select 'Florida', using: :select2
expect(@session).to have_field(type: 'select', with: 'FL', visible: false)
end

it 'can set a default plugin to use' do
Capybara.default_plugin[:select] = :select2
@session.select 'Florida', from: 'Click this to focus the single select element'
expect(@session).to have_field(type: 'select', with: 'FL', visible: false)
end

it 'can override a default plugin' do
@session.visit('/form')
Capybara.default_plugin[:select] = :select2
@session.select 'Miss', from: 'Title', using: nil
expect(@session.find_field('Title').value).to eq('Miss')
end
end
7 changes: 7 additions & 0 deletions lib/capybara/spec/session/select_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@
expect(extract_results(@session)['locale']).to eq('sv')
end

it 'should select an option when called on the select box' do
el = @session.find(:css, 'select#form_locale')
el.select('Swedish')
@session.click_button('awesome')
expect(extract_results(@session)['locale']).to eq('sv')
end

it 'should escape quotes' do
@session.select("John's made-up language", from: 'Locale')
@session.click_button('awesome')
Expand Down

0 comments on commit ab6135a

Please sign in to comment.