build querystring with all form elements and then parse to get query params

* Basically Field#to_param was replaced for Field#to_query_string and
some methods related to build params were moved to Form class.

Before this commit the params hash was made by parsing each element
querystring to param and then merge, now we build the whole querystring
first and then parse it to get params with Rack or Rails depending of
the configure mode.
This commit is contained in:
Álvaro Gil 2010-04-02 22:14:46 -03:00
parent 2f12422ae8
commit fa881a88c8
5 changed files with 154 additions and 126 deletions

View File

@ -1,6 +1,7 @@
require "cgi" require "cgi"
require "digest/md5"
require "webrat/core_extensions/blank" require "webrat/core_extensions/blank"
require "webrat/core_extensions/nil_to_param" require "webrat/core_extensions/nil_to_query_string"
require "webrat/core/elements/element" require "webrat/core/elements/element"
@ -13,7 +14,7 @@ module Webrat
attr_reader :value attr_reader :value
def self.xpath_search def self.xpath_search
[".//button", ".//input", ".//textarea", ".//select"] ".//button|.//input|.//textarea|.//select"
end end
def self.xpath_search_excluding_hidden def self.xpath_search_excluding_hidden
@ -84,19 +85,17 @@ module Webrat
raise DisabledFieldError.new("Cannot interact with disabled form element (#{self})") raise DisabledFieldError.new("Cannot interact with disabled form element (#{self})")
end end
def to_param def to_query_string
return nil if disabled? return nil if disabled?
params = case Webrat.configuration.mode query_string = case Webrat.configuration.mode
when :rails when :rails, :merb, :rack, :sinatra
parse_rails_request_params("#{name}=#{escaped_value}") build_query_string
when :merb when :mechanize
::Merb::Parse.query("#{name}=#{escaped_value}") build_query_string(false)
else
{ name => [*@value].first.to_s }
end end
unescape_params(params) query_string
end end
def set(value) def set(value)
@ -109,18 +108,6 @@ module Webrat
protected protected
def parse_rails_request_params(params)
if defined?(ActionController::AbstractRequest)
ActionController::AbstractRequest.parse_query_parameters(params)
elsif defined?(ActionController::UrlEncodedPairParser)
# For Rails > 2.2
ActionController::UrlEncodedPairParser.parse_query_parameters(params)
else
# For Rails > 2.3
Rack::Utils.parse_nested_query(params)
end
end
def form def form
Form.load(@session, form_element) Form.load(@session, form_element)
end end
@ -138,23 +125,20 @@ module Webrat
@element["name"] @element["name"]
end end
def escaped_value def build_query_string(escape_value=true)
CGI.escape(@value.to_s) if @value.is_a?(Array)
@value.collect {|value| "#{name}=#{ escape_value ? escape(value) : value }" }.join("&")
else
"#{name}=#{ escape_value ? escape(value) : value }"
end
end end
# Because we have to escape it before sending it to the above case statement, def escape(value)
# we have to make sure we unescape each value when it gets back so assertions CGI.escape(value.to_s)
# 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 escaped_value
CGI.escape(@value.to_s)
end end
def labels def labels
@ -186,22 +170,6 @@ module Webrat
def default_value def default_value
@element["value"] @element["value"]
end end
def replace_param_value(params, oval, nval)
output = Hash.new
params.each do |key, value|
case value
when Hash
value = replace_param_value(value, oval, nval)
when Array
value = value.map { |o| o == oval ? nval : oval }
when oval
value = nval
end
output[key] = value
end
output
end
end end
class ButtonField < Field #:nodoc: class ButtonField < Field #:nodoc:
@ -210,7 +178,7 @@ module Webrat
[".//button", ".//input[@type = 'submit']", ".//input[@type = 'button']", ".//input[@type = 'image']"] [".//button", ".//input[@type = 'submit']", ".//input[@type = 'button']", ".//input[@type = 'image']"]
end end
def to_param def to_query_string
return nil if @value.nil? return nil if @value.nil?
super super
end end
@ -233,13 +201,13 @@ module Webrat
".//input[@type = 'hidden']" ".//input[@type = 'hidden']"
end end
def to_param def to_query_string
if collection_name? if collection_name?
super super
else else
checkbox_with_same_name = form.field_named(name, CheckboxField) checkbox_with_same_name = form.field_named(name, CheckboxField)
if checkbox_with_same_name.to_param.blank? if checkbox_with_same_name.to_query_string.blank?
super super
else else
nil nil
@ -261,7 +229,7 @@ module Webrat
".//input[@type = 'checkbox']" ".//input[@type = 'checkbox']"
end end
def to_param def to_query_string
return nil if @value.nil? return nil if @value.nil?
super super
end end
@ -306,7 +274,7 @@ module Webrat
".//input[@type = 'radio']" ".//input[@type = 'radio']"
end end
def to_param def to_query_string
return nil if @value.nil? return nil if @value.nil?
super super
end end
@ -363,30 +331,32 @@ module Webrat
attr_accessor :content_type attr_accessor :content_type
def set(value, content_type = nil) def set(value, content_type = nil)
@original_value = @value
@content_type ||= content_type
super(value) super(value)
@content_type = content_type
end end
def to_param def digest_value
if @value.nil? @value ? Digest::MD5.hexdigest(self.object_id.to_s) : ""
end
def to_query_string
@value.nil? ? set("") : set(digest_value)
super super
else
replace_param_value(super, @value, test_uploaded_file)
end end
end
protected
def test_uploaded_file def test_uploaded_file
return "" if @original_value.blank?
case Webrat.configuration.mode case Webrat.configuration.mode
when :rails when :rails
if content_type if content_type
ActionController::TestUploadedFile.new(@value, content_type) ActionController::TestUploadedFile.new(@original_value, content_type)
else else
ActionController::TestUploadedFile.new(@value) ActionController::TestUploadedFile.new(@original_value)
end end
when :rack, :merb when :rack, :merb
Rack::Test::UploadedFile.new(@value, content_type) Rack::Test::UploadedFile.new(@original_value, content_type)
end end
end end
@ -450,25 +420,6 @@ module Webrat
@value.delete(value) @value.delete(value)
end 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 protected
# Overwrite SelectField definition because we don't want to select the first option # Overwrite SelectField definition because we don't want to select the first option

