Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: Khan/khan-exercises
...
head fork: Khan/khan-exercises
Checking mergeability… Don’t worry, you can still create the pull request.
  • 10 commits
  • 9 files changed
  • 0 commit comments
  • 1 contributor
View
9 khan-exercise.js
@@ -368,6 +368,15 @@ var Khan = (function() {
seed = ((seed + 0xfd7046c5) + (seed << 3)) & 0xffffffff;
seed = ((seed ^ 0xb55a4f09) ^ (seed >>> 16)) & 0xffffffff;
return (randomSeed = (seed & 0xfffffff)) / 0x10000000;
+ },
+
+ load: function(module){
+ var ku = this;
+ var safeHas = Object.prototype.hasOwnProperty;
+ if (safeHas.call(module, "NAME")){
+ delete module.NAME;
+ }
+ $.extend(ku, module);
}
},
View
170 test/index.html
@@ -1,159 +1,31 @@
<!DOCTYPE html>
<html>
<head>
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>Khan Academy Exercise Testing</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <title>Khan Academy Exercise Testing</title>
- <!-- Include dependencies -->
- <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
+ <!-- Include dependencies -->
+ <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
+ <script src="../utils/underscore.js" type="text/javascript" charset="utf-8"></script>
- <!-- Include QUnit -->
- <link rel="stylesheet" href="qunit/qunit/qunit.css" type="text/css" media="screen">
- <script src="qunit/qunit/qunit.js"></script>
+ <!-- Include QUnit -->
+ <link rel="stylesheet" href="qunit/qunit/qunit.css" type="text/css" media="screen">
+ <script src="qunit/qunit/qunit.js"></script>
+ <script src="utils/unit-tests.js"></script>
+ <script type="text/javascript" charset="utf-8">
+ KhanTests.run({
+ tests: "angles",
+ deps: ["math", "angles"]
+ })
+ </script>
- <!-- Include utility files and tests. -->
- <script src="exercise-runner.js"></script>
-
- <script src="exercises/absolute_value.js"></script>
- <script src="exercises/absolute_value_equations.js"></script>
- <script src="exercises/adding_and_subtracting_fractions.js"></script>
- <script src="exercises/adding_and_subtracting_negative_numbers.js"></script>
- <script src="exercises/adding_decimals.js"></script>
- <script src="exercises/adding_vectors.js"></script>
- <script src="exercises/addition_1.js"></script>
- <script src="exercises/addition_2.js"></script>
- <script src="exercises/addition_3.js"></script>
- <script src="exercises/addition_4.js"></script>
- <script src="exercises/age_word_problems.js"></script>
- <script src="exercises/angle_types.js"></script>
- <script src="exercises/angles_1.js"></script>
- <script src="exercises/angles_2.js"></script>
- <script src="exercises/arithmetic_word_problems.js"></script>
- <script src="exercises/average_word_problems.js"></script>
- <script src="exercises/chain_rule.js"></script>
- <script src="exercises/circles_1.js"></script>
- <script src="exercises/comparing_fractions_1.js"></script>
- <script src="exercises/comparing_fractions_2.js"></script>
- <script src="exercises/complementary_and_supplementary_angles.js"></script>
- <script src="exercises/complementary_angles.js"></script>
- <script src="exercises/degrees_to_radians.js"></script>
- <script src="exercises/dependent_probability.js"></script>
- <script src="exercises/derivatives_1.js"></script>
- <script src="exercises/determinants_1.js"></script>
- <script src="exercises/direct_and_inverse_variation.js"></script>
- <script src="exercises/distance_formula.js"></script>
- <script src="exercises/dividing_decimals.js"></script>
- <script src="exercises/dividing_fractions.js"></script>
- <script src="exercises/divisibility.js"></script>
- <script src="exercises/divisibility_intro.js"></script>
- <script src="exercises/division_0.5.js"></script>
- <script src="exercises/division_1.5.js"></script>
- <script src="exercises/division_1.js"></script>
- <script src="exercises/division_2.js"></script>
- <script src="exercises/division_3.js"></script>
- <script src="exercises/division_4.js"></script>
- <script src="exercises/domain_of_a_function.js"></script>
- <script src="exercises/equation_of_a_circle_1.js"></script>
- <script src="exercises/equation_of_a_circle_2.js"></script>
- <script src="exercises/equation_of_a_line.js"></script>
- <script src="exercises/equivalent_fractions.js"></script>
- <script src="exercises/equivalent_fractions_2.js"></script>
- <script src="exercises/evaluating_expressions_1.js"></script>
- <script src="exercises/even_and_odd_functions.js"></script>
- <script src="exercises/exponent_rules.js"></script>
- <script src="exercises/exponents_1.js"></script>
- <script src="exercises/exponents_2.js"></script>
- <script src="exercises/exponents_3.js"></script>
- <script src="exercises/exponents_4.js"></script>
- <script src="exercises/expressions_with_unknown_variables.js"></script>
- <script src="exercises/expressions_with_unknown_variables_2.js"></script>
- <script src="exercises/fraction_word_problems_1.js"></script>
- <script src="exercises/functions_1.js"></script>
- <script src="exercises/functions_2.js"></script>
- <script src="exercises/functions_3.js"></script>
- <script src="exercises/geometry_1.js"></script>
- <script src="exercises/graphing_points.js"></script>
- <script src="exercises/graphs_of_sine_and_cosine.js"></script>
- <script src="exercises/greatest_common_divisor.js"></script>
- <script src="exercises/inverse_trig_functions.js"></script>
- <script src="exercises/inverses_of_functions.js"></script>
- <script src="exercises/kinematic_equations.js"></script>
- <script src="exercises/least_common_multiple.js"></script>
- <script src="exercises/limits_1.js"></script>
- <script src="exercises/limits_2.js"></script>
- <script src="exercises/line_graph_intuition.js"></script>
- <script src="exercises/line_relationships.js"></script>
- <script src="exercises/linear_equations_1.js"></script>
- <script src="exercises/linear_equations_2.js"></script>
- <script src="exercises/linear_equations_3.js"></script>
- <script src="exercises/linear_equations_4.js"></script>
- <script src="exercises/linear_inequalities.js"></script>
- <script src="exercises/logarithms_1.js"></script>
- <script src="exercises/logarithms_2.js"></script>
- <script src="exercises/mean_median_and_mode.js"></script>
- <script src="exercises/midpoint_formula.js"></script>
- <script src="exercises/multiplication_0.5.js"></script>
- <script src="exercises/multiplication_1.5.js"></script>
- <script src="exercises/multiplication_1.js"></script>
- <script src="exercises/multiplication_2.js"></script>
- <script src="exercises/multiplication_3.js"></script>
- <script src="exercises/multiplication_4.js"></script>
- <script src="exercises/multiplying_and_dividing_negative_numbers.js"></script>
- <script src="exercises/multiplying_decimals.js"></script>
- <script src="exercises/multiplying_expressions_1.js"></script>
- <script src="exercises/multiplying_fractions.js"></script>
- <script src="exercises/new_definitions_1.js"></script>
- <script src="exercises/new_definitions_2.js"></script>
- <script src="exercises/order_of_operations.js"></script>
- <script src="exercises/ordering_numbers.js"></script>
- <script src="exercises/percentage_word_problems_1.js"></script>
- <script src="exercises/percentage_word_problems_2.js"></script>
- <script src="exercises/place_value.js"></script>
- <script src="exercises/power_rule.js"></script>
- <script src="exercises/prime_factorization.js"></script>
- <script src="exercises/prime_numbers.js"></script>
- <script src="exercises/probability_1.js"></script>
- <script src="exercises/product_rule.js"></script>
- <script src="exercises/pythagorean_theorem_1.js"></script>
- <script src="exercises/pythagorean_theorem_2.js"></script>
- <script src="exercises/quadratic_equation.js"></script>
- <script src="exercises/quotient_rule.js"></script>
- <script src="exercises/radians_and_degrees.js"></script>
- <script src="exercises/radians_to_degrees.js"></script>
- <script src="exercises/radical_equations.js"></script>
- <script src="exercises/range_of_a_function.js"></script>
- <script src="exercises/rate_problems_1.js"></script>
- <script src="exercises/recognizing_fractions.js"></script>
- <script src="exercises/rounding_numbers.js"></script>
- <script src="exercises/scaling_vectors.js"></script>
- <script src="exercises/scientific_notation.js"></script>
- <script src="exercises/shifting_and_reflecting_functions.js"></script>
- <script src="exercises/simplifying_fractions.js"></script>
- <script src="exercises/simplifying_radicals.js"></script>
- <script src="exercises/slope_of_a_line.js"></script>
- <script src="exercises/solid_geometry.js"></script>
- <script src="exercises/solving_for_a_variable.js"></script>
- <script src="exercises/solving_for_the_y-intercept.js"></script>
- <script src="exercises/solving_quadratics_by_factoring.js"></script>
- <script src="exercises/special_derivatives.js"></script>
- <script src="exercises/squares_and_rectangles.js"></script>
- <script src="exercises/subtracting_decimals.js"></script>
- <script src="exercises/subtraction_1.js"></script>
- <script src="exercises/subtraction_2.js"></script>
- <script src="exercises/subtraction_3.js"></script>
- <script src="exercises/subtraction_4.js"></script>
- <script src="exercises/supplementary_angles.js"></script>
- <script src="exercises/systems_of_equations.js"></script>
- <script src="exercises/units.js"></script>
- <script src="exercises/writing_expressions_1.js"></script>
- <script src="exercises/writing_expressions_2.js"></script>
</head>
<body>
- <h1 id="qunit-header">Khan Academy Exercise Testing</h1>
- <h2 id="qunit-banner"></h2>
- <div id="qunit-testrunner-toolbar"></div>
- <h2 id="qunit-userAgent"></h2>
- <ol id="qunit-tests"></ol>
- <div id="qunit-fixture">test markup</div>
+ <h1 id="qunit-header">Khan Academy Exercise Testing</h1>
+ <h2 id="qunit-banner"></h2>
+ <div id="qunit-testrunner-toolbar"></div>
+ <h2 id="qunit-userAgent"></h2>
+ <ol id="qunit-tests"></ol>
+ <div id="qunit-fixture">test markup</div>
</body>
</html>
View
115 test/utils/unit-tests.js
@@ -0,0 +1,115 @@
+
+var KhanTests = (function(){
+
+ // the tests object is populated by loadTest and contains the names
+ // of all the functions to run in the module you care about
+ var tests = {};
+
+ // set up bare bones Khan module
+ var Khan = {
+ "Util": {
+ "imports_": {},
+ load: function(module){
+ var ku = this;
+ // only extend KU like this for testrunning
+ if(_(module).has("NAME")){
+ var name = module.NAME;
+ delete module.NAME;
+ var mod = {};
+ mod[name] = module;
+ $.extend(this.imports_, mod);
+ }
+
+ // dump all methods from this module into KhanUtil
+ var importedMethods = _(module).keys();
+ var collisions = _.intersection(_(ku).keys(), importedMethods);
+ if(collisions.length){
+ // log the collisions that ocurred if any
+ console.error("KhanUtil.load Collision Error:",
+ collisions.join(", "))
+ }else{
+ $.extend(ku, module);
+ }
+
+ // run tests in module context
+ if(mod){
+ runTests(mod)
+ }
+ }
+ }
+ };
+
+ // load + extend these tests in dynamically before loading all the libs
+ // tests = {"angles":{"toDegrees": function(){ ok(true, "looks good!") }}}
+ // tests = {};
+
+ // runTests for a module (i.e. the files in utils) by iterating over
+ // all its exported functions and then looking for a test for each.
+ // lib is an object like so: {"lib": { "foo": function(){},...}}
+ var runTests = function(lib){
+ _(lib).chain().keys().each(function(libName){
+
+ exported = lib[libName];
+ module(libName)
+ _(exported).chain().functions().each(function(func){
+ console.log(" - trying to run test for " + func)
+ test(func, function(){
+ // do any tests exist for this method?
+ testsForLib = _(tests).has(libName) && _(tests[libName]).has(func);
+ testMessage = testsForLib ? "Tests written for "+func :
+ "No tests written for '" + func +"' :("
+ ok(testsForLib, testMessage)
+
+ // run any tests that exist for this function
+ if(testsForLib && _(tests[libName][func]).isFunction()){
+ tests[libName][func]()
+ }
+ });
+ });
+
+ });
+ }
+
+ var init = function(files){
+ var KhanUtil = $.extend({},Khan.Util);
+ _(files).each(function(file){
+ $.getScript("../utils/"+file+".js");
+ })
+ }
+
+ // create a simple setter for loading in tests without having to deal with window
+ var loadTest = function(obj){
+ $.extend(tests, obj);
+ }
+
+ // a wrapper for loading tests
+ // wants {tests:"basepath", deps: ["basepath", "basepath"]}
+ // where for `tests`, basepath is located in /utils/test/basepath.js
+ // and for deps, the basepath is located in /utils/basepath.js
+ var setupTests = function(spec){
+ if(!spec){ return; }
+
+ // reset the tests object when starting a new test
+ tests = {};
+
+ var testPath = "../utils/test/" + spec.tests + ".js";
+ var deps = spec.deps;
+ $.ajax({
+ dataType: "script",
+ url: testPath,
+ success: function(){ init(deps); },
+ error: function(){ console.error(arguments); }
+ })
+ }
+
+ var exported = {
+ init: init,
+ util: Khan.Util,
+ run: setupTests,
+ loadTest: loadTest
+ };
+
+ return exported;
+})()
+// create fake KhanUtil object
+KhanUtil = KhanTests.util;
View
4 utils/angles.js
@@ -1,4 +1,6 @@
-$.extend(KhanUtil, {
+// requires math.js
+KhanUtil.load({
+ NAME: "angles",
commonAngles: [
{deg: 15, rad: "\\frac{\\pi}{12}"},
{deg: 30, rad: "\\frac{\\pi}{6}"},
View
4 utils/calculus.js
@@ -1,4 +1,6 @@
-$.extend(KhanUtil, {
+// requires polynomials.js
+KhanUtil.load({
+ NAME: "calculus",
trigFuncs: ["sin", "cos", "tan"],
ddxTrigFuncs: {
"sin": function(expr) {
View
2  utils/math.js
@@ -1,4 +1,4 @@
-$.extend(KhanUtil, {
+KhanUtil.load({
// Simplify formulas before display
cleanMath: function(expr) {
View
3  utils/polynomials.js
@@ -1,4 +1,5 @@
-$.extend(KhanUtil, {
+KhanUtil.load({
+ "NAME": "polynomials",
Polynomial: function(minDegree, maxDegree, coefs, variable, name) {
var term = function(coef, vari, degree) {
View
21 utils/test/angles.js
@@ -0,0 +1,21 @@
+// test for angles.js
+
+/* For each function in /utils/angles.js, there should be a corresponding function in
+ * this file. This function can actually contain any number of tests, it doesn't need
+ * to contain anything even if the function is not worth testing (huh!) but not including
+ * it will cause the whole suite to fail.
+ */
+
+KhanTests.loadTest({
+ "angles":{
+ toRadians: function(){
+ equal(KhanUtil.toRadians(45), (45 * Math.PI / 180 ), "toRadians works for 45 Degrees")
+ },
+ toDegrees: function(){
+ equal(KhanUtil.toDegrees(.5), (.5 * 180 / Math.PI), "toDegrees works for .5 Radians")
+ },
+ wrongCommonAngle: function(){},
+ wrongDegrees: function(){},
+ wrongRadians: function(){}
+ }
+})
View
419 utils/underscore.js
@@ -1,5 +1,5 @@
-// Underscore.js 1.2.1
-// (c) 2011 Jeremy Ashkenas, DocumentCloud Inc.
+// Underscore.js 1.3.3
+// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Underscore is freely distributable under the MIT license.
// Portions of Underscore are inspired or borrowed from Prototype,
// Oliver Steele's Functional, and John Resig's Micro-Templating.
@@ -48,26 +48,21 @@
// Create a safe reference to the Underscore object for use below.
var _ = function(obj) { return new wrapper(obj); };
- // Export the Underscore object for **Node.js** and **"CommonJS"**, with
- // backwards-compatibility for the old `require()` API. If we're not in
- // CommonJS, add `_` to the global object.
+ // Export the Underscore object for **Node.js**, with
+ // backwards-compatibility for the old `require()` API. If we're in
+ // the browser, add `_` as a global object via a string identifier,
+ // for Closure Compiler "advanced" mode.
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = _;
}
exports._ = _;
- } else if (typeof define === 'function' && define.amd) {
- // Register as a named module with AMD.
- define('underscore', function() {
- return _;
- });
} else {
- // Exported as a string, for Closure Compiler "advanced" mode.
root['_'] = _;
}
// Current version.
- _.VERSION = '1.2.1';
+ _.VERSION = '1.3.3';
// Collection Functions
// --------------------
@@ -85,7 +80,7 @@
}
} else {
for (var key in obj) {
- if (hasOwnProperty.call(obj, key)) {
+ if (_.has(obj, key)) {
if (iterator.call(context, obj[key], key, obj) === breaker) return;
}
}
@@ -94,20 +89,21 @@
// Return the results of applying the iterator to each element.
// Delegates to **ECMAScript 5**'s native `map` if available.
- _.map = function(obj, iterator, context) {
+ _.map = _.collect = function(obj, iterator, context) {
var results = [];
if (obj == null) return results;
if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
each(obj, function(value, index, list) {
results[results.length] = iterator.call(context, value, index, list);
});
+ if (obj.length === +obj.length) results.length = obj.length;
return results;
};
// **Reduce** builds up a single result from a list of values, aka `inject`,
// or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
_.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
- var initial = memo !== void 0;
+ var initial = arguments.length > 2;
if (obj == null) obj = [];
if (nativeReduce && obj.reduce === nativeReduce) {
if (context) iterator = _.bind(iterator, context);
@@ -121,20 +117,22 @@
memo = iterator.call(context, memo, value, index, list);
}
});
- if (!initial) throw new TypeError("Reduce of empty array with no initial value");
+ if (!initial) throw new TypeError('Reduce of empty array with no initial value');
return memo;
};
// The right-associative version of reduce, also known as `foldr`.
// Delegates to **ECMAScript 5**'s native `reduceRight` if available.
_.reduceRight = _.foldr = function(obj, iterator, memo, context) {
+ var initial = arguments.length > 2;
if (obj == null) obj = [];
if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
if (context) iterator = _.bind(iterator, context);
- return memo !== void 0 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
+ return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
}
- var reversed = (_.isArray(obj) ? obj.slice() : _.toArray(obj)).reverse();
- return _.reduce(reversed, iterator, memo, context);
+ var reversed = _.toArray(obj).reverse();
+ if (context && !initial) iterator = _.bind(iterator, context);
+ return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator);
};
// Return the first value which passes a truth test. Aliased as `detect`.
@@ -182,19 +180,19 @@
each(obj, function(value, index, list) {
if (!(result = result && iterator.call(context, value, index, list))) return breaker;
});
- return result;
+ return !!result;
};
// Determine if at least one element in the object matches a truth test.
// Delegates to **ECMAScript 5**'s native `some` if available.
// Aliased as `any`.
var any = _.some = _.any = function(obj, iterator, context) {
- iterator = iterator || _.identity;
+ iterator || (iterator = _.identity);
var result = false;
if (obj == null) return result;
if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
each(obj, function(value, index, list) {
- if (result |= iterator.call(context, value, index, list)) return breaker;
+ if (result || (result = iterator.call(context, value, index, list))) return breaker;
});
return !!result;
};
@@ -206,7 +204,7 @@
if (obj == null) return found;
if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
found = any(obj, function(value) {
- if (value === target) return true;
+ return value === target;
});
return found;
};
@@ -215,7 +213,7 @@
_.invoke = function(obj, method) {
var args = slice.call(arguments, 2);
return _.map(obj, function(value) {
- return (method.call ? method || value : value[method]).apply(value, args);
+ return (_.isFunction(method) ? method || value : value[method]).apply(value, args);
});
};
@@ -226,7 +224,7 @@
// Return the maximum element or (element-based computation).
_.max = function(obj, iterator, context) {
- if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj);
+ if (!iterator && _.isArray(obj) && obj[0] === +obj[0]) return Math.max.apply(Math, obj);
if (!iterator && _.isEmpty(obj)) return -Infinity;
var result = {computed : -Infinity};
each(obj, function(value, index, list) {
@@ -238,7 +236,7 @@
// Return the minimum element (or element-based computation).
_.min = function(obj, iterator, context) {
- if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj);
+ if (!iterator && _.isArray(obj) && obj[0] === +obj[0]) return Math.min.apply(Math, obj);
if (!iterator && _.isEmpty(obj)) return Infinity;
var result = {computed : Infinity};
each(obj, function(value, index, list) {
@@ -252,19 +250,16 @@
_.shuffle = function(obj) {
var shuffled = [], rand;
each(obj, function(value, index, list) {
- if (index == 0) {
- shuffled[0] = value;
- } else {
- rand = Math.floor(Math.random() * (index + 1));
- shuffled[index] = shuffled[rand];
- shuffled[rand] = value;
- }
+ rand = Math.floor(Math.random() * (index + 1));
+ shuffled[index] = shuffled[rand];
+ shuffled[rand] = value;
});
return shuffled;
};
// Sort the object's values by a criterion produced by an iterator.
- _.sortBy = function(obj, iterator, context) {
+ _.sortBy = function(obj, val, context) {
+ var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; };
return _.pluck(_.map(obj, function(value, index, list) {
return {
value : value,
@@ -272,6 +267,8 @@
};
}).sort(function(left, right) {
var a = left.criteria, b = right.criteria;
+ if (a === void 0) return 1;
+ if (b === void 0) return -1;
return a < b ? -1 : a > b ? 1 : 0;
}), 'value');
};
@@ -301,26 +298,26 @@
};
// Safely convert anything iterable into a real, live array.
- _.toArray = function(iterable) {
- if (!iterable) return [];
- if (iterable.toArray) return iterable.toArray();
- if (_.isArray(iterable)) return slice.call(iterable);
- if (_.isArguments(iterable)) return slice.call(iterable);
- return _.values(iterable);
+ _.toArray = function(obj) {
+ if (!obj) return [];
+ if (_.isArray(obj)) return slice.call(obj);
+ if (_.isArguments(obj)) return slice.call(obj);
+ if (obj.toArray && _.isFunction(obj.toArray)) return obj.toArray();
+ return _.values(obj);
};
// Return the number of elements in an object.
_.size = function(obj) {
- return _.toArray(obj).length;
+ return _.isArray(obj) ? obj.length : _.keys(obj).length;
};
// Array Functions
// ---------------
// Get the first element of an array. Passing **n** will return the first N
- // values in the array. Aliased as `head`. The **guard** check allows it to work
- // with `_.map`.
- _.first = _.head = function(array, n, guard) {
+ // values in the array. Aliased as `head` and `take`. The **guard** check
+ // allows it to work with `_.map`.
+ _.first = _.head = _.take = function(array, n, guard) {
return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
};
@@ -335,7 +332,11 @@
// Get the last element of an array. Passing **n** will return the last N
// values in the array. The **guard** check allows it to work with `_.map`.
_.last = function(array, n, guard) {
- return (n != null) && !guard ? slice.call(array, array.length - n) : array[array.length - 1];
+ if ((n != null) && !guard) {
+ return slice.call(array, Math.max(array.length - n, 0));
+ } else {
+ return array[array.length - 1];
+ }
};
// Returns everything but the first entry of the array. Aliased as `tail`.
@@ -370,15 +371,17 @@
// Aliased as `unique`.
_.uniq = _.unique = function(array, isSorted, iterator) {
var initial = iterator ? _.map(array, iterator) : array;
- var result = [];
- _.reduce(initial, function(memo, el, i) {
- if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) {
- memo[memo.length] = el;
- result[result.length] = array[i];
+ var results = [];
+ // The `isSorted` flag is irrelevant if the array only contains two elements.
+ if (array.length < 3) isSorted = true;
+ _.reduce(initial, function (memo, value, index) {
+ if (isSorted ? _.last(memo) !== value || !memo.length : !_.include(memo, value)) {
+ memo.push(value);
+ results.push(array[index]);
}
return memo;
}, []);
- return result;
+ return results;
};
// Produce an array that contains the union: each distinct element from all of
@@ -398,10 +401,11 @@
});
};
- // Take the difference between one array and another.
+ // Take the difference between one array and a number of other arrays.
// Only the elements present in just the first array will remain.
- _.difference = function(array, other) {
- return _.filter(array, function(value){ return !_.include(other, value); });
+ _.difference = function(array) {
+ var rest = _.flatten(slice.call(arguments, 1), true);
+ return _.filter(array, function(value){ return !_.include(rest, value); });
};
// Zip together multiple lists into a single array -- elements that share
@@ -428,7 +432,7 @@
return array[i] === item ? i : -1;
}
if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
- for (i = 0, l = array.length; i < l; i++) if (array[i] === item) return i;
+ for (i = 0, l = array.length; i < l; i++) if (i in array && array[i] === item) return i;
return -1;
};
@@ -437,7 +441,7 @@
if (array == null) return -1;
if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item);
var i = array.length;
- while (i--) if (array[i] === item) return i;
+ while (i--) if (i in array && array[i] === item) return i;
return -1;
};
@@ -503,7 +507,7 @@
hasher || (hasher = _.identity);
return function() {
var key = hasher.apply(this, arguments);
- return hasOwnProperty.call(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
+ return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
};
};
@@ -511,7 +515,7 @@
// it with the arguments supplied.
_.delay = function(func, wait) {
var args = slice.call(arguments, 2);
- return setTimeout(function(){ return func.apply(func, args); }, wait);
+ return setTimeout(function(){ return func.apply(null, args); }, wait);
};
// Defers a function, scheduling it to run after the current call stack has
@@ -523,35 +527,42 @@
// Returns a function, that, when invoked, will only be triggered at most once
// during a given window of time.
_.throttle = function(func, wait) {
- var timeout, context, args, throttling, finishThrottle;
- finishThrottle = _.debounce(function(){ throttling = false; }, wait);
+ var context, args, timeout, throttling, more, result;
+ var whenDone = _.debounce(function(){ more = throttling = false; }, wait);
return function() {
context = this; args = arguments;
- var throttler = function() {
+ var later = function() {
timeout = null;
- func.apply(context, args);
- finishThrottle();
+ if (more) func.apply(context, args);
+ whenDone();
};
- if (!timeout) timeout = setTimeout(throttler, wait);
- if (!throttling) func.apply(context, args);
- if (finishThrottle) finishThrottle();
+ if (!timeout) timeout = setTimeout(later, wait);
+ if (throttling) {
+ more = true;
+ } else {
+ result = func.apply(context, args);
+ }
+ whenDone();
throttling = true;
+ return result;
};
};
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
- // N milliseconds.
- _.debounce = function(func, wait) {
+ // N milliseconds. If `immediate` is passed, trigger the function on the
+ // leading edge, instead of the trailing.
+ _.debounce = function(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
- var throttler = function() {
+ var later = function() {
timeout = null;
- func.apply(context, args);
+ if (!immediate) func.apply(context, args);
};
+ if (immediate && !timeout) func.apply(context, args);
clearTimeout(timeout);
- timeout = setTimeout(throttler, wait);
+ timeout = setTimeout(later, wait);
};
};
@@ -571,7 +582,7 @@
// conditionally execute the original function.
_.wrap = function(func, wrapper) {
return function() {
- var args = [func].concat(slice.call(arguments));
+ var args = [func].concat(slice.call(arguments, 0));
return wrapper.apply(this, args);
};
};
@@ -579,9 +590,9 @@
// Returns a function that is the composition of a list of functions, each
// consuming the return value of the function that follows.
_.compose = function() {
- var funcs = slice.call(arguments);
+ var funcs = arguments;
return function() {
- var args = slice.call(arguments);
+ var args = arguments;
for (var i = funcs.length - 1; i >= 0; i--) {
args = [funcs[i].apply(this, args)];
}
@@ -591,6 +602,7 @@
// Returns a function that will only be executed after being called N times.
_.after = function(times, func) {
+ if (times <= 0) return func();
return function() {
if (--times < 1) { return func.apply(this, arguments); }
};
@@ -604,7 +616,7 @@
_.keys = nativeKeys || function(obj) {
if (obj !== Object(obj)) throw new TypeError('Invalid object');
var keys = [];
- for (var key in obj) if (hasOwnProperty.call(obj, key)) keys[keys.length] = key;
+ for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key;
return keys;
};
@@ -627,12 +639,21 @@
_.extend = function(obj) {
each(slice.call(arguments, 1), function(source) {
for (var prop in source) {
- if (source[prop] !== void 0) obj[prop] = source[prop];
+ obj[prop] = source[prop];
}
});
return obj;
};
+ // Return a copy of the object only containing the whitelisted properties.
+ _.pick = function(obj) {
+ var result = {};
+ each(_.flatten(slice.call(arguments, 1)), function(key) {
+ if (key in obj) result[key] = obj[key];
+ });
+ return result;
+ };
+
// Fill in a given object with default properties.
_.defaults = function(obj) {
each(slice.call(arguments, 1), function(source) {
@@ -663,48 +684,40 @@
// See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
if (a === b) return a !== 0 || 1 / a == 1 / b;
// A strict comparison is necessary because `null == undefined`.
- if ((a == null) || (b == null)) return a === b;
+ if (a == null || b == null) return a === b;
// Unwrap any wrapped objects.
if (a._chain) a = a._wrapped;
if (b._chain) b = b._wrapped;
// Invoke a custom `isEqual` method if one is provided.
- if (_.isFunction(a.isEqual)) return a.isEqual(b);
- if (_.isFunction(b.isEqual)) return b.isEqual(a);
- // Compare object types.
- var typeA = typeof a;
- if (typeA != typeof b) return false;
- // Optimization; ensure that both values are truthy or falsy.
- if (!a != !b) return false;
- // `NaN` values are equal.
- if (_.isNaN(a)) return _.isNaN(b);
- // Compare string objects by value.
- var isStringA = _.isString(a), isStringB = _.isString(b);
- if (isStringA || isStringB) return isStringA && isStringB && String(a) == String(b);
- // Compare number objects by value.
- var isNumberA = _.isNumber(a), isNumberB = _.isNumber(b);
- if (isNumberA || isNumberB) return isNumberA && isNumberB && +a == +b;
- // Compare boolean objects by value. The value of `true` is 1; the value of `false` is 0.
- var isBooleanA = _.isBoolean(a), isBooleanB = _.isBoolean(b);
- if (isBooleanA || isBooleanB) return isBooleanA && isBooleanB && +a == +b;
- // Compare dates by their millisecond values.
- var isDateA = _.isDate(a), isDateB = _.isDate(b);
- if (isDateA || isDateB) return isDateA && isDateB && a.getTime() == b.getTime();
- // Compare RegExps by their source patterns and flags.
- var isRegExpA = _.isRegExp(a), isRegExpB = _.isRegExp(b);
- if (isRegExpA || isRegExpB) {
- // Ensure commutative equality for RegExps.
- return isRegExpA && isRegExpB &&
- a.source == b.source &&
- a.global == b.global &&
- a.multiline == b.multiline &&
- a.ignoreCase == b.ignoreCase;
+ if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b);
+ if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a);
+ // Compare `[[Class]]` names.
+ var className = toString.call(a);
+ if (className != toString.call(b)) return false;
+ switch (className) {
+ // Strings, numbers, dates, and booleans are compared by value.
+ case '[object String]':
+ // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
+ // equivalent to `new String("5")`.
+ return a == String(b);
+ case '[object Number]':
+ // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
+ // other numeric values.
+ return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
+ case '[object Date]':
+ case '[object Boolean]':
+ // Coerce dates and booleans to numeric primitive values. Dates are compared by their
+ // millisecond representations. Note that invalid dates with millisecond representations
+ // of `NaN` are not equivalent.
+ return +a == +b;
+ // RegExps are compared by their source patterns and flags.
+ case '[object RegExp]':
+ return a.source == b.source &&
+ a.global == b.global &&
+ a.multiline == b.multiline &&
+ a.ignoreCase == b.ignoreCase;
}
- // Ensure that both values are objects.
- if (typeA != 'object') return false;
- // Arrays or Arraylikes with different lengths are not equal.
- if (a.length !== b.length) return false;
- // Objects with different constructors are not equal.
- if (a.constructor !== b.constructor) return false;
+ if (typeof a != 'object' || typeof b != 'object') return false;
// Assume equality for cyclic structures. The algorithm for detecting cyclic
// structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
var length = stack.length;
@@ -716,21 +729,37 @@
// Add the first object to the stack of traversed objects.
stack.push(a);
var size = 0, result = true;
- // Deep compare objects.
- for (var key in a) {
- if (hasOwnProperty.call(a, key)) {
- // Count the expected number of properties.
- size++;
- // Deep compare each member.
- if (!(result = hasOwnProperty.call(b, key) && eq(a[key], b[key], stack))) break;
+ // Recursively compare objects and arrays.
+ if (className == '[object Array]') {
+ // Compare array lengths to determine if a deep comparison is necessary.
+ size = a.length;
+ result = size == b.length;
+ if (result) {
+ // Deep compare the contents, ignoring non-numeric properties.
+ while (size--) {
+ // Ensure commutative equality for sparse arrays.
+ if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break;
+ }
}
- }
- // Ensure that both objects contain the same number of properties.
- if (result) {
- for (key in b) {
- if (hasOwnProperty.call(b, key) && !size--) break;
+ } else {
+ // Objects with different constructors are not equivalent.
+ if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false;
+ // Deep compare objects.
+ for (var key in a) {
+ if (_.has(a, key)) {
+ // Count the expected number of properties.
+ size++;
+ // Deep compare each member.
+ if (!(result = _.has(b, key) && eq(a[key], b[key], stack))) break;
+ }
+ }
+ // Ensure that both objects contain the same number of properties.
+ if (result) {
+ for (key in b) {
+ if (_.has(b, key) && !(size--)) break;
+ }
+ result = !size;
}
- result = !size;
}
// Remove the first object from the stack of traversed objects.
stack.pop();
@@ -745,8 +774,9 @@
// Is a given array, string, or object empty?
// An "empty" object has no enumerable own-properties.
_.isEmpty = function(obj) {
+ if (obj == null) return true;
if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
- for (var key in obj) if (hasOwnProperty.call(obj, key)) return false;
+ for (var key in obj) if (_.has(obj, key)) return false;
return true;
};
@@ -767,13 +797,12 @@
};
// Is a given variable an arguments object?
- if (toString.call(arguments) == '[object Arguments]') {
- _.isArguments = function(obj) {
- return toString.call(obj) == '[object Arguments]';
- };
- } else {
+ _.isArguments = function(obj) {
+ return toString.call(obj) == '[object Arguments]';
+ };
+ if (!_.isArguments(arguments)) {
_.isArguments = function(obj) {
- return !!(obj && hasOwnProperty.call(obj, 'callee'));
+ return !!(obj && _.has(obj, 'callee'));
};
}
@@ -792,6 +821,11 @@
return toString.call(obj) == '[object Number]';
};
+ // Is a given object a finite number?
+ _.isFinite = function(obj) {
+ return _.isNumber(obj) && isFinite(obj);
+ };
+
// Is the given value `NaN`?
_.isNaN = function(obj) {
// `NaN` is the only value for which `===` is not reflexive.
@@ -823,6 +857,11 @@
return obj === void 0;
};
+ // Has own property?
+ _.has = function(obj, key) {
+ return hasOwnProperty.call(obj, key);
+ };
+
// Utility Functions
// -----------------
@@ -845,7 +884,15 @@
// Escape a string for HTML interpolation.
_.escape = function(string) {
- return (''+string).replace(/&(?!\w+;|#\d+;|#x[\da-f]+;)/gi, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#x27;').replace(/\//g,'&#x2F;');
+ return (''+string).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#x27;').replace(/\//g,'&#x2F;');
+ };
+
+ // If the value of the named property is a function then invoke it;
+ // otherwise, return it.
+ _.result = function(object, property) {
+ if (object == null) return null;
+ var value = object[property];
+ return _.isFunction(value) ? value.call(object) : value;
};
// Add your own custom functions to the Underscore object, ensuring that
@@ -872,31 +919,82 @@
escape : /<%-([\s\S]+?)%>/g
};
+ // When customizing `templateSettings`, if you don't want to define an
+ // interpolation, evaluation or escaping regex, we need one that is
+ // guaranteed not to match.
+ var noMatch = /.^/;
+
+ // Certain characters need to be escaped so that they can be put into a
+ // string literal.
+ var escapes = {
+ '\\': '\\',
+ "'": "'",
+ 'r': '\r',
+ 'n': '\n',
+ 't': '\t',
+ 'u2028': '\u2028',
+ 'u2029': '\u2029'
+ };
+
+ for (var p in escapes) escapes[escapes[p]] = p;
+ var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
+ var unescaper = /\\(\\|'|r|n|t|u2028|u2029)/g;
+
+ // Within an interpolation, evaluation, or escaping, remove HTML escaping
+ // that had been previously added.
+ var unescape = function(code) {
+ return code.replace(unescaper, function(match, escape) {
+ return escapes[escape];
+ });
+ };
+
// JavaScript micro-templating, similar to John Resig's implementation.
// Underscore templating handles arbitrary delimiters, preserves whitespace,
// and correctly escapes quotes within interpolated code.
- _.template = function(str, data) {
- var c = _.templateSettings;
- var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' +
- 'with(obj||{}){__p.push(\'' +
- str.replace(/\\/g, '\\\\')
- .replace(/'/g, "\\'")
- .replace(c.escape, function(match, code) {
- return "',_.escape(" + code.replace(/\\'/g, "'") + "),'";
- })
- .replace(c.interpolate, function(match, code) {
- return "'," + code.replace(/\\'/g, "'") + ",'";
- })
- .replace(c.evaluate || null, function(match, code) {
- return "');" + code.replace(/\\'/g, "'")
- .replace(/[\r\n\t]/g, ' ') + "__p.push('";
- })
- .replace(/\r/g, '\\r')
- .replace(/\n/g, '\\n')
- .replace(/\t/g, '\\t')
- + "');}return __p.join('');";
- var func = new Function('obj', tmpl);
- return data ? func(data) : func;
+ _.template = function(text, data, settings) {
+ settings = _.defaults(settings || {}, _.templateSettings);
+
+ // Compile the template source, taking care to escape characters that
+ // cannot be included in a string literal and then unescape them in code
+ // blocks.
+ var source = "__p+='" + text
+ .replace(escaper, function(match) {
+ return '\\' + escapes[match];
+ })
+ .replace(settings.escape || noMatch, function(match, code) {
+ return "'+\n_.escape(" + unescape(code) + ")+\n'";
+ })
+ .replace(settings.interpolate || noMatch, function(match, code) {
+ return "'+\n(" + unescape(code) + ")+\n'";
+ })
+ .replace(settings.evaluate || noMatch, function(match, code) {
+ return "';\n" + unescape(code) + "\n;__p+='";
+ }) + "';\n";
+
+ // If a variable is not specified, place data values in local scope.
+ if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
+
+ source = "var __p='';" +
+ "var print=function(){__p+=Array.prototype.join.call(arguments, '')};\n" +
+ source + "return __p;\n";
+
+ var render = new Function(settings.variable || 'obj', '_', source);
+ if (data) return render(data, _);
+ var template = function(data) {
+ return render.call(this, data, _);
+ };
+
+ // Provide the compiled function source as a convenience for build time
+ // precompilation.
+ template.source = 'function(' + (settings.variable || 'obj') + '){\n' +
+ source + '}';
+
+ return template;
+ };
+
+ // Add a "chain" function, which will delegate to the wrapper.
+ _.chain = function(obj) {
+ return _(obj).chain();
};
// The OOP Wrapper
@@ -931,8 +1029,11 @@
each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
var method = ArrayProto[name];
wrapper.prototype[name] = function() {
- method.apply(this._wrapped, arguments);
- return result(this._wrapped, this._chain);
+ var wrapped = this._wrapped;
+ method.apply(wrapped, arguments);
+ var length = wrapped.length;
+ if ((name == 'shift' || name == 'splice') && length === 0) delete wrapped[0];
+ return result(wrapped, this._chain);
};
});
@@ -955,4 +1056,4 @@
return this._wrapped;
};
-})();
+}).call(this);

No commit comments for this range

Something went wrong with that request. Please try again.