diff --git a/CHANGELOG.md b/CHANGELOG.md index 03ce51b763dd..47affbbbe336 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,7 +47,36 @@ - `Symbol.patternMatch` ([for stage 1 pattern matching proposal](https://github.com/tc39/proposal-pattern-matching)) - `Symbol.dispose` ([for stage 1 `using` statement proposal](https://github.com/tc39/proposal-using-statement)) - `Promise.allSettled` ([stage 1 proposal](https://github.com/jasonwilliams/proposal-promise-allSettled)) - - `.forEach` method to iterable DOM collections ([#329](https://github.com/zloirock/core-js/issues/329)) + - `URL` and `URLSearchParam` [from `URL` standard](https://url.spec.whatwg.org/), also [stage 0 proposal to ECMAScript](https://github.com/jasnell/proposal-url) + - `URL` + - `URL#href` + - `URL#origin` + - `URL#protocol` + - `URL#username` + - `URL#password` + - `URL#host` + - `URL#hostname` + - `URL#port` + - `URL#pathname` + - `URL#search` + - `URL#searchParams` + - `URL#hash` + - `URL#toString` + - `URL#toJSON` + - `URLSearchParams` + - `URLSearchParams#append` + - `URLSearchParams#delete` + - `URLSearchParams#get` + - `URLSearchParams#getAll` + - `URLSearchParams#has` + - `URLSearchParams#set` + - `URLSearchParams#sort` + - `URLSearchParams#toString` + - `URLSearchParams#keys` + - `URLSearchParams#values` + - `URLSearchParams#entries` + - `URLSearchParams#@@iterator` + - `.forEach` method on iterable DOM collections ([#329](https://github.com/zloirock/core-js/issues/329)) - Improve existing features: - Add triggering unhandled `Promise` rejection events (instead of only global handlers), [#205](https://github.com/zloirock/core-js/issues/205). - Add support of `@@isConcatSpreadable` to `Array#concat`. diff --git a/README.md b/README.md index c85831a88164..b99c88fb29d0 100644 --- a/README.md +++ b/README.md @@ -72,9 +72,10 @@ Promise.resolve(32).then(x => console.log(x)); // => 32 - [stage 0 proposals](#stage-0-proposals) - [pre-stage 0 proposals](#pre-stage-0-proposals) - [Web standards](#web-standards) - - [setTimeout / setInterval](#settimeout--setinterval) - - [setImmediate](#setimmediate) - - [queueMicrotask](#queuemicrotask) + - [`setTimeout` and `setInterval`](#settimeout-and-setinterval) + - [`setImmediate`](#setimmediate) + - [`queueMicrotask`](#queuemicrotask) + - [`URL` and `URLSearchParams`](#url-and-urlsearchparams) - [iterable DOM collections](#iterable-dom-collections) - [Iteration helpers](#iteration-helpers) - [Missing polyfills](#missing-polyfills) @@ -535,7 +536,7 @@ class RegExp { @@replace(string: string, replaceValue: Function | string): string; @@search(string: string): number; @@split(string: string, limit: number): Array; - get flags: string; // IE9+ + readonly attribute flags: string; // IE9+ } ``` [*CommonJS entry points:*](#commonjs) @@ -1024,7 +1025,7 @@ class Map { keys(): Iterator; entries(): Iterator<[key, value]>; @@iterator(): Iterator<[key, value]>; - get size: number; + readonly attribute size: number; } ``` [*CommonJS entry points:*](#commonjs) @@ -1078,7 +1079,7 @@ class Set { keys(): Iterator; entries(): Iterator<[value, value]>; @@iterator(): Iterator; - get size: number; + readonly attribute size: number; } ``` [*CommonJS entry points:*](#commonjs) @@ -1194,7 +1195,7 @@ Modules [`es.array-buffer.constructor`](https://github.com/zloirock/core-js/blob class ArrayBuffer { constructor(length: any): ArrayBuffer; slice(start: any, end: any): ArrayBuffer; - get byteLength: number; + readonly attribute byteLength: number; static isView(arg: any): boolean; } @@ -1216,9 +1217,9 @@ class DataView { setUint32(offset: any, value: any, littleEndian?: boolean = false): void; setFloat32(offset: any, value: any, littleEndian?: boolean = false): void; setFloat64(offset: any, value: any, littleEndian?: boolean = false): void; - get buffer: ArrayBuffer; - get byteLength: number; - get byteOffset: number; + readonly attribute buffer: ArrayBuffer; + readonly attribute byteLength: number; + readonly attribute byteOffset: number; } class [ @@ -1261,10 +1262,10 @@ class [ keys(): Iterator; entries(): Iterator<[index, value]>; @@iterator(): Iterator; - get buffer: ArrayBuffer; - get byteLength: number; - get byteOffset: number; - get length: number; + readonly attribute buffer: ArrayBuffer; + readonly attribute byteLength: number; + readonly attribute byteOffset: number; + readonly attribute length: number; BYTES_PER_ELEMENT: number; static from(items: Iterable | ArrayLike, mapFn?: (value: any, index: number) => any, thisArg?: any): %TypedArray%; static of(...args: Array): %TypedArray%; @@ -1533,7 +1534,7 @@ globalThis.Array === Array; // => true * `Symbol#description` [proposal](https://github.com/tc39/proposal-Symbol-description) - module [`esnext.symbol.description`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.symbol.description.js) ```js class Symbol { - get description: string | void; + readonly attribute description: string | void; } ``` [*CommonJS entry points:*](#commonjs) @@ -1572,7 +1573,7 @@ core-js(-pure)/features/set/union [*Examples*](https://goo.gl/YjaxTN): ```js new Set([1, 2, 3]).union([3, 4, 5]); // => Set {1, 2, 3, 4, 5} -new Set([1, 2, 3]).intersection([3, 4, 5]); // => Set {3} +new Set([1, 2, 3]).intersection([3, 4, 5]); // => Set {3} new Set([1, 2, 3]).difference([3, 4, 5]); // => Set {1, 2} new Set([1, 2, 3]).symmetricDifference([3, 4, 5]); // => Set {1, 2, 4, 5} ``` @@ -1585,9 +1586,8 @@ core-js(-pure)/stage/1 * Getting last item from `Array` [proposal](https://github.com/keithamus/proposal-array-last) - modules [`esnext.array.last-item`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.array.last-item.js) and [`esnext.array.last-index`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.array.last-index.js) ```js class Array { - get lastItem: value; - set lastItem(value); - get lastIndex: uint; + attribute lastItem: any; + readonly attribute lastIndex: uint; } ``` [*CommonJS entry points:*](#commonjs) @@ -1797,7 +1797,7 @@ class Observable { @@observable(): this; static of(...items: Aray): Observable; static from(x: Observable | Iterable): Observable; - static get @@species: this; + static readonly attribute @@species: this; } class Symbol { @@ -1938,6 +1938,7 @@ core-js(-pure)/features/symbol/dispose ```js core-js(-pure)/stage/0 ``` +* `URL` [proposal](https://github.com/jasnell/proposal-url), see more info [in web standards namespace](#url-and-urlsearchparams) * `String#at` [proposal](https://github.com/mathiasbynens/String.prototype.at) - module [`esnext.string.at`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.string.at.js) ```js class String { @@ -2019,7 +2020,7 @@ Reflect.getOwnMetadata('foo', object); // => 'bar' ```js core-js(-pure)/web ``` -#### setTimeout / setInterval +#### `setTimeout` and `setInterval` Module [`web.timers`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/web.timers.js). Additional arguments fix for IE9-. ```js function setTimeout(callback: any, time: any, ...args: Array): number; @@ -2037,7 +2038,7 @@ setTimeout(log.bind(null, 42), 1000); // After: setTimeout(log, 1000, 42); ``` -#### setImmediate +#### `setImmediate` Module [`web.immediate`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/web.immediate.js). [`setImmediate` proposal](https://developer.mozilla.org/en-US/docs/Web/API/Window.setImmediate) polyfill. ```js function setImmediate(callback: any, ...args: Array): number; @@ -2059,7 +2060,8 @@ clearImmediate(setImmediate(() => { console.log('Message will not be displayed'); })); ``` -#### queueMicrotask + +#### `queueMicrotask` [Spec](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#dom-queuemicrotask), module [`web.queue-microtask`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/web.queue-microtask.js) ```js function queueMicrotask(fn: Function): void; @@ -2074,6 +2076,61 @@ core-js(-pure)/features/queue-microtask queueMicrotask(() => console.log('called as microtask')); ``` +#### `URL` and `URLSearchParams` +[`URL` standard](https://url.spec.whatwg.org/) implementation. Modules [`web.url`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/web.url.js), [`web.url.to-json`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/web.url.to-json.js), [`web.url-search-params`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/web.url-search-params.js), [`web.url-search-params.sort`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/web.url-search-params.sort.js). +```js +class URL { + constructor(url: string, base?: string); + attribute href: string; + readonly attribute origin: string; + attribute protocol: string; + attribute username: string; + attribute password: string; + attribute host: string; + attribute hostname: string; + attribute port: string; + attribute pathname: string; + attribute search: string; + readonly attribute searchParams: URLSearchParams; + attribute hash: string; + toJSON(): string; + toString(): string; +} + +class URLSearchParams { + constructor(params?: string | Iterable<[key, value]> | Object); + append(name: string, value: string): void; + delete(name: string): void; + get(name: string): string | void; + getAll(name: string): Array; + has(name: string): boolean; + set(name: string, value: string): void; + sort(): void; + toString(): string; + forEach(callbackfn: (value: any, index: number, target: any) => void, thisArg: any): void; + entries(): Iterator<[key, value]>; + keys(): Iterator; + values(): Iterator; + @@iterator(): Iterator<[key, value]>; +} +``` +[*CommonJS entry points:*](#commonjs) +```js +core-js/proposals/url +core-js(-pure)/web/url +core-js(-pure)/web/url-search-params +core-js(-pure)/features/url +core-js/features/url/to-json +core-js(-pure)/features/url-search-params +core-js/features/url-search-params/sort +``` +[*Examples*](): +```js +const url = new URL('http://zloirock.ru/'); + +const params = new URLSearchParams('?a=1&b=2&a=3'); +``` + #### Iterable DOM collections Some DOM collections should have [iterable interface](https://heycam.github.io/webidl/#idl-iterable) or should be [inherited from `Array`](https://heycam.github.io/webidl/#LegacyArrayClass). That means they should have `forEach`, `keys`, `values`, `entries` and `@@iterator` methods for iteration. So add them. Modules [`web.dom-collections.iterator`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/web.dom-collections.iterator.js) and [`web.dom-collections.for-each`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/web.dom-collections.for-each.js). ```js @@ -2112,8 +2169,8 @@ class [ } class [DOMTokenList, NodeList] { - entries(): Iterator<[key, value]>; forEach(callbackfn: (value: any, index: number, target: any) => void, thisArg: any): void; + entries(): Iterator<[key, value]>; keys(): Iterator; values(): Iterator; @@iterator(): Iterator; diff --git a/packages/core-js-builder/config.js b/packages/core-js-builder/config.js index ecaaa31f9d96..e9630fd2e810 100644 --- a/packages/core-js-builder/config.js +++ b/packages/core-js-builder/config.js @@ -279,6 +279,10 @@ module.exports = { 'web.immediate', 'web.queue-microtask', 'web.timers', + 'web.url', + 'web.url.to-json', + 'web.url-search-params', + 'web.url-search-params.sort', ], /* eslint-disable prefer-template */ banner: '/**\n' + diff --git a/packages/core-js-pure/override/internals/redefine-all.js b/packages/core-js-pure/override/internals/redefine-all.js index b7aeedff7d94..b6f71ef32abf 100644 --- a/packages/core-js-pure/override/internals/redefine-all.js +++ b/packages/core-js-pure/override/internals/redefine-all.js @@ -1,8 +1,8 @@ -var hide = require('../internals/hide'); +var redefine = require('../internals/redefine'); -module.exports = function (target, src, safe) { +module.exports = function (target, src, options) { for (var key in src) { - if (safe && target[key]) target[key] = src[key]; - else hide(target, key, src[key]); + if (options && options.unsafe && target[key]) target[key] = src[key]; + else redefine(target, key, src[key], options); } return target; }; diff --git a/packages/core-js-pure/override/internals/redefine.js b/packages/core-js-pure/override/internals/redefine.js index 71639f253967..1351097770cb 100644 --- a/packages/core-js-pure/override/internals/redefine.js +++ b/packages/core-js-pure/override/internals/redefine.js @@ -1 +1,6 @@ -module.exports = require('../internals/hide'); +var hide = require('../internals/hide'); + +module.exports = function (target, key, value, options) { + if (options && options.enumerable) target[key] = value; + else hide(target, key, value); +}; diff --git a/packages/core-js-pure/override/modules/web.url-search-params.sort.js b/packages/core-js-pure/override/modules/web.url-search-params.sort.js new file mode 100644 index 000000000000..8b1a393741c9 --- /dev/null +++ b/packages/core-js-pure/override/modules/web.url-search-params.sort.js @@ -0,0 +1 @@ +// empty diff --git a/packages/core-js-pure/override/modules/web.url.to-json.js b/packages/core-js-pure/override/modules/web.url.to-json.js new file mode 100644 index 000000000000..8b1a393741c9 --- /dev/null +++ b/packages/core-js-pure/override/modules/web.url.to-json.js @@ -0,0 +1 @@ +// empty diff --git a/packages/core-js/features/url-search-params/index.js b/packages/core-js/features/url-search-params/index.js new file mode 100644 index 000000000000..7fb743701298 --- /dev/null +++ b/packages/core-js/features/url-search-params/index.js @@ -0,0 +1,4 @@ +require('../../modules/web.url-search-params'); +require('../../modules/web.url-search-params.sort'); + +module.exports = require('../../internals/path').URLSearchParams; diff --git a/packages/core-js/features/url-search-params/sort.js b/packages/core-js/features/url-search-params/sort.js new file mode 100644 index 000000000000..92ea3294e4e1 --- /dev/null +++ b/packages/core-js/features/url-search-params/sort.js @@ -0,0 +1 @@ +require('../../modules/web.url-search-params.sort'); diff --git a/packages/core-js/features/url/index.js b/packages/core-js/features/url/index.js new file mode 100644 index 000000000000..9405e46b738d --- /dev/null +++ b/packages/core-js/features/url/index.js @@ -0,0 +1,6 @@ +require('../../modules/web.url'); +require('../../modules/web.url.to-json'); +require('../../modules/web.url-search-params'); +require('../../modules/web.url-search-params.sort'); + +module.exports = require('../../internals/path').URL; diff --git a/packages/core-js/features/url/to-json.js b/packages/core-js/features/url/to-json.js new file mode 100644 index 000000000000..0d841b68ef23 --- /dev/null +++ b/packages/core-js/features/url/to-json.js @@ -0,0 +1 @@ +require('../../modules/web.url.to-json'); diff --git a/packages/core-js/index.js b/packages/core-js/index.js index 807cc5002df8..14c035d825f6 100644 --- a/packages/core-js/index.js +++ b/packages/core-js/index.js @@ -276,4 +276,8 @@ require('./modules/web.dom-collections.iterator'); require('./modules/web.immediate'); require('./modules/web.queue-microtask'); require('./modules/web.timers'); +require('./modules/web.url'); +require('./modules/web.url.to-json'); +require('./modules/web.url-search-params'); +require('./modules/web.url-search-params.sort'); module.exports = require('./internals/path'); diff --git a/packages/core-js/internals/export.js b/packages/core-js/internals/export.js index 86023dbea44c..178b470c4067 100644 --- a/packages/core-js/internals/export.js +++ b/packages/core-js/internals/export.js @@ -5,16 +5,17 @@ var setGlobal = require('../internals/set-global'); var copyConstructorProperties = require('../internals/copy-constructor-properties'); /* - options.target - name of the target object - options.global - target is the global object - options.stat - export as static methods of target - options.proto - export as prototype methods of target - options.real - real prototype method for the `pure` version - options.forced - export even if the native feature is available - options.bind - bind methods to the target, required for the `pure` version - options.wrap - wrap constructors to preventing global pollution, required for the `pure` version - options.unsafe - use the simple assignment of property instead of delete + defineProperty - options.sham - add a flag to not completely full polyfills + options.target - name of the target object + options.global - target is the global object + options.stat - export as static methods of target + options.proto - export as prototype methods of target + options.real - real prototype method for the `pure` version + options.forced - export even if the native feature is available + options.bind - bind methods to the target, required for the `pure` version + options.wrap - wrap constructors to preventing global pollution, required for the `pure` version + options.unsafe - use the simple assignment of property instead of delete + defineProperty + options.sham - add a flag to not completely full polyfills + options.enumerable - export as enumerable property */ module.exports = function (options, source) { var TARGET = options.target; @@ -39,6 +40,6 @@ module.exports = function (options, source) { hide(sourceProperty, 'sham', true); } // extend global - redefine(target, key, sourceProperty, options.unsafe); + redefine(target, key, sourceProperty, options); } }; diff --git a/packages/core-js/internals/native-url.js b/packages/core-js/internals/native-url.js new file mode 100644 index 000000000000..57f4b7f7b7f6 --- /dev/null +++ b/packages/core-js/internals/native-url.js @@ -0,0 +1,13 @@ +var IS_PURE = require('../internals/is-pure'); +var ITERATOR = require('../internals/well-known-symbol')('iterator'); + +module.exports = !require('../internals/fails')(function () { + var url = new URL('b?e=1', 'http://a'); + var searchParams = url.searchParams; + url.pathname = 'c%20d'; + return (IS_PURE && (!url.toJSON || !searchParams.sort)) + || url.href !== 'http://a/c%20d?e=1' + || searchParams.get('e') !== '1' + || String(new URLSearchParams('?a=1')) !== 'a=1' + || !searchParams[ITERATOR]; +}); diff --git a/packages/core-js/internals/redefine-all.js b/packages/core-js/internals/redefine-all.js index 174b1b966046..3dc4477f65d3 100644 --- a/packages/core-js/internals/redefine-all.js +++ b/packages/core-js/internals/redefine-all.js @@ -1,6 +1,6 @@ var redefine = require('../internals/redefine'); -module.exports = function (target, src, safe) { - for (var key in src) redefine(target, key, src[key], safe); +module.exports = function (target, src, options) { + for (var key in src) redefine(target, key, src[key], options); return target; }; diff --git a/packages/core-js/internals/redefine.js b/packages/core-js/internals/redefine.js index bd005e7593fe..5dfae3bc0296 100644 --- a/packages/core-js/internals/redefine.js +++ b/packages/core-js/internals/redefine.js @@ -12,21 +12,23 @@ require('../internals/shared')('inspectSource', function (it) { return nativeFunctionToString.call(it); }); -(module.exports = function (O, key, value, unsafe) { +(module.exports = function (O, key, value, options) { + var unsafe = options ? !!options.unsafe : false; + var simple = options ? !!options.enumerable : false; if (typeof value == 'function') { if (typeof key == 'string' && !has(value, 'name')) hide(value, 'name', key); enforceInternalState(value).source = TEMPLATE.join(typeof key == 'string' ? key : ''); } if (O === global) { setGlobal(key, value); + return; } else if (!unsafe) { delete O[key]; - hide(O, key, value); } else if (O[key]) { - O[key] = value; - } else { - hide(O, key, value); + simple = true; } + if (simple) O[key] = value; + else hide(O, key, value); // add fake Function#toString for correct work wrapped methods / constructors with methods like LoDash isNative })(Function.prototype, 'toString', function toString() { return typeof this == 'function' && getInternalState(this).source || nativeFunctionToString.call(this); diff --git a/packages/core-js/modules/es.object.to-string.js b/packages/core-js/modules/es.object.to-string.js index 0b00cea13be1..be3673e223fe 100644 --- a/packages/core-js/modules/es.object.to-string.js +++ b/packages/core-js/modules/es.object.to-string.js @@ -3,5 +3,5 @@ var toString = require('../internals/object-to-string'); // `Object.prototype.toString` method // https://tc39.github.io/ecma262/#sec-object.prototype.tostring if (toString !== ({}).toString) { - require('../internals/redefine')(Object.prototype, 'toString', toString, true); + require('../internals/redefine')(Object.prototype, 'toString', toString, { unsafe: true }); } diff --git a/packages/core-js/modules/es.regexp.to-string.js b/packages/core-js/modules/es.regexp.to-string.js index 9acbf45528d7..66d442eff7bd 100644 --- a/packages/core-js/modules/es.regexp.to-string.js +++ b/packages/core-js/modules/es.regexp.to-string.js @@ -17,5 +17,5 @@ if (NOT_GENERIC || INCORRECT_NAME) { var R = anObject(this); return '/'.concat(R.source, '/', 'flags' in R ? R.flags : !DESCRIPTORS && R instanceof RegExp ? flags.call(R) : undefined); - }, true); + }, { unsafe: true }); } diff --git a/packages/core-js/modules/es.symbol.js b/packages/core-js/modules/es.symbol.js index 9b4f38d1329e..16966f363f44 100644 --- a/packages/core-js/modules/es.symbol.js +++ b/packages/core-js/modules/es.symbol.js @@ -183,7 +183,7 @@ if (!USE_NATIVE) { } }); if (!IS_PURE) { - redefine(ObjectPrototype, 'propertyIsEnumerable', $propertyIsEnumerable, true); + redefine(ObjectPrototype, 'propertyIsEnumerable', $propertyIsEnumerable, { unsafe: true }); } } diff --git a/packages/core-js/modules/web.url-search-params.js b/packages/core-js/modules/web.url-search-params.js new file mode 100644 index 000000000000..2616684cc1dc --- /dev/null +++ b/packages/core-js/modules/web.url-search-params.js @@ -0,0 +1,273 @@ +'use strict'; +require('../modules/es.array.iterator'); +var USE_NATIVE_URL = require('../internals/native-url'); +var redefine = require('../internals/redefine'); +var redefineAll = require('../internals/redefine-all'); +var createIteratorConstructor = require('../internals/create-iterator-constructor'); +var InternalStateModule = require('../internals/internal-state'); +var anInstance = require('../internals/an-instance'); +var hasOwn = require('../internals/has'); +var bind = require('../internals/bind-context'); +var anObject = require('../internals/an-object'); +var isObject = require('../internals/is-object'); +var getIterator = require('../internals/get-iterator'); +var getIteratorMethod = require('../internals/get-iterator-method'); +var ITERATOR = require('../internals/well-known-symbol')('iterator'); +var URL_SEARCH_PARAMS = 'URLSearchParams'; +var URL_SEARCH_PARAMS_ITERATOR = URL_SEARCH_PARAMS + 'Iterator'; +var setInternalState = InternalStateModule.set; +var getInternalParamsState = InternalStateModule.getterFor(URL_SEARCH_PARAMS); +var getInternalIteratorState = InternalStateModule.getterFor(URL_SEARCH_PARAMS_ITERATOR); + +var plus = /\+/g; + +var deserialize = function (it) { + return decodeURIComponent(it.replace(plus, ' ')); +}; + +var find = /[!'()~]|%20/g; + +var replace = { + '!': '%21', + "'": '%27', + '(': '%28', + ')': '%29', + '~': '%7E', + '%20': '+' +}; + +var replacer = function (match) { + return replace[match]; +}; + +var serialize = function (it) { + return encodeURIComponent(it).replace(find, replacer); +}; + +var parseSearchParams = function (result, search) { + var string = typeof search === 'string' ? search.charAt(0) === '?' ? search.slice(1) : search : search + ''; + if (string !== '') { + var attributes = string.split('&'); + var i = 0; + var attribute, entry; + while (i < attributes.length) { + attribute = attributes[i++]; + if (attribute.length) { + entry = attribute.split('='); + result.push({ + key: deserialize(entry.shift()), + value: deserialize(entry.join('=')) + }); + } + } + } return result; +}; + +var validateArgumentsLength = function (passed, required) { + if (passed < required) throw new TypeError('Not enough arguments!'); +}; + +var URLSearchParamsIterator = createIteratorConstructor(function Iterator(params, kind) { + setInternalState(this, { + type: URL_SEARCH_PARAMS_ITERATOR, + iterator: getIterator(getInternalParamsState(params).entries), + kind: kind + }); +}, 'Iterator', function next() { + var state = getInternalIteratorState(this); + var kind = state.kind; + var step = state.iterator.next(); + var entry = step.value; + if (!step.done) { + step.value = kind === 'keys' ? entry.key : kind === 'values' ? entry.value : [entry.key, entry.value]; + } return step; +}); + +// `URLSearchParams` constructor +// https://url.spec.whatwg.org/#interface-urlsearchparams +var URLSearchParams = function URLSearchParams(/* init */) { + anInstance(this, URLSearchParams, URL_SEARCH_PARAMS); + var init = arguments.length > 0 ? arguments[0] : undefined; + var that = this; + var entries = []; + var iteratorMethod, iterator, step, entryIterator, first, second, key; + + setInternalState(that, { + type: URL_SEARCH_PARAMS, + entries: entries, + updateURL: null, + updateSearchParams: function (string) { + entries.length = 0; + parseSearchParams(entries, string); + } + }); + + if (init !== undefined) { + if (isObject(init)) { + iteratorMethod = getIteratorMethod(init); + if (typeof iteratorMethod === 'function') { + iterator = iteratorMethod.call(init); + while (!(step = iterator.next()).done) { + entryIterator = getIterator(anObject(step.value)); + if ( + (first = entryIterator.next()).done || + (second = entryIterator.next()).done || + !entryIterator.next().done + ) throw new TypeError('Expected sequence with length 2'); + entries.push({ key: first.value + '', value: second.value + '' }); + } + } else for (key in init) if (hasOwn(init, key)) entries.push({ key: key, value: init[key] + '' }); + } else parseSearchParams(entries, init); + } +}; + +var URLSearchParamsPrototype = URLSearchParams.prototype; + +redefineAll(URLSearchParamsPrototype, { + // `URLSearchParams.prototype.appent` method + // https://url.spec.whatwg.org/#dom-urlsearchparams-append + append: function append(name, value) { + validateArgumentsLength(arguments.length, 2); + var state = getInternalParamsState(this); + state.entries.push({ key: name + '', value: value + '' }); + if (state.updateURL) state.updateURL(); + }, + // `URLSearchParams.prototype.delete` method + // https://url.spec.whatwg.org/#dom-urlsearchparams-delete + 'delete': function (name) { + validateArgumentsLength(arguments.length, 1); + var state = getInternalParamsState(this); + var entries = state.entries; + var key = name + ''; + var i = 0; + while (i < entries.length) { + if (entries[i].key === key) entries.splice(i, 1); + else i++; + } + if (state.updateURL) state.updateURL(); + }, + // `URLSearchParams.prototype.get` method + // https://url.spec.whatwg.org/#dom-urlsearchparams-get + get: function get(name) { + validateArgumentsLength(arguments.length, 1); + var entries = getInternalParamsState(this).entries; + var key = name + ''; + var i = 0; + for (; i < entries.length; i++) if (entries[i].key === key) return entries[i].value; + return null; + }, + // `URLSearchParams.prototype.getAll` method + // https://url.spec.whatwg.org/#dom-urlsearchparams-getall + getAll: function getAll(name) { + validateArgumentsLength(arguments.length, 1); + var entries = getInternalParamsState(this).entries; + var key = name + ''; + var result = []; + var i = 0; + for (; i < entries.length; i++) if (entries[i].key === key) result.push(entries[i].value); + return result; + }, + // `URLSearchParams.prototype.has` method + // https://url.spec.whatwg.org/#dom-urlsearchparams-has + has: function has(name) { + validateArgumentsLength(arguments.length, 1); + var entries = getInternalParamsState(this).entries; + var key = name + ''; + var i = 0; + while (i < entries.length) if (entries[i++].key === key) return true; + return false; + }, + // `URLSearchParams.prototype.set` method + // https://url.spec.whatwg.org/#dom-urlsearchparams-set + set: function set(name, value) { + validateArgumentsLength(arguments.length, 1); + var state = getInternalParamsState(this); + var entries = state.entries; + var found = false; + var key = name + ''; + var val = value + ''; + var i = 0; + var entry; + for (; i < entries.length; i++) { + entry = entries[i]; + if (entry.key === key) { + if (found) entries.splice(i--, 1); + else { + found = true; + entry.value = val; + } + } + } + if (!found) entries.push({ key: key, value: val }); + if (state.updateURL) state.updateURL(); + }, + // `URLSearchParams.prototype.sort` method + // https://url.spec.whatwg.org/#dom-urlsearchparams-sort + sort: function sort() { + var state = getInternalParamsState(this); + var entries = state.entries; + // Array#sort is not stable in some engines + var slice = entries.slice(); + var entry, i, j; + entries.length = 0; + for (i = 0; i < slice.length; i++) { + entry = slice[i]; + for (j = 0; j < i; j++) if (entries[j].key > entry.key) { + entries.splice(j, 0, entry); + break; + } + if (j === i) entries.push(entry); + } + if (state.updateURL) state.updateURL(); + }, + // `URLSearchParams.prototype.forEach` method + forEach: function forEach(callback /* , thisArg */) { + var entries = getInternalParamsState(this).entries; + var boundFunction = bind(callback, arguments.length > 1 ? arguments[1] : undefined, 3); + var i = 0; + var entry; + while (i < entries.length) { + entry = entries[i++]; + boundFunction(entry.value, entry.key, this); + } + }, + // `URLSearchParams.prototype.keys` method + keys: function keys() { + return new URLSearchParamsIterator(this, 'keys'); + }, + // `URLSearchParams.prototype.values` method + values: function values() { + return new URLSearchParamsIterator(this, 'values'); + }, + // `URLSearchParams.prototype.entries` method + entries: function entries() { + return new URLSearchParamsIterator(this, 'entries'); + } +}, { enumerable: true }); + +// `URLSearchParams.prototype[@@iterator]` method +redefine(URLSearchParamsPrototype, ITERATOR, URLSearchParamsPrototype.entries); + +// `URLSearchParams.prototype.toString` method +// https://url.spec.whatwg.org/#urlsearchparams-stringification-behavior +redefine(URLSearchParamsPrototype, 'toString', function toString() { + var entries = getInternalParamsState(this).entries; + var result = []; + var i = 0; + var entry; + while (i < entries.length) { + entry = entries[i++]; + result.push(serialize(entry.key) + '=' + serialize(entry.value)); + } return result.join('&'); +}, { enumerable: true }); + +require('../internals/set-to-string-tag')(URLSearchParams, URL_SEARCH_PARAMS); + +require('../internals/export')({ global: true, forced: !USE_NATIVE_URL }, { + URLSearchParams: URLSearchParams +}); + +module.exports = { + URLSearchParams: URLSearchParams, + getState: getInternalParamsState +}; diff --git a/packages/core-js/modules/web.url-search-params.sort.js b/packages/core-js/modules/web.url-search-params.sort.js new file mode 100644 index 000000000000..46782b4a1ef5 --- /dev/null +++ b/packages/core-js/modules/web.url-search-params.sort.js @@ -0,0 +1,29 @@ +'use strict'; +// `URLSearchParams.prototype.sort` method +// https://url.spec.whatwg.org/#example-searchparams-sort +require('../internals/export')({ target: 'URLSearchParams', proto: true, enumerable: true }, { + sort: function sort() { + var URLSearchParamsPrototype = URLSearchParams.prototype; + var URLSearchParamsForEach = URLSearchParamsPrototype.forEach; + var URLSearchParamsAppend = URLSearchParamsPrototype.append; + var URLSearchParamsDelete = URLSearchParamsPrototype['delete']; + var items = []; + var length, entry, i; + // Array#sort is not stable in some engines + URLSearchParamsForEach.call(this, function (value, key) { + length = items.length; + entry = { key: key, value: value }; + for (i = 0; i < length; i++) if (items[i].key > key) { + items.splice(i, 0, entry); + break; + } + if (i === length) items.push(entry); + }); + for (i = 0; i < items.length; i++) { + URLSearchParamsDelete.call(this, items[i].key); + } + for (i = 0; i < items.length; i++) { + URLSearchParamsAppend.call(this, items[i].key, items[i].value); + } + } +}); diff --git a/packages/core-js/modules/web.url.js b/packages/core-js/modules/web.url.js new file mode 100644 index 000000000000..537a6bf6d80d --- /dev/null +++ b/packages/core-js/modules/web.url.js @@ -0,0 +1,685 @@ +'use strict'; +/* eslint-disable no-labels */ +var DESCRIPTORS = require('../internals/descriptors'); +var USE_NATIVE_URL = require('../internals/native-url'); +var NativeURL = require('../internals/global').URL; +var defineProperties = require('../internals/object-define-properties'); +var redefine = require('../internals/redefine'); +var anInstance = require('../internals/an-instance'); +var create = require('../internals/object-create'); +var has = require('../internals/has'); +var URLSearchParamsModule = require('../modules/web.url-search-params'); +var URLSearchParams = URLSearchParamsModule.URLSearchParams; +var getInternalSearchParamsState = URLSearchParamsModule.getState; +var InternalStateModule = require('../internals/internal-state'); +var setInternalState = InternalStateModule.set; +var getInternalURLState = InternalStateModule.getterFor('URL'); + +var trim = /^[ \t\r\n\f]+|[ \t\r\n\f]+$/g; + +var relative = create(null); +relative.ftp = 21; +relative.file = 0; +relative.gopher = 70; +relative.http = 80; +relative.https = 443; +relative.ws = 80; +relative.wss = 443; + +var relativePathDotMapping = create(null); +relativePathDotMapping['%2e'] = '.'; +relativePathDotMapping['.%2e'] = '..'; +relativePathDotMapping['%2e.'] = '..'; +relativePathDotMapping['%2e%2e'] = '..'; + +var isRelativeScheme = function (scheme) { + return has(relative, scheme); +}; + +var invalid = function (state) { + clear(state); + state.isInvalid = true; +}; + +var IDNAToASCII = function (state, h) { + if ('' == h) invalid(state); + return h.toLowerCase(); +}; + +var percentEscape = function (char) { + var code = char.charCodeAt(0); + return code > 0x20 && code < 0x7F && + // " # < > ? ` + 0x22 != code && 0x23 != code && 0x3C != code && 0x3E != code && 0x3F != code && 0x60 != code + ? char : encodeURIComponent(char); +}; + +var percentEscapeQuery = function (char) { + var code = char.charCodeAt(0); + return code > 0x20 && code < 0x7F && + // " # < > ` (do not escape '?') + 0x22 != code && 0x23 != code && 0x3C != code && 0x3E != code && 0x60 != code + ? char : encodeURIComponent(char); +}; + +var ALPHA = /[a-zA-Z]/; +var ALPHANUMERIC = /[a-zA-Z0-9+\-.]/; +var EOF = ''; + +// States: +var SCHEME_START = {}; +var SCHEME = {}; +var SCHEME_DATA = {}; +var NO_SCHEME = {}; +var RELATIVE_OR_AUTHORITY = {}; +var RELATIVE = {}; +var RELATIVE_SLASH = {}; +var AUTHORITY_FIRST_SLASH = {}; +var AUTHORITY_SECOND_SLASH = {}; +var AUTHORITY_IGNORE_SLASHES = {}; +var AUTHORITY = {}; +var FILE_HOST = {}; +var HOST = {}; +var HOSTNAME = {}; +var PORT = {}; +var RELATIVE_PATH_START = {}; +var RELATIVE_PATH = {}; +var QUERY = {}; +var FRAGMENT = {}; + +// URL parser based on https://github.com/webcomponents/URL +// eslint-disable-next-line max-statements +var parse = function (urlState, input, stateOverride, baseState) { + var err = function (message) { + errors.push(message); + }; + + var state = stateOverride || SCHEME_START; + var cursor = 0; + var buffer = ''; + var seenAt = false; + var seenBracket = false; + var errors = []; + + loop: while ((input.charAt(cursor - 1) != EOF || cursor == 0) && !urlState.isInvalid) { + var char = input.charAt(cursor); + switch (state) { + case SCHEME_START: + if (char && ALPHA.test(char)) { + buffer += char.toLowerCase(); // ASCII-safe + state = SCHEME; + } else if (!stateOverride) { + buffer = ''; + state = NO_SCHEME; + continue; + } else { + err('Invalid scheme.'); + break loop; + } break; + + case SCHEME: + if (char && ALPHANUMERIC.test(char)) { + buffer += char.toLowerCase(); // ASCII-safe + } else if (':' == char) { + urlState.scheme = buffer; + buffer = ''; + if (stateOverride) break loop; + if (isRelativeScheme(urlState.scheme)) urlState.isRelative = true; + if ('file' == urlState.scheme) { + state = RELATIVE; + } else if (urlState.isRelative && baseState && baseState.scheme == urlState.scheme) { + state = RELATIVE_OR_AUTHORITY; + } else if (urlState.isRelative) { + state = AUTHORITY_FIRST_SLASH; + } else state = SCHEME_DATA; + } else if (!stateOverride) { + buffer = ''; + cursor = 0; + state = NO_SCHEME; + continue; + } else if (EOF == char) { + break loop; + } else { + err('Code point not allowed in scheme: ' + char); + break loop; + } break; + + case SCHEME_DATA: + if ('?' == char) { + urlState.query = '?'; + state = QUERY; + } else if ('#' == char) { + urlState.fragment = '#'; + state = FRAGMENT; + // XXX error handling + } else if (EOF != char && '\t' != char && '\n' != char && '\r' != char) { + urlState.schemeData += percentEscape(char); + } break; + + case NO_SCHEME: + if (!baseState || !(isRelativeScheme(baseState.scheme))) { + err('Missing scheme.'); + invalid(urlState); + } else { + state = RELATIVE; + continue; + } break; + + case RELATIVE_OR_AUTHORITY: + if ('/' == char && '/' == input.charAt(cursor + 1)) { + state = AUTHORITY_IGNORE_SLASHES; + } else { + err('Expected /, got: ' + char); + state = RELATIVE; + continue; + } break; + + case RELATIVE: + urlState.isRelative = true; + if (!baseState) baseState = getInternalURLState(new URL()); + if ('file' != urlState.scheme) urlState.scheme = baseState.scheme; + if (EOF == char) { + urlState.host = baseState.host; + urlState.port = baseState.port; + urlState.path = baseState.path.slice(); + urlState.query = baseState.query; + urlState.username = baseState.username; + urlState.password = baseState.password; + break loop; + } else if ('/' == char || '\\' == char) { + if ('\\' == char) err('\\ is an invalid code point.'); + state = RELATIVE_SLASH; + } else if ('?' == char) { + urlState.host = baseState.host; + urlState.port = baseState.port; + urlState.path = baseState.path.slice(); + urlState.query = '?'; + urlState.username = baseState.username; + urlState.password = baseState.password; + state = QUERY; + } else if ('#' == char) { + urlState.host = baseState.host; + urlState.port = baseState.port; + urlState.path = baseState.path.slice(); + urlState.query = baseState.query; + urlState.fragment = '#'; + urlState.username = baseState.username; + urlState.password = baseState.password; + state = FRAGMENT; + } else { + var nextC = input.charAt(cursor + 1); + var nextNextC = input.charAt(cursor + 2); + if ( + 'file' != urlState.scheme || !ALPHA.test(char) || + (nextC != ':' && nextC != '|') || + (EOF != nextNextC && '/' != nextNextC && '\\' != nextNextC && '?' != nextNextC && '#' != nextNextC) + ) { + urlState.host = baseState.host; + urlState.port = baseState.port; + urlState.username = baseState.username; + urlState.password = baseState.password; + urlState.path = baseState.path.slice(); + urlState.path.pop(); + } + state = RELATIVE_PATH; + continue; + } break; + + case RELATIVE_SLASH: + if ('/' == char || '\\' == char) { + if ('\\' == char) err('\\ is an invalid code point.'); + if ('file' == urlState.scheme) state = FILE_HOST; + else state = AUTHORITY_IGNORE_SLASHES; + } else { + if ('file' != urlState.scheme) { + urlState.host = baseState.host; + urlState.port = baseState.port; + urlState.username = baseState.username; + urlState.password = baseState.password; + } + state = RELATIVE_PATH; + continue; + } break; + + case AUTHORITY_FIRST_SLASH: + if ('/' == char) { + state = AUTHORITY_SECOND_SLASH; + } else { + err("Expected '/', got: " + char); + state = AUTHORITY_IGNORE_SLASHES; + continue; + } break; + + case AUTHORITY_SECOND_SLASH: + state = AUTHORITY_IGNORE_SLASHES; + if ('/' != char) { + err("Expected '/', got: " + char); + continue; + } break; + + case AUTHORITY_IGNORE_SLASHES: + if ('/' != char && '\\' != char) { + state = AUTHORITY; + continue; + } else err('Expected authority, got: ' + char); + break; + + case AUTHORITY: + if ('@' == char) { + if (seenAt) { + err('@ already seen.'); + buffer += '%40'; + } + seenAt = true; + for (var i = 0; i < buffer.length; i++) { + var cp = buffer.charAt(i); + if ('\t' == cp || '\n' == cp || '\r' == cp) { + err('Invalid whitespace in authority.'); + continue; + } + // XXX check URL code points + if (':' == cp && null === urlState.password) { + urlState.password = ''; + continue; + } + var tempC = percentEscape(cp); + if (null !== urlState.password) urlState.password += tempC; + else urlState.username += tempC; + } + buffer = ''; + } else if (EOF == char || '/' == char || '\\' == char || '?' == char || '#' == char) { + cursor -= buffer.length; + buffer = ''; + state = HOST; + continue; + } else buffer += char; + break; + + case FILE_HOST: + if (EOF == char || '/' == char || '\\' == char || '?' == char || '#' == char) { + if ( + buffer.length == 2 && ALPHA.test(buffer.charAt(0)) && + (buffer.charAt(1) == ':' || buffer.charAt(1) == '|') + ) { + state = RELATIVE_PATH; + } else if (buffer.length == 0) { + state = RELATIVE_PATH_START; + } else { + urlState.host = IDNAToASCII(urlState, buffer); + buffer = ''; + state = RELATIVE_PATH_START; + } + continue; + } else if ('\t' == char || '\n' == char || '\r' == char) { + err('Invalid whitespace in file host.'); + } else { + buffer += char; + } break; + + case HOST: + case HOSTNAME: + if (':' == char && !seenBracket) { + // XXX host parsing + urlState.host = IDNAToASCII(urlState, buffer); + buffer = ''; + state = PORT; + if (HOSTNAME == stateOverride) break loop; + } else if (EOF == char || '/' == char || '\\' == char || '?' == char || '#' == char) { + urlState.host = IDNAToASCII(urlState, buffer); + buffer = ''; + state = RELATIVE_PATH_START; + if (stateOverride) break loop; + continue; + } else if ('\t' != char && '\n' != char && '\r' != char) { + if ('[' == char) seenBracket = true; + else if (']' == char) seenBracket = false; + buffer += char; + } else err('Invalid code point in host/hostname: ' + char); + break; + + case PORT: + if (/[0-9]/.test(char)) { + buffer += char; + } else if (EOF == char || '/' == char || '\\' == char || '?' == char || '#' == char || stateOverride) { + if ('' != buffer) { + var temp = parseInt(buffer, 10); + if (temp > 65535) { + err('Invalid port: ' + temp); + } else if (temp != relative[urlState.scheme]) { + urlState.port = temp + ''; + } + buffer = ''; + } + if (stateOverride) break loop; + state = RELATIVE_PATH_START; + continue; + } else if ('\t' == char || '\n' == char || '\r' == char) { + err('Invalid code point in port: ' + char); + } else { + invalid(urlState); + } break; + + case RELATIVE_PATH_START: + if ('\\' == char) err("'\\' not allowed in path."); + state = RELATIVE_PATH; + if ('/' != char && '\\' != char) continue; + break; + + case RELATIVE_PATH: + if (EOF == char || '/' == char || '\\' == char || (!stateOverride && ('?' == char || '#' == char))) { + if ('\\' == char) err('\\ not allowed in relative path.'); + var tmp = relativePathDotMapping[buffer.toLowerCase()]; + if (tmp) buffer = tmp; + if ('..' == buffer) { + urlState.path.pop(); + if ('/' != char && '\\' != char) { + urlState.path.push(''); + } + } else if ('.' == buffer && '/' != char && '\\' != char) { + urlState.path.push(''); + } else if ('.' != buffer) { + if ( + 'file' == urlState.scheme && urlState.path.length == 0 && + buffer.length == 2 && ALPHA.test(buffer.charAt(0)) && buffer.charAt(1) == '|' + ) buffer = buffer[0] + ':'; + urlState.path.push(buffer); + } + buffer = ''; + if ('?' == char) { + urlState.query = '?'; + state = QUERY; + } else if ('#' == char) { + urlState.fragment = '#'; + state = FRAGMENT; + } + } else if ('\t' != char && '\n' != char && '\r' != char) { + buffer += percentEscape(char); + } break; + + case QUERY: + if (!stateOverride && '#' == char) { + urlState.fragment = '#'; + state = FRAGMENT; + } else if (EOF != char && '\t' != char && '\n' != char && '\r' != char) { + urlState.query += percentEscapeQuery(char); + } break; + + case FRAGMENT: + if (EOF != char && '\t' != char && '\n' != char && '\r' != char) urlState.fragment += char; + break; + } + + cursor++; + } +}; + +var clear = function (state) { + state.scheme = ''; + state.schemeData = ''; + state.username = ''; + state.password = null; + state.host = ''; + state.port = ''; + state.path = []; + state.query = ''; + state.fragment = ''; + state.isInvalid = false; + state.isRelative = false; +}; + +// `URL` constructor +// https://url.spec.whatwg.org/#url-class +// Does not process domain names or IP addresses. +// Does not handle encoding for the query parameter. +var URL = function URL(url /* , base */) { + var that = anInstance(this, URL, 'URL'); + var base = arguments.length > 1 ? arguments[1] : undefined; + var urlString = String(url); + var state = setInternalState(that, { type: 'URL' }); + if (base !== undefined && !(base instanceof URL)) base = new URL(String(base)); + state.url = urlString; + clear(state); + parse(state, urlString.replace(trim, ''), null, base && getInternalURLState(base)); + var searchParams = state.searchParams = new URLSearchParams(state.query); + getInternalSearchParamsState(searchParams).updateURL = function () { + var query = String(searchParams); + state.query = query === '' ? '' : '?' + query; + }; + if (!DESCRIPTORS) { + that.href = getHref.call(that); + that.origin = getOrigin.call(that); + that.protocol = getProtocol.call(that); + that.username = getUsername.call(that); + that.password = getPassword.call(that); + that.host = getHost.call(that); + that.hostname = getHostname.call(that); + that.port = getPort.call(that); + that.pathname = getPathname.call(that); + that.search = getSearch.call(that); + that.searchParams = getSearchParams.call(that); + that.hash = getHash.call(that); + } +}; + +var URLPrototype = URL.prototype; + +var getHref = function () { + var that = this; + var state = getInternalURLState(that); + var authority = ''; + if (state.isInvalid) return state.url; + if ('' !== state.username || null !== state.password) { + authority = state.username + (null !== state.password ? ':' + state.password : '') + '@'; + } + return state.scheme + ':' + (state.isRelative ? '//' + authority + getHost.call(that) : '') + + getPathname.call(that) + state.query + state.fragment; +}; + +var getOrigin = function () { + var state = getInternalURLState(this); + if (state.isInvalid || !state.scheme) return ''; + switch (state.scheme) { + case 'data': + case 'file': + case 'javascript': + case 'mailto': + return 'null'; + } + var host = getHost.call(this); + if (!host) return ''; + return state.scheme + '://' + host; +}; + +var getProtocol = function () { + return getInternalURLState(this).scheme + ':'; +}; + +var getUsername = function () { + return getInternalURLState(this).username; +}; + +var getPassword = function () { + return getInternalURLState(this).password || ''; +}; + +var getHost = function () { + var state = getInternalURLState(this); + return state.isInvalid ? '' : state.port ? state.host + ':' + state.port : state.host; +}; + +var getHostname = function () { + return getInternalURLState(this).host; +}; + +var getPort = function () { + return getInternalURLState(this).port; +}; + +var getPathname = function () { + var state = getInternalURLState(this); + return state.isInvalid ? '' : state.isRelative ? '/' + state.path.join('/') : state.schemeData; +}; + +var getSearch = function () { + var state = getInternalURLState(this); + return state.isInvalid || state.query === '?' ? '' : state.query; +}; + +var getSearchParams = function () { + return getInternalURLState(this).searchParams; +}; + +var getHash = function () { + var state = getInternalURLState(this); + return state.isInvalid || !state.fragment || '#' == state.fragment ? '' : state.fragment; +}; + +var cannotHaveUsernamePasswordPort = function (state) { + return state.isInvalid || !state.isRelative; +}; + +var accessorDescriptor = function (getter, setter) { + return { get: getter, set: setter, configurable: true, enumerable: true }; +}; + +if (DESCRIPTORS) { + defineProperties(URLPrototype, { + // `URL.prototype.href` accessors pair + // https://url.spec.whatwg.org/#dom-url-href + href: accessorDescriptor(getHref, function (href) { + var state = getInternalURLState(this); + clear(state); + parse(state, href + ''); + getInternalSearchParamsState(state.searchParams).updateSearchParams(state.query); + }), + // `URL.prototype.origin` getter + // https://url.spec.whatwg.org/#dom-url-origin + origin: accessorDescriptor(getOrigin), + // `URL.prototype.protocol` accessors pair + // https://url.spec.whatwg.org/#dom-url-protocol + protocol: accessorDescriptor(getProtocol, function (protocol) { + var state = getInternalURLState(this); + if (state.isInvalid) return; + parse(state, protocol + ':', SCHEME_START); + }), + // `URL.prototype.username` accessors pair + // https://url.spec.whatwg.org/#dom-url-username + username: accessorDescriptor(getUsername, function (username) { + var state = getInternalURLState(this); + if (cannotHaveUsernamePasswordPort(state)) return; + var chars = String(username).split(''); + state.username = ''; + for (var i = 0; i < chars.length; i++) { + state.username += percentEscape(chars[i]); + } + }), + // `URL.prototype.password` accessors pair + // https://url.spec.whatwg.org/#dom-url-password + password: accessorDescriptor(getPassword, function (password) { + var state = getInternalURLState(this); + if (cannotHaveUsernamePasswordPort(state)) return; + var chars = String(password).split(''); + state.password = ''; + for (var i = 0; i < chars.length; i++) { + state.password += percentEscape(chars[i]); + } + }), + // `URL.prototype.host` accessors pair + // https://url.spec.whatwg.org/#dom-url-host + host: accessorDescriptor(getHost, function (host) { + var state = getInternalURLState(this); + host += ''; + if (state.isInvalid || !state.isRelative) return; + parse(state, host, HOST); + }), + // `URL.prototype.hostname` accessors pair + // https://url.spec.whatwg.org/#dom-url-hostname + hostname: accessorDescriptor(getHostname, function (hostname) { + var state = getInternalURLState(this); + if (state.isInvalid || !state.isRelative) return; + parse(state, hostname + '', HOSTNAME); + }), + // `URL.prototype.port` accessors pair + // https://url.spec.whatwg.org/#dom-url-port + port: accessorDescriptor(getPort, function (port) { + var state = getInternalURLState(this); + if (cannotHaveUsernamePasswordPort(state)) return; + parse(state, port + '', PORT); + }), + // `URL.prototype.pathname` accessors pair + // https://url.spec.whatwg.org/#dom-url-pathname + pathname: accessorDescriptor(getPathname, function (pathname) { + var state = getInternalURLState(this); + if (state.isInvalid || !state.isRelative) return; + state.path = []; + parse(state, pathname + '', RELATIVE_PATH_START); + }), + // `URL.prototype.search` accessors pair + // https://url.spec.whatwg.org/#dom-url-search + search: accessorDescriptor(getSearch, function (search) { + var state = getInternalURLState(this); + search += ''; + if (state.isInvalid || !state.isRelative) return; + if ('?' == search.charAt(0)) search = search.slice(1); + if (search === '') { + state.query = ''; + } else { + state.query = '?'; + parse(state, search, QUERY); + } + getInternalSearchParamsState(state.searchParams).updateSearchParams(state.query); + }), + // `URL.prototype.searchParams` getter + // https://url.spec.whatwg.org/#dom-url-searchparams + searchParams: accessorDescriptor(getSearchParams), + // `URL.prototype.hash` accessors pair + // https://url.spec.whatwg.org/#dom-url-hash + hash: accessorDescriptor(getHash, function (hash) { + var state = getInternalURLState(this); + hash += ''; + if (state.isInvalid) return; + if ('#' == hash.charAt(0)) hash = hash.slice(1); + if (hash === '') { + state.fragment = ''; + return; + } + state.fragment = '#'; + parse(state, hash, FRAGMENT); + }) + }); +} + +// `URL.prototype.toJSON` method +// https://url.spec.whatwg.org/#dom-url-tojson +redefine(URLPrototype, 'toJSON', function toJSON() { + return getHref.call(this); +}, { enumerable: true }); + +// `URL.prototype.toString` method +// https://url.spec.whatwg.org/#URL-stringification-behavior +redefine(URLPrototype, 'toString', function toString() { + return getHref.call(this); +}, { enumerable: true }); + +if (NativeURL) { + var nativeCreateObjectURL = NativeURL.createObjectURL; + var nativeRevokeObjectURL = NativeURL.revokeObjectURL; + // `URL.createObjectURL` method + // https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL + // eslint-disable-next-line no-unused-vars + if (nativeCreateObjectURL) redefine(URL, 'createObjectURL', function createObjectURL(blob) { + return nativeCreateObjectURL.apply(NativeURL, arguments); + }); + // `URL.revokeObjectURL` method + // https://developer.mozilla.org/en-US/docs/Web/API/URL/revokeObjectURL + // eslint-disable-next-line no-unused-vars + if (nativeRevokeObjectURL) redefine(URL, 'revokeObjectURL', function revokeObjectURL(url) { + return nativeRevokeObjectURL.apply(NativeURL, arguments); + }); +} + +require('../internals/set-to-string-tag')(URL, 'URL'); + +require('../internals/export')({ global: true, forced: !USE_NATIVE_URL }, { + URL: URL +}); diff --git a/packages/core-js/modules/web.url.to-json.js b/packages/core-js/modules/web.url.to-json.js new file mode 100644 index 000000000000..2c755469d2d8 --- /dev/null +++ b/packages/core-js/modules/web.url.to-json.js @@ -0,0 +1,8 @@ +'use strict'; +// `URL.prototype.toJSON` method +// https://url.spec.whatwg.org/#dom-url-tojson +require('../internals/export')({ target: 'URL', proto: true, enumerable: true }, { + toJSON: function toJSON() { + return URL.prototype.toString.call(this); + } +}); diff --git a/packages/core-js/proposals/url.js b/packages/core-js/proposals/url.js new file mode 100644 index 000000000000..8984b29f20b0 --- /dev/null +++ b/packages/core-js/proposals/url.js @@ -0,0 +1,4 @@ +require('../modules/web.url'); +require('../modules/web.url.to-json'); +require('../modules/web.url-search-params'); +require('../modules/web.url-search-params.sort'); diff --git a/packages/core-js/stage/0.js b/packages/core-js/stage/0.js index 90465e1b1f55..4f720f194a36 100644 --- a/packages/core-js/stage/0.js +++ b/packages/core-js/stage/0.js @@ -1,4 +1,5 @@ require('../proposals/efficient-64-bit-arithmetic'); require('../proposals/string-at'); +require('../proposals/url'); module.exports = require('./1'); diff --git a/packages/core-js/web/index.js b/packages/core-js/web/index.js index 3787e026e299..0ad796d0bff1 100644 --- a/packages/core-js/web/index.js +++ b/packages/core-js/web/index.js @@ -3,5 +3,9 @@ require('../modules/web.dom-collections.iterator'); require('../modules/web.immediate'); require('../modules/web.queue-microtask'); require('../modules/web.timers'); +require('../modules/web.url'); +require('../modules/web.url.to-json'); +require('../modules/web.url-search-params'); +require('../modules/web.url-search-params.sort'); module.exports = require('../internals/path'); diff --git a/packages/core-js/web/url-search-params.js b/packages/core-js/web/url-search-params.js new file mode 100644 index 000000000000..bb37b48dc8e7 --- /dev/null +++ b/packages/core-js/web/url-search-params.js @@ -0,0 +1,4 @@ +require('../modules/web.url-search-params'); +require('../modules/web.url-search-params.sort'); + +module.exports = require('../internals/path').URLSearchParams; diff --git a/packages/core-js/web/url.js b/packages/core-js/web/url.js new file mode 100644 index 000000000000..a55e42056950 --- /dev/null +++ b/packages/core-js/web/url.js @@ -0,0 +1,6 @@ +require('../modules/web.url'); +require('../modules/web.url.to-json'); +require('../modules/web.url-search-params'); +require('../modules/web.url-search-params.sort'); + +module.exports = require('../internals/path').URL; diff --git a/tests/commonjs.js b/tests/commonjs.js index ea1ac97c8ad9..90e8f6318293 100644 --- a/tests/commonjs.js +++ b/tests/commonjs.js @@ -347,6 +347,10 @@ for (const _PATH of ['../packages/core-js-pure', '../packages/core-js']) { ok(typeof load('features/queue-microtask') === 'function'); ok(typeof load('features/composite-key')({}, 1, {}) === 'object'); ok(typeof load('features/composite-symbol')({}, 1, {}) === 'symbol'); + ok(typeof load('features/url') === 'function'); + load('features/url/to-json'); + ok(typeof load('features/url-search-params') === 'function'); + load('features/url-search-params/sort'); ok(load('features/is-iterable')([])); ok(typeof load('features/get-iterator-method')([]) === 'function'); ok('next' in load('features/get-iterator')([])); @@ -590,6 +594,8 @@ for (const _PATH of ['../packages/core-js-pure', '../packages/core-js']) { ok('setImmediate' in load('web/immediate')); ok(typeof load('web/queue-microtask') === 'function'); load('web/dom-collections'); + ok(typeof load('web/url') === 'function'); + ok(typeof load('web/url-search-params') === 'function'); ok('setImmediate' in load('web')); load('proposals/array-flat-and-flat-map'); load('proposals/array-last'); @@ -615,6 +621,7 @@ for (const _PATH of ['../packages/core-js-pure', '../packages/core-js']) { load('proposals/string-trim-start-end'); load('proposals/symbol-description'); load('proposals/using-statement'); + load('proposals/url'); load('proposals'); ok(load('stage/4')); ok(load('stage/3')); diff --git a/tests/helpers/qunit-helpers.js b/tests/helpers/qunit-helpers.js index 09ee7da71fe5..15a1d44fd857 100644 --- a/tests/helpers/qunit-helpers.js +++ b/tests/helpers/qunit-helpers.js @@ -87,12 +87,21 @@ QUnit.assert.name = function (fn, name, message) { }); }; +QUnit.assert.enumerable = function (O, key, message) { + if (DESCRIPTORS) this.pushResult({ + result: propertyIsEnumerable.call(O, key), + actual: false, + expected: true, + message: message || `${ typeof key === 'symbol' ? 'method' : `'${ key }'` } is enumerable`, + }); +}; + QUnit.assert.nonEnumerable = function (O, key, message) { if (DESCRIPTORS) this.pushResult({ result: !propertyIsEnumerable.call(O, key), actual: false, expected: true, - message: message || `${ typeof key === 'symbol' ? 'method' : key } is non-enumerable`, + message: message || `${ typeof key === 'symbol' ? 'method' : `'${ key }'` } is non-enumerable`, }); }; diff --git a/tests/pure/index.js b/tests/pure/index.js index 88eba4f56352..51f2b80c98a1 100644 --- a/tests/pure/index.js +++ b/tests/pure/index.js @@ -214,6 +214,7 @@ import './web.dom-collections.iterator'; import './web.immediate'; import './web.queue-microtask'; import './web.timers'; +import './web.url-search-params'; QUnit.module('Helpers'); import './helpers.get-iterator-method'; diff --git a/tests/pure/web.url-search-params.js b/tests/pure/web.url-search-params.js new file mode 100644 index 000000000000..de58895731a0 --- /dev/null +++ b/tests/pure/web.url-search-params.js @@ -0,0 +1,529 @@ +import { createIterable } from '../helpers/helpers'; + +import { Symbol, URLSearchParams } from 'core-js-pure'; + +QUnit.test('URLSearchParams', assert => { + assert.isFunction(URLSearchParams); + assert.arity(URLSearchParams, 0); + assert.name(URLSearchParams, 'URLSearchParams'); + + assert.same(String(new URLSearchParams()), ''); + assert.same(String(new URLSearchParams('')), ''); + assert.same(String(new URLSearchParams('a=b')), 'a=b'); + assert.same(String(new URLSearchParams(new URLSearchParams('a=b'))), 'a=b'); + assert.same(String(new URLSearchParams([])), ''); + assert.same(String(new URLSearchParams([[1, 2], ['a', 'b']])), '1=2&a=b'); + assert.same(String(new URLSearchParams(createIterable([createIterable(['a', 'b']), createIterable(['c', 'd'])]))), 'a=b&c=d'); + assert.same(String(new URLSearchParams({})), ''); + assert.same(String(new URLSearchParams({ 1: 2, a: 'b' })), '1=2&a=b'); + + assert.same(String(new URLSearchParams('?a=b')), 'a=b', 'leading ? should be ignored'); + assert.same(String(new URLSearchParams('??a=b')), '%3Fa=b'); + assert.same(String(new URLSearchParams('?')), ''); + assert.same(String(new URLSearchParams('??')), '%3F='); + + assert.same(String(new URLSearchParams('a=b c')), 'a=b+c'); + assert.same(String(new URLSearchParams('a=b&b=c&a=d')), 'a=b&b=c&a=d'); + + assert.same(String(new URLSearchParams('a==')), 'a=%3D'); + assert.same(String(new URLSearchParams('a=b=')), 'a=b%3D'); + assert.same(String(new URLSearchParams('a=b=c')), 'a=b%3Dc'); + assert.same(String(new URLSearchParams('a==b')), 'a=%3Db'); + + let params = new URLSearchParams('a=b'); + assert.same(params.has('a'), true, 'search params object has name "a"'); + assert.same(params.has('b'), false, 'search params object has not got name "b"'); + + params = new URLSearchParams('a=b&c'); + assert.same(params.has('a'), true, 'search params object has name "a"'); + assert.same(params.has('c'), true, 'search params object has name "c"'); + + params = new URLSearchParams('&a&&& &&&&&a+b=& c&m%c3%b8%c3%b8'); + assert.same(params.has('a'), true, 'search params object has name "a"'); + assert.same(params.has('a b'), true, 'search params object has name "a b"'); + assert.same(params.has(' '), true, 'search params object has name " "'); + assert.same(params.has('c'), false, 'search params object did not have the name "c"'); + assert.same(params.has(' c'), true, 'search params object has name " c"'); + assert.same(params.has('møø'), true, 'search params object has name "møø"'); + + assert.throws(() => { + URLSearchParams(''); + }, 'throws w/o `new`'); + + assert.throws(() => { + new URLSearchParams([[1, 2, 3]]); + }, 'sequence elements must be pairs #1'); + + assert.throws(() => { + new URLSearchParams([createIterable([createIterable([1, 2, 3])])]); + }, 'sequence elements must be pairs #2'); +}); + +QUnit.test('URLSearchParams#append', assert => { + const { append } = URLSearchParams.prototype; + assert.isFunction(append); + assert.arity(append, 2); + assert.name(append, 'append'); + assert.enumerable(URLSearchParams.prototype, 'append'); + + assert.same(new URLSearchParams().append('a', 'b'), undefined, 'void'); + + let params = new URLSearchParams(); + params.append('a', 'b'); + assert.same(String(params), 'a=b'); + params.append('a', 'b'); + assert.same(String(params), 'a=b&a=b'); + params.append('a', 'c'); + assert.same(String(params), 'a=b&a=b&a=c'); + + params = new URLSearchParams(); + params.append('', ''); + assert.same(String(params), '='); + params.append('', ''); + assert.same(String(params), '=&='); + + params = new URLSearchParams(); + params.append(undefined, undefined); + assert.same(String(params), 'undefined=undefined'); + params.append(undefined, undefined); + assert.same(String(params), 'undefined=undefined&undefined=undefined'); + + params = new URLSearchParams(); + params.append(null, null); + assert.same(String(params), 'null=null'); + params.append(null, null); + assert.same(String(params), 'null=null&null=null'); + + params = new URLSearchParams(); + params.append('first', 1); + params.append('second', 2); + params.append('third', ''); + params.append('first', 10); + assert.ok(params.has('first'), 'search params object has name "first"'); + assert.same(params.get('first'), '1', 'search params object has name "first" with value "1"'); + assert.same(params.get('second'), '2', 'search params object has name "second" with value "2"'); + assert.same(params.get('third'), '', 'search params object has name "third" with value ""'); + params.append('first', 10); + assert.same(params.get('first'), '1', 'search params object has name "first" with value "1"'); + + assert.throws(() => { + return new URLSearchParams('').append(); + }, 'throws w/o arguments'); +}); + +QUnit.test('URLSearchParams#delete', assert => { + const $delete = URLSearchParams.prototype.delete; + assert.isFunction($delete); + assert.arity($delete, 1); + assert.enumerable(URLSearchParams.prototype, 'delete'); + + let params = new URLSearchParams('a=b&c=d'); + params.delete('a'); + assert.same(String(params), 'c=d'); + + params = new URLSearchParams('a=a&b=b&a=a&c=c'); + params.delete('a'); + assert.same(String(params), 'b=b&c=c'); + + params = new URLSearchParams('a=a&=&b=b&c=c'); + params.delete(''); + assert.same(String(params), 'a=a&b=b&c=c'); + + params = new URLSearchParams('a=a&null=null&b=b'); + params.delete(null); + assert.same(String(params), 'a=a&b=b'); + + params = new URLSearchParams('a=a&undefined=undefined&b=b'); + params.delete(undefined); + assert.same(String(params), 'a=a&b=b'); + + params = new URLSearchParams(); + params.append('first', 1); + assert.same(params.has('first'), true, 'search params object has name "first"'); + assert.same(params.get('first'), '1', 'search params object has name "first" with value "1"'); + params.delete('first'); + assert.same(params.has('first'), false, 'search params object has no "first" name'); + params.append('first', 1); + params.append('first', 10); + params.delete('first'); + assert.same(params.has('first'), false, 'search params object has no "first" name'); + + assert.throws(() => { + return new URLSearchParams('').delete(); + }, 'throws w/o arguments'); +}); + +QUnit.test('URLSearchParams#get', assert => { + const { get } = URLSearchParams.prototype; + assert.isFunction(get); + assert.arity(get, 1); + assert.name(get, 'get'); + assert.enumerable(URLSearchParams.prototype, 'get'); + + let params = new URLSearchParams('a=b&c=d'); + assert.same(params.get('a'), 'b'); + assert.same(params.get('c'), 'd'); + assert.same(params.get('e'), null); + + params = new URLSearchParams('a=b&c=d&a=e'); + assert.same(params.get('a'), 'b'); + + params = new URLSearchParams('=b&c=d'); + assert.same(params.get(''), 'b'); + + params = new URLSearchParams('a=&c=d&a=e'); + assert.same(params.get('a'), ''); + + params = new URLSearchParams('first=second&third&&'); + assert.same(params.has('first'), true, 'Search params object has name "first"'); + assert.same(params.get('first'), 'second', 'Search params object has name "first" with value "second"'); + assert.same(params.get('third'), '', 'Search params object has name "third" with the empty value.'); + assert.same(params.get('fourth'), null, 'Search params object has no "fourth" name and value.'); + + assert.same(new URLSearchParams('a=b c').get('a'), 'b c'); + assert.same(new URLSearchParams('a b=c').get('a b'), 'c'); + + assert.same(new URLSearchParams('a=b%20c').get('a'), 'b c', 'parse %20'); + assert.same(new URLSearchParams('a%20b=c').get('a b'), 'c', 'parse %20'); + + assert.same(new URLSearchParams('a=b\0c').get('a'), 'b\0c', 'parse \\0'); + assert.same(new URLSearchParams('a\0b=c').get('a\0b'), 'c', 'parse \\0'); + + assert.same(new URLSearchParams('a=b%2Bc').get('a'), 'b+c', 'parse %2B'); + assert.same(new URLSearchParams('a%2Bb=c').get('a+b'), 'c', 'parse %2B'); + + assert.same(new URLSearchParams('a=b%00c').get('a'), 'b\0c', 'parse %00'); + assert.same(new URLSearchParams('a%00b=c').get('a\0b'), 'c', 'parse %00'); + + assert.same(new URLSearchParams('a==').get('a'), '=', 'parse ='); + assert.same(new URLSearchParams('a=b=').get('a'), 'b=', 'parse ='); + assert.same(new URLSearchParams('a=b=c').get('a'), 'b=c', 'parse ='); + assert.same(new URLSearchParams('a==b').get('a'), '=b', 'parse ='); + + assert.same(new URLSearchParams('a=b\u2384').get('a'), 'b\u2384', 'parse \\u2384'); + assert.same(new URLSearchParams('a\u2384b=c').get('a\u2384b'), 'c', 'parse \\u2384'); + + assert.same(new URLSearchParams('a=b%e2%8e%84').get('a'), 'b\u2384', 'parse %e2%8e%84'); + assert.same(new URLSearchParams('a%e2%8e%84b=c').get('a\u2384b'), 'c', 'parse %e2%8e%84'); + + assert.same(new URLSearchParams('a=b\uD83D\uDCA9c').get('a'), 'b\uD83D\uDCA9c', 'parse \\uD83D\\uDCA9'); + assert.same(new URLSearchParams('a\uD83D\uDCA9b=c').get('a\uD83D\uDCA9b'), 'c', 'parse \\uD83D\\uDCA9'); + + assert.same(new URLSearchParams('a=b%f0%9f%92%a9c').get('a'), 'b\uD83D\uDCA9c', 'parse %f0%9f%92%a9'); + assert.same(new URLSearchParams('a%f0%9f%92%a9b=c').get('a\uD83D\uDCA9b'), 'c', 'parse %f0%9f%92%a9'); + + assert.same(new URLSearchParams('=').get(''), '', 'parse ='); + + assert.throws(() => { + return new URLSearchParams('').get(); + }, 'throws w/o arguments'); +}); + +QUnit.test('URLSearchParams#getAll', assert => { + const { getAll } = URLSearchParams.prototype; + assert.isFunction(getAll); + assert.arity(getAll, 1); + assert.name(getAll, 'getAll'); + assert.enumerable(URLSearchParams.prototype, 'getAll'); + + let params = new URLSearchParams('a=b&c=d'); + assert.arrayEqual(params.getAll('a'), ['b']); + assert.arrayEqual(params.getAll('c'), ['d']); + assert.arrayEqual(params.getAll('e'), []); + + params = new URLSearchParams('a=b&c=d&a=e'); + assert.arrayEqual(params.getAll('a'), ['b', 'e']); + + params = new URLSearchParams('=b&c=d'); + assert.arrayEqual(params.getAll(''), ['b']); + + params = new URLSearchParams('a=&c=d&a=e'); + assert.arrayEqual(params.getAll('a'), ['', 'e']); + + params = new URLSearchParams('a=1&a=2&a=3&a'); + assert.arrayEqual(params.getAll('a'), ['1', '2', '3', ''], 'search params object has expected name "a" values'); + params.set('a', 'one'); + assert.arrayEqual(params.getAll('a'), ['one'], 'search params object has expected name "a" values'); + + assert.throws(() => { + return new URLSearchParams('').getAll(); + }, 'throws w/o arguments'); +}); + +QUnit.test('URLSearchParams#has', assert => { + const { has } = URLSearchParams.prototype; + assert.isFunction(has); + assert.arity(has, 1); + assert.name(has, 'has'); + assert.enumerable(URLSearchParams.prototype, 'has'); + + let params = new URLSearchParams('a=b&c=d'); + assert.same(params.has('a'), true); + assert.same(params.has('c'), true); + assert.same(params.has('e'), false); + + params = new URLSearchParams('a=b&c=d&a=e'); + assert.same(params.has('a'), true); + + params = new URLSearchParams('=b&c=d'); + assert.same(params.has(''), true); + + params = new URLSearchParams('null=a'); + assert.same(params.has(null), true); + + params = new URLSearchParams('a=b&c=d&&'); + params.append('first', 1); + params.append('first', 2); + assert.same(params.has('a'), true, 'search params object has name "a"'); + assert.same(params.has('c'), true, 'search params object has name "c"'); + assert.same(params.has('first'), true, 'search params object has name "first"'); + assert.same(params.has('d'), false, 'search params object has no name "d"'); + params.delete('first'); + assert.same(params.has('first'), false, 'search params object has no name "first"'); + + assert.throws(() => { + return new URLSearchParams('').has(); + }, 'throws w/o arguments'); +}); + +QUnit.test('URLSearchParams#set', assert => { + const { set } = URLSearchParams.prototype; + assert.isFunction(set); + assert.arity(set, 2); + assert.name(set, 'set'); + assert.enumerable(URLSearchParams.prototype, 'set'); + + let params = new URLSearchParams('a=b&c=d'); + params.set('a', 'B'); + assert.same(String(params), 'a=B&c=d'); + + params = new URLSearchParams('a=b&c=d&a=e'); + params.set('a', 'B'); + assert.same(String(params), 'a=B&c=d'); + params.set('e', 'f'); + assert.same(String(params), 'a=B&c=d&e=f'); + + params = new URLSearchParams('a=1&a=2&a=3'); + assert.same(params.has('a'), true, 'search params object has name "a"'); + assert.same(params.get('a'), '1', 'search params object has name "a" with value "1"'); + params.set('first', 4); + assert.same(params.has('a'), true, 'search params object has name "a"'); + assert.same(params.get('a'), '1', 'search params object has name "a" with value "1"'); + assert.same(String(params), 'a=1&a=2&a=3&first=4'); + params.set('a', 4); + assert.same(params.has('a'), true, 'search params object has name "a"'); + assert.same(params.get('a'), '4', 'search params object has name "a" with value "4"'); + assert.same(String(params), 'a=4&first=4'); + + assert.throws(() => { + return new URLSearchParams('').set(); + }, 'throws w/o arguments'); +}); + +QUnit.test('URLSearchParams#sort', assert => { + const { sort } = URLSearchParams.prototype; + assert.isFunction(sort); + assert.arity(sort, 0); + assert.name(sort, 'sort'); + assert.enumerable(URLSearchParams.prototype, 'sort'); + + const params = new URLSearchParams('a=1&b=4&a=3&b=2'); + params.sort(); + assert.same(String(params), 'a=1&a=3&b=4&b=2'); + params.delete('a'); + params.append('a', '0'); + params.append('b', '0'); + params.sort(); + assert.same(String(params), 'a=0&b=4&b=2&b=0'); +}); + +QUnit.test('URLSearchParams#toString', assert => { + const { toString } = URLSearchParams.prototype; + assert.isFunction(toString); + assert.arity(toString, 0); + assert.name(toString, 'toString'); + + let params = new URLSearchParams(); + params.append('a', 'b c'); + assert.same(String(params), 'a=b+c'); + params.delete('a'); + params.append('a b', 'c'); + assert.same(String(params), 'a+b=c'); + + params = new URLSearchParams(); + params.append('a', ''); + assert.same(String(params), 'a='); + params.append('a', ''); + assert.same(String(params), 'a=&a='); + params.append('', 'b'); + assert.same(String(params), 'a=&a=&=b'); + params.append('', ''); + assert.same(String(params), 'a=&a=&=b&='); + params.append('', ''); + assert.same(String(params), 'a=&a=&=b&=&='); + + params = new URLSearchParams(); + params.append('', 'b'); + assert.same(String(params), '=b'); + params.append('', 'b'); + assert.same(String(params), '=b&=b'); + + params = new URLSearchParams(); + params.append('', ''); + assert.same(String(params), '='); + params.append('', ''); + assert.same(String(params), '=&='); + + params = new URLSearchParams(); + params.append('a', 'b+c'); + assert.same(String(params), 'a=b%2Bc'); + params.delete('a'); + params.append('a+b', 'c'); + assert.same(String(params), 'a%2Bb=c'); + + params = new URLSearchParams(); + params.append('=', 'a'); + assert.same(String(params), '%3D=a'); + params.append('b', '='); + assert.same(String(params), '%3D=a&b=%3D'); + + params = new URLSearchParams(); + params.append('&', 'a'); + assert.same(String(params), '%26=a'); + params.append('b', '&'); + assert.same(String(params), '%26=a&b=%26'); + + params = new URLSearchParams(); + params.append('a', '\r'); + assert.same(String(params), 'a=%0D'); + + params = new URLSearchParams(); + params.append('a', '\n'); + assert.same(String(params), 'a=%0A'); + + params = new URLSearchParams(); + params.append('a', '\r\n'); + assert.same(String(params), 'a=%0D%0A'); + + params = new URLSearchParams(); + params.append('a', 'b%c'); + assert.same(String(params), 'a=b%25c'); + params.delete('a'); + params.append('a%b', 'c'); + assert.same(String(params), 'a%25b=c'); + + params = new URLSearchParams(); + params.append('a', 'b\0c'); + assert.same(String(params), 'a=b%00c'); + params.delete('a'); + params.append('a\0b', 'c'); + assert.same(String(params), 'a%00b=c'); + + params = new URLSearchParams(); + params.append('a', 'b\uD83D\uDCA9c'); + assert.same(String(params), 'a=b%F0%9F%92%A9c'); + params.delete('a'); + params.append('a\uD83D\uDCA9b', 'c'); + assert.same(String(params), 'a%F0%9F%92%A9b=c'); + + params = new URLSearchParams('a=b&c=d&&e&&'); + assert.same(String(params), 'a=b&c=d&e='); + params = new URLSearchParams('a = b &a=b&c=d%20'); + assert.same(String(params), 'a+=+b+&a=b&c=d+'); + params = new URLSearchParams('a=&a=b'); + assert.same(String(params), 'a=&a=b'); +}); + +QUnit.test('URLSearchParams#forEach', assert => { + const { forEach } = URLSearchParams.prototype; + assert.isFunction(forEach); + assert.arity(forEach, 1); + assert.name(forEach, 'forEach'); + assert.enumerable(URLSearchParams.prototype, 'forEach'); + + const expectedValues = { a: '1', b: '2', c: '3' }; + const params = new URLSearchParams('a=1&b=2&c=3'); + let result = ''; + params.forEach((value, key, that) => { + assert.same(params.get(key), expectedValues[key]); + assert.same(value, expectedValues[key]); + assert.same(that, params); + result += key; + }); + assert.same(result, 'abc'); +}); + +QUnit.test('URLSearchParams#entries', assert => { + const { entries } = URLSearchParams.prototype; + assert.isFunction(entries); + assert.arity(entries, 0); + assert.name(entries, 'entries'); + assert.enumerable(URLSearchParams.prototype, 'entries'); + + const expectedValues = { a: '1', b: '2', c: '3' }; + const params = new URLSearchParams('a=1&b=2&c=3'); + const iterator = params.entries(); + let result = ''; + let entry; + while (!(entry = iterator.next()).done) { + const [key, value] = entry.value; + assert.same(params.get(key), expectedValues[key]); + assert.same(value, expectedValues[key]); + result += key; + } + assert.same(result, 'abc'); +}); + +QUnit.test('URLSearchParams#keys', assert => { + const { keys } = URLSearchParams.prototype; + assert.isFunction(keys); + assert.arity(keys, 0); + assert.name(keys, 'keys'); + assert.enumerable(URLSearchParams.prototype, 'keys'); + + const iterator = new URLSearchParams('a=1&b=2&c=3').keys(); + let result = ''; + let entry; + while (!(entry = iterator.next()).done) { + result += entry.value; + } + assert.same(result, 'abc'); +}); + +QUnit.test('URLSearchParams#values', assert => { + const { values } = URLSearchParams.prototype; + assert.isFunction(values); + assert.arity(values, 0); + assert.name(values, 'values'); + assert.enumerable(URLSearchParams.prototype, 'values'); + + const iterator = new URLSearchParams('a=1&b=2&c=3').values(); + let result = ''; + let entry; + while (!(entry = iterator.next()).done) { + result += entry.value; + } + assert.same(result, '123'); +}); + +QUnit.test('URLSearchParams#@@iterator', assert => { + const entries = URLSearchParams.prototype[Symbol.iterator]; + assert.isFunction(entries); + assert.arity(entries, 0); + assert.name(entries, 'entries'); + + assert.same(entries, URLSearchParams.prototype.entries); + + const expectedValues = { a: '1', b: '2', c: '3' }; + const params = new URLSearchParams('a=1&b=2&c=3'); + const iterator = params[Symbol.iterator](); + let result = ''; + let entry; + while (!(entry = iterator.next()).done) { + const [key, value] = entry.value; + assert.same(params.get(key), expectedValues[key]); + assert.same(value, expectedValues[key]); + result += key; + } + assert.same(result, 'abc'); +}); diff --git a/tests/tests/index.js b/tests/tests/index.js index 08c438b0aeea..809284b89312 100644 --- a/tests/tests/index.js +++ b/tests/tests/index.js @@ -267,3 +267,5 @@ import './web.dom-collections.iterator'; import './web.immediate'; import './web.queue-microtask'; import './web.timers'; +import './web.url'; +import './web.url-search-params'; diff --git a/tests/tests/web.url-search-params.js b/tests/tests/web.url-search-params.js new file mode 100644 index 000000000000..9d79ad7a8fe0 --- /dev/null +++ b/tests/tests/web.url-search-params.js @@ -0,0 +1,546 @@ +import { createIterable } from '../helpers/helpers'; + +QUnit.test('URLSearchParams', assert => { + assert.isFunction(URLSearchParams); + assert.arity(URLSearchParams, 0); + assert.name(URLSearchParams, 'URLSearchParams'); + assert.looksNative(URLSearchParams); + + assert.same(String(new URLSearchParams()), ''); + assert.same(String(new URLSearchParams('')), ''); + assert.same(String(new URLSearchParams('a=b')), 'a=b'); + assert.same(String(new URLSearchParams(new URLSearchParams('a=b'))), 'a=b'); + assert.same(String(new URLSearchParams([])), ''); + assert.same(String(new URLSearchParams([[1, 2], ['a', 'b']])), '1=2&a=b'); + assert.same(String(new URLSearchParams(createIterable([createIterable(['a', 'b']), createIterable(['c', 'd'])]))), 'a=b&c=d'); + assert.same(String(new URLSearchParams({})), ''); + assert.same(String(new URLSearchParams({ 1: 2, a: 'b' })), '1=2&a=b'); + + assert.same(String(new URLSearchParams('?a=b')), 'a=b', 'leading ? should be ignored'); + assert.same(String(new URLSearchParams('??a=b')), '%3Fa=b'); + assert.same(String(new URLSearchParams('?')), ''); + assert.same(String(new URLSearchParams('??')), '%3F='); + + assert.same(String(new URLSearchParams('a=b c')), 'a=b+c'); + assert.same(String(new URLSearchParams('a=b&b=c&a=d')), 'a=b&b=c&a=d'); + + assert.same(String(new URLSearchParams('a==')), 'a=%3D'); + assert.same(String(new URLSearchParams('a=b=')), 'a=b%3D'); + assert.same(String(new URLSearchParams('a=b=c')), 'a=b%3Dc'); + assert.same(String(new URLSearchParams('a==b')), 'a=%3Db'); + + let params = new URLSearchParams('a=b'); + assert.same(params.has('a'), true, 'search params object has name "a"'); + assert.same(params.has('b'), false, 'search params object has not got name "b"'); + + params = new URLSearchParams('a=b&c'); + assert.same(params.has('a'), true, 'search params object has name "a"'); + assert.same(params.has('c'), true, 'search params object has name "c"'); + + params = new URLSearchParams('&a&&& &&&&&a+b=& c&m%c3%b8%c3%b8'); + assert.same(params.has('a'), true, 'search params object has name "a"'); + assert.same(params.has('a b'), true, 'search params object has name "a b"'); + assert.same(params.has(' '), true, 'search params object has name " "'); + assert.same(params.has('c'), false, 'search params object did not have the name "c"'); + assert.same(params.has(' c'), true, 'search params object has name " c"'); + assert.same(params.has('møø'), true, 'search params object has name "møø"'); + + assert.throws(() => { + URLSearchParams(''); + }, 'throws w/o `new`'); + + assert.throws(() => { + new URLSearchParams([[1, 2, 3]]); + }, 'sequence elements must be pairs #1'); + + assert.throws(() => { + new URLSearchParams([createIterable([createIterable([1, 2, 3])])]); + }, 'sequence elements must be pairs #2'); +}); + +QUnit.test('URLSearchParams#append', assert => { + const { append } = URLSearchParams.prototype; + assert.isFunction(append); + assert.arity(append, 2); + assert.name(append, 'append'); + assert.enumerable(URLSearchParams.prototype, 'append'); + assert.looksNative(append); + + assert.same(new URLSearchParams().append('a', 'b'), undefined, 'void'); + + let params = new URLSearchParams(); + params.append('a', 'b'); + assert.same(String(params), 'a=b'); + params.append('a', 'b'); + assert.same(String(params), 'a=b&a=b'); + params.append('a', 'c'); + assert.same(String(params), 'a=b&a=b&a=c'); + + params = new URLSearchParams(); + params.append('', ''); + assert.same(String(params), '='); + params.append('', ''); + assert.same(String(params), '=&='); + + params = new URLSearchParams(); + params.append(undefined, undefined); + assert.same(String(params), 'undefined=undefined'); + params.append(undefined, undefined); + assert.same(String(params), 'undefined=undefined&undefined=undefined'); + + params = new URLSearchParams(); + params.append(null, null); + assert.same(String(params), 'null=null'); + params.append(null, null); + assert.same(String(params), 'null=null&null=null'); + + params = new URLSearchParams(); + params.append('first', 1); + params.append('second', 2); + params.append('third', ''); + params.append('first', 10); + assert.ok(params.has('first'), 'search params object has name "first"'); + assert.same(params.get('first'), '1', 'search params object has name "first" with value "1"'); + assert.same(params.get('second'), '2', 'search params object has name "second" with value "2"'); + assert.same(params.get('third'), '', 'search params object has name "third" with value ""'); + params.append('first', 10); + assert.same(params.get('first'), '1', 'search params object has name "first" with value "1"'); + + assert.throws(() => { + return new URLSearchParams('').append(); + }, 'throws w/o arguments'); +}); + +QUnit.test('URLSearchParams#delete', assert => { + const $delete = URLSearchParams.prototype.delete; + assert.isFunction($delete); + assert.arity($delete, 1); + assert.enumerable(URLSearchParams.prototype, 'delete'); + assert.looksNative($delete); + + let params = new URLSearchParams('a=b&c=d'); + params.delete('a'); + assert.same(String(params), 'c=d'); + + params = new URLSearchParams('a=a&b=b&a=a&c=c'); + params.delete('a'); + assert.same(String(params), 'b=b&c=c'); + + params = new URLSearchParams('a=a&=&b=b&c=c'); + params.delete(''); + assert.same(String(params), 'a=a&b=b&c=c'); + + params = new URLSearchParams('a=a&null=null&b=b'); + params.delete(null); + assert.same(String(params), 'a=a&b=b'); + + params = new URLSearchParams('a=a&undefined=undefined&b=b'); + params.delete(undefined); + assert.same(String(params), 'a=a&b=b'); + + params = new URLSearchParams(); + params.append('first', 1); + assert.same(params.has('first'), true, 'search params object has name "first"'); + assert.same(params.get('first'), '1', 'search params object has name "first" with value "1"'); + params.delete('first'); + assert.same(params.has('first'), false, 'search params object has no "first" name'); + params.append('first', 1); + params.append('first', 10); + params.delete('first'); + assert.same(params.has('first'), false, 'search params object has no "first" name'); + + assert.throws(() => { + return new URLSearchParams('').delete(); + }, 'throws w/o arguments'); +}); + +QUnit.test('URLSearchParams#get', assert => { + const { get } = URLSearchParams.prototype; + assert.isFunction(get); + assert.arity(get, 1); + assert.name(get, 'get'); + assert.enumerable(URLSearchParams.prototype, 'get'); + assert.looksNative(get); + + let params = new URLSearchParams('a=b&c=d'); + assert.same(params.get('a'), 'b'); + assert.same(params.get('c'), 'd'); + assert.same(params.get('e'), null); + + params = new URLSearchParams('a=b&c=d&a=e'); + assert.same(params.get('a'), 'b'); + + params = new URLSearchParams('=b&c=d'); + assert.same(params.get(''), 'b'); + + params = new URLSearchParams('a=&c=d&a=e'); + assert.same(params.get('a'), ''); + + params = new URLSearchParams('first=second&third&&'); + assert.same(params.has('first'), true, 'Search params object has name "first"'); + assert.same(params.get('first'), 'second', 'Search params object has name "first" with value "second"'); + assert.same(params.get('third'), '', 'Search params object has name "third" with the empty value.'); + assert.same(params.get('fourth'), null, 'Search params object has no "fourth" name and value.'); + + assert.same(new URLSearchParams('a=b c').get('a'), 'b c'); + assert.same(new URLSearchParams('a b=c').get('a b'), 'c'); + + assert.same(new URLSearchParams('a=b%20c').get('a'), 'b c', 'parse %20'); + assert.same(new URLSearchParams('a%20b=c').get('a b'), 'c', 'parse %20'); + + assert.same(new URLSearchParams('a=b\0c').get('a'), 'b\0c', 'parse \\0'); + assert.same(new URLSearchParams('a\0b=c').get('a\0b'), 'c', 'parse \\0'); + + assert.same(new URLSearchParams('a=b%2Bc').get('a'), 'b+c', 'parse %2B'); + assert.same(new URLSearchParams('a%2Bb=c').get('a+b'), 'c', 'parse %2B'); + + assert.same(new URLSearchParams('a=b%00c').get('a'), 'b\0c', 'parse %00'); + assert.same(new URLSearchParams('a%00b=c').get('a\0b'), 'c', 'parse %00'); + + assert.same(new URLSearchParams('a==').get('a'), '=', 'parse ='); + assert.same(new URLSearchParams('a=b=').get('a'), 'b=', 'parse ='); + assert.same(new URLSearchParams('a=b=c').get('a'), 'b=c', 'parse ='); + assert.same(new URLSearchParams('a==b').get('a'), '=b', 'parse ='); + + assert.same(new URLSearchParams('a=b\u2384').get('a'), 'b\u2384', 'parse \\u2384'); + assert.same(new URLSearchParams('a\u2384b=c').get('a\u2384b'), 'c', 'parse \\u2384'); + + assert.same(new URLSearchParams('a=b%e2%8e%84').get('a'), 'b\u2384', 'parse %e2%8e%84'); + assert.same(new URLSearchParams('a%e2%8e%84b=c').get('a\u2384b'), 'c', 'parse %e2%8e%84'); + + assert.same(new URLSearchParams('a=b\uD83D\uDCA9c').get('a'), 'b\uD83D\uDCA9c', 'parse \\uD83D\\uDCA9'); + assert.same(new URLSearchParams('a\uD83D\uDCA9b=c').get('a\uD83D\uDCA9b'), 'c', 'parse \\uD83D\\uDCA9'); + + assert.same(new URLSearchParams('a=b%f0%9f%92%a9c').get('a'), 'b\uD83D\uDCA9c', 'parse %f0%9f%92%a9'); + assert.same(new URLSearchParams('a%f0%9f%92%a9b=c').get('a\uD83D\uDCA9b'), 'c', 'parse %f0%9f%92%a9'); + + assert.same(new URLSearchParams('=').get(''), '', 'parse ='); + + assert.throws(() => { + return new URLSearchParams('').get(); + }, 'throws w/o arguments'); +}); + +QUnit.test('URLSearchParams#getAll', assert => { + const { getAll } = URLSearchParams.prototype; + assert.isFunction(getAll); + assert.arity(getAll, 1); + assert.name(getAll, 'getAll'); + assert.enumerable(URLSearchParams.prototype, 'getAll'); + assert.looksNative(getAll); + + let params = new URLSearchParams('a=b&c=d'); + assert.arrayEqual(params.getAll('a'), ['b']); + assert.arrayEqual(params.getAll('c'), ['d']); + assert.arrayEqual(params.getAll('e'), []); + + params = new URLSearchParams('a=b&c=d&a=e'); + assert.arrayEqual(params.getAll('a'), ['b', 'e']); + + params = new URLSearchParams('=b&c=d'); + assert.arrayEqual(params.getAll(''), ['b']); + + params = new URLSearchParams('a=&c=d&a=e'); + assert.arrayEqual(params.getAll('a'), ['', 'e']); + + params = new URLSearchParams('a=1&a=2&a=3&a'); + assert.arrayEqual(params.getAll('a'), ['1', '2', '3', ''], 'search params object has expected name "a" values'); + params.set('a', 'one'); + assert.arrayEqual(params.getAll('a'), ['one'], 'search params object has expected name "a" values'); + + assert.throws(() => { + return new URLSearchParams('').getAll(); + }, 'throws w/o arguments'); +}); + +QUnit.test('URLSearchParams#has', assert => { + const { has } = URLSearchParams.prototype; + assert.isFunction(has); + assert.arity(has, 1); + assert.name(has, 'has'); + assert.enumerable(URLSearchParams.prototype, 'has'); + assert.looksNative(has); + + let params = new URLSearchParams('a=b&c=d'); + assert.same(params.has('a'), true); + assert.same(params.has('c'), true); + assert.same(params.has('e'), false); + + params = new URLSearchParams('a=b&c=d&a=e'); + assert.same(params.has('a'), true); + + params = new URLSearchParams('=b&c=d'); + assert.same(params.has(''), true); + + params = new URLSearchParams('null=a'); + assert.same(params.has(null), true); + + params = new URLSearchParams('a=b&c=d&&'); + params.append('first', 1); + params.append('first', 2); + assert.same(params.has('a'), true, 'search params object has name "a"'); + assert.same(params.has('c'), true, 'search params object has name "c"'); + assert.same(params.has('first'), true, 'search params object has name "first"'); + assert.same(params.has('d'), false, 'search params object has no name "d"'); + params.delete('first'); + assert.same(params.has('first'), false, 'search params object has no name "first"'); + + assert.throws(() => { + return new URLSearchParams('').has(); + }, 'throws w/o arguments'); +}); + +QUnit.test('URLSearchParams#set', assert => { + const { set } = URLSearchParams.prototype; + assert.isFunction(set); + assert.arity(set, 2); + assert.name(set, 'set'); + assert.enumerable(URLSearchParams.prototype, 'set'); + assert.looksNative(set); + + let params = new URLSearchParams('a=b&c=d'); + params.set('a', 'B'); + assert.same(String(params), 'a=B&c=d'); + + params = new URLSearchParams('a=b&c=d&a=e'); + params.set('a', 'B'); + assert.same(String(params), 'a=B&c=d'); + params.set('e', 'f'); + assert.same(String(params), 'a=B&c=d&e=f'); + + params = new URLSearchParams('a=1&a=2&a=3'); + assert.same(params.has('a'), true, 'search params object has name "a"'); + assert.same(params.get('a'), '1', 'search params object has name "a" with value "1"'); + params.set('first', 4); + assert.same(params.has('a'), true, 'search params object has name "a"'); + assert.same(params.get('a'), '1', 'search params object has name "a" with value "1"'); + assert.same(String(params), 'a=1&a=2&a=3&first=4'); + params.set('a', 4); + assert.same(params.has('a'), true, 'search params object has name "a"'); + assert.same(params.get('a'), '4', 'search params object has name "a" with value "4"'); + assert.same(String(params), 'a=4&first=4'); + + assert.throws(() => { + return new URLSearchParams('').set(); + }, 'throws w/o arguments'); +}); + +QUnit.test('URLSearchParams#sort', assert => { + const { sort } = URLSearchParams.prototype; + assert.isFunction(sort); + assert.arity(sort, 0); + assert.name(sort, 'sort'); + assert.enumerable(URLSearchParams.prototype, 'sort'); + assert.looksNative(sort); + + const params = new URLSearchParams('a=1&b=4&a=3&b=2'); + params.sort(); + assert.same(String(params), 'a=1&a=3&b=4&b=2'); + params.delete('a'); + params.append('a', '0'); + params.append('b', '0'); + params.sort(); + assert.same(String(params), 'a=0&b=4&b=2&b=0'); +}); + +QUnit.test('URLSearchParams#toString', assert => { + const { toString } = URLSearchParams.prototype; + assert.isFunction(toString); + assert.arity(toString, 0); + assert.name(toString, 'toString'); + assert.looksNative(toString); + + let params = new URLSearchParams(); + params.append('a', 'b c'); + assert.same(String(params), 'a=b+c'); + params.delete('a'); + params.append('a b', 'c'); + assert.same(String(params), 'a+b=c'); + + params = new URLSearchParams(); + params.append('a', ''); + assert.same(String(params), 'a='); + params.append('a', ''); + assert.same(String(params), 'a=&a='); + params.append('', 'b'); + assert.same(String(params), 'a=&a=&=b'); + params.append('', ''); + assert.same(String(params), 'a=&a=&=b&='); + params.append('', ''); + assert.same(String(params), 'a=&a=&=b&=&='); + + params = new URLSearchParams(); + params.append('', 'b'); + assert.same(String(params), '=b'); + params.append('', 'b'); + assert.same(String(params), '=b&=b'); + + params = new URLSearchParams(); + params.append('', ''); + assert.same(String(params), '='); + params.append('', ''); + assert.same(String(params), '=&='); + + params = new URLSearchParams(); + params.append('a', 'b+c'); + assert.same(String(params), 'a=b%2Bc'); + params.delete('a'); + params.append('a+b', 'c'); + assert.same(String(params), 'a%2Bb=c'); + + params = new URLSearchParams(); + params.append('=', 'a'); + assert.same(String(params), '%3D=a'); + params.append('b', '='); + assert.same(String(params), '%3D=a&b=%3D'); + + params = new URLSearchParams(); + params.append('&', 'a'); + assert.same(String(params), '%26=a'); + params.append('b', '&'); + assert.same(String(params), '%26=a&b=%26'); + + params = new URLSearchParams(); + params.append('a', '\r'); + assert.same(String(params), 'a=%0D'); + + params = new URLSearchParams(); + params.append('a', '\n'); + assert.same(String(params), 'a=%0A'); + + params = new URLSearchParams(); + params.append('a', '\r\n'); + assert.same(String(params), 'a=%0D%0A'); + + params = new URLSearchParams(); + params.append('a', 'b%c'); + assert.same(String(params), 'a=b%25c'); + params.delete('a'); + params.append('a%b', 'c'); + assert.same(String(params), 'a%25b=c'); + + params = new URLSearchParams(); + params.append('a', 'b\0c'); + assert.same(String(params), 'a=b%00c'); + params.delete('a'); + params.append('a\0b', 'c'); + assert.same(String(params), 'a%00b=c'); + + params = new URLSearchParams(); + params.append('a', 'b\uD83D\uDCA9c'); + assert.same(String(params), 'a=b%F0%9F%92%A9c'); + params.delete('a'); + params.append('a\uD83D\uDCA9b', 'c'); + assert.same(String(params), 'a%F0%9F%92%A9b=c'); + + params = new URLSearchParams('a=b&c=d&&e&&'); + assert.same(String(params), 'a=b&c=d&e='); + params = new URLSearchParams('a = b &a=b&c=d%20'); + assert.same(String(params), 'a+=+b+&a=b&c=d+'); + params = new URLSearchParams('a=&a=b'); + assert.same(String(params), 'a=&a=b'); +}); + +QUnit.test('URLSearchParams#forEach', assert => { + const { forEach } = URLSearchParams.prototype; + assert.isFunction(forEach); + assert.arity(forEach, 1); + assert.name(forEach, 'forEach'); + assert.enumerable(URLSearchParams.prototype, 'forEach'); + assert.looksNative(forEach); + + const expectedValues = { a: '1', b: '2', c: '3' }; + const params = new URLSearchParams('a=1&b=2&c=3'); + let result = ''; + params.forEach((value, key, that) => { + assert.same(params.get(key), expectedValues[key]); + assert.same(value, expectedValues[key]); + assert.same(that, params); + result += key; + }); + assert.same(result, 'abc'); +}); + +QUnit.test('URLSearchParams#entries', assert => { + const { entries } = URLSearchParams.prototype; + assert.isFunction(entries); + assert.arity(entries, 0); + assert.name(entries, 'entries'); + assert.enumerable(URLSearchParams.prototype, 'entries'); + assert.looksNative(entries); + + const expectedValues = { a: '1', b: '2', c: '3' }; + const params = new URLSearchParams('a=1&b=2&c=3'); + const iterator = params.entries(); + let result = ''; + let entry; + while (!(entry = iterator.next()).done) { + const [key, value] = entry.value; + assert.same(params.get(key), expectedValues[key]); + assert.same(value, expectedValues[key]); + result += key; + } + assert.same(result, 'abc'); +}); + +QUnit.test('URLSearchParams#keys', assert => { + const { keys } = URLSearchParams.prototype; + assert.isFunction(keys); + assert.arity(keys, 0); + assert.name(keys, 'keys'); + assert.enumerable(URLSearchParams.prototype, 'keys'); + assert.looksNative(keys); + + const iterator = new URLSearchParams('a=1&b=2&c=3').keys(); + let result = ''; + let entry; + while (!(entry = iterator.next()).done) { + result += entry.value; + } + assert.same(result, 'abc'); +}); + +QUnit.test('URLSearchParams#values', assert => { + const { values } = URLSearchParams.prototype; + assert.isFunction(values); + assert.arity(values, 0); + assert.name(values, 'values'); + assert.enumerable(URLSearchParams.prototype, 'values'); + assert.looksNative(values); + + const iterator = new URLSearchParams('a=1&b=2&c=3').values(); + let result = ''; + let entry; + while (!(entry = iterator.next()).done) { + result += entry.value; + } + assert.same(result, '123'); +}); + +QUnit.test('URLSearchParams#@@iterator', assert => { + const entries = URLSearchParams.prototype[Symbol.iterator]; + assert.isFunction(entries); + assert.arity(entries, 0); + assert.name(entries, 'entries'); + assert.looksNative(entries); + + assert.same(entries, URLSearchParams.prototype.entries); + + const expectedValues = { a: '1', b: '2', c: '3' }; + const params = new URLSearchParams('a=1&b=2&c=3'); + const iterator = params[Symbol.iterator](); + let result = ''; + let entry; + while (!(entry = iterator.next()).done) { + const [key, value] = entry.value; + assert.same(params.get(key), expectedValues[key]); + assert.same(value, expectedValues[key]); + result += key; + } + assert.same(result, 'abc'); +}); + +QUnit.test('URLSearchParams#@@toStringTag', assert => { + const params = new URLSearchParams('a=b'); + assert.same(({}).toString.call(params), '[object URLSearchParams]'); +}); diff --git a/tests/tests/web.url.js b/tests/tests/web.url.js new file mode 100644 index 000000000000..878156f665ca --- /dev/null +++ b/tests/tests/web.url.js @@ -0,0 +1,389 @@ +/* eslint-disable prefer-template */ +import { DESCRIPTORS } from '../helpers/constants'; + +const { hasOwnProperty } = Object.prototype; + +QUnit.test('URL constructor', assert => { + assert.isFunction(URL); + assert.arity(URL, 1); + assert.name(URL, 'URL'); + assert.looksNative(URL); + + assert.same(new URL('http://www.domain.com/a/b').toString(), 'http://www.domain.com/a/b'); + assert.same(new URL('/c/d', 'http://www.domain.com/a/b').toString(), 'http://www.domain.com/c/d'); + assert.same(new URL('b/c', 'http://www.domain.com/a/b').toString(), 'http://www.domain.com/a/b/c'); + assert.same(new URL('b/c', new URL('http://www.domain.com/a/b')).toString(), 'http://www.domain.com/a/b/c'); + assert.same(new URL({ toString: () => 'https://example.org/' }).toString(), 'https://example.org/'); + + // assert.same(new URL('https://測試').toString(), 'https://xn--g6w251d/'); + assert.same(new URL('http://Example.com/', 'https://example.org/').toString(), 'http://example.com/'); + assert.same(new URL('https://Example.com/', 'https://example.org/').toString(), 'https://example.com/'); + assert.same(new URL('foo://Example.com/', 'https://example.org/').toString(), 'foo://Example.com/'); + assert.same(new URL('http:Example.com/', 'https://example.org/').toString(), 'http://example.com/'); + assert.same(new URL('https:Example.com/', 'https://example.org/').toString(), 'https://example.org/Example.com/'); + assert.same(new URL('foo:Example.com/', 'https://example.org/').toString(), 'foo:Example.com/'); +}); + +QUnit.test('URL#href', assert => { + const url = new URL('http://zloirock.ru/'); + + if (DESCRIPTORS) { + assert.ok(!hasOwnProperty.call(url, 'href')); + const descriptor = Object.getOwnPropertyDescriptor(URL.prototype, 'href'); + assert.same(descriptor.enumerable, true); + assert.same(descriptor.configurable, true); + assert.same(typeof descriptor.get, 'function'); + assert.same(typeof descriptor.set, 'function'); + } + + assert.same(url.href, 'http://zloirock.ru/'); + + if (DESCRIPTORS) { + url.searchParams.append('foo', 'bar'); + assert.same(url.href, 'http://zloirock.ru/?foo=bar'); + } +}); + +QUnit.test('URL#origin', assert => { + const url = new URL('http://es6.zloirock.ru/tests.html'); + + if (DESCRIPTORS) { + assert.ok(!hasOwnProperty.call(url, 'origin')); + const descriptor = Object.getOwnPropertyDescriptor(URL.prototype, 'origin'); + assert.same(descriptor.enumerable, true); + assert.same(descriptor.configurable, true); + assert.same(typeof descriptor.get, 'function'); + } + + assert.same(url.origin, 'http://es6.zloirock.ru'); + + // assert.same(new URL('https://測試/tests').origin, 'https://xn--g6w251d'); +}); + +QUnit.test('URL#protocol', assert => { + let url = new URL('http://zloirock.ru/'); + + if (DESCRIPTORS) { + assert.ok(!hasOwnProperty.call(url, 'protocol')); + const descriptor = Object.getOwnPropertyDescriptor(URL.prototype, 'protocol'); + assert.same(descriptor.enumerable, true); + assert.same(descriptor.configurable, true); + assert.same(typeof descriptor.get, 'function'); + assert.same(typeof descriptor.set, 'function'); + } + + assert.same(url.protocol, 'http:'); + + if (DESCRIPTORS) { + url = new URL('http://zloirock.ru/'); + url.protocol = 'https'; + assert.same(url.protocol, 'https:'); + assert.same(String(url), 'https://zloirock.ru/'); + + // https://nodejs.org/api/url.html#url_special_schemes + // url = new URL('http://zloirock.ru/'); + // url.protocol = 'fish'; + // assert.same(url.protocol, 'http:'); + // assert.same(url.href, 'http://zloirock.ru/'); + // assert.same(String(url), 'http://zloirock.ru/'); + } +}); + +QUnit.test('URL#username', assert => { + let url = new URL('http://zloirock.ru/'); + + if (DESCRIPTORS) { + assert.ok(!hasOwnProperty.call(url, 'username')); + const descriptor = Object.getOwnPropertyDescriptor(URL.prototype, 'username'); + assert.same(descriptor.enumerable, true); + assert.same(descriptor.configurable, true); + assert.same(typeof descriptor.get, 'function'); + assert.same(typeof descriptor.set, 'function'); + } + + assert.same(url.username, ''); + + url = new URL('http://username@zloirock.ru/'); + assert.same(url.username, 'username'); + + if (DESCRIPTORS) { + url = new URL('http://zloirock.ru/'); + url.username = 'username'; + assert.same(url.username, 'username'); + assert.same(String(url), 'http://username@zloirock.ru/'); + } +}); + +QUnit.test('URL#password', assert => { + let url = new URL('http://zloirock.ru/'); + + if (DESCRIPTORS) { + assert.ok(!hasOwnProperty.call(url, 'password')); + const descriptor = Object.getOwnPropertyDescriptor(URL.prototype, 'password'); + assert.same(descriptor.enumerable, true); + assert.same(descriptor.configurable, true); + assert.same(typeof descriptor.get, 'function'); + assert.same(typeof descriptor.set, 'function'); + } + + assert.same(url.password, ''); + + url = new URL('http://username:password@zloirock.ru/'); + assert.same(url.password, 'password'); + + // url = new URL('http://:password@zloirock.ru/'); // TypeError in FF + // assert.same(url.password, 'password'); + + if (DESCRIPTORS) { + url = new URL('http://zloirock.ru/'); + url.username = 'username'; + url.password = 'password'; + assert.same(url.password, 'password'); + assert.same(String(url), 'http://username:password@zloirock.ru/'); + + // url = new URL('http://zloirock.ru/'); + // url.password = 'password'; + // assert.same(url.password, 'password'); // '' in FF + // assert.same(String(url), 'http://:password@zloirock.ru/'); // 'http://zloirock.ru/' in FF + } +}); + +QUnit.test('URL#host', assert => { + let url = new URL('http://zloirock.ru:81/'); + + if (DESCRIPTORS) { + assert.ok(!hasOwnProperty.call(url, 'host')); + const descriptor = Object.getOwnPropertyDescriptor(URL.prototype, 'host'); + assert.same(descriptor.enumerable, true); + assert.same(descriptor.configurable, true); + assert.same(typeof descriptor.get, 'function'); + assert.same(typeof descriptor.set, 'function'); + } + + assert.same(url.host, 'zloirock.ru:81'); + + if (DESCRIPTORS) { + url = new URL('http://zloirock.ru:81/'); + url.host = 'example.com:82'; + assert.same(url.host, 'example.com:82'); + assert.same(String(url), 'http://example.com:82/'); + } +}); + +QUnit.test('URL#hostname', assert => { + let url = new URL('http://zloirock.ru:81/'); + + if (DESCRIPTORS) { + assert.ok(!hasOwnProperty.call(url, 'hostname')); + const descriptor = Object.getOwnPropertyDescriptor(URL.prototype, 'hostname'); + assert.same(descriptor.enumerable, true); + assert.same(descriptor.configurable, true); + assert.same(typeof descriptor.get, 'function'); + assert.same(typeof descriptor.set, 'function'); + } + + assert.same(url.hostname, 'zloirock.ru'); + + if (DESCRIPTORS) { + url = new URL('http://zloirock.ru:81/'); + url.hostname = 'example.com'; + assert.same(url.hostname, 'example.com'); + assert.same(String(url), 'http://example.com:81/'); + + // url = new URL('http://zloirock.ru:81/'); + // url.hostname = 'example.com:82'; + // assert.same(url.hostname, 'example.com'); // '' in Chrome + // assert.same(String(url), 'http://example.com:81/'); // 'ttp://example.com:82:81/' in Chrome + } +}); + +QUnit.test('URL#port', assert => { + let url = new URL('http://zloirock.ru:1337/'); + + if (DESCRIPTORS) { + assert.ok(!hasOwnProperty.call(url, 'port')); + const descriptor = Object.getOwnPropertyDescriptor(URL.prototype, 'port'); + assert.same(descriptor.enumerable, true); + assert.same(descriptor.configurable, true); + assert.same(typeof descriptor.get, 'function'); + assert.same(typeof descriptor.set, 'function'); + } + + assert.same(url.port, '1337'); + + if (DESCRIPTORS) { + url = new URL('http://zloirock.ru/'); + url.port = 80; + assert.same(url.port, ''); + assert.same(String(url), 'http://zloirock.ru/'); + url.port = 1337; + assert.same(url.port, '1337'); + assert.same(String(url), 'http://zloirock.ru:1337/'); + // url.port = 'abcd'; + // assert.same(url.port, '1337'); // '0' in Chrome + // assert.same(String(url), 'http://zloirock.ru:1337/'); // 'http://zloirock.ru:0/' in Chrome + // url.port = '5678abcd'; + // assert.same(url.port, '5678'); // '1337' in FF + // assert.same(String(url), 'http://zloirock.ru:5678/'); // 'http://zloirock.ru:1337/"' in FF + url.port = 1234.5678; + assert.same(url.port, '1234'); + assert.same(String(url), 'http://zloirock.ru:1234/'); + // url.port = 1e10; + // assert.same(url.port, '1234'); // '0' in Chrome + // assert.same(String(url), 'http://zloirock.ru:1234/'); // 'http://zloirock.ru:0/' in Chrome + } +}); + +QUnit.test('URL#pathname', assert => { + let url = new URL('http://zloirock.ru/foo/bar'); + + if (DESCRIPTORS) { + assert.ok(!hasOwnProperty.call(url, 'pathname')); + const descriptor = Object.getOwnPropertyDescriptor(URL.prototype, 'pathname'); + assert.same(descriptor.enumerable, true); + assert.same(descriptor.configurable, true); + assert.same(typeof descriptor.get, 'function'); + assert.same(typeof descriptor.set, 'function'); + } + + assert.same(url.pathname, '/foo/bar'); + + if (DESCRIPTORS) { + url = new URL('http://zloirock.ru/'); + url.pathname = 'bar/baz'; + assert.same(url.pathname, '/bar/baz'); + assert.same(String(url), 'http://zloirock.ru/bar/baz'); + } +}); + +QUnit.test('URL#search', assert => { + let url = new URL('http://zloirock.ru/'); + + if (DESCRIPTORS) { + assert.ok(!hasOwnProperty.call(url, 'search')); + const descriptor = Object.getOwnPropertyDescriptor(URL.prototype, 'search'); + assert.same(descriptor.enumerable, true); + assert.same(descriptor.configurable, true); + assert.same(typeof descriptor.get, 'function'); + assert.same(typeof descriptor.set, 'function'); + } + + assert.same(url.search, ''); + + url = new URL('http://zloirock.ru/?foo=bar'); + assert.same(url.search, '?foo=bar'); + + if (DESCRIPTORS) { + url = new URL('http://zloirock.ru/?'); + assert.same(url.search, ''); + assert.same(String(url), 'http://zloirock.ru/?'); + url.search = 'foo=bar'; + assert.same(url.search, '?foo=bar'); + assert.same(String(url), 'http://zloirock.ru/?foo=bar'); + url.search = '?bar=baz'; + assert.same(url.search, '?bar=baz'); + assert.same(String(url), 'http://zloirock.ru/?bar=baz'); + url.search = ''; + assert.same(url.search, ''); + assert.same(String(url), 'http://zloirock.ru/'); + } +}); + +QUnit.test('URL#searchParams', assert => { + let url = new URL('http://zloirock.ru/?foo=bar&bar=baz'); + + if (DESCRIPTORS) { + assert.ok(!hasOwnProperty.call(url, 'searchParams')); + const descriptor = Object.getOwnPropertyDescriptor(URL.prototype, 'searchParams'); + assert.same(descriptor.enumerable, true); + assert.same(descriptor.configurable, true); + assert.same(typeof descriptor.get, 'function'); + } + + assert.ok(url.searchParams instanceof URLSearchParams); + assert.same(url.searchParams.get('foo'), 'bar'); + assert.same(url.searchParams.get('bar'), 'baz'); + + if (DESCRIPTORS) { + url = new URL('http://zloirock.ru/'); + url.searchParams.append('foo', 'bar'); + assert.same(String(url), 'http://zloirock.ru/?foo=bar'); + + url = new URL('http://zloirock.ru/'); + url.search = 'foo=bar'; + assert.same(url.searchParams.get('foo'), 'bar'); + + url = new URL('http://zloirock.ru/?foo=bar&bar=baz'); + url.search = ''; + assert.same(url.searchParams.has('foo'), false); + } +}); + +QUnit.test('URL#hash', assert => { + let url = new URL('http://zloirock.ru/'); + + if (DESCRIPTORS) { + assert.ok(!hasOwnProperty.call(url, 'hash')); + const descriptor = Object.getOwnPropertyDescriptor(URL.prototype, 'hash'); + assert.same(descriptor.enumerable, true); + assert.same(descriptor.configurable, true); + assert.same(typeof descriptor.get, 'function'); + assert.same(typeof descriptor.set, 'function'); + } + + assert.same(url.hash, ''); + + url = new URL('http://zloirock.ru/#foo'); + assert.same(url.hash, '#foo'); + + if (DESCRIPTORS) { + url = new URL('http://zloirock.ru/#'); + assert.same(url.hash, ''); + assert.same(String(url), 'http://zloirock.ru/#'); + url.hash = 'foo'; + assert.same(url.hash, '#foo'); + assert.same(String(url), 'http://zloirock.ru/#foo'); + url.hash = ''; + assert.same(url.hash, ''); + assert.same(String(url), 'http://zloirock.ru/'); + } +}); + +QUnit.test('URL#toJSON', assert => { + const { toJSON } = URL.prototype; + assert.isFunction(toJSON); + assert.arity(toJSON, 0); + assert.name(toJSON, 'toJSON'); + assert.enumerable(URL.prototype, 'toJSON'); + assert.looksNative(toJSON); + + const url = new URL('http://zloirock.ru/'); + assert.same(url.toJSON(), 'http://zloirock.ru/'); + + if (DESCRIPTORS) { + url.searchParams.append('foo', 'bar'); + assert.same(url.toJSON(), 'http://zloirock.ru/?foo=bar'); + } +}); + +QUnit.test('URL#toString', assert => { + const { toString } = URL.prototype; + assert.isFunction(toString); + assert.arity(toString, 0); + assert.name(toString, 'toString'); + assert.enumerable(URL.prototype, 'toString'); + assert.looksNative(toString); + + const url = new URL('http://zloirock.ru/'); + assert.same(url.toString(), 'http://zloirock.ru/'); + + if (DESCRIPTORS) { + url.searchParams.append('foo', 'bar'); + assert.same(url.toString(), 'http://zloirock.ru/?foo=bar'); + } +}); + +QUnit.test('URL#@@toStringTag', assert => { + const url = new URL('http://zloirock.ru/'); + assert.same(({}).toString.call(url), '[object URL]'); +});