| // Copyright 2014 the V8 project authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| (function testArrayConcatArity() { | |
| "use strict"; | |
| assertEquals(1, Array.prototype.concat.length); | |
| })(); | |
| (function testArrayConcatNoPrototype() { | |
| "use strict"; | |
| assertEquals(void 0, Array.prototype.concat.prototype); | |
| })(); | |
| (function testArrayConcatDescriptor() { | |
| "use strict"; | |
| var desc = Object.getOwnPropertyDescriptor(Array.prototype, 'concat'); | |
| assertEquals(false, desc.enumerable); | |
| })(); | |
| (function testNonConcatSpreadableArray() { | |
| "use strict" | |
| var array = [1, 2, 3]; | |
| assertEquals(array, [].concat(array)); | |
| assertEquals(array, array.concat([])); | |
| array[Symbol.isConcatSpreadable] = false; | |
| assertEquals([[1,2,3]], [].concat(array)); | |
| assertEquals([[1,2,3]], array.concat([])); | |
| })(); | |
| (function testConcatArrayLike() { | |
| "use strict"; | |
| var obj = { | |
| "length": 6, | |
| "1": "A", | |
| "3": "B", | |
| "5": "C" | |
| }; | |
| obj[Symbol.isConcatSpreadable] = true; | |
| var obj2 = { length: 3, "0": "0", "1": "1", "2": "2" }; | |
| var arr = ["X", "Y", "Z"]; | |
| assertEquals([void 0, "A", void 0, "B", void 0, "C", | |
| { "length": 3, "0": "0", "1": "1", "2": "2" }, | |
| "X", "Y", "Z"], Array.prototype.concat.call(obj, obj2, arr)); | |
| })(); | |
| (function testConcatArrayLikeStringLength() { | |
| "use strict"; | |
| var obj = { | |
| "length": "6", | |
| "1": "A", | |
| "3": "B", | |
| "5": "C" | |
| }; | |
| obj[Symbol.isConcatSpreadable] = true; | |
| var obj2 = { length: 3, "0": "0", "1": "1", "2": "2" }; | |
| var arr = ["X", "Y", "Z"]; | |
| assertEquals([void 0, "A", void 0, "B", void 0, "C", | |
| { "length": 3, "0": "0", "1": "1", "2": "2" }, | |
| "X", "Y", "Z"], Array.prototype.concat.call(obj, obj2, arr)); | |
| })(); | |
| (function testConcatArrayLikeNegativeLength() { | |
| "use strict"; | |
| var obj = { | |
| "length": -6, | |
| "1": "A", | |
| "3": "B", | |
| "5": "C" | |
| }; | |
| obj[Symbol.isConcatSpreadable] = true; | |
| assertEquals([], [].concat(obj)); | |
| obj.length = -6.7; | |
| assertEquals([], [].concat(obj)); | |
| obj.length = "-6"; | |
| assertEquals([], [].concat(obj)); | |
| })(); | |
| (function testConcatArrayLikeToLengthThrows() { | |
| "use strict"; | |
| var obj = { | |
| "length": {valueOf: null, toString: null}, | |
| "1": "A", | |
| "3": "B", | |
| "5": "C" | |
| }; | |
| obj[Symbol.isConcatSpreadable] = true; | |
| var obj2 = { length: 3, "0": "0", "1": "1", "2": "2" }; | |
| var arr = ["X", "Y", "Z"]; | |
| assertThrows(function() { | |
| Array.prototype.concat.call(obj, obj2, arr); | |
| }, TypeError); | |
| })(); | |
| (function testConcatArrayLikePrimitiveNonNumberLength() { | |
| "use strict"; | |
| var obj = { | |
| "1": "A", | |
| "3": "B", | |
| "5": "C" | |
| }; | |
| obj[Symbol.isConcatSpreadable] = true; | |
| obj.length = {toString: function() { return "SIX"; }, valueOf: null }; | |
| assertEquals([], [].concat(obj)); | |
| obj.length = {toString: null, valueOf: function() { return "SIX"; } }; | |
| assertEquals([], [].concat(obj)); | |
| })(); | |
| (function testConcatArrayLikeLengthToStringThrows() { | |
| "use strict"; | |
| function MyError() {} | |
| var obj = { | |
| "length": { toString: function() { | |
| throw new MyError(); | |
| }, valueOf: null | |
| }, | |
| "1": "A", | |
| "3": "B", | |
| "5": "C" | |
| }; | |
| obj[Symbol.isConcatSpreadable] = true; | |
| assertThrows(function() { | |
| [].concat(obj); | |
| }, MyError); | |
| })(); | |
| (function testConcatArrayLikeLengthValueOfThrows() { | |
| "use strict"; | |
| function MyError() {} | |
| var obj = { | |
| "length": { valueOf: function() { | |
| throw new MyError(); | |
| }, toString: null | |
| }, | |
| "1": "A", | |
| "3": "B", | |
| "5": "C" | |
| }; | |
| obj[Symbol.isConcatSpreadable] = true; | |
| assertThrows(function() { | |
| [].concat(obj); | |
| }, MyError); | |
| })(); | |
| (function testConcatHoleyArray() { | |
| "use strict"; | |
| var arr = []; | |
| arr[4] = "Item 4"; | |
| arr[8] = "Item 8"; | |
| var arr2 = [".", "!", "?"]; | |
| assertEquals([void 0, void 0, void 0, void 0, "Item 4", void 0, void 0, | |
| void 0, "Item 8", ".", "!", "?"], arr.concat(arr2)); | |
| })(); | |
| (function testIsConcatSpreadableGetterThrows() { | |
| "use strict"; | |
| function MyError() {} | |
| var obj = {}; | |
| Object.defineProperty(obj, Symbol.isConcatSpreadable, { | |
| get: function() { throw new MyError(); } | |
| }); | |
| assertThrows(function() { | |
| [].concat(obj); | |
| }, MyError); | |
| assertThrows(function() { | |
| Array.prototype.concat.call(obj, 1, 2, 3); | |
| }, MyError); | |
| })(); | |
| (function testConcatLengthThrows() { | |
| "use strict"; | |
| function MyError() {} | |
| var obj = {}; | |
| obj[Symbol.isConcatSpreadable] = true; | |
| Object.defineProperty(obj, "length", { | |
| get: function() { throw new MyError(); } | |
| }); | |
| assertThrows(function() { | |
| [].concat(obj); | |
| }, MyError); | |
| assertThrows(function() { | |
| Array.prototype.concat.call(obj, 1, 2, 3); | |
| }, MyError); | |
| })(); | |
| (function testConcatArraySubclass() { | |
| "use strict"; | |
| // If @@isConcatSpreadable is not used, the value of IsArray(O) | |
| // is used to determine the spreadable property. | |
| class A extends Array {} | |
| var obj = [].concat(new A(1, 2, 3), new A(4, 5, 6), new A(7, 8, 9)); | |
| assertEquals(9, obj.length); | |
| for (var i = 0; i < obj.length; ++i) { | |
| assertEquals(i + 1, obj[i]); | |
| } | |
| // TODO(caitp): when concat is called on instances of classes which extend | |
| // Array, they should: | |
| // | |
| // - return an instance of the class, rather than an Array instance (if from | |
| // same Realm) | |
| // - always treat such classes as concat-spreadable | |
| })(); | |
| (function testConcatArraySubclassOptOut() { | |
| "use strict"; | |
| class A extends Array { | |
| get [Symbol.isConcatSpreadable]() { return false; } | |
| } | |
| var obj = [].concat(new A(1, 2, 3), new A(4, 5, 6), new A(7, 8, 9)); | |
| assertEquals(3, obj.length); | |
| assertEquals(3, obj[0].length); | |
| assertEquals(3, obj[1].length); | |
| assertEquals(3, obj[2].length); | |
| })(); | |
| (function testConcatNonArray() { | |
| "use strict"; | |
| class NonArray { | |
| constructor() { Array.apply(this, arguments); } | |
| }; | |
| var obj = new NonArray(1,2,3); | |
| var result = Array.prototype.concat.call(obj, 4, 5, 6); | |
| assertEquals(Array, result.constructor); | |
| assertEquals([obj,4,5,6], result); | |
| assertFalse(result instanceof NonArray); | |
| })(); | |
| function testConcatTypedArray(type, elems, modulo) { | |
| "use strict"; | |
| var items = new Array(elems); | |
| var ta_by_len = new type(elems); | |
| for (var i = 0; i < elems; ++i) { | |
| ta_by_len[i] = items[i] = modulo === false ? i : elems % modulo; | |
| } | |
| var ta = new type(items); | |
| assertEquals([ta, ta], [].concat(ta, ta)); | |
| ta[Symbol.isConcatSpreadable] = true; | |
| assertEquals(items, [].concat(ta)); | |
| assertEquals([ta_by_len, ta_by_len], [].concat(ta_by_len, ta_by_len)); | |
| ta_by_len[Symbol.isConcatSpreadable] = true; | |
| assertEquals(items, [].concat(ta_by_len)); | |
| // TypedArray with fake `length`. | |
| ta = new type(1); | |
| var defValue = ta[0]; | |
| var expected = new Array(4000); | |
| expected[0] = defValue; | |
| Object.defineProperty(ta, "length", { value: 4000 }); | |
| ta[Symbol.isConcatSpreadable] = true; | |
| assertEquals(expected, [].concat(ta)); | |
| } | |
| (function testConcatSmallTypedArray() { | |
| var length = 1; | |
| testConcatTypedArray(Uint8Array, length, Math.pow(2, 8)); | |
| testConcatTypedArray(Uint16Array, length, Math.pow(2, 16)); | |
| testConcatTypedArray(Uint32Array, length, Math.pow(2, 32)); | |
| testConcatTypedArray(Float32Array, length, false); | |
| testConcatTypedArray(Float64Array, length, false); | |
| })(); | |
| (function testConcatLargeTypedArray() { | |
| var length = 4000; | |
| testConcatTypedArray(Uint8Array, length, Math.pow(2, 8)); | |
| testConcatTypedArray(Uint16Array, length, Math.pow(2, 16)); | |
| testConcatTypedArray(Uint32Array, length, Math.pow(2, 32)); | |
| testConcatTypedArray(Float32Array, length, false); | |
| testConcatTypedArray(Float64Array, length, false); | |
| })(); | |
| (function testConcatStrictArguments() { | |
| var args = (function(a, b, c) { "use strict"; return arguments; })(1,2,3); | |
| args[Symbol.isConcatSpreadable] = true; | |
| assertEquals([1, 2, 3, 1, 2, 3], [].concat(args, args)); | |
| Object.defineProperty(args, "length", { value: 6 }); | |
| assertEquals([1, 2, 3, void 0, void 0, void 0], [].concat(args)); | |
| })(); | |
| (function testConcatSloppyArguments() { | |
| var args = (function(a, b, c) { return arguments; })(1,2,3); | |
| args[Symbol.isConcatSpreadable] = true; | |
| assertEquals([1, 2, 3, 1, 2, 3], [].concat(args, args)); | |
| Object.defineProperty(args, "length", { value: 6 }); | |
| assertEquals([1, 2, 3, void 0, void 0, void 0], [].concat(args)); | |
| })(); | |
| (function testConcatSloppyArgumentsWithDupes() { | |
| var args = (function(a, a, a) { return arguments; })(1,2,3); | |
| args[Symbol.isConcatSpreadable] = true; | |
| assertEquals([1, 2, 3, 1, 2, 3], [].concat(args, args)); | |
| Object.defineProperty(args, "length", { value: 6 }); | |
| assertEquals([1, 2, 3, void 0, void 0, void 0], [].concat(args)); | |
| })(); | |
| (function testConcatSloppyArgumentsThrows() { | |
| function MyError() {} | |
| var args = (function(a) { return arguments; })(1,2,3); | |
| Object.defineProperty(args, 0, { | |
| get: function() { throw new MyError(); } | |
| }); | |
| args[Symbol.isConcatSpreadable] = true; | |
| assertThrows(function() { | |
| return [].concat(args, args); | |
| }, MyError); | |
| })(); | |
| (function testConcatHoleySloppyArguments() { | |
| var args = (function(a) { return arguments; })(1,2,3); | |
| delete args[1]; | |
| args[Symbol.isConcatSpreadable] = true; | |
| assertEquals([1, void 0, 3, 1, void 0, 3], [].concat(args, args)); | |
| })(); | |
| (function testConcatSpreadableStringWrapper() { | |
| "use strict"; | |
| var str1 = new String("yuck\uD83D\uDCA9") | |
| // String wrapper objects are not concat-spreadable by default | |
| assertEquals([str1], [].concat(str1)); | |
| // String wrapper objects may be individually concat-spreadable | |
| str1[Symbol.isConcatSpreadable] = true; | |
| assertEquals(["y", "u", "c", "k", "\uD83D", "\uDCA9"], | |
| [].concat(str1)); | |
| String.prototype[Symbol.isConcatSpreadable] = true; | |
| // String wrapper objects may be concat-spreadable | |
| assertEquals(["y", "u", "c", "k", "\uD83D", "\uDCA9"], | |
| [].concat(new String("yuck\uD83D\uDCA9"))); | |
| // String values are never concat-spreadable | |
| assertEquals(["yuck\uD83D\uDCA9"], [].concat("yuck\uD83D\uDCA9")); | |
| delete String.prototype[Symbol.isConcatSpreadable]; | |
| })(); | |
| (function testConcatSpreadableBooleanWrapper() { | |
| "use strict"; | |
| var bool = new Boolean(true) | |
| // Boolean wrapper objects are not concat-spreadable by default | |
| assertEquals([bool], [].concat(bool)); | |
| // Boolean wrapper objects may be individually concat-spreadable | |
| bool[Symbol.isConcatSpreadable] = true; | |
| bool.length = 3; | |
| bool[0] = 1, bool[1] = 2, bool[2] = 3; | |
| assertEquals([1, 2, 3], [].concat(bool)); | |
| Boolean.prototype[Symbol.isConcatSpreadable] = true; | |
| // Boolean wrapper objects may be concat-spreadable | |
| assertEquals([], [].concat(new Boolean(true))); | |
| Boolean.prototype[0] = 1; | |
| Boolean.prototype[1] = 2; | |
| Boolean.prototype[2] = 3; | |
| Boolean.prototype.length = 3; | |
| assertEquals([1,2,3], [].concat(new Boolean(true))); | |
| // Boolean values are never concat-spreadable | |
| assertEquals([true], [].concat(true)); | |
| delete Boolean.prototype[Symbol.isConcatSpreadable]; | |
| delete Boolean.prototype[0]; | |
| delete Boolean.prototype[1]; | |
| delete Boolean.prototype[2]; | |
| delete Boolean.prototype.length; | |
| })(); | |
| (function testConcatSpreadableNumberWrapper() { | |
| "use strict"; | |
| var num = new Number(true) | |
| // Number wrapper objects are not concat-spreadable by default | |
| assertEquals([num], [].concat(num)); | |
| // Number wrapper objects may be individually concat-spreadable | |
| num[Symbol.isConcatSpreadable] = true; | |
| num.length = 3; | |
| num[0] = 1, num[1] = 2, num[2] = 3; | |
| assertEquals([1, 2, 3], [].concat(num)); | |
| Number.prototype[Symbol.isConcatSpreadable] = true; | |
| // Number wrapper objects may be concat-spreadable | |
| assertEquals([], [].concat(new Number(123))); | |
| Number.prototype[0] = 1; | |
| Number.prototype[1] = 2; | |
| Number.prototype[2] = 3; | |
| Number.prototype.length = 3; | |
| assertEquals([1,2,3], [].concat(new Number(123))); | |
| // Number values are never concat-spreadable | |
| assertEquals([true], [].concat(true)); | |
| delete Number.prototype[Symbol.isConcatSpreadable]; | |
| delete Number.prototype[0]; | |
| delete Number.prototype[1]; | |
| delete Number.prototype[2]; | |
| delete Number.prototype.length; | |
| })(); | |
| (function testConcatSpreadableFunction() { | |
| "use strict"; | |
| var fn = function(a, b, c) {} | |
| // Functions are not concat-spreadable by default | |
| assertEquals([fn], [].concat(fn)); | |
| // Functions may be individually concat-spreadable | |
| fn[Symbol.isConcatSpreadable] = true; | |
| fn[0] = 1, fn[1] = 2, fn[2] = 3; | |
| assertEquals([1, 2, 3], [].concat(fn)); | |
| Function.prototype[Symbol.isConcatSpreadable] = true; | |
| // Functions may be concat-spreadable | |
| assertEquals([void 0, void 0, void 0], [].concat(function(a,b,c) {})); | |
| Function.prototype[0] = 1; | |
| Function.prototype[1] = 2; | |
| Function.prototype[2] = 3; | |
| assertEquals([1,2,3], [].concat(function(a, b, c) {})); | |
| delete Function.prototype[Symbol.isConcatSpreadable]; | |
| delete Function.prototype[0]; | |
| delete Function.prototype[1]; | |
| delete Function.prototype[2]; | |
| })(); | |
| (function testConcatSpreadableRegExp() { | |
| "use strict"; | |
| var re = /abc/; | |
| // RegExps are not concat-spreadable by default | |
| assertEquals([re], [].concat(re)); | |
| // RegExps may be individually concat-spreadable | |
| re[Symbol.isConcatSpreadable] = true; | |
| re[0] = 1, re[1] = 2, re[2] = 3, re.length = 3; | |
| assertEquals([1, 2, 3], [].concat(re)); | |
| // RegExps may be concat-spreadable | |
| RegExp.prototype[Symbol.isConcatSpreadable] = true; | |
| RegExp.prototype.length = 3; | |
| assertEquals([void 0, void 0, void 0], [].concat(/abc/)); | |
| RegExp.prototype[0] = 1; | |
| RegExp.prototype[1] = 2; | |
| RegExp.prototype[2] = 3; | |
| assertEquals([1,2,3], [].concat(/abc/)); | |
| delete RegExp.prototype[Symbol.isConcatSpreadable]; | |
| delete RegExp.prototype[0]; | |
| delete RegExp.prototype[1]; | |
| delete RegExp.prototype[2]; | |
| delete RegExp.prototype.length; | |
| })(); | |
| (function testArrayConcatSpreadableSparseObject() { | |
| "use strict"; | |
| var obj = { length: 5 }; | |
| obj[Symbol.isConcatSpreadable] = true; | |
| assertEquals([void 0, void 0, void 0, void 0, void 0], [].concat(obj)); | |
| obj.length = 4000; | |
| assertEquals(new Array(4000), [].concat(obj)); | |
| })(); | |
| // ES5 tests | |
| (function testArrayConcatES5() { | |
| "use strict"; | |
| var poses; | |
| var pos; | |
| poses = [140, 4000000000]; | |
| while (pos = poses.shift()) { | |
| var a = new Array(pos); | |
| var array_proto = []; | |
| a.__proto__ = array_proto; | |
| assertEquals(pos, a.length); | |
| a.push('foo'); | |
| assertEquals(pos + 1, a.length); | |
| var b = ['bar']; | |
| var c = a.concat(b); | |
| assertEquals(pos + 2, c.length); | |
| assertEquals("undefined", typeof(c[pos - 1])); | |
| assertEquals("foo", c[pos]); | |
| assertEquals("bar", c[pos + 1]); | |
| // Can we fool the system by putting a number in a string? | |
| var onetwofour = "124"; | |
| a[onetwofour] = 'doo'; | |
| assertEquals(a[124], 'doo'); | |
| c = a.concat(b); | |
| assertEquals(c[124], 'doo'); | |
| // If we put a number in the prototype, then the spec says it should be | |
| // copied on concat. | |
| array_proto["123"] = 'baz'; | |
| assertEquals(a[123], 'baz'); | |
| c = a.concat(b); | |
| assertEquals(pos + 2, c.length); | |
| assertEquals("baz", c[123]); | |
| assertEquals("undefined", typeof(c[pos - 1])); | |
| assertEquals("foo", c[pos]); | |
| assertEquals("bar", c[pos + 1]); | |
| // When we take the number off the prototype it disappears from a, but | |
| // the concat put it in c itself. | |
| array_proto["123"] = undefined; | |
| assertEquals("undefined", typeof(a[123])); | |
| assertEquals("baz", c[123]); | |
| // If the element of prototype is shadowed, the element on the instance | |
| // should be copied, but not the one on the prototype. | |
| array_proto[123] = 'baz'; | |
| a[123] = 'xyz'; | |
| assertEquals('xyz', a[123]); | |
| c = a.concat(b); | |
| assertEquals('xyz', c[123]); | |
| // Non-numeric properties on the prototype or the array shouldn't get | |
| // copied. | |
| array_proto.moe = 'joe'; | |
| a.ben = 'jerry'; | |
| assertEquals(a["moe"], 'joe'); | |
| assertEquals(a["ben"], 'jerry'); | |
| c = a.concat(b); | |
| // ben was not copied | |
| assertEquals("undefined", typeof(c.ben)); | |
| // When we take moe off the prototype it disappears from all arrays. | |
| array_proto.moe = undefined; | |
| assertEquals("undefined", typeof(c.moe)); | |
| // Negative indices don't get concated. | |
| a[-1] = 'minus1'; | |
| assertEquals("minus1", a[-1]); | |
| assertEquals("undefined", typeof(a[0xffffffff])); | |
| c = a.concat(b); | |
| assertEquals("undefined", typeof(c[-1])); | |
| assertEquals("undefined", typeof(c[0xffffffff])); | |
| assertEquals(c.length, a.length + 1); | |
| } | |
| poses = [140, 4000000000]; | |
| while (pos = poses.shift()) { | |
| var a = new Array(pos); | |
| assertEquals(pos, a.length); | |
| a.push('foo'); | |
| assertEquals(pos + 1, a.length); | |
| var b = ['bar']; | |
| var c = a.concat(b); | |
| assertEquals(pos + 2, c.length); | |
| assertEquals("undefined", typeof(c[pos - 1])); | |
| assertEquals("foo", c[pos]); | |
| assertEquals("bar", c[pos + 1]); | |
| // Can we fool the system by putting a number in a string? | |
| var onetwofour = "124"; | |
| a[onetwofour] = 'doo'; | |
| assertEquals(a[124], 'doo'); | |
| c = a.concat(b); | |
| assertEquals(c[124], 'doo'); | |
| // If we put a number in the prototype, then the spec says it should be | |
| // copied on concat. | |
| Array.prototype["123"] = 'baz'; | |
| assertEquals(a[123], 'baz'); | |
| c = a.concat(b); | |
| assertEquals(pos + 2, c.length); | |
| assertEquals("baz", c[123]); | |
| assertEquals("undefined", typeof(c[pos - 1])); | |
| assertEquals("foo", c[pos]); | |
| assertEquals("bar", c[pos + 1]); | |
| // When we take the number off the prototype it disappears from a, but | |
| // the concat put it in c itself. | |
| Array.prototype["123"] = undefined; | |
| assertEquals("undefined", typeof(a[123])); | |
| assertEquals("baz", c[123]); | |
| // If the element of prototype is shadowed, the element on the instance | |
| // should be copied, but not the one on the prototype. | |
| Array.prototype[123] = 'baz'; | |
| a[123] = 'xyz'; | |
| assertEquals('xyz', a[123]); | |
| c = a.concat(b); | |
| assertEquals('xyz', c[123]); | |
| // Non-numeric properties on the prototype or the array shouldn't get | |
| // copied. | |
| Array.prototype.moe = 'joe'; | |
| a.ben = 'jerry'; | |
| assertEquals(a["moe"], 'joe'); | |
| assertEquals(a["ben"], 'jerry'); | |
| c = a.concat(b); | |
| // ben was not copied | |
| assertEquals("undefined", typeof(c.ben)); | |
| // moe was not copied, but we can see it through the prototype | |
| assertEquals("joe", c.moe); | |
| // When we take moe off the prototype it disappears from all arrays. | |
| Array.prototype.moe = undefined; | |
| assertEquals("undefined", typeof(c.moe)); | |
| // Negative indices don't get concated. | |
| a[-1] = 'minus1'; | |
| assertEquals("minus1", a[-1]); | |
| assertEquals("undefined", typeof(a[0xffffffff])); | |
| c = a.concat(b); | |
| assertEquals("undefined", typeof(c[-1])); | |
| assertEquals("undefined", typeof(c[0xffffffff])); | |
| assertEquals(c.length, a.length + 1); | |
| } | |
| a = []; | |
| c = a.concat('Hello'); | |
| assertEquals(1, c.length); | |
| assertEquals("Hello", c[0]); | |
| assertEquals("Hello", c.toString()); | |
| // Check that concat preserves holes. | |
| var holey = [void 0,'a',,'c'].concat(['d',,'f',[0,,2],void 0]) | |
| assertEquals(9, holey.length); // hole in embedded array is ignored | |
| for (var i = 0; i < holey.length; i++) { | |
| if (i == 2 || i == 5) { | |
| assertFalse(i in holey); | |
| } else { | |
| assertTrue(i in holey); | |
| } | |
| } | |
| // Polluted prototype from prior tests. | |
| delete Array.prototype[123]; | |
| // Check that concat reads getters in the correct order. | |
| var arr1 = [,2]; | |
| var arr2 = [1,3]; | |
| var r1 = [].concat(arr1, arr2); // [,2,1,3] | |
| assertEquals([,2,1,3], r1); | |
| // Make first array change length of second array. | |
| Object.defineProperty(arr1, 0, {get: function() { | |
| arr2.push("X"); | |
| return undefined; | |
| }, configurable: true}) | |
| var r2 = [].concat(arr1, arr2); // [undefined,2,1,3,"X"] | |
| assertEquals([undefined,2,1,3,"X"], r2); | |
| // Make first array change length of second array massively. | |
| arr2.length = 2; | |
| Object.defineProperty(arr1, 0, {get: function() { | |
| arr2[500000] = "X"; | |
| return undefined; | |
| }, configurable: true}) | |
| var r3 = [].concat(arr1, arr2); // [undefined,2,1,3,"X"] | |
| var expected = [undefined,2,1,3]; | |
| expected[500000 + 2] = "X"; | |
| assertEquals(expected, r3); | |
| var arr3 = []; | |
| var trace = []; | |
| var expectedTrace = [] | |
| function mkGetter(i) { return function() { trace.push(i); }; } | |
| arr3.length = 10000; | |
| for (var i = 0; i < 100; i++) { | |
| Object.defineProperty(arr3, i * i, {get: mkGetter(i)}); | |
| expectedTrace[i] = i; | |
| expectedTrace[100 + i] = i; | |
| } | |
| var r4 = [0].concat(arr3, arr3); | |
| assertEquals(1 + arr3.length * 2, r4.length); | |
| assertEquals(expectedTrace, trace); | |
| // Clean up. | |
| delete Array.prototype[123]; | |
| delete Array.prototype["123"]; | |
| delete Array.prototype["moe"]; | |
| })(); | |
| //////////////////////////////////////////////////////////////////////////////// | |
| // Tests with proxies | |
| // Note: concat does not currently support species so there is no difference | |
| // between [].concat(foo) and Array.prototype.concat.apply(foo). | |
| var log = []; | |
| var logger = {}; | |
| var handler = new Proxy({}, logger); | |
| logger.get = function(t, trap, r) { | |
| return function(...args) { | |
| log.push([trap, ...args]); | |
| return Reflect[trap](...args); | |
| } | |
| }; | |
| (function testUnspreadableNonArrayLikeProxy() { | |
| var target = {0: "a", 1: "b"}; | |
| var obj = new Proxy(target, handler); | |
| log.length = 0; | |
| assertEquals([obj], [].concat(obj)); | |
| assertEquals(1, log.length); | |
| for (var i in log) assertSame(target, log[i][1]); | |
| assertEquals(["get", target, Symbol.isConcatSpreadable, obj], log[0]); | |
| log.length = 0; | |
| assertEquals([obj], Array.prototype.concat.apply(obj)); | |
| assertEquals(1, log.length); | |
| for (var i in log) assertSame(target, log[i][1]); | |
| assertEquals(["get", target, Symbol.isConcatSpreadable, obj], log[0]); | |
| })(); | |
| (function testSpreadableNonArrayLikeProxy() { | |
| var target = {0: "a", 1: "b", [Symbol.isConcatSpreadable]: "truish"}; | |
| var obj = new Proxy(target, handler); | |
| log.length = 0; | |
| assertEquals([], [].concat(obj)); | |
| assertEquals(2, log.length); | |
| for (var i in log) assertSame(target, log[i][1]); | |
| assertEquals(["get", target, Symbol.isConcatSpreadable, obj], log[0]); | |
| assertEquals(["get", target, "length", obj], log[1]); | |
| log.length = 0; | |
| assertEquals([], Array.prototype.concat.apply(obj)); | |
| assertEquals(2, log.length); | |
| for (var i in log) assertSame(target, log[i][1]); | |
| assertEquals(["get", target, Symbol.isConcatSpreadable, obj], log[0]); | |
| assertEquals(["get", target, "length", obj], log[1]); | |
| target.length = 3; | |
| log.length = 0; | |
| assertEquals(["a", "b", undefined], [].concat(obj)); | |
| assertEquals(7, log.length); | |
| for (var i in log) assertSame(target, log[i][1]); | |
| assertEquals(["get", target, Symbol.isConcatSpreadable, obj], log[0]); | |
| assertEquals(["get", target, "length", obj], log[1]); | |
| assertEquals(["has", target, "0"], log[2]); | |
| assertEquals(["get", target, "0", obj], log[3]); | |
| assertEquals(["has", target, "1"], log[4]); | |
| assertEquals(["get", target, "1", obj], log[5]); | |
| assertEquals(["has", target, "2"], log[6]); | |
| log.length = 0; | |
| assertEquals(["a", "b", undefined], Array.prototype.concat.apply(obj)); | |
| assertEquals(7, log.length); | |
| for (var i in log) assertSame(target, log[i][1]); | |
| assertEquals(["get", target, Symbol.isConcatSpreadable, obj], log[0]); | |
| assertEquals(["get", target, "length", obj], log[1]); | |
| assertEquals(["has", target, "0"], log[2]); | |
| assertEquals(["get", target, "0", obj], log[3]); | |
| assertEquals(["has", target, "1"], log[4]); | |
| assertEquals(["get", target, "1", obj], log[5]); | |
| assertEquals(["has", target, "2"], log[6]); | |
| })(); | |
| (function testUnspreadableArrayLikeProxy() { | |
| var target = ["a", "b"]; | |
| target[Symbol.isConcatSpreadable] = ""; | |
| var obj = new Proxy(target, handler); | |
| log.length = 0; | |
| assertEquals([obj], [].concat(obj)); | |
| assertEquals(1, log.length); | |
| for (var i in log) assertSame(target, log[i][1]); | |
| assertEquals(["get", target, Symbol.isConcatSpreadable, obj], log[0]); | |
| log.length = 0; | |
| assertEquals([obj], Array.prototype.concat.apply(obj)); | |
| assertEquals(2, log.length); // An extra read for the constructor | |
| for (var i in log) assertSame(target, log[i][1]); | |
| assertEquals(["get", target, "constructor", obj], log[0]); | |
| assertEquals(["get", target, Symbol.isConcatSpreadable, obj], log[1]); | |
| })(); | |
| (function testSpreadableArrayLikeProxy() { | |
| var target = ["a", "b"]; | |
| target[Symbol.isConcatSpreadable] = undefined; | |
| var obj = new Proxy(target, handler); | |
| log.length = 0; | |
| assertEquals(["a", "b"], [].concat(obj)); | |
| assertEquals(6, log.length); | |
| for (var i in log) assertSame(target, log[i][1]); | |
| assertEquals(["get", target, Symbol.isConcatSpreadable, obj], log[0]); | |
| assertEquals(["get", target, "length", obj], log[1]); | |
| assertEquals(["has", target, "0"], log[2]); | |
| assertEquals(["get", target, "0", obj], log[3]); | |
| assertEquals(["has", target, "1"], log[4]); | |
| assertEquals(["get", target, "1", obj], log[5]); | |
| log.length = 0; | |
| assertEquals(["a", "b"], Array.prototype.concat.apply(obj)); | |
| assertEquals(7, log.length); | |
| for (var i in log) assertSame(target, log[i][1]); | |
| assertEquals(["get", target, "constructor", obj], log[0]); | |
| assertEquals(["get", target, Symbol.isConcatSpreadable, obj], log[1]); | |
| assertEquals(["get", target, "length", obj], log[2]); | |
| assertEquals(["has", target, "0"], log[3]); | |
| assertEquals(["get", target, "0", obj], log[4]); | |
| assertEquals(["has", target, "1"], log[5]); | |
| assertEquals(["get", target, "1", obj], log[6]); | |
| })(); | |
| (function testSpreadableArrayLikeProxyWithNontrivialLength() { | |
| var getTrap = function(t, key) { | |
| if (key === "length") return {[Symbol.toPrimitive]() {return 3}}; | |
| if (key === "2") return "baz"; | |
| if (key === "3") return "bar"; | |
| }; | |
| var target = []; | |
| var obj = new Proxy(target, {get: getTrap, has: () => true}); | |
| assertEquals([undefined, undefined, "baz"], [].concat(obj)); | |
| assertEquals([undefined, undefined, "baz"], Array.prototype.concat.apply(obj)) | |
| })(); | |
| (function testSpreadableArrayLikeProxyWithBogusLength() { | |
| var getTrap = function(t, key) { | |
| if (key === "length") return Symbol(); | |
| if (key === "2") return "baz"; | |
| if (key === "3") return "bar"; | |
| }; | |
| var target = []; | |
| var obj = new Proxy(target, {get: getTrap, has: () => true}); | |
| assertThrows(() => [].concat(obj), TypeError); | |
| assertThrows(() => Array.prototype.concat.apply(obj), TypeError); | |
| })(); | |
| (function testConcatRevokedProxy() { | |
| "use strict"; | |
| var target = []; | |
| var handler = { | |
| get(_, name) { | |
| if (name === Symbol.isConcatSpreadable) { | |
| p.revoke(); | |
| } | |
| return target[name]; | |
| } | |
| } | |
| p = Proxy.revocable(target, handler); | |
| target = {}; | |
| target.__proto__ = p.proxy; | |
| assertThrows(function() { [].concat({ __proto__: p.proxy }); }, TypeError); | |
| target = []; | |
| var p = Proxy.revocable(target, handler); | |
| assertThrows(function() { [].concat(p.proxy); }, TypeError); | |
| })(); |