Add coverage for Set for environments w/o native support.

This commit is contained in:
John-David Dalton
2014-01-19 23:52:27 -08:00
parent 77fbffdc4b
commit a3d18d0429
3 changed files with 280 additions and 91 deletions

134
test/asset/set.js Normal file
View File

@@ -0,0 +1,134 @@
;(function() {
/** Used to determine if values are of the language type Object */
var objectTypes = {
'function': true,
'object': true
};
/** Used as the `Set#toString` return value */
var nativeString = String(Object.prototype.toString).replace(/toString/g, 'Set');
/** Used as a reference to the global object */
var root = (objectTypes[typeof window] && window) || this;
/** Detect free variable `global` from Node.js or Browserified code and use it as `root` */
var freeGlobal = objectTypes[typeof global] && global;
if (freeGlobal && (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal)) {
root = freeGlobal;
}
/*--------------------------------------------------------------------------*/
/**
* Creates a Set object to optimize linear searches of large arrays.
*/
function Set() {
this.__cache__ = {};
}
/*--------------------------------------------------------------------------*/
/**
* Gets the index at which the first occurrence of `value` is found using
* strict equality for comparisons, i.e. `===`.
*
* @private
* @param {Array} array The array to search.
* @param {*} value The value to search for.
* @returns {number} Returns the index of the matched value or `-1`.
*/
function indexOf(array, value) {
var index = -1,
length = array.length;
while (++index < length) {
if (array[index] === value) {
return index;
}
}
return -1;
}
/*--------------------------------------------------------------------------*/
/**
* Checks if `value` is in the set.
*
* @memberOf Set
* @param {*} value The value to search for.
* @returns {boolean} Returns `true` if `value` is found, else `false`.
*/
function has(value) {
var type = typeof value,
cache = this.__cache__;
if (type == 'boolean' || value == null) {
return cache[value] || false;
}
if (type != 'number' && type != 'string') {
type = 'object';
}
var key = type == 'number' ? value : '_' + value;
cache = (cache = cache[type]) && cache[key];
return type == 'object'
? (cache && indexOf(cache, value) > -1 ? true : false)
: (cache || false);
}
/**
* Adds `value` to the set.
*
* @memberOf Set
* @param {*} value The value to add to the set.
*/
function add(value) {
var cache = this.__cache__,
type = typeof value;
if (type == 'boolean' || value == null) {
cache[value] = true;
} else {
if (type != 'number' && type != 'string') {
type = 'object';
}
var key = type == 'number' ? value : '_' + value,
typeCache = cache[type] || (cache[type] = {});
if (type == 'object') {
var array = typeCache[key];
if (array) {
array.push(value);
} else {
typeCache[key] = [value];
}
} else {
typeCache[key] = true;
}
}
}
/**
* Produces the `toString` result of `Set`.
*
* @static
* @memberOf Set
* @returns {string} Returns the string result.
*/
function toString() {
return nativeString;
}
/*--------------------------------------------------------------------------*/
Set.toString = toString;
Set.prototype.add = add;
Set.prototype.has = has;
// expose `Set`
if (!root.Set) {
root.Set = Set;
}
}.call(this));

View File

