Skip to content

Commit

Permalink
Use URLSearchParams for paramsObject and update corresponding tests t…
Browse files Browse the repository at this point in the history
…o expect URLSearchParams objects

Add utility methods for URLSearchParams

URLSearchParams don't have notions of equality defined in the spec, so we go through iterating over all entries checking for equality for testing purposes; Chrome (v60) currently doesn't support constructing URLSearchParams from objects, so add utility method to convert objects into arrays of pairs

Update tests to expect URLSearchParams for search query

Tests that would normally expect objects for search params now expect URLSearchParams; add test for multiple values same key

Implement URLSearchParams for search query

Most of the work is offloaded to URLSearchParams re: parsing and serializing, this change is predominatly updating the type of paramsObject

Add polyfill for URLSearchParams

I.E. does not implement URLSearchParams
  • Loading branch information
Cole Hansen committed Jul 27, 2017
1 parent faaa959 commit e0976e3
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 55 deletions.
54 changes: 22 additions & 32 deletions iron-query-params.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,24 @@
@demo demo/iron-query-params.html
-->
<link rel="import" href="../polymer/polymer.html">

<!-- Polyfill URLSearchParams
polyfill provided by:
Andrea Giammarchi (https://github.com/WebReflection/url-search-params)
/*! (C) Andrea Giammarchi - Mit Style License */
-->
<script src="../url-search-params/build/url-search-params.js"></script>
<script>
'use strict';

if (!window.URLSearchParams) {
// Polyfill URLSearchParams
// if it doesn't exist with the current browser
// polyfill provided by:
// Andrea Giammarchi (https://github.com/WebReflection/url-search-params)
/*! (C) Andrea Giammarchi - Mit Style License */
window.URLSearchParams = function(){"use strict";function e(e){var n,r,i,s,o,l,c=Object.create(null);this[u]=c;if(!e)return;if(typeof e=="string"){e.charAt(0)==="?"&&(e=e.slice(1));for(s=e.split("&"),o=0,l=s.length;o<l;o++)i=s[o],n=i.indexOf("="),-1<n?a(c,f(i.slice(0,n)),f(i.slice(n+1))):i.length&&a(c,f(i),"")}else if(t(e))for(o=0,l=e.length;o<l;o++)i=e[o],a(c,i[0],i[1]);else for(r in e)a(c,r,e[r])}function a(e,n,r){n in e?e[n].push(""+r):e[n]=t(r)?r:[""+r]}function f(e){return decodeURIComponent(e.replace(i," "))}function l(e){return encodeURIComponent(e).replace(r,o)}var t=Array.isArray,n=e.prototype,r=/[!'\(\)~]|%20|%00/g,i=/\+/g,s={"!":"%21","'":"%27","(":"%28",")":"%29","~":"%7E","%20":"+","%00":"\0"},o=function(e){return s[e]},u="__URLSearchParams__:"+Math.random();n.append=function(t,n){a(this[u],t,n)},n.delete=function(t){delete this[u][t]},n.get=function(t){var n=this[u];return t in n?n[t][0]:null},n.getAll=function(t){var n=this[u];return t in n?n[t].slice(0):[]},n.has=function(t){return t in this[u]},n.set=function(t,n){this[u][t]=[""+n]},n.forEach=function(t,n){var r=this[u];Object.getOwnPropertyNames(r).forEach(function(e){r[e].forEach(function(r){t.call(n,r,e,this)},this)},this)},n.toJSON=function(){return{}},n.toString=function y(){var e=this[u],t=[],n,r,i,s;for(r in e){i=l(r);for(n=0,s=e[r];n<s.length;n++)t.push(i+"="+l(s[n]))}return t.join("&")};var c=Object.defineProperty,h=Object.getOwnPropertyDescriptor,p=function(e){function t(t,r){n.append.call(this,t,r),t=this.toString(),e.set.call(this._usp,t?"?"+t:"")}function r(t){n.delete.call(this,t),t=this.toString(),e.set.call(this._usp,t?"?"+t:"")}function i(t,r){n.set.call(this,t,r),t=this.toString(),e.set.call(this._usp,t?"?"+t:"")}return function(e,n){return e.append=t,e.delete=r,e.set=i,c(e,"_usp",{configurable:!0,writable:!0,value:n})}},d=function(e){return function(t,n){return c(t,"_searchParams",{configurable:!0,writable:!0,value:e(n,t)}),n}},v=function(t){var r=t.append;t.append=n.append,e.call(t,t._usp.search.slice(1)),t.append=r},m=function(e,t){if(!(e instanceof t))throw new TypeError("'searchParams' accessed on an object that does not implement interface "+t.name)},g=function(t){var n=t.prototype,r=h(n,"searchParams"),i=h(n,"href"),s=h(n,"search"),o;!r&&s&&s.set&&(o=d(p(s)),Object.defineProperties(n,{href:{get:function(){return i.get.call(this)},set:function(e){var t=this._searchParams;i.set.call(this,e),t&&v(t)}},search:{get:function(){return s.get.call(this)},set:function(e){var t=this._searchParams;s.set.call(this,e),t&&v(t)}},searchParams:{get:function(){return m(this,t),this._searchParams||o(this,new e(this.search.slice(1)))},set:function(e){m(this,t),o(this,e)}}}))};return g(HTMLAnchorElement),/^function|object$/.test(typeof URL)&&URL.prototype&&g(URL),e}();(function(e){var t=function(){try{return!!Symbol.iterator}catch(e){return!1}}();"forEach"in e||(e.forEach=function(t,n){var r=Object.create(null);this.toString().replace(/=[\s\S]*?(?:&|$)/g,"=").split("=").forEach(function(e){if(!e.length||e in r)return;(r[e]=this.getAll(e)).forEach(function(r){t.call(n,r,e,this)},this)},this)}),"keys"in e||(e.keys=function(){var n=[];this.forEach(function(e,t){n.push(t)});var r={next:function(){var e=n.shift();return{done:e===undefined,value:e}}};return t&&(r[Symbol.iterator]=function(){return r}),r}),"values"in e||(e.values=function(){var n=[];this.forEach(function(e){n.push(e)});var r={next:function(){var e=n.shift();return{done:e===undefined,value:e}}};return t&&(r[Symbol.iterator]=function(){return r}),r}),"entries"in e||(e.entries=function(){var n=[];this.forEach(function(e,t){n.push([t,e])});var r={next:function(){var e=n.shift();return{done:e===undefined,value:e}}};return t&&(r[Symbol.iterator]=function(){return r}),r}),t&&!(Symbol.iterator in e)&&(e[Symbol.iterator]=e.entries),"sort"in e||(e.sort=function(){var t=this.entries(),n=t.next(),r=n.done,i=[],s=Object.create(null),o,u,a;while(!r)a=n.value,u=a[0],i.push(u),u in s||(s[u]=[]),s[u].push(a[1]),n=t.next(),r=n.done;i.sort();for(o=0;o<i.length;o++)this.delete(i[o]);for(o=0;o<i.length;o++)u=i[o],this.append(u,s[u].shift())})})(URLSearchParams.prototype);
}

Polymer({
is: 'iron-query-params',

Expand All @@ -28,7 +42,7 @@
type: Object,
notify: true,
value: function() {
return {};
return new URLSearchParams();
}
},

Expand Down Expand Up @@ -61,39 +75,15 @@
},

_encodeParams: function(params) {
var encodedParams = [];

for (var key in params) {
var value = params[key];

if (value === '') {
encodedParams.push(encodeURIComponent(key));

} else if (value) {
encodedParams.push(
encodeURIComponent(key) +
'=' +
encodeURIComponent(value.toString())
);
}
}
return encodedParams.join('&');
},

_decodeParams: function(paramString) {
var params = {};
let paramString = params.toString();
// Work around a bug in decodeURIComponent where + is not
// converted to spaces:
paramString = (paramString || '').replace(/\+/g, '%20');
var paramList = paramString.split('&');
for (var i = 0; i < paramList.length; i++) {
var param = paramList[i].split('=');
if (param[0]) {
params[decodeURIComponent(param[0])] =
decodeURIComponent(param[1] || '');
}
}
return params;
return paramString;
},

_decodeParams: function(paramString) {
return new URLSearchParams(paramString);
}
});
</script>
23 changes: 12 additions & 11 deletions test/integration.html
Original file line number Diff line number Diff line change
Expand Up @@ -92,22 +92,22 @@
expect(ironLocationQuery).to.contain(ironLocation.query);
expect(ironLocationQuery).to.contain(ironQueryParams.paramsString);
if (plainTextQuery) {
expect('').to.be.equal(ironQueryParams.paramsObject[plainTextQuery])
expect('').to.be.equal(ironQueryParams.paramsObject.get(plainTextQuery))
} else {
expect(ironQueryParams.paramsObject[plainTextQuery]).to.be.undefined;
expect(ironQueryParams.paramsObject.get(plainTextQuery)).to.be.null;
}
}
});

