Merge branch 'nested_params_support'

This commit is contained in:
Damian Janowski 2010-04-12 17:22:54 -03:00
commit e3bcf5c599
6 changed files with 185 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 = self.class.query_string_to_params(query_string.join('&'))
query_params = self.class.replace_params_values(query_params, replaces)
self.class.unescape_params(query_params)
end end
def form_method def form_method
@ -57,47 +66,62 @@ 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) def self.replace_param_value(params, oval, nval)
new_param.each do |key, value| output = Hash.new
case all_params[key] params.each do |key, value|
when *hash_classes case value
merge_hash_values(all_params[key], 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.is_a?(Hash) ? replace_param_value(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

View File

@ -78,4 +78,37 @@ describe "attach_file" do
attach_file "Picture", @filename, "image/png" attach_file "Picture", @filename, "image/png"
click_button click_button
end end
it "should support nested attributes" do
with_html <<-HTML
<html>
<form method="post" action="/albums">
<label for="photo_file1">Photo</label>
<input type="file" id="photo_file1" name="album[photos_attributes][][image]" />
<input type="submit" />
</form>
</html>
HTML
webrat_session.should_receive(:post).with("/albums", { "album" => { "photos_attributes" => [{"image" => @uploaded_file}] } })
attach_file "Photo", @filename
click_button
end
it "should support nested attributes with multiple files" do
with_html <<-HTML
<html>
<form method="post" action="/albums">
<label for="photo_file1">Photo 1</label>
<input type="file" id="photo_file1" name="album[photos_attributes][][image]" />
<label for="photo_file2">Photo 2</label>
<input type="file" id="photo_file2" name="album[photos_attributes][][image]" />
<input type="submit" />
</form>
</html>
HTML
webrat_session.should_receive(:post).with("/albums", { "album" => { "photos_attributes" => [{"image" => @uploaded_file}, {"image" => @uploaded_file}] } })
attach_file "Photo 1", @filename
attach_file "Photo 2", @filename
click_button
end
end end