Make Hash.toQueryString serialize undefined values. Ensure consistency with String.prototype.toQueryParams. Closes #7806. [Mislav Marohnic]

This commit is contained in:
Thomas Fuchs 2007-03-27 20:55:56 +00:00
parent ab4bb656e1
commit 5f2acb4be5
7 changed files with 296 additions and 75 deletions

View File

@ -1,5 +1,12 @@
*SVN*
* Make Hash.toQueryString serialize undefined values. Ensure consistency with String.prototype.toQueryParams. Closes #7806. [Mislav Marohnić]
Examples:
$H({a:'b',c:undefined}).toQueryString() -> 'a=b&c'
$H({a:'b',c:null}).toQueryString() -> 'a=b&c='
$H('a=b&c'.toQueryParams()).toQueryString() -> 'a=b&c'
$H('a=b&c='.toQueryParams()).toQueryString() -> 'a=b&c='
* Fix issues with Selector an+b logic, :not support, attribute selector double quotes, plus performance improvements. Closes #7873, #7901. [Andrew Dupont]
* Fix an issue with Element.getDimensions with some element types on non-IE browsers. Closes #7683. [Andrew Dupont]

View File

@ -35,9 +35,9 @@ Object.extend(Hash, {
});
Hash.toQueryString.addPair = function(key, value, prefix) {
if (value == null) return;
key = encodeURIComponent(key);
this.push(key + '=' + (value == null ? '' : encodeURIComponent(value)));
if (value === undefined) this.push(key);
else this.push(key + '=' + (value == null ? '' : encodeURIComponent(value)));
}
Object.extend(Hash.prototype, Enumerable);

View File

