Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 1 | // Copyright 2014 the V8 project authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | (function() { |
| 6 | |
| 7 | assertEquals(1, Array.from.length); |
| 8 | |
| 9 | function assertArrayLikeEquals(value, expected, type) { |
| 10 | assertInstanceof(value, type); |
| 11 | assertEquals(expected.length, value.length); |
| 12 | for (var i=0; i<value.length; ++i) { |
| 13 | assertEquals(expected[i], value[i]); |
| 14 | } |
| 15 | } |
| 16 | |
| 17 | // Assert that constructor is called with "length" for array-like objects |
| 18 | var myCollectionCalled = false; |
| 19 | function MyCollection(length) { |
| 20 | myCollectionCalled = true; |
| 21 | assertEquals(1, arguments.length); |
| 22 | assertEquals(5, length); |
| 23 | } |
| 24 | |
| 25 | Array.from.call(MyCollection, {length: 5}); |
| 26 | assertTrue(myCollectionCalled); |
| 27 | |
| 28 | // Assert that calling mapfn with / without thisArg in sloppy and strict modes |
| 29 | // works as expected. |
| 30 | var global = this; |
| 31 | function non_strict(){ assertEquals(global, this); } |
| 32 | function strict(){ "use strict"; assertEquals(void 0, this); } |
| 33 | function strict_null(){ "use strict"; assertEquals(null, this); } |
| 34 | Array.from([1], non_strict); |
| 35 | Array.from([1], non_strict, void 0); |
| 36 | Array.from([1], non_strict, null); |
| 37 | Array.from([1], strict); |
| 38 | Array.from([1], strict, void 0); |
| 39 | Array.from([1], strict_null, null); |
| 40 | |
| 41 | function testArrayFrom(thisArg, constructor) { |
| 42 | assertArrayLikeEquals(Array.from.call(thisArg, [], undefined), [], |
| 43 | constructor); |
| 44 | assertArrayLikeEquals(Array.from.call(thisArg, NaN), [], constructor); |
| 45 | assertArrayLikeEquals(Array.from.call(thisArg, Infinity), [], constructor); |
| 46 | assertArrayLikeEquals(Array.from.call(thisArg, 10000000), [], constructor); |
| 47 | assertArrayLikeEquals(Array.from.call(thisArg, 'test'), ['t', 'e', 's', 't'], |
| 48 | constructor); |
| 49 | |
| 50 | assertArrayLikeEquals(Array.from.call(thisArg, |
| 51 | { length: 1, '0': { 'foo': 'bar' } }), [{'foo': 'bar'}], constructor); |
| 52 | |
| 53 | assertArrayLikeEquals(Array.from.call(thisArg, |
| 54 | { length: -1, '0': { 'foo': 'bar' } }), [], constructor); |
| 55 | |
| 56 | assertArrayLikeEquals(Array.from.call(thisArg, |
| 57 | [ 'foo', 'bar', 'baz' ]), ['foo', 'bar', 'baz'], constructor); |
| 58 | |
| 59 | var kSet = new Set(['foo', 'bar', 'baz']); |
| 60 | assertArrayLikeEquals(Array.from.call(thisArg, kSet), ['foo', 'bar', 'baz'], |
| 61 | constructor); |
| 62 | |
| 63 | var kMap = new Map(['foo', 'bar', 'baz'].entries()); |
| 64 | assertArrayLikeEquals(Array.from.call(thisArg, kMap), |
| 65 | [[0, 'foo'], [1, 'bar'], [2, 'baz']], constructor); |
| 66 | |
| 67 | |
| 68 | function* generator() { |
| 69 | yield 'a'; |
| 70 | yield 'b'; |
| 71 | yield 'c'; |
| 72 | } |
| 73 | |
| 74 | assertArrayLikeEquals(Array.from.call(thisArg, generator()), |
| 75 | ['a', 'b', 'c'], constructor); |
| 76 | |
| 77 | // Mozilla: |
| 78 | // Array.from on a string handles surrogate pairs correctly. |
| 79 | var gclef = "\uD834\uDD1E"; // U+1D11E MUSICAL SYMBOL G CLEF |
| 80 | assertArrayLikeEquals(Array.from.call(thisArg, gclef), [gclef], constructor); |
| 81 | assertArrayLikeEquals(Array.from.call(thisArg, gclef + " G"), |
| 82 | [gclef, " ", "G"], constructor); |
| 83 | |
| 84 | assertArrayLikeEquals(Array.from.call(thisArg, 'test', function(x) { |
| 85 | return this.filter(x); |
| 86 | }, { |
| 87 | filter: function(x) { return x.toUpperCase(); } |
| 88 | }), ['T', 'E', 'S', 'T'], constructor); |
| 89 | assertArrayLikeEquals(Array.from.call(thisArg, 'test', function(x) { |
| 90 | return x.toUpperCase(); |
| 91 | }), ['T', 'E', 'S', 'T'], constructor); |
| 92 | |
| 93 | assertThrows(function() { Array.from.call(thisArg, null); }, TypeError); |
| 94 | assertThrows(function() { Array.from.call(thisArg, undefined); }, TypeError); |
| 95 | assertThrows(function() { Array.from.call(thisArg, [], null); }, TypeError); |
| 96 | assertThrows(function() { Array.from.call(thisArg, [], "noncallable"); }, |
| 97 | TypeError); |
| 98 | |
| 99 | var nullIterator = {}; |
| 100 | nullIterator[Symbol.iterator] = null; |
| 101 | assertArrayLikeEquals(Array.from.call(thisArg, nullIterator), [], |
| 102 | constructor); |
| 103 | |
| 104 | var nonObjIterator = {}; |
| 105 | nonObjIterator[Symbol.iterator] = function() { return "nonObject"; }; |
| 106 | assertThrows(function() { Array.from.call(thisArg, nonObjIterator); }, |
| 107 | TypeError); |
| 108 | |
| 109 | assertThrows(function() { Array.from.call(thisArg, [], null); }, TypeError); |
| 110 | |
| 111 | // Ensure iterator is only accessed once, and only invoked once |
| 112 | var called = false; |
| 113 | var arr = [1, 2, 3]; |
| 114 | var obj = {}; |
| 115 | |
| 116 | // Test order --- only get iterator method once |
| 117 | function testIterator() { |
| 118 | assertFalse(called, "@@iterator should be called only once"); |
| 119 | called = true; |
| 120 | assertEquals(obj, this); |
| 121 | return arr[Symbol.iterator](); |
| 122 | } |
| 123 | var getCalled = false; |
| 124 | Object.defineProperty(obj, Symbol.iterator, { |
| 125 | get: function() { |
| 126 | assertFalse(getCalled, "@@iterator should be accessed only once"); |
| 127 | getCalled = true; |
| 128 | return testIterator; |
| 129 | }, |
| 130 | set: function() { |
| 131 | assertUnreachable("@@iterator should not be set"); |
| 132 | } |
| 133 | }); |
| 134 | assertArrayLikeEquals(Array.from.call(thisArg, obj), [1, 2, 3], constructor); |
| 135 | } |
| 136 | |
| 137 | function Other() {} |
| 138 | |
| 139 | var boundFn = (function() {}).bind(Array, 27); |
| 140 | |
| 141 | testArrayFrom(Array, Array); |
| 142 | testArrayFrom(null, Array); |
| 143 | testArrayFrom({}, Array); |
| 144 | testArrayFrom(Object, Object); |
| 145 | testArrayFrom(Other, Other); |
| 146 | testArrayFrom(Math.cos, Array); |
| 147 | testArrayFrom(Math.cos.bind(Math), Array); |
| 148 | testArrayFrom(boundFn, boundFn); |
| 149 | |
| 150 | // Assert that [[DefineOwnProperty]] is used in ArrayFrom, meaning a |
| 151 | // setter isn't called, and a failed [[DefineOwnProperty]] will throw. |
| 152 | var setterCalled = 0; |
| 153 | function exotic() { |
| 154 | Object.defineProperty(this, '0', { |
| 155 | get: function() { return 2; }, |
| 156 | set: function() { setterCalled++; } |
| 157 | }); |
| 158 | } |
| 159 | // Non-configurable properties can't be overwritten with DefineOwnProperty |
| 160 | assertThrows(function () { Array.from.call(exotic, [1]); }, TypeError); |
| 161 | // The setter wasn't called |
| 162 | assertEquals(0, setterCalled); |
| 163 | |
| 164 | // Check that array properties defined are writable, enumerable, configurable |
| 165 | function ordinary() { } |
| 166 | var x = Array.from.call(ordinary, [2]); |
| 167 | var xlength = Object.getOwnPropertyDescriptor(x, 'length'); |
| 168 | assertEquals(1, xlength.value); |
| 169 | assertEquals(true, xlength.writable); |
| 170 | assertEquals(true, xlength.enumerable); |
| 171 | assertEquals(true, xlength.configurable); |
| 172 | var x0 = Object.getOwnPropertyDescriptor(x, 0); |
| 173 | assertEquals(2, x0.value); |
| 174 | assertEquals(true, xlength.writable); |
| 175 | assertEquals(true, xlength.enumerable); |
| 176 | assertEquals(true, xlength.configurable); |
| 177 | |
| 178 | })(); |