From a8d0cebde3880b98444c2c53cf7952d31400826b Mon Sep 17 00:00:00 2001 From: Kieran Pilkington Date: Mon, 31 Aug 2009 18:34:04 +1200 Subject: [PATCH] Various improvements, refinements, and bugfixes for select field usage. Multiple select boxes are now handled a lot better. * You can now select more than one field (where as previously, the last one select would override previous selections) * You can now unselect a value. It works like select, takes the same params, but removes the selection value Issues regarding ampersands in values have been resolved. * Values are now unescaped when the to_param method is run on elements, to make assertions less complicated * Locating an option with ampersand values now works as expected (it will check for both escaped and unescaped occurrences) Bunch of new specs and 3 broken pending ones have now been fixed. [#145 status:resolved] --- lib/webrat/core/elements/field.rb | 83 ++++++++- lib/webrat/core/elements/select_option.rb | 9 + .../core/locators/select_option_locator.rb | 8 +- lib/webrat/core/methods.rb | 1 + lib/webrat/core/scope.rb | 15 ++ lib/webrat/core/session.rb | 1 + spec/public/click_button_spec.rb | 22 ++- spec/public/select_spec.rb | 168 +++++++++++++++++- 8 files changed, 285 insertions(+), 22 deletions(-) diff --git a/lib/webrat/core/elements/field.rb b/lib/webrat/core/elements/field.rb index 91c9243..53a72d0 100644 --- a/lib/webrat/core/elements/field.rb +++ b/lib/webrat/core/elements/field.rb @@ -38,7 +38,12 @@ module Webrat def self.field_class(element) case element.name when "button" then ButtonField - when "select" then SelectField + when "select" + if element.attributes["multiple"].nil? + SelectField + else + MultipleSelectField + end when "textarea" then TextareaField else case element["type"] @@ -82,14 +87,16 @@ module Webrat def to_param return nil if disabled? - case Webrat.configuration.mode + params = case Webrat.configuration.mode when :rails parse_rails_request_params("#{name}=#{escaped_value}") when :merb ::Merb::Parse.query("#{name}=#{escaped_value}") else - { name => value } + { name => [*@value].first.to_s } end + + unescape_params(params) end def set(value) @@ -135,6 +142,21 @@ module Webrat CGI.escape([*@value].first.to_s) end + # Because we have to escape it before sending it to the above case statement, + # we have to make sure we unescape each value when it gets back so assertions + # involving characters like <, >, and & work as expected + def unescape_params(params) + case params.class.name + when 'Hash', 'Mash' + params.each { |key,value| params[key] = unescape_params(value) } + params + when 'Array' + params.collect { |value| unescape_params(value) } + else + CGI.unescapeHTML(params) + end + end + def labels @labels ||= label_elements.map do |element| Label.load(@session, element) @@ -385,13 +407,17 @@ module Webrat class SelectField < Field #:nodoc: def self.xpath_search - [".//select"] + [".//select[not(@multiple)]"] end def options @options ||= SelectOption.load_all(@session, @element) end + def unset(value) + @value = [] + end + protected def default_value @@ -405,4 +431,53 @@ module Webrat end end + + class MultipleSelectField < SelectField #:nodoc: + + def self.xpath_search + [".//select[@multiple='multiple']"] + end + + def set(value) + @value << value + end + + def unset(value) + @value.delete(value) + end + + # We have to overide how the uri string is formed when dealing with multiples + # Where normally a select field might produce name=value with a multiple, + # we need to form something like name[]=value1&name[]=value2 + def to_param + return nil if disabled? + + uri_string = @value.collect {|value| "#{name}=#{CGI.escape(value)}"}.join("&") + params = case Webrat.configuration.mode + when :rails + parse_rails_request_params(uri_string) + when :merb + ::Merb::Parse.query(uri_string) + else + { name => @value } + end + + unescape_params(params) + end + + protected + + # Overwrite SelectField definition because we don't want to select the first option + # (mutliples don't select the first option unlike their non multiple versions) + def default_value + selected_options = @element.xpath(".//option[@selected = 'selected']") + + selected_options.map do |option| + return "" if option.nil? + option["value"] || option.inner_html + end.uniq + end + + end + end diff --git a/lib/webrat/core/elements/select_option.rb b/lib/webrat/core/elements/select_option.rb index c0763b4..8e19e4f 100644 --- a/lib/webrat/core/elements/select_option.rb +++ b/lib/webrat/core/elements/select_option.rb @@ -12,6 +12,11 @@ module Webrat select.set(value) end + def unchoose + select.raise_error_if_disabled + select.unset(value) + end + protected def select @@ -31,5 +36,9 @@ module Webrat @element["value"] || @element.inner_html end + def label + @element.inner_html + end + end end diff --git a/lib/webrat/core/locators/select_option_locator.rb b/lib/webrat/core/locators/select_option_locator.rb index 674ad1a..10f509b 100644 --- a/lib/webrat/core/locators/select_option_locator.rb +++ b/lib/webrat/core/locators/select_option_locator.rb @@ -21,7 +21,7 @@ module Webrat if @option_text.is_a?(Regexp) o.element.inner_html =~ @option_text else - o.element.inner_html == @option_text.to_s + escaped_or_non_escaped_values.include?(o.element.inner_html) end end else @@ -29,7 +29,7 @@ module Webrat if @option_text.is_a?(Regexp) o.inner_html =~ @option_text else - o.inner_html == @option_text.to_s + escaped_or_non_escaped_values.include?(o.inner_html) end end @@ -37,6 +37,10 @@ module Webrat end end + def escaped_or_non_escaped_values + [@option_text.to_s, CGI.escapeHTML(@option_text.to_s)] + end + def option_elements @dom.xpath(*SelectOption.xpath_search) end diff --git a/lib/webrat/core/methods.rb b/lib/webrat/core/methods.rb index d6b2aee..7c0788d 100644 --- a/lib/webrat/core/methods.rb +++ b/lib/webrat/core/methods.rb @@ -39,6 +39,7 @@ module Webrat :unchecks, :uncheck, :chooses, :choose, :selects, :select, + :unselects, :unselect, :attaches_file, :attach_file, :current_page, :current_url, diff --git a/lib/webrat/core/scope.rb b/lib/webrat/core/scope.rb index 6c66b87..41cb869 100644 --- a/lib/webrat/core/scope.rb +++ b/lib/webrat/core/scope.rb @@ -112,6 +112,21 @@ module Webrat webrat_deprecate :selects, :select + # Verifies that a an option element exists on the current page with the specified + # text. You can optionally restrict the search to a specific select list by + # assigning options[:from] the value of the select list's name or + # a label. Remove the option's value before the form is submitted. + # + # Examples: + # unselect "January" + # unselect "February", :from => "event_month" + # unselect "February", :from => "Event Month" + def unselect(option_text, options={}) + select_option(option_text, options[:from]).unchoose + end + + webrat_deprecate :unselects, :unselect + DATE_TIME_SUFFIXES = { :year => '1i', :month => '2i', diff --git a/lib/webrat/core/session.rb b/lib/webrat/core/session.rb index ec3e1e7..ee180d7 100644 --- a/lib/webrat/core/session.rb +++ b/lib/webrat/core/session.rb @@ -260,6 +260,7 @@ For example: def_delegators :current_scope, :uncheck, :unchecks def_delegators :current_scope, :choose, :chooses def_delegators :current_scope, :select, :selects + def_delegators :current_scope, :unselect, :unselects def_delegators :current_scope, :select_datetime, :selects_datetime def_delegators :current_scope, :select_date, :selects_date def_delegators :current_scope, :select_time, :selects_time diff --git a/spec/public/click_button_spec.rb b/spec/public/click_button_spec.rb index 4bea34e..2963004 100644 --- a/spec/public/click_button_spec.rb +++ b/spec/public/click_button_spec.rb @@ -331,18 +331,16 @@ describe "click_button" do end it "should properly handle HTML entities in textarea default values" do - pending "needs bug fix" do - with_html <<-HTML - -
- - -
- - HTML - webrat_session.should_receive(:post).with("/posts", "post" => {"body" => "Peanut butter & jelly"}) - click_button - end + with_html <<-HTML + +
+ + +
+ + HTML + webrat_session.should_receive(:post).with("/posts", "post" => {"body" => "Peanut butter & jelly"}) + click_button end it "should send default selected option value from select" do diff --git a/spec/public/select_spec.rb b/spec/public/select_spec.rb index 98dfca0..22f722f 100644 --- a/spec/public/select_spec.rb +++ b/spec/public/select_spec.rb @@ -201,7 +201,6 @@ describe "select" do end it "should properly handle submitting HTML entities in select values" do - pending "needs bug fix" do with_html <<-HTML
@@ -212,11 +211,9 @@ describe "select" do HTML webrat_session.should_receive(:post).with("/login", "month" => "Peanut butter & jelly") click_button - end end it "should properly handle locating with HTML entities in select values" do - pending "needs bug fix" do with_html <<-HTML @@ -229,7 +226,6 @@ describe "select" do lambda { select "Peanut butter & jelly" }.should_not raise_error(Webrat::NotFoundError) - end end it "should submit duplicates selected options as a single value" do @@ -246,4 +242,168 @@ describe "select" do click_button end + it "should allow fields to be unselected" do + with_html <<-HTML + + + + +
+ + HTML + + webrat_session.should_receive(:post).with("/login", {"clothes"=>""}) + unselect "tshirt" + click_button + end + + # + # Mutliple Selection Fields + # + + it "should not submit any values for multiples without any selected" do + with_html <<-HTML + +
+ + +
+ + HTML + + webrat_session.should_receive(:post).with("/login", {}) + click_button + end + + it "should submit with preselected values" do + with_html <<-HTML + +
+ + +
+ + HTML + + webrat_session.should_receive(:post).with("/login", "clothes" => ['tshirt', 'pants']) + click_button + end + + it "should allow selection of multiple fields" do + with_html <<-HTML + +
+ + +
+ + HTML + + webrat_session.should_receive(:post).with("/login", "clothes" => ['pants']) + select 'pants' + click_button + end + + it "should not overwrite preselected multiples" do + with_html <<-HTML + +
+ + +
+ + HTML + + webrat_session.should_receive(:post).with("/login", "clothes" => ['tshirt', 'pants']) + select 'pants' + click_button + end + + it "should allow fields that exist to be selected or throw errors" do + with_html <<-HTML + +
+ + +
+ + HTML + + lambda { select "shirt" }.should_not raise_error(Webrat::NotFoundError) + lambda { select "trousers" }.should_not raise_error(Webrat::NotFoundError) + lambda { select "shoes" }.should raise_error(Webrat::NotFoundError) + end + + it "should allow selected fields to be unselected" do + with_html <<-HTML + +
+ + +
+ + HTML + + webrat_session.should_receive(:post).with("/login", "clothes" => ['pants']) + unselect 'tshirt' + click_button + end + + it "should be able to select options with special characters" do + with_html <<-HTML + +
+ + +
+ + HTML + + webrat_session.should_receive(:post).with("/login", "clothes" => ['pants & socks']) + select 'pants & socks' + click_button + end + + it "should be able to unselect options with special characters" do + with_html <<-HTML + +
+ + +
+ + HTML + + webrat_session.should_receive(:post).with("/login", "clothes" => ['tshirt & sweater']) + unselect 'pants & socks' + click_button + end + end