test('propagations from iqp to location', function() {
var queryEncodingExamples = {
'foo': '?foo',
'': '',
'foo bar': '?foo%20bar',
'foo#bar': '?foo%23bar',
'foo?bar': '?foo?bar',
'/foo\'bar\'baz': ['?/foo%27bar%27baz', '?/foo\'bar\'baz'],
'foo/bar/baz': '?foo/bar/baz'
'foo': '?foo=',
'': '?=',
'foo bar': '?foo%20bar=',
'foo#bar': '?foo%23bar=',
'foo?bar': '?foo?bar=',
'/foo\'bar\'baz': ['?/foo%27bar%27baz=', '?/foo\'bar\'baz='],
'foo/bar/baz': '?foo/bar/baz='
};
for (var plainTextQuery in queryEncodingExamples) {
var encodedQueries = queryEncodingExamples[plainTextQuery];
Expand All @@ -121,10 +121,11 @@
});
}

var newParamsObject = {};
newParamsObject[plainTextQuery] = '';
var newParamsObject = new URLSearchParams();
newParamsObject.append(plainTextQuery, '');

ironQueryParams.paramsObject = newParamsObject;
// might need to notify
expect(encodedQueries).to.contain(window.location.search);
expect(ironLocationQuery).to.contain(ironLocation.query);
}
Expand Down
70 changes: 58 additions & 12 deletions test/iron-query-params.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<link rel="import" href="../../polymer/polymer.html">
<link rel="import" href="../../promise-polyfill/promise-polyfill.html">
<link rel="import" href="../iron-query-params.html">
<link rel="import" href="./urlsearchparams.html">
</head>
<body>

