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
' <% if (isKeysFast && useHas) { %>\n' +
' var ownIndex = -1,\n' +
' ownProps = nativeKeys(iteratee),\n' +
' ownProps = objectTypes[typeof iteratee] ? nativeKeys(iteratee) : [],\n' +
' length = ownProps.length;\n\n' +
' <%= objectBranch.beforeLoop %>;\n' +
' while (++ownIndex < length) {\n' +
' index = ownProps[ownIndex];\n' +
' if (!(skipProto && index == \'prototype\')) {\n' +
' value = iteratee[index];\n' +
' <%= objectBranch.inLoop %>\n' +
' }\n' +
' <% if (!hasDontEnumBug) { %>if (!(skipProto && index == \'prototype\')) {\n <% } %>' +
' value = iteratee[index];\n' +
' <%= objectBranch.inLoop %>\n' +
' <% if (!hasDontEnumBug) { %>}\n<% } %>' +
' }' +
// else using a for-in loop
@@ -946,7 +946,6 @@
*/
var shimKeys = createIterator({
'args': 'object',
'exit': 'if (!(object && objectTypes[typeof object])) throw TypeError()',
'init': '[]',
'inLoop': 'result.push(index)'
});
@@ -1231,7 +1230,7 @@
* // => true
*/
function has(object, property) {
return hasOwnProperty.call(object, property);
return object ? hasOwnProperty.call(object, property) : false;
}
/**
@@ -1282,7 +1281,7 @@
* // => true
*/
function isElement(value) {
return !!(value && value.nodeType == 1);
return value ? value.nodeType == 1 : false;
}
/**
@@ -1547,7 +1546,7 @@
// http://es5.github.com/#x8
// and avoid a V8 bug
// 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)
*/
var keys = !nativeKeys ? shimKeys : function(object) {
var type = typeof object;
// avoid iterating over the `prototype` property
return typeof object == 'function' && propertyIsEnumerable.call(object, 'prototype')
? shimKeys(object)
: nativeKeys(object);
if (type == 'function' && propertyIsEnumerable.call(object, 'prototype')) {
return shimKeys(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.
* Property names may be specified as individual arguments or as arrays of
@@ -1709,11 +1770,14 @@
* // => { 'name': 'moe', 'age': 40 }
*/
function pick(object) {
var result = {};
if (!object) {
return result;
}
var prop,
index = 0,
props = concat.apply(ArrayProto, arguments),
length = props.length,
result = {};
length = props.length;
// start `index` at `1` to skip `object`
while (++index < length) {
@@ -2025,63 +2089,6 @@
*/
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
* 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');
(function() {
@@ -1489,88 +1533,43 @@
/*--------------------------------------------------------------------------*/
QUnit.module('lodash "Arrays" methods');
QUnit.module('lodash methods');
(function() {
test('should allow a falsey `array` argument', function() {
_.each([
'compact',
'difference',
'first',
'flatten',
'groupBy',
'indexOf',
'initial',
'intersection',
'last',
'lastIndexOf',
'max',
'min',
'range',
'rest',
'shuffle',
'sortBy',
'sortedIndex',
'union',
'uniq',
'without',
'zip',
'zipObject'
], function(methodName) {
test('should allow a falsey arguments', function() {
var funcs = _.without.apply(_, [_.functions(_)].concat([
'_iteratorTemplate',
'_shimKeys',
'after',
'bind',
'bindAll',
'compose',
'debounce',
'defer',
'delay',
'functions',
'memoize',
'once',
'partial',
'tap',
'template',
'throttle',
'wrap'
]));
_.each(funcs, function(methodName) {
var func = _[methodName],
pass = true;
_.each(falsey, function(value, index) {
try {
index ? func() : func(value);
index ? func(value) : func();
} catch(e) {
pass = false;
}
});
ok(pass, methodName + ' allows a falsey `array` argument');
});
});
}());
/*--------------------------------------------------------------------------*/
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');
ok(pass, methodName + ' allows a falsey arguments');
});
});
}());