@@ -18,78 +18,122 @@
<div id="qunit"></div>
<div id="exports"></div>
<script>
var setProperty = (function() {
var defineProperty = (function() {
try {
var o = {},
func = Object.defineProperty,
result = func(o, o, o) && func;
} catch(e) { }
return result;
}());
if (!defineProperty) {
return function(object, key, value) {
object[key] = value;
};
}
return function(object, key, value) {
// avoid a bug where overwriting non-enumerable built-ins makes them enumerable
// https://code.google.com/p/v8/issues/detail?id=1623
defineProperty(object, key, {
'configurable': true,
'enumerable': false,
'writable': true,
'value': value
});
}
}());
function addBizarroMethods() {
// add extensions
Function.prototype._method = function() {};
// allow bypassing native checks
setProperty(Function.prototype, '_toString', _fnToString);
setProperty(Function.prototype, 'toString', (function() {
var func = Function.prototype.toString;
function toString() {
return this.toString !== toString ? this.toString() : func.call(this);
}
return toString;
}()));
// set bad shims
Array._isArray = Array.isArray;
Array.isArray = function() {};
setProperty(Array, '_isArray', Array.isArray);
setProperty(Array, 'isArray', function() {});
Date._now = Date.now;
Date.now = function() {};
setProperty(Date, '_now', Date.now);
setProperty(Date, 'now', function() {});
Object._create = Object.create;
Object.create = function() {};
setProperty(Object, '_create', Object.create);
setProperty(Object, 'create', function() {});
Object._defineProperty = Object.defineProperty;
Object.defineProperty = function() {};
setProperty(Object, '_defineProperty', Object.defineProperty);
setProperty(Object, 'defineProperty', function() {});
Object._getPrototypeOf = Object.getPrototypeOf;
Object.getPrototypeOf = function() {};
setProperty(Object, '_getPrototypeOf', Object.getPrototypeOf);
setProperty(Object, 'getPrototypeOf', function() {});
Object._keys = Object.keys;
Object.keys = function() {};
setProperty(Object, '_keys', Object.keys);
setProperty(Object, 'keys', function() {});
String.prototype._contains = String.prototype.contains;
String.prototype.contains = String.prototype._contains ? function() {} : Boolean;
setProperty(String.prototype, '_contains', String.prototype.contains);
setProperty(String.prototype, 'contains', String.prototype._contains ? function() {} : Boolean);
String.prototype._trim = String.prototype.trim;
String.prototype.trim = String.prototype._trim ? function() {} : String;
setProperty(String.prototype, '_trim', String.prototype.trim);
setProperty(String.prototype, 'trim', String.prototype._trim ? function() {} : String);
String.prototype._trimLeft = String.prototype.trimLeft;
String.prototype.trimLeft = String.prototype._trimLeft ? function() {} : String;
setProperty(String.prototype, '_trimLeft', String.prototype.trimLeft);
setProperty(String.prototype, 'trimLeft', String.prototype._trimLeft ? function() {} : String);
String.prototype._trimRight = String.prototype.trimRight;
String.prototype.trimRight = String.prototype._trimRight ? function() {} : String;
setProperty(String.prototype, '_trimRight', String.prototype.trimRight);
setProperty(String.prototype, 'trimRight', String.prototype._trimRight ? function() {} : String);
window.WinRTError = Error;
setProperty(window, '_Set', window.Set);
setProperty(window, 'WinRTError', Error);
document._createDocumentFragment = document.createDocumentFragment;
setProperty(document, '_createDocumentFragment', document.createDocumentFragment);
document.createDocumentFragment = function() {};
}
function removeBizarroMethods() {
if (Array._isArray) {
Array.isArray = Array._isArray;
setProperty(Array, 'isArray', Array._isArray);
} else {
delete Array.isArray;
}
if (Date._now) {
Date.now = Date._now;
setProperty(Date, 'now', Date._now);
} else {
delete Date.now;
}
if (Object._create) {
Object.create = Object._create;
setProperty(Object, 'create', Object._create);
} else {
delete Object.create;
}
if (Object._defineProperty) {
Object.defineProperty = Object._defineProperty;
setProperty(Object, 'defineProperty', Object._defineProperty);
} else {
delete Object.defineProperty;
}
if (Object._getPrototypeOf) {
Object.getPrototypeOf = Object._getPrototypeOf;
setProperty(Object, 'getPrototypeOf', Object._getPrototypeOf);
} else {
delete Object.getPrototypeOf;
}
if (Object._keys) {
Object.keys = Object._keys;
setProperty(Object, 'keys', Object._keys);
} else {
delete Object.keys;
}
if (window._Set) {
setProperty(window, 'Set', _Set);
} else {
setProperty(window, 'Set', undefined);
}
for (var key in {
'contains': true,
'trim': true,
@@ -98,29 +142,22 @@
}) {
var func = String.prototype['_' + key];
if (func) {
if (Object.defineProperty) {
Object.defineProperty(String.prototype, key, {
'configurable': true,
'enumerable': false,
'writable': true,
'value': func
});
} else {
String.prototype[key] = func;
}
setProperty(String.prototype, key, func);
} else {
delete String.prototype[key];
}
delete String.prototype['_' + key];
}
window.WinRTError = undefined;
setProperty(window, 'WinRTError', undefined);
setProperty(Function.prototype, 'toString', Function.prototype._toString);
document.createDocumentFragment = document._createDocumentFragment;
document._createDocumentFragment = undefined;
setProperty(document, '_createDocumentFragment', undefined);
delete Array._isArray;
delete Date._now;
delete Function.prototype._method;
delete Function.prototype._toString;
delete Object._create;
delete Object._defineProperty;
delete Object._getPrototypeOf;
@@ -130,6 +167,7 @@
addBizarroMethods();
// load Lo-Dash and expose it to the bad extensions/shims
document.write('<script src="./asset/set.js"><\/script>');
document.write('<script src="' + (ui.isModularize ? '../lodash.js' : ui.buildPath) + '"><\/script>');
</script>
<script>

View File

@@ -151,7 +151,7 @@
var empties = [[], {}].concat(falsey.slice(1));
/** Used as the size when optimizations are enabled for large arrays */
var largeArraySize = 75;
var LARGE_ARRAY_SIZE = 75;
/** Used to set property descriptors */
var defineProperty = (function() {
@@ -192,6 +192,32 @@
});
}
/**
* Sets a non-enumerable property value on `object`.
*
* @private
* @param {Object} object The object augment.
* @param {string} key The name of the property to set.
* @param {*} value The property value.
*/
var setProperty = (function() {
if (!defineProperty) {
return function(object, key, value) {
object[key] = value;
};
}
return function(object, key, value) {
// avoid a bug where overwriting non-enumerable built-ins makes them enumerable
// https://code.google.com/p/v8/issues/detail?id=1623
defineProperty(object, key, {
'configurable': true,
'enumerable': false,
'writable': true,
'value': value
});
};
}());
/**
* Skips a given number of tests with a passing result.
*
@@ -211,8 +237,6 @@
(function() {
if (!amd) {
try {
emptyObject(require.cache);
_.extend(_, require('vm').runInNewContext([
'({',
"'_arguments': (function() { return arguments; }(1, 2, 3)),",
@@ -231,61 +255,60 @@
].join('\n')));
// fake `WinRTError`
global.WinRTError = Error;
setProperty(global, 'WinRTError', Error);
// fake DOM
global.window = {
'document': {
'createDocumentFragment': function() {
return { 'nodeType': 11 };
}
}
};
setProperty(global, 'window', {});
setProperty(global.window, 'document', {});
setProperty(global.window.document, 'createDocumentFragment', function() {
return { 'nodeType': 11 };
});
// add extensions
Function.prototype._method = function() {};
// set bad shims
var _isArray = Array.isArray;
Array.isArray = function() {};
setProperty(Array, 'isArray', function() {});
var _now = Date.now;
Date.now = function() {};
setProperty(Date, 'now', function() {});
var _create = Object.create;
Object.create = function() {};
setProperty(Object, 'create', function() {});
var _defineProperty = Object.defineProperty;
Object.defineProperty = function() {};
setProperty(Object, 'defineProperty', function() {});
var _getPrototypeOf = Object.getPrototypeOf;
Object.getPrototypeOf = function() {};
setProperty(Object, 'getPrototypeOf', function() {});
var _keys = Object.keys;
Object.keys = function() {};
setProperty(Object, 'keys', function() {});
var _contains = String.prototype.contains;
String.prototype.contains = _contains ? function() {} : Boolean;
setProperty(String.prototype, 'contains', _contains ? function() {} : Boolean);
var _trim = String.prototype.trim;
String.prototype.trim = _trim ? function() {} : String;
setProperty(String.prototype, 'trim', _trim ? function() {} : String);
var _trimLeft = String.prototype.trimLeft;
String.prototype.trimLeft = _trimLeft ? function() {} : String;
setProperty(String.prototype, 'trimLeft', _trimLeft ? function() {} : String);
var _trimRight = String.prototype.trimRight;
String.prototype.trimRight = _trimRight ? function() {} : String;
setProperty(String.prototype, 'trimRight', _trimRight ? function() {} : String);
// load Lo-Dash and expose it to the bad extensions/shims
emptyObject(require.cache);
lodashBizarro = (lodashBizarro = require(filePath))._ || lodashBizarro;
// restore native methods
Array.isArray = _isArray;
Date.now = _now;
Object.create = _create;
Object.defineProperty = _defineProperty;
Object.getPrototypeOf = _getPrototypeOf;
Object.keys = _keys;
setProperty(Array, 'isArray', _isArray);
setProperty(Date, 'now', _now);
setProperty(Object, 'create', _create);
setProperty(Object, 'defineProperty', _defineProperty);
setProperty(Object, 'getPrototypeOf', _getPrototypeOf);
setProperty(Object, 'keys', _keys);
_.forOwn({
'contains': _contains,
@@ -295,14 +318,7 @@
},
function(func, key) {
if (func) {
// avoid a bug where overwriting non-enumerable built-ins makes them enumerable
// https://code.google.com/p/v8/issues/detail?id=1623
defineProperty(String.prototype, key, {
'configurable': true,
'enumerable': false,
'writable': true,
'value': func
});
setProperty(String.prototype, key, func);
} else {
delete String.prototype[key];
}
@@ -2030,7 +2046,7 @@
});
test('should work with large arrays', 1, function() {
var array1 = _.range(largeArraySize),
var array1 = _.range(LARGE_ARRAY_SIZE),
array2 = array1.slice(),
a = {},
b = {},
@@ -2045,7 +2061,7 @@
test('should work with large arrays of objects', 1, function() {
var object = {};
var largeArray = _.times(largeArraySize, function() {
var largeArray = _.times(LARGE_ARRAY_SIZE, function() {
return object;
});
@@ -2948,7 +2964,7 @@
stringObject = Object(stringLiteral),
expected = [stringLiteral, stringObject];
var largeArray = _.times(largeArraySize, function(count) {
var largeArray = _.times(LARGE_ARRAY_SIZE, function(count) {
return count % 2 ? stringObject : stringLiteral;
});
@@ -3227,6 +3243,10 @@
var array = [1, new Foo, 3, new Foo],
indexOf = _.indexOf;
var largeArray = _.times(LARGE_ARRAY_SIZE, function() {
return new Foo;
});
test('`_.contains` should work with a custom `_.indexOf` method', 1, function() {
if (!isModularize) {
_.indexOf = custom;
@@ -3238,25 +3258,27 @@
}
});
test('`_.difference` should work with a custom `_.indexOf` method', 1, function() {
test('`_.difference` should work with a custom `_.indexOf` method', 2, function() {
if (!isModularize) {
_.indexOf = custom;
deepEqual(_.difference(array, [new Foo]), [1, 3]);
deepEqual(_.difference(array, largeArray), [1, 3]);
_.indexOf = indexOf;
}
else {
skipTest();
skipTest(2);
}
});
test('`_.intersection` should work with a custom `_.indexOf` method', 1, function() {
test('`_.intersection` should work with a custom `_.indexOf` method', 2, function() {
if (!isModularize) {
_.indexOf = custom;
deepEqual(_.intersection(array, [new Foo]), [array[1]]);
deepEqual(_.intersection(largeArray, [new Foo]), [array[1]]);
_.indexOf = indexOf;
}
else {
skipTest();
skipTest(2);
}
});
@@ -3264,11 +3286,6 @@
if (!isModularize) {
_.indexOf = custom;
deepEqual(_.uniq(array), array.slice(0, 3));
var largeArray = _.times(largeArraySize, function() {
return new Foo;
});
deepEqual(_.uniq(largeArray), [largeArray[0]]);
_.indexOf = indexOf;
}
@@ -3388,7 +3405,7 @@
var object = {},
expected = [object];
var largeArray = _.times(largeArraySize, function() {
var largeArray = _.times(LARGE_ARRAY_SIZE, function() {
return object;
});
@@ -8202,7 +8219,7 @@
test('should work with large arrays', 1, function() {
var object = {};
var largeArray = _.times(largeArraySize, function(index) {
var largeArray = _.times(LARGE_ARRAY_SIZE, function(index) {
switch (index % 3) {
case 0: return 0;
case 1: return 'a';
@@ -8247,7 +8264,7 @@
test('should work with large arrays of boolean, `null`, and `undefined` values', 1, function() {
var array = [],
expected = [true, false, null, undefined],
count = Math.ceil(largeArraySize / expected.length);
count = Math.ceil(LARGE_ARRAY_SIZE / expected.length);
_.times(count, function() {
push.apply(array, expected);
@@ -8258,7 +8275,7 @@
test('should distinguish between numbers and numeric strings', 1, function() {
var array = [],
expected = ['2', 2, Object('2'), Object(2)],
count = Math.ceil(largeArraySize / expected.length);
count = Math.ceil(LARGE_ARRAY_SIZE / expected.length);
_.times(count, function() {
push.apply(array, expected);