From 2f3afd36377c84460f41b1f33a1aa2503a6552ed Mon Sep 17 00:00:00 2001 From: John-David Dalton Date: Thu, 6 Nov 2014 23:39:39 -0800 Subject: [PATCH] Add `_.propertyOf`. --- lodash.js | 43 +++++++++++--- test/test.js | 163 +++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 159 insertions(+), 47 deletions(-) diff --git a/lodash.js b/lodash.js index 5042a810d..cbabbc8be 100644 --- a/lodash.js +++ b/lodash.js @@ -1011,12 +1011,12 @@ * `initial`, `intersection`, `invert`, `invoke`, `keys`, `keysIn`, `map`, * `mapValues`, `matches`, `memoize`, `merge`, `mixin`, `negate`, `noop`, * `omit`, `once`, `pairs`, `partial`, `partialRight`, `partition`, `pick`, - * `pluck`, `property`, `pull`, `pullAt`, `push`, `range`, `rearg`, `reject`, - * `remove`, `rest`, `reverse`, `shuffle`, `slice`, `sort`, `sortBy`, `splice`, - * `take`, `takeRight`, `takeRightWhile`, `takeWhile`, `tap`, `throttle`, - * `thru`, `times`, `toArray`, `transform`, `union`, `uniq`, `unshift`, - * `unzip`, `values`, `valuesIn`, `where`, `without`, `wrap`, `xor`, `zip`, - * and `zipObject` + * `pluck`, `property`, `propertyOf`, `pull`, `pullAt`, `push`, `range`, + * `rearg`, `reject`, `remove`, `rest`, `reverse`, `shuffle`, `slice`, `sort`, + * `sortBy`, `splice`, `take`, `takeRight`, `takeRightWhile`, `takeWhile`, + * `tap`, `throttle`, `thru`, `times`, `toArray`, `transform`, `union`, `uniq`, + * `unshift`, `unzip`, `values`, `valuesIn`, `where`, `without`, `wrap`, `xor`, + * `zip`, and `zipObject` * * The non-chainable wrapper functions are: * `attempt`, `camelCase`, `capitalize`, `clone`, `cloneDeep`, `contains`, @@ -9561,8 +9561,8 @@ } /** - * Creates a "_.pluck" style function which returns the `key` value of a - * given object. + * Creates a "_.pluck" style function which returns the value associated with + * the `key` of a given object. * * @static * @memberOf _ @@ -9585,11 +9585,37 @@ * // => [{ 'user': 'barney', 'age': 36 }, { 'user': 'fred', 'age': 40 }] */ function property(key) { + key = String(key); return function(object) { return object == null ? undefined : object[key]; }; } + /** + * The inverse of `_.property`; this method creates a function which returns + * the value associated with a given key of `object`. + * + * @static + * @memberOf _ + * @category Utility + * @param {Object} object The object to inspect. + * @returns {Function} Returns the new function. + * @example + * + * var fred = { 'user': 'fred', 'age': 40, 'active': true }; + * _.map(['age', 'active', _.propertyOf(fred)); + * // => [40, true] + * + * var object = { 'a': 3, 'b': 1, 'c': 2 }; + * _.sortBy(['a', 'b', 'c'], _.propertyOf(object)); + * // => ['b', 'c', 'a'] + */ + function propertyOf(object) { + return function(key) { + return object == null ? undefined : object[key]; + }; + } + /** * Produces a random number between `min` and `max` (inclusive). If only one * argument is provided a number between `0` and the given number is returned. @@ -9887,6 +9913,7 @@ lodash.pick = pick; lodash.pluck = pluck; lodash.property = property; + lodash.propertyOf = propertyOf; lodash.pull = pull; lodash.pullAt = pullAt; lodash.range = range; diff --git a/test/test.js b/test/test.js index e09a4883e..0bca330ff 100644 --- a/test/test.js +++ b/test/test.js @@ -928,17 +928,22 @@ QUnit.module('lodash.at'); (function() { - var args = arguments; + var args = arguments, + array = ['a', 'b', 'c']; + + array['1.1'] = array['-1'] = 1; + + test('should return the elements corresponding to the specified keys', 1, function() { + var actual = _.at(array, [0, 2]); + deepEqual(actual, ['a', 'c']); + }); test('should return `undefined` for nonexistent keys', 1, function() { - var actual = _.at(['a', 'b', 'c'], [2, 4, 0]); + var actual = _.at(array, [2, 4, 0]); deepEqual(actual, ['c', undefined, 'a']); }); test('should use `undefined` for non-index keys on array-like values', 1, function() { - var array = ['a', 'b', 'c']; - array['1.1'] = array['-1'] = 1; - var values = _.reject(empties, function(value) { return value === 0 || _.isArray(value); }).concat(-1, 1.1); @@ -949,8 +954,9 @@ deepEqual(actual, expected); }); - test('should return an empty array when no keys are provided', 1, function() { - deepEqual(_.at(['a', 'b', 'c']), []); + test('should return an empty array when no keys are provided', 2, function() { + deepEqual(_.at(array), []); + deepEqual(_.at(array, [], []), []); }); test('should accept multiple key arguments', 1, function() { @@ -958,7 +964,7 @@ deepEqual(actual, ['d', 'a', 'c']); }); - test('should work with a falsey `array` argument when keys are provided', 1, function() { + test('should work with a falsey `collection` argument when keys are provided', 1, function() { var expected = _.map(falsey, _.constant([undefined, undefined])); var actual = _.map(falsey, function(value) { @@ -972,7 +978,12 @@ test('should work with an `arguments` object for `collection`', 1, function() { var actual = _.at(args, [2, 0]); - deepEqual(actual, ['c', 'a']); + deepEqual(actual, [3, 1]); + }); + + test('should work with `arguments` object as secondary arguments', 1, function() { + var actual = _.at([1, 2, 3, 4, 5], args); + deepEqual(actual, [2, 3, 4]); }); test('should work with an object for `collection`', 1, function() { @@ -980,6 +991,14 @@ deepEqual(actual, [3, 1]); }); + test('should pluck inherited property values', 1, function() { + function Foo() { this.a = 1; } + Foo.prototype.b = 2; + + var actual = _.at(new Foo, 'b'); + deepEqual(actual, [2]); + }); + _.each({ 'literal': 'abc', 'object': Object('abc') @@ -989,7 +1008,7 @@ deepEqual(_.at(collection, [2, 0]), ['c', 'a']); }); }); - }('a', 'b', 'c')); + }(1, 2, 3)); /*--------------------------------------------------------------------------*/ @@ -4922,7 +4941,7 @@ test('should include inherited functions', 1, function() { function Foo() { this.a = _.identity; - this.b = 'b' + this.b = 'b'; } Foo.prototype.c = _.noop; deepEqual(_.functions(new Foo).sort(), ['a', 'c']); @@ -5388,6 +5407,7 @@ test('should only add multiple values to own, not inherited, properties', 2, function() { var object = { 'a': 'hasOwnProperty', 'b': 'constructor' }; + deepEqual(_.invert(object), { 'hasOwnProperty': 'a', 'constructor': 'b' }); ok(_.isEqual(_.invert(object, true), { 'hasOwnProperty': ['a'], 'constructor': ['b'] })); }); @@ -7298,13 +7318,10 @@ }); test('`_.' + methodName + '` should ' + (isKeys ? 'not' : '') + ' include inherited properties', 1, function() { - function Foo() { - this.a = 1; - this.b = 2; - } - Foo.prototype.c = 3; + function Foo() { this.a = 1; } + Foo.prototype.b = 2; - var expected = isKeys ? ['a', 'b'] : ['a', 'b', 'c']; + var expected = isKeys ? ['a'] : ['a', 'b']; deepEqual(func(new Foo).sort(), expected); }); }); @@ -7776,14 +7793,12 @@ }); test('should not match by inherited `source` properties', 1, function() { - function Foo() {} - Foo.prototype = { 'b': 2 }; - - var source = new Foo; - source.a = 1; + function Foo() { this.a = 1; } + Foo.prototype.b = 2; var objects = [{ 'a': 1 }, { 'a': 1, 'b': 2 }], expected = _.map(objects, _.constant(true)), + source = new Foo, matches = _.matches(source), actual = _.map(objects, matches); @@ -8330,7 +8345,7 @@ test('should not assign inherited `source` properties', 1, function() { function Foo() {} - Foo.prototype = { 'a': _.noop }; + Foo.prototype.a = _.noop; deepEqual(_.mixin({}, new Foo, {}), {}); }); @@ -9286,10 +9301,15 @@ (function() { test('should return an array of property values from each element of a collection', 1, function() { - var objects = [{ 'name': 'barney', 'age': 36 }, { 'name': 'fred', 'age': 40 }], - actual = _.pluck(objects, 'name'); + var objects = [{ 'name': 'barney', 'age': 36 }, { 'name': 'fred', 'age': 40 }]; + deepEqual(_.pluck(objects, 'name'), ['barney', 'fred']); + }); - deepEqual(actual, ['barney', 'fred']); + test('should pluck inherited property values', 1, function() { + function Foo() { this.a = 1; } + Foo.prototype.b = 2; + + deepEqual(_.pluck([new Foo], 'b'), [2]); }); test('should work with an object for `collection`', 1, function() { @@ -9324,20 +9344,88 @@ (function() { test('should create a function that plucks a property value of a given object', 3, function() { var object = { 'a': 1, 'b': 2 }, - property = _.property('a'); + prop = _.property('a'); - strictEqual(property.length, 1); - strictEqual(property(object), 1); + strictEqual(prop.length, 1); + strictEqual(prop(object), 1); - property = _.property('b'); - strictEqual(property(object), 2); + prop = _.property('b'); + strictEqual(prop(object), 2); }); test('should work with non-string `prop` arguments', 1, function() { - var array = [1, 2, 3], - property = _.property(1); + var prop = _.property(1); + strictEqual(prop([1, 2, 3]), 2); + }); - strictEqual(property(array), 2); + test('should coerce key to a string', 1, function() { + function fn() {} + fn.toString = _.constant('fn'); + + var objects = [{ 'null': 1 }, { 'undefined': 2 }, { 'fn': 3 }, { '[object Object]': 4 }], + values = [null, undefined, fn, {}] + + var actual = _.map(objects, function(object, index) { + var prop = _.property(values[index]); + return prop(object); + }); + + deepEqual(actual, [1, 2, 3, 4]); + }); + + test('should pluck inherited property values', 1, function() { + function Foo() { this.a = 1; } + Foo.prototype.b = 2; + + var prop = _.property('b'); + strictEqual(prop(new Foo), 2); + }); + + test('should work when `object` is nullish', 1, function() { + var values = [, null, undefined], + expected = _.map(values, _.constant(undefined)); + + var actual = _.map(values, function(value, index) { + var prop = _.property('a'); + return index ? prop(value) : prop(); + }); + + deepEqual(actual, expected); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('lodash.propertyOf'); + + (function() { + test('should create a function that plucks a property value of a given key', 3, function() { + var object = { 'a': 1, 'b': 2 }, + propOf = _.propertyOf(object); + + strictEqual(propOf.length, 1); + strictEqual(propOf('a'), 1); + strictEqual(propOf('b'), 2); + }); + + test('should pluck inherited property values', 1, function() { + function Foo() { this.a = 1; } + Foo.prototype.b = 2; + + var propOf = _.propertyOf(new Foo); + strictEqual(propOf('b'), 2); + }); + + test('should work when `object` is nullish', 1, function() { + var values = [, null, undefined], + expected = _.map(values, _.constant(undefined)); + + var actual = _.map(values, function(value, index) { + var propOf = index ? _.propertyOf(value) : _.propertyOf(); + return propOf('a'); + }); + + deepEqual(actual, expected); }); }()); @@ -13141,14 +13229,11 @@ var args = arguments, array = [1, 2, 3, 4, 5, 6]; - test('should work with `arguments` objects', 31, function() { + test('should work with `arguments` objects', 29, function() { function message(methodName) { return '`_.' + methodName + '` should work with `arguments` objects'; } - deepEqual(_.at(args, 0, 4), [1, 5], message('at')); - deepEqual(_.at(array, args), [2, undefined, 4, undefined, 6], '_.at should work with `arguments` objects as secondary arguments'); - deepEqual(_.difference(args, [null]), [1, [3], 5], message('difference')); deepEqual(_.difference(array, args), [2, 3, 4, 6], '_.difference should work with `arguments` objects as secondary arguments'); @@ -13319,7 +13404,7 @@ var acceptFalsey = _.difference(allMethods, rejectFalsey); - test('should accept falsey arguments', 198, function() { + test('should accept falsey arguments', 199, function() { var emptyArrays = _.map(falsey, _.constant([])), isExposed = '_' in root, oldDash = root._;