Browse files

Ensure fields are sent in the order they are described.

  • Loading branch information...
1 parent 61bddad commit 7bc6cf486be16cfcbf5cbb9bffd5da9d8a476f7d @josevalim josevalim committed Jan 19, 2011
Showing with 51 additions and 10 deletions.
  1. +20 −1 spec/forms-spec.coffee
  2. +3 −6 src/zombie/forms.coffee
  3. +28 −3 src/zombie/history.coffee
View
21 spec/forms-spec.coffee
@@ -38,6 +38,20 @@ brains.get "/form", (req, res)-> res.send """
<option>neither</option>
</select>
+ <span>First address<span>
+ <label for='address1_street'>Street</label>
+ <input type="text" name="addresses[][street]" value="" id="address1_street">
+
+ <label for='address1_city'>City</label>
+ <input type="text" name="addresses[][city]" value="" id="address1_city">
+
+ <span>Second address<span>
+ <label for='address2_street'>Street</label>
+ <input type="text" name="addresses[][street]" value="" id="address2_street">
+
+ <label for='address2_city'>City</label>
+ <input type="text" name="addresses[][city]" value="" id="address2_city">
+
<select name="kills" id="field-kills">
<option>Five</option>
<option>Seventeen</option>
@@ -79,6 +93,7 @@ brains.post "/submit", (req, res)-> res.send """
<div id="state">#{req.body.state}</div>
<div id="unselected_state">#{req.body.unselected_state}</div>
<div id="hobbies">#{JSON.stringify(req.body.hobbies)}</div>
+ <div id="addresses">#{JSON.stringify(req.body.addresses)}</div>
<div id="unknown">#{req.body.unknown}</div>
<div id="clicked">#{req.body.button}</div>
<div id="image_clicked">#{req.body.image}</div>
@@ -304,7 +319,9 @@ vows.describe("Forms").addBatch(
topic: (browser)->
browser.fill("Name", "ArmBiter").fill("likes", "Arm Biting").check("You bet").
check("Certainly").choose("Scary").select("state", "dead").select("looks", "Choose one").
- select("#field-hobbies", "Eat Brains").select("#field-hobbies", "Sleep").check("Brains?")
+ select("#field-hobbies", "Eat Brains").select("#field-hobbies", "Sleep").check("Brains?").
+ fill('#address1_city', 'Paris').fill('#address1_street', 'CDG').
+ fill('#address2_city', 'Mikolaiv').fill('#address2_street', 'PGS')
browser.querySelector("form").submit()
browser.wait @callback
@@ -327,6 +344,8 @@ vows.describe("Forms").addBatch(
assert.equal browser.text("#looks"), ""
"should send multiple selected options to server": (browser)->
assert.equal browser.text("#hobbies"), '["Eat Brains","Sleep"]'
+ "should send nested attributes in the order they are declared": (browser) ->
+ assert.equal browser.text("#addresses"), '[{"street":"CDG"},{"city":"Paris"},{"street":"PGS"},{"city":"Mikolaiv"}]'
"by clicking button":
zombie.wants "http://localhost:3003/form"
View
9 src/zombie/forms.coffee
@@ -25,7 +25,7 @@ UploadedFile = (filename)->
# This method takes the submitting button so we can send the button name/value.
core.HTMLFormElement.prototype.submit = (button)->
document = @ownerDocument
- params = {}
+ params = []
process = (index)=>
if field = @elements.item(index)
@@ -52,13 +52,10 @@ core.HTMLFormElement.prototype.submit = (button)->
if field.value && field.type != "submit" && field.type != "image"
value = field.value
- if value?
- value = [value] unless typeof value == "array"
- params[name] = (params[name] || []).concat(value)
-
+ params.push [name, value] if value?
process index + 1
else
- params[button.name] = button.value if button && button.name
+ params.push [button.name, button.value] if button && button.name
history = document.parentWindow.history
history._submit @getAttribute("action"), @getAttribute("method"), params, @getAttribute("enctype")
process 0
View
31 src/zombie/history.coffee
@@ -32,6 +32,26 @@ class History
stack = []
index = -1
+ stringifyPrimitive = (v) =>
+ switch Object.prototype.toString.call(v)
+ when '[object Boolean]' then v ? 'true' : 'false'
+ when '[object Number]' then isFinite(v) ? v : ''
+ when '[object String]' then v
+ else ''
+
+ stringify = (obj) =>
+ sep = '&'
+ eq = '='
+
+ obj.map((k) ->
+ if Array.isArray(k[1])
+ k[1].map((v) ->
+ qs.escape(stringifyPrimitive(k[0])) + eq + qs.escape(stringifyPrimitive(v))
+ ).join(sep);
+ else
+ qs.escape(stringifyPrimitive(k[0])) + eq + qs.escape(stringifyPrimitive(k[1]))
+ ).join(sep)
+
# Called when we switch to a new page with the URL of the old page.
pageChanged = (was)=>
url = stack[index]?.url
@@ -82,18 +102,22 @@ class History
browser.cookies(url.hostname, url.pathname).addHeader headers
if method == "GET" || method == "HEAD"
- url.search = "?" + qs.stringify(data, '&', '=', false) if data
+ url.search = "?" + stringify(data) if data
data = null
headers["content-length"] = 0
else
headers["content-type"] = enctype || "application/x-www-form-urlencoded"
switch headers["content-type"]
when "application/x-www-form-urlencoded"
- data = qs.stringify(data, '&', '=', false)
+ data = stringify(data)
when "multipart/form-data"
boundary = "#{new Date().getTime()}#{Math.random()}"
lines = ["--#{boundary}"]
- for name, values of data
+ data.map((item) ->
+ name = item[0]
+ values = item[1]
+ values = [values] unless typeof values == "array"
+
for value in values
disp = "Content-Disposition: form-data; name=\"#{name}\""
@@ -114,6 +138,7 @@ class History
lines.push content
lines.push "--#{boundary}"
+ )
data = lines.join("\r\n") + "--\r\n"
headers["content-type"] += "; boundary=#{boundary}"
else data = data.toString()

1 comment on commit 7bc6cf4

@boblail

Nice job, José!

Please sign in to comment.