From 159db813779761dc9a83b96b304a26a4f9c39809 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Dopazo Date: Tue, 9 Dec 2014 17:28:59 -0800 Subject: [PATCH 1/5] Add test262 tests --- tests/test262.js | 348 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 348 insertions(+) create mode 100644 tests/test262.js diff --git a/tests/test262.js b/tests/test262.js new file mode 100644 index 0000000..fea2a8f --- /dev/null +++ b/tests/test262.js @@ -0,0 +1,348 @@ +var chai = require('chai'); +require('../index'); + +var globalThis = global; + +describe('Test 262: Array.prototype.find', function () { + function runTestCase(fn) { + chai.assert(fn(), 'Test did not pass'); + } + + function $ERROR(message) { + chai.assert(false, message); + } + + var assert = { + sameValue: function (actual, expected, message) { + chai.assert.strictEqual(actual, expected, message); + }, + notSameValue: function (actual, expected, message) { + chai.assert.notStrictEqual(actual, expected, message); + } + }; + + var supportsGetters = (function () { + var value = 2; + var obj = {}; + + if (!Object.defineProperty) { + return false; + } + Object.defineProperty(obj, 'foo', { + get: function () { + return value * 2; + } + }); + value = 4; + + return obj.foo === 8; + }()); + + /* + Test Array.prototype.find_callable-predicate ignored: Uses Proxy and arrow functions + */ + specify('Array.prototype.find_empty-array-undefined', function () { + // Copyright (c) 2014 Matthew Meyers. All rights reserved. + // This code is governed by the BSD license found in the LICENSE file. + + /*--- + description: Find on empty array should return undefined + ---*/ + + var a = [].find(function () { + return true; + }); + + if (a !== undefined) { + $ERROR('#1: a !== undefined. Actual: ' + typeof a); + } + + }); + specify('Array.prototype.find_length-property', function () { + // Copyright (c) 2014 Matthew Meyers. All rights reserved. + // This code is governed by the BSD license found in the LICENSE file. + + /*--- + description: The length property of the find method is 1 + ---*/ + + if ([].find.length !== 1) { + $ERROR('1: [].find.length !== 1. Actual: ' + [].find.length); + } + + }); + specify('Array.prototype.find_modify-after-start', function () { + // Copyright (c) 2014 Matthew Meyers. All rights reserved. + // This code is governed by the BSD license found in the LICENSE file. + + /*--- + description: Array may be mutated by calls to the predicate + ---*/ + + [1, 2, 3].find(function (v, i, arr) { + arr[i + 1] = arr[i + 1] + 1; + switch (i) { + case 0: + if (arr[i] !== 1) { + $ERROR('#1: arr[0] !== 1. Actual: ' + arr[i]); + } + break; + case 1: + if (arr[i] !== 3) { + $ERROR('#2: arr[1] !== 3. Actual: ' + arr[i]); + } + break; + case 2: + if (arr[i] !== 4) { + $ERROR('#3: arr[1] !== 4. Actual: ' + arr[i]); + } + break; + } + }); + + }); + specify('Array.prototype.find_non-returning-predicate', function () { + // Copyright (c) 2014 Matthew Meyers. All rights reserved. + // This code is governed by the BSD license found in the LICENSE file. + + /*--- + description: Find with a predicate with no return value should return undefined + ---*/ + + var a = [1, 2, 3].find(function () {}); + + if (a !== undefined) { + $ERROR('#1: a !== undefined. Actual: ' + typeof a); + } + + }); + specify('Array.prototype.find_noncallable-predicate', function () { + // Copyright (c) 2014 Matthew Meyers. All rights reserved. + // This code is governed by the BSD license found in the LICENSE file. + + /*--- + description: > + Array.prototype.find should throw a TypeError if + IsCallable(predicate) is false + includes: [runTestCase.js] + ---*/ + + var uncallableValues = [ + undefined, + null, + true, + this, + {}, + 'string', + 0 + ]; + + function testcase() { + for (var i = 0, len = uncallableValues.length; i < len; i++) { + try { + [].find(uncallableValues[i]); + return false; + } catch (e) { + if (!(e instanceof TypeError)) { + return false; + } + } + } + return true; + } + + runTestCase(testcase); + + }); + specify('Array.prototype.find_predicate-arguments', function () { + // Copyright (c) 2014 Matthew Meyers. All rights reserved. + // This code is governed by the BSD license found in the LICENSE file. + + /*--- + description: > + predicate is called with three arguments: the value of the + element, the index of the element, and the object being traversed. + ---*/ + + var a = [1]; + + var b = a.find(function (v, i, arr) { + if (arguments.length !== 3) { + $ERROR('#1: arguments.length !== 3. Actual: ' + arguments.length); + } + if (v !== 1) { + $ERROR('#2: element value !== 1. Actual: ' + v); + } + if (i !== 0) { + $ERROR('#3: index !== 0. Actual: ' + i); + } + if (arr !== a) { + $ERROR('#4: object being traversed !== a'); + } + }); + + }); + specify('Array.prototype.find_push-after-start', function () { + // Copyright (c) 2014 Matthew Meyers. All rights reserved. + // This code is governed by the BSD license found in the LICENSE file. + + /*--- + description: > + Elements added to array after find has been called should not be + visited + ---*/ + + [1].find(function (v, i, arr) { + arr.push('string'); + if (v === 'string') { + $ERROR('#' + i + ': \'string\' should not be visited'); + } + }); + + }); + specify('Array.prototype.find_remove-after-start', function () { + // Copyright (c) 2014 Matthew Meyers. All rights reserved. + // This code is governed by the BSD license found in the LICENSE file. + + /*--- + description: > + Elements removed from array after find has been called should not + be visited + ---*/ + + [1, 'string', 2].find(function (v, i, arr) { + var stringIndex = arr.indexOf('string'); + if (stringIndex !== -1) delete arr[stringIndex]; + if (v === 'string') { + $ERROR('#1: \'string\' should not exist, it has been deleted'); + } + if (v === undefined) { + $ERROR('#2: deleted element should not be visited'); + } + }); + + [1, 'string', 2].find(function (v, i, arr) { + var stringIndex = arr.indexOf('string'); + if (stringIndex !== -1) arr.splice(stringIndex, 1); + if (v === 'string') { + $ERROR('#3: \'string\' should not exist, it has been deleted'); + } + if (v === undefined) { + $ERROR('#4: deleted element should not be visited'); + } + }); + + }); + specify('Array.prototype.find_return-found-value', function () { + // Copyright (c) 2014 Matthew Meyers. All rights reserved. + // This code is governed by the BSD license found in the LICENSE file. + + /*--- + description: Find should return value if predicate returns true + ---*/ + + var testVals = [ + undefined, + null, + 0, + 'string', + String, + this, + true, + [1,2,3] + ]; + + var a; + + for (var i = 0, len = testVals.length; i < len; i++) { + a = testVals.find(function (v) { + return v === testVals[i]; + }); + if (a !== testVals[i]) { + $ERROR('#' + (i + 1) + ': a !== testVals[' + i + ']. Actual: ' + a); + } + } + + }); + specify('Array.prototype.find_skip-empty', function () { + // Copyright (c) 2014 Matthew Meyers. All rights reserved. + // This code is governed by the BSD license found in the LICENSE file. + + /*--- + description: > + predicate is called only for elements of the array which actually + exist; it is not called for missing elements of the array + ---*/ + + var a = []; + + a[10] = 1; + a[11] = 2; + + var b = a.find(function (v) { + return v !== 1; + }); + + if (b !== 2) { + $ERROR('#1: b !== 2. Actual: ' + b); + } + + }); + specify('Array.prototype.find_this-defined', function () { + // Copyright (c) 2014 Matthew Meyers. All rights reserved. + // This code is governed by the BSD license found in the LICENSE file. + + /*--- + description: thisArg should be bound to this if provided + ---*/ + + [1].find(function () { + assert.sameValue(this, Array, 'this should equal Array'); + assert.notSameValue(this, globalThis, 'this should not equal globalThis'); + }, Array); + + }); + specify('Array.prototype.find_this-is-object', function () { + // Copyright (c) 2014 Matthew Meyers. All rights reserved. + // This code is governed by the BSD license found in the LICENSE file. + + /*--- + description: Array.prototype.find should convert thisArg into an object + ---*/ + + var dataTypes = [ + undefined, + null, + true, + this, + {}, + 'string', + 0, + function () {} + ] + + for (var i = 0, len = dataTypes.length; i < len; i++) { + [1].find(function () { + if (!(this instanceof Object)) { + $ERROR('#' + i + ': !(this instanceof Object). Actual: ' + typeof this); + } + }, dataTypes[i]) + } + + }); + specify('Array.prototype.find_this-undefined', function () { + // Copyright (c) 2014 Matthew Meyers. All rights reserved. + // This code is governed by the BSD license found in the LICENSE file. + + /*--- + description: thisArg should be undefined if not provided + ---*/ + + [1].find(function () { + if (this !== globalThis) { + $ERROR('#1: this !== globalThis'); + } + }); + + }); +}); From 61c98c38784225a1c38ed319722cbcfd0ff22394 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Dopazo Date: Tue, 9 Dec 2014 17:53:32 -0800 Subject: [PATCH 2/5] Fix based on the latest ES6 text --- index.js | 11 ++++++++--- tests/test.js | 6 +++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index 22f6bea..4f83cbf 100644 --- a/index.js +++ b/index.js @@ -6,15 +6,20 @@ var find = function(predicate) { var list = Object(this); - var length = list.length < 0 ? 0 : list.length >>> 0; // ES.ToUint32; - if (length === 0) return undefined; + // Lose implementation of ToLength: + // * It does not deal with negative numbers correctly, but it does not + // matter because it will iterate between 0 and length + // * It only casts to Integer (with |0) after making sure it has the maximum + // integer or |0 will cast Infinity to 0 + var length = Math.min(Number(length), 9007199254740991) | 0; if (typeof predicate !== 'function' || Object.prototype.toString.call(predicate) !== '[object Function]') { throw new TypeError('Array#find: predicate must be a function'); } + if (length === 0) return undefined; var thisArg = arguments[1]; for (var i = 0, value; i < length; i++) { value = list[i]; - if (predicate.call(thisArg, value, i, list)) return value; + if (i in list && predicate.call(thisArg, value, i, list)) return value; } return undefined; }; diff --git a/tests/test.js b/tests/test.js index d2d1744..3ad0358 100644 --- a/tests/test.js +++ b/tests/test.js @@ -64,7 +64,7 @@ var runTests = function () { return false; }); expect(found).to.equal(undefined); - expect(seen).to.eql([[0, 1], [1, undefined], [2, undefined]]); + expect(seen).to.eql([[0, 1], [2, undefined]]); }); it('should work with a sparse array-like object', function() { @@ -75,7 +75,7 @@ var runTests = function () { return false; }); expect(found).to.equal(undefined); - expect(seen).to.eql([[0, 1], [1, undefined], [2, undefined]]); + expect(seen).to.eql([[0, 1], [2, undefined]]); }); }); }); @@ -88,4 +88,4 @@ describe('polluted Object.prototype', function() { Object.prototype[1] = 42; runTests(); delete Object.prototype[1]; -}); \ No newline at end of file +}); From 454d02825ccd45533b9e0fc01d7f508a17db7469 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Dopazo Date: Tue, 9 Dec 2014 18:18:59 -0800 Subject: [PATCH 3/5] Use Number.MAX_SAFE_INTEGER when available Also fix a typo and remove an unnecessary part of the tests. --- index.js | 6 ++++-- tests/test262.js | 17 ----------------- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/index.js b/index.js index 4f83cbf..9cb6449 100644 --- a/index.js +++ b/index.js @@ -4,14 +4,16 @@ (function(globals){ if (Array.prototype.find) return; + var maxInteger = Number.MAX_SAFE_INTEGER || (Math.pow(2, 53) - 1); + var find = function(predicate) { var list = Object(this); - // Lose implementation of ToLength: + // Loose implementation of ToLength: // * It does not deal with negative numbers correctly, but it does not // matter because it will iterate between 0 and length // * It only casts to Integer (with |0) after making sure it has the maximum // integer or |0 will cast Infinity to 0 - var length = Math.min(Number(length), 9007199254740991) | 0; + var length = Math.min(Number(length), maxInteger) | 0; if (typeof predicate !== 'function' || Object.prototype.toString.call(predicate) !== '[object Function]') { throw new TypeError('Array#find: predicate must be a function'); } diff --git a/tests/test262.js b/tests/test262.js index fea2a8f..2a3381e 100644 --- a/tests/test262.js +++ b/tests/test262.js @@ -21,23 +21,6 @@ describe('Test 262: Array.prototype.find', function () { } }; - var supportsGetters = (function () { - var value = 2; - var obj = {}; - - if (!Object.defineProperty) { - return false; - } - Object.defineProperty(obj, 'foo', { - get: function () { - return value * 2; - } - }); - value = 4; - - return obj.foo === 8; - }()); - /* Test Array.prototype.find_callable-predicate ignored: Uses Proxy and arrow functions */ From cbbd75d20f139614ac90001a045c7fa3d911132a Mon Sep 17 00:00:00 2001 From: Juan Ignacio Dopazo Date: Tue, 9 Dec 2014 18:23:47 -0800 Subject: [PATCH 4/5] Do not skip holes --- index.js | 4 ++-- tests/test.js | 4 ++-- tests/test262.js | 57 ------------------------------------------------ 3 files changed, 4 insertions(+), 61 deletions(-) diff --git a/index.js b/index.js index 9cb6449..4ce90b5 100644 --- a/index.js +++ b/index.js @@ -13,7 +13,7 @@ // matter because it will iterate between 0 and length // * It only casts to Integer (with |0) after making sure it has the maximum // integer or |0 will cast Infinity to 0 - var length = Math.min(Number(length), maxInteger) | 0; + var length = Math.min(Number(list.length), maxInteger) | 0; if (typeof predicate !== 'function' || Object.prototype.toString.call(predicate) !== '[object Function]') { throw new TypeError('Array#find: predicate must be a function'); } @@ -21,7 +21,7 @@ var thisArg = arguments[1]; for (var i = 0, value; i < length; i++) { value = list[i]; - if (i in list && predicate.call(thisArg, value, i, list)) return value; + if (predicate.call(thisArg, value, i, list)) return value; } return undefined; }; diff --git a/tests/test.js b/tests/test.js index 3ad0358..03cea7e 100644 --- a/tests/test.js +++ b/tests/test.js @@ -64,7 +64,7 @@ var runTests = function () { return false; }); expect(found).to.equal(undefined); - expect(seen).to.eql([[0, 1], [2, undefined]]); + expect(seen).to.eql([[0, 1], [1, undefined], [2, undefined]]); }); it('should work with a sparse array-like object', function() { @@ -75,7 +75,7 @@ var runTests = function () { return false; }); expect(found).to.equal(undefined); - expect(seen).to.eql([[0, 1], [2, undefined]]); + expect(seen).to.eql([[0, 1], [1, undefined], [2, undefined]]); }); }); }); diff --git a/tests/test262.js b/tests/test262.js index 2a3381e..2e4c42d 100644 --- a/tests/test262.js +++ b/tests/test262.js @@ -182,39 +182,6 @@ describe('Test 262: Array.prototype.find', function () { } }); - }); - specify('Array.prototype.find_remove-after-start', function () { - // Copyright (c) 2014 Matthew Meyers. All rights reserved. - // This code is governed by the BSD license found in the LICENSE file. - - /*--- - description: > - Elements removed from array after find has been called should not - be visited - ---*/ - - [1, 'string', 2].find(function (v, i, arr) { - var stringIndex = arr.indexOf('string'); - if (stringIndex !== -1) delete arr[stringIndex]; - if (v === 'string') { - $ERROR('#1: \'string\' should not exist, it has been deleted'); - } - if (v === undefined) { - $ERROR('#2: deleted element should not be visited'); - } - }); - - [1, 'string', 2].find(function (v, i, arr) { - var stringIndex = arr.indexOf('string'); - if (stringIndex !== -1) arr.splice(stringIndex, 1); - if (v === 'string') { - $ERROR('#3: \'string\' should not exist, it has been deleted'); - } - if (v === undefined) { - $ERROR('#4: deleted element should not be visited'); - } - }); - }); specify('Array.prototype.find_return-found-value', function () { // Copyright (c) 2014 Matthew Meyers. All rights reserved. @@ -246,30 +213,6 @@ describe('Test 262: Array.prototype.find', function () { } } - }); - specify('Array.prototype.find_skip-empty', function () { - // Copyright (c) 2014 Matthew Meyers. All rights reserved. - // This code is governed by the BSD license found in the LICENSE file. - - /*--- - description: > - predicate is called only for elements of the array which actually - exist; it is not called for missing elements of the array - ---*/ - - var a = []; - - a[10] = 1; - a[11] = 2; - - var b = a.find(function (v) { - return v !== 1; - }); - - if (b !== 2) { - $ERROR('#1: b !== 2. Actual: ' + b); - } - }); specify('Array.prototype.find_this-defined', function () { // Copyright (c) 2014 Matthew Meyers. All rights reserved. From b8d57d019a686854d4ceb9f3a3418e56f7e6a62e Mon Sep 17 00:00:00 2001 From: Juan Ignacio Dopazo Date: Tue, 9 Dec 2014 18:51:44 -0800 Subject: [PATCH 5/5] Exit early on negative lengths --- index.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/index.js b/index.js index 4ce90b5..2fba7ea 100644 --- a/index.js +++ b/index.js @@ -17,13 +17,12 @@ if (typeof predicate !== 'function' || Object.prototype.toString.call(predicate) !== '[object Function]') { throw new TypeError('Array#find: predicate must be a function'); } - if (length === 0) return undefined; + if (length <= 0) return; var thisArg = arguments[1]; for (var i = 0, value; i < length; i++) { value = list[i]; if (predicate.call(thisArg, value, i, list)) return value; } - return undefined; }; if (Object.defineProperty) {