Skip to content

Commit

Permalink
url: extend URLSearchParams constructor
Browse files Browse the repository at this point in the history
PR-URL: #12507
Fixes: #10635
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information
TimothyGu authored and evanlucas committed May 1, 2017
1 parent 5ccafa2 commit b4052e6
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 41 deletions.
50 changes: 46 additions & 4 deletions lib/internal/url.js
Original file line number Diff line number Diff line change
Expand Up @@ -614,11 +614,53 @@ function defineIDLClass(proto, classStr, obj) {
}

class URLSearchParams {
constructor(init = '') {
if (init instanceof URLSearchParams) {
const childParams = init[searchParams];
this[searchParams] = childParams.slice();
// URL Standard says the default value is '', but as undefined and '' have
// the same result, undefined is used to prevent unnecessary parsing.
// Default parameter is necessary to keep URLSearchParams.length === 0 in
// accordance with Web IDL spec.
constructor(init = undefined) {
if (init === null || init === undefined) {
this[searchParams] = [];
} else if (typeof init === 'object') {
const method = init[Symbol.iterator];
if (method === this[Symbol.iterator]) {
// While the spec does not have this branch, we can use it as a
// shortcut to avoid having to go through the costly generic iterator.
const childParams = init[searchParams];
this[searchParams] = childParams.slice();
} else if (method !== null && method !== undefined) {
if (typeof method !== 'function') {
throw new TypeError('Query pairs must be iterable');
}

// sequence<sequence<USVString>>
// Note: per spec we have to first exhaust the lists then process them
const pairs = [];
for (const pair of init) {
if (typeof pair !== 'object' ||
typeof pair[Symbol.iterator] !== 'function') {
throw new TypeError('Each query pair must be iterable');
}
pairs.push(Array.from(pair));
}

this[searchParams] = [];
for (const pair of pairs) {
if (pair.length !== 2) {
throw new TypeError('Each query pair must be a name/value tuple');
}
this[searchParams].push(String(pair[0]), String(pair[1]));
}
} else {
// record<USVString, USVString>
this[searchParams] = [];
for (const key of Object.keys(init)) {
const value = String(init[key]);
this[searchParams].push(key, value);
}
}
} else {
// USVString
init = String(init);
if (init[0] === '?') init = init.slice(1);
initSearchParams(this, init);
Expand Down
87 changes: 50 additions & 37 deletions test/parallel/test-whatwg-url-searchparams-constructor.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ const common = require('../common');
const assert = require('assert');
const URLSearchParams = require('url').URLSearchParams;
const {
test, assert_equals, assert_true, assert_false
test, assert_equals, assert_true,
assert_false, assert_throws, assert_array_equals
} = common.WPT;

/* eslint-disable */
Expand Down Expand Up @@ -40,10 +41,10 @@ test(() => {
assert_equals(params.__proto__, URLSearchParams.prototype, 'expected URLSearchParams.prototype as prototype.');
}, "URLSearchParams constructor, empty string as argument")

// test(() => {
// params = new URLSearchParams({});
// assert_equals(params + '', "");
// }, 'URLSearchParams constructor, {} as argument');
test(() => {
params = new URLSearchParams({});
assert_equals(params + '', "");
}, 'URLSearchParams constructor, {} as argument');

test(function() {
var params = new URLSearchParams('a=b');
Expand Down Expand Up @@ -142,39 +143,39 @@ test(function() {
assert_equals(params.get('a\uD83D\uDCA9b'), 'c');
}, 'Parse %f0%9f%92%a9'); // Unicode Character 'PILE OF POO' (U+1F4A9)

// test(function() {
// var params = new URLSearchParams([]);
// assert_true(params != null, 'constructor returned non-null value.');
// params = new URLSearchParams([['a', 'b'], ['c', 'd']]);
// assert_equals(params.get("a"), "b");
// assert_equals(params.get("c"), "d");
// assert_throws(new TypeError(), function() { new URLSearchParams([[1]]); });
// assert_throws(new TypeError(), function() { new URLSearchParams([[1,2,3]]); });
// }, "Constructor with sequence of sequences of strings");

// [
test(function() {
var params = new URLSearchParams([]);
assert_true(params != null, 'constructor returned non-null value.');
params = new URLSearchParams([['a', 'b'], ['c', 'd']]);
assert_equals(params.get("a"), "b");
assert_equals(params.get("c"), "d");
assert_throws(new TypeError(), function() { new URLSearchParams([[1]]); });
assert_throws(new TypeError(), function() { new URLSearchParams([[1,2,3]]); });
}, "Constructor with sequence of sequences of strings");

[
// { "input": {"+": "%C2"}, "output": [[" ", "\uFFFD"]], "name": "object with +" },
// { "input": {c: "x", a: "?"}, "output": [["c", "x"], ["a", "?"]], "name": "object with two keys" },
// { "input": [["c", "x"], ["a", "?"]], "output": [["c", "x"], ["a", "?"]], "name": "array with two keys" }
// ].forEach((val) => {
// test(() => {
// let params = new URLSearchParams(val.input),
// i = 0
// for (let param of params) {
// assert_array_equals(param, val.output[i])
// i++
// }
// }, "Construct with " + val.name)
// })
{ "input": {c: "x", a: "?"}, "output": [["c", "x"], ["a", "?"]], "name": "object with two keys" },
{ "input": [["c", "x"], ["a", "?"]], "output": [["c", "x"], ["a", "?"]], "name": "array with two keys" }
].forEach((val) => {
test(() => {
let params = new URLSearchParams(val.input),
i = 0
for (let param of params) {
assert_array_equals(param, val.output[i])
i++
}
}, "Construct with " + val.name)
})

// test(() => {
// params = new URLSearchParams()
// params[Symbol.iterator] = function *() {
// yield ["a", "b"]
// }
// let params2 = new URLSearchParams(params)
// assert_equals(params2.get("a"), "b")
// }, "Custom [Symbol.iterator]")
test(() => {
params = new URLSearchParams()
params[Symbol.iterator] = function *() {
yield ["a", "b"]
}
let params2 = new URLSearchParams(params)
assert_equals(params2.get("a"), "b")
}, "Custom [Symbol.iterator]")
/* eslint-enable */

// Tests below are not from WPT.
Expand All @@ -192,5 +193,17 @@ test(function() {
params = new URLSearchParams(undefined);
assert.strictEqual(params.toString(), '');
params = new URLSearchParams(null);
assert.strictEqual(params.toString(), 'null=');
assert.strictEqual(params.toString(), '');
assert.throws(() => new URLSearchParams([[1]]),
/^TypeError: Each query pair must be a name\/value tuple$/);
assert.throws(() => new URLSearchParams([[1, 2, 3]]),
/^TypeError: Each query pair must be a name\/value tuple$/);
assert.throws(() => new URLSearchParams({ [Symbol.iterator]: 42 }),
/^TypeError: Query pairs must be iterable$/);
assert.throws(() => new URLSearchParams([{}]),
/^TypeError: Each query pair must be iterable$/);
assert.throws(() => new URLSearchParams(['a']),
/^TypeError: Each query pair must be iterable$/);
assert.throws(() => new URLSearchParams([{ [Symbol.iterator]: 42 }]),
/^TypeError: Each query pair must be iterable$/);
}

0 comments on commit b4052e6

Please sign in to comment.