Browse Source

url: improve URLSearchParams spec compliance

- Make URLSearchParams constructor spec-compliant
- Strip leading `?` in URL#search's setter
- Spec-compliant iterable interface
- More precise handling of update steps as mandated by the spec
- Add class strings to URLSearchParams objects and their prototype
- Make sure `this instanceof URLSearchParams` in methods

Also included are relevant tests from W3C's Web Platform Tests
(https://github.com/w3c/web-platform-tests/tree/master/url).

Fixes: https://github.com/nodejs/node/issues/9302
PR-URL: https://github.com/nodejs/node/pull/9484
Reviewed-By: James M Snell <jasnell@gmail.com>
v6
Timothy Gu 8 years ago
committed by Italo A. Casas
parent
commit
61d6293033
  1. 283
      lib/internal/url.js
  2. 52
      test/parallel/test-whatwg-url-searchparams-append.js
  3. 134
      test/parallel/test-whatwg-url-searchparams-constructor.js
  4. 44
      test/parallel/test-whatwg-url-searchparams-delete.js
  5. 43
      test/parallel/test-whatwg-url-searchparams-foreach.js
  6. 35
      test/parallel/test-whatwg-url-searchparams-get.js
  7. 43
      test/parallel/test-whatwg-url-searchparams-getall.js
  8. 39
      test/parallel/test-whatwg-url-searchparams-has.js
  9. 38
      test/parallel/test-whatwg-url-searchparams-set.js
  10. 116
      test/parallel/test-whatwg-url-searchparams-stringifier.js
  11. 13
      test/parallel/test-whatwg-url-searchparams.js

283
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);
@ -101,7 +106,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;
});
}
@ -318,8 +324,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() {
@ -493,76 +522,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];
@ -570,28 +652,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))

52
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"');

134
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');

44
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');

43
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');
}

35
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.');

43
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');

39
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"');

38
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"');

116
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');

13
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');

Loading…
Cancel
Save