| // 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); |
| })(); |