From 34a548efe8dd27e3e702babcd7d3f591df728eb4 Mon Sep 17 00:00:00 2001 From: Jonas Nicklas Date: Fri, 13 Aug 2010 15:34:01 +0200 Subject: [PATCH] cleanup! --- lib/capybara/node/finders.rb | 2 +- lib/capybara/xpath.rb | 287 +++++++++++++---------------------- spec/xpath_spec.rb | 173 --------------------- 3 files changed, 104 insertions(+), 358 deletions(-) delete mode 100644 spec/xpath_spec.rb diff --git a/lib/capybara/node/finders.rb b/lib/capybara/node/finders.rb index f4986449..8ae01973 100644 --- a/lib/capybara/node/finders.rb +++ b/lib/capybara/node/finders.rb @@ -121,7 +121,7 @@ module Capybara def all(*args) options = if args.last.is_a?(Hash) then args.pop else {} end - results = Capybara::XPath.tempwrap(normalize_locator(*args)).map do |path| + results = Capybara::XPath.wrap(normalize_locator(*args)).map do |path| base.find(path) end.flatten diff --git a/lib/capybara/xpath.rb b/lib/capybara/xpath.rb index 8f0906ed..372bda39 100644 --- a/lib/capybara/xpath.rb +++ b/lib/capybara/xpath.rb @@ -14,225 +14,144 @@ module Capybara # # This will generate an XPath that matches either a text field or a link. # - class XPath + module XPath - class << self - include ::XPath + include ::XPath + extend self - def from_css(css) - collection(*Nokogiri::CSS.xpath_for(css).map { |selector| ::XPath::Expression::Literal.new(:".#{selector}") }.flatten) - end - alias_method :for_css, :from_css + def from_css(css) + collection(*Nokogiri::CSS.xpath_for(css).map { |selector| ::XPath::Expression::Literal.new(:".#{selector}") }.flatten) + end + alias_method :for_css, :from_css - def collection(*args) - ::XPath::Collection.new(*args) - end + def collection(*args) + ::XPath::Collection.new(*args) + end - def link(locator) - link = descendant(:a)[attr(:href)] - link[attr(:id).equals(locator) | text.is(locator) | attr(:title).is(locator) | descendant(:img)[attr(:alt).is(locator)]] - end + def link(locator) + link = descendant(:a)[attr(:href)] + link[attr(:id).equals(locator) | text.is(locator) | attr(:title).is(locator) | descendant(:img)[attr(:alt).is(locator)]] + end - def content(locator) - child(:"descendant-or-self::*")[current.n.contains(locator)] - end + def content(locator) + child(:"descendant-or-self::*")[current.n.contains(locator)] + end - def button(locator) - collection( - descendant(:input)[attr(:type).one_of('submit', 'image', 'button')][attr(:id).equals(locator) | attr(:value).is(locator)], - descendant(:button)[attr(:id).equals(locator) | attr(:value).is(locator) | text.is(locator)], - descendant(:input)[attr(:type).equals('image')][attr(:alt).is(locator)] - ) - end + def button(locator) + collection( + descendant(:input)[attr(:type).one_of('submit', 'image', 'button')][attr(:id).equals(locator) | attr(:value).is(locator)], + descendant(:button)[attr(:id).equals(locator) | attr(:value).is(locator) | text.is(locator)], + descendant(:input)[attr(:type).equals('image')][attr(:alt).is(locator)] + ) + end - def link_or_button(locator) - collection(link(locator), button(locator)) - end + def link_or_button(locator) + collection(link(locator), button(locator)) + end - def fieldset(locator) - descendant(:fieldset)[attr(:id).equals(locator) | descendant(:legend)[text.is(locator)]] - end + def fieldset(locator) + descendant(:fieldset)[attr(:id).equals(locator) | descendant(:legend)[text.is(locator)]] + end - def field(locator, options={}) - if options[:with] - fillable_field(locator, options) - else - xpath = descendant(:input, :textarea, :select)[attr(:type).one_of('submit', 'image', 'hidden').inverse] - xpath = locate_field(xpath, locator) - xpath = xpath[attr(:checked)] if options[:checked] - xpath = xpath[attr(:checked).inverse] if options[:unchecked] - xpath - end - end - - def fillable_field(locator, options={}) - xpath = descendant(:input, :textarea)[attr(:type).one_of('submit', 'image', 'radio', 'checkbox', 'hidden', 'file').inverse] + def field(locator, options={}) + if options[:with] + fillable_field(locator, options) + else + xpath = descendant(:input, :textarea, :select)[attr(:type).one_of('submit', 'image', 'hidden').inverse] xpath = locate_field(xpath, locator) - xpath = xpath[field_value(options[:with])] if options.has_key?(:with) + xpath = xpath[attr(:checked)] if options[:checked] + xpath = xpath[attr(:checked).inverse] if options[:unchecked] xpath end - - def select(locator, options={}) - xpath = locate_field(descendant(:select), locator) - - options[:options].each do |option| - xpath = xpath[descendant(:option).text.equals(option)] - end if options[:options] - - [options[:selected]].flatten.each do |option| - xpath = xpath[descendant(:option)[attr(:selected)].text.equals(option)] - end if options[:selected] - - xpath - end - - def checkbox(locator, options={}) - xpath = locate_field(descendant(:input)[attr(:type).equals('checkbox')], locator) - end - - def radio_button(locator, options={}) - locate_field(descendant(:input)[attr(:type).equals('radio')], locator) - end - - def file_field(locator, options={}) - locate_field(descendant(:input)[attr(:type).equals('file')], locator) - end - - def field_value(value) - (text.is(value) & name.equals('textarea')) | (attr(:value).equals(value) & name.equals('textarea').inverse) - end - - def locate_field(xpath, locator) - collection( - xpath[attr(:id).equals(locator) | attr(:name).equals(locator) | attr(:id).equals(anywhere(:label)[text.is(locator)].attr(:for))], - descendant(:label)[text.is(locator)].descendant(xpath) - ) - end - - def table(locator, options={}) - xpath = descendant(:table)[attr(:id).equals(locator) | descendant(:caption).contains(locator)] - xpath = xpath[table_rows(options[:rows])] if options[:rows] - xpath - end - - def table_rows(rows) - row_conditions = descendant(:tr)[table_row(rows.first)] - rows.drop(1).each do |row| - row_conditions = row_conditions.next_sibling(:tr)[table_row(row)] - end - row_conditions - end - - def table_row(cells) - cell_conditions = child(:td, :th)[text.equals(cells.first)] - cells.drop(1).each do |cell| - cell_conditions = cell_conditions.next_sibling(:td, :th)[text.equals(cell)] - end - cell_conditions - end - - def escape(string) - if string.include?("'") - string = string.split("'", -1).map do |substr| - "'#{substr}'" - end.join(%q{,"'",}) - "concat(#{string})" - else - "'#{string}'" - end - end - - def wrap(path) - if path.is_a?(self) - path - else - new(path.to_s) - end - end - - def tempwrap(path) - if path.is_a?(::XPath::Expression) or path.is_a?(::XPath::Collection) - paths = path.to_xpaths - paths - else - wrap(path).paths - end - end - - def respond_to?(method) - new.respond_to?(method) - end - - def method_missing(*args) - new.send(*args) - end end - attr_reader :paths - - def initialize(*paths) - @paths = paths + def fillable_field(locator, options={}) + xpath = descendant(:input, :textarea)[attr(:type).one_of('submit', 'image', 'radio', 'checkbox', 'hidden', 'file').inverse] + xpath = locate_field(xpath, locator) + xpath = xpath[field_value(options[:with])] if options.has_key?(:with) + xpath end - def to_s - @paths.join(' | ') + def select(locator, options={}) + xpath = locate_field(descendant(:select), locator) + + options[:options].each do |option| + xpath = xpath[descendant(:option).text.equals(option)] + end if options[:options] + + [options[:selected]].flatten.each do |option| + xpath = xpath[descendant(:option)[attr(:selected)].text.equals(option)] + end if options[:selected] + + xpath end - def append(path) - XPath.new(*[@paths, XPath.wrap(path).paths].flatten) + def checkbox(locator, options={}) + xpath = locate_field(descendant(:input)[attr(:type).equals('checkbox')], locator) end - def prepend(path) - XPath.new(*[XPath.wrap(path).paths, @paths].flatten) + def radio_button(locator, options={}) + locate_field(descendant(:input)[attr(:type).equals('radio')], locator) end + def file_field(locator, options={}) + locate_field(descendant(:input)[attr(:type).equals('file')], locator) + end - - + def field_value(value) + (text.is(value) & name.equals('textarea')) | (attr(:value).equals(value) & name.equals('textarea').inverse) + end def option(name) - append(".//option[normalize-space(text())=#{s(name)}]").append(".//option[contains(.,#{s(name)})]") + descendant(:option)[text.n.is(name)] end - protected - - def input_field(type, locator, options={}) - options = options.merge(:value => options[:with]) if options.has_key?(:with) - add_field(locator, ".//input[@type='#{type}']", options) + def locate_field(xpath, locator) + collection( + xpath[attr(:id).equals(locator) | attr(:name).equals(locator) | attr(:id).equals(anywhere(:label)[text.is(locator)].attr(:for))], + descendant(:label)[text.is(locator)].descendant(xpath) + ) end - # place this between to nodes to indicate that they should be siblings - def sibling + def table(locator, options={}) + xpath = descendant(:table)[attr(:id).equals(locator) | descendant(:caption).contains(locator)] + xpath = xpath[table_rows(options[:rows])] if options[:rows] + xpath end - def add_field(locator, field, options={}) - postfix = extract_postfix(options) - xpath = append("#{field}[@id=#{s(locator)}]#{postfix}") - xpath = xpath.append("#{field}[@name=#{s(locator)}]#{postfix}") - xpath = xpath.append("#{field}[@id=//label[contains(.,#{s(locator)})]/@for]#{postfix}") - # FIXME: Label should not be scoped to node, temporary workaround!!! - xpath = xpath.append(".//label[contains(.,#{s(locator)})]/#{field}#{postfix}") - xpath.prepend("#{field}[@id=//label[text()=#{s(locator)}]/@for]#{postfix}") + def table_rows(rows) + row_conditions = descendant(:tr)[table_row(rows.first)] + rows.drop(1).each do |row| + row_conditions = row_conditions.next_sibling(:tr)[table_row(row)] + end + row_conditions end - def extract_postfix(options) - options.inject("") do |postfix, (key, value)| - case key - when :value then postfix += "[@value=#{s(value)}]" - when :text then postfix += "[text()=#{s(value)}]" - when :checked then postfix += "[@checked]" - when :unchecked then postfix += "[not(@checked)]" - when :options then postfix += value.map { |o| "[.//option/text()=#{s(o)}]" }.join - when :selected then postfix += [value].flatten.map { |o| "[.//option[@selected]/text()=#{s(o)}]" }.join - end - postfix + def table_row(cells) + cell_conditions = child(:td, :th)[text.equals(cells.first)] + cells.drop(1).each do |cell| + cell_conditions = cell_conditions.next_sibling(:td, :th)[text.equals(cell)] + end + cell_conditions + end + + def escape(string) + if string.include?("'") + string = string.split("'", -1).map do |substr| + "'#{substr}'" + end.join(%q{,"'",}) + "concat(#{string})" + else + "'#{string}'" end end - - # Sanitize a String for putting it into an xpath query - def s(string) - XPath.escape(string) + + def wrap(path) + if path.respond_to?(:to_xpaths) + path.to_xpaths + else + [path.to_s].flatten + end end - end end diff --git a/spec/xpath_spec.rb b/spec/xpath_spec.rb deleted file mode 100644 index cf1dc3a6..00000000 --- a/spec/xpath_spec.rb +++ /dev/null @@ -1,173 +0,0 @@ -require File.expand_path('spec_helper', File.dirname(__FILE__)) - -describe Capybara::XPath do - - before do - @driver = Capybara::Driver::RackTest.new(TestApp) - @driver.visit('/form') - @xpath = Capybara::XPath.new - end - - it "should proxy any class method calls to a new instance" do - @query = Capybara::XPath.fillable_field('First Name').to_s - @driver.find(@query).first.value.should == 'John' - end - - it "should respond to instance methods at the class level" do - Capybara::XPath.should respond_to(:fillable_field) - end - - describe '.wrap' do - it "should return an XPath unmodified" do - Capybara::XPath.wrap(@xpath).should == @xpath - end - - it "should wrap a string in an xpath object" do - @xpath = Capybara::XPath.wrap('//foo/bar') - @xpath.should be_an_instance_of(Capybara::XPath) - @xpath.paths.should == ['//foo/bar'] - end - end - - describe '#append' do - it "should append an XPath's paths" do - @xpath = Capybara::XPath.wrap('//test') - @xpath = @xpath.append(Capybara::XPath.wrap('//foo/bar')) - @xpath.paths.should == ['//test', '//foo/bar'] - end - - it "should append an String as a new path" do - @xpath = Capybara::XPath.wrap('//test') - @xpath = @xpath.append('//foo/bar') - @xpath.paths.should == ['//test', '//foo/bar'] - end - end - - describe '#prepend' do - it "should prepend an XPath's paths" do - @xpath = Capybara::XPath.wrap('//test') - @xpath = @xpath.prepend(Capybara::XPath.wrap('//foo/bar')) - @xpath.paths.should == ['//foo/bar', '//test'] - end - - it "should prepend an String as a new path" do - @xpath = Capybara::XPath.wrap('//test') - @xpath = @xpath.prepend('//foo/bar') - @xpath.paths.should == ['//foo/bar', '//test'] - end - end - - describe '#field' do - it "should find any field by id or label" do - @query = @xpath.field('First Name').to_s - @driver.find(@query).first.value.should == 'John' - @query = @xpath.field('Password').to_s - @driver.find(@query).first.value.should == 'seeekrit' - @query = @xpath.field('Description').to_s - @driver.find(@query).first.text.should == 'Descriptive text goes here' - @query = @xpath.field('Document').to_s - @driver.find(@query).first[:name].should == 'form[document]' - @query = @xpath.field('Cat').to_s - @driver.find(@query).first.value.should == 'cat' - @query = @xpath.field('Male').to_s - @driver.find(@query).first.value.should == 'male' - @query = @xpath.field('Region').to_s - @driver.find(@query).first[:name].should == 'form[region]' - end - - it "should be chainable" do - @query = @xpath.field('First Name').button('Click me!').to_s - @driver.find(@query).first.value.should == 'John' - end - end - - describe '#fillable_field' do - it "should find a text field, password field, or text area by id or label" do - @query = @xpath.fillable_field('First Name').to_s - @driver.find(@query).first.value.should == 'John' - @query = @xpath.fillable_field('Password').to_s - @driver.find(@query).first.value.should == 'seeekrit' - @query = @xpath.fillable_field('Description').to_s - @driver.find(@query).first.text.should == 'Descriptive text goes here' - end - - it "should be chainable" do - @query = @xpath.fillable_field('First Name').button('Click me!').to_s - @driver.find(@query).first.value.should == 'John' - end - end - - describe '#text_area' do - it "should find a text area by id or label" do - @query = @xpath.text_area('form_description').to_s - @driver.find(@query).first.text.should == 'Descriptive text goes here' - @query = @xpath.text_area('Description').to_s - @driver.find(@query).first.text.should == 'Descriptive text goes here' - end - - it "should be chainable" do - @query = @xpath.text_area('Description').button('Click me!').to_s - @driver.find(@query).first.text.should == 'Descriptive text goes here' - end - end - - describe '#button' do - it "should find a button by id or content" do - @query = @xpath.button('awe123').to_s - @driver.find(@query).first.value.should == 'awesome' - @query = @xpath.button('okay556').to_s - @driver.find(@query).first.value.should == 'okay' - @query = @xpath.button('click_me_123').to_s - @driver.find(@query).first.value.should == 'click_me' - @query = @xpath.button('Click me!').to_s - @driver.find(@query).first.value.should == 'click_me' - @query = @xpath.button('fresh_btn').to_s - @driver.find(@query).first.value.should == 'i am fresh' - @query = @xpath.button('i am fresh').to_s - @driver.find(@query).first[:name].should == 'form[fresh]' - end - end - - describe '#radio_button' do - it "should find a radio button by id or label" do - @query = @xpath.radio_button('Male').to_s - @driver.find(@query).first.value.should == 'male' - @query = @xpath.radio_button('gender_male').to_s - @driver.find(@query).first.value.should == 'male' - end - - it "should be chainable" do - @query = @xpath.radio_button('Male').button('Click me!').to_s - @driver.find(@query).first.value.should == 'male' - end - end - - describe '#checkbox' do - it "should find a checkbox by id or label" do - @query = @xpath.checkbox('Cat').to_s - @driver.find(@query).first.value.should == 'cat' - @query = @xpath.checkbox('form_pets_cat').to_s - @driver.find(@query).first.value.should == 'cat' - end - - it "should be chainable" do - @query = @xpath.checkbox('Cat').button('Click me!').to_s - @driver.find(@query).first.value.should == 'cat' - end - end - - describe '#select' do - it "should find a select by id or label" do - @query = @xpath.select('Region').to_s - @driver.find(@query).first[:name].should == 'form[region]' - @query = @xpath.select('form_region').to_s - @driver.find(@query).first[:name].should == 'form[region]' - end - - it "should be chainable" do - @query = @xpath.select('Region').button('Click me!').to_s - @driver.find(@query).first[:name].should == 'form[region]' - end - end - -end