From b4052e656ce1a9b90d1388575c2e51c413809e7f Mon Sep 17 00:00:00 2001 From: Timothy Gu Date: Sat, 28 Jan 2017 10:37:45 -0800 Subject: [PATCH] url: extend URLSearchParams constructor PR-URL: https://github.com/nodejs/node/pull/12507 Fixes: https://github.com/nodejs/node/issues/10635 Reviewed-By: James M Snell --- lib/internal/url.js | 50 ++++++++++- ...est-whatwg-url-searchparams-constructor.js | 87 +++++++++++-------- 2 files changed, 96 insertions(+), 41 deletions(-) diff --git a/lib/internal/url.js b/lib/internal/url.js index a474ed30b4d6e0..c646a757024f34 100644 --- a/lib/internal/url.js +++ b/lib/internal/url.js @@ -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> + // 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 + 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); diff --git a/test/parallel/test-whatwg-url-searchparams-constructor.js b/test/parallel/test-whatwg-url-searchparams-constructor.js index 4e177ce59ccf20..d57373e727ac51 100644 --- a/test/parallel/test-whatwg-url-searchparams-constructor.js +++ b/test/parallel/test-whatwg-url-searchparams-constructor.js @@ -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 */ @@ -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'); @@ -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. @@ -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$/); }