diff --git a/lib/internal/url.js b/lib/internal/url.js index 79b9e1cb0f46bb..79a40f90116f3e 100644 --- a/lib/internal/url.js +++ b/lib/internal/url.js @@ -20,6 +20,11 @@ const kHost = Symbol('host'); const kPort = Symbol('port'); const kDomain = Symbol('domain'); +// https://tc39.github.io/ecma262/#sec-%iteratorprototype%-object +const IteratorPrototype = Object.getPrototypeOf( + Object.getPrototypeOf([][Symbol.iterator]()) +); + function StorageObject() {} StorageObject.prototype = Object.create(null); @@ -92,7 +97,8 @@ class URL { this[context].query = query; this[context].fragment = fragment; this[context].host = host; - this[searchParams] = new URLSearchParams(this); + this[searchParams] = new URLSearchParams(query); + this[searchParams][context] = this; }); } @@ -309,8 +315,31 @@ class URL { } set search(search) { - update(this, search); - this[searchParams][searchParams] = querystring.parse(this.search); + search = String(search); + if (search[0] === '?') search = search.slice(1); + if (!search) { + this[context].query = null; + this[context].flags &= ~binding.URL_FLAGS_HAS_QUERY; + this[searchParams][searchParams] = {}; + return; + } + this[context].query = ''; + binding.parse(search, + binding.kQuery, + null, + this[context], + (flags, protocol, username, password, + host, port, path, query, fragment) => { + if (flags & binding.URL_FLAGS_FAILED) + return; + if (query) { + this[context].query = query; + this[context].flags |= binding.URL_FLAGS_HAS_QUERY; + } else { + this[context].flags &= ~binding.URL_FLAGS_HAS_QUERY; + } + }); + this[searchParams][searchParams] = querystring.parse(search); } get hash() { @@ -484,76 +513,129 @@ function encodeAuth(str) { return out; } -function update(url, search) { - search = String(search); - if (!search) { - url[context].query = null; - url[context].flags &= ~binding.URL_FLAGS_HAS_QUERY; +function update(url, params) { + if (!url) return; + + url[context].query = params.toString(); +} + +function getSearchParamPairs(target) { + const obj = target[searchParams]; + const keys = Object.keys(obj); + const values = []; + for (var i = 0; i < keys.length; i++) { + const name = keys[i]; + const value = obj[name]; + if (Array.isArray(value)) { + for (const item of value) + values.push([name, item]); + } else { + values.push([name, value]); + } } - if (search[0] === '?') search = search.slice(1); - url[context].query = ''; - binding.parse(search, - binding.kQuery, - null, - url[context], - (flags, protocol, username, password, - host, port, path, query, fragment) => { - if (flags & binding.URL_FLAGS_FAILED) - return; - if (query) { - url[context].query = query; - url[context].flags |= binding.URL_FLAGS_HAS_QUERY; - } else { - url[context].flags &= ~binding.URL_FLAGS_HAS_QUERY; - } - }); + return values; } class URLSearchParams { - constructor(url) { - this[context] = url; - this[searchParams] = querystring.parse(url[context].search || ''); + constructor(init = '') { + if (init instanceof URLSearchParams) { + const childParams = init[searchParams]; + this[searchParams] = Object.assign(Object.create(null), childParams); + } else { + init = String(init); + if (init[0] === '?') init = init.slice(1); + this[searchParams] = querystring.parse(init); + } + + // "associated url object" + this[context] = null; + + // Class string for an instance of URLSearchParams. This is different from + // the class string of the prototype object (set below). + Object.defineProperty(this, Symbol.toStringTag, { + value: 'URLSearchParams', + writable: false, + enumerable: false, + configurable: true + }); } append(name, value) { + if (!this || !(this instanceof URLSearchParams)) { + throw new TypeError('Value of `this` is not a URLSearchParams'); + } + if (arguments.length < 2) { + throw new TypeError( + 'Both `name` and `value` arguments need to be specified'); + } + const obj = this[searchParams]; name = String(name); value = String(value); var existing = obj[name]; - if (!existing) { + if (existing === undefined) { obj[name] = value; } else if (Array.isArray(existing)) { existing.push(value); } else { obj[name] = [existing, value]; } - update(this[context], querystring.stringify(obj)); + update(this[context], this); } delete(name) { + if (!this || !(this instanceof URLSearchParams)) { + throw new TypeError('Value of `this` is not a URLSearchParams'); + } + if (arguments.length < 1) { + throw new TypeError('The `name` argument needs to be specified'); + } + const obj = this[searchParams]; name = String(name); delete obj[name]; - update(this[context], querystring.stringify(obj)); + update(this[context], this); } set(name, value) { + if (!this || !(this instanceof URLSearchParams)) { + throw new TypeError('Value of `this` is not a URLSearchParams'); + } + if (arguments.length < 2) { + throw new TypeError( + 'Both `name` and `value` arguments need to be specified'); + } + const obj = this[searchParams]; name = String(name); value = String(value); obj[name] = value; - update(this[context], querystring.stringify(obj)); + update(this[context], this); } get(name) { + if (!this || !(this instanceof URLSearchParams)) { + throw new TypeError('Value of `this` is not a URLSearchParams'); + } + if (arguments.length < 1) { + throw new TypeError('The `name` argument needs to be specified'); + } + const obj = this[searchParams]; name = String(name); var value = obj[name]; - return Array.isArray(value) ? value[0] : value; + return value === undefined ? null : Array.isArray(value) ? value[0] : value; } getAll(name) { + if (!this || !(this instanceof URLSearchParams)) { + throw new TypeError('Value of `this` is not a URLSearchParams'); + } + if (arguments.length < 1) { + throw new TypeError('The `name` argument needs to be specified'); + } + const obj = this[searchParams]; name = String(name); var value = obj[name]; @@ -561,28 +643,143 @@ class URLSearchParams { } has(name) { + if (!this || !(this instanceof URLSearchParams)) { + throw new TypeError('Value of `this` is not a URLSearchParams'); + } + if (arguments.length < 1) { + throw new TypeError('The `name` argument needs to be specified'); + } + const obj = this[searchParams]; name = String(name); return name in obj; } - *[Symbol.iterator]() { - const obj = this[searchParams]; - for (const name in obj) { - const value = obj[name]; - if (Array.isArray(value)) { - for (const item of value) - yield [name, item]; - } else { - yield [name, value]; - } + // https://heycam.github.io/webidl/#es-iterators + // Define entries here rather than [Symbol.iterator] as the function name + // must be set to `entries`. + entries() { + if (!this || !(this instanceof URLSearchParams)) { + throw new TypeError('Value of `this` is not a URLSearchParams'); } + + return createSearchParamsIterator(this, 'key+value'); } + forEach(callback, thisArg = undefined) { + if (!this || !(this instanceof URLSearchParams)) { + throw new TypeError('Value of `this` is not a URLSearchParams'); + } + if (arguments.length < 1) { + throw new TypeError('The `callback` argument needs to be specified'); + } + + let pairs = getSearchParamPairs(this); + + var i = 0; + while (i < pairs.length) { + const [key, value] = pairs[i]; + callback.call(thisArg, value, key, this); + pairs = getSearchParamPairs(this); + i++; + } + } + + // https://heycam.github.io/webidl/#es-iterable + keys() { + if (!this || !(this instanceof URLSearchParams)) { + throw new TypeError('Value of `this` is not a URLSearchParams'); + } + + return createSearchParamsIterator(this, 'key'); + } + + values() { + if (!this || !(this instanceof URLSearchParams)) { + throw new TypeError('Value of `this` is not a URLSearchParams'); + } + + return createSearchParamsIterator(this, 'value'); + } + + // https://url.spec.whatwg.org/#urlsearchparams-stringification-behavior toString() { + if (!this || !(this instanceof URLSearchParams)) { + throw new TypeError('Value of `this` is not a URLSearchParams'); + } + return querystring.stringify(this[searchParams]); } } +// https://heycam.github.io/webidl/#es-iterable-entries +URLSearchParams.prototype[Symbol.iterator] = URLSearchParams.prototype.entries; +Object.defineProperty(URLSearchParams.prototype, Symbol.toStringTag, { + value: 'URLSearchParamsPrototype', + writable: false, + enumerable: false, + configurable: true +}); + +// https://heycam.github.io/webidl/#dfn-default-iterator-object +function createSearchParamsIterator(target, kind) { + const iterator = Object.create(URLSearchParamsIteratorPrototype); + iterator[context] = { + target, + kind, + index: 0 + }; + return iterator; +} + +// https://heycam.github.io/webidl/#dfn-iterator-prototype-object +const URLSearchParamsIteratorPrototype = Object.setPrototypeOf({ + next() { + if (!this || + Object.getPrototypeOf(this) !== URLSearchParamsIteratorPrototype) { + throw new TypeError('Value of `this` is not a URLSearchParamsIterator'); + } + + const { + target, + kind, + index + } = this[context]; + const values = getSearchParamPairs(target); + const len = values.length; + if (index >= len) { + return { + value: undefined, + done: true + }; + } + + const pair = values[index]; + this[context].index = index + 1; + + let result; + if (kind === 'key') { + result = pair[0]; + } else if (kind === 'value') { + result = pair[1]; + } else { + result = pair; + } + + return { + value: result, + done: false + }; + } +}, IteratorPrototype); + +// Unlike interface and its prototype object, both default iterator object and +// iterator prototype object of an interface have the same class string. +Object.defineProperty(URLSearchParamsIteratorPrototype, Symbol.toStringTag, { + value: 'URLSearchParamsIterator', + writable: false, + enumerable: false, + configurable: true +}); URL.originFor = function(url) { if (!(url instanceof URL)) diff --git a/test/parallel/test-whatwg-url-searchparams-append.js b/test/parallel/test-whatwg-url-searchparams-append.js new file mode 100644 index 00000000000000..1f61cb0d11bb6b --- /dev/null +++ b/test/parallel/test-whatwg-url-searchparams-append.js @@ -0,0 +1,52 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const URL = require('url').URL; + +const m = new URL('http://example.org'); +let params = m.searchParams; + +// Until we export URLSearchParams +const URLSearchParams = params.constructor; + +// Append same name +params = new URLSearchParams(); +params.append('a', 'b'); +assert.strictEqual(params + '', 'a=b'); +params.append('a', 'b'); +assert.strictEqual(params + '', 'a=b&a=b'); +params.append('a', 'c'); +assert.strictEqual(params + '', 'a=b&a=b&a=c'); + +// Append empty strings +params = new URLSearchParams(); +params.append('', ''); +assert.strictEqual(params + '', '='); +params.append('', ''); +assert.strictEqual(params + '', '=&='); + +// Append null +params = new URLSearchParams(); +params.append(null, null); +assert.strictEqual(params + '', 'null=null'); +params.append(null, null); +assert.strictEqual(params + '', 'null=null&null=null'); + +// Append multiple +params = new URLSearchParams(); +params.append('first', 1); +params.append('second', 2); +params.append('third', ''); +params.append('first', 10); +assert.strictEqual(true, params.has('first'), + 'Search params object has name "first"'); +assert.strictEqual(params.get('first'), '1', + 'Search params object has name "first" with value "1"'); +assert.strictEqual(params.get('second'), '2', + 'Search params object has name "second" with value "2"'); +assert.strictEqual(params.get('third'), '', + 'Search params object has name "third" with value ""'); +params.append('first', 10); +assert.strictEqual(params.get('first'), '1', + 'Search params object has name "first" with value "1"'); diff --git a/test/parallel/test-whatwg-url-searchparams-constructor.js b/test/parallel/test-whatwg-url-searchparams-constructor.js new file mode 100644 index 00000000000000..98349021586f1a --- /dev/null +++ b/test/parallel/test-whatwg-url-searchparams-constructor.js @@ -0,0 +1,134 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const URL = require('url').URL; + +const m = new URL('http://example.org'); +let params = m.searchParams; + +// Until we export URLSearchParams +const URLSearchParams = params.constructor; + +// Basic URLSearchParams construction +params = new URLSearchParams(); +assert.strictEqual(params + '', ''); +params = new URLSearchParams(''); +assert.strictEqual(params + '', ''); +params = new URLSearchParams('a=b'); +assert.strictEqual(params + '', 'a=b'); +params = new URLSearchParams(params); +assert.strictEqual(params + '', 'a=b'); + +// URLSearchParams constructor, empty. +assert.throws(() => URLSearchParams(), TypeError, + 'Calling \'URLSearchParams\' without \'new\' should throw.'); +// assert.throws(() => new URLSearchParams(DOMException.prototype), TypeError); +assert.throws(() => { + new URLSearchParams({ + toString() { throw new TypeError('Illegal invocation'); } + }); +}, TypeError); +params = new URLSearchParams(''); +assert.notEqual(params, null, 'constructor returned non-null value.'); +// eslint-disable-next-line no-proto +assert.strictEqual(params.__proto__, URLSearchParams.prototype, + 'expected URLSearchParams.prototype as prototype.'); +params = new URLSearchParams({}); +// assert.strictEqual(params + '', '%5Bobject+Object%5D='); +assert.strictEqual(params + '', '%5Bobject%20Object%5D='); + +// URLSearchParams constructor, string. +params = new URLSearchParams('a=b'); +assert.notEqual(params, null, 'constructor returned non-null value.'); +assert.strictEqual(true, params.has('a'), + 'Search params object has name "a"'); +assert.strictEqual(false, params.has('b'), + 'Search params object has not got name "b"'); +params = new URLSearchParams('a=b&c'); +assert.notEqual(params, null, 'constructor returned non-null value.'); +assert.strictEqual(true, params.has('a'), + 'Search params object has name "a"'); +assert.strictEqual(true, params.has('c'), + 'Search params object has name "c"'); +params = new URLSearchParams('&a&&& &&&&&a+b=& c&m%c3%b8%c3%b8'); +assert.notEqual(params, null, 'constructor returned non-null value.'); +assert.strictEqual(true, params.has('a'), 'Search params object has name "a"'); +assert.strictEqual(true, params.has('a b'), + 'Search params object has name "a b"'); +assert.strictEqual(true, params.has(' '), + 'Search params object has name " "'); +assert.strictEqual(false, params.has('c'), + 'Search params object did not have the name "c"'); +assert.strictEqual(true, params.has(' c'), + 'Search params object has name " c"'); +assert.strictEqual(true, params.has('møø'), + 'Search params object has name "møø"'); + +// URLSearchParams constructor, object. +const seed = new URLSearchParams('a=b&c=d'); +params = new URLSearchParams(seed); +assert.notEqual(params, null, 'constructor returned non-null value.'); +assert.strictEqual(params.get('a'), 'b'); +assert.strictEqual(params.get('c'), 'd'); +assert.strictEqual(false, params.has('d')); +// The name-value pairs are copied when created; later updates +// should not be observable. +seed.append('e', 'f'); +assert.strictEqual(false, params.has('e')); +params.append('g', 'h'); +assert.strictEqual(false, seed.has('g')); + +// Parse + +params = new URLSearchParams('a=b+c'); +assert.strictEqual(params.get('a'), 'b c'); +params = new URLSearchParams('a+b=c'); +assert.strictEqual(params.get('a b'), 'c'); + +// Parse space +params = new URLSearchParams('a=b c'); +assert.strictEqual(params.get('a'), 'b c'); +params = new URLSearchParams('a b=c'); +assert.strictEqual(params.get('a b'), 'c'); + +// Parse %20 +params = new URLSearchParams('a=b%20c'); +assert.strictEqual(params.get('a'), 'b c'); +params = new URLSearchParams('a%20b=c'); +assert.strictEqual(params.get('a b'), 'c'); + +// Parse \0 +params = new URLSearchParams('a=b\0c'); +assert.strictEqual(params.get('a'), 'b\0c'); +params = new URLSearchParams('a\0b=c'); +assert.strictEqual(params.get('a\0b'), 'c'); + +// Parse %00 +params = new URLSearchParams('a=b%00c'); +assert.strictEqual(params.get('a'), 'b\0c'); +params = new URLSearchParams('a%00b=c'); +assert.strictEqual(params.get('a\0b'), 'c'); + +// Parse \u2384 (Unicode Character 'COMPOSITION SYMBOL' (U+2384)) +params = new URLSearchParams('a=b\u2384'); +assert.strictEqual(params.get('a'), 'b\u2384'); +params = new URLSearchParams('a\u2384b=c'); +assert.strictEqual(params.get('a\u2384b'), 'c'); + +// Parse %e2%8e%84 (Unicode Character 'COMPOSITION SYMBOL' (U+2384)) +params = new URLSearchParams('a=b%e2%8e%84'); +assert.strictEqual(params.get('a'), 'b\u2384'); +params = new URLSearchParams('a%e2%8e%84b=c'); +assert.strictEqual(params.get('a\u2384b'), 'c'); + +// Parse \uD83D\uDCA9 (Unicode Character 'PILE OF POO' (U+1F4A9)) +params = new URLSearchParams('a=b\uD83D\uDCA9c'); +assert.strictEqual(params.get('a'), 'b\uD83D\uDCA9c'); +params = new URLSearchParams('a\uD83D\uDCA9b=c'); +assert.strictEqual(params.get('a\uD83D\uDCA9b'), 'c'); + +// Parse %f0%9f%92%a9 (Unicode Character 'PILE OF POO' (U+1F4A9)) +params = new URLSearchParams('a=b%f0%9f%92%a9c'); +assert.strictEqual(params.get('a'), 'b\uD83D\uDCA9c'); +params = new URLSearchParams('a%f0%9f%92%a9b=c'); +assert.strictEqual(params.get('a\uD83D\uDCA9b'), 'c'); diff --git a/test/parallel/test-whatwg-url-searchparams-delete.js b/test/parallel/test-whatwg-url-searchparams-delete.js new file mode 100644 index 00000000000000..bdccf49baf1cc4 --- /dev/null +++ b/test/parallel/test-whatwg-url-searchparams-delete.js @@ -0,0 +1,44 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const URL = require('url').URL; + +const m = new URL('http://example.org'); +let params = m.searchParams; + +// Until we export URLSearchParams +const URLSearchParams = params.constructor; + +// Delete basics +params = new URLSearchParams('a=b&c=d'); +params.delete('a'); +assert.strictEqual(params + '', 'c=d'); +params = new URLSearchParams('a=a&b=b&a=a&c=c'); +params.delete('a'); +assert.strictEqual(params + '', 'b=b&c=c'); +params = new URLSearchParams('a=a&=&b=b&c=c'); +params.delete(''); +assert.strictEqual(params + '', 'a=a&b=b&c=c'); +params = new URLSearchParams('a=a&null=null&b=b'); +params.delete(null); +assert.strictEqual(params + '', 'a=a&b=b'); +params = new URLSearchParams('a=a&undefined=undefined&b=b'); +params.delete(undefined); +assert.strictEqual(params + '', 'a=a&b=b'); + +// Deleting appended multiple +params = new URLSearchParams(); +params.append('first', 1); +assert.strictEqual(true, params.has('first'), + 'Search params object has name "first"'); +assert.strictEqual(params.get('first'), '1', + 'Search params object has name "first" with value "1"'); +params.delete('first'); +assert.strictEqual(false, params.has('first'), + 'Search params object has no "first" name'); +params.append('first', 1); +params.append('first', 10); +params.delete('first'); +assert.strictEqual(false, params.has('first'), + 'Search params object has no "first" name'); diff --git a/test/parallel/test-whatwg-url-searchparams-foreach.js b/test/parallel/test-whatwg-url-searchparams-foreach.js new file mode 100644 index 00000000000000..b6d684b06743b0 --- /dev/null +++ b/test/parallel/test-whatwg-url-searchparams-foreach.js @@ -0,0 +1,43 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const URL = require('url').URL; + +const m = new URL('http://example.org'); +let params = m.searchParams; + +// Until we export URLSearchParams +const URLSearchParams = params.constructor; + +let a, b, i; + +// ForEach Check +params = new URLSearchParams('a=1&b=2&c=3'); +const keys = []; +const values = []; +params.forEach(function(value, key) { + keys.push(key); + values.push(value); +}); +assert.deepStrictEqual(keys, ['a', 'b', 'c']); +assert.deepStrictEqual(values, ['1', '2', '3']); + +// For-of Check +a = new URL('http://a.b/c?a=1&b=2&c=3&d=4'); +b = a.searchParams; +const c = []; +for (i of b) { + a.search = 'x=1&y=2&z=3'; + c.push(i); +} +assert.deepStrictEqual(c[0], ['a', '1']); +assert.deepStrictEqual(c[1], ['y', '2']); +assert.deepStrictEqual(c[2], ['z', '3']); + +// empty +a = new URL('http://a.b/c'); +b = a.searchParams; +for (i of b) { + assert(false, 'should not be reached'); +} diff --git a/test/parallel/test-whatwg-url-searchparams-get.js b/test/parallel/test-whatwg-url-searchparams-get.js new file mode 100644 index 00000000000000..667738f817b707 --- /dev/null +++ b/test/parallel/test-whatwg-url-searchparams-get.js @@ -0,0 +1,35 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const URL = require('url').URL; + +const m = new URL('http://example.org'); +let params = m.searchParams; + +// Until we export URLSearchParams +const URLSearchParams = params.constructor; + +// Get basics +params = new URLSearchParams('a=b&c=d'); +assert.strictEqual(params.get('a'), 'b'); +assert.strictEqual(params.get('c'), 'd'); +assert.strictEqual(params.get('e'), null); +params = new URLSearchParams('a=b&c=d&a=e'); +assert.strictEqual(params.get('a'), 'b'); +params = new URLSearchParams('=b&c=d'); +assert.strictEqual(params.get(''), 'b'); +params = new URLSearchParams('a=&c=d&a=e'); +assert.strictEqual(params.get('a'), ''); + +// More get() basics +params = new URLSearchParams('first=second&third&&'); +assert.notEqual(params, null, 'constructor returned non-null value.'); +assert.strictEqual(true, params.has('first'), + 'Search params object has name "first"'); +assert.strictEqual(params.get('first'), 'second', + 'Search params object has name "first" with value "second"'); +assert.strictEqual(params.get('third'), '', + 'Search params object has name "third" with empty value.'); +assert.strictEqual(params.get('fourth'), null, + 'Search params object has no "fourth" name and value.'); diff --git a/test/parallel/test-whatwg-url-searchparams-getall.js b/test/parallel/test-whatwg-url-searchparams-getall.js new file mode 100644 index 00000000000000..8333982d688c3b --- /dev/null +++ b/test/parallel/test-whatwg-url-searchparams-getall.js @@ -0,0 +1,43 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const URL = require('url').URL; + +const m = new URL('http://example.org'); +let params = m.searchParams; + +// Until we export URLSearchParams +const URLSearchParams = params.constructor; + +let matches; + +// getAll() basics +params = new URLSearchParams('a=b&c=d'); +assert.deepStrictEqual(params.getAll('a'), ['b']); +assert.deepStrictEqual(params.getAll('c'), ['d']); +assert.deepStrictEqual(params.getAll('e'), []); +params = new URLSearchParams('a=b&c=d&a=e'); +assert.deepStrictEqual(params.getAll('a'), ['b', 'e']); +params = new URLSearchParams('=b&c=d'); +assert.deepStrictEqual(params.getAll(''), ['b']); +params = new URLSearchParams('a=&c=d&a=e'); +assert.deepStrictEqual(params.getAll('a'), ['', 'e']); + +// getAll() multiples +params = new URLSearchParams('a=1&a=2&a=3&a'); +assert.strictEqual(true, params.has('a'), + 'Search params object has name "a"'); +matches = params.getAll('a'); +assert(matches && matches.length == 4, + 'Search params object has values for name "a"'); +assert.deepStrictEqual(matches, ['1', '2', '3', ''], + 'Search params object has expected name "a" values'); +params.set('a', 'one'); +assert.strictEqual(params.get('a'), 'one', + 'Search params object has name "a" with value "one"'); +matches = params.getAll('a'); +assert(matches && matches.length == 1, + 'Search params object has values for name "a"'); +assert.deepStrictEqual(matches, ['one'], + 'Search params object has expected name "a" values'); diff --git a/test/parallel/test-whatwg-url-searchparams-has.js b/test/parallel/test-whatwg-url-searchparams-has.js new file mode 100644 index 00000000000000..c884227e0b0f1d --- /dev/null +++ b/test/parallel/test-whatwg-url-searchparams-has.js @@ -0,0 +1,39 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const URL = require('url').URL; + +const m = new URL('http://example.org'); +let params = m.searchParams; + +// Until we export URLSearchParams +const URLSearchParams = params.constructor; + +// Has basics +params = new URLSearchParams('a=b&c=d'); +assert.strictEqual(true, params.has('a')); +assert.strictEqual(true, params.has('c')); +assert.strictEqual(false, params.has('e')); +params = new URLSearchParams('a=b&c=d&a=e'); +assert.strictEqual(true, params.has('a')); +params = new URLSearchParams('=b&c=d'); +assert.strictEqual(true, params.has('')); +params = new URLSearchParams('null=a'); +assert.strictEqual(true, params.has(null)); + +// has() following delete() +params = new URLSearchParams('a=b&c=d&&'); +params.append('first', 1); +params.append('first', 2); +assert.strictEqual(true, params.has('a'), + 'Search params object has name "a"'); +assert.strictEqual(true, params.has('c'), + 'Search params object has name "c"'); +assert.strictEqual(true, params.has('first'), + 'Search params object has name "first"'); +assert.strictEqual(false, params.has('d'), + 'Search params object has no name "d"'); +params.delete('first'); +assert.strictEqual(false, params.has('first'), + 'Search params object has no name "first"'); diff --git a/test/parallel/test-whatwg-url-searchparams-set.js b/test/parallel/test-whatwg-url-searchparams-set.js new file mode 100644 index 00000000000000..2d9ae8aaa8d021 --- /dev/null +++ b/test/parallel/test-whatwg-url-searchparams-set.js @@ -0,0 +1,38 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const URL = require('url').URL; + +const m = new URL('http://example.org'); +let params = m.searchParams; + +// Until we export URLSearchParams +const URLSearchParams = params.constructor; + +// Set basics +params = new URLSearchParams('a=b&c=d'); +params.set('a', 'B'); +assert.strictEqual(params + '', 'a=B&c=d'); +params = new URLSearchParams('a=b&c=d&a=e'); +params.set('a', 'B'); +assert.strictEqual(params + '', 'a=B&c=d'); +params.set('e', 'f'); +assert.strictEqual(params + '', 'a=B&c=d&e=f'); + +// URLSearchParams.set +params = new URLSearchParams('a=1&a=2&a=3'); +assert.strictEqual(true, params.has('a'), + 'Search params object has name "a"'); +assert.strictEqual(params.get('a'), '1', + 'Search params object has name "a" with value "1"'); +params.set('first', 4); +assert.strictEqual(true, params.has('a'), + 'Search params object has name "a"'); +assert.strictEqual(params.get('a'), '1', + 'Search params object has name "a" with value "1"'); +params.set('a', 4); +assert.strictEqual(true, params.has('a'), + 'Search params object has name "a"'); +assert.strictEqual(params.get('a'), '4', + 'Search params object has name "a" with value "4"'); diff --git a/test/parallel/test-whatwg-url-searchparams-stringifier.js b/test/parallel/test-whatwg-url-searchparams-stringifier.js new file mode 100644 index 00000000000000..0a53df634454b6 --- /dev/null +++ b/test/parallel/test-whatwg-url-searchparams-stringifier.js @@ -0,0 +1,116 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const URL = require('url').URL; + +const m = new URL('http://example.org'); +let params = m.searchParams; + +// Until we export URLSearchParams +const URLSearchParams = params.constructor; + +// Serialize space +// querystring does not currently handle spaces intelligently +// params = new URLSearchParams(); +// params.append('a', 'b c'); +// assert.strictEqual(params + '', 'a=b+c'); +// params.delete('a'); +// params.append('a b', 'c'); +// assert.strictEqual(params + '', 'a+b=c'); + +// Serialize empty value +params = new URLSearchParams(); +params.append('a', ''); +assert.strictEqual(params + '', 'a='); +params.append('a', ''); +assert.strictEqual(params + '', 'a=&a='); +params.append('', 'b'); +assert.strictEqual(params + '', 'a=&a=&=b'); +params.append('', ''); +assert.strictEqual(params + '', 'a=&a=&=b&='); +params.append('', ''); +assert.strictEqual(params + '', 'a=&a=&=b&=&='); + +// Serialize empty name +params = new URLSearchParams(); +params.append('', 'b'); +assert.strictEqual(params + '', '=b'); +params.append('', 'b'); +assert.strictEqual(params + '', '=b&=b'); + +// Serialize empty name and value +params = new URLSearchParams(); +params.append('', ''); +assert.strictEqual(params + '', '='); +params.append('', ''); +assert.strictEqual(params + '', '=&='); + +// Serialize + +params = new URLSearchParams(); +params.append('a', 'b+c'); +assert.strictEqual(params + '', 'a=b%2Bc'); +params.delete('a'); +params.append('a+b', 'c'); +assert.strictEqual(params + '', 'a%2Bb=c'); + +// Serialize = +params = new URLSearchParams(); +params.append('=', 'a'); +assert.strictEqual(params + '', '%3D=a'); +params.append('b', '='); +assert.strictEqual(params + '', '%3D=a&b=%3D'); + +// Serialize & +params = new URLSearchParams(); +params.append('&', 'a'); +assert.strictEqual(params + '', '%26=a'); +params.append('b', '&'); +assert.strictEqual(params + '', '%26=a&b=%26'); + +// Serialize *-._ +params = new URLSearchParams(); +params.append('a', '*-._'); +assert.strictEqual(params + '', 'a=*-._'); +params.delete('a'); +params.append('*-._', 'c'); +assert.strictEqual(params + '', '*-._=c'); + +// Serialize % +params = new URLSearchParams(); +params.append('a', 'b%c'); +assert.strictEqual(params + '', 'a=b%25c'); +params.delete('a'); +params.append('a%b', 'c'); +assert.strictEqual(params + '', 'a%25b=c'); + +// Serialize \\0 +params = new URLSearchParams(); +params.append('a', 'b\0c'); +assert.strictEqual(params + '', 'a=b%00c'); +params.delete('a'); +params.append('a\0b', 'c'); +assert.strictEqual(params + '', 'a%00b=c'); + +// Serialize \uD83D\uDCA9 +// Unicode Character 'PILE OF POO' (U+1F4A9) +params = new URLSearchParams(); +params.append('a', 'b\uD83D\uDCA9c'); +assert.strictEqual(params + '', 'a=b%F0%9F%92%A9c'); +params.delete('a'); +params.append('a\uD83D\uDCA9b', 'c'); +assert.strictEqual(params + '', 'a%F0%9F%92%A9b=c'); + +// URLSearchParams.toString + +// querystring parses `&&` as {'': ''} +// params = new URLSearchParams('a=b&c=d&&e&&'); +// assert.strictEqual(params.toString(), 'a=b&c=d&e='); + +// querystring does not currently handle spaces intelligently +// params = new URLSearchParams('a = b &a=b&c=d%20'); +// assert.strictEqual(params.toString(), 'a+=+b+&a=b&c=d+'); + +// The lone '=' _does_ survive the roundtrip. +params = new URLSearchParams('a=&a=b'); +assert.strictEqual(params.toString(), 'a=&a=b'); diff --git a/test/parallel/test-whatwg-url-searchparams.js b/test/parallel/test-whatwg-url-searchparams.js index 99e2e6a748c264..54743b97a31da9 100644 --- a/test/parallel/test-whatwg-url-searchparams.js +++ b/test/parallel/test-whatwg-url-searchparams.js @@ -29,8 +29,21 @@ assert.strictEqual(sp.toString(), serialized); assert.strictEqual(m.search, `?${serialized}`); +assert.strictEqual(sp[Symbol.iterator], sp.entries); + var key, val, n = 0; for ([key, val] of sp) { assert.strictEqual(key, 'a'); assert.strictEqual(val, String(values[n++])); } +n = 0; +for (key of sp.keys()) { + assert.strictEqual(key, 'a'); +} +n = 0; +for (val of sp.values()) { + assert.strictEqual(val, String(values[n++])); +} + +m.search = '?a=a&b=b'; +assert.strictEqual(sp.toString(), 'a=a&b=b');