Ensure isXYZ methods return boolean values and almost all methods allow falsey arguments.

Former-commit-id: a842eaf2fd262bed03df4a71b560b91801b7a75f
This commit is contained in:
John-David Dalton
2012-08-18 20:52:31 -07:00
parent 07a370fd24
commit 285f0bc6dd
2 changed files with 147 additions and 141 deletions

149
lodash.js
View File

@@ -379,15 +379,15 @@
// iterate own properties using `Object.keys` if it's fast // iterate own properties using `Object.keys` if it's fast
' <% if (isKeysFast && useHas) { %>\n' + ' <% if (isKeysFast && useHas) { %>\n' +
' var ownIndex = -1,\n' + ' var ownIndex = -1,\n' +
' ownProps = nativeKeys(iteratee),\n' + ' ownProps = objectTypes[typeof iteratee] ? nativeKeys(iteratee) : [],\n' +
' length = ownProps.length;\n\n' + ' length = ownProps.length;\n\n' +
' <%= objectBranch.beforeLoop %>;\n' + ' <%= objectBranch.beforeLoop %>;\n' +
' while (++ownIndex < length) {\n' + ' while (++ownIndex < length) {\n' +
' index = ownProps[ownIndex];\n' + ' index = ownProps[ownIndex];\n' +
' if (!(skipProto && index == \'prototype\')) {\n' + ' <% if (!hasDontEnumBug) { %>if (!(skipProto && index == \'prototype\')) {\n <% } %>' +
' value = iteratee[index];\n' + ' value = iteratee[index];\n' +
' <%= objectBranch.inLoop %>\n' + ' <%= objectBranch.inLoop %>\n' +
' }\n' + ' <% if (!hasDontEnumBug) { %>}\n<% } %>' +
' }' + ' }' +
// else using a for-in loop // else using a for-in loop
@@ -946,7 +946,6 @@
*/ */
var shimKeys = createIterator({ var shimKeys = createIterator({
'args': 'object', 'args': 'object',
'exit': 'if (!(object && objectTypes[typeof object])) throw TypeError()',
'init': '[]', 'init': '[]',
'inLoop': 'result.push(index)' 'inLoop': 'result.push(index)'
}); });
@@ -1231,7 +1230,7 @@
* // => true * // => true
*/ */
function has(object, property) { function has(object, property) {
return hasOwnProperty.call(object, property); return object ? hasOwnProperty.call(object, property) : false;
} }
/** /**
@@ -1282,7 +1281,7 @@
* // => true * // => true
*/ */
function isElement(value) { function isElement(value) {
return !!(value && value.nodeType == 1); return value ? value.nodeType == 1 : false;
} }
/** /**
@@ -1547,7 +1546,7 @@
// http://es5.github.com/#x8 // http://es5.github.com/#x8
// and avoid a V8 bug // and avoid a V8 bug
// http://code.google.com/p/v8/issues/detail?id=2291 // http://code.google.com/p/v8/issues/detail?id=2291
return value && objectTypes[typeof value]; return value ? objectTypes[typeof value] : false;
} }
/** /**
@@ -1686,12 +1685,74 @@
* // => ['one', 'two', 'three'] (order is not guaranteed) * // => ['one', 'two', 'three'] (order is not guaranteed)
*/ */
var keys = !nativeKeys ? shimKeys : function(object) { var keys = !nativeKeys ? shimKeys : function(object) {
var type = typeof object;
// avoid iterating over the `prototype` property // avoid iterating over the `prototype` property
return typeof object == 'function' && propertyIsEnumerable.call(object, 'prototype') if (type == 'function' && propertyIsEnumerable.call(object, 'prototype')) {
? shimKeys(object) return shimKeys(object);
: nativeKeys(object); }
return object && objectTypes[type]
? nativeKeys(object)
: [];
}; };
/**
* Merges enumerable properties of the source object(s) into the `destination`
* object. Subsequent sources will overwrite propery assignments of previous
* sources.
*
* @static
* @memberOf _
* @category Objects
* @param {Object} object The destination object.
* @param {Object} [source1, source2, ...] The source objects.
* @param {Object} [indicator] Internally used to indicate that the `stack`
* argument is an array of traversed objects instead of another source object.
* @param {Array} [stack=[]] Internally used to keep track of traversed objects
* to avoid circular references.
* @returns {Object} Returns the destination object.
* @example
*
* var stooges = [
* { 'name': 'moe' },
* { 'name': 'larry' }
* ];
*
* var ages = [
* { 'age': 40 },
* { 'age': 50 }
* ];
*
* _.merge(stooges, ages);
* // => [{ 'name': 'moe', 'age': 40 }, { 'name': 'larry', 'age': 50 }]
*/
var merge = createIterator(extendIteratorOptions, {
'args': 'object, source, indicator, stack',
'top':
'var destValue, found, isArr, stackLength, recursive = indicator == isPlainObject;\n' +
'if (!recursive) stack = [];\n' +
'for (var argsIndex = 1, argsLength = recursive ? 2 : arguments.length; argsIndex < argsLength; argsIndex++) {\n' +
' if (iteratee = arguments[argsIndex]) {',
'inLoop':
'if (value && ((isArr = isArray(value)) || isPlainObject(value))) {\n' +
' found = false; stackLength = stack.length;\n' +
' while (stackLength--) {\n' +
' if (found = stack[stackLength].source == value) break\n' +
' }\n' +
' if (found) {\n' +
' result[index] = stack[stackLength].value\n' +
' } else {\n' +
' destValue = (destValue = result[index]) && isArr\n' +
' ? (isArray(destValue) ? destValue : [])\n' +
' : (isPlainObject(destValue) ? destValue : {});\n' +
' stack.push({ value: destValue, source: value });\n' +
' result[index] = callee(destValue, value, isPlainObject, stack)\n' +
' }\n' +
'} else if (value != null) {\n' +
' result[index] = value\n' +
'}'
});
/** /**
* Creates a shallow clone of `object` composed of the specified properties. * Creates a shallow clone of `object` composed of the specified properties.
* Property names may be specified as individual arguments or as arrays of * Property names may be specified as individual arguments or as arrays of
@@ -1709,11 +1770,14 @@
* // => { 'name': 'moe', 'age': 40 } * // => { 'name': 'moe', 'age': 40 }
*/ */
function pick(object) { function pick(object) {
var result = {};
if (!object) {
return result;
}
var prop, var prop,
index = 0, index = 0,
props = concat.apply(ArrayProto, arguments), props = concat.apply(ArrayProto, arguments),
length = props.length, length = props.length;
result = {};
// start `index` at `1` to skip `object` // start `index` at `1` to skip `object`
while (++index < length) { while (++index < length) {
@@ -2025,63 +2089,6 @@
*/ */
var map = createIterator(baseIteratorOptions, mapIteratorOptions); var map = createIterator(baseIteratorOptions, mapIteratorOptions);
/**
* Merges enumerable properties of the source object(s) into the `destination`
* object. Subsequent sources will overwrite propery assignments of previous
* sources.
*
* @static
* @memberOf _
* @category Objects
* @param {Object} object The destination object.
* @param {Object} [source1, source2, ...] The source objects.
* @param {Object} [indicator] Internally used to indicate that the `stack`
* argument is an array of traversed objects instead of another source object.
* @param {Array} [stack=[]] Internally used to keep track of traversed objects
* to avoid circular references.
* @returns {Object} Returns the destination object.
* @example
*
* var stooges = [
* { 'name': 'moe' },
* { 'name': 'larry' }
* ];
*
* var ages = [
* { 'age': 40 },
* { 'age': 50 }
* ];
*
* _.merge(stooges, ages);
* // => [{ 'name': 'moe', 'age': 40 }, { 'name': 'larry', 'age': 50 }]
*/
var merge = createIterator(extendIteratorOptions, {
'args': 'object, source, indicator, stack',
'top':
'var destValue, found, isArr, stackLength, recursive = indicator == isPlainObject;\n' +
'if (!recursive) stack = [];\n' +
'for (var argsIndex = 1, argsLength = recursive ? 2 : arguments.length; argsIndex < argsLength; argsIndex++) {\n' +
' if (iteratee = arguments[argsIndex]) {',
'inLoop':
'if (value && ((isArr = isArray(value)) || isPlainObject(value))) {\n' +
' found = false; stackLength = stack.length;\n' +
' while (stackLength--) {\n' +
' if (found = stack[stackLength].source == value) break\n' +
' }\n' +
' if (found) {\n' +
' result[index] = stack[stackLength].value\n' +
' } else {\n' +
' destValue = (destValue = result[index]) && isArr\n' +
' ? (isArray(destValue) ? destValue : [])\n' +
' : (isPlainObject(destValue) ? destValue : {});\n' +
' stack.push({ value: destValue, source: value });\n' +
' result[index] = callee(destValue, value, isPlainObject, stack)\n' +
' }\n' +
'} else if (value != null) {\n' +
' result[index] = value\n' +
'}'
});
/** /**
* Retrieves the value of a specified property from all elements in * Retrieves the value of a specified property from all elements in
* the `collection`. * the `collection`.

View File

@@ -817,6 +817,50 @@
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
_.each([
'isArguments',
'isArray',
'isBoolean',
'isDate',
'isElement',
'isEmpty',
'isEqual',
'isFinite',
'isFunction',
'isNaN',
'isNull',
'isNumber',
'isObject',
'isRegExp',
'isString',
'isUndefined'
], function(methodName) {
var func = _[methodName];
QUnit.module('lodash.' + methodName + ' result');
test('should return a boolean', function() {
var expected = 'boolean';
equal(typeof func(arguments), expected);
equal(typeof func([]), expected);
equal(typeof func(true), expected);
equal(typeof func(false), expected);
equal(typeof func(new Date), expected);
equal(typeof func(window.document && document.body), expected);
equal(typeof func({}), expected);
equal(typeof func(undefined), expected);
equal(typeof func(Infinity), expected);
equal(typeof func(_), expected);
equal(typeof func(NaN), expected);
equal(typeof func(null), expected);
equal(typeof func(0), expected);
equal(typeof func({ 'a': 1 }), expected);
equal(typeof func('a'), expected);
});
});
/*--------------------------------------------------------------------------*/
QUnit.module('lodash.keys'); QUnit.module('lodash.keys');
(function() { (function() {
@@ -1489,88 +1533,43 @@
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
QUnit.module('lodash "Arrays" methods'); QUnit.module('lodash methods');
(function() { (function() {
test('should allow a falsey `array` argument', function() { test('should allow a falsey arguments', function() {
_.each([ var funcs = _.without.apply(_, [_.functions(_)].concat([
'compact', '_iteratorTemplate',
'difference', '_shimKeys',
'first', 'after',
'flatten', 'bind',
'groupBy', 'bindAll',
'indexOf', 'compose',
'initial', 'debounce',
'intersection', 'defer',
'last', 'delay',
'lastIndexOf', 'functions',
'max', 'memoize',
'min', 'once',
'range', 'partial',
'rest', 'tap',
'shuffle', 'template',
'sortBy', 'throttle',
'sortedIndex', 'wrap'
'union', ]));
'uniq',
'without', _.each(funcs, function(methodName) {
'zip',
'zipObject'
], function(methodName) {
var func = _[methodName], var func = _[methodName],
pass = true; pass = true;
_.each(falsey, function(value, index) { _.each(falsey, function(value, index) {
try { try {
index ? func() : func(value); index ? func(value) : func();
} catch(e) { } catch(e) {
pass = false; pass = false;
} }
}); });
ok(pass, methodName + ' allows a falsey `array` argument'); ok(pass, methodName + ' allows a falsey arguments');
});
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('lodash "Collections" methods');
(function() {
test('should allow a falsey `collection` argument', function() {
_.each([
'contains',
'every',
'filter',
'find',
'forEach',
'invoke',
'map',
'pluck',
'reduce',
'reduceRight',
'reject',
'some',
'toArray'
], function(methodName) {
var func = _[methodName],
identity = _.identity,
pass = true;
_.each(falsey, function(value, index) {
try {
if (/^(?:contains|toArray)$/.test(methodName)) {
index ? func() : func(value);
} else if (index) {
func(value, identity);
}
} catch(e) {
pass = false;
}
});
ok(pass, methodName + ' allows a falsey `collection` argument');
}); });
}); });
}()); }());