Expand All @@ -33,24 +34,57 @@
suite('<iron-query-params>', function () {

var paramsElem;
var expectedSearchParams;
setup(function() {
paramsElem = fixture('BasicQueryParams');
});

test('basic functionality with ?key=value params', function() {
// Check initialization
expect(paramsElem.paramsString).to.be.eq('');
expect(paramsElem.paramsObject).to.deep.eq({});
expectedSearchParams = new URLSearchParams();
expect(URLSearchParamsUtil.equal(
paramsElem.paramsObject,
expectedSearchParams
)).to.be.true;

// Check the mapping from paramsString to paramsObject.
paramsElem.paramsString = 'greeting=hello&target=world';
expect(paramsElem.paramsObject).to.deep.equal(
{greeting: 'hello', target: 'world'});
expectedSearchParams = new URLSearchParams([
['greeting', 'hello'],
['target', 'world']
]);
expect(URLSearchParamsUtil.equal(
paramsElem.paramsObject,
expectedSearchParams
)).to.be.true;

// Check the mapping from paramsString to paramsObject
// with multiple values for the same key
// NOTE: order matters in the creation for URLSearchParams
// the spec has a sequence (which is ordered) as one of
// the valid constructor inits. This could be ameliorated
// with the .sort method, but only Firefox implements it
// as of July 2017.
paramsElem.paramsString =
'greeting=hello&target=world&greeting=hola&target=mundo';
expectedSearchParams = new URLSearchParams([
['greeting', 'hello'],
['target', 'world'],
['greeting', 'hola'],
['target', 'mundo']
]);
expect(URLSearchParamsUtil.equal(
paramsElem.paramsObject,
expectedSearchParams
)).to.be.true;

// Check the mapping from paramsObject back to paramsString.
paramsElem.set('paramsObject.another', 'key');
let newParamsObject = new URLSearchParams(paramsElem.paramsObject);
newParamsObject.append('another', 'key');
paramsElem.set('paramsObject', newParamsObject);
expect(paramsElem.paramsString).to.be.equal(
'greeting=hello&target=world&another=key');
'greeting=hello&target=world&greeting=hola&target=mundo&another=key');
});
test('encoding of params', function() {
var mappings = [
Expand All @@ -59,7 +93,7 @@
object: {'a': 'b'}
},
{
string: 'debug&ok',
string: 'debug=&ok=',
object: {'debug': '', 'ok': ''}
},
{
Expand All @@ -74,27 +108,39 @@

mappings.forEach(function(mapping) {
// Clear
paramsElem.paramsObject = {};
paramsElem.paramsObject = new URLSearchParams();
expect(paramsElem.paramsString).to.be.equal('');

// Test going from string to object
paramsElem.paramsString = mapping.string;
expect(paramsElem.paramsObject).to.deep.equal(mapping.object);
console.log(paramsElem.paramsObject.toString(), mapping.string);
expect(URLSearchParamsUtil.equal(
paramsElem.paramsObject,
new URLSearchParams(
URLSearchParamsUtil.objectToArray(mapping.object)
)
)).to.be.true;

// Clear again.
paramsElem.paramsObject = {};
paramsElem.paramsObject = new URLSearchParams();
expect(paramsElem.paramsString).to.be.equal('');

// Test going from object to string
paramsElem.paramsObject = mapping.object;
paramsElem.paramsObject = new URLSearchParams(
URLSearchParamsUtil.objectToArray(mapping.object)
);
expect(paramsElem.paramsString).to.be.equal(mapping.string);
});
});
test('query strings with alternative encodings', function() {
// Check the mapping for querystrings with + instead of %20.
paramsElem.paramsString = 'key=value+with+spaces';
expect(paramsElem.paramsObject).to.deep.equal(
{key: 'value with spaces'});
expect(URLSearchParamsUtil.equal(
paramsElem.paramsObject,
new URLSearchParams(
URLSearchParamsUtil.objectToArray({key: 'value with spaces'})
)
)).to.be.true;
});
});

Expand Down
27 changes: 27 additions & 0 deletions test/urlsearchparams.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<script>
var URLSearchParamsUtil = (function() {
return {
equal: function(url1, url2) {
var url1Array = Array.from(url1.entries()),
url2Array = Array.from(url2.entries());
if (url1Array.length !== url2Array.length) {
return false;
}
for (var i = 0; i < url1Array.length; ++i) {
if (url1Array[i][0] !== url2Array[i][0] ||
url1Array[i][1] !== url2Array[i][1]) {
return false;
}
}
return true;
},
objectToArray: function(obj) {
var arr = [];
for (var prop in obj) {
arr.push([prop, obj[prop]]);
}
return arr;
}
}
})();
</script>

0 comments on commit e0976e3

Please sign in to comment.