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 "digest/md5"
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"
@ -13,7 +14,7 @@ module Webrat
attr_reader :value
def self.xpath_search
[".//button", ".//input", ".//textarea", ".//select"]
".//button|.//input|.//textarea|.//select"
end
def self.xpath_search_excluding_hidden
@ -84,19 +85,17 @@ module Webrat
raise DisabledFieldError.new("Cannot interact with disabled form element (#{self})")
end
def to_param
def to_query_string
return nil if disabled?
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].first.to_s }
query_string = case Webrat.configuration.mode
when :rails, :merb, :rack, :sinatra
build_query_string
when :mechanize
build_query_string(false)
end
unescape_params(params)
query_string
end
def set(value)
@ -109,18 +108,6 @@ module Webrat
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
Form.load(@session, form_element)
end
@ -138,23 +125,20 @@ module Webrat
@element["name"]
end
def escaped_value
CGI.escape(@value.to_s)
def build_query_string(escape_value=true)
if @value.is_a?(Array)
@value.collect {|value| "#{name}=#{ escape_value ? escape(value) : value }" }.join("&")
else
"#{name}=#{ escape_value ? escape(value) : value }"
end
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
def escape(value)
CGI.escape(value.to_s)
end
def escaped_value
CGI.escape(@value.to_s)
end
def labels
@ -186,22 +170,6 @@ module Webrat
def default_value
@element["value"]
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
class ButtonField < Field #:nodoc:
@ -210,7 +178,7 @@ module Webrat
[".//button", ".//input[@type = 'submit']", ".//input[@type = 'button']", ".//input[@type = 'image']"]
end
def to_param
def to_query_string
return nil if @value.nil?
super
end
@ -233,13 +201,13 @@ module Webrat
".//input[@type = 'hidden']"
end
def to_param
def to_query_string
if collection_name?
super
else
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
else
nil
@ -261,7 +229,7 @@ module Webrat
".//input[@type = 'checkbox']"
end
def to_param
def to_query_string
return nil if @value.nil?
super
end
@ -306,7 +274,7 @@ module Webrat
".//input[@type = 'radio']"
end
def to_param
def to_query_string
return nil if @value.nil?
super
end
@ -363,30 +331,32 @@ module Webrat
attr_accessor :content_type
def set(value, content_type = nil)
@original_value = @value
@content_type ||= content_type
super(value)
@content_type = content_type
end
def to_param
if @value.nil?
super
else
replace_param_value(super, @value, test_uploaded_file)
end
def digest_value
@value ? Digest::MD5.hexdigest(self.object_id.to_s) : ""
end
protected
def to_query_string
@value.nil? ? set("") : set(digest_value)
super
end
def test_uploaded_file
return "" if @original_value.blank?
case Webrat.configuration.mode
when :rails
if content_type
ActionController::TestUploadedFile.new(@value, content_type)
ActionController::TestUploadedFile.new(@original_value, content_type)
else
ActionController::TestUploadedFile.new(@value)
ActionController::TestUploadedFile.new(@original_value)
end
when :rack, :merb
Rack::Test::UploadedFile.new(@value, content_type)
Rack::Test::UploadedFile.new(@original_value, content_type)
end
end
@ -450,25 +420,6 @@ module Webrat
@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

View File

@ -38,15 +38,24 @@ module Webrat
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
all_params = {}
query_string = []
replaces = {}
fields.each do |field|
next if field.to_param.nil?
merge(all_params, field.to_param)
next if field.to_query_string.nil?
replaces.merge!({field.digest_value => field.test_uploaded_file}) if field.is_a?(FileField)
query_string << field.to_query_string
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
def form_method
@ -57,47 +66,64 @@ module Webrat
@element["action"].blank? ? @session.current_url : @element["action"]
end
def merge(all_params, new_param)
new_param.each do |key, value|
case all_params[key]
when *hash_classes
merge_hash_values(all_params[key], value)
protected
def self.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
all_params[key] += value
else
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
params.is_a?(String) ? CGI.unescapeHTML(params) : params
end
end
def merge_hash_values(a, b) # :nodoc:
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]
def self.query_string_to_params(query_string)
case Webrat.configuration.mode
when :rails
klasses << HashWithIndifferentAccess
parse_rails_request_params(query_string)
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
klasses
end
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

View File

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

View File

@ -77,7 +77,7 @@ module Webrat
element = Webrat::XML.document(html).css('input').first
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

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