From e857058b9a89340127cedead45d26e2b5d9dfe96 Mon Sep 17 00:00:00 2001 From: Jonas Nicklas Date: Fri, 27 Aug 2010 20:37:39 +0200 Subject: [PATCH] Custom selectors can now take options, API changed This allows custom selectors to be run for certain objects, allowing very powerful abstractions. --- History.txt | 4 +-- lib/capybara.rb | 12 +------ lib/capybara/node/finders.rb | 8 ++--- lib/capybara/selector.rb | 47 ++++++++++++++++++++++++++ lib/capybara/spec/session/find_spec.rb | 17 +++++++++- 5 files changed, 68 insertions(+), 20 deletions(-) create mode 100644 lib/capybara/selector.rb diff --git a/History.txt b/History.txt index cd9ac873..cb4ceb57 100644 --- a/History.txt +++ b/History.txt @@ -19,8 +19,8 @@ Release date: ### Added * It's now possible to call all session methods on nodes, like `find('#foo').fill_in(...)` -* Custom selectors can be added with Capybara.add_selector -* The :id selector is added by default, use it lile `find(:id, 'foo')` +* Custom selectors can be added with Capybara::Selector.add +* The :id selector is added by default, use it lile `find(:id, 'foo')` or `find(:foo)` * Added Capybara.configure for less wordy configuration ### Fixed diff --git a/lib/capybara.rb b/lib/capybara.rb index 1d7392c5..9b30c00b 100644 --- a/lib/capybara.rb +++ b/lib/capybara.rb @@ -45,14 +45,6 @@ module Capybara def configure yield self end - - def add_selector(name, &block) - selectors[name.to_sym] = block - end - - def selectors - @selectors ||= {} - end end autoload :Server, 'capybara/server' @@ -60,6 +52,7 @@ module Capybara autoload :Node, 'capybara/node' autoload :Document, 'capybara/node' autoload :Element, 'capybara/node' + autoload :Selector, 'capybara/selector' autoload :VERSION, 'capybara/version' module Driver @@ -79,6 +72,3 @@ Capybara.configure do |config| config.ignore_hidden_elements = false end -Capybara.add_selector(:xpath) { |xpath| xpath } -Capybara.add_selector(:id) { |id| XPath.generate { |x| x.descendant(:*)[x.attr(:id) == id] } } -Capybara.add_selector(:css) { |css| XPath::HTML.from_css(css) } diff --git a/lib/capybara/node/finders.rb b/lib/capybara/node/finders.rb index a75c96b7..41194d5f 100644 --- a/lib/capybara/node/finders.rb +++ b/lib/capybara/node/finders.rb @@ -121,7 +121,8 @@ module Capybara def all(*args) options = if args.last.is_a?(Hash) then args.pop else {} end - results = XPath::HTML.wrap(normalize_locator(*args)).map do |path| + selector = Capybara::Selector.normalize(*args) + results = XPath::HTML.wrap(selector).map do |path| base.find(path) end.flatten @@ -140,11 +141,6 @@ module Capybara protected - def normalize_locator(kind, locator=nil) - kind, locator = Capybara.default_selector, kind if locator.nil? - Capybara.selectors[kind.to_sym].call(locator) - end - def wait_conditionally_until if driver.wait? then session.wait_until { yield } else yield end end diff --git a/lib/capybara/selector.rb b/lib/capybara/selector.rb new file mode 100644 index 00000000..aac40427 --- /dev/null +++ b/lib/capybara/selector.rb @@ -0,0 +1,47 @@ +module Capybara + class Selector + attr_reader :name, :options, :block + + class << self + def all + @selectors ||= {} + end + + def add(name, options={}, &block) + all[name.to_sym] = Capybara::Selector.new(name.to_sym, options, &block) + end + + def remove(name) + all.delete(name.to_sym) + end + + def normalize(name_or_locator, locator=nil) + if locator + all[name_or_locator.to_sym].call(locator) + else + selector = all.values.find { |s| s.match?(name_or_locator) } + selector ||= all[Capybara.default_selector] + selector.call(name_or_locator) + end + end + end + + def initialize(name, options={}, &block) + @name = name + @options = options + @block = block + end + + def call(locator) + @block.call(locator) + end + + def match?(locator) + @options[:for] and @options[:for] === locator + end + end +end + +Capybara::Selector.add(:xpath) { |xpath| xpath } +Capybara::Selector.add(:css) { |css| XPath::HTML.from_css(css) } +Capybara::Selector.add(:id, :for => Symbol) { |id| XPath.generate { |x| x.descendant(:*)[x.attr(:id) == id.to_s] } } diff --git a/lib/capybara/spec/session/find_spec.rb b/lib/capybara/spec/session/find_spec.rb index d9453b1c..7ea97386 100644 --- a/lib/capybara/spec/session/find_spec.rb +++ b/lib/capybara/spec/session/find_spec.rb @@ -4,6 +4,10 @@ shared_examples_for "find" do @session.visit('/with_html') end + after do + Capybara::Selector.remove(:monkey) + end + it "should find the first element using the given locator" do @session.find('//h1').text.should == 'This is a test' @session.find("//input[@id='test_field']")[:value].should == 'monkey' @@ -46,6 +50,7 @@ shared_examples_for "find" do it "should find the first element using the given locator" do @session.find(:id, 'john_monkey').text.should == 'Monkey John' @session.find(:id, 'red').text.should == 'Redirect' + @session.find(:red).text.should == 'Redirect' end end @@ -58,12 +63,22 @@ shared_examples_for "find" do context "with custom selector" do it "should use the custom selector" do - Capybara.add_selector(:monkey) { |name| ".//*[@id='#{name}_monkey']" } + Capybara::Selector.add(:monkey) { |name| ".//*[@id='#{name}_monkey']" } @session.find(:monkey, 'john').text.should == 'Monkey John' @session.find(:monkey, 'paul').text.should == 'Monkey Paul' end end + context "with custom selector with :for option" do + it "should use the selector when it matches the :for option" do + Capybara::Selector.add(:monkey, :for => Fixnum) { |num| ".//*[contains(@id, 'monkey')][#{num}]" } + @session.find(:monkey, '2').text.should == 'Monkey Paul' + @session.find(1).text.should == 'Monkey John' + @session.find(2).text.should == 'Monkey Paul' + @session.find('//h1').text.should == 'This is a test' + end + end + context "with css as default selector" do before { Capybara.default_selector = :css } it "should find the first element using the given locator" do