View File

@ -38,15 +38,24 @@ module Webrat
end end
end end
# iterate over all form fields to build a request querystring to get params from it,
# for file_field we made a work around to pass a digest as value to later replace it
# in params hash with the real file.
def params def params
all_params = {} query_string = []
replaces = {}
fields.each do |field| fields.each do |field|
next if field.to_param.nil? next if field.to_query_string.nil?
merge(all_params, field.to_param) replaces.merge!({field.digest_value => field.test_uploaded_file}) if field.is_a?(FileField)
query_string << field.to_query_string
end end
all_params query_params = Form.query_string_to_params(query_string.join('&'))
query_params = Form.replace_params_values(query_params, replaces)
Form.unescape_params(query_params)
end end
def form_method def form_method
@ -57,47 +66,64 @@ module Webrat
@element["action"].blank? ? @session.current_url : @element["action"] @element["action"].blank? ? @session.current_url : @element["action"]
end end
def merge(all_params, new_param) protected
new_param.each do |key, value|
case all_params[key] def self.replace_param_value(params, oval, nval)
when *hash_classes output = Hash.new
merge_hash_values(all_params[key], value) params.each do |key, value|
case value
when Hash
value = replace_param_value(value, oval, nval)
when Array when Array
all_params[key] += value value = value.map { |o| o == oval ? nval : o }
when oval
value = nval
end
output[key] = value
end
output
end
def self.replace_params_values(params, values)
values.each do |key, value|
params = replace_param_value(params, key, value)
end
params
end
def self.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 else
all_params[key] = value params.is_a?(String) ? CGI.unescapeHTML(params) : params
end
end end
end end
def merge_hash_values(a, b) # :nodoc: def self.query_string_to_params(query_string)
a.keys.each do |k|
if b.has_key?(k)
case [a[k], b[k]].map{|value| value.class}
when *hash_classes.zip(hash_classes)
a[k] = merge_hash_values(a[k], b[k])
b.delete(k)
when [Array, Array]
a[k] += b[k]
b.delete(k)
end
end
end
a.merge!(b)
end
def hash_classes
klasses = [Hash]
case Webrat.configuration.mode case Webrat.configuration.mode
when :rails when :rails
klasses << HashWithIndifferentAccess parse_rails_request_params(query_string)
when :merb when :merb
klasses << Mash ::Merb::Parse.query(query_string)
when :rack, :sinatra
Rack::Utils.parse_nested_query(query_string)
else
query_string.split('&').map {|query| { query.split('=').first => query.split('=').last }}
end
end end
klasses def self.parse_rails_request_params(query_string)
if defined?(ActionController::AbstractRequest)
ActionController::AbstractRequest.parse_query_parameters(query_string)
elsif defined?(ActionController::UrlEncodedPairParser)
ActionController::UrlEncodedPairParser.parse_query_parameters(query_string)
else
Rack::Utils.parse_nested_query(query_string)
end
end end
end end
end end

View File

@ -1,5 +1,5 @@
class NilClass #:nodoc: class NilClass #:nodoc:
def to_param def to_query_string
nil nil
end end
end end

View File

@ -77,7 +77,7 @@ module Webrat
element = Webrat::XML.document(html).css('input').first element = Webrat::XML.document(html).css('input').first
text_field = TextField.new(nil, element) text_field = TextField.new(nil, element)
text_field.to_param.should == { 'email' => 'user@example.com' } text_field.to_query_string.should == 'email=user@example.com'
end end
end end
end end

View File

@ -0,0 +1,51 @@
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
describe "Multiple nested params" do
it "should be corretly posted" do
Webrat.configuration.mode = :rails
with_html <<-HTML
<html>
<form method="post" action="/family">
<div class="couple">
<div class="parent">
<select name="user[family][parents][0][][gender]">
<option selected="selected" value="Mother">Mother</option>
<option value="Father">Father</option>
</select>
<input type="text" value="Alice" name="user[family][parents][0][][name]" />
</div>
<div class="parent">
<select name="user[family][parents][0][][gender]">
<option value="Mother">Mother</option>
<option selected="selected" value="Father">Father</option>
</select>
<input type="text" value="Michael" name="user[family][parents][0][][name]" />
</div>
</div>
<div class="couple">
<div class="parent">
<select name="user[family][parents][1][][gender]">
<option selected="selected" value="Mother">Mother</option>
<option value="Father">Father</option>
</select>
<input type="text" value="Jenny" name="user[family][parents][1][][name]" />
</div>
</div>
<input type="submit" />
</form>
</html>
HTML
params = { "user" => { "family" => { "parents" => {
"0" => [ {"name" => "Alice", "gender"=>"Mother"}, {"name" => "Michael", "gender"=>"Father"} ],
"1" => [ {"name" => "Jenny", "gender"=>"Mother"} ]
}
}
}
}
webrat_session.should_receive(:post).with("/family", params)
click_button
end
end