diff --git a/CHANGELOG.md b/CHANGELOG.md index c8239118f990..9b7e4e9bfd89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## Changelog ##### Unreleased +- Added polyfill of stable sort in `{ Array, %TypedArray% }.prototype.sort`, [#769](https://github.com/zloirock/core-js/issues/769) +- Fixed `Safari` 14.0- `%TypedArray%.prototype.sort` validation of arguments bug - `.at` marked as supported from V8 9.2 ##### 3.13.1 - 2021.05.29 diff --git a/README.md b/README.md index e8c415394bc7..509ec6da5b8e 100644 --- a/README.md +++ b/README.md @@ -541,7 +541,7 @@ class Array { slice(start?: number, end?: number): Array; // with adding support of @@species splice(start?: number, deleteCount?: number, ...items: Array): Array; // with adding support of @@species some(callbackfn: (value: any, index: number, target: any) => boolean, thisArg?: any): boolean; - sort(comparefn?: (a: any, b: any) => number): this; + sort(comparefn?: (a: any, b: any) => number): this; // with modern behavior like stable sort values(): Iterator; @@iterator(): Iterator; @@unscopables: { [newMethodNames: string]: true }; @@ -1510,7 +1510,7 @@ class %TypedArray% { set(array: ArrayLike, offset?: number): void; slice(start?: number, end?: number): %TypedArray%; some(callbackfn: (value: number, index: number, target: %TypedArray%) => boolean, thisArg?: any): boolean; - sort(comparefn?: (a: number, b: number) => number): this; + sort(comparefn?: (a: number, b: number) => number): this; // with modern behavior like stable sort subarray(begin?: number, end?: number): %TypedArray%; toString(): string; toLocaleString(): string; diff --git a/packages/core-js-compat/src/data.js b/packages/core-js-compat/src/data.js index 3c683d3684f8..871e028c1815 100644 --- a/packages/core-js-compat/src/data.js +++ b/packages/core-js-compat/src/data.js @@ -236,9 +236,8 @@ const data = { safari: '8.0', }, 'es.array.sort': { - chrome: '63', + chrome: '70', firefox: '4', - ie: '9', safari: '12.0', }, 'es.array.species': { @@ -1229,10 +1228,10 @@ const data = { safari: '10.0', }, 'es.typed-array.sort': { - chrome: '45', - edge: '13', - firefox: '46', - safari: '10.0', + chrome: '74', + firefox: '67', + // 10.0 - 14.0 accept incorrect arguments + safari: '14.1', }, 'es.typed-array.subarray': { chrome: '26', diff --git a/packages/core-js/internals/array-sort.js b/packages/core-js/internals/array-sort.js new file mode 100644 index 000000000000..876c93296176 --- /dev/null +++ b/packages/core-js/internals/array-sort.js @@ -0,0 +1,45 @@ +// TODO: use something more complex like timsort? +var floor = Math.floor; + +var mergeSort = function (array, comparefn) { + var length = array.length; + var middle = floor(length / 2); + return length < 8 ? insertionSort(array, comparefn) : merge( + mergeSort(array.slice(0, middle), comparefn), + mergeSort(array.slice(middle), comparefn), + comparefn + ); +}; + +var insertionSort = function (array, comparefn) { + var length = array.length; + var i = 1; + var element, j; + + while (i < length) { + j = i; + element = array[i]; + while (j && comparefn(array[j - 1], element) > 0) { + array[j] = array[--j]; + } + if (j !== i++) array[j] = element; + } return array; +}; + +var merge = function (left, right, comparefn) { + var llength = left.length; + var rlength = right.length; + var lindex = 0; + var rindex = 0; + var result = []; + + while (lindex < llength || rindex < rlength) { + if (lindex < llength && rindex < rlength) { + result.push(comparefn(left[lindex], right[rindex]) <= 0 ? left[lindex++] : right[rindex++]); + } else { + result.push(lindex < llength ? left[lindex++] : right[rindex++]); + } + } return result; +}; + +module.exports = mergeSort; diff --git a/packages/core-js/internals/engine-ff-version.js b/packages/core-js/internals/engine-ff-version.js new file mode 100644 index 000000000000..83ba0ffb9e45 --- /dev/null +++ b/packages/core-js/internals/engine-ff-version.js @@ -0,0 +1,5 @@ +var userAgent = require('../internals/engine-user-agent'); + +var firefox = userAgent.match(/firefox\/(\d+)/i); + +module.exports = !!firefox && +firefox[1]; diff --git a/packages/core-js/internals/engine-is-ie-or-edge.js b/packages/core-js/internals/engine-is-ie-or-edge.js new file mode 100644 index 000000000000..813c745eda81 --- /dev/null +++ b/packages/core-js/internals/engine-is-ie-or-edge.js @@ -0,0 +1,3 @@ +var UA = require('../internals/engine-user-agent'); + +module.exports = /MSIE|Trident/.test(UA); diff --git a/packages/core-js/internals/engine-webkit-version.js b/packages/core-js/internals/engine-webkit-version.js new file mode 100644 index 000000000000..55f7d47807bd --- /dev/null +++ b/packages/core-js/internals/engine-webkit-version.js @@ -0,0 +1,5 @@ +var userAgent = require('../internals/engine-user-agent'); + +var webkit = userAgent.match(/AppleWebKit\/(\d+)\./); + +module.exports = !!webkit && +webkit[1]; diff --git a/packages/core-js/internals/object-prototype-accessors-forced.js b/packages/core-js/internals/object-prototype-accessors-forced.js index fb45bd2f212c..2d65faa9aa05 100644 --- a/packages/core-js/internals/object-prototype-accessors-forced.js +++ b/packages/core-js/internals/object-prototype-accessors-forced.js @@ -2,14 +2,13 @@ var IS_PURE = require('../internals/is-pure'); var global = require('../internals/global'); var fails = require('../internals/fails'); -var userAgent = require('../internals/engine-user-agent'); +var WEBKIT = require('../internals/engine-webkit-version'); // Forced replacement object prototype accessors methods module.exports = IS_PURE || !fails(function () { // This feature detection crashes old WebKit // https://github.com/zloirock/core-js/issues/232 - var webkit = userAgent.match(/AppleWebKit\/(\d+)\./); - if (webkit && +webkit[1] < 535) return; + if (WEBKIT && WEBKIT < 535) return; var key = Math.random(); // In FF throws only define methods // eslint-disable-next-line no-undef, no-useless-call -- required for testing diff --git a/packages/core-js/modules/es.array.sort.js b/packages/core-js/modules/es.array.sort.js index fd36157804a2..ce7296a51380 100644 --- a/packages/core-js/modules/es.array.sort.js +++ b/packages/core-js/modules/es.array.sort.js @@ -2,8 +2,14 @@ var $ = require('../internals/export'); var aFunction = require('../internals/a-function'); var toObject = require('../internals/to-object'); +var toLength = require('../internals/to-length'); var fails = require('../internals/fails'); +var internalSort = require('../internals/array-sort'); var arrayMethodIsStrict = require('../internals/array-method-is-strict'); +var FF = require('../internals/engine-ff-version'); +var IE_OR_EDGE = require('../internals/engine-is-ie-or-edge'); +var V8 = require('../internals/engine-v8-version'); +var WEBKIT = require('../internals/engine-webkit-version'); var test = []; var nativeSort = test.sort; @@ -19,14 +25,77 @@ var FAILS_ON_NULL = fails(function () { // Old WebKit var STRICT_METHOD = arrayMethodIsStrict('sort'); -var FORCED = FAILS_ON_UNDEFINED || !FAILS_ON_NULL || !STRICT_METHOD; +var STABLE_SORT = !fails(function () { + // feature detection can be too slow, so check engines versions + if (V8) return V8 < 70; + if (FF && FF > 3) return; + if (IE_OR_EDGE) return true; + if (WEBKIT) return WEBKIT < 603; + + var result = ''; + var code, chr, value, index; + + // generate an array with more 512 elements (Chakra and old V8 fails only in this case) + for (code = 65; code < 76; code++) { + chr = String.fromCharCode(code); + + switch (code) { + case 66: case 69: case 70: case 72: value = 3; break; + case 68: case 71: value = 4; break; + default: value = 2; + } + + for (index = 0; index < 47; index++) { + test.push({ k: chr + index, v: value }); + } + } + + test.sort(function (a, b) { return b.v - a.v; }); + + for (index = 0; index < test.length; index++) { + chr = test[index].k.charAt(0); + if (result.charAt(result.length - 1) !== chr) result += chr; + } + + return result !== 'DGBEFHACIJK'; +}); + +var FORCED = FAILS_ON_UNDEFINED || !FAILS_ON_NULL || !STRICT_METHOD || !STABLE_SORT; + +var getSortCompare = function (comparefn) { + return function (x, y) { + if (y === undefined) return -1; + if (x === undefined) return 1; + if (comparefn !== undefined) return +comparefn(x, y) || 0; + return String(x) > String(y) ? 1 : -1; + }; +}; // `Array.prototype.sort` method // https://tc39.es/ecma262/#sec-array.prototype.sort $({ target: 'Array', proto: true, forced: FORCED }, { sort: function sort(comparefn) { - return comparefn === undefined - ? nativeSort.call(toObject(this)) - : nativeSort.call(toObject(this), aFunction(comparefn)); + if (comparefn !== undefined) aFunction(comparefn); + + var array = toObject(this); + + if (STABLE_SORT) return comparefn === undefined ? nativeSort.call(array) : nativeSort.call(array, comparefn); + + var items = []; + var arrayLength = toLength(array.length); + var itemsLength, index; + + for (index = 0; index < arrayLength; index++) { + if (index in array) items.push(array[index]); + } + + items = internalSort(items, getSortCompare(comparefn)); + itemsLength = items.length; + index = 0; + + while (index < itemsLength) array[index] = items[index++]; + while (index < arrayLength) delete array[index++]; + + return array; } }); diff --git a/packages/core-js/modules/es.typed-array.sort.js b/packages/core-js/modules/es.typed-array.sort.js index 46cc25e4b0ac..b0cae2f68065 100644 --- a/packages/core-js/modules/es.typed-array.sort.js +++ b/packages/core-js/modules/es.typed-array.sort.js @@ -1,12 +1,86 @@ 'use strict'; var ArrayBufferViewCore = require('../internals/array-buffer-view-core'); +var global = require('../internals/global'); +var fails = require('../internals/fails'); +var aFunction = require('../internals/a-function'); +var toLength = require('../internals/to-length'); +var internalSort = require('../internals/array-sort'); +var FF = require('../internals/engine-ff-version'); +var IE_OR_EDGE = require('../internals/engine-is-ie-or-edge'); +var V8 = require('../internals/engine-v8-version'); +var WEBKIT = require('../internals/engine-webkit-version'); var aTypedArray = ArrayBufferViewCore.aTypedArray; var exportTypedArrayMethod = ArrayBufferViewCore.exportTypedArrayMethod; -var $sort = [].sort; +var Uint16Array = global.Uint16Array; +var nativeSort = Uint16Array && Uint16Array.prototype.sort; + +// WebKit +var ACCEPT_INCORRECT_ARGUMENTS = !!nativeSort && !fails(function () { + var array = new Uint16Array(2); + array.sort(null); + array.sort({}); +}); + +var STABLE_SORT = !!nativeSort && !fails(function () { + // feature detection can be too slow, so check engines versions + if (V8) return V8 < 74; + if (FF) return FF < 67; + if (IE_OR_EDGE) return true; + if (WEBKIT) return WEBKIT < 602; + + var array = new Uint16Array(516); + var expected = Array(516); + var index, mod; + + for (index = 0; index < 516; index++) { + mod = index % 4; + array[index] = 515 - index; + expected[index] = index - 2 * mod + 3; + } + + array.sort(function (a, b) { + return (a / 4 | 0) - (b / 4 | 0); + }); + + for (index = 0; index < 516; index++) { + if (array[index] !== expected[index]) return true; + } +}); + +var getSortCompare = function (comparefn) { + return function (x, y) { + if (comparefn !== undefined) return +comparefn(x, y) || 0; + // eslint-disable-next-line no-self-compare -- NaN check + if (y !== y) return -1; + // eslint-disable-next-line no-self-compare -- NaN check + if (x !== x) return 1; + if (x === 0 && y === 0) return 1 / x > 0 && 1 / y < 0 ? 1 : -1; + return x > y; + }; +}; // `%TypedArray%.prototype.sort` method // https://tc39.es/ecma262/#sec-%typedarray%.prototype.sort exportTypedArrayMethod('sort', function sort(comparefn) { - return $sort.call(aTypedArray(this), comparefn); -}); + var array = this; + if (comparefn !== undefined) aFunction(comparefn); + if (STABLE_SORT) return nativeSort.call(array, comparefn); + + aTypedArray(array); + var arrayLength = toLength(array.length); + var items = Array(arrayLength); + var index; + + for (index = 0; index < arrayLength; index++) { + items[index] = array[index]; + } + + items = internalSort(array, getSortCompare(comparefn)); + + for (index = 0; index < arrayLength; index++) { + array[index] = items[index]; + } + + return array; +}, !STABLE_SORT || ACCEPT_INCORRECT_ARGUMENTS); diff --git a/tests/compat/tests.js b/tests/compat/tests.js index ba30ae67c239..93a4a83f470a 100644 --- a/tests/compat/tests.js +++ b/tests/compat/tests.js @@ -382,7 +382,33 @@ GLOBAL.tests = { [1, 2, 3].sort(null); } catch (error2) { [1, 2, 3].sort(undefined); - return true; + + // stable sort + var array = []; + var result = ''; + var code, chr, value, index; + + // generate an array with more 512 elements (Chakra and old V8 fails only in this case) + for (code = 65; code < 76; code++) { + chr = String.fromCharCode(code); + switch (code) { + case 66: case 69: case 70: case 72: value = 3; break; + case 68: case 71: value = 4; break; + default: value = 2; + } + for (index = 0; index < 47; index++) { + array.push({ k: chr + index, v: value }); + } + } + + array.sort(function (a, b) { return b.v - a.v; }); + + for (index = 0; index < array.length; index++) { + chr = array[index].k.charAt(0); + if (result.charAt(result.length - 1) !== chr) result += chr; + } + + return result === 'DGBEFHACIJK'; } } }, @@ -1039,7 +1065,29 @@ GLOBAL.tests = { return Int8Array.prototype.some; }], 'es.typed-array.sort': [ARRAY_BUFFER_VIEWS_SUPPORT, function () { - return Int8Array.prototype.sort; + try { + new Uint16Array(1).sort(null); + new Uint16Array(1).sort({}); + return false; + } catch (error) { /* empty */ } + // stable sort + var array = new Uint16Array(516); + var expected = Array(516); + var index, mod; + + for (index = 0; index < 516; index++) { + mod = index % 4; + array[index] = 515 - index; + expected[index] = index - 2 * mod + 3; + } + + array.sort(function (a, b) { + return (a / 4 | 0) - (b / 4 | 0); + }); + + for (index = 0; index < 516; index++) { + if (array[index] !== expected[index]) return; + } return true; }], 'es.typed-array.subarray': [ARRAY_BUFFER_VIEWS_SUPPORT, function () { return Int8Array.prototype.subarray; diff --git a/tests/pure/es.array.sort.js b/tests/pure/es.array.sort.js index 73074a0c14dc..e0dbf62f4e18 100644 --- a/tests/pure/es.array.sort.js +++ b/tests/pure/es.array.sort.js @@ -7,6 +7,111 @@ QUnit.test('Array#sort', assert => { assert.notThrows(() => sort([1, 2, 3], undefined), 'works with undefined'); assert.throws(() => sort([1, 2, 3], null), 'throws on null'); assert.throws(() => sort([1, 2, 3], {}), 'throws on {}'); + + assert.deepEqual(sort([1, 3, 2]), [1, 2, 3], '#1'); + assert.deepEqual(sort([1, 3, 2, 11]), [1, 11, 2, 3], '#2'); + assert.deepEqual(sort([1, -1, 3, NaN, 2, 0, 11, -0]), [-1, 0, -0, 1, 11, 2, 3, NaN], '#3'); + + let array = Array(5); + array[0] = 1; + array[2] = 3; + array[4] = 2; + let expected = Array(5); + expected[0] = 1; + expected[1] = 2; + expected[2] = 3; + assert.deepEqual(sort(array), expected, 'holes'); + + array = 'zyxwvutsrqponMLKJIHGFEDCBA'.split(''); + expected = 'ABCDEFGHIJKLMnopqrstuvwxyz'.split(''); + assert.deepEqual(sort(array), expected, 'alpha #1'); + + array = 'ёяюэьыъщшчцхфутсрПОНМЛКЙИЗЖЕДГВБА'.split(''); + expected = 'АБВГДЕЖЗИЙКЛМНОПрстуфхцчшщъыьэюяё'.split(''); + assert.deepEqual(sort(array), expected, 'alpha #2'); + + array = [undefined, 1]; + assert.notThrows(() => sort(array, () => { throw 1; }), 'undefined #1'); + assert.deepEqual(array, [1, undefined], 'undefined #2'); + + const object = { + valueOf: () => 1, + toString: () => -1, + }; + + array = { + 0: undefined, + 1: 2, + 2: 1, + 3: 'X', + 4: -1, + 5: 'a', + 6: true, + 7: object, + 8: NaN, + 10: Infinity, + length: 11, + }; + + expected = { + 0: -1, + 1: object, + 2: 1, + 3: 2, + 4: Infinity, + 5: NaN, + 6: 'X', + 7: 'a', + 8: true, + 9: undefined, + length: 11, + }; + + assert.deepEqual(sort(array), expected, 'custom generic'); + + let index, mod, code, chr, value; + expected = Array(516); + array = Array(516); + + for (index = 0; index < 516; index++) { + mod = index % 4; + array[index] = 515 - index; + expected[index] = index - 2 * mod + 3; + } + + sort(array, (a, b) => (a / 4 | 0) - (b / 4 | 0)); + + assert.ok(1 / sort([0, -0])[0] > 0, '-0'); + + assert.same(String(array), String(expected), 'stable #1'); + + let result = ''; + array = []; + + // generate an array with more 512 elements (Chakra and old V8 fails only in this case) + for (code = 65; code < 76; code++) { + chr = String.fromCharCode(code); + + switch (code) { + case 66: case 69: case 70: case 72: value = 3; break; + case 68: case 71: value = 4; break; + default: value = 2; + } + + for (index = 0; index < 47; index++) { + array.push({ k: chr + index, v: value }); + } + } + + sort(array, (a, b) => b.v - a.v); + + for (index = 0; index < array.length; index++) { + chr = array[index].k.charAt(0); + if (result.charAt(result.length - 1) !== chr) result += chr; + } + + assert.same(result, 'DGBEFHACIJK', 'stable #2'); + if (STRICT) { assert.throws(() => sort(null), TypeError, 'ToObject(this)'); assert.throws(() => sort(undefined), TypeError, 'ToObject(this)'); diff --git a/tests/tests/es.array.sort.js b/tests/tests/es.array.sort.js index c6bb7c9d9043..f2195f0b5afe 100644 --- a/tests/tests/es.array.sort.js +++ b/tests/tests/es.array.sort.js @@ -7,6 +7,111 @@ QUnit.test('Array#sort', assert => { assert.name(sort, 'sort'); assert.looksNative(sort); assert.nonEnumerable(Array.prototype, 'sort'); + + assert.deepEqual([1, 3, 2].sort(), [1, 2, 3], '#1'); + assert.deepEqual([1, 3, 2, 11].sort(), [1, 11, 2, 3], '#2'); + assert.deepEqual([1, -1, 3, NaN, 2, 0, 11, -0].sort(), [-1, 0, -0, 1, 11, 2, 3, NaN], '#1'); + + let array = Array(5); + array[0] = 1; + array[2] = 3; + array[4] = 2; + let expected = Array(5); + expected[0] = 1; + expected[1] = 2; + expected[2] = 3; + assert.deepEqual(array.sort(), expected, 'holes'); + + array = 'zyxwvutsrqponMLKJIHGFEDCBA'.split(''); + expected = 'ABCDEFGHIJKLMnopqrstuvwxyz'.split(''); + assert.deepEqual(array.sort(), expected, 'alpha #1'); + + array = 'ёяюэьыъщшчцхфутсрПОНМЛКЙИЗЖЕДГВБА'.split(''); + expected = 'АБВГДЕЖЗИЙКЛМНОПрстуфхцчшщъыьэюяё'.split(''); + assert.deepEqual(array.sort(), expected, 'alpha #2'); + + array = [undefined, 1]; + assert.notThrows(() => array.sort(() => { throw 1; }), 'undefined #1'); + assert.deepEqual(array, [1, undefined], 'undefined #2'); + + const object = { + valueOf: () => 1, + toString: () => -1, + }; + + array = { + 0: undefined, + 1: 2, + 2: 1, + 3: 'X', + 4: -1, + 5: 'a', + 6: true, + 7: object, + 8: NaN, + 10: Infinity, + length: 11, + }; + + expected = { + 0: -1, + 1: object, + 2: 1, + 3: 2, + 4: Infinity, + 5: NaN, + 6: 'X', + 7: 'a', + 8: true, + 9: undefined, + length: 11, + }; + + assert.deepEqual(sort.call(array), expected, 'custom generic'); + + let index, mod, code, chr, value; + expected = Array(516); + array = Array(516); + + for (index = 0; index < 516; index++) { + mod = index % 4; + array[index] = 515 - index; + expected[index] = index - 2 * mod + 3; + } + + array.sort((a, b) => (a / 4 | 0) - (b / 4 | 0)); + + assert.ok(1 / [0, -0].sort()[0] > 0, '-0'); + + assert.same(String(array), String(expected), 'stable #1'); + + let result = ''; + array = []; + + // generate an array with more 512 elements (Chakra and old V8 fails only in this case) + for (code = 65; code < 76; code++) { + chr = String.fromCharCode(code); + + switch (code) { + case 66: case 69: case 70: case 72: value = 3; break; + case 68: case 71: value = 4; break; + default: value = 2; + } + + for (index = 0; index < 47; index++) { + array.push({ k: chr + index, v: value }); + } + } + + array.sort((a, b) => b.v - a.v); + + for (index = 0; index < array.length; index++) { + chr = array[index].k.charAt(0); + if (result.charAt(result.length - 1) !== chr) result += chr; + } + + assert.same(result, 'DGBEFHACIJK', 'stable #2'); + assert.notThrows(() => [1, 2, 3].sort(undefined).length === 3, 'works with undefined'); assert.throws(() => [1, 2, 3].sort(null), 'throws on null'); assert.throws(() => [1, 2, 3].sort({}), 'throws on {}'); diff --git a/tests/tests/es.typed-array.sort.js b/tests/tests/es.typed-array.sort.js new file mode 100644 index 000000000000..d7088b6c0c87 --- /dev/null +++ b/tests/tests/es.typed-array.sort.js @@ -0,0 +1,69 @@ +import { DESCRIPTORS, GLOBAL, STRICT, TYPED_ARRAYS } from '../helpers/constants'; + +if (DESCRIPTORS) QUnit.test('%TypedArrayPrototype%.sort', assert => { + // we can't implement %TypedArrayPrototype% in all engines, so run all tests for each typed array constructor + for (const name in TYPED_ARRAYS) { + const TypedArray = GLOBAL[name]; + const { sort } = TypedArray.prototype; + assert.isFunction(sort, `${ name }::sort is function`); + assert.arity(sort, 1, `${ name }::sort arity is 1`); + assert.name(sort, 'sort', `${ name }::sort name is 'sort'`); + assert.looksNative(sort, `${ name }::sort looks native`); + + if (name.indexOf('Float') === 0) { + assert.deepEqual(new TypedArray([1, -1, 3, NaN, 2, 0, 11, -0]).sort(), new TypedArray([-1, -0, 0, 1, 2, 3, 11, NaN]), '#1'); + assert.ok(1 / new TypedArray([0, -0]).sort()[0] < 0, '-0'); + assert.deepEqual(new TypedArray([NaN, 1, NaN]).sort(), new TypedArray([1, NaN, NaN]), 'NaN'); + } + + if (name.indexOf('8') === -1) { + const expected = Array(516); + let array = new TypedArray(516); + let index, mod, j, k, postfix; + + for (index = 0; index < 516; index++) { + mod = index % 4; + array[index] = 515 - index; + expected[index] = index - 2 * mod + 3; + } + + array.sort((a, b) => (a / 4 | 0) - (b / 4 | 0)); + + assert.same(String(array), String(expected), 'stable #1'); + + let result = ''; + array = new TypedArray(520); + index = 0; + + for (j = 0; j < 10; j++) { + switch (j) { + case 1: case 4: case 5: case 7: postfix = 3; break; + case 3: case 6: postfix = 4; break; + default: postfix = 2; + } + + for (k = 0; k < 52; k++) { + array[index] = 10 * index++ + postfix; + } + } + + array.sort((a, b) => b % 10 - a % 10); + + for (index = 0; index < array.length; index++) { + j = String((array[index] / 520) | 0); + if (result.charAt(result.length - 1) !== j) result += j; + } + + assert.same(result, '3614570289', 'stable #2'); + } + + assert.throws(() => sort.call([0], () => { /* empty */ }), "isn't generic"); + assert.notThrows(() => new TypedArray([1, 2, 3]).sort(undefined).length === 3, 'works with undefined'); + assert.throws(() => new TypedArray([1, 2, 3]).sort(null), 'throws on null'); + assert.throws(() => new TypedArray([1, 2, 3]).sort({}), 'throws on {}'); + if (STRICT) { + assert.throws(() => sort.call(null), TypeError, 'ToObject(this)'); + assert.throws(() => sort.call(undefined), TypeError, 'ToObject(this)'); + } + } +});