@ -95,15 +95,15 @@ Object.extend(String.prototype, {
return match[1].split(separator || '&').inject({}, function(hash, pair) {
if ((pair = pair.split('='))[0]) {
var name = decodeURIComponent(pair[0]);
var value = pair[1] ? decodeURIComponent(pair[1]) : undefined;
if (hash[name] !== undefined) {
if (hash[name].constructor != Array)
hash[name] = [hash[name]];
if (value) hash[name].push(value);
var key = decodeURIComponent(pair.shift());
var value = pair.length > 1 ? pair.join('=') : pair[0];
if (value != undefined) value = decodeURIComponent(value);
if (key in hash) {
if (hash[key].constructor != Array) hash[key] = [hash[key]];
hash[key].push(value);
}
else hash[name] = value;
else hash[key] = value;
}
return hash;
});

View File

@ -303,6 +303,12 @@ Test.Unit.Assertions.prototype = {
'", actual "' + Test.Unit.inspect(actual) + '"'); }
catch(e) { this.error(e); }
},
assertNotEqual: function(expected, actual) {
var message = arguments[2] || "assertNotEqual";
try { (expected != actual) ? this.pass() :
this.fail(message + ': got "' + Test.Unit.inspect(actual) + '"'); }
catch(e) { this.error(e); }
},
assertEnumEqual: function(expected, actual) {
var message = arguments[2] || "assertEnumEqual";
expected = $A(expected);
@ -313,10 +319,44 @@ Test.Unit.Assertions.prototype = {
', actual ' + Test.Unit.inspect(actual)); }
catch(e) { this.error(e); }
},
assertNotEqual: function(expected, actual) {
var message = arguments[2] || "assertNotEqual";
try { (expected != actual) ? this.pass() :
this.fail(message + ': got "' + Test.Unit.inspect(actual) + '"'); }
assertEnumNotEqual: function(expected, actual) {
var message = arguments[2] || "assertEnumEqual";
expected = $A(expected);
actual = $A(actual);
try { expected.length != actual.length ||
expected.zip(actual).any(function(pair) { return pair[0] != pair[1] }) ?
this.pass() : this.fail(message + ': ' + Test.Unit.inspect(expected) +
' was the same as ' + Test.Unit.inspect(actual)); }
catch(e) { this.error(e); }
},
assertHashEqual: function(expected, actual) {
var message = arguments[2] || "assertHashEqual";
expected = $H(expected);
actual = $H(actual);
var expected_array = expected.toArray().sort(), actual_array = actual.toArray().sort();
// from now we recursively zip & compare nested arrays
try { expected_array.length == actual_array.length &&
expected_array.zip(actual_array).all(function(pair) {
return pair.all(function(i){ return i && i.constructor == Array }) ?
pair[0].zip(pair[1]).all(arguments.callee) : pair[0] == pair[1];
}) ?
this.pass() : this.fail(message + ': expected ' + Test.Unit.inspect(expected) +
', actual ' + Test.Unit.inspect(actual)); }
catch(e) { this.error(e); }
},
assertHashNotEqual: function(expected, actual) {
var message = arguments[2] || "assertHashEqual";
expected = $H(expected);
actual = $H(actual);
var expected_array = expected.toArray().sort(), actual_array = actual.toArray().sort();
// from now we recursively zip & compare nested arrays
try { !(expected_array.length == actual_array.length &&
expected_array.zip(actual_array).all(function(pair) {
return pair.all(function(i){ return i && i.constructor == Array }) ?
pair[0].zip(pair[1]).all(arguments.callee) : pair[0] == pair[1];
})) ?
this.pass() : this.fail(message + ': ' + Test.Unit.inspect(expected) +
' was the same as ' + Test.Unit.inspect(actual)); }
catch(e) { this.error(e); }
},
assertIdentical: function(expected, actual) {
@ -372,18 +412,21 @@ Test.Unit.Assertions.prototype = {
assertMatch: function(expected, actual) {
var message = arguments[2] || 'assertMatch';
var regex = new RegExp(expected);
try { (regex.exec(actual)) ? this.pass() :
try { regex.exec(actual) ? this.pass() :
this.fail(message + ' : regex: "' + Test.Unit.inspect(expected) + ' did not match: ' + Test.Unit.inspect(actual) + '"'); }
catch(e) { this.error(e); }
},
assertNoMatch: function(expected, actual) {
var message = arguments[2] || 'assertMatch';
var regex = new RegExp(expected);
try { !regex.exec(actual) ? this.pass() :
this.fail(message + ' : regex: "' + Test.Unit.inspect(expected) + ' matched: ' + Test.Unit.inspect(actual) + '"'); }
catch(e) { this.error(e); }
},
assertHidden: function(element) {
var message = arguments[1] || 'assertHidden';
this.assertEqual("none", element.style.display, message);
},
assertNotNull: function(object) {
var message = arguments[1] || 'assertNotNull';
this.assert(object != null, message);
},
assertInstanceOf: function(expected, actual) {
var message = arguments[2] || 'assertInstanceOf';
try {

View File

@ -38,8 +38,8 @@
},
functions: {
alpha: 'foo',
beta: function(n) { return n+1; }
quad: function(n) { return n*n },
plus: function(n) { return n+n }
},
multiple: { color: $w('r g b') },
@ -65,61 +65,65 @@
new Test.Unit.Runner({
testConstruct: function(){ with(this) {
var h = $H(Fixtures.one)
assertNotIdentical(Fixtures.one, h)
assertIdentical(h, $H(h))
var h = $H(Fixtures.one);
assertNotIdentical(Fixtures.one, h);
assertIdentical(h, $H(h));
var h2 = new Hash(h)
assertNotIdentical(h, h2)
assertEqual(h.inspect(), h2.inspect())
var h2 = new Hash(h);
assertNotIdentical(h, h2);
assertHashEqual(h, h2);
}},
testKeys: function(){ with(this) {
assertEnumEqual([], $H({}).keys());
assertEnumEqual(['a'], $H(Fixtures.one).keys());
assertEnumEqual($w('a b c d'), $H(Fixtures.many).keys());
assertEnumEqual($w('alpha beta'), $H(Fixtures.functions).keys());
assertEnumEqual($w('a b c d'), $H(Fixtures.many).keys().sort());
assertEnumEqual($w('plus quad'), $H(Fixtures.functions).keys().sort());
}},
testValues: function(){ with(this) {
assertEnumEqual([], $H({}).values());
assertEnumEqual(['A#'], $H(Fixtures.one).values());
assertEnumEqual($w('A B C D#'), $H(Fixtures.many).values());
assertEqual('function', typeof $H(Fixtures.functions).values()[1]);
assertEqual(2, $H(Fixtures.functions).beta(1));
assertEnumEqual($w('A B C D#'), $H(Fixtures.many).values().sort());
assertEnumEqual($w('function function'),
$H(Fixtures.functions).values().map(function(i){ return typeof i }));
assertEqual(9, $H(Fixtures.functions).quad(3));
assertEqual(6, $H(Fixtures.functions).plus(3));
}},
testMerge: function(){ with(this) {
assertEqual($H(Fixtures.many).inspect(), $H(Fixtures.many).merge().inspect());
assertEqual($H(Fixtures.many).inspect(), $H(Fixtures.many).merge({}).inspect());
assertEqual("#<Hash:{'a': 'A', 'b': 'B', 'c': 'C', 'd': 'D#', 'aaa': 'AAA'}>", $H(Fixtures.many).merge({aaa: 'AAA'}).inspect());
assertEqual("#<Hash:{'a': 'A#', 'b': 'B', 'c': 'C', 'd': 'D#'}>", $H(Fixtures.many).merge(Fixtures.one).inspect());
assertHashEqual(Fixtures.many, $H(Fixtures.many).merge());
assertHashEqual(Fixtures.many, $H(Fixtures.many).merge({}));
assertHashEqual(Fixtures.many, $H(Fixtures.many).merge($H()));
assertHashEqual({a:'A', b:'B', c:'C', d:'D#', aaa:'AAA' }, $H(Fixtures.many).merge({aaa: 'AAA'}));
assertHashEqual({a:'A#', b:'B', c:'C', d:'D#' }, $H(Fixtures.many).merge(Fixtures.one));
}},
testRemove: function(){ with(this) {
var hash = $H(Fixtures.many);
var values = hash.remove('b', 'c');
assertEnumEqual($w('a d'), hash.keys());
assertHashEqual({a:'A', d:'D#'}, hash);
assertEnumEqual($w('B C'), values);
}},
testToQueryString: function(){ with(this) {
assertEqual('', $H({}).toQueryString());
assertEqual('a=A%23', $H(Fixtures.one).toQueryString());
assertEqual('a=A&b=B&c=C&d=D%23', $H(Fixtures.many).toQueryString());
assertEqual("a=b", $H(Fixtures.value_undefined).toQueryString());
assertEqual("a=b", $H(Fixtures.value_null).toQueryString());
assertEqual("a=b&c=0", $H(Fixtures.value_zero).toQueryString());
assertEqual('', $H({}).toQueryString());
assertEqual('a=A%23', $H(Fixtures.one).toQueryString());
assertEqual('a=A&b=B&c=C&d=D%23', $H(Fixtures.many).toQueryString());
assertEqual("a=b&c", $H(Fixtures.value_undefined).toQueryString());
assertEqual("a=b&c", $H("a=b&c".toQueryParams()).toQueryString());
assertEqual("a=b&c=", $H(Fixtures.value_null).toQueryString());
assertEqual("a=b&c=0", $H(Fixtures.value_zero).toQueryString());
assertEqual("color=r&color=g&color=b", $H(Fixtures.multiple).toQueryString());
assertEqual("color=r&color=g&color=0", $H(Fixtures.multiple_nil).toQueryString());
assertEqual("", $H(Fixtures.multiple_all_nil).toQueryString());
assertEqual("", $H(Fixtures.multiple_empty).toQueryString());
assertEqual("color=r&color=&color=g&color&color=0", $H(Fixtures.multiple_nil).toQueryString());
assertEqual("color=&color", $H(Fixtures.multiple_all_nil).toQueryString());
assertEqual("", $H(Fixtures.multiple_empty).toQueryString());
assertEqual("stuff%5B%5D=%24&stuff%5B%5D=a&stuff%5B%5D=%3B", $H(Fixtures.multiple_special).toQueryString());
assertEnumEqual($w("_each=E map=M keys=K values=V collect=C inject=I").sort(),
Hash.toQueryString(Fixtures.dangerous).split('&').sort());
assertEnumEqual($w('_each=E map=M keys=K values=V collect=C inject=I').sort(),
$H(Fixtures.dangerous).toQueryString().split('&').sort());
assertHashEqual(Fixtures.multiple_special, $H(Fixtures.multiple_special).toQueryString().toQueryParams());
var danger = $w("_each=E collect=C inject=I keys=K map=M values=V");
assertEnumEqual(danger, Hash.toQueryString(Fixtures.dangerous).split('&').sort());
assertEnumEqual(danger, $H(Fixtures.dangerous).toQueryString().split('&').sort());
}},
testInspect: function(){ with(this) {

View File

@ -289,32 +289,29 @@
}},
testToQueryParams: function() {with(this) {
assertEnumEqual([], Object.keys(''.toQueryParams()));
assertEnumEqual([], Object.keys('foo?'.toQueryParams()));
// only the query part
var result = {a:undefined, b:'c'};
assertHashEqual({}, ''.toQueryParams(), 'empty query');
assertHashEqual({}, 'foo?'.toQueryParams(), 'empty query with URL');
assertHashEqual(result, 'foo?a&b=c'.toQueryParams(), 'query with URL');
assertHashEqual(result, 'foo?a&b=c#fragment'.toQueryParams(), 'query with URL and fragment');
assertHashEqual(result, 'a;b=c'.toQueryParams(';'), 'custom delimiter');
assertHashEqual({a:undefined}, 'a'.toQueryParams(), 'key without value');
assertHashEqual({a:'b'}, 'a=b&=c'.toQueryParams(), 'empty key');
assertHashEqual({a:'b', c:''}, 'a=b&c='.toQueryParams(), 'empty value');
assertEnumEqual(['a', 'b'], Object.keys('foo?a&b'.toQueryParams()));
assertEnumEqual(['a', 'b'], Object.keys('foo?a&b#fragment'.toQueryParams()));
assertEnumEqual(['a', 'b'], Object.keys('a;b'.toQueryParams(';')));
assertHashEqual({'a b':'c', d:'e f', g:'h'},
'a%20b=c&d=e%20f&g=h'.toQueryParams(), 'proper decoding');
assertHashEqual({a:'b=c=d'}, 'a=b=c=d'.toQueryParams(), 'multiple equal signs');
assertHashEqual({a:'b', c:'d'}, '&a=b&&&c=d'.toQueryParams(), 'proper splitting');
var result = 'a'.toQueryParams();
assertEqual(undefined, result['a']);
assert(result.hasOwnProperty('a'));
result = 'a&b=c'.toQueryParams();
assertEqual(undefined, result['a']);
assert(result.hasOwnProperty('a'));
assertEqual('c', result['b']);
result = 'a%20b=c&d=e%20f&g=h'.toQueryParams();
assertEqual('c', result['a b']);
assertEqual('e f', result['d']);
assertEqual('h', result['g']);
result = 'color=r&color=g&color=b'.toQueryParams();
assertEnumEqual(['r', 'g', 'b'], result['color']);
assertEnumEqual(['r', 'b'], 'c=r&c=&c=b'.toQueryParams()['c']);
assertEqual('blue', 'c=&c=blue'.toQueryParams()['c']);
assertEqual('blue', 'c=blue&c='.toQueryParams()['c']);
assertEnumEqual($w('r g b'), 'col=r&col=g&col=b'.toQueryParams()['col'],
'collection without square brackets');
var msg = 'empty values inside collection';
assertEnumEqual(['r', '', 'b'], 'c=r&c=&c=b'.toQueryParams()['c'], msg);
assertEnumEqual(['', 'blue'], 'c=&c=blue'.toQueryParams()['c'], msg);
assertEnumEqual(['blue', ''], 'c=blue&c='.toQueryParams()['c'], msg);
}},
testInspect: function() {with(this) {

170
test/unit/unit_tests.html Normal file
View File

@ -0,0 +1,170 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Prototype Unit test file</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<script src="../../dist/prototype.js" type="text/javascript"></script>
<script src="../lib/unittest.js" type="text/javascript"></script>
<link rel="stylesheet" href="../test.css" type="text/css" />
<style type="text/css" media="screen">
/* <![CDATA[ */
#testcss1 { font-size:11px; color: #f00; }
#testcss2 { font-size:12px; color: #0f0; display: none; }
/* ]]> */
</style>
</head>
<body>
<h1>Prototype Unit test file</h1>
<p>
Test the unit testing library (unittest.js)
</p>
<!-- Log output -->
<div id="testlog"> </div>
<!-- Test elements follow -->
<div id="test_1" class="a bbbbbbbbbbbb cccccccccc dddd"> </div>
<div id="test_2"> <span> </span>
<div><div></div> </div><span> </span>
</div>
<ul id="tlist"><li id="tlist_1">x1</li><li id="tlist_2">x2</li></ul>
<ul id="tlist2"><li class="a" id="tlist2_1">x1</li><li id="tlist2_2">x2</li></ul>
<div id="testmoveby" style="background-color:#333;width:100px;">XXXX</div>
<div id="testcss1">testcss1<span id="testcss1_span" style="display:none;">blah</span></div><div id="testcss2">testcss1</div>
<!-- Tests follow -->
<script type="text/javascript" language="javascript" charset="utf-8">
// <![CDATA[
var testObj = {
isNice: function(){
return true;
},
isBroken: function(){
return false;
}
}
new Test.Unit.Runner({
testAssertEqual: function() { with(this) {
assertEqual(0, 0);
assertEqual(0, 0, "test");
assertEqual(0,'0');
assertEqual(65.0, 65);
assertEqual("a", "a");
assertEqual("a", "a", "test");
assertNotEqual(0, 1);
assertNotEqual("a","b");
assertNotEqual({},{});
assertNotEqual([],[]);
assertNotEqual([],{});
}},
testAssertEnumEqual: function() { with(this) {
assertEnumEqual([], []);
assertEnumEqual(['a', 'b'], ['a', 'b']);
assertEnumEqual(['1', '2'], [1, 2]);
assertEnumNotEqual(['1', '2'], [1, 2, 3]);
}},
testAssertHashEqual: function() { with(this) {
assertHashEqual({}, {});
assertHashEqual({a:'b'}, {a:'b'});
assertHashEqual({a:'b', c:'d'}, {c:'d', a:'b'});
assertHashNotEqual({a:'b', c:'d'}, {c:'d', a:'boo!'});
}},
testAssertRespondsTo: function() { with(this) {
assertRespondsTo('isNice', testObj);
assertRespondsTo('isBroken', testObj);
}},
testAssertIdentical: function() { with(this) {
assertIdentical(0, 0);
assertIdentical(0, 0, "test");
assertIdentical(1, 1);
assertIdentical('a', 'a');
assertIdentical('a', 'a', "test");
assertIdentical('', '');
assertIdentical(undefined, undefined);
assertIdentical(null, null);
assertIdentical(true, true);
assertIdentical(false, false);
var obj = {a:'b'};
assertIdentical(obj, obj);
assertNotIdentical({1:2,3:4},{1:2,3:4});
assertIdentical(1, 1.0); // both are typeof == 'number'
assertNotIdentical(1, '1');
assertNotIdentical(1, '1.0');
}},
testAssertNullAndAssertUndefined: function() { with(this) {
assertNull(null);
assertNotNull(undefined);
assertNotNull(0);
assertNotNull('');
assertNotUndefined(null);
assertUndefined(undefined);
assertNotUndefined(0);
assertNotUndefined('');
assertNullOrUndefined(null);
assertNullOrUndefined(undefined);
assertNotNullOrUndefined(0);
assertNotNullOrUndefined('');
}},
testAssertMatch: function() { with(this) {
assertMatch(/knowmad.jpg$/, 'http://script.aculo.us/images/knowmad.jpg');
assertMatch(/Fuc/, 'Thomas Fuchs');
assertMatch(/^\$(\d{1,3}(\,\d{3})*|(\d+))(\.\d{2})?$/, '$19.95');
assertMatch(/(\d{3}\) ?)|(\d{3}[- \.])?\d{3}[- \.]\d{4}(\s(x\d+)?){0,1}$/, '704-343-9330');
assertMatch(/^(?:(?:(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00)))(\/|-|\.)(?:0?2\1(?:29)))|(?:(?:(?:1[6-9]|[2-9]\d)?\d{2})(\/|-|\.)(?:(?:(?:0?[13578]|1[02])\2(?:31))|(?:(?:0?[1,3-9]|1[0-2])\2(29|30))|(?:(?:0?[1-9])|(?:1[0-2]))\2(?:0?[1-9]|1\d|2[0-8]))))$/, '2001-06-16');
assertMatch(/^((0?[123456789])|(1[012]))\s*:\s*([012345]\d)(\s*:\s*([012345]\d))?\s*[ap]m\s*-\s*((0?[123456789])|(1[012]))\s*:\s*([012345]\d)(\s*:\s*([012345]\d))?\s*[ap]m$/i, '2:00PM-2:15PM');
assertNoMatch(/zubar/, 'foo bar');
}},
testAssertInstanceOf: function() { with(this) {
assertInstanceOf(String, new String);
assertInstanceOf(RegExp, /foo/);
assertNotInstanceOf(String, {});
}},
testAssertVisible: function() { with(this) {
assertVisible('testcss1');
assertNotVisible('testcss1_span');
//assertNotVisible('testcss2', "Due to a Safari bug, this test fails in Safari.");
Element.hide('testcss1');
assertNotVisible('testcss1');
assertNotVisible('testcss1_span');
Element.show('testcss1');
assertVisible('testcss1');
assertNotVisible('testcss1_span');
Element.show('testcss1_span');
assertVisible('testcss1_span');
Element.hide('testcss1');
assertNotVisible('testcss1_span'); // hidden by parent
}}
}, "testlog");
// ]]>
</script>
</body>
</html>