diff --git a/vendor/underscore/.eslintrc b/vendor/underscore/.eslintrc
new file mode 100644
index 000000000..5ab39e461
--- /dev/null
+++ b/vendor/underscore/.eslintrc
@@ -0,0 +1,93 @@
+{
+ "env": {
+ "browser": true,
+ "node": true,
+ "amd": true
+ },
+
+ "rules": {
+ "array-bracket-spacing": [2],
+ "block-scoped-var": 1,
+ "brace-style": [1, "1tbs"],
+ "camelcase": 2,
+ "comma-dangle": [2, "never"],
+ "comma-spacing": 2,
+ "computed-property-spacing": [2, "never"],
+ "consistent-return": 1,
+ "dot-notation": [2, { "allowKeywords": false }],
+ "eol-last": 2,
+ "eqeqeq": [2, "smart"],
+ "indent": [2, 2, {"SwitchCase": 1, "VariableDeclarator": 2}],
+ "key-spacing": 1,
+ "linebreak-style": 2,
+ "max-depth": [1, 4],
+ "max-params": [1, 5],
+ "new-cap": 2,
+ "no-alert": 2,
+ "no-caller": 2,
+ "no-catch-shadow": 2,
+ "no-console": 2,
+ "no-debugger": 2,
+ "no-delete-var": 2,
+ "no-div-regex": 1,
+ "no-dupe-args": 2,
+ "no-dupe-keys": 2,
+ "no-duplicate-case": 2,
+ "no-else-return": 1,
+ "no-empty-character-class": 2,
+ "no-empty-label": 2,
+ "no-eval": 2,
+ "no-ex-assign": 2,
+ "no-extend-native": 2,
+ "no-extra-boolean-cast": 2,
+ "no-extra-parens": 1,
+ "no-extra-semi": 2,
+ "no-fallthrough": 2,
+ "no-floating-decimal": 2,
+ "no-func-assign": 2,
+ "no-implied-eval": 2,
+ "no-inner-declarations": 2,
+ "no-irregular-whitespace": 2,
+ "no-label-var": 2,
+ "no-lone-blocks": 2,
+ "no-lonely-if": 2,
+ "no-multi-spaces": 1,
+ "no-multi-str": 2,
+ "no-native-reassign": 2,
+ "no-negated-in-lhs": 1,
+ "no-nested-ternary": 2,
+ "no-new-object": 2,
+ "no-new-wrappers": 2,
+ "no-obj-calls": 2,
+ "no-octal": 2,
+ "no-octal-escape": 2,
+ "no-proto": 2,
+ "no-redeclare": 2,
+ "no-shadow": 2,
+ "no-spaced-func": 2,
+ "no-throw-literal": 2,
+ "no-trailing-spaces": 2,
+ "no-undef": 2,
+ "no-undef-init": 2,
+ "no-undefined": 2,
+ "no-unneeded-ternary": 2,
+ "no-unreachable": 2,
+ "no-unused-expressions": 2,
+ "no-unused-vars": 2,
+ "no-use-before-define": [1, "nofunc"],
+ "no-with": 2,
+ "object-curly-spacing": [2, "never"],
+ "quote-props": [1, "as-needed"],
+ "quotes": [2, "single", "avoid-escape"],
+ "radix": 2,
+ "semi": 2,
+ "space-after-keywords": [2, "always"],
+ "space-before-function-paren": [2, {"anonymous": "never", "named": "never"}],
+ "space-infix-ops": 2,
+ "space-return-throw-case": 2,
+ "space-unary-ops": [2, { "words": true, "nonwords": false }],
+ "use-isnan": 2,
+ "valid-typeof": 2,
+ "wrap-iife": 2
+ }
+}
diff --git a/vendor/underscore/.gitignore b/vendor/underscore/.gitignore
index 029616c3c..ab5b5c583 100644
--- a/vendor/underscore/.gitignore
+++ b/vendor/underscore/.gitignore
@@ -1,2 +1,7 @@
raw
node_modules
+*.log
+*.idea
+*.swp
+nyc_output
+coverage
diff --git a/vendor/underscore/.travis.yml b/vendor/underscore/.travis.yml
index 99dc7712c..f0e6ad148 100644
--- a/vendor/underscore/.travis.yml
+++ b/vendor/underscore/.travis.yml
@@ -1,5 +1,30 @@
language: node_js
+sudo: false
node_js:
- - 0.8
+ - "0.8"
+ - "0.10"
+ - "0.12"
+matrix:
+ include:
+ - node_js: "4.0"
+ env: BROWSER=true
+before_install:
+ - npm install -g npm@2.6
+ - npm install -g karma-cli
+before_script:
+ - npm install karma-sauce-launcher
+ - export DISPLAY=:99.0
+ - sh -e /etc/init.d/xvfb start
+script:
+ - npm test
+ - "[ $BROWSER == false ] || npm run test-browser"
+ - "[ $BROWSER == false ] || karma start karma.conf-sauce.js"
notifications:
email: false
+env:
+ global:
+ - NPM_CONFIG_PROGRESS="false"
+ - secure: bDZSBQfqr21hCayjcZ20IxrV6+XGhxQPFIfwWqEKLrF93Gu8LLVjZRxXE/mE8I8N4Z5WtDNb4ZHrm/TTzmcPa5MuHgIxEdknQCncobH8oimwc83SHwEPk6okeNKl39VlCjvvnmoe/V/KpnknuYn3Rqghtl/Uv9KLpCwskwjTtcw=
+ - secure: SRECgXuwcZTcD3GVxTS2bYNgRyye4vq6BLrV2PH9FyNenowsKQR2EwlC/dppc1Q8NWMgv79J/R96q9JOFh+mEH9L5dlBb2yhnGH8amVeM/ChAJHT/F8YktKM453uVpz5fR00QcCQDDUOx6Pvx374ID0OKNpWKAkQBWA9mPTsLnE=
+ matrix: BROWSER=false
+after_success: npm run coveralls
diff --git a/vendor/underscore/CODE_OF_CONDUCT.md b/vendor/underscore/CODE_OF_CONDUCT.md
new file mode 100644
index 000000000..6b777fb6d
--- /dev/null
+++ b/vendor/underscore/CODE_OF_CONDUCT.md
@@ -0,0 +1,93 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, gender identity and expression, level of experience,
+nationality, personal appearance, race, religion, or sexual identity and
+orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at jashkenas@gmail.com. The project team
+will review and investigate all complaints, and will respond in a way that it deems
+appropriate to the circumstances. The project team is obligated to maintain
+confidentiality with regard to the reporter of an incident.
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Moderation
+
+Edits of another user's comment must be clearly marked with "**edit**", the
+moderator's username, and a timestamp for each occurrence. The only acceptable
+reasons for editing another user's comment are:
+
+1. to edit out violations of Our Pledge. These edits must include a rationale.
+2. to direct future readers to a relevant point later in the conversation
+ (usually the resolution). These edits must be append-only.
+
+Deletion of another user's comment is only acceptable when the comment includes
+no original value, such as "+1", ":+1:", or "me too".
+
+## Self-Moderation
+
+Edits of your own comment after someone has responded must be append-only and
+clearly marked with "**edit**". Typographical and formatting fixes to your own
+comment which do not affect its meaning are exempt from this requirement.
+Deletion of your own comment is only acceptable before any later comments have
+been posted.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
+available at [http://contributor-covenant.org/version/1/4][version]
+
+[homepage]: http://contributor-covenant.org
+[version]: http://contributor-covenant.org/version/1/4/
diff --git a/vendor/underscore/CONTRIBUTING.md b/vendor/underscore/CONTRIBUTING.md
index e133ebea1..7e9a579e4 100644
--- a/vendor/underscore/CONTRIBUTING.md
+++ b/vendor/underscore/CONTRIBUTING.md
@@ -1,7 +1,11 @@
## How to contribute to Underscore.js
+* This project adheres to a [code of conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code.
+
* Before you open a ticket or send a pull request, [search](https://github.com/jashkenas/underscore/issues) for previous discussions about the same feature or issue. Add to the earlier ticket if you find one.
+* If you're proposing a new feature, make sure it isn't already implemented in [Underscore-Contrib](https://github.com/documentcloud/underscore-contrib).
+
* Before sending a pull request for a feature, be sure to have [tests](http://underscorejs.org/test/).
* Use the same coding style as the rest of the [codebase](https://github.com/jashkenas/underscore/blob/master/underscore.js).
diff --git a/vendor/underscore/LICENSE b/vendor/underscore/LICENSE
index 3acf90838..04be0d9fb 100644
--- a/vendor/underscore/LICENSE
+++ b/vendor/underscore/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative
+Copyright (c) 2009-2017 Jeremy Ashkenas, DocumentCloud and Investigative
Reporters & Editors
Permission is hereby granted, free of charge, to any person
diff --git a/vendor/underscore/README.md b/vendor/underscore/README.md
index c2ba2590c..a99db1895 100644
--- a/vendor/underscore/README.md
+++ b/vendor/underscore/README.md
@@ -12,9 +12,15 @@ Underscore.js is a utility-belt library for JavaScript that provides
support for the usual functional suspects (each, map, reduce, filter...)
without extending any core JavaScript objects.
+This project adheres to a [code of conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code.
+
For Docs, License, Tests, and pre-packed downloads, see:
http://underscorejs.org
+For support and questions, please use
+[the gitter channel](https://gitter.im/jashkenas/underscore)
+or [stackoverflow](http://stackoverflow.com/search?q=underscore.js)
+
Underscore is an open-sourced component of DocumentCloud:
https://github.com/documentcloud
diff --git a/vendor/underscore/bower.json b/vendor/underscore/bower.json
new file mode 100644
index 000000000..d5e1d7852
--- /dev/null
+++ b/vendor/underscore/bower.json
@@ -0,0 +1,6 @@
+{
+ "name": "underscore",
+ "main": "underscore.js",
+ "keywords": ["util", "functional", "server", "client", "browser"],
+ "ignore" : ["docs", "test", "*.yml", "CNAME", "index.html", "favicon.ico", "CONTRIBUTING.md", ".*", "package.json", "karma.*"]
+}
diff --git a/vendor/underscore/docs/.eslintrc b/vendor/underscore/docs/.eslintrc
new file mode 100644
index 000000000..aac42a91c
--- /dev/null
+++ b/vendor/underscore/docs/.eslintrc
@@ -0,0 +1,5 @@
+{
+ "globals": {
+ "_": true
+ }
+}
diff --git a/vendor/underscore/docs/docco.css b/vendor/underscore/docs/docco.css
index f690a0794..b60f6fa3d 100644
--- a/vendor/underscore/docs/docco.css
+++ b/vendor/underscore/docs/docco.css
@@ -21,11 +21,11 @@
}
@font-face {
- font-family: 'novecento-bold';
- src: url('public/fonts/novecento-bold.eot');
- src: url('public/fonts/novecento-bold.eot?#iefix') format('embedded-opentype'),
- url('public/fonts/novecento-bold.woff') format('woff'),
- url('public/fonts/novecento-bold.ttf') format('truetype');
+ font-family: 'roboto-black';
+ src: url('public/fonts/roboto-black.eot');
+ src: url('public/fonts/roboto-black.eot?#iefix') format('embedded-opentype'),
+ url('public/fonts/roboto-black.woff') format('woff'),
+ url('public/fonts/roboto-black.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}
@@ -51,15 +51,23 @@ b, strong {
font-family: "aller-bold";
}
-p, ul, ol {
+p {
margin: 15px 0 0px;
}
+ .annotation ul, .annotation ol {
+ margin: 25px 0;
+ }
+ .annotation ul li, .annotation ol li {
+ font-size: 14px;
+ line-height: 18px;
+ margin: 10px 0;
+ }
h1, h2, h3, h4, h5, h6 {
color: #112233;
line-height: 1em;
font-weight: normal;
- font-family: "novecento-bold";
+ font-family: "roboto-black";
text-transform: uppercase;
margin: 30px 0 15px 0;
}
@@ -67,10 +75,13 @@ h1, h2, h3, h4, h5, h6 {
h1 {
margin-top: 40px;
}
+h2 {
+ font-size: 1.26em;
+}
hr {
border: 0;
- background: 1px solid #ddd;
+ background: 1px #ddd;
height: 1px;
margin: 20px 0;
}
@@ -172,9 +183,18 @@ ul.sections > li > div {
display: block;
}
+#jump_page_wrapper{
+ position: fixed;
+ right: 0;
+ top: 0;
+ bottom: 0;
+}
+
#jump_page {
padding: 5px 0 3px;
margin: 0 0 25px 25px;
+ max-height: 100%;
+ overflow: auto;
}
#jump_page .source {
@@ -205,7 +225,6 @@ ul.sections > li > div {
}
ul.sections > li > div.content {
- background: #f5f5ff;
overflow-x:auto;
-webkit-box-shadow: inset 0 0 5px #e5e5ee;
box-shadow: inset 0 0 5px #e5e5ee;
@@ -306,7 +325,6 @@ ul.sections > li > div {
ul.sections > li > div.content {
padding: 13px;
vertical-align: top;
- background: #f5f5ff;
border: none;
-webkit-box-shadow: none;
box-shadow: none;
@@ -376,125 +394,125 @@ pre code {
background: #f8f8ff
}
-pre .comment,
-pre .template_comment,
-pre .diff .header,
-pre .javadoc {
+pre .hljs-comment,
+pre .hljs-template_comment,
+pre .hljs-diff .hljs-header,
+pre .hljs-javadoc {
color: #408080;
font-style: italic
}
-pre .keyword,
-pre .assignment,
-pre .literal,
-pre .css .rule .keyword,
-pre .winutils,
-pre .javascript .title,
-pre .lisp .title,
-pre .subst {
+pre .hljs-keyword,
+pre .hljs-assignment,
+pre .hljs-literal,
+pre .hljs-css .hljs-rule .hljs-keyword,
+pre .hljs-winutils,
+pre .hljs-javascript .hljs-title,
+pre .hljs-lisp .hljs-title,
+pre .hljs-subst {
color: #954121;
/*font-weight: bold*/
}
-pre .number,
-pre .hexcolor {
+pre .hljs-number,
+pre .hljs-hexcolor {
color: #40a070
}
-pre .string,
-pre .tag .value,
-pre .phpdoc,
-pre .tex .formula {
+pre .hljs-string,
+pre .hljs-tag .hljs-value,
+pre .hljs-phpdoc,
+pre .hljs-tex .hljs-formula {
color: #219161;
}
-pre .title,
-pre .id {
+pre .hljs-title,
+pre .hljs-id {
color: #19469D;
}
-pre .params {
+pre .hljs-params {
color: #00F;
}
-pre .javascript .title,
-pre .lisp .title,
-pre .subst {
+pre .hljs-javascript .hljs-title,
+pre .hljs-lisp .hljs-title,
+pre .hljs-subst {
font-weight: normal
}
-pre .class .title,
-pre .haskell .label,
-pre .tex .command {
+pre .hljs-class .hljs-title,
+pre .hljs-haskell .hljs-label,
+pre .hljs-tex .hljs-command {
color: #458;
font-weight: bold
}
-pre .tag,
-pre .tag .title,
-pre .rules .property,
-pre .django .tag .keyword {
+pre .hljs-tag,
+pre .hljs-tag .hljs-title,
+pre .hljs-rules .hljs-property,
+pre .hljs-django .hljs-tag .hljs-keyword {
color: #000080;
font-weight: normal
}
-pre .attribute,
-pre .variable,
-pre .instancevar,
-pre .lisp .body {
+pre .hljs-attribute,
+pre .hljs-variable,
+pre .hljs-instancevar,
+pre .hljs-lisp .hljs-body {
color: #008080
}
-pre .regexp {
+pre .hljs-regexp {
color: #B68
}
-pre .class {
+pre .hljs-class {
color: #458;
font-weight: bold
}
-pre .symbol,
-pre .ruby .symbol .string,
-pre .ruby .symbol .keyword,
-pre .ruby .symbol .keymethods,
-pre .lisp .keyword,
-pre .tex .special,
-pre .input_number {
+pre .hljs-symbol,
+pre .hljs-ruby .hljs-symbol .hljs-string,
+pre .hljs-ruby .hljs-symbol .hljs-keyword,
+pre .hljs-ruby .hljs-symbol .hljs-keymethods,
+pre .hljs-lisp .hljs-keyword,
+pre .hljs-tex .hljs-special,
+pre .hljs-input_number {
color: #990073
}
-pre .builtin,
-pre .constructor,
-pre .built_in,
-pre .lisp .title {
+pre .hljs-builtin,
+pre .hljs-constructor,
+pre .hljs-built_in,
+pre .hljs-lisp .hljs-title {
color: #0086b3
}
-pre .preprocessor,
-pre .pi,
-pre .doctype,
-pre .shebang,
-pre .cdata {
+pre .hljs-preprocessor,
+pre .hljs-pi,
+pre .hljs-doctype,
+pre .hljs-shebang,
+pre .hljs-cdata {
color: #999;
font-weight: bold
}
-pre .deletion {
+pre .hljs-deletion {
background: #fdd
}
-pre .addition {
+pre .hljs-addition {
background: #dfd
}
-pre .diff .change {
+pre .hljs-diff .hljs-change {
background: #0086b3
}
-pre .chunk {
+pre .hljs-chunk {
color: #aaa
}
-pre .tex .formula {
+pre .hljs-tex .hljs-formula {
opacity: 0.5;
}
diff --git a/vendor/underscore/docs/main.js b/vendor/underscore/docs/main.js
new file mode 100644
index 000000000..009cd0ad0
--- /dev/null
+++ b/vendor/underscore/docs/main.js
@@ -0,0 +1,63 @@
+(function() {
+ var functions = document.querySelectorAll('[data-name]');
+ var sections = document.querySelectorAll('.searchable_section');
+ var searchInput = document.getElementById('function_filter');
+
+ function searchValue() {
+ return searchInput.value.trim().replace(/^_\.?/, '');
+ }
+
+ function strIn(a, b) {
+ a = a.toLowerCase();
+ b = b.toLowerCase();
+ return b.indexOf(a) >= 0;
+ }
+
+ function doesMatch(element) {
+ var name = element.getAttribute('data-name');
+ var aliases = element.getAttribute('data-aliases') || '';
+ var value = searchValue();
+ return strIn(value, name) || strIn(value, aliases);
+ }
+
+ function filterElement(element) {
+ element.style.display = doesMatch(element) ? '' : 'none';
+ }
+
+ function filterToc() {
+ _.each(functions, filterElement);
+
+ var emptySearch = searchValue() === '';
+
+ // Hide the titles of empty sections
+ _.each(sections, function(section) {
+ var sectionFunctions = section.querySelectorAll('[data-name]');
+ var showSection = emptySearch || _.some(sectionFunctions, doesMatch);
+ section.style.display = showSection ? '' : 'none';
+ });
+ }
+
+ function gotoFirst() {
+ var firstFunction = _.find(functions, doesMatch);
+ if (firstFunction) {
+ window.location.hash = firstFunction.getAttribute('data-name');
+ searchInput.focus();
+ }
+ }
+
+ searchInput.addEventListener('input', filterToc, false);
+
+ // Press "Enter" to jump to the first matching function
+ searchInput.addEventListener('keypress', function(e) {
+ if (e.which === 13) {
+ gotoFirst();
+ }
+ });
+
+ // Press "/" to search
+ document.body.addEventListener('keyup', function(event) {
+ if (191 === event.which) {
+ searchInput.focus();
+ }
+ });
+}());
diff --git a/vendor/underscore/docs/public/fonts/roboto-black.eot b/vendor/underscore/docs/public/fonts/roboto-black.eot
new file mode 100644
index 000000000..571ed4912
Binary files /dev/null and b/vendor/underscore/docs/public/fonts/roboto-black.eot differ
diff --git a/vendor/underscore/docs/public/fonts/roboto-black.ttf b/vendor/underscore/docs/public/fonts/roboto-black.ttf
new file mode 100644
index 000000000..e0300b3ee
Binary files /dev/null and b/vendor/underscore/docs/public/fonts/roboto-black.ttf differ
diff --git a/vendor/underscore/docs/public/fonts/roboto-black.woff b/vendor/underscore/docs/public/fonts/roboto-black.woff
new file mode 100644
index 000000000..642e5b60f
Binary files /dev/null and b/vendor/underscore/docs/public/fonts/roboto-black.woff differ
diff --git a/vendor/underscore/docs/underscore.html b/vendor/underscore/docs/underscore.html
index b96d40874..2170598b4 100644
--- a/vendor/underscore/docs/underscore.html
+++ b/vendor/underscore/docs/underscore.html
@@ -27,14 +27,15 @@
- Underscore.js 1.5.2
-http://underscorejs.org
-(c) 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
-Underscore may be freely distributed under the MIT license.
-
+ Underscore.js 1.8 .3
+http:
+(c) 2009 -2017 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+Underscore may be freely distributed under the MIT license.
+
-
+
@@ -42,10 +43,10 @@ Underscore may be freely distributed under the MIT license.
-
+
-
Baseline setup
+
Baseline setup
@@ -58,12 +59,9 @@ Underscore may be freely distributed under the MIT license.
-
Establish the root object, window
in the browser, or exports
on the server.
-
+
-
-
@@ -73,11 +71,11 @@ Underscore may be freely distributed under the MIT license.
- Save the previous value of the _
variable.
+ Establish the root object, window
in the browser, or exports
on the server.
- var previousUnderscore = root._;
+
@@ -88,11 +86,11 @@ Underscore may be freely distributed under the MIT license.
- Establish the object that gets returned to break out of a loop iteration.
+ Save the previous value of the _
variable.
-
+ var previousUnderscore = root._;
@@ -107,7 +105,7 @@ Underscore may be freely distributed under the MIT license.
- var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
+ var ArrayProto = Array .prototype, ObjProto = Object .prototype, FuncProto = Function .prototype;
@@ -122,10 +120,9 @@ Underscore may be freely distributed under the MIT license.
- var
+ var
push = ArrayProto.push,
slice = ArrayProto.slice,
- concat = ArrayProto.concat,
toString = ObjProto.toString,
hasOwnProperty = ObjProto.hasOwnProperty;
@@ -143,19 +140,11 @@ are declared here.
-
var
- nativeForEach = ArrayProto.forEach,
- nativeMap = ArrayProto.map,
- nativeReduce = ArrayProto.reduce,
- nativeReduceRight = ArrayProto.reduceRight,
- nativeFilter = ArrayProto.filter,
- nativeEvery = ArrayProto.every,
- nativeSome = ArrayProto.some,
- nativeIndexOf = ArrayProto.indexOf,
- nativeLastIndexOf = ArrayProto.lastIndexOf,
- nativeIsArray = Array.isArray,
- nativeKeys = Object.keys,
- nativeBind = FuncProto.bind;
+
var
+ nativeIsArray = Array .isArray,
+ nativeKeys = Object .keys,
+ nativeBind = FuncProto.bind,
+ nativeCreate = Object .create;
@@ -166,15 +155,11 @@ are declared here.
-
Create a safe reference to the Underscore object for use below.
+
Naked function reference for surrogate-prototype-swapping.
- var _ = function (obj) {
- if (obj instanceof _) return obj;
- if (!(this instanceof _)) return new _(obj);
- this ._wrapped = obj;
- };
+
@@ -185,21 +170,15 @@ are declared here.
- 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.
+ Create a safe reference to the Underscore object for use below.
- if (typeof exports !== 'undefined' ) {
- if (typeof module !== 'undefined' && module.exports) {
- exports = module.exports = _;
- }
- exports._ = _;
- } else {
- root._ = _;
- }
+ var _ = function (obj) {
+ if (obj instanceof _) return obj;
+ if (!(this instanceof _)) return new _(obj);
+ this ._wrapped = obj;
+ };
@@ -210,11 +189,20 @@ for Closure Compiler "advanced" mode.
- Current version.
+ 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.
-
+ if (typeof exports !== 'undefined' ) {
+ if (typeof module !== 'undefined' && module .exports) {
+ exports = module .exports = _;
+ }
+ exports._ = _;
+ } else {
+ root._ = _;
+ }
@@ -222,13 +210,15 @@ for Closure Compiler "advanced" mode.
-
+
-
Collection Functions
+
Current version.
+
+
@@ -238,26 +228,31 @@ for Closure Compiler "advanced" mode.
-
The cornerstone, an each
implementation, aka forEach
.
-Handles objects with the built-in forEach
, arrays, and raw objects.
-Delegates to ECMAScript 5 's native forEach
if available.
+
Internal function that returns an efficient (for current engines) version
+of the passed-in callback, to be repeatedly applied in other Underscore
+functions.
- var each = _.each = _.forEach = function (obj, iterator, context) {
- if (obj == null ) return ;
- if (nativeForEach && obj.forEach === nativeForEach) {
- obj.forEach(iterator, context);
- } else if (obj.length === +obj.length) {
- for (var i = 0 , length = obj.length; i < length; i++) {
- if (iterator.call(context, obj[i], i, obj) === breaker) return ;
- }
- } else {
- var keys = _.keys(obj);
- for (var i = 0 , length = keys.length; i < length; i++) {
- if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return ;
- }
+ var optimizeCb = function (func, context, argCount) {
+ if (context === void 0 ) return func;
+ switch (argCount == null ? 3 : argCount) {
+ case 1 : return function (value) {
+ return func.call(context, value);
+ };
+ case 2 : return function (value, other) {
+ return func.call(context, value, other);
+ };
+ case 3 : return function (value, index, collection) {
+ return func.call(context, value, index, collection);
+ };
+ case 4 : return function (accumulator, value, index, collection) {
+ return func.call(context, accumulator, value, index, collection);
+ };
}
+ return function () {
+ return func.apply(context, arguments );
+ };
};
@@ -269,22 +264,21 @@ Delegates to ECMAScript 5 's native forEach
if
- Return the results of applying the iterator to each element.
-Delegates to ECMAScript 5 's native map
if available.
+ A mostly-internal function to generate callbacks that can be applied
+to each element in a collection, returning the desired result — either
+identity, an arbitrary callback, a property matcher, or a property accessor.
-
_.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.push(iterator.call(context, value, index, list));
- });
- return results;
+ var cb = function (value, context, argCount) {
+ if (value == null ) return _.identity;
+ if (_.isFunction(value)) return optimizeCb(value, context, argCount);
+ if (_.isObject(value)) return _.matcher(value);
+ return _.property(value);
};
-
- var reduceError = 'Reduce of empty array with no initial value' ;
+ _.iteratee = function (value, context) {
+ return cb(value, context, Infinity );
+ };
@@ -295,28 +289,25 @@ Delegates to
ECMAScript 5 's native
map
if avai
-
Reduce builds up a single result from a list of values, aka inject
,
-or foldl
. Delegates to ECMAScript 5 's native reduce
if available.
+
An internal function for creating assigner functions.
- _.reduce = _.foldl = _.inject = function (obj, iterator, memo, context) {
- var initial = arguments.length > 2 ;
- if (obj == null ) obj = [];
- if (nativeReduce && obj.reduce === nativeReduce) {
- if (context) iterator = _.bind(iterator, context);
- return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
- }
- each(obj, function (value, index, list) {
- if (!initial) {
- memo = value;
- initial = true ;
- } else {
- memo = iterator.call(context, memo, value, index, list);
+ var createAssigner = function (keysFunc, undefinedOnly) {
+ return function (obj) {
+ var length = arguments .length;
+ if (length < 2 || obj == null ) return obj;
+ for (var index = 1 ; index < length; index++) {
+ var source = arguments [index],
+ keys = keysFunc(source),
+ l = keys.length;
+ for (var i = 0 ; i < l; i++) {
+ var key = keys[i];
+ if (!undefinedOnly || obj[key] === void 0 ) obj[key] = source[key];
+ }
}
- });
- if (!initial) throw new TypeError(reduceError);
- return memo;
+ return obj;
+ };
};
@@ -328,34 +319,23 @@ or foldl
. Delegates to ECMAScript 5 's native <
- The right-associative version of reduce, also known as foldr
.
-Delegates to ECMAScript 5 's native reduceRight
if available.
+ An internal function for creating a new object that inherits from another.
-
_.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 initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
- }
- var length = obj.length;
- if (length !== +length) {
- var keys = _.keys(obj);
- length = keys.length;
- }
- each(obj, function (value, index, list) {
- index = keys ? keys[--length] : --length;
- if (!initial) {
- memo = obj[index];
- initial = true ;
- } else {
- memo = iterator.call(context, memo, obj[index], index, list);
- }
- });
- if (!initial) throw new TypeError(reduceError);
- return memo;
+ var baseCreate = function (prototype) {
+ if (!_.isObject(prototype)) return {};
+ if (nativeCreate) return nativeCreate(prototype);
+ Ctor.prototype = prototype;
+ var result = new Ctor;
+ Ctor.prototype = null ;
+ return result;
+ };
+
+ var property = function (key) {
+ return function (obj) {
+ return obj == null ? void 0 : obj[key];
+ };
};
@@ -367,19 +347,18 @@ Delegates to ECMAScript 5 's native reduceRight
- Return the first value which passes a truth test. Aliased as detect
.
+ Helper for collection methods to determine whether a collection
+should be iterated as an array or as an object
+Related: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength
+Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094
-
_.find = _.detect = function (obj, iterator, context) {
- var result;
- any(obj, function (value, index, list) {
- if (iterator.call(context, value, index, list)) {
- result = value;
- return true ;
- }
- });
- return result;
+ var MAX_ARRAY_INDEX = Math .pow(2 , 53 ) - 1 ;
+ var getLength = property('length' );
+ var isArrayLike = function (collection) {
+ var length = getLength(collection);
+ return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
};
@@ -391,22 +370,10 @@ Delegates to ECMAScript 5 's native reduceRight
- Return all the elements that pass a truth test.
-Delegates to ECMAScript 5 's native filter
if available.
-Aliased as select
.
+ Collection Functions
-
_.filter = _.select = function (obj, iterator, context) {
- var results = [];
- if (obj == null ) return results;
- if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
- each(obj, function (value, index, list) {
- if (iterator.call(context, value, index, list)) results.push(value);
- });
- return results;
- };
-
@@ -416,16 +383,9 @@ Aliased as
select
.
-
Return all the elements for which a truth test fails.
-
+
-
_.reject = function (obj, iterator, context) {
- return _.filter(obj, function (value, index, list) {
- return !iterator.call(context, value, index, list);
- }, context);
- };
-
@@ -435,21 +395,26 @@ Aliased as
select
.
-
Determine whether all of the elements match a truth test.
-Delegates to ECMAScript 5 's native every
if available.
-Aliased as all
.
+
The cornerstone, an each
implementation, aka forEach
.
+Handles raw objects in addition to array-likes. Treats all
+sparse array-likes as if they were dense.
-
_.every = _.all = function (obj, iterator, context) {
- iterator || (iterator = _.identity);
- var result = true ;
- if (obj == null ) return result;
- if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
- each(obj, function (value, index, list) {
- if (!(result = result && iterator.call(context, value, index, list))) return breaker;
- });
- return !!result;
+ _.each = _.forEach = function (obj, iteratee, context) {
+ iteratee = optimizeCb(iteratee, context);
+ var i, length;
+ if (isArrayLike(obj)) {
+ for (i = 0 , length = obj.length; i < length; i++) {
+ iteratee(obj[i], i, obj);
+ }
+ } else {
+ var keys = _.keys(obj);
+ for (i = 0 , length = keys.length; i < length; i++) {
+ iteratee(obj[keys[i]], keys[i], obj);
+ }
+ }
+ return obj;
};
@@ -461,21 +426,20 @@ Aliased as all
.
- 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
.
+ Return the results of applying the iteratee to each element.
-
var any = _.some = _.any = function (obj, iterator, context) {
- 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 || (result = iterator.call(context, value, index, list))) return breaker;
- });
- return !!result;
+ _.map = _.collect = function (obj, iteratee, context) {
+ iteratee = cb(iteratee, context);
+ var keys = !isArrayLike(obj) && _.keys(obj),
+ length = (keys || obj).length,
+ results = Array (length);
+ for (var index = 0 ; index < length; index++) {
+ var currentKey = keys ? keys[index] : index;
+ results[index] = iteratee(obj[currentKey], currentKey, obj);
+ }
+ return results;
};
@@ -487,18 +451,11 @@ Aliased as any
.
- Determine if the array or object contains a given value (using ===
).
-Aliased as include
.
+ Create a reducing function iterating left or right.
-
_.contains = _.include = function (obj, target) {
- if (obj == null ) return false ;
- if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1 ;
- return any(obj, function (value) {
- return value === target;
- });
- };
+
function createReduce (dir) {
@@ -509,17 +466,24 @@ Aliased as
include
.
-
Invoke a method (with arguments) on every item in a collection.
+
Optimized iterator function as using arguments.length
+in the main function will deoptimize the, see #1991.
-
_.invoke = function (obj, method) {
- var args = slice.call(arguments, 2 );
- var isFunc = _.isFunction(method);
- return _.map(obj, function (value) {
- return (isFunc ? method : value[method]).apply(value, args);
- });
- };
+
function iterator (obj, iteratee, memo, keys, index, length) {
+ for (; index >= 0 && index < length; index += dir) {
+ var currentKey = keys ? keys[index] : index;
+ memo = iteratee(memo, obj[currentKey], currentKey, obj);
+ }
+ return memo;
+ }
+
+ return function (obj, iteratee, memo, context) {
+ iteratee = optimizeCb(iteratee, context, 4 );
+ var keys = !isArrayLike(obj) && _.keys(obj),
+ length = (keys || obj).length,
+ index = dir > 0 ? 0 : length - 1 ;
@@ -530,13 +494,17 @@ Aliased as
include
.
-
Convenience version of a common use case of map
: fetching a property.
+
Determine the initial value if none is provided.
-
_.pluck = function (obj, key) {
- return _.map(obj, function (value){ return value[key]; });
- };
+
if (arguments .length < 3 ) {
+ memo = obj[keys ? keys[index] : index];
+ index += dir;
+ }
+ return iterator(obj, iteratee, memo, keys, index, length);
+ };
+ }
@@ -547,20 +515,12 @@ Aliased as
include
.
-
Convenience version of a common use case of filter
: selecting only objects
-containing specific key:value
pairs.
+
Reduce builds up a single result from a list of values, aka inject
,
+or foldl
.
- _.where = function (obj, attrs, first) {
- if (_.isEmpty(attrs)) return first ? void 0 : [];
- return _[first ? 'find' : 'filter' ](obj, function (value) {
- for (var key in attrs) {
- if (attrs[key] !== value[key]) return false ;
- }
- return true ;
- });
- };
+ _.reduce = _.foldl = _.inject = createReduce(1 );
@@ -571,14 +531,11 @@ containing specific key:value
pairs.
- Convenience version of a common use case of find
: getting the first object
-containing specific key:value
pairs.
+ The right-associative version of reduce, also known as foldr
.
- _.findWhere = function (obj, attrs) {
- return _.where(obj, attrs, true );
- };
+ _.reduceRight = _.foldr = createReduce(-1 );
@@ -589,23 +546,18 @@ containing specific key:value
pairs.
- Return the maximum element or (element-based computation).
-Can't optimize arrays of integers longer than 65,535 elements.
-See WebKit Bug 80797
+ Return the first value which passes a truth test. Aliased as detect
.
- _.max = function (obj, iterator, context) {
- if (!iterator && _.isArray(obj) && obj[0 ] === +obj[0 ] && obj.length < 65535 ) {
- return Math.max.apply(Math, obj);
+ _.find = _.detect = function (obj, predicate, context) {
+ var key;
+ if (isArrayLike(obj)) {
+ key = _.findIndex(obj, predicate, context);
+ } else {
+ key = _.findKey(obj, predicate, context);
}
- if (!iterator && _.isEmpty(obj)) return -Infinity ;
- var result = {computed : -Infinity , value: -Infinity };
- each(obj, function (value, index, list) {
- var computed = iterator ? iterator.call(context, value, index, list) : value;
- computed > result.computed && (result = {value : value, computed : computed});
- });
- return result.value;
+ if (key !== void 0 && key !== -1 ) return obj[key];
};
@@ -617,21 +569,18 @@ See WebKit Bug 80797
- Return the minimum element (or element-based computation).
+ Return all the elements that pass a truth test.
+Aliased as select
.
-
_.min = function (obj, iterator, context) {
- if (!iterator && _.isArray(obj) && obj[0 ] === +obj[0 ] && obj.length < 65535 ) {
- return Math.min.apply(Math, obj);
- }
- if (!iterator && _.isEmpty(obj)) return Infinity ;
- var result = {computed : Infinity , value: Infinity };
- each(obj, function (value, index, list) {
- var computed = iterator ? iterator.call(context, value, index, list) : value;
- computed < result.computed && (result = {value : value, computed : computed});
+ _.filter = _.select = function (obj, predicate, context) {
+ var results = [];
+ predicate = cb(predicate, context);
+ _.each(obj, function (value, index, list) {
+ if (predicate(value, index, list)) results.push(value);
});
- return result.value;
+ return results;
};
@@ -643,21 +592,12 @@ See WebKit Bug 80797
- Shuffle an array, using the modern version of the
-Fisher-Yates shuffle .
+ Return all the elements for which a truth test fails.
-
_.shuffle = function (obj) {
- var rand;
- var index = 0 ;
- var shuffled = [];
- each(obj, function (value) {
- rand = _.random(index++);
- shuffled[index - 1 ] = shuffled[rand];
- shuffled[rand] = value;
- });
- return shuffled;
+ _.reject = function (obj, predicate, context) {
+ return _.filter(obj, _.negate(cb(predicate)), context);
};
@@ -669,17 +609,20 @@ See WebKit Bug 80797
- Sample n random values from an array.
-If n is not specified, returns a single random element from the array.
-The internal guard
argument allows it to work with map
.
+ Determine whether all of the elements match a truth test.
+Aliased as all
.
-
_.sample = function (obj, n, guard) {
- if (arguments.length < 2 || guard) {
- return obj[_.random(obj.length - 1 )];
+ _.every = _.all = function (obj, predicate, context) {
+ predicate = cb(predicate, context);
+ var keys = !isArrayLike(obj) && _.keys(obj),
+ length = (keys || obj).length;
+ for (var index = 0 ; index < length; index++) {
+ var currentKey = keys ? keys[index] : index;
+ if (!predicate(obj[currentKey], currentKey, obj)) return false ;
}
- return _.shuffle(obj).slice(0 , Math.max(0 , n));
+ return true ;
};
@@ -691,12 +634,20 @@ The internal guard
argument allows it to work with map
- An internal function to generate lookup iterators.
+ Determine if at least one element in the object matches a truth test.
+Aliased as any
.
-
var lookupIterator = function (value) {
- return _.isFunction(value) ? value : function (obj){ return obj[value]; };
+ _.some = _.any = function (obj, predicate, context) {
+ predicate = cb(predicate, context);
+ var keys = !isArrayLike(obj) && _.keys(obj),
+ length = (keys || obj).length;
+ for (var index = 0 ; index < length; index++) {
+ var currentKey = keys ? keys[index] : index;
+ if (predicate(obj[currentKey], currentKey, obj)) return true ;
+ }
+ return false ;
};
@@ -708,27 +659,15 @@ The internal guard
argument allows it to work with map
- Sort the object's values by a criterion produced by an iterator.
+ Determine if the array or object contains a given item (using ===
).
+Aliased as includes
and include
.
-
_.sortBy = function (obj, value, context) {
- var iterator = lookupIterator(value);
- return _.pluck(_.map(obj, function (value, index, list) {
- return {
- value: value,
- index: index,
- criteria: iterator.call(context, value, index, list)
- };
- }).sort(function (left, right) {
- var a = left.criteria;
- var b = right.criteria;
- if (a !== b) {
- if (a > b || a === void 0 ) return 1 ;
- if (a < b || b === void 0 ) return -1 ;
- }
- return left.index - right.index;
- }), 'value' );
+ _.contains = _.includes = _.include = function (obj, item, fromIndex, guard) {
+ if (!isArrayLike(obj)) obj = _.values(obj);
+ if (typeof fromIndex != 'number' || guard) fromIndex = 0 ;
+ return _.indexOf(obj, item, fromIndex) >= 0 ;
};
@@ -740,20 +679,17 @@ The internal guard
argument allows it to work with map
- An internal function used for aggregate "group by" operations.
+ Invoke a method (with arguments) on every item in a collection.
-
var group = function (behavior) {
- return function (obj, value, context) {
- var result = {};
- var iterator = value == null ? _.identity : lookupIterator(value);
- each(obj, function (value, index) {
- var key = iterator.call(context, value, index, obj);
- behavior(result, key, value);
- });
- return result;
- };
+ _.invoke = function (obj, method) {
+ var args = slice.call(arguments , 2 );
+ var isFunc = _.isFunction(method);
+ return _.map(obj, function (value) {
+ var func = isFunc ? method : value[method];
+ return func == null ? func : func.apply(value, args);
+ });
};
@@ -765,14 +701,13 @@ The internal guard
argument allows it to work with map
- Groups the object's values by a criterion. Pass either a string attribute
-to group by, or a function that returns the criterion.
+ Convenience version of a common use case of map
: fetching a property.
-
_.groupBy = group(function (result, key, value) {
- (_.has(result, key) ? result[key] : (result[key] = [])).push(value);
- });
+
_.pluck = function (obj, key) {
+ return _.map(obj, _.property(key));
+ };
@@ -783,14 +718,14 @@ to group by, or a function that returns the criterion.
-
Indexes the object's values by a criterion, similar to groupBy
, but for
-when you know that your index values will be unique.
+
Convenience version of a common use case of filter
: selecting only objects
+containing specific key:value
pairs.
-
_.indexBy = group(function (result, key, value) {
- result[key] = value;
- });
+
_.where = function (obj, attrs) {
+ return _.filter(obj, _.matcher(attrs));
+ };
@@ -801,15 +736,14 @@ when you know that your index values will be unique.
-
Counts instances of an object that group by a certain criterion. Pass
-either a string attribute to count by, or a function that returns the
-criterion.
+
Convenience version of a common use case of find
: getting the first object
+containing specific key:value
pairs.
-
_.countBy = group(function (result, key) {
- _.has(result, key) ? result[key]++ : result[key] = 1 ;
- });
+
_.findWhere = function (obj, attrs) {
+ return _.find(obj, _.matcher(attrs));
+ };
@@ -820,20 +754,32 @@ criterion.
-
Use a comparator function to figure out the smallest index at which
-an object should be inserted so as to maintain order. Uses binary search.
+
Return the maximum element (or element-based computation).
-
_.sortedIndex = function (array, obj, iterator, context) {
- iterator = iterator == null ? _.identity : lookupIterator(iterator);
- var value = iterator.call(context, obj);
- var low = 0 , high = array.length;
- while (low < high) {
- var mid = (low + high) >>> 1 ;
- iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid;
+ _.max = function (obj, iteratee, context) {
+ var result = -Infinity , lastComputed = -Infinity ,
+ value, computed;
+ if (iteratee == null && obj != null ) {
+ obj = isArrayLike(obj) ? obj : _.values(obj);
+ for (var i = 0 , length = obj.length; i < length; i++) {
+ value = obj[i];
+ if (value > result) {
+ result = value;
+ }
+ }
+ } else {
+ iteratee = cb(iteratee, context);
+ _.each(obj, function (value, index, list) {
+ computed = iteratee(value, index, list);
+ if (computed > lastComputed || computed === -Infinity && result === -Infinity ) {
+ result = value;
+ lastComputed = computed;
+ }
+ });
}
- return low;
+ return result;
};
@@ -845,15 +791,32 @@ an object should be inserted so as to maintain order. Uses binary search.
- Safely create a real, live array from anything iterable.
+ Return the minimum element (or element-based computation).
-
_.toArray = function (obj) {
- if (!obj) return [];
- if (_.isArray(obj)) return slice.call(obj);
- if (obj.length === +obj.length) return _.map(obj, _.identity);
- return _.values(obj);
+ _.min = function (obj, iteratee, context) {
+ var result = Infinity , lastComputed = Infinity ,
+ value, computed;
+ if (iteratee == null && obj != null ) {
+ obj = isArrayLike(obj) ? obj : _.values(obj);
+ for (var i = 0 , length = obj.length; i < length; i++) {
+ value = obj[i];
+ if (value < result) {
+ result = value;
+ }
+ }
+ } else {
+ iteratee = cb(iteratee, context);
+ _.each(obj, function (value, index, list) {
+ computed = iteratee(value, index, list);
+ if (computed < lastComputed || computed === Infinity && result === Infinity ) {
+ result = value;
+ lastComputed = computed;
+ }
+ });
+ }
+ return result;
};
@@ -865,13 +828,21 @@ an object should be inserted so as to maintain order. Uses binary search.
- Return the number of elements in an object.
+ Shuffle a collection, using the modern version of the
+Fisher-Yates shuffle .
-
_.size = function (obj) {
- if (obj == null ) return 0 ;
- return (obj.length === +obj.length) ? obj.length : _.keys(obj).length;
+ _.shuffle = function (obj) {
+ var set = isArrayLike(obj) ? obj : _.values(obj);
+ var length = set.length;
+ var shuffled = Array (length);
+ for (var index = 0 , rand; index < length; index++) {
+ rand = _.random(0 , index);
+ if (rand !== index) shuffled[index] = shuffled[rand];
+ shuffled[rand] = set[index];
+ }
+ return shuffled;
};
@@ -880,13 +851,23 @@ an object should be inserted so as to maintain order. Uses binary search.
-
+
-
Array Functions
+
Sample n random values from a collection.
+If n is not specified, returns a single random element.
+The internal guard
argument allows it to work with map
.
+
_.sample = function (obj, n, guard) {
+ if (n == null || guard) {
+ if (!isArrayLike(obj)) obj = _.values(obj);
+ return obj[_.random(obj.length - 1 )];
+ }
+ return _.shuffle(obj).slice(0 , Math .max(0 , n));
+ };
+
@@ -896,15 +877,27 @@ an object should be inserted so as to maintain order. Uses binary search.
-
Get the first element of an array. Passing n will return the first N
-values in the array. Aliased as head
and take
. The guard check
-allows it to work with _.map
.
+
Sort the object’s values by a criterion produced by an iteratee.
- _.first = _.head = _.take = function (array, n, guard) {
- if (array == null ) return void 0 ;
- return (n == null ) || guard ? array[0 ] : slice.call(array, 0 , n);
+ _.sortBy = function (obj, iteratee, context) {
+ iteratee = cb(iteratee, context);
+ return _.pluck(_.map(obj, function (value, index, list) {
+ return {
+ value: value,
+ index: index,
+ criteria: iteratee(value, index, list)
+ };
+ }).sort(function (left, right) {
+ var a = left.criteria;
+ var b = right.criteria;
+ if (a !== b) {
+ if (a > b || a === void 0 ) return 1 ;
+ if (a < b || b === void 0 ) return -1 ;
+ }
+ return left.index - right.index;
+ }), 'value' );
};
@@ -916,15 +909,20 @@ allows it to work with _.map
.
- Returns everything but the last entry of the array. Especially useful on
-the arguments object. Passing n will return all the values in
-the array, excluding the last N. The guard check allows it to work with
-_.map
.
+ An internal function used for aggregate “group by” operations.
-
_.initial = function (array, n, guard) {
- return slice.call(array, 0 , array.length - ((n == null ) || guard ? 1 : n));
+ var group = function (behavior) {
+ return function (obj, iteratee, context) {
+ var result = {};
+ iteratee = cb(iteratee, context);
+ _.each(obj, function (value, index) {
+ var key = iteratee(value, index, obj);
+ behavior(result, value, key);
+ });
+ return result;
+ };
};
@@ -936,19 +934,14 @@ the array, excluding the last N. The guard check allows it to w
- 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
.
+ Groups the object’s values by a criterion. Pass either a string attribute
+to group by, or a function that returns the criterion.
-
_.last = function (array, n, guard) {
- if (array == null ) return void 0 ;
- if ((n == null ) || guard) {
- return array[array.length - 1 ];
- } else {
- return slice.call(array, Math.max(array.length - n, 0 ));
- }
- };
+
_.groupBy = group(function (result, value, key) {
+ if (_.has(result, key)) result[key].push(value); else result[key] = [value];
+ });
@@ -959,16 +952,14 @@ values in the array. The
guard check allows it to work with
¶
-
Returns everything but the first entry of the array. Aliased as tail
and drop
.
-Especially useful on the arguments object. Passing an n will return
-the rest N values in the array. The guard
-check allows it to work with _.map
.
+
Indexes the object’s values by a criterion, similar to groupBy
, but for
+when you know that your index values will be unique.
- _.rest = _.tail = _.drop = function (array, n, guard) {
- return slice.call(array, (n == null ) || guard ? 1 : n);
- };
+ _.indexBy = group(function (result, value, key) {
+ result[key] = value;
+ });
@@ -979,13 +970,15 @@ check allows it to work with
_.map
.
-
Trim out all falsy values from an array.
+
Counts instances of an object that group by a certain criterion. Pass
+either a string attribute to count by, or a function that returns the
+criterion.
-
_.compact = function (array) {
- return _.filter(array, _.identity);
- };
+
_.countBy = group(function (result, value, key) {
+ if (_.has(result, key)) result[key]++; else result[key] = 1 ;
+ });
@@ -996,22 +989,15 @@ check allows it to work with
_.map
.
-
Internal implementation of a recursive flatten
function.
+
Safely create a real, live array from anything iterable.
-
var flatten = function (input, shallow, output) {
- if (shallow && _.every(input, _.isArray)) {
- return concat.apply(output, input);
- }
- each(input, function (value) {
- if (_.isArray(value) || _.isArguments(value)) {
- shallow ? push.apply(output, value) : flatten(value, shallow, output);
- } else {
- output.push(value);
- }
- });
- return output;
+ _.toArray = function (obj) {
+ if (!obj) return [];
+ if (_.isArray(obj)) return slice.call(obj);
+ if (isArrayLike(obj)) return _.map(obj, _.identity);
+ return _.values(obj);
};
@@ -1023,12 +1009,13 @@ check allows it to work with _.map
.
- Flatten out an array, either recursively (by default), or just one level.
+ Return the number of elements in an object.
-
_.flatten = function (array, shallow) {
- return flatten(array, shallow, []);
+ _.size = function (obj) {
+ if (obj == null ) return 0 ;
+ return isArrayLike(obj) ? obj.length : _.keys(obj).length;
};
@@ -1040,65 +1027,51 @@ check allows it to work with _.map
.
- Return a version of the array that does not contain the specified value(s).
+ Split a collection into two arrays: one whose elements all satisfy the given
+predicate, and one whose elements all do not satisfy the predicate.
-
_.without = function (array) {
- return _.difference(array, slice.call(arguments, 1 ));
+ _.partition = function (obj, predicate, context) {
+ predicate = cb(predicate, context);
+ var pass = [], fail = [];
+ _.each(obj, function (value, key, obj) {
+ (predicate(value, key, obj) ? pass : fail).push(value);
+ });
+ return [pass, fail];
};
-
-
+
+
-
Produce a duplicate-free version of the array. If the array has already
-been sorted, you have the option of using a faster algorithm.
-Aliased as unique
.
+
Trim out all falsy values from an object.
- _.uniq = _.unique = function (array, isSorted, iterator, context) {
- if (_.isFunction(isSorted)) {
- context = iterator;
- iterator = isSorted;
- isSorted = false ;
- }
- var initial = iterator ? _.map(array, iterator, context) : array;
- var results = [];
- var seen = [];
- each(initial, function (value, index) {
- if (isSorted ? (!index || seen[seen.length - 1 ] !== value) : !_.contains(seen, value)) {
- seen.push(value);
- results.push(array[index]);
- }
- });
- return results;
+ _.compact = function (obj) {
+ return _.filter(obj, _.identity);
};
+
-
Produce an array that contains the union: each distinct element from all of
-the passed-in arrays.
+
Array Functions
- _.union = function () {
- return _.uniq(_.flatten(arguments, true ));
- };
-
@@ -1106,22 +1079,11 @@ the passed-in arrays.
-
Produce an array that contains every item shared between all the
-passed-in arrays.
-
+
-
_.intersection = function (array) {
- var rest = slice.call(arguments, 1 );
- return _.filter(_.uniq(array), function (item) {
- return _.every(rest, function (other) {
- return _.indexOf(other, item) >= 0 ;
- });
- });
- };
-
@@ -1129,16 +1091,18 @@ passed-in arrays.
-
Take the difference between one array and a number of other arrays.
-Only the elements present in just the first array will remain.
+
Get the first element of an array. Passing n will return the first N
+values in the array. Aliased as head
and take
. The guard check
+allows it to work with _.map
.
-
_.difference = function (array) {
- var rest = concat.apply(ArrayProto, slice.call(arguments, 1 ));
- return _.filter(array, function (value){ return !_.contains(rest, value); });
+ _.first = _.head = _.take = function (array, n, guard) {
+ if (array == null ) return void 0 ;
+ if (n == null || guard) return array[0 ];
+ return _.initial(array, array.length - n);
};
@@ -1148,20 +1112,16 @@ Only the elements present in just the first array will remain.
-
Zip together multiple lists into a single array -- elements that share
-an index go together.
+
Returns everything but the last entry of the array. Especially useful on
+the arguments object. Passing n will return all the values in
+the array, excluding the last N.
- _.zip = function () {
- var length = _.max(_.pluck(arguments, "length" ).concat(0 ));
- var results = new Array(length);
- for (var i = 0 ; i < length; i++) {
- results[i] = _.pluck(arguments, '' + i);
- }
- return results;
+ _.initial = function (array, n, guard) {
+ return slice.call(array, 0 , Math .max(0 , array.length - (n == null || guard ? 1 : n)));
};
@@ -1171,25 +1131,17 @@ an index go together.
-
Converts lists into objects. Pass either a single array of [key, value]
-pairs, or two parallel arrays of the same length -- one of keys, and one of
-the corresponding values.
+
Get the last element of an array. Passing n will return the last N
+values in the array.
- _.object = function (list, values) {
- if (list == null ) return {};
- var result = {};
- for (var i = 0 , length = list.length; i < length; i++) {
- if (values) {
- result[list[i]] = values[i];
- } else {
- result[list[i][0 ]] = list[i][1 ];
- }
- }
- return result;
+ _.last = function (array, n, guard) {
+ if (array == null ) return void 0 ;
+ if (n == null || guard) return array[array.length - 1 ];
+ return _.rest(array, Math .max(0 , array.length - n));
};
@@ -1199,34 +1151,19 @@ the corresponding values.
-
If the browser doesn't supply us with indexOf (I'm looking at you, MSIE ),
-we need this function. Return the position of the first occurrence of an
-item in an array, or -1 if the item is not included in the array.
-Delegates to ECMAScript 5 's native indexOf
if available.
-If the array is large and already in sort order, pass true
-for isSorted to use binary search.
+
Returns everything but the first entry of the array. Aliased as tail
and drop
.
+Especially useful on the arguments object. Passing an n will return
+the rest N values in the array.
- _.indexOf = function (array, item, isSorted) {
- if (array == null ) return -1 ;
- var i = 0 , length = array.length;
- if (isSorted) {
- if (typeof isSorted == 'number' ) {
- i = (isSorted < 0 ? Math.max(0 , length + isSorted) : isSorted);
- } else {
- i = _.sortedIndex(array, item);
- return array[i] === item ? i : -1 ;
- }
- }
- if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted);
- for (; i < length; i++) if (array[i] === item) return i;
- return -1 ;
+ _.rest = _.tail = _.drop = function (array, n, guard) {
+ return slice.call(array, n == null || guard ? 1 : n);
};
-
+
@@ -1235,20 +1172,15 @@ for isSorted to use binary search.
- Delegates to ECMAScript 5 's native lastIndexOf
if available.
+ Internal implementation of a recursive flatten
function.
-
_.lastIndexOf = function (array, item, from) {
- if (array == null ) return -1 ;
- var hasIndex = from != null ;
- if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) {
- return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item);
- }
- var i = (hasIndex ? from : array.length);
- while (i--) if (array[i] === item) return i;
- return -1 ;
- };
+
var flatten = function (input, shallow, strict, startIndex) {
+ var output = [], idx = 0 ;
+ for (var i = startIndex || 0 , length = getLength(input); i < length; i++) {
+ var value = input[i];
+ if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) {
@@ -1259,29 +1191,21 @@ for
isSorted to use binary search.
-
Generate an integer Array containing an arithmetic progression. A port of
-the native Python range()
function. See
-the Python documentation .
+
flatten current level of array or arguments object
- _.range = function (start, stop, step) {
- if (arguments.length <= 1 ) {
- stop = start || 0 ;
- start = 0 ;
+ if (!shallow) value = flatten(value, shallow, strict);
+ var j = 0 , len = value.length;
+ output.length += len;
+ while (j < len) {
+ output[idx++] = value[j++];
+ }
+ } else if (!strict) {
+ output[idx++] = value;
+ }
}
- step = arguments[2 ] || 1 ;
-
- var length = Math.max(Math.ceil((stop - start) / step), 0 );
- var idx = 0 ;
- var range = new Array(length);
-
- while (idx < length) {
- range[idx++] = start;
- start += step;
- }
-
- return range;
+ return output;
};
@@ -1290,13 +1214,17 @@ the native Python range()
function. See
-
+
-
Function (ahem) Functions
+
Flatten out an array, either recursively (by default), or just one level.
+
_.flatten = function (array, shallow) {
+ return flatten(array, shallow, false );
+ };
+
@@ -1306,11 +1234,13 @@ the native Python
range()
function. See
-
Reusable constructor function for prototype setting.
+
Return a version of the array that does not contain the specified value(s).
-
+ _.without = function (array) {
+ return _.difference(array, slice.call(arguments , 1 ));
+ };
@@ -1321,26 +1251,37 @@ the native Python
range()
function. See
-
Create a function bound to a given object (assigning this
, and arguments,
-optionally). Delegates to ECMAScript 5 's native Function.bind
if
-available.
+
Produce a duplicate-free version of the array. If the array has already
+been sorted, you have the option of using a faster algorithm.
+Aliased as unique
.
-
_.bind = function (func, context) {
- var args, bound;
- if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1 ));
- if (!_.isFunction(func)) throw new TypeError;
- args = slice.call(arguments, 2 );
- return bound = function () {
- if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
- ctor.prototype = func.prototype;
- var self = new ctor;
- ctor.prototype = null ;
- var result = func.apply(self, args.concat(slice.call(arguments)));
- if (Object(result) === result) return result;
- return self;
- };
+ _.uniq = _.unique = function (array, isSorted, iteratee, context) {
+ if (!_.isBoolean(isSorted)) {
+ context = iteratee;
+ iteratee = isSorted;
+ isSorted = false ;
+ }
+ if (iteratee != null ) iteratee = cb(iteratee, context);
+ var result = [];
+ var seen = [];
+ for (var i = 0 , length = getLength(array); i < length; i++) {
+ var value = array[i],
+ computed = iteratee ? iteratee(value, i, array) : value;
+ if (isSorted) {
+ if (!i || seen !== computed) result.push(value);
+ seen = computed;
+ } else if (iteratee) {
+ if (!_.contains(seen, computed)) {
+ seen.push(computed);
+ result.push(value);
+ }
+ } else if (!_.contains(result, value)) {
+ result.push(value);
+ }
+ }
+ return result;
};
@@ -1352,16 +1293,13 @@ available.
- Partially apply a function by creating a version that has had some of its
-arguments pre-filled, without changing its dynamic this
context.
+ Produce an array that contains the union: each distinct element from all of
+the passed-in arrays.
-
_.partial = function (func) {
- var args = slice.call(arguments, 1 );
- return function () {
- return func.apply(this , args.concat(slice.call(arguments)));
- };
+ _.union = function () {
+ return _.uniq(flatten(arguments , true , true ));
};
@@ -1373,16 +1311,23 @@ arguments pre-filled, without changing its dynamic this
context.
¶
-
Bind all of an object's methods to that object. Useful for ensuring that
-all callbacks defined on an object belong to it.
+
Produce an array that contains every item shared between all the
+passed-in arrays.
-
_.bindAll = function (obj) {
- var funcs = slice.call(arguments, 1 );
- if (funcs.length === 0 ) throw new Error("bindAll must be passed function names" );
- each(funcs, function (f) { obj[f] = _.bind(obj[f], obj); });
- return obj;
+ _.intersection = function (array) {
+ var result = [];
+ var argsLength = arguments .length;
+ for (var i = 0 , length = getLength(array); i < length; i++) {
+ var item = array[i];
+ if (_.contains(result, item)) continue ;
+ for (var j = 1 ; j < argsLength; j++) {
+ if (!_.contains(arguments [j], item)) break ;
+ }
+ if (j === argsLength) result.push(item);
+ }
+ return result;
};
@@ -1394,17 +1339,16 @@ all callbacks defined on an object belong to it.
- Memoize an expensive function by storing its results.
+ Take the difference between one array and a number of other arrays.
+Only the elements present in just the first array will remain.
-
_.memoize = function (func, hasher) {
- var memo = {};
- hasher || (hasher = _.identity);
- return function () {
- var key = hasher.apply(this , arguments);
- return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this , arguments));
- };
+ _.difference = function (array) {
+ var rest = flatten(arguments , true , true , 1 );
+ return _.filter(array, function (value) {
+ return !_.contains(rest, value);
+ });
};
@@ -1416,14 +1360,13 @@ all callbacks defined on an object belong to it.
- Delays a function for the given number of milliseconds, and then calls
-it with the arguments supplied.
+ Zip together multiple lists into a single array — elements that share
+an index go together.
-
_.delay = function (func, wait) {
- var args = slice.call(arguments, 2 );
- return setTimeout(function (){ return func.apply(null , args); }, wait);
+ _.zip = function () {
+ return _.unzip(arguments );
};
@@ -1435,13 +1378,19 @@ it with the arguments supplied.
- Defers a function, scheduling it to run after the current call stack has
-cleared.
+ Complement of _.zip. Unzip accepts an array of arrays and groups
+each array’s elements on shared indices
-
_.defer = function (func) {
- return _.delay.apply(_, [func, 1 ].concat(slice.call(arguments, 1 )));
+ _.unzip = function (array) {
+ var length = array && _.max(array, getLength).length || 0 ;
+ var result = Array (length);
+
+ for (var index = 0 ; index < length; index++) {
+ result[index] = _.pluck(array, index);
+ }
+ return result;
};
@@ -1453,40 +1402,22 @@ cleared.
- Returns a function, that, when invoked, will only be triggered at most once
-during a given window of time. Normally, the throttled function will run
-as much as it can, without ever going more than once per wait
duration;
-but if you'd like to disable the execution on the leading edge, pass
-{leading: false}
. To disable execution on the trailing edge, ditto.
+ Converts lists into objects. Pass either a single array of [key, value]
+pairs, or two parallel arrays of the same length — one of keys, and one of
+the corresponding values.
-
_.throttle = function (func, wait, options) {
- var context, args, result;
- var timeout = null ;
- var previous = 0 ;
- options || (options = {});
- var later = function () {
- previous = options.leading === false ? 0 : new Date;
- timeout = null ;
- result = func.apply(context, args);
- };
- return function () {
- var now = new Date;
- if (!previous && options.leading === false ) previous = now;
- var remaining = wait - (now - previous);
- context = this ;
- args = arguments;
- if (remaining <= 0 ) {
- clearTimeout(timeout);
- timeout = null ;
- previous = now;
- result = func.apply(context, args);
- } else if (!timeout && options.trailing !== false ) {
- timeout = setTimeout(later, remaining);
+ _.object = function (list, values) {
+ var result = {};
+ for (var i = 0 , length = getLength(list); i < length; i++) {
+ if (values) {
+ result[list[i]] = values[i];
+ } else {
+ result[list[i][0 ]] = list[i][1 ];
}
- return result;
- };
+ }
+ return result;
};
@@ -1498,36 +1429,21 @@ but if you'd like to disable the execution on the leading edge, pass
- 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. If immediate
is passed, trigger the function on the
-leading edge, instead of the trailing.
+ Generator function to create the findIndex and findLastIndex functions
-
_.debounce = function (func, wait, immediate) {
- var timeout, args, context, timestamp, result;
- return function () {
- context = this ;
- args = arguments;
- timestamp = new Date();
- var later = function () {
- var last = (new Date()) - timestamp;
- if (last < wait) {
- timeout = setTimeout(later, wait - last);
- } else {
- timeout = null ;
- if (!immediate) result = func.apply(context, args);
- }
- };
- var callNow = immediate && !timeout;
- if (!timeout) {
- timeout = setTimeout(later, wait);
+ function createPredicateIndexFinder (dir) {
+ return function (array, predicate, context) {
+ predicate = cb(predicate, context);
+ var length = getLength(array);
+ var index = dir > 0 ? 0 : length - 1 ;
+ for (; index >= 0 && index < length; index += dir) {
+ if (predicate(array[index], index, array)) return index;
}
- if (callNow) result = func.apply(context, args);
- return result;
+ return -1 ;
};
- };
+ }
@@ -1538,21 +1454,12 @@ leading edge, instead of the trailing.
-
Returns a function that will be executed at most one time, no matter how
-often you call it. Useful for lazy initialization.
+
Returns the first index on an array-like that passes a predicate test
-
_.once = function (func) {
- var ran = false , memo;
- return function () {
- if (ran) return memo;
- ran = true ;
- memo = func.apply(this , arguments);
- func = null ;
- return memo;
- };
- };
+
_.findIndex = createPredicateIndexFinder(1 );
+ _.findLastIndex = createPredicateIndexFinder(-1 );
@@ -1563,18 +1470,20 @@ often you call it. Useful for lazy initialization.
-
Returns the first function passed as an argument to the second,
-allowing you to adjust arguments, run code before and after, and
-conditionally execute the original function.
+
Use a comparator function to figure out the smallest index at which
+an object should be inserted so as to maintain order. Uses binary search.
-
_.wrap = function (func, wrapper) {
- return function () {
- var args = [func];
- push.apply(args, arguments);
- return wrapper.apply(this , args);
- };
+ _.sortedIndex = function (array, obj, iteratee, context) {
+ iteratee = cb(iteratee, context, 1 );
+ var value = iteratee(obj);
+ var low = 0 , high = getLength(array);
+ while (low < high) {
+ var mid = Math .floor((low + high) / 2 );
+ if (iteratee(array[mid]) < value) low = mid + 1 ; else high = mid;
+ }
+ return low;
};
@@ -1586,21 +1495,33 @@ conditionally execute the original function.
- Returns a function that is the composition of a list of functions, each
-consuming the return value of the function that follows.
+ Generator function to create the indexOf and lastIndexOf functions
-
_.compose = function () {
- var funcs = arguments;
- return function () {
- var args = arguments;
- for (var i = funcs.length - 1 ; i >= 0 ; i--) {
- args = [funcs[i].apply(this , args)];
+ function createIndexFinder (dir, predicateFind, sortedIndex) {
+ return function (array, item, idx) {
+ var i = 0 , length = getLength(array);
+ if (typeof idx == 'number' ) {
+ if (dir > 0 ) {
+ i = idx >= 0 ? idx : Math .max(idx + length, i);
+ } else {
+ length = idx >= 0 ? Math .min(idx + 1 , length) : idx + length + 1 ;
+ }
+ } else if (sortedIndex && idx && length) {
+ idx = sortedIndex(array, item);
+ return array[idx] === item ? idx : -1 ;
}
- return args[0 ];
+ if (item !== item) {
+ idx = predicateFind(slice.call(array, i, length), _.isNaN);
+ return idx >= 0 ? idx + i : -1 ;
+ }
+ for (idx = dir > 0 ? i : length - 1 ; idx >= 0 && idx < length; idx += dir) {
+ if (array[idx] === item) return idx;
+ }
+ return -1 ;
};
- };
+ }
@@ -1611,17 +1532,15 @@ consuming the return value of the function that follows.
-
Returns a function that will only be executed after being called N times.
+
Return the position of the first occurrence of an item in an array,
+or -1 if the item is not included in the array.
+If the array is large and already in sort order, pass true
+for isSorted to use binary search.
-
_.after = function (times, func) {
- return function () {
- if (--times < 1 ) {
- return func.apply(this , arguments);
- }
- };
- };
+
_.indexOf = createIndexFinder(1 , _.findIndex, _.sortedIndex);
+ _.lastIndexOf = createIndexFinder(-1 , _.findLastIndex);
@@ -1629,13 +1548,32 @@ consuming the return value of the function that follows.
-
+
-
Object Functions
+
Generate an integer Array containing an arithmetic progression. A port of
+the native Python range()
function. See
+the Python documentation .
+
_.range = function (start, stop, step) {
+ if (stop == null ) {
+ stop = start || 0 ;
+ start = 0 ;
+ }
+ step = step || 1 ;
+
+ var length = Math .max(Math .ceil((stop - start) / step), 0 );
+ var range = Array (length);
+
+ for (var idx = 0 ; idx < length; idx++, start += step) {
+ range[idx] = start;
+ }
+
+ return range;
+ };
+
@@ -1645,18 +1583,10 @@ consuming the return value of the function that follows.
-
Retrieve the names of an object's properties.
-Delegates to ECMAScript 5 's native Object.keys
+
Function (ahem) Functions
- _.keys = nativeKeys || function (obj) {
- if (obj !== Object(obj)) throw new TypeError('Invalid object' );
- var keys = [];
- for (var key in obj) if (_.has(obj, key)) keys.push(key);
- return keys;
- };
-
@@ -1666,20 +1596,9 @@ Delegates to
ECMAScript 5 's native
Object.keys
-
Retrieve the values of an object's properties.
-
+
-
_.values = function (obj) {
- var keys = _.keys(obj);
- var length = keys.length;
- var values = new Array(length);
- for (var i = 0 ; i < length; i++) {
- values[i] = obj[keys[i]];
- }
- return values;
- };
-
@@ -1689,18 +1608,17 @@ Delegates to
ECMAScript 5 's native
Object.keys
-
Convert an object into a list of [key, value]
pairs.
+
Determines whether to execute a function as a constructor
+or a normal function with the provided arguments
-
_.pairs = function (obj) {
- var keys = _.keys(obj);
- var length = keys.length;
- var pairs = new Array(length);
- for (var i = 0 ; i < length; i++) {
- pairs[i] = [keys[i], obj[keys[i]]];
- }
- return pairs;
+ var executeBound = function (sourceFunc, boundFunc, context, callingContext, args) {
+ if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args);
+ var self = baseCreate(sourceFunc.prototype);
+ var result = sourceFunc.apply(self, args);
+ if (_.isObject(result)) return result;
+ return self;
};
@@ -1712,17 +1630,20 @@ Delegates to ECMAScript 5 's native Object.keys
- Invert the keys and values of an object. The values must be serializable.
+ Create a function bound to a given object (assigning this
, and arguments,
+optionally). Delegates to ECMAScript 5 ‘s native Function.bind
if
+available.
-
_.invert = function (obj) {
- var result = {};
- var keys = _.keys(obj);
- for (var i = 0 , length = keys.length; i < length; i++) {
- result[obj[keys[i]]] = keys[i];
- }
- return result;
+ _.bind = function (func, context) {
+ if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments , 1 ));
+ if (!_.isFunction(func)) throw new TypeError ('Bind must be called on a function' );
+ var args = slice.call(arguments , 2 );
+ var bound = function () {
+ return executeBound(func, bound, context, this , args.concat(slice.call(arguments )));
+ };
+ return bound;
};
@@ -1734,17 +1655,24 @@ Delegates to ECMAScript 5 's native Object.keys
- Return a sorted list of the function names available on the object.
-Aliased as methods
+ Partially apply a function by creating a version that has had some of its
+arguments pre-filled, without changing its dynamic this
context. _ acts
+as a placeholder, allowing any combination of arguments to be pre-filled.
-
_.functions = _.methods = function (obj) {
- var names = [];
- for (var key in obj) {
- if (_.isFunction(obj[key])) names.push(key);
- }
- return names.sort();
+ _.partial = function (func) {
+ var boundArgs = slice.call(arguments , 1 );
+ var bound = function () {
+ var position = 0 , length = boundArgs.length;
+ var args = Array (length);
+ for (var i = 0 ; i < length; i++) {
+ args[i] = boundArgs[i] === _ ? arguments [position++] : boundArgs[i];
+ }
+ while (position < arguments .length) args.push(arguments [position++]);
+ return executeBound(func, bound, this , this , args);
+ };
+ return bound;
};
@@ -1756,19 +1684,20 @@ Aliased as methods
- Extend a given object with all the properties in passed-in object(s).
+ Bind a number of an object’s methods to that object. Remaining arguments
+are the method names to be bound. Useful for ensuring that all callbacks
+defined on an object belong to it.
-
_.extend = function (obj) {
- each(slice.call(arguments, 1 ), function (source) {
- if (source) {
- for (var prop in source) {
- obj[prop] = source[prop];
- }
- }
- });
- return obj;
+ _.bindAll = function (obj) {
+ var i, length = arguments .length, key;
+ if (length <= 1 ) throw new Error ('bindAll must be passed function names' );
+ for (i = 1 ; i < length; i++) {
+ key = arguments [i];
+ obj[key] = _.bind(obj[key], obj);
+ }
+ return obj;
};
@@ -1780,17 +1709,19 @@ Aliased as methods
- Return a copy of the object only containing the whitelisted properties.
+ Memoize an expensive function by storing its results.
-
_.pick = function (obj) {
- var copy = {};
- var keys = concat.apply(ArrayProto, slice.call(arguments, 1 ));
- each(keys, function (key) {
- if (key in obj) copy[key] = obj[key];
- });
- return copy;
+ _.memoize = function (func, hasher) {
+ var memoize = function (key) {
+ var cache = memoize.cache;
+ var address = '' + (hasher ? hasher.apply(this , arguments ) : key);
+ if (!_.has(cache, address)) cache[address] = func.apply(this , arguments );
+ return cache[address];
+ };
+ memoize.cache = {};
+ return memoize;
};
@@ -1802,17 +1733,16 @@ Aliased as methods
- Return a copy of the object without the blacklisted properties.
+ Delays a function for the given number of milliseconds, and then calls
+it with the arguments supplied.
-
_.omit = function (obj) {
- var copy = {};
- var keys = concat.apply(ArrayProto, slice.call(arguments, 1 ));
- for (var key in obj) {
- if (!_.contains(keys, key)) copy[key] = obj[key];
- }
- return copy;
+ _.delay = function (func, wait) {
+ var args = slice.call(arguments , 2 );
+ return setTimeout(function () {
+ return func.apply(null , args);
+ }, wait);
};
@@ -1824,20 +1754,12 @@ Aliased as methods
- Fill in a given object with default properties.
+ Defers a function, scheduling it to run after the current call stack has
+cleared.
-
_.defaults = function (obj) {
- each(slice.call(arguments, 1 ), function (source) {
- if (source) {
- for (var prop in source) {
- if (obj[prop] === void 0 ) obj[prop] = source[prop];
- }
- }
- });
- return obj;
- };
+
_.defer = _.partial(_.delay, _, 1 );
@@ -1848,13 +1770,44 @@ Aliased as
methods
-
Create a (shallow-cloned) duplicate of an object.
+
Returns a function, that, when invoked, will only be triggered at most once
+during a given window of time. Normally, the throttled function will run
+as much as it can, without ever going more than once per wait
duration;
+but if you’d like to disable the execution on the leading edge, pass
+{leading: false}
. To disable execution on the trailing edge, ditto.
-
_.clone = function (obj) {
- if (!_.isObject(obj)) return obj;
- return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
+ _.throttle = function (func, wait, options) {
+ var context, args, result;
+ var timeout = null ;
+ var previous = 0 ;
+ if (!options) options = {};
+ var later = function () {
+ previous = options.leading === false ? 0 : _.now();
+ timeout = null ;
+ result = func.apply(context, args);
+ if (!timeout) context = args = null ;
+ };
+ return function () {
+ var now = _.now();
+ if (!previous && options.leading === false ) previous = now;
+ var remaining = wait - (now - previous);
+ context = this ;
+ args = arguments ;
+ if (remaining <= 0 || remaining > wait) {
+ if (timeout) {
+ clearTimeout(timeout);
+ timeout = null ;
+ }
+ previous = now;
+ result = func.apply(context, args);
+ if (!timeout) context = args = null ;
+ } else if (!timeout && options.trailing !== false ) {
+ timeout = setTimeout(later, remaining);
+ }
+ return result;
+ };
};
@@ -1866,15 +1819,43 @@ Aliased as methods
- Invokes interceptor with the obj, and then returns obj.
-The primary purpose of this method is to "tap into" a method chain, in
-order to perform operations on intermediate results within the chain.
+ 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. If immediate
is passed, trigger the function on the
+leading edge, instead of the trailing.
-
_.tap = function (obj, interceptor) {
- interceptor(obj);
- return obj;
+ _.debounce = function (func, wait, immediate) {
+ var timeout, args, context, timestamp, result;
+
+ var later = function () {
+ var last = _.now() - timestamp;
+
+ if (last < wait && last >= 0 ) {
+ timeout = setTimeout(later, wait - last);
+ } else {
+ timeout = null ;
+ if (!immediate) {
+ result = func.apply(context, args);
+ if (!timeout) context = args = null ;
+ }
+ }
+ };
+
+ return function () {
+ context = this ;
+ args = arguments ;
+ timestamp = _.now();
+ var callNow = immediate && !timeout;
+ if (!timeout) timeout = setTimeout(later, wait);
+ if (callNow) {
+ result = func.apply(context, args);
+ context = args = null ;
+ }
+
+ return result;
+ };
};
@@ -1886,11 +1867,15 @@ order to perform operations on intermediate results within the chain.
- Internal recursive comparison function for isEqual
.
+ Returns the first function passed as an argument to the second,
+allowing you to adjust arguments, run code before and after, and
+conditionally execute the original function.
-
var eq = function (a, b, aStack, bStack) {
+
_.wrap = function (func, wrapper) {
+ return _.partial(wrapper, func);
+ };
@@ -1901,12 +1886,15 @@ order to perform operations on intermediate results within the chain.
-
Identical objects are equal. 0 === -0
, but they aren't identical.
-See the Harmony egal
proposal .
+
Returns a negated version of the passed-in predicate.
-
if (a === b) return a !== 0 || 1 / a == 1 / b;
+
_.negate = function (predicate) {
+ return function () {
+ return !predicate.apply(this , arguments );
+ };
+ };
@@ -1917,11 +1905,21 @@ See the
Harmony
¶
-
A strict comparison is necessary because null == undefined
.
+
Returns a function that is the composition of a list of functions, each
+consuming the return value of the function that follows.
-
if (a == null || b == null ) return a === b;
+
_.compose = function () {
+ var args = arguments ;
+ var start = args.length - 1 ;
+ return function () {
+ var i = start;
+ var result = args[start].apply(this , arguments );
+ while (i--) result = args[i].call(this , result);
+ return result;
+ };
+ };
@@ -1932,12 +1930,17 @@ See the
Harmony
¶
-
Unwrap any wrapped objects.
+
Returns a function that will only be executed on and after the Nth call.
-
if (a instanceof _) a = a._wrapped;
- if (b instanceof _) b = b._wrapped;
+
_.after = function (times, func) {
+ return function () {
+ if (--times < 1 ) {
+ return func.apply(this , arguments );
+ }
+ };
+ };
@@ -1948,13 +1951,20 @@ See the
Harmony
¶
-
Compare [[Class]]
names.
+
Returns a function that will only be executed up to (but not including) the Nth call.
-
var className = toString.call(a);
- if (className != toString.call(b)) return false ;
- switch (className) {
+
_.before = function (times, func) {
+ var memo;
+ return function () {
+ if (--times > 0 ) {
+ memo = func.apply(this , arguments );
+ }
+ if (times <= 1 ) func = null ;
+ return memo;
+ };
+ };
@@ -1965,11 +1975,12 @@ See the
Harmony
¶
-
Strings, numbers, dates, and booleans are compared by value.
+
Returns a function that will be executed at most one time, no matter how
+often you call it. Useful for lazy initialization.
-
+
_.once = _.partial(_.before, 2 );
@@ -1980,14 +1991,10 @@ See the
Harmony
¶
- Primitives and their corresponding object wrappers are equivalent; thus, "5"
is
-equivalent to new String("5")
.
+ Object Functions
-
return a == String(b);
- case '[object Number]' :
-
@@ -1997,15 +2004,9 @@ equivalent to
new String("5")
.
-
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]' :
-
@@ -2015,13 +2016,18 @@ other numeric values.
- 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.
+ Keys in IE < 9 that won’t be iterated by for key in ...
and thus missed.
-
+
var hasEnumBug = !{toString: null }.propertyIsEnumerable('toString' );
+ var nonEnumerableProps = ['valueOf' , 'isPrototypeOf' , 'toString' ,
+ 'propertyIsEnumerable' , 'hasOwnProperty' , 'toLocaleString' ];
+
+ function collectNonEnumProps (obj, keys) {
+ var nonEnumIdx = nonEnumerableProps.length;
+ var constructor = obj.constructor;
+ var proto = (_.isFunction(constructor) && constructor.prototype) || ObjProto;
@@ -2032,17 +2038,20 @@ of
NaN
are not equivalent.
-
RegExps are compared by their source patterns and flags.
+
Constructor is a special case.
- case '[object RegExp]' :
- return a.source == b.source &&
- a.global == b.global &&
- a.multiline == b.multiline &&
- a.ignoreCase == b.ignoreCase;
+ var prop = 'constructor' ;
+ if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop);
+
+ while (nonEnumIdx--) {
+ prop = nonEnumerableProps[nonEnumIdx];
+ if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) {
+ keys.push(prop);
+ }
}
- if (typeof a != 'object' || typeof b != 'object' ) return false ;
+ }
@@ -2053,13 +2062,16 @@ of NaN
are not equivalent.
- Assume equality for cyclic structures. The algorithm for detecting cyclic
-structures is adapted from ES 5.1 section 15.12.3, abstract operation JO
.
+ Retrieve the names of an object’s own properties.
+Delegates to ECMAScript 5 ‘s native Object.keys
-
var length = aStack.length;
- while (length--) {
+
_.keys = function (obj) {
+ if (!_.isObject(obj)) return [];
+ if (nativeKeys) return nativeKeys(obj);
+ var keys = [];
+ for (var key in obj) if (_.has(obj, key)) keys.push(key);
@@ -2070,13 +2082,13 @@ structures is adapted from ES 5.1 section 15.12.3, abstract operation
JO
- Linear search. Performance is inversely proportional to the number of
-unique nested structures.
+ Ahem, IE < 9.
-
if (aStack[length] == a) return bStack[length] == b;
- }
+
if (hasEnumBug) collectNonEnumProps(obj, keys);
+ return keys;
+ };
@@ -2087,16 +2099,14 @@ unique nested structures.
-
Objects with different constructors are not equivalent, but Object
s
-from different frames are.
+
Retrieve all the property names of an object.
-
var aCtor = a.constructor, bCtor = b.constructor;
- if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) &&
- _.isFunction(bCtor) && (bCtor instanceof bCtor))) {
- return false ;
- }
+
_.allKeys = function (obj) {
+ if (!_.isObject(obj)) return [];
+ var keys = [];
+ for (var key in obj) keys.push(key);
@@ -2107,13 +2117,13 @@ from different frames are.
-
Add the first object to the stack of traversed objects.
+
Ahem, IE < 9.
- aStack.push(a);
- bStack.push(b);
- var size = 0 , result = true ;
+ if (hasEnumBug) collectNonEnumProps(obj, keys);
+ return keys;
+ };
@@ -2124,11 +2134,19 @@ from different frames are.
-
Recursively compare objects and arrays.
+
Retrieve the values of an object’s properties.
-
if (className == '[object Array]' ) {
+
_.values = function (obj) {
+ var keys = _.keys(obj);
+ var length = keys.length;
+ var values = Array (length);
+ for (var i = 0 ; i < length; i++) {
+ values[i] = obj[keys[i]];
+ }
+ return values;
+ };
@@ -2139,13 +2157,23 @@ from different frames are.
-
Compare array lengths to determine if a deep comparison is necessary.
+
Returns the results of applying the iteratee to each element of the object
+In contrast to _.map it returns an object
-
size = a.length;
- result = size == b.length;
- if (result) {
+
_.mapObject = function (obj, iteratee, context) {
+ iteratee = cb(iteratee, context);
+ var keys = _.keys(obj),
+ length = keys.length,
+ results = {},
+ currentKey;
+ for (var index = 0 ; index < length; index++) {
+ currentKey = keys[index];
+ results[currentKey] = iteratee(obj[currentKey], currentKey, obj);
+ }
+ return results;
+ };
@@ -2156,15 +2184,19 @@ from different frames are.
-
Deep compare the contents, ignoring non-numeric properties.
+
Convert an object into a list of [key, value]
pairs.
-
while (size--) {
- if (!(result = eq(a[size], b[size], aStack, bStack))) break ;
- }
- }
- } else {
+
_.pairs = function (obj) {
+ var keys = _.keys(obj);
+ var length = keys.length;
+ var pairs = Array (length);
+ for (var i = 0 ; i < length; i++) {
+ pairs[i] = [keys[i], obj[keys[i]]];
+ }
+ return pairs;
+ };
@@ -2175,12 +2207,18 @@ from different frames are.
-
Deep compare objects.
+
Invert the keys and values of an object. The values must be serializable.
-
for (var key in a) {
- if (_.has(a, key)) {
+
_.invert = function (obj) {
+ var result = {};
+ var keys = _.keys(obj);
+ for (var i = 0 , length = keys.length; i < length; i++) {
+ result[obj[keys[i]]] = keys[i];
+ }
+ return result;
+ };
@@ -2191,11 +2229,18 @@ from different frames are.
-
Count the expected number of properties.
+
Return a sorted list of the function names available on the object.
+Aliased as methods
-
+
_.functions = _.methods = function (obj) {
+ var names = [];
+ for (var key in obj) {
+ if (_.isFunction(obj[key])) names.push(key);
+ }
+ return names.sort();
+ };
@@ -2206,13 +2251,11 @@ from different frames are.
-
Deep compare each member.
+
Extend a given object with all the properties in passed-in object(s).
-
if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break ;
- }
- }
+
_.extend = createAssigner(_.allKeys);
@@ -2223,17 +2266,12 @@ from different frames are.
-
Ensure that both objects contain the same number of properties.
+
Assigns a given object with all the own properties in the passed-in object(s)
+(https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign )
-
if (result) {
- for (key in b) {
- if (_.has(b, key) && !(size--)) break ;
- }
- result = !size;
- }
- }
+
_.extendOwn = _.assign = createAssigner(_.keys);
@@ -2244,13 +2282,17 @@ from different frames are.
-
Remove the first object from the stack of traversed objects.
+
Returns the first key on an object that passes a predicate test
-
aStack.pop();
- bStack.pop();
- return result;
+ _.findKey = function (obj, predicate, context) {
+ predicate = cb(predicate, context);
+ var keys = _.keys(obj), key;
+ for (var i = 0 , length = keys.length; i < length; i++) {
+ key = keys[i];
+ if (predicate(obj[key], key, obj)) return key;
+ }
};
@@ -2262,12 +2304,27 @@ from different frames are.
- Perform a deep comparison to check if two objects are equal.
+ Return a copy of the object only containing the whitelisted properties.
-
_.isEqual = function (a, b) {
- return eq(a, b, [], []);
+ _.pick = function (object, oiteratee, context) {
+ var result = {}, obj = object, iteratee, keys;
+ if (obj == null ) return result;
+ if (_.isFunction(oiteratee)) {
+ keys = _.allKeys(obj);
+ iteratee = optimizeCb(oiteratee, context);
+ } else {
+ keys = flatten(arguments , false , false , 1 );
+ iteratee = function (value, key, obj) { return key in obj; };
+ obj = Object (obj);
+ }
+ for (var i = 0 , length = keys.length; i < length; i++) {
+ var key = keys[i];
+ var value = obj[key];
+ if (iteratee(value, key, obj)) result[key] = value;
+ }
+ return result;
};
@@ -2279,16 +2336,20 @@ from different frames are.
- Is a given array, string, or object empty?
-An "empty" object has no enumerable own-properties.
+ Return a copy of the object without the blacklisted properties.
-
_.isEmpty = function (obj) {
- if (obj == null ) return true ;
- if (_.isArray(obj) || _.isString(obj)) return obj.length === 0 ;
- for (var key in obj) if (_.has(obj, key)) return false ;
- return true ;
+ _.omit = function (obj, iteratee, context) {
+ if (_.isFunction(iteratee)) {
+ iteratee = _.negate(iteratee);
+ } else {
+ var keys = _.map(flatten(arguments , false , false , 1 ), String );
+ iteratee = function (value, key) {
+ return !_.contains(keys, key);
+ };
+ }
+ return _.pick(obj, iteratee, context);
};
@@ -2300,13 +2361,11 @@ An "empty" object has no enumerable own-properties.
- Is a given value a DOM element?
+ Fill in a given object with default properties.
-
_.isElement = function (obj) {
- return !!(obj && obj.nodeType === 1 );
- };
+
_.defaults = createAssigner(_.allKeys, true );
@@ -2317,13 +2376,16 @@ An "empty" object has no enumerable own-properties.
-
Is a given value an array?
-Delegates to ECMA5's native Array.isArray
+
Creates an object that inherits from the given prototype object.
+If additional properties are provided then they will be added to the
+created object.
-
_.isArray = nativeIsArray || function (obj) {
- return toString.call(obj) == '[object Array]' ;
+ _.create = function (prototype, props) {
+ var result = baseCreate(prototype);
+ if (props) _.extendOwn(result, props);
+ return result;
};
@@ -2335,12 +2397,13 @@ Delegates to ECMA5's native Array.isArray
- Is a given variable an object?
+ Create a (shallow-cloned) duplicate of an object.
-
_.isObject = function (obj) {
- return obj === Object(obj);
+ _.clone = function (obj) {
+ if (!_.isObject(obj)) return obj;
+ return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
};
@@ -2352,15 +2415,16 @@ Delegates to ECMA5's native Array.isArray
- Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.
+ Invokes interceptor with the obj, and then returns obj.
+The primary purpose of this method is to “tap into” a method chain, in
+order to perform operations on intermediate results within the chain.
-
each(['Arguments' , 'Function' , 'String' , 'Number' , 'Date' , 'RegExp' ], function (name) {
- _['is' + name] = function (obj) {
- return toString.call(obj) == '[object ' + name + ']' ;
- };
- });
+
_.tap = function (obj, interceptor) {
+ interceptor(obj);
+ return obj;
+ };
@@ -2371,16 +2435,20 @@ Delegates to ECMA5's native Array.isArray
-
Define a fallback version of the method in browsers (ahem, IE), where
-there isn't any inspectable "Arguments" type.
+
Returns whether an object has a given set of key:value
pairs.
-
if (!_.isArguments(arguments)) {
- _.isArguments = function (obj) {
- return !!(obj && _.has(obj, 'callee' ));
- };
- }
+
_.isMatch = function (object, attrs) {
+ var keys = _.keys(attrs), length = keys.length;
+ if (object == null ) return !length;
+ var obj = Object (object);
+ for (var i = 0 ; i < length; i++) {
+ var key = keys[i];
+ if (attrs[key] !== obj[key] || !(key in obj)) return false ;
+ }
+ return true ;
+ };
@@ -2391,15 +2459,11 @@ there isn't any inspectable "Arguments" type.
-
Optimize isFunction
if appropriate.
+
Internal recursive comparison function for isEqual
.
-
if (typeof (/./ ) !== 'function' ) {
- _.isFunction = function (obj) {
- return typeof obj === 'function' ;
- };
- }
+
var eq = function (a, b, aStack, bStack) {
@@ -2410,13 +2474,12 @@ there isn't any inspectable "Arguments" type.
-
Is a given object a finite number?
+
Identical objects are equal. 0 === -0
, but they aren’t identical.
+See the Harmony egal
proposal .
-
_.isFinite = function (obj) {
- return isFinite(obj) && !isNaN(parseFloat(obj));
- };
+
if (a === b) return a !== 0 || 1 / a === 1 / b;
@@ -2427,13 +2490,11 @@ there isn't any inspectable "Arguments" type.
-
Is the given value NaN
? (NaN is the only number which does not equal itself).
+
A strict comparison is necessary because null == undefined
.
-
_.isNaN = function (obj) {
- return _.isNumber(obj) && obj != +obj;
- };
+
if (a == null || b == null ) return a === b;
@@ -2444,13 +2505,12 @@ there isn't any inspectable "Arguments" type.
-
Is a given value a boolean?
+
Unwrap any wrapped objects.
-
_.isBoolean = function (obj) {
- return obj === true || obj === false || toString.call(obj) == '[object Boolean]' ;
- };
+
if (a instanceof _) a = a._wrapped;
+ if (b instanceof _) b = b._wrapped;
@@ -2461,13 +2521,13 @@ there isn't any inspectable "Arguments" type.
-
Is a given value equal to null?
+
Compare [[Class]]
names.
- _.isNull = function (obj) {
- return obj === null ;
- };
+ var className = toString.call(a);
+ if (className !== toString.call(b)) return false ;
+ switch (className) {
@@ -2478,13 +2538,11 @@ there isn't any inspectable "Arguments" type.
- Is a given variable undefined?
+ Strings, numbers, regular expressions, dates, and booleans are compared by value.
- _.isUndefined = function (obj) {
- return obj === void 0 ;
- };
+
@@ -2495,14 +2553,11 @@ there isn't any inspectable "Arguments" type.
- Shortcut function for checking if an object has a given property directly
-on itself (in other words, not on a prototype).
+ RegExps are coerced to strings for comparison (Note: ‘’ + /a/i === ‘/a/i’)
- _.has = function (obj, key) {
- return hasOwnProperty.call(obj, key);
- };
+
@@ -2510,13 +2565,17 @@ on itself (in other words, not on a prototype).
-
+
-
Utility Functions
+
Primitives and their corresponding object wrappers are equivalent; thus, "5"
is
+equivalent to new String("5")
.
+
return '' + a === '' + b;
+ case '[object Number]' :
+
@@ -2526,15 +2585,12 @@ on itself (in other words, not on a prototype).
-
Run Underscore.js in noConflict mode, returning the _
variable to its
-previous owner. Returns a reference to the Underscore object.
+
NaN
s are equivalent, but non-reflexive.
+Object(NaN) is equivalent to NaN
- _.noConflict = function () {
- root._ = previousUnderscore;
- return this ;
- };
+ if (+a !== +a) return +b !== +b;
@@ -2545,13 +2601,13 @@ previous owner. Returns a reference to the Underscore object.
- Keep the identity function around for default iterators.
+ An egal
comparison is performed for other numeric values.
- _.identity = function (value) {
- return value;
- };
+ return +a === 0 ? 1 / +a === 1 / b : +a === +b;
+ case '[object Date]' :
+ case '[object Boolean]' :
@@ -2562,15 +2618,18 @@ previous owner. Returns a reference to the Underscore object.
- Run a function n times.
+ 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.
- _.times = function (n, iterator, context) {
- var accum = Array(Math.max(0 , n));
- for (var i = 0 ; i < n; i++) accum[i] = iterator.call(context, i);
- return accum;
- };
+ return +a === +b;
+ }
+
+ var areArrays = className === '[object Array]' ;
+ if (!areArrays) {
+ if (typeof a != 'object' || typeof b != 'object' ) return false ;
@@ -2581,17 +2640,18 @@ previous owner. Returns a reference to the Underscore object.
- Return a random integer between min and max (inclusive).
+ Objects with different constructors are not equivalent, but Object
s or Array
s
+from different frames are.
- _.random = function (min, max) {
- if (max == null ) {
- max = min;
- min = 0 ;
- }
- return min + Math.floor(Math.random() * (max - min + 1 ));
- };
+ var aCtor = a.constructor, bCtor = b.constructor;
+ if (aCtor !== bCtor && !(_.isFunction(aCtor) && aCtor instanceof aCtor &&
+ _.isFunction(bCtor) && bCtor instanceof bCtor)
+ && ('constructor' in a && 'constructor' in b)) {
+ return false ;
+ }
+ }
@@ -2602,21 +2662,11 @@ previous owner. Returns a reference to the Underscore object.
- List of HTML entities for escaping.
+ 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 entityMap = {
- escape: {
- '&' : '&' ,
- '<' : '<' ,
- '>' : '>' ,
- '"' : '"' ,
- "'" : '''
- }
- };
- entityMap.unescape = _.invert(entityMap.escape);
-
@@ -2626,14 +2676,15 @@ previous owner. Returns a reference to the Underscore object.
- Regexes containing the keys and values listed immediately above.
+ Initializing stack of traversed objects.
+It’s done here since we only need them for objects and arrays comparison.
- var entityRegexes = {
- escape: new RegExp('[' + _.keys(entityMap.escape).join('' ) + ']' , 'g' ),
- unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|' ) + ')' , 'g' )
- };
+ aStack = aStack || [];
+ bStack = bStack || [];
+ var length = aStack.length;
+ while (length--) {
@@ -2644,18 +2695,13 @@ previous owner. Returns a reference to the Underscore object.
- Functions for escaping and unescaping strings to/from HTML interpolation.
+ Linear search. Performance is inversely proportional to the number of
+unique nested structures.
- _.each(['escape' , 'unescape' ], function (method) {
- _[method] = function (string) {
- if (string == null ) return '' ;
- return ('' + string).replace(entityRegexes[method], function (match) {
- return entityMap[method][match];
- });
- };
- });
+ if (aStack[length] === a) return bStack[length] === b;
+ }
@@ -2666,16 +2712,12 @@ previous owner. Returns a reference to the Underscore object.
- If the value of the named property
is a function then invoke it with the
-object
as context; otherwise, return it.
+ Add the first object to the stack of traversed objects.
- _.result = function (object, property) {
- if (object == null ) return void 0 ;
- var value = object[property];
- return _.isFunction(value) ? value.call(object) : value;
- };
+ aStack.push(a);
+ bStack.push(b);
@@ -2686,20 +2728,11 @@ previous owner. Returns a reference to the Underscore object.
- Add your own custom functions to the Underscore object.
+ Recursively compare objects and arrays.
- _.mixin = function (obj) {
- each(_.functions(obj), function (name) {
- var func = _[name] = obj[name];
- _.prototype[name] = function () {
- var args = [this ._wrapped];
- push.apply(args, arguments);
- return result.call(this , func.apply(_, args));
- };
- });
- };
+
@@ -2710,16 +2743,12 @@ previous owner. Returns a reference to the Underscore object.
- Generate a unique integer id (unique within the entire client session).
-Useful for temporary DOM ids.
+ Compare array lengths to determine if a deep comparison is necessary.
- var idCounter = 0 ;
- _.uniqueId = function (prefix) {
- var id = ++idCounter + '' ;
- return prefix ? prefix + id : id;
- };
+ length = a.length;
+ if (length !== b.length) return false ;
@@ -2730,16 +2759,14 @@ Useful for temporary DOM ids.
- By default, Underscore uses ERB-style template delimiters, change the
-following template settings to use alternative delimiters.
+ Deep compare the contents, ignoring non-numeric properties.
- _.templateSettings = {
- evaluate : /<%([\s\S]+?)%>/g ,
- interpolate : /<%=([\s\S]+?)%>/g ,
- escape : /<%-([\s\S]+?)%>/g
- };
+ while (length--) {
+ if (!eq(a[length], b[length], aStack, bStack)) return false ;
+ }
+ } else {
@@ -2750,13 +2777,12 @@ following template settings to use alternative delimiters.
- 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.
+ Deep compare objects.
-
+ var keys = _.keys(a), key;
+ length = keys.length;
@@ -2767,22 +2793,12 @@ guaranteed not to match.
- Certain characters need to be escaped so that they can be put into a
-string literal.
+ Ensure that both objects contain the same number of properties before comparing deep equality.
- var escapes = {
- "'" : "'" ,
- '\\' : '\\' ,
- '\r' : 'r' ,
- '\n' : 'n' ,
- '\t' : 't' ,
- '\u2028' : 'u2028' ,
- '\u2029' : 'u2029'
- };
-
- var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g ;
+ if (_.keys(b).length !== length) return false ;
+ while (length--) {
@@ -2793,15 +2809,14 @@ string literal.
- JavaScript micro-templating, similar to John Resig's implementation.
-Underscore templating handles arbitrary delimiters, preserves whitespace,
-and correctly escapes quotes within interpolated code.
+ Deep compare each member
- _.template = function (text, data, settings) {
- var render;
- settings = _.defaults({}, settings, _.templateSettings);
+ key = keys[length];
+ if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false ;
+ }
+ }
@@ -2812,15 +2827,14 @@ and correctly escapes quotes within interpolated code.
- Combine delimiters into one regular expression via alternation.
+ Remove the first object from the stack of traversed objects.
- var matcher = new RegExp([
- (settings.escape || noMatch).source,
- (settings.interpolate || noMatch).source,
- (settings.evaluate || noMatch).source
- ].join('|' ) + '|$' , 'g' );
+ aStack.pop();
+ bStack.pop();
+ return true ;
+ };
@@ -2831,29 +2845,13 @@ and correctly escapes quotes within interpolated code.
- Compile the template source, escaping string literals appropriately.
+ Perform a deep comparison to check if two objects are equal.
- var index = 0 ;
- var source = "__p+='" ;
- text.replace(matcher, function (match, escape, interpolate, evaluate, offset) {
- source += text.slice(index, offset)
- .replace(escaper, function (match) { return '\\' + escapes[match]; });
-
- if (escape) {
- source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'" ;
- }
- if (interpolate) {
- source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'" ;
- }
- if (evaluate) {
- source += "';\n" + evaluate + "\n__p+='" ;
- }
- index = offset + match.length;
- return match;
- });
- source += "';\n" ;
+ _.isEqual = function (a, b) {
+ return eq(a, b);
+ };
@@ -2864,27 +2862,16 @@ and correctly escapes quotes within interpolated code.
- If a variable is not specified, place data values in local scope.
+ Is a given array, string, or object empty?
+An “empty” object has no enumerable own-properties.
- if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n' ;
-
- source = "var __t,__p='',__j=Array.prototype.join," +
- "print=function(){__p+=__j.call(arguments,'');};\n" +
- source + "return __p;\n" ;
-
- try {
- render = new Function(settings.variable || 'obj' , '_' , source);
- } catch (e) {
- e.source = source;
- throw e;
- }
-
- if (data) return render(data, _);
- var template = function (data) {
- return render.call(this , data, _);
- };
+ _.isEmpty = function (obj) {
+ if (obj == null ) return true ;
+ if (isArrayLike(obj) && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0 ;
+ return _.keys(obj).length === 0 ;
+ };
@@ -2895,13 +2882,12 @@ and correctly escapes quotes within interpolated code.
- Provide the compiled function source as a convenience for precompilation.
+ Is a given value a DOM element?
- template.source = 'function(' + (settings.variable || 'obj' ) + '){\n' + source + '}' ;
-
- return template;
+ _.isElement = function (obj) {
+ return !!(obj && obj.nodeType === 1 );
};
@@ -2913,12 +2899,13 @@ and correctly escapes quotes within interpolated code.
- Add a "chain" function, which will delegate to the wrapper.
+ Is a given value an array?
+Delegates to ECMA5’s native Array.isArray
-
_.chain = function (obj) {
- return _(obj).chain();
+ _.isArray = nativeIsArray || function (obj) {
+ return toString.call(obj) === '[object Array]' ;
};
@@ -2927,13 +2914,18 @@ and correctly escapes quotes within interpolated code.
-
+
-
OOP
+
Is a given variable an object?
+
_.isObject = function (obj) {
+ var type = typeof obj;
+ return type === 'function' || type === 'object' && !!obj;
+ };
+
@@ -2943,16 +2935,15 @@ and correctly escapes quotes within interpolated code.
-
If Underscore is called as a function, it returns a wrapped object that
-can be used OO-style. This wrapper holds altered versions of all the
-underscore functions. Wrapped objects may be chained.
-
Helper function to continue chaining intermediate results.
+
Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError.
- var result = function (obj) {
- return this ._chain ? _(obj).chain() : obj;
- };
+ _.each(['Arguments' , 'Function' , 'String' , 'Number' , 'Date' , 'RegExp' , 'Error' ], function (name) {
+ _['is' + name] = function (obj) {
+ return toString.call(obj) === '[object ' + name + ']' ;
+ };
+ });
@@ -2963,11 +2954,16 @@ underscore functions. Wrapped objects may be chained.
-
Add all of the Underscore functions to the wrapper object.
+
Define a fallback version of the method in browsers (ahem, IE < 9), where
+there isn’t any inspectable “Arguments” type.
-
+
if (!_.isArguments(arguments )) {
+ _.isArguments = function (obj) {
+ return _.has(obj, 'callee' );
+ };
+ }
@@ -2978,19 +2974,16 @@ underscore functions. Wrapped objects may be chained.
-
Add all mutator Array functions to the wrapper.
+
Optimize isFunction
if appropriate. Work around some typeof bugs in old v8,
+IE 11 (#1621), and in Safari 8 (#1929).
-
each(['pop' , 'push' , 'reverse' , 'shift' , 'sort' , 'splice' , 'unshift' ], function (name) {
- var method = ArrayProto[name];
- _.prototype[name] = function () {
- var obj = this ._wrapped;
- method.apply(obj, arguments);
- if ((name == 'shift' || name == 'splice' ) && obj.length === 0 ) delete obj[0 ];
- return result.call(this , obj);
+ if (typeof /./ != 'function' && typeof Int8Array != 'object' ) {
+ _.isFunction = function (obj) {
+ return typeof obj == 'function' || false ;
};
- });
+ }
@@ -3001,18 +2994,13 @@ underscore functions. Wrapped objects may be chained.
-
Add all accessor Array functions to the wrapper.
+
Is a given object a finite number?
- each(['concat' , 'join' , 'slice' ], function (name) {
- var method = ArrayProto[name];
- _.prototype[name] = function () {
- return result.call(this , method.apply(this ._wrapped, arguments));
- };
- });
-
- _.extend(_.prototype, {
+ _.isFinite = function (obj) {
+ return isFinite (obj) && !isNaN (parseFloat (obj));
+ };
@@ -3023,14 +3011,13 @@ underscore functions. Wrapped objects may be chained.
- Start chaining a wrapped Underscore object.
+ Is the given value NaN
? (NaN is the only number which does not equal itself).
- chain: function () {
- this ._chain = true ;
- return this ;
- },
+ _.isNaN = function (obj) {
+ return _.isNumber(obj) && obj !== +obj;
+ };
@@ -3041,17 +3028,761 @@ underscore functions. Wrapped objects may be chained.
+ Is a given value a boolean?
+
+
+
+ _.isBoolean = function (obj) {
+ return obj === true || obj === false || toString.call(obj) === '[object Boolean]' ;
+ };
+
+
+
+
+
+
+
+
+
Is a given value equal to null?
+
+
+
+ _.isNull = function (obj) {
+ return obj === null ;
+ };
+
+
+
+
+
+
+
+
+
Is a given variable undefined?
+
+
+
+ _.isUndefined = function (obj) {
+ return obj === void 0 ;
+ };
+
+
+
+
+
+
+
+
+
Shortcut function for checking if an object has a given property directly
+on itself (in other words, not on a prototype).
+
+
+
+ _.has = function (obj, key) {
+ return obj != null && hasOwnProperty.call(obj, key);
+ };
+
+
+
+
+
+
+
+
+
Utility Functions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Run Underscore.js in noConflict mode, returning the _
variable to its
+previous owner. Returns a reference to the Underscore object.
+
+
+
+ _.noConflict = function () {
+ root._ = previousUnderscore;
+ return this ;
+ };
+
+
+
+
+
+
+
+
+
Keep the identity function around for default iteratees.
+
+
+
+ _.identity = function (value) {
+ return value;
+ };
+
+
+
+
+
+
+
+
+
Predicate-generating functions. Often useful outside of Underscore.
+
+
+
+ _.constant = function (value) {
+ return function () {
+ return value;
+ };
+ };
+
+ _.noop = function () {};
+
+ _.property = property;
+
+
+
+
+
+
+
+
+
Generates a function for a given object that returns a given property.
+
+
+
+ _.propertyOf = function (obj) {
+ return obj == null ? function () {} : function (key) {
+ return obj[key];
+ };
+ };
+
+
+
+
+
+
+
+
+
Returns a predicate for checking whether an object has a given set of
+key:value
pairs.
+
+
+
+ _.matcher = _.matches = function (attrs) {
+ attrs = _.extendOwn({}, attrs);
+ return function (obj) {
+ return _.isMatch(obj, attrs);
+ };
+ };
+
+
+
+
+
+
+
+
+
Run a function n times.
+
+
+
+ _.times = function (n, iteratee, context) {
+ var accum = Array (Math .max(0 , n));
+ iteratee = optimizeCb(iteratee, context, 1 );
+ for (var i = 0 ; i < n; i++) accum[i] = iteratee(i);
+ return accum;
+ };
+
+
+
+
+
+
+
+
+
Return a random integer between min and max (inclusive).
+
+
+
+ _.random = function (min, max) {
+ if (max == null ) {
+ max = min;
+ min = 0 ;
+ }
+ return min + Math .floor(Math .random() * (max - min + 1 ));
+ };
+
+
+
+
+
+
+
+
+
A (possibly faster) way to get the current timestamp as an integer.
+
+
+
+ _.now = Date .now || function () {
+ return new Date ().getTime();
+ };
+
+
+
+
+
+
+
+
+
List of HTML entities for escaping.
+
+
+
+ var escapeMap = {
+ '&' : '&' ,
+ '<' : '<' ,
+ '>' : '>' ,
+ '"' : '"' ,
+ "'" : ''' ,
+ '`' : '`'
+ };
+ var unescapeMap = _.invert(escapeMap);
+
+
+
+
+
+
+
+
+
Functions for escaping and unescaping strings to/from HTML interpolation.
+
+
+
+ var createEscaper = function (map) {
+ var escaper = function (match) {
+ return map[match];
+ };
+
+
+
+
+
+
+
+
+
Regexes for identifying a key that needs to be escaped
+
+
+
+ var source = '(?:' + _.keys(map).join('|' ) + ')' ;
+ var testRegexp = RegExp (source);
+ var replaceRegexp = RegExp (source, 'g' );
+ return function (string) {
+ string = string == null ? '' : '' + string;
+ return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string;
+ };
+ };
+ _.escape = createEscaper(escapeMap);
+ _.unescape = createEscaper(unescapeMap);
+
+
+
+
+
+
+
+
+
If the value of the named property
is a function then invoke it with the
+object
as context; otherwise, return it.
+
+
+
+ _.result = function (object, property, fallback) {
+ var value = object == null ? void 0 : object[property];
+ if (value === void 0 ) {
+ value = fallback;
+ }
+ return _.isFunction(value) ? value.call(object) : value;
+ };
+
+
+
+
+
+
+
+
+
Generate a unique integer id (unique within the entire client session).
+Useful for temporary DOM ids.
+
+
+
+ var idCounter = 0 ;
+ _.uniqueId = function (prefix) {
+ var id = ++idCounter + '' ;
+ return prefix ? prefix + id : id;
+ };
+
+
+
+
+
+
+
+
+
By default, Underscore uses ERB-style template delimiters, change the
+following template settings to use alternative delimiters.
+
+
+
+ _.templateSettings = {
+ evaluate : /<%([\s\S]+?)%>/g ,
+ interpolate : /<%=([\s\S]+?)%>/g ,
+ 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.
+
+
+
+
+
+
+
+
+
+
+
+
+
Certain characters need to be escaped so that they can be put into a
+string literal.
+
+
+
+ var escapes = {
+ "'" : "'" ,
+ '\\' : '\\' ,
+ '\r' : 'r' ,
+ '\n' : 'n' ,
+ '\u2028' : 'u2028' ,
+ '\u2029' : 'u2029'
+ };
+
+ var escaper = /\\|'|\r|\n|\u2028|\u2029/g ;
+
+ var escapeChar = function (match) {
+ return '\\' + escapes[match];
+ };
+
+
+
+
+
+
+
+
+
JavaScript micro-templating, similar to John Resig’s implementation.
+Underscore templating handles arbitrary delimiters, preserves whitespace,
+and correctly escapes quotes within interpolated code.
+NB: oldSettings
only exists for backwards compatibility.
+
+
+
+ _.template = function (text, settings, oldSettings) {
+ if (!settings && oldSettings) settings = oldSettings;
+ settings = _.defaults({}, settings, _.templateSettings);
+
+
+
+
+
+
+
+
+
Combine delimiters into one regular expression via alternation.
+
+
+
+ var matcher = RegExp ([
+ (settings.escape || noMatch).source,
+ (settings.interpolate || noMatch).source,
+ (settings.evaluate || noMatch).source
+ ].join('|' ) + '|$' , 'g' );
+
+
+
+
+
+
+
+
+
Compile the template source, escaping string literals appropriately.
+
+
+
+ var index = 0 ;
+ var source = "__p+='" ;
+ text.replace(matcher, function (match, escape, interpolate, evaluate, offset) {
+ source += text.slice(index, offset).replace(escaper, escapeChar);
+ index = offset + match.length;
+
+ if (escape ) {
+ source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'" ;
+ } else if (interpolate) {
+ source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'" ;
+ } else if (evaluate) {
+ source += "';\n" + evaluate + "\n__p+='" ;
+ }
+
+
+
+
+
+
+
+
+
Adobe VMs need the match returned to produce the correct offset.
+
+
+
+ return match;
+ });
+ source += "';\n" ;
+
+
+
+
+
+
+
+
+
If a variable is not specified, place data values in local scope.
+
+
+
+ if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n' ;
+
+ source = "var __t,__p='',__j=Array.prototype.join," +
+ "print=function(){__p+=__j.call(arguments,'');};\n" +
+ source + 'return __p;\n' ;
+
+ try {
+ var render = new Function (settings.variable || 'obj' , '_' , source);
+ } catch (e) {
+ e.source = source;
+ throw e;
+ }
+
+ var template = function (data) {
+ return render.call(this , data, _);
+ };
+
+
+
+
+
+
+
+
+
Provide the compiled source as a convenience for precompilation.
+
+
+
+ var argument = settings.variable || 'obj' ;
+ template.source = 'function(' + argument + '){\n' + source + '}' ;
+
+ return template;
+ };
+
+
+
+
+
+
+
+
+
Add a “chain” function. Start chaining a wrapped Underscore object.
+
+
+
+ _.chain = function (obj) {
+ var instance = _(obj);
+ instance._chain = true ;
+ return instance;
+ };
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
If Underscore is called as a function, it returns a wrapped object that
+can be used OO-style. This wrapper holds altered versions of all the
+underscore functions. Wrapped objects may be chained.
+
+
+
+
+
+
+
+
+
+
+
Helper function to continue chaining intermediate results.
+
+
+
+ var result = function (instance, obj) {
+ return instance._chain ? _(obj).chain() : obj;
+ };
+
+
+
+
+
+
+
+
+
Add your own custom functions to the Underscore object.
+
+
+
+ _.mixin = function (obj) {
+ _.each(_.functions(obj), function (name) {
+ var func = _[name] = obj[name];
+ _.prototype[name] = function () {
+ var args = [this ._wrapped];
+ push.apply(args, arguments );
+ return result(this , func.apply(_, args));
+ };
+ });
+ };
+
+
+
+
+
+
+
+
+
Add all of the Underscore functions to the wrapper object.
+
+
+
+
+
+
+
+
+
+
+
+
+
Add all mutator Array functions to the wrapper.
+
+
+
+ _.each(['pop' , 'push' , 'reverse' , 'shift' , 'sort' , 'splice' , 'unshift' ], function (name) {
+ var method = ArrayProto[name];
+ _.prototype[name] = function () {
+ var obj = this ._wrapped;
+ method.apply(obj, arguments );
+ if ((name === 'shift' || name === 'splice' ) && obj.length === 0 ) delete obj[0 ];
+ return result(this , obj);
+ };
+ });
+
+
+
+
+
+
+
+
+
Add all accessor Array functions to the wrapper.
+
+
+
+ _.each(['concat' , 'join' , 'slice' ], function (name) {
+ var method = ArrayProto[name];
+ _.prototype[name] = function () {
+ return result(this , method.apply(this ._wrapped, arguments ));
+ };
+ });
+
+
+
+
+
+
+
+
Extracts the result from a wrapped and chained object.
- value: function () {
- return this ._wrapped;
- }
+ _.prototype.value = function () {
+ return this ._wrapped;
+ };
+
+
+
+
+
+
+
+
+
Provide unwrapping proxy for some methods used in engine operations
+such as arithmetic and JSON stringification.
- });
+
+
+ _.prototype.valueOf = _.prototype.toJSON = _.prototype.value;
-}).call(this );
+ _.prototype.toString = function () {
+ return '' + this ._wrapped;
+ };
+
+
+
+
+
+
+
+
+
AMD registration happens at the end for compatibility with AMD loaders
+that may not enforce next-turn semantics on modules. Even though general
+practice for AMD registration is to be anonymous, underscore registers
+as a named module because, like jQuery, it is a base library that is
+popular enough to be bundled in a third party lib, but not be part of
+an AMD load request. Those cases could generate an error when an
+anonymous define() is called outside of a loader request.
+
+
+
+ if (typeof define === 'function' && define.amd) {
+ define('underscore' , [], function () {
+ return _;
+ });
+ }
+}.call(this ));
diff --git a/vendor/underscore/index.html b/vendor/underscore/index.html
index eddf69f38..173ca6fdc 100644
--- a/vendor/underscore/index.html
+++ b/vendor/underscore/index.html
@@ -6,7 +6,7 @@
-
+
Underscore.js
';
-
- // HTML markup for the UI
- var MARKUP = ' \
-
Run Tests \
-
Stop Tests \
-
\
-
\
-
Normalize results \
-
\
- \
- \
- \
- \
- ' + platform + ' \
- Test Ops/sec \
- \
- \
- Ready \
- \
-
\
-
\
-
\
-
\
- TinyURL (for chart): \
-
\
-
\
-
Powered by JSLitmus \
-
';
-
- /**
- * The public API for creating and running tests
- */
- window.JSLitmus = {
- /** The list of all tests that have been registered with JSLitmus.test */
- _tests: [],
- /** The queue of tests that need to be run */
- _queue: [],
-
- /**
- * The parsed query parameters the current page URL. This is provided as a
- * convenience for test functions - it's not used by JSLitmus proper
- */
- params: {},
-
- /**
- * Initialize
- */
- _init: function() {
- // Parse query params into JSLitmus.params[] hash
- var match = (location + '').match(/([^?#]*)(#.*)?$/);
- if (match) {
- var pairs = match[1].split('&');
- for (var i = 0; i < pairs.length; i++) {
- var pair = pairs[i].split('=');
- if (pair.length > 1) {
- var key = pair.shift();
- var value = pair.length > 1 ? pair.join('=') : pair[0];
- this.params[key] = value;
- }
- }
- }
-
- // Write out the stylesheet. We have to do this here because IE
- // doesn't honor sheets written after the document has loaded.
- document.write(STYLESHEET);
-
- // Setup the rest of the UI once the document is loaded
- if (window.addEventListener) {
- window.addEventListener('load', this._setup, false);
- } else if (document.addEventListener) {
- document.addEventListener('load', this._setup, false);
- } else if (window.attachEvent) {
- window.attachEvent('onload', this._setup);
- }
-
- return this;
- },
-
- /**
- * Set up the UI
- */
- _setup: function() {
- var el = jsl.$('jslitmus_container');
- if (!el) document.body.appendChild(el = document.createElement('div'));
-
- el.innerHTML = MARKUP;
-
- // Render the UI for all our tests
- for (var i=0; i < JSLitmus._tests.length; i++)
- JSLitmus.renderTest(JSLitmus._tests[i]);
- },
-
- /**
- * (Re)render all the test results
- */
- renderAll: function() {
- for (var i = 0; i < JSLitmus._tests.length; i++)
- JSLitmus.renderTest(JSLitmus._tests[i]);
- JSLitmus.renderChart();
- },
-
- /**
- * (Re)render the chart graphics
- */
- renderChart: function() {
- var url = JSLitmus.chartUrl();
- jsl.$('chart_link').href = url;
- jsl.$('chart_image').src = url;
- jsl.$('chart').style.display = '';
-
- // Update the tiny URL
- jsl.$('tiny_url').src = 'http://tinyurl.com/api-create.php?url='+escape(url);
- },
-
- /**
- * (Re)render the results for a specific test
- */
- renderTest: function(test) {
- // Make a new row if needed
- if (!test._row) {
- var trow = jsl.$('test_row_template');
- if (!trow) return;
-
- test._row = trow.cloneNode(true);
- test._row.style.display = '';
- test._row.id = '';
- test._row.onclick = function() {JSLitmus._queueTest(test);};
- test._row.title = 'Run ' + test.name + ' test';
- trow.parentNode.appendChild(test._row);
- test._row.cells[0].innerHTML = test.name;
- }
-
- var cell = test._row.cells[1];
- var cns = [test.loopArg ? 'test_looping' : 'test_nonlooping'];
-
- if (test.error) {
- cns.push('test_error');
- cell.innerHTML =
- '' + test.error + '
' +
- '' +
- jsl.join(test.error, ': ', ' ') +
- ' ';
- } else {
- if (test.running) {
- cns.push('test_running');
- cell.innerHTML = 'running';
- } else if (jsl.indexOf(JSLitmus._queue, test) >= 0) {
- cns.push('test_pending');
- cell.innerHTML = 'pending';
- } else if (test.count) {
- cns.push('test_done');
- var hz = test.getHz(jsl.$('test_normalize').checked);
- cell.innerHTML = hz != Infinity ? hz : '∞';
- } else {
- cell.innerHTML = 'ready';
- }
- }
- cell.className = cns.join(' ');
- },
-
- /**
- * Create a new test
- */
- test: function(name, f) {
- // Create the Test object
- var test = new Test(name, f);
- JSLitmus._tests.push(test);
-
- // Re-render if the test state changes
- test.onChange = JSLitmus.renderTest;
-
- // Run the next test if this one finished
- test.onStop = function(test) {
- if (JSLitmus.onTestFinish) JSLitmus.onTestFinish(test);
- JSLitmus.currentTest = null;
- JSLitmus._nextTest();
- };
-
- // Render the new test
- this.renderTest(test);
- },
-
- /**
- * Add all tests to the run queue
- */
- runAll: function(e) {
- e = e || window.event;
- var reverse = e && e.shiftKey, len = JSLitmus._tests.length;
- for (var i = 0; i < len; i++) {
- JSLitmus._queueTest(JSLitmus._tests[!reverse ? i : (len - i - 1)]);
- }
- },
-
- /**
- * Remove all tests from the run queue. The current test has to finish on
- * it's own though
- */
- stop: function() {
- while (JSLitmus._queue.length) {
- var test = JSLitmus._queue.shift();
- JSLitmus.renderTest(test);
- }
- },
-
- /**
- * Run the next test in the run queue
- */
- _nextTest: function() {
- if (!JSLitmus.currentTest) {
- var test = JSLitmus._queue.shift();
- if (test) {
- jsl.$('stop_button').disabled = false;
- JSLitmus.currentTest = test;
- test.run();
- JSLitmus.renderTest(test);
- if (JSLitmus.onTestStart) JSLitmus.onTestStart(test);
- } else {
- jsl.$('stop_button').disabled = true;
- JSLitmus.renderChart();
- }
- }
- },
-
- /**
- * Add a test to the run queue
- */
- _queueTest: function(test) {
- if (jsl.indexOf(JSLitmus._queue, test) >= 0) return;
- JSLitmus._queue.push(test);
- JSLitmus.renderTest(test);
- JSLitmus._nextTest();
- },
-
- /**
- * Generate a Google Chart URL that shows the data for all tests
- */
- chartUrl: function() {
- var n = JSLitmus._tests.length, markers = [], data = [];
- var d, min = 0, max = -1e10;
- var normalize = jsl.$('test_normalize').checked;
-
- // Gather test data
- for (var i=0; i < JSLitmus._tests.length; i++) {
- var test = JSLitmus._tests[i];
- if (test.count) {
- var hz = test.getHz(normalize);
- var v = hz != Infinity ? hz : 0;
- data.push(v);
- markers.push('t' + jsl.escape(test.name + '(' + jsl.toLabel(hz)+ ')') + ',000000,0,' +
- markers.length + ',10');
- max = Math.max(v, max);
- }
- }
- if (markers.length <= 0) return null;
-
- // Build chart title
- var title = document.getElementsByTagName('title');
- title = (title && title.length) ? title[0].innerHTML : null;
- var chart_title = [];
- if (title) chart_title.push(title);
- chart_title.push('Ops/sec (' + platform + ')');
-
- // Build labels
- var labels = [jsl.toLabel(min), jsl.toLabel(max)];
-
- var w = 250, bw = 15;
- var bs = 5;
- var h = markers.length*(bw + bs) + 30 + chart_title.length*20;
-
- var params = {
- chtt: escape(chart_title.join('|')),
- chts: '000000,10',
- cht: 'bhg', // chart type
- chd: 't:' + data.join(','), // data set
- chds: min + ',' + max, // max/min of data
- chxt: 'x', // label axes
- chxl: '0:|' + labels.join('|'), // labels
- chsp: '0,1',
- chm: markers.join('|'), // test names
- chbh: [bw, 0, bs].join(','), // bar widths
- // chf: 'bg,lg,0,eeeeee,0,eeeeee,.5,ffffff,1', // gradient
- chs: w + 'x' + h
- };
- return 'http://chart.apis.google.com/chart?' + jsl.join(params, '=', '&');
- }
- };
-
- JSLitmus._init();
-})();
\ No newline at end of file
diff --git a/vendor/underscore/test/vendor/qunit-extras.js b/vendor/underscore/test/vendor/qunit-extras.js
new file mode 100644
index 000000000..663d32489
--- /dev/null
+++ b/vendor/underscore/test/vendor/qunit-extras.js
@@ -0,0 +1,776 @@
+/*!
+ * QUnit Extras v1.4.1
+ * Copyright 2011-2015 John-David Dalton
+ * Based on a gist by Jörn Zaefferer
+ * Available under MIT license
+ */
+;(function() {
+
+ /** Used as a safe reference for `undefined` in pre ES5 environments. */
+ var undefined;
+
+ /** Used as a horizontal rule in console output. */
+ var hr = '----------------------------------------';
+
+ /** Used for native method references. */
+ var arrayProto = Array.prototype;
+
+ /** Native method shortcut. */
+ var push = arrayProto.push,
+ unshift = arrayProto.unshift;
+
+ /** Used to match HTML entities. */
+ var reEscapedHtml = /(&|<|>|"|')/g;
+
+ /** Used to match parts of the assert message. */
+ var reDied = /^Died on test #\d+/,
+ reMessage = /^([\s\S]*?)<\/span>/;
+
+ /** Used to associate color names with their corresponding codes. */
+ var ansiCodes = {
+ 'bold': 1,
+ 'green': 32,
+ 'magenta': 35,
+ 'red': 31
+ };
+
+ /** Used to convert HTML entities to characters. */
+ var htmlUnescapes = {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ ''': "'"
+ };
+
+ /** Used to determine if values are of the language type Object. */
+ var objectTypes = {
+ 'function': true,
+ 'object': true
+ };
+
+ /** Used as a reference to the global object. */
+ var root = (objectTypes[typeof window] && window) || this;
+
+ /** Detect free variable `exports`. */
+ var freeExports = objectTypes[typeof exports] && exports && !exports.nodeType && exports;
+
+ /** Detect free variable `module`. */
+ var freeModule = objectTypes[typeof module] && module && !module.nodeType && module;
+
+ /** Detect free variable `global` from Node.js or Browserified code and use it as `root`. */
+ var freeGlobal = freeExports && freeModule && typeof global == 'object' && global;
+ if (freeGlobal && (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal || freeGlobal.self === freeGlobal)) {
+ root = freeGlobal;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Checks if a given value is present in an array using strict equality
+ * for comparisons, i.e. `===`.
+ *
+ * @private
+ * @param {Array} array The array to iterate over.
+ * @param {*} value The value to check for.
+ * @returns {boolean} Returns `true` if the `value` is found, else `false`.
+ */
+ function contains(array, value) {
+ var index = -1,
+ length = array ? array.length : 0;
+
+ while (++index < length) {
+ if (array[index] === value) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks if `value` is the language type of `Object`.
+ * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an object, else `false`.
+ */
+ function isObject(value) {
+ var type = typeof value;
+ return type == 'function' || (value && type == 'object') || false;
+ }
+
+ /**
+ * Creates a string with `text` repeated `n` number of times.
+ *
+ * @private
+ * @param {string} text The text to repeat.
+ * @param {number} n The number of times to repeat `text`.
+ * @returns {string} The created string.
+ */
+ function repeat(text, n) {
+ return Array(n + 1).join(text);
+ }
+
+ /**
+ * Resolves the value of property `key` on `object`.
+ *
+ * @private
+ * @param {Object} object The object to inspect.
+ * @param {string} key The name of the property to resolve.
+ * @returns {*} Returns the resolved value.
+ */
+ function result(object, key) {
+ return object == null ? undefined : object[key];
+ }
+
+ /**
+ * Converts the HTML entities `&`, `<`, `>`, `"`, and `'`
+ * in `string` to their corresponding characters.
+ *
+ * @private
+ * @param {string} string The string to unescape.
+ * @returns {string} Returns the unescaped string.
+ */
+ function unescape(string) {
+ return string == null ? '' : String(string).replace(reEscapedHtml, unescapeHtmlChar);
+ }
+
+ /**
+ * Used by `unescape` to convert HTML entities to characters.
+ *
+ * @private
+ * @param {string} match The matched character to unescape.
+ * @returns {string} Returns the unescaped character.
+ */
+ function unescapeHtmlChar(match) {
+ return htmlUnescapes[match];
+ }
+
+ /**
+ * Creates a function that provides `value` to the wrapper function as its
+ * first argument. Additional arguments provided to the function are appended
+ * to those provided to the wrapper function. The wrapper is executed with
+ * the `this` binding of the created function.
+ *
+ * @private
+ * @param {*} value The value to wrap.
+ * @param {Function} wrapper The wrapper function.
+ * @returns {Function} Returns the new function.
+ */
+ function wrap(value, wrapper) {
+ return function() {
+ var args = [value];
+ push.apply(args, arguments);
+ return wrapper.apply(this, args);
+ };
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Installs the QUnit additions on the given `context` object.
+ *
+ * @memberOf exports
+ * @param {Object} context The context object.
+ */
+ function runInContext(context) {
+
+ /** Object references. */
+ var phantom = context.phantom,
+ define = context.define,
+ document = !phantom && context.document,
+ process = phantom || context.process,
+ amd = define && define.amd,
+ console = context.console,
+ java = !document && context.java,
+ print = context.print,
+ require = context.require;
+
+ /** Detects if running on Node.js. */
+ var isNode = isObject(process) && typeof process.on == 'function';
+
+ /** Detects if running in a PhantomJS web page. */
+ var isPhantomPage = typeof context.callPhantom == 'function';
+
+ /** Detects if QUnit Extras should log to the console. */
+ var isSilent = document && !isPhantomPage;
+
+ /** Used to indicate if running in Windows. */
+ var isWindows = isNode && process.platform == 'win32';
+
+ /** Used to indicate if ANSI escape codes are supported. */
+ var isAnsiSupported = (function() {
+ if (isNode && process.stdout && !process.stdout.isTTY) {
+ return false;
+ }
+ if (isWindows || getEnv('COLORTERM')) {
+ return true;
+ }
+ return /^(?:ansi|cygwin|linux|screen|xterm|vt100)$|color/i.test(getEnv('TERM'));
+ }());
+
+ /** Used to display the wait throbber. */
+ var throbberDelay = 500,
+ waitCount = -1;
+
+ /** Shorten `context.QUnit.QUnit` to `context.QUnit`. */
+ var QUnit = context.QUnit = context.QUnit.QUnit || context.QUnit;
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Schedules timer-based callbacks.
+ *
+ * @private
+ * @param {Function|string} fn The function to call.
+ * @param {number} delay The number of milliseconds to delay the `fn` call.
+ * @param {Array} args Arguments to invoke `fn` with.
+ * @param {boolean} repeated A flag to specify whether `fn` is called repeatedly.
+ * @returns {number} The ID of the timeout.
+ */
+ function schedule(fn, delay, args, repeated) {
+ // Rhino 1.7RC4 will error assigning `task` below.
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=775566.
+ var task = ids[++counter] = new JavaAdapter(java.util.TimerTask, {
+ 'run': function() {
+ fn.apply(context, args);
+ }
+ });
+ // Support non-functions.
+ if (typeof fn != 'function') {
+ fn = (function(code) {
+ code = String(code);
+ return function() { eval(code); };
+ }(fn));
+ }
+ // Used by `setInterval`.
+ if (repeated) {
+ timer.schedule(task, delay, delay);
+ }
+ // Used by `setTimeout`.
+ else {
+ timer.schedule(task, delay);
+ }
+ return counter;
+ }
+
+ /**
+ * Clears the delay set by `setInterval` or `setTimeout`.
+ *
+ * @memberOf context
+ * @param {number} id The ID of the timeout to be cleared.
+ */
+ function clearTimer(id) {
+ if (ids[id]) {
+ ids[id].cancel();
+ timer.purge();
+ delete ids[id];
+ }
+ }
+
+ /**
+ * Executes a code snippet or function repeatedly, with a delay between each call.
+ *
+ * @memberOf context
+ * @param {Function|string} fn The function to call or string to evaluate.
+ * @param {number} delay The number of milliseconds to delay each `fn` call.
+ * @param {...*} [args] Arguments to invoke `fn` with.
+ * @returns {number} The ID of the timeout.
+ */
+ function setInterval(fn, delay) {
+ return schedule(fn, delay, slice.call(arguments, 2), true);
+ }
+
+ /**
+ * Executes a code snippet or a function after specified delay.
+ *
+ * @memberOf context
+ * @param {Function|string} fn The function to call or string to evaluate.
+ * @param {number} delay The number of milliseconds to delay the `fn` call.
+ * @param {...*} [args] Arguments to invoke `fn` with.
+ * @returns {number} The ID of the timeout.
+ */
+ function setTimeout(fn, delay) {
+ return schedule(fn, delay, slice.call(arguments, 2));
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Gets the environment variable value by a given name.
+ *
+ * @private
+ * @param {string} name The name of the environment variable to get.
+ * @returns {*} Returns the environment variable value.
+ */
+ function getEnv(name) {
+ if (isNode) {
+ return process.env[name];
+ }
+ if (java) {
+ return java.lang.System.getenv(name);
+ }
+ if (!amd && typeof require == 'function') {
+ try {
+ return require('system').env[name];
+ } catch(e) {}
+ }
+ }
+
+ /**
+ * Adds text color to the terminal output of `string`.
+ *
+ * @private
+ * @param {string} colorName The name of the color to add.
+ * @param {string} string The string to add colors to.
+ * @returns {string} Returns the colored string.
+ */
+ function color(colorName, string) {
+ return isAnsiSupported
+ ? ('\x1B[' + ansiCodes[colorName] + 'm' + string + '\x1B[0m')
+ : string;
+ }
+
+ /**
+ * Writes an inline message to standard output.
+ *
+ * @private
+ * @param {string} [text=''] The text to log.
+ */
+ var logInline = (function() {
+ if (!isNode || isWindows) {
+ return function() {};
+ }
+ // Cleanup any inline logs when exited via `ctrl+c`.
+ process.on('SIGINT', function() {
+ logInline();
+ process.exit();
+ });
+
+ var prevLine = '';
+ return function(text) {
+ var blankLine = repeat(' ', prevLine.length);
+ if (text == null) {
+ text = '';
+ }
+ if (text.length > hr.length) {
+ text = text.slice(0, hr.length - 3) + '...';
+ }
+ prevLine = text;
+ process.stdout.write(text + blankLine.slice(text.length) + '\r');
+ }
+ }());
+
+ /**
+ * Writes the wait throbber to standard output.
+ *
+ * @private
+ */
+ function logThrobber() {
+ logInline('Please wait' + repeat('.', (++waitCount % 3) + 1));
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * The number of retries async tests have to succeed.
+ *
+ * @memberOf QUnit.config
+ * @type number
+ */
+ QUnit.config.asyncRetries = 0;
+
+ /**
+ * An object of excused tests and assertions.
+ *
+ * @memberOf QUnit.config
+ * @type Object
+ */
+ QUnit.config.excused = {};
+
+ /**
+ * An object used to hold "extras" information about the current running test.
+ *
+ * @memberOf QUnit.config
+ * @type Object
+ */
+ QUnit.config.extrasData = {
+
+ /**
+ * The data object for the active test module.
+ *
+ * @memberOf QUnit.config.extrasData
+ * @type Object
+ */
+ 'module': {},
+
+ /**
+ * The data object for Sauce Labs.
+ *
+ * @memberOf QUnit.config.extrasData
+ * @type Object
+ */
+ 'sauce': {
+
+ /**
+ * An array of failed test details.
+ *
+ * @memberOf QUnit.config.extrasData.sauce
+ * @type Array
+ */
+ 'tests': []
+ }
+ };
+
+ /**
+ * Converts an object into a string representation.
+ *
+ * @memberOf QUnit
+ * @type Function
+ * @param {Object} object The object to stringify.
+ * @returns {string} The result string.
+ */
+ QUnit.jsDump.parsers.object = (function() {
+ var func = QUnit.jsDump.parsers.object;
+ if (isSilent) {
+ return func;
+ }
+ return function(object) {
+ if (typeof object.rhinoException != 'object') {
+ return func(object);
+ }
+ return object.name +
+ ' { message: "' + object.message +
+ '", fileName: "' + object.fileName +
+ '", lineNumber: ' + object.lineNumber + ' }';
+ };
+ }());
+
+ /*------------------------------------------------------------------------*/
+
+ // Add a callback to be triggered after every assertion.
+ QUnit.log(function(details) {
+ QUnit.config.extrasData.module.logs.push(details);
+ });
+
+ // Add a callback to be triggered at the start of every test module.
+ QUnit.moduleStart(function(details) {
+ var module = QUnit.config.extrasData.module;
+ module.name = details.name;
+ module.logs = [];
+ module.printed = false;
+ });
+
+ // Wrap old API to intercept `expected` and `message`.
+ if (QUnit.push) {
+ QUnit.push = wrap(QUnit.push, function(push, result, actual, expected, message) {
+ push.call(this, result, actual, expected, message);
+
+ var asserts = QUnit.config.current.assertions,
+ item = asserts[asserts.length - 1];
+
+ item.expected = QUnit.jsDump.parse(expected);
+ item.text = message;
+ });
+ }
+ // Wrap old API to intercept `message`.
+ if (QUnit.pushFailure) {
+ QUnit.pushFailure = wrap(QUnit.pushFailure, function(pushFailure, message, source, actual) {
+ pushFailure.call(this, message, source, actual);
+
+ var asserts = QUnit.config.current.assertions,
+ item = asserts[asserts.length - 1];
+
+ item.expected = '';
+ item.text = message;
+ });
+ }
+ // Wrap to flag tests using `assert.async`.
+ if (QUnit.assert.async) {
+ QUnit.assert.async = wrap(QUnit.assert.async, function(async) {
+ this.test.usesAsync = true;
+ return async.call(this);
+ });
+ }
+ // Add a callback to be triggered at the start of every test.
+ QUnit.testStart(function(details) {
+ var config = QUnit.config,
+ test = config.current;
+
+ var excused = config.excused || {},
+ excusedTests = excused[details.module],
+ excusedAsserts = excusedTests && excusedTests[details.name];
+
+ // Allow async tests to retry.
+ if (!test.retries) {
+ test.retries = 0;
+ test.finish = wrap(test.finish, function(finish) {
+ if (this.async || this.usesAsync) {
+ var asserts = this.assertions,
+ config = QUnit.config,
+ index = -1,
+ length = asserts.length,
+ logs = config.extrasData.module.logs,
+ queue = config.queue;
+
+ while (++index < length) {
+ var assert = asserts[index];
+ if (!assert.result && this.retries < config.asyncRetries) {
+ var oldLength = queue.length;
+ logs.length -= asserts.length;
+ asserts.length = 0;
+
+ this.retries++;
+ this.queue();
+
+ unshift.apply(queue, queue.splice(oldLength, queue.length - oldLength));
+ return;
+ }
+ }
+ }
+ finish.call(this);
+ });
+ }
+ // Exit early when there is nothing to excuse.
+ if (!excusedAsserts) {
+ return;
+ }
+ // Excuse the entire test.
+ if (excusedAsserts === true) {
+ test.async = test.usesAsync = false;
+ test.callback = function() {};
+ test.expected = 0;
+ return;
+ }
+ // Wrap to intercept `expected` and `message`.
+ if (test.push) {
+ test.push = wrap(test.push, function(push, result, actual, expected, message) {
+ push.call(this, result, actual, expected, message);
+
+ var item = this.assertions[this.assertions.length - 1];
+ item.expected = QUnit.jsDump.parse(expected);
+ item.text = message;
+ });
+ }
+ // Wrap to intercept `message`.
+ if (test.pushFailure) {
+ test.pushFailure = wrap(test.pushFailure, function(pushFailure, message, source, actual) {
+ pushFailure.call(this, message, source, actual);
+
+ var item = this.assertions[this.assertions.length - 1];
+ item.expected = '';
+ item.text = message;
+ });
+ }
+ // Wrap to excuse specific assertions.
+ test.finish = wrap(test.finish, function(finish) {
+ var asserts = this.assertions,
+ config = QUnit.config,
+ expected = this.expected,
+ items = asserts.slice(),
+ length = items.length;
+
+ if (expected == null) {
+ if (config.requireExpects) {
+ expected = length;
+ items.push('Expected number of assertions to be defined, but expect() was not called.');
+ } else if (!length) {
+ expected = 1;
+ items.push('Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.');
+ }
+ } else if (expected != length) {
+ items.push('Expected ' + expected + ' assertions, but ' + length + ' were run');
+ }
+ var index = -1;
+ length = items.length;
+
+ while (++index < length) {
+ var assert = items[index],
+ isStr = typeof assert == 'string';
+
+ var assertMessage = isStr ? assert : assert.text || unescape(result(reMessage.exec(assert.message), 1)),
+ assertValue = isStr ? assert : assert.expected,
+ assertDied = result(reDied.exec(assertMessage), 0);
+
+ if ((assertMessage && contains(excusedAsserts, assertMessage)) ||
+ (assertDied && contains(excusedAsserts, assertDied)) ||
+ (assertValue && (
+ contains(excusedAsserts, assertValue) ||
+ contains(excusedAsserts, assertValue.replace(/\s+/g, ''))
+ ))) {
+ if (isStr) {
+ while (asserts.length < expected) {
+ asserts.push({ 'result': true });
+ }
+ asserts.length = expected;
+ }
+ else {
+ assert.result = true;
+ }
+ }
+ }
+ finish.call(this);
+ });
+ });
+
+ // Add a callback to be triggered after a test is completed.
+ QUnit.testDone(function(details) {
+ var config = QUnit.config,
+ data = config.extrasData,
+ failures = details.failed,
+ hidepassed = config.hidepassed,
+ module = data.module,
+ moduleLogs = module.logs,
+ sauceTests = data.sauce.tests;
+
+ if (hidepassed && !failures) {
+ return;
+ }
+ if (!isSilent) {
+ logInline();
+ if (!module.printed) {
+ module.printed = true;
+ console.log(hr);
+ console.log(color('bold', module.name));
+ console.log(hr);
+ }
+ console.log(' ' + (failures ? color('red', 'FAIL') : color('green', 'PASS')) + ' - ' + details.name);
+ }
+ if (!failures) {
+ return;
+ }
+ var index = -1,
+ length = moduleLogs.length;
+
+ while(++index < length) {
+ var entry = moduleLogs[index];
+ if (hidepassed && entry.result) {
+ continue;
+ }
+ var expected = entry.expected,
+ result = entry.result,
+ type = typeof expected != 'undefined' ? 'EQ' : 'OK';
+
+ var message = [
+ result ? color('green', 'PASS') : color('red', 'FAIL'),
+ type,
+ entry.message || 'ok'
+ ];
+
+ if (!result && type == 'EQ') {
+ message.push(color('magenta', 'Expected: ' + expected + ', Actual: ' + entry.actual));
+ }
+ if (!isSilent) {
+ console.log(' ' + message.join(' | '));
+ }
+ if (!entry.result) {
+ sauceTests.push(entry);
+ }
+ }
+ });
+
+ // Add a callback to be triggered when all testing has completed.
+ QUnit.done(function(details) {
+ var failures = details.failed,
+ statusColor = failures ? 'magenta' : 'green';
+
+ if (!isSilent) {
+ logInline();
+ console.log(hr);
+ console.log(color(statusColor, ' PASS: ' + details.passed + ' FAIL: ' + failures + ' TOTAL: ' + details.total));
+ console.log(color(statusColor, ' Finished in ' + details.runtime + ' milliseconds.'));
+ console.log(hr);
+ }
+ // Exit out of Node.js or PhantomJS.
+ try {
+ if (failures) {
+ process.exit(1);
+ } else {
+ process.exit(0);
+ }
+ } catch(e) {}
+
+ // Exit out of Narwhal, Rhino, or RingoJS.
+ try {
+ if (failures) {
+ java.lang.System.exit(1);
+ } else {
+ quit();
+ }
+ } catch(e) {}
+
+ // Assign results to `global_test_results` for Sauce Labs.
+ details.tests = QUnit.config.extrasData.sauce.tests;
+ context.global_test_results = details;
+ });
+
+ /*------------------------------------------------------------------------*/
+
+ // Replace poisoned `raises` method.
+ context.raises = QUnit.raises = QUnit['throws'] || QUnit.raises;
+
+ // Add CLI extras.
+ if (!document) {
+ // Timeout fallbacks based on the work of Andrea Giammarchi and Weston C.
+ // See https://github.com/WebReflection/wru/blob/master/src/rhinoTimers.js
+ // and http://stackoverflow.com/questions/2261705/how-to-run-a-javascript-function-asynchronously-without-using-settimeout.
+ try {
+ var counter = 0,
+ ids = {},
+ slice = Array.prototype.slice,
+ timer = new java.util.Timer;
+
+ (function() {
+ var getDescriptor = Object.getOwnPropertyDescriptor || function() {
+ return { 'writable': true };
+ };
+
+ var descriptor;
+ if ((!context.clearInterval || ((descriptor = getDescriptor(context, 'clearInterval')) && (descriptor.writable || descriptor.set))) &&
+ (!context.setInterval || ((descriptor = getDescriptor(context, 'setInterval')) && (descriptor.writable || descriptor.set)))) {
+ context.clearInterval = clearTimer;
+ context.setInterval = setInterval;
+ }
+ if ((!context.clearTimeout || ((descriptor = getDescriptor(context, 'clearTimeout')) && (descriptor.writable || descriptor.set))) &&
+ (!context.setTimeout || ((descriptor = getDescriptor(context, 'setTimeout')) && (descriptor.writable || descriptor.set)))) {
+ context.clearTimeout = clearTimer;
+ context.setTimeout = setTimeout;
+ }
+ }());
+ } catch(e) {}
+
+ // Expose QUnit API on `context`.
+ // Exclude `module` because some environments have it as a built-in object.
+ ('asyncTest deepEqual equal equals expect notDeepEqual notEqual notStrictEqual ' +
+ 'ok raises same start stop strictEqual test throws').replace(/\S+/g, function(methodName) {
+ context[methodName] = QUnit[methodName];
+ });
+
+ // Add `console.log` support to Narwhal, Rhino, and RingoJS.
+ if (!console) {
+ console = context.console = { 'log': function() {} };
+ }
+ // RingoJS removes ANSI escape codes in `console.log`, but not in `print`.
+ if (java && typeof print == 'function') {
+ console.log = print;
+ }
+ // Start log throbber.
+ if (!isSilent) {
+ context.setInterval(logThrobber, throbberDelay);
+ }
+ // Must call `QUnit.start` in the test file if not loaded in a browser.
+ QUnit.config.autostart = false;
+ QUnit.init();
+ }
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ // Export QUnit Extras.
+ if (freeExports) {
+ freeExports.runInContext = runInContext;
+ } else {
+ runInContext(root);
+ }
+}.call(this));
\ No newline at end of file
diff --git a/vendor/underscore/test/vendor/qunit.css b/vendor/underscore/test/vendor/qunit.css
index 7ba3f9a30..f1dcd4e1c 100644
--- a/vendor/underscore/test/vendor/qunit.css
+++ b/vendor/underscore/test/vendor/qunit.css
@@ -1,11 +1,12 @@
-/**
- * QUnit v1.12.0 - A JavaScript Unit Testing Framework
+/*!
+ * QUnit 1.18.0
+ * http://qunitjs.com/
*
- * http://qunitjs.com
- *
- * Copyright 2012 jQuery Foundation and other contributors
- * Released under the MIT license.
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license
* http://jquery.org/license
+ *
+ * Date: 2015-04-03T10:23Z
*/
/** Font Family and Sizes */
@@ -31,32 +32,29 @@
#qunit-header {
padding: 0.5em 0 0.5em 1em;
- color: #8699a4;
- background-color: #0d3349;
+ color: #8699A4;
+ background-color: #0D3349;
font-size: 1.5em;
line-height: 1em;
- font-weight: normal;
+ font-weight: 400;
border-radius: 5px 5px 0 0;
- -moz-border-radius: 5px 5px 0 0;
- -webkit-border-top-right-radius: 5px;
- -webkit-border-top-left-radius: 5px;
}
#qunit-header a {
text-decoration: none;
- color: #c2ccd1;
+ color: #C2CCD1;
}
#qunit-header a:hover,
#qunit-header a:focus {
- color: #fff;
+ color: #FFF;
}
#qunit-testrunner-toolbar label {
display: inline-block;
- padding: 0 .5em 0 .1em;
+ padding: 0 0.5em 0 0.1em;
}
#qunit-banner {
@@ -64,21 +62,33 @@
}
#qunit-testrunner-toolbar {
- padding: 0.5em 0 0.5em 2em;
+ padding: 0.5em 1em 0.5em 1em;
color: #5E740B;
- background-color: #eee;
+ background-color: #EEE;
overflow: hidden;
}
#qunit-userAgent {
- padding: 0.5em 0 0.5em 2.5em;
- background-color: #2b81af;
- color: #fff;
+ padding: 0.5em 1em 0.5em 1em;
+ background-color: #2B81AF;
+ color: #FFF;
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
}
#qunit-modulefilter-container {
float: right;
+ padding: 0.2em;
+}
+
+.qunit-url-config {
+ display: inline-block;
+ padding: 0.1em;
+}
+
+.qunit-filter {
+ display: block;
+ float: right;
+ margin-left: 1em;
}
/** Tests: Pass/Fail */
@@ -88,24 +98,51 @@
}
#qunit-tests li {
- padding: 0.4em 0.5em 0.4em 2.5em;
- border-bottom: 1px solid #fff;
+ padding: 0.4em 1em 0.4em 1em;
+ border-bottom: 1px solid #FFF;
list-style-position: inside;
}
-#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
+#qunit-tests > li {
display: none;
}
+#qunit-tests li.running,
+#qunit-tests li.pass,
+#qunit-tests li.fail,
+#qunit-tests li.skipped {
+ display: list-item;
+}
+
+#qunit-tests.hidepass li.running,
+#qunit-tests.hidepass li.pass {
+ visibility: hidden;
+ position: absolute;
+ width: 0px;
+ height: 0px;
+ padding: 0;
+ border: 0;
+ margin: 0;
+}
+
#qunit-tests li strong {
cursor: pointer;
}
+#qunit-tests li.skipped strong {
+ cursor: default;
+}
+
#qunit-tests li a {
padding: 0.5em;
- color: #c2ccd1;
+ color: #C2CCD1;
text-decoration: none;
}
+
+#qunit-tests li p a {
+ padding: 0.25em;
+ color: #6B6464;
+}
#qunit-tests li a:hover,
#qunit-tests li a:focus {
color: #000;
@@ -120,11 +157,9 @@
margin-top: 0.5em;
padding: 0.5em;
- background-color: #fff;
+ background-color: #FFF;
border-radius: 5px;
- -moz-border-radius: 5px;
- -webkit-border-radius: 5px;
}
.qunit-collapsed {
@@ -133,13 +168,13 @@
#qunit-tests table {
border-collapse: collapse;
- margin-top: .2em;
+ margin-top: 0.2em;
}
#qunit-tests th {
text-align: right;
vertical-align: top;
- padding: 0 .5em 0 0;
+ padding: 0 0.5em 0 0;
}
#qunit-tests td {
@@ -153,26 +188,26 @@
}
#qunit-tests del {
- background-color: #e0f2be;
- color: #374e0c;
+ background-color: #E0F2BE;
+ color: #374E0C;
text-decoration: none;
}
#qunit-tests ins {
- background-color: #ffcaca;
+ background-color: #FFCACA;
color: #500;
text-decoration: none;
}
/*** Test Counts */
-#qunit-tests b.counts { color: black; }
+#qunit-tests b.counts { color: #000; }
#qunit-tests b.passed { color: #5E740B; }
#qunit-tests b.failed { color: #710909; }
#qunit-tests li li {
padding: 5px;
- background-color: #fff;
+ background-color: #FFF;
border-bottom: none;
list-style-position: inside;
}
@@ -180,8 +215,8 @@
/*** Passing Styles */
#qunit-tests li li.pass {
- color: #3c510c;
- background-color: #fff;
+ color: #3C510C;
+ background-color: #FFF;
border-left: 10px solid #C6E746;
}
@@ -189,7 +224,7 @@
#qunit-tests .pass .test-name { color: #366097; }
#qunit-tests .pass .test-actual,
-#qunit-tests .pass .test-expected { color: #999999; }
+#qunit-tests .pass .test-expected { color: #999; }
#qunit-banner.qunit-pass { background-color: #C6E746; }
@@ -197,40 +232,52 @@
#qunit-tests li li.fail {
color: #710909;
- background-color: #fff;
+ background-color: #FFF;
border-left: 10px solid #EE5757;
white-space: pre;
}
#qunit-tests > li:last-child {
border-radius: 0 0 5px 5px;
- -moz-border-radius: 0 0 5px 5px;
- -webkit-border-bottom-right-radius: 5px;
- -webkit-border-bottom-left-radius: 5px;
}
-#qunit-tests .fail { color: #000000; background-color: #EE5757; }
+#qunit-tests .fail { color: #000; background-color: #EE5757; }
#qunit-tests .fail .test-name,
-#qunit-tests .fail .module-name { color: #000000; }
+#qunit-tests .fail .module-name { color: #000; }
#qunit-tests .fail .test-actual { color: #EE5757; }
-#qunit-tests .fail .test-expected { color: green; }
+#qunit-tests .fail .test-expected { color: #008000; }
#qunit-banner.qunit-fail { background-color: #EE5757; }
+/*** Skipped tests */
+
+#qunit-tests .skipped {
+ background-color: #EBECE9;
+}
+
+#qunit-tests .qunit-skipped-label {
+ background-color: #F4FF77;
+ display: inline-block;
+ font-style: normal;
+ color: #366097;
+ line-height: 1.8em;
+ padding: 0 0.5em;
+ margin: -0.4em 0.4em -0.4em 0;
+}
/** Result */
#qunit-testresult {
- padding: 0.5em 0.5em 0.5em 2.5em;
+ padding: 0.5em 1em 0.5em 1em;
- color: #2b81af;
+ color: #2B81AF;
background-color: #D2E0E6;
- border-bottom: 1px solid white;
+ border-bottom: 1px solid #FFF;
}
#qunit-testresult .module-name {
- font-weight: bold;
+ font-weight: 700;
}
/** Fixture */
diff --git a/vendor/underscore/test/vendor/qunit.js b/vendor/underscore/test/vendor/qunit.js
index 84c73907d..f3542ca9d 100644
--- a/vendor/underscore/test/vendor/qunit.js
+++ b/vendor/underscore/test/vendor/qunit.js
@@ -1,35 +1,42 @@
-/**
- * QUnit v1.12.0 - A JavaScript Unit Testing Framework
+/*!
+ * QUnit 1.18.0
+ * http://qunitjs.com/
*
- * http://qunitjs.com
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
*
- * Copyright 2013 jQuery Foundation and other contributors
- * Released under the MIT license.
- * https://jquery.org/license/
+ * Date: 2015-04-03T10:23Z
*/
(function( window ) {
var QUnit,
- assert,
config,
onErrorFnPrev,
- testId = 0,
- fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""),
+ loggingCallbacks = {},
+ fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" ),
toString = Object.prototype.toString,
hasOwn = Object.prototype.hasOwnProperty,
// Keep a local reference to Date (GH-283)
Date = window.Date,
+ now = Date.now || function() {
+ return new Date().getTime();
+ },
+ globalStartCalled = false,
+ runStarted = false,
setTimeout = window.setTimeout,
+ clearTimeout = window.clearTimeout,
defined = {
- setTimeout: typeof window.setTimeout !== "undefined",
+ document: window.document !== undefined,
+ setTimeout: window.setTimeout !== undefined,
sessionStorage: (function() {
var x = "qunit-test-string";
try {
sessionStorage.setItem( x, x );
sessionStorage.removeItem( x );
return true;
- } catch( e ) {
+ } catch ( e ) {
return false;
}
}())
@@ -71,627 +78,18 @@ var QUnit,
* @return {Object} New object with only the own properties (recursively).
*/
objectValues = function( obj ) {
- // Grunt 0.3.x uses an older version of jshint that still has jshint/jshint#392.
- /*jshint newcap: false */
var key, val,
vals = QUnit.is( "array", obj ) ? [] : {};
for ( key in obj ) {
if ( hasOwn.call( obj, key ) ) {
- val = obj[key];
- vals[key] = val === Object(val) ? objectValues(val) : val;
+ val = obj[ key ];
+ vals[ key ] = val === Object( val ) ? objectValues( val ) : val;
}
}
return vals;
};
-function Test( settings ) {
- extend( this, settings );
- this.assertions = [];
- this.testNumber = ++Test.count;
-}
-
-Test.count = 0;
-
-Test.prototype = {
- init: function() {
- var a, b, li,
- tests = id( "qunit-tests" );
-
- if ( tests ) {
- b = document.createElement( "strong" );
- b.innerHTML = this.nameHtml;
-
- // `a` initialized at top of scope
- a = document.createElement( "a" );
- a.innerHTML = "Rerun";
- a.href = QUnit.url({ testNumber: this.testNumber });
-
- li = document.createElement( "li" );
- li.appendChild( b );
- li.appendChild( a );
- li.className = "running";
- li.id = this.id = "qunit-test-output" + testId++;
-
- tests.appendChild( li );
- }
- },
- setup: function() {
- if (
- // Emit moduleStart when we're switching from one module to another
- this.module !== config.previousModule ||
- // They could be equal (both undefined) but if the previousModule property doesn't
- // yet exist it means this is the first test in a suite that isn't wrapped in a
- // module, in which case we'll just emit a moduleStart event for 'undefined'.
- // Without this, reporters can get testStart before moduleStart which is a problem.
- !hasOwn.call( config, "previousModule" )
- ) {
- if ( hasOwn.call( config, "previousModule" ) ) {
- runLoggingCallbacks( "moduleDone", QUnit, {
- name: config.previousModule,
- failed: config.moduleStats.bad,
- passed: config.moduleStats.all - config.moduleStats.bad,
- total: config.moduleStats.all
- });
- }
- config.previousModule = this.module;
- config.moduleStats = { all: 0, bad: 0 };
- runLoggingCallbacks( "moduleStart", QUnit, {
- name: this.module
- });
- }
-
- config.current = this;
-
- this.testEnvironment = extend({
- setup: function() {},
- teardown: function() {}
- }, this.moduleTestEnvironment );
-
- this.started = +new Date();
- runLoggingCallbacks( "testStart", QUnit, {
- name: this.testName,
- module: this.module
- });
-
- /*jshint camelcase:false */
-
-
- /**
- * Expose the current test environment.
- *
- * @deprecated since 1.12.0: Use QUnit.config.current.testEnvironment instead.
- */
- QUnit.current_testEnvironment = this.testEnvironment;
-
- /*jshint camelcase:true */
-
- if ( !config.pollution ) {
- saveGlobal();
- }
- if ( config.notrycatch ) {
- this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert );
- return;
- }
- try {
- this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert );
- } catch( e ) {
- QUnit.pushFailure( "Setup failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) );
- }
- },
- run: function() {
- config.current = this;
-
- var running = id( "qunit-testresult" );
-
- if ( running ) {
- running.innerHTML = "Running: " + this.nameHtml;
- }
-
- if ( this.async ) {
- QUnit.stop();
- }
-
- this.callbackStarted = +new Date();
-
- if ( config.notrycatch ) {
- this.callback.call( this.testEnvironment, QUnit.assert );
- this.callbackRuntime = +new Date() - this.callbackStarted;
- return;
- }
-
- try {
- this.callback.call( this.testEnvironment, QUnit.assert );
- this.callbackRuntime = +new Date() - this.callbackStarted;
- } catch( e ) {
- this.callbackRuntime = +new Date() - this.callbackStarted;
-
- QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
- // else next test will carry the responsibility
- saveGlobal();
-
- // Restart the tests if they're blocking
- if ( config.blocking ) {
- QUnit.start();
- }
- }
- },
- teardown: function() {
- config.current = this;
- if ( config.notrycatch ) {
- if ( typeof this.callbackRuntime === "undefined" ) {
- this.callbackRuntime = +new Date() - this.callbackStarted;
- }
- this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert );
- return;
- } else {
- try {
- this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert );
- } catch( e ) {
- QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) );
- }
- }
- checkPollution();
- },
- finish: function() {
- config.current = this;
- if ( config.requireExpects && this.expected === null ) {
- QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack );
- } else if ( this.expected !== null && this.expected !== this.assertions.length ) {
- QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack );
- } else if ( this.expected === null && !this.assertions.length ) {
- QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack );
- }
-
- var i, assertion, a, b, time, li, ol,
- test = this,
- good = 0,
- bad = 0,
- tests = id( "qunit-tests" );
-
- this.runtime = +new Date() - this.started;
- config.stats.all += this.assertions.length;
- config.moduleStats.all += this.assertions.length;
-
- if ( tests ) {
- ol = document.createElement( "ol" );
- ol.className = "qunit-assert-list";
-
- for ( i = 0; i < this.assertions.length; i++ ) {
- assertion = this.assertions[i];
-
- li = document.createElement( "li" );
- li.className = assertion.result ? "pass" : "fail";
- li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" );
- ol.appendChild( li );
-
- if ( assertion.result ) {
- good++;
- } else {
- bad++;
- config.stats.bad++;
- config.moduleStats.bad++;
- }
- }
-
- // store result when possible
- if ( QUnit.config.reorder && defined.sessionStorage ) {
- if ( bad ) {
- sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad );
- } else {
- sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName );
- }
- }
-
- if ( bad === 0 ) {
- addClass( ol, "qunit-collapsed" );
- }
-
- // `b` initialized at top of scope
- b = document.createElement( "strong" );
- b.innerHTML = this.nameHtml + " (" + bad + " , " + good + " , " + this.assertions.length + ") ";
-
- addEvent(b, "click", function() {
- var next = b.parentNode.lastChild,
- collapsed = hasClass( next, "qunit-collapsed" );
- ( collapsed ? removeClass : addClass )( next, "qunit-collapsed" );
- });
-
- addEvent(b, "dblclick", function( e ) {
- var target = e && e.target ? e.target : window.event.srcElement;
- if ( target.nodeName.toLowerCase() === "span" || target.nodeName.toLowerCase() === "b" ) {
- target = target.parentNode;
- }
- if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
- window.location = QUnit.url({ testNumber: test.testNumber });
- }
- });
-
- // `time` initialized at top of scope
- time = document.createElement( "span" );
- time.className = "runtime";
- time.innerHTML = this.runtime + " ms";
-
- // `li` initialized at top of scope
- li = id( this.id );
- li.className = bad ? "fail" : "pass";
- li.removeChild( li.firstChild );
- a = li.firstChild;
- li.appendChild( b );
- li.appendChild( a );
- li.appendChild( time );
- li.appendChild( ol );
-
- } else {
- for ( i = 0; i < this.assertions.length; i++ ) {
- if ( !this.assertions[i].result ) {
- bad++;
- config.stats.bad++;
- config.moduleStats.bad++;
- }
- }
- }
-
- runLoggingCallbacks( "testDone", QUnit, {
- name: this.testName,
- module: this.module,
- failed: bad,
- passed: this.assertions.length - bad,
- total: this.assertions.length,
- duration: this.runtime
- });
-
- QUnit.reset();
-
- config.current = undefined;
- },
-
- queue: function() {
- var bad,
- test = this;
-
- synchronize(function() {
- test.init();
- });
- function run() {
- // each of these can by async
- synchronize(function() {
- test.setup();
- });
- synchronize(function() {
- test.run();
- });
- synchronize(function() {
- test.teardown();
- });
- synchronize(function() {
- test.finish();
- });
- }
-
- // `bad` initialized at top of scope
- // defer when previous test run passed, if storage is available
- bad = QUnit.config.reorder && defined.sessionStorage &&
- +sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName );
-
- if ( bad ) {
- run();
- } else {
- synchronize( run, true );
- }
- }
-};
-
-// Root QUnit object.
-// `QUnit` initialized at top of scope
-QUnit = {
-
- // call on start of module test to prepend name to all tests
- module: function( name, testEnvironment ) {
- config.currentModule = name;
- config.currentModuleTestEnvironment = testEnvironment;
- config.modules[name] = true;
- },
-
- asyncTest: function( testName, expected, callback ) {
- if ( arguments.length === 2 ) {
- callback = expected;
- expected = null;
- }
-
- QUnit.test( testName, expected, callback, true );
- },
-
- test: function( testName, expected, callback, async ) {
- var test,
- nameHtml = "" + escapeText( testName ) + " ";
-
- if ( arguments.length === 2 ) {
- callback = expected;
- expected = null;
- }
-
- if ( config.currentModule ) {
- nameHtml = "" + escapeText( config.currentModule ) + " : " + nameHtml;
- }
-
- test = new Test({
- nameHtml: nameHtml,
- testName: testName,
- expected: expected,
- async: async,
- callback: callback,
- module: config.currentModule,
- moduleTestEnvironment: config.currentModuleTestEnvironment,
- stack: sourceFromStacktrace( 2 )
- });
-
- if ( !validTest( test ) ) {
- return;
- }
-
- test.queue();
- },
-
- // Specify the number of expected assertions to guarantee that failed test (no assertions are run at all) don't slip through.
- expect: function( asserts ) {
- if (arguments.length === 1) {
- config.current.expected = asserts;
- } else {
- return config.current.expected;
- }
- },
-
- start: function( count ) {
- // QUnit hasn't been initialized yet.
- // Note: RequireJS (et al) may delay onLoad
- if ( config.semaphore === undefined ) {
- QUnit.begin(function() {
- // This is triggered at the top of QUnit.load, push start() to the event loop, to allow QUnit.load to finish first
- setTimeout(function() {
- QUnit.start( count );
- });
- });
- return;
- }
-
- config.semaphore -= count || 1;
- // don't start until equal number of stop-calls
- if ( config.semaphore > 0 ) {
- return;
- }
- // ignore if start is called more often then stop
- if ( config.semaphore < 0 ) {
- config.semaphore = 0;
- QUnit.pushFailure( "Called start() while already started (QUnit.config.semaphore was 0 already)", null, sourceFromStacktrace(2) );
- return;
- }
- // A slight delay, to avoid any current callbacks
- if ( defined.setTimeout ) {
- setTimeout(function() {
- if ( config.semaphore > 0 ) {
- return;
- }
- if ( config.timeout ) {
- clearTimeout( config.timeout );
- }
-
- config.blocking = false;
- process( true );
- }, 13);
- } else {
- config.blocking = false;
- process( true );
- }
- },
-
- stop: function( count ) {
- config.semaphore += count || 1;
- config.blocking = true;
-
- if ( config.testTimeout && defined.setTimeout ) {
- clearTimeout( config.timeout );
- config.timeout = setTimeout(function() {
- QUnit.ok( false, "Test timed out" );
- config.semaphore = 1;
- QUnit.start();
- }, config.testTimeout );
- }
- }
-};
-
-// `assert` initialized at top of scope
-// Assert helpers
-// All of these must either call QUnit.push() or manually do:
-// - runLoggingCallbacks( "log", .. );
-// - config.current.assertions.push({ .. });
-// We attach it to the QUnit object *after* we expose the public API,
-// otherwise `assert` will become a global variable in browsers (#341).
-assert = {
- /**
- * Asserts rough true-ish result.
- * @name ok
- * @function
- * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
- */
- ok: function( result, msg ) {
- if ( !config.current ) {
- throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) );
- }
- result = !!result;
- msg = msg || (result ? "okay" : "failed" );
-
- var source,
- details = {
- module: config.current.module,
- name: config.current.testName,
- result: result,
- message: msg
- };
-
- msg = "" + escapeText( msg ) + " ";
-
- if ( !result ) {
- source = sourceFromStacktrace( 2 );
- if ( source ) {
- details.source = source;
- msg += "Source: " + escapeText( source ) + "
";
- }
- }
- runLoggingCallbacks( "log", QUnit, details );
- config.current.assertions.push({
- result: result,
- message: msg
- });
- },
-
- /**
- * Assert that the first two arguments are equal, with an optional message.
- * Prints out both actual and expected values.
- * @name equal
- * @function
- * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" );
- */
- equal: function( actual, expected, message ) {
- /*jshint eqeqeq:false */
- QUnit.push( expected == actual, actual, expected, message );
- },
-
- /**
- * @name notEqual
- * @function
- */
- notEqual: function( actual, expected, message ) {
- /*jshint eqeqeq:false */
- QUnit.push( expected != actual, actual, expected, message );
- },
-
- /**
- * @name propEqual
- * @function
- */
- propEqual: function( actual, expected, message ) {
- actual = objectValues(actual);
- expected = objectValues(expected);
- QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
- },
-
- /**
- * @name notPropEqual
- * @function
- */
- notPropEqual: function( actual, expected, message ) {
- actual = objectValues(actual);
- expected = objectValues(expected);
- QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message );
- },
-
- /**
- * @name deepEqual
- * @function
- */
- deepEqual: function( actual, expected, message ) {
- QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
- },
-
- /**
- * @name notDeepEqual
- * @function
- */
- notDeepEqual: function( actual, expected, message ) {
- QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message );
- },
-
- /**
- * @name strictEqual
- * @function
- */
- strictEqual: function( actual, expected, message ) {
- QUnit.push( expected === actual, actual, expected, message );
- },
-
- /**
- * @name notStrictEqual
- * @function
- */
- notStrictEqual: function( actual, expected, message ) {
- QUnit.push( expected !== actual, actual, expected, message );
- },
-
- "throws": function( block, expected, message ) {
- var actual,
- expectedOutput = expected,
- ok = false;
-
- // 'expected' is optional
- if ( typeof expected === "string" ) {
- message = expected;
- expected = null;
- }
-
- config.current.ignoreGlobalErrors = true;
- try {
- block.call( config.current.testEnvironment );
- } catch (e) {
- actual = e;
- }
- config.current.ignoreGlobalErrors = false;
-
- if ( actual ) {
- // we don't want to validate thrown error
- if ( !expected ) {
- ok = true;
- expectedOutput = null;
- // expected is a regexp
- } else if ( QUnit.objectType( expected ) === "regexp" ) {
- ok = expected.test( errorString( actual ) );
- // expected is a constructor
- } else if ( actual instanceof expected ) {
- ok = true;
- // expected is a validation function which returns true is validation passed
- } else if ( expected.call( {}, actual ) === true ) {
- expectedOutput = null;
- ok = true;
- }
-
- QUnit.push( ok, actual, expectedOutput, message );
- } else {
- QUnit.pushFailure( message, null, "No exception was thrown." );
- }
- }
-};
-
-/**
- * @deprecated since 1.8.0
- * Kept assertion helpers in root for backwards compatibility.
- */
-extend( QUnit, assert );
-
-/**
- * @deprecated since 1.9.0
- * Kept root "raises()" for backwards compatibility.
- * (Note that we don't introduce assert.raises).
- */
-QUnit.raises = assert[ "throws" ];
-
-/**
- * @deprecated since 1.0.0, replaced with error pushes since 1.3.0
- * Kept to avoid TypeErrors for undefined methods.
- */
-QUnit.equals = function() {
- QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" );
-};
-QUnit.same = function() {
- QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" );
-};
-
-// We want access to the constructor's prototype
-(function() {
- function F() {}
- F.prototype = QUnit;
- QUnit = new F();
- // Make F QUnit's constructor so that we can add to the prototype later
- QUnit.constructor = F;
-}());
+QUnit = {};
/**
* Config object: Maintain internal state
@@ -705,10 +103,6 @@ config = {
// block until document ready
blocking: true,
- // when enabled, show only failing tests
- // gets persisted through sessionStorage and can be changed in UI via checkbox
- hidepassed: false,
-
// by default, run previously failed tests first
// very useful in combination with "Hide passed tests" checked
reorder: true,
@@ -716,165 +110,233 @@ config = {
// by default, modify document.title when suite is done
altertitle: true,
+ // by default, scroll to top of the page when suite is done
+ scrolltop: true,
+
// when enabled, all tests must call expect()
requireExpects: false,
+ // depth up-to which object will be dumped
+ maxDepth: 5,
+
// add checkboxes that are persisted in the query-string
// when enabled, the id is set to `true` as a `QUnit.config` property
urlConfig: [
+ {
+ id: "hidepassed",
+ label: "Hide passed tests",
+ tooltip: "Only show tests and assertions that fail. Stored as query-strings."
+ },
{
id: "noglobals",
label: "Check for Globals",
- tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings."
+ tooltip: "Enabling this will test if any test introduces new properties on the " +
+ "`window` object. Stored as query-strings."
},
{
id: "notrycatch",
label: "No try-catch",
- tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings."
+ tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " +
+ "exceptions in IE reasonable. Stored as query-strings."
}
],
// Set of all modules.
- modules: {},
+ modules: [],
- // logging callback queues
- begin: [],
- done: [],
- log: [],
- testStart: [],
- testDone: [],
- moduleStart: [],
- moduleDone: []
+ // The first unnamed module
+ currentModule: {
+ name: "",
+ tests: []
+ },
+
+ callbacks: {}
};
-// Export global variables, unless an 'exports' object exists,
-// in that case we assume we're in CommonJS (dealt with on the bottom of the script)
-if ( typeof exports === "undefined" ) {
- extend( window, QUnit.constructor.prototype );
-
- // Expose QUnit object
- window.QUnit = QUnit;
-}
+// Push a loose unnamed module to the modules collection
+config.modules.push( config.currentModule );
// Initialize more QUnit.config and QUnit.urlParams
(function() {
- var i,
+ var i, current,
location = window.location || { search: "", protocol: "file:" },
params = location.search.slice( 1 ).split( "&" ),
length = params.length,
- urlParams = {},
- current;
+ urlParams = {};
if ( params[ 0 ] ) {
for ( i = 0; i < length; i++ ) {
current = params[ i ].split( "=" );
current[ 0 ] = decodeURIComponent( current[ 0 ] );
+
// allow just a key to turn on a flag, e.g., test.html?noglobals
current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
- urlParams[ current[ 0 ] ] = current[ 1 ];
+ if ( urlParams[ current[ 0 ] ] ) {
+ urlParams[ current[ 0 ] ] = [].concat( urlParams[ current[ 0 ] ], current[ 1 ] );
+ } else {
+ urlParams[ current[ 0 ] ] = current[ 1 ];
+ }
}
}
+ if ( urlParams.filter === true ) {
+ delete urlParams.filter;
+ }
+
QUnit.urlParams = urlParams;
// String search anywhere in moduleName+testName
config.filter = urlParams.filter;
- // Exact match of the module name
- config.module = urlParams.module;
+ if ( urlParams.maxDepth ) {
+ config.maxDepth = parseInt( urlParams.maxDepth, 10 ) === -1 ?
+ Number.POSITIVE_INFINITY :
+ urlParams.maxDepth;
+ }
- config.testNumber = parseInt( urlParams.testNumber, 10 ) || null;
+ config.testId = [];
+ if ( urlParams.testId ) {
+
+ // Ensure that urlParams.testId is an array
+ urlParams.testId = decodeURIComponent( urlParams.testId ).split( "," );
+ for ( i = 0; i < urlParams.testId.length; i++ ) {
+ config.testId.push( urlParams.testId[ i ] );
+ }
+ }
// Figure out if we're running the tests from a server or not
QUnit.isLocal = location.protocol === "file:";
+
+ // Expose the current QUnit version
+ QUnit.version = "1.18.0";
}());
-// Extend QUnit object,
-// these after set here because they should not be exposed as global functions
+// Root QUnit object.
+// `QUnit` initialized at top of scope
extend( QUnit, {
- assert: assert,
- config: config,
+ // call on start of module test to prepend name to all tests
+ module: function( name, testEnvironment ) {
+ var currentModule = {
+ name: name,
+ testEnvironment: testEnvironment,
+ tests: []
+ };
- // Initialize the configuration options
- init: function() {
- extend( config, {
- stats: { all: 0, bad: 0 },
- moduleStats: { all: 0, bad: 0 },
- started: +new Date(),
- updateRate: 1000,
- blocking: false,
- autostart: true,
- autorun: false,
- filter: "",
- queue: [],
- semaphore: 1
+ // DEPRECATED: handles setup/teardown functions,
+ // beforeEach and afterEach should be used instead
+ if ( testEnvironment && testEnvironment.setup ) {
+ testEnvironment.beforeEach = testEnvironment.setup;
+ delete testEnvironment.setup;
+ }
+ if ( testEnvironment && testEnvironment.teardown ) {
+ testEnvironment.afterEach = testEnvironment.teardown;
+ delete testEnvironment.teardown;
+ }
+
+ config.modules.push( currentModule );
+ config.currentModule = currentModule;
+ },
+
+ // DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0.
+ asyncTest: function( testName, expected, callback ) {
+ if ( arguments.length === 2 ) {
+ callback = expected;
+ expected = null;
+ }
+
+ QUnit.test( testName, expected, callback, true );
+ },
+
+ test: function( testName, expected, callback, async ) {
+ var test;
+
+ if ( arguments.length === 2 ) {
+ callback = expected;
+ expected = null;
+ }
+
+ test = new Test({
+ testName: testName,
+ expected: expected,
+ async: async,
+ callback: callback
});
- var tests, banner, result,
- qunit = id( "qunit" );
-
- if ( qunit ) {
- qunit.innerHTML =
- "" +
- " " +
- "
" +
- " " +
- " ";
- }
-
- tests = id( "qunit-tests" );
- banner = id( "qunit-banner" );
- result = id( "qunit-testresult" );
-
- if ( tests ) {
- tests.innerHTML = "";
- }
-
- if ( banner ) {
- banner.className = "";
- }
-
- if ( result ) {
- result.parentNode.removeChild( result );
- }
-
- if ( tests ) {
- result = document.createElement( "p" );
- result.id = "qunit-testresult";
- result.className = "result";
- tests.parentNode.insertBefore( result, tests );
- result.innerHTML = "Running... ";
- }
+ test.queue();
},
- // Resets the test setup. Useful for tests that modify the DOM.
- /*
- DEPRECATED: Use multiple tests instead of resetting inside a test.
- Use testStart or testDone for custom cleanup.
- This method will throw an error in 2.0, and will be removed in 2.1
- */
- reset: function() {
- var fixture = id( "qunit-fixture" );
- if ( fixture ) {
- fixture.innerHTML = config.fixture;
- }
+ skip: function( testName ) {
+ var test = new Test({
+ testName: testName,
+ skip: true
+ });
+
+ test.queue();
},
- // Trigger an event on an element.
- // @example triggerEvent( document.body, "click" );
- triggerEvent: function( elem, type, event ) {
- if ( document.createEvent ) {
- event = document.createEvent( "MouseEvents" );
- event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
- 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+ // DEPRECATED: The functionality of QUnit.start() will be altered in QUnit 2.0.
+ // In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostart` blocking behavior.
+ start: function( count ) {
+ var globalStartAlreadyCalled = globalStartCalled;
- elem.dispatchEvent( event );
- } else if ( elem.fireEvent ) {
- elem.fireEvent( "on" + type );
+ if ( !config.current ) {
+ globalStartCalled = true;
+
+ if ( runStarted ) {
+ throw new Error( "Called start() outside of a test context while already started" );
+ } else if ( globalStartAlreadyCalled || count > 1 ) {
+ throw new Error( "Called start() outside of a test context too many times" );
+ } else if ( config.autostart ) {
+ throw new Error( "Called start() outside of a test context when " +
+ "QUnit.config.autostart was true" );
+ } else if ( !config.pageLoaded ) {
+
+ // The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it
+ config.autostart = true;
+ return;
+ }
+ } else {
+
+ // If a test is running, adjust its semaphore
+ config.current.semaphore -= count || 1;
+
+ // Don't start until equal number of stop-calls
+ if ( config.current.semaphore > 0 ) {
+ return;
+ }
+
+ // throw an Error if start is called more often than stop
+ if ( config.current.semaphore < 0 ) {
+ config.current.semaphore = 0;
+
+ QUnit.pushFailure(
+ "Called start() while already started (test's semaphore was 0 already)",
+ sourceFromStacktrace( 2 )
+ );
+ return;
+ }
}
+
+ resumeProcessing();
},
+ // DEPRECATED: QUnit.stop() will be removed in QUnit 2.0.
+ stop: function( count ) {
+
+ // If there isn't a test running, don't allow QUnit.stop() to be called
+ if ( !config.current ) {
+ throw new Error( "Called stop() outside of a test context" );
+ }
+
+ // If a test is running, adjust its semaphore
+ config.current.semaphore += count || 1;
+
+ pauseProcessing();
+ },
+
+ config: config,
+
// Safe object type checking
is: function( type, obj ) {
return QUnit.objectType( obj ) === type;
@@ -882,19 +344,20 @@ extend( QUnit, {
objectType: function( obj ) {
if ( typeof obj === "undefined" ) {
- return "undefined";
- // consider: typeof null === object
- }
- if ( obj === null ) {
- return "null";
+ return "undefined";
}
- var match = toString.call( obj ).match(/^\[object\s(.*)\]$/),
- type = match && match[1] || "";
+ // Consider: typeof null === object
+ if ( obj === null ) {
+ return "null";
+ }
+
+ var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ),
+ type = match && match[ 1 ] || "";
switch ( type ) {
case "Number":
- if ( isNaN(obj) ) {
+ if ( isNaN( obj ) ) {
return "nan";
}
return "number";
@@ -912,308 +375,65 @@ extend( QUnit, {
return undefined;
},
- push: function( result, actual, expected, message ) {
- if ( !config.current ) {
- throw new Error( "assertion outside test context, was " + sourceFromStacktrace() );
- }
-
- var output, source,
- details = {
- module: config.current.module,
- name: config.current.testName,
- result: result,
- message: message,
- actual: actual,
- expected: expected
- };
-
- message = escapeText( message ) || ( result ? "okay" : "failed" );
- message = "" + message + " ";
- output = message;
-
- if ( !result ) {
- expected = escapeText( QUnit.jsDump.parse(expected) );
- actual = escapeText( QUnit.jsDump.parse(actual) );
- output += "Expected: " + expected + " ";
-
- if ( actual !== expected ) {
- output += "Result: " + actual + " ";
- output += "Diff: " + QUnit.diff( expected, actual ) + " ";
- }
-
- source = sourceFromStacktrace();
-
- if ( source ) {
- details.source = source;
- output += "Source: " + escapeText( source ) + " ";
- }
-
- output += "
";
- }
-
- runLoggingCallbacks( "log", QUnit, details );
-
- config.current.assertions.push({
- result: !!result,
- message: output
- });
- },
-
- pushFailure: function( message, source, actual ) {
- if ( !config.current ) {
- throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) );
- }
-
- var output,
- details = {
- module: config.current.module,
- name: config.current.testName,
- result: false,
- message: message
- };
-
- message = escapeText( message ) || "error";
- message = "" + message + " ";
- output = message;
-
- output += "";
-
- if ( actual ) {
- output += "Result: " + escapeText( actual ) + " ";
- }
-
- if ( source ) {
- details.source = source;
- output += "Source: " + escapeText( source ) + " ";
- }
-
- output += "
";
-
- runLoggingCallbacks( "log", QUnit, details );
-
- config.current.assertions.push({
- result: false,
- message: output
- });
- },
-
- url: function( params ) {
- params = extend( extend( {}, QUnit.urlParams ), params );
- var key,
- querystring = "?";
-
- for ( key in params ) {
- if ( hasOwn.call( params, key ) ) {
- querystring += encodeURIComponent( key ) + "=" +
- encodeURIComponent( params[ key ] ) + "&";
- }
- }
- return window.location.protocol + "//" + window.location.host +
- window.location.pathname + querystring.slice( 0, -1 );
- },
-
extend: extend,
- id: id,
- addEvent: addEvent,
- addClass: addClass,
- hasClass: hasClass,
- removeClass: removeClass
- // load, equiv, jsDump, diff: Attached later
+
+ load: function() {
+ config.pageLoaded = true;
+
+ // Initialize the configuration options
+ extend( config, {
+ stats: { all: 0, bad: 0 },
+ moduleStats: { all: 0, bad: 0 },
+ started: 0,
+ updateRate: 1000,
+ autostart: true,
+ filter: ""
+ }, true );
+
+ config.blocking = false;
+
+ if ( config.autostart ) {
+ resumeProcessing();
+ }
+ }
});
-/**
- * @deprecated: Created for backwards compatibility with test runner that set the hook function
- * into QUnit.{hook}, instead of invoking it and passing the hook function.
- * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here.
- * Doing this allows us to tell if the following methods have been overwritten on the actual
- * QUnit object.
- */
-extend( QUnit.constructor.prototype, {
+// Register logging callbacks
+(function() {
+ var i, l, key,
+ callbacks = [ "begin", "done", "log", "testStart", "testDone",
+ "moduleStart", "moduleDone" ];
- // Logging callbacks; all receive a single argument with the listed properties
- // run test/logs.html for any related changes
- begin: registerLoggingCallback( "begin" ),
-
- // done: { failed, passed, total, runtime }
- done: registerLoggingCallback( "done" ),
-
- // log: { result, actual, expected, message }
- log: registerLoggingCallback( "log" ),
-
- // testStart: { name }
- testStart: registerLoggingCallback( "testStart" ),
-
- // testDone: { name, failed, passed, total, duration }
- testDone: registerLoggingCallback( "testDone" ),
-
- // moduleStart: { name }
- moduleStart: registerLoggingCallback( "moduleStart" ),
-
- // moduleDone: { name, failed, passed, total }
- moduleDone: registerLoggingCallback( "moduleDone" )
-});
-
-if ( typeof document === "undefined" || document.readyState === "complete" ) {
- config.autorun = true;
-}
-
-QUnit.load = function() {
- runLoggingCallbacks( "begin", QUnit, {} );
-
- // Initialize the config, saving the execution queue
- var banner, filter, i, label, len, main, ol, toolbar, userAgent, val,
- urlConfigCheckboxesContainer, urlConfigCheckboxes, moduleFilter,
- numModules = 0,
- moduleNames = [],
- moduleFilterHtml = "",
- urlConfigHtml = "",
- oldconfig = extend( {}, config );
-
- QUnit.init();
- extend(config, oldconfig);
-
- config.blocking = false;
-
- len = config.urlConfig.length;
-
- for ( i = 0; i < len; i++ ) {
- val = config.urlConfig[i];
- if ( typeof val === "string" ) {
- val = {
- id: val,
- label: val,
- tooltip: "[no tooltip available]"
- };
- }
- config[ val.id ] = QUnit.urlParams[ val.id ];
- urlConfigHtml += "" + val.label + " ";
- }
- for ( i in config.modules ) {
- if ( config.modules.hasOwnProperty( i ) ) {
- moduleNames.push(i);
- }
- }
- numModules = moduleNames.length;
- moduleNames.sort( function( a, b ) {
- return a.localeCompare( b );
- });
- moduleFilterHtml += "Module: < All Modules > ";
-
-
- for ( i = 0; i < numModules; i++) {
- moduleFilterHtml += "" + escapeText(moduleNames[i]) + " ";
- }
- moduleFilterHtml += " ";
-
- // `userAgent` initialized at top of scope
- userAgent = id( "qunit-userAgent" );
- if ( userAgent ) {
- userAgent.innerHTML = navigator.userAgent;
- }
-
- // `banner` initialized at top of scope
- banner = id( "qunit-header" );
- if ( banner ) {
- banner.innerHTML = "" + banner.innerHTML + " ";
- }
-
- // `toolbar` initialized at top of scope
- toolbar = id( "qunit-testrunner-toolbar" );
- if ( toolbar ) {
- // `filter` initialized at top of scope
- filter = document.createElement( "input" );
- filter.type = "checkbox";
- filter.id = "qunit-filter-pass";
-
- addEvent( filter, "click", function() {
- var tmp,
- ol = document.getElementById( "qunit-tests" );
-
- if ( filter.checked ) {
- ol.className = ol.className + " hidepass";
- } else {
- tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " ";
- ol.className = tmp.replace( / hidepass /, " " );
+ function registerLoggingCallback( key ) {
+ var loggingCallback = function( callback ) {
+ if ( QUnit.objectType( callback ) !== "function" ) {
+ throw new Error(
+ "QUnit logging methods require a callback function as their first parameters."
+ );
}
- if ( defined.sessionStorage ) {
- if (filter.checked) {
- sessionStorage.setItem( "qunit-filter-passed-tests", "true" );
- } else {
- sessionStorage.removeItem( "qunit-filter-passed-tests" );
- }
- }
- });
- if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem( "qunit-filter-passed-tests" ) ) {
- filter.checked = true;
- // `ol` initialized at top of scope
- ol = document.getElementById( "qunit-tests" );
- ol.className = ol.className + " hidepass";
+ config.callbacks[ key ].push( callback );
+ };
+
+ // DEPRECATED: This will be removed on QUnit 2.0.0+
+ // Stores the registered functions allowing restoring
+ // at verifyLoggingCallbacks() if modified
+ loggingCallbacks[ key ] = loggingCallback;
+
+ return loggingCallback;
+ }
+
+ for ( i = 0, l = callbacks.length; i < l; i++ ) {
+ key = callbacks[ i ];
+
+ // Initialize key collection of logging callback
+ if ( QUnit.objectType( config.callbacks[ key ] ) === "undefined" ) {
+ config.callbacks[ key ] = [];
}
- toolbar.appendChild( filter );
- // `label` initialized at top of scope
- label = document.createElement( "label" );
- label.setAttribute( "for", "qunit-filter-pass" );
- label.setAttribute( "title", "Only show tests and assertions that fail. Stored in sessionStorage." );
- label.innerHTML = "Hide passed tests";
- toolbar.appendChild( label );
-
- urlConfigCheckboxesContainer = document.createElement("span");
- urlConfigCheckboxesContainer.innerHTML = urlConfigHtml;
- urlConfigCheckboxes = urlConfigCheckboxesContainer.getElementsByTagName("input");
- // For oldIE support:
- // * Add handlers to the individual elements instead of the container
- // * Use "click" instead of "change"
- // * Fallback from event.target to event.srcElement
- addEvents( urlConfigCheckboxes, "click", function( event ) {
- var params = {},
- target = event.target || event.srcElement;
- params[ target.name ] = target.checked ? true : undefined;
- window.location = QUnit.url( params );
- });
- toolbar.appendChild( urlConfigCheckboxesContainer );
-
- if (numModules > 1) {
- moduleFilter = document.createElement( "span" );
- moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );
- moduleFilter.innerHTML = moduleFilterHtml;
- addEvent( moduleFilter.lastChild, "change", function() {
- var selectBox = moduleFilter.getElementsByTagName("select")[0],
- selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value);
-
- window.location = QUnit.url({
- module: ( selectedModule === "" ) ? undefined : selectedModule,
- // Remove any existing filters
- filter: undefined,
- testNumber: undefined
- });
- });
- toolbar.appendChild(moduleFilter);
- }
+ QUnit[ key ] = registerLoggingCallback( key );
}
-
- // `main` initialized at top of scope
- main = id( "qunit-fixture" );
- if ( main ) {
- config.fixture = main.innerHTML;
- }
-
- if ( config.autostart ) {
- QUnit.start();
- }
-};
-
-addEvent( window, "load", QUnit.load );
+})();
// `onErrorFnPrev` initialized at top of scope
// Preserve other handlers
@@ -1222,7 +442,7 @@ onErrorFnPrev = window.onerror;
// Cover uncaught exceptions
// Returning true will suppress the default browser handler,
// returning false will let it run.
-window.onerror = function ( error, filePath, linerNr ) {
+window.onerror = function( error, filePath, linerNr ) {
var ret = false;
if ( onErrorFnPrev ) {
ret = onErrorFnPrev( error, filePath, linerNr );
@@ -1237,9 +457,9 @@ window.onerror = function ( error, filePath, linerNr ) {
}
QUnit.pushFailure( error, filePath + ":" + linerNr );
} else {
- QUnit.test( "global failure", extend( function() {
+ QUnit.test( "global failure", extend(function() {
QUnit.pushFailure( error, filePath + ":" + linerNr );
- }, { validTest: validTest } ) );
+ }, { validTest: true } ) );
}
return false;
}
@@ -1248,71 +468,27 @@ window.onerror = function ( error, filePath, linerNr ) {
};
function done() {
+ var runtime, passed;
+
config.autorun = true;
// Log the last module results
- if ( config.currentModule ) {
- runLoggingCallbacks( "moduleDone", QUnit, {
- name: config.currentModule,
+ if ( config.previousModule ) {
+ runLoggingCallbacks( "moduleDone", {
+ name: config.previousModule.name,
+ tests: config.previousModule.tests,
failed: config.moduleStats.bad,
passed: config.moduleStats.all - config.moduleStats.bad,
- total: config.moduleStats.all
+ total: config.moduleStats.all,
+ runtime: now() - config.moduleStats.started
});
}
delete config.previousModule;
- var i, key,
- banner = id( "qunit-banner" ),
- tests = id( "qunit-tests" ),
- runtime = +new Date() - config.started,
- passed = config.stats.all - config.stats.bad,
- html = [
- "Tests completed in ",
- runtime,
- " milliseconds. ",
- "",
- passed,
- " assertions of ",
- config.stats.all,
- " passed, ",
- config.stats.bad,
- " failed."
- ].join( "" );
+ runtime = now() - config.started;
+ passed = config.stats.all - config.stats.bad;
- if ( banner ) {
- banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" );
- }
-
- if ( tests ) {
- id( "qunit-testresult" ).innerHTML = html;
- }
-
- if ( config.altertitle && typeof document !== "undefined" && document.title ) {
- // show ✖ for good, ✔ for bad suite result in title
- // use escape sequences in case file gets loaded with non-utf-8-charset
- document.title = [
- ( config.stats.bad ? "\u2716" : "\u2714" ),
- document.title.replace( /^[\u2714\u2716] /i, "" )
- ].join( " " );
- }
-
- // clear own sessionStorage items if all tests passed
- if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) {
- // `key` & `i` initialized at top of scope
- for ( i = 0; i < sessionStorage.length; i++ ) {
- key = sessionStorage.key( i++ );
- if ( key.indexOf( "qunit-test-" ) === 0 ) {
- sessionStorage.removeItem( key );
- }
- }
- }
-
- // scroll back to top to show results
- if ( window.scrollTo ) {
- window.scrollTo(0, 0);
- }
-
- runLoggingCallbacks( "done", QUnit, {
+ runLoggingCallbacks( "done", {
failed: config.stats.bad,
passed: passed,
total: config.stats.all,
@@ -1320,60 +496,16 @@ function done() {
});
}
-/** @return Boolean: true if this test should be ran */
-function validTest( test ) {
- var include,
- filter = config.filter && config.filter.toLowerCase(),
- module = config.module && config.module.toLowerCase(),
- fullName = (test.module + ": " + test.testName).toLowerCase();
-
- // Internally-generated tests are always valid
- if ( test.callback && test.callback.validTest === validTest ) {
- delete test.callback.validTest;
- return true;
- }
-
- if ( config.testNumber ) {
- return test.testNumber === config.testNumber;
- }
-
- if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) {
- return false;
- }
-
- if ( !filter ) {
- return true;
- }
-
- include = filter.charAt( 0 ) !== "!";
- if ( !include ) {
- filter = filter.slice( 1 );
- }
-
- // If the filter matches, we need to honour include
- if ( fullName.indexOf( filter ) !== -1 ) {
- return include;
- }
-
- // Otherwise, do the opposite
- return !include;
-}
-
-// so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions)
-// Later Safari and IE10 are supposed to support error.stack as well
+// Doesn't support IE6 to IE9, it will return undefined on these browsers
// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
function extractStacktrace( e, offset ) {
- offset = offset === undefined ? 3 : offset;
+ offset = offset === undefined ? 4 : offset;
var stack, include, i;
- if ( e.stacktrace ) {
- // Opera
- return e.stacktrace.split( "\n" )[ offset + 3 ];
- } else if ( e.stack ) {
- // Firefox, Chrome
+ if ( e.stack ) {
stack = e.stack.split( "\n" );
- if (/^error$/i.test( stack[0] ) ) {
+ if ( /^error$/i.test( stack[ 0 ] ) ) {
stack.shift();
}
if ( fileName ) {
@@ -1389,51 +521,43 @@ function extractStacktrace( e, offset ) {
}
}
return stack[ offset ];
+
+ // Support: Safari <=6 only
} else if ( e.sourceURL ) {
- // Safari, PhantomJS
- // hopefully one day Safari provides actual stacktraces
+
// exclude useless self-reference for generated Error objects
if ( /qunit.js$/.test( e.sourceURL ) ) {
return;
}
+
// for actual exceptions, this is useful
return e.sourceURL + ":" + e.line;
}
}
-function sourceFromStacktrace( offset ) {
- try {
- throw new Error();
- } catch ( e ) {
- return extractStacktrace( e, offset );
- }
-}
-/**
- * Escape text for attribute or text content.
- */
-function escapeText( s ) {
- if ( !s ) {
- return "";
- }
- s = s + "";
- // Both single quotes and double quotes (for attributes)
- return s.replace( /['"<>&]/g, function( s ) {
- switch( s ) {
- case "'":
- return "'";
- case "\"":
- return """;
- case "<":
- return "<";
- case ">":
- return ">";
- case "&":
- return "&";
+function sourceFromStacktrace( offset ) {
+ var error = new Error();
+
+ // Support: Safari <=7 only, IE <=10 - 11 only
+ // Not all browsers generate the `stack` property for `new Error()`, see also #636
+ if ( !error.stack ) {
+ try {
+ throw error;
+ } catch ( err ) {
+ error = err;
}
- });
+ }
+
+ return extractStacktrace( error, offset );
}
function synchronize( callback, last ) {
+ if ( QUnit.objectType( callback ) === "array" ) {
+ while ( callback.length ) {
+ synchronize( callback.shift() );
+ }
+ return;
+ }
config.queue.push( callback );
if ( config.autorun && !config.blocking ) {
@@ -1445,11 +569,17 @@ function process( last ) {
function next() {
process( last );
}
- var start = new Date().getTime();
- config.depth = config.depth ? config.depth + 1 : 1;
+ var start = now();
+ config.depth = ( config.depth || 0 ) + 1;
while ( config.queue.length && !config.blocking ) {
- if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) {
+ if ( !defined.setTimeout || config.updateRate <= 0 ||
+ ( ( now() - start ) < config.updateRate ) ) {
+ if ( config.current ) {
+
+ // Reset async tracking for each phase of the Test lifecycle
+ config.current.usedAsync = false;
+ }
config.queue.shift()();
} else {
setTimeout( next, 13 );
@@ -1462,6 +592,79 @@ function process( last ) {
}
}
+function begin() {
+ var i, l,
+ modulesLog = [];
+
+ // If the test run hasn't officially begun yet
+ if ( !config.started ) {
+
+ // Record the time of the test run's beginning
+ config.started = now();
+
+ verifyLoggingCallbacks();
+
+ // Delete the loose unnamed module if unused.
+ if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) {
+ config.modules.shift();
+ }
+
+ // Avoid unnecessary information by not logging modules' test environments
+ for ( i = 0, l = config.modules.length; i < l; i++ ) {
+ modulesLog.push({
+ name: config.modules[ i ].name,
+ tests: config.modules[ i ].tests
+ });
+ }
+
+ // The test run is officially beginning now
+ runLoggingCallbacks( "begin", {
+ totalTests: Test.count,
+ modules: modulesLog
+ });
+ }
+
+ config.blocking = false;
+ process( true );
+}
+
+function resumeProcessing() {
+ runStarted = true;
+
+ // A slight delay to allow this iteration of the event loop to finish (more assertions, etc.)
+ if ( defined.setTimeout ) {
+ setTimeout(function() {
+ if ( config.current && config.current.semaphore > 0 ) {
+ return;
+ }
+ if ( config.timeout ) {
+ clearTimeout( config.timeout );
+ }
+
+ begin();
+ }, 13 );
+ } else {
+ begin();
+ }
+}
+
+function pauseProcessing() {
+ config.blocking = true;
+
+ if ( config.testTimeout && defined.setTimeout ) {
+ clearTimeout( config.timeout );
+ config.timeout = setTimeout(function() {
+ if ( config.current ) {
+ config.current.semaphore = 0;
+ QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) );
+ } else {
+ throw new Error( "Test timed out" );
+ }
+ resumeProcessing();
+ }, config.testTimeout );
+ }
+}
+
function saveGlobal() {
config.pollution = [];
@@ -1487,12 +690,12 @@ function checkPollution() {
newGlobals = diff( config.pollution, old );
if ( newGlobals.length > 0 ) {
- QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") );
+ QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) );
}
deletedGlobals = diff( old, config.pollution );
if ( deletedGlobals.length > 0 ) {
- QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") );
+ QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) );
}
}
@@ -1503,7 +706,7 @@ function diff( a, b ) {
for ( i = 0; i < result.length; i++ ) {
for ( j = 0; j < b.length; j++ ) {
- if ( result[i] === b[j] ) {
+ if ( result[ i ] === b[ j ] ) {
result.splice( i, 1 );
i--;
break;
@@ -1513,14 +716,15 @@ function diff( a, b ) {
return result;
}
-function extend( a, b ) {
+function extend( a, b, undefOnly ) {
for ( var prop in b ) {
if ( hasOwn.call( b, prop ) ) {
+
// Avoid "Member not found" error in IE8 caused by messing with window.constructor
if ( !( prop === "constructor" && a === window ) ) {
if ( b[ prop ] === undefined ) {
delete a[ prop ];
- } else {
+ } else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) {
a[ prop ] = b[ prop ];
}
}
@@ -1530,77 +734,680 @@ function extend( a, b ) {
return a;
}
-/**
- * @param {HTMLElement} elem
- * @param {string} type
- * @param {Function} fn
- */
-function addEvent( elem, type, fn ) {
- // Standards-based browsers
- if ( elem.addEventListener ) {
- elem.addEventListener( type, fn, false );
- // IE
- } else {
- elem.attachEvent( "on" + type, fn );
+function runLoggingCallbacks( key, args ) {
+ var i, l, callbacks;
+
+ callbacks = config.callbacks[ key ];
+ for ( i = 0, l = callbacks.length; i < l; i++ ) {
+ callbacks[ i ]( args );
}
}
-/**
- * @param {Array|NodeList} elems
- * @param {string} type
- * @param {Function} fn
- */
-function addEvents( elems, type, fn ) {
- var i = elems.length;
- while ( i-- ) {
- addEvent( elems[i], type, fn );
- }
-}
+// DEPRECATED: This will be removed on 2.0.0+
+// This function verifies if the loggingCallbacks were modified by the user
+// If so, it will restore it, assign the given callback and print a console warning
+function verifyLoggingCallbacks() {
+ var loggingCallback, userCallback;
-function hasClass( elem, name ) {
- return (" " + elem.className + " ").indexOf(" " + name + " ") > -1;
-}
+ for ( loggingCallback in loggingCallbacks ) {
+ if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) {
-function addClass( elem, name ) {
- if ( !hasClass( elem, name ) ) {
- elem.className += (elem.className ? " " : "") + name;
- }
-}
+ userCallback = QUnit[ loggingCallback ];
-function removeClass( elem, name ) {
- var set = " " + elem.className + " ";
- // Class name may appear multiple times
- while ( set.indexOf(" " + name + " ") > -1 ) {
- set = set.replace(" " + name + " " , " ");
- }
- // If possible, trim it for prettiness, but not necessarily
- elem.className = typeof set.trim === "function" ? set.trim() : set.replace(/^\s+|\s+$/g, "");
-}
+ // Restore the callback function
+ QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ];
-function id( name ) {
- return !!( typeof document !== "undefined" && document && document.getElementById ) &&
- document.getElementById( name );
-}
+ // Assign the deprecated given callback
+ QUnit[ loggingCallback ]( userCallback );
-function registerLoggingCallback( key ) {
- return function( callback ) {
- config[key].push( callback );
- };
-}
-
-// Supports deprecated method of completely overwriting logging callbacks
-function runLoggingCallbacks( key, scope, args ) {
- var i, callbacks;
- if ( QUnit.hasOwnProperty( key ) ) {
- QUnit[ key ].call(scope, args );
- } else {
- callbacks = config[ key ];
- for ( i = 0; i < callbacks.length; i++ ) {
- callbacks[ i ].call( scope, args );
+ if ( window.console && window.console.warn ) {
+ window.console.warn(
+ "QUnit." + loggingCallback + " was replaced with a new value.\n" +
+ "Please, check out the documentation on how to apply logging callbacks.\n" +
+ "Reference: http://api.qunitjs.com/category/callbacks/"
+ );
+ }
}
}
}
+// from jquery.js
+function inArray( elem, array ) {
+ if ( array.indexOf ) {
+ return array.indexOf( elem );
+ }
+
+ for ( var i = 0, length = array.length; i < length; i++ ) {
+ if ( array[ i ] === elem ) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+function Test( settings ) {
+ var i, l;
+
+ ++Test.count;
+
+ extend( this, settings );
+ this.assertions = [];
+ this.semaphore = 0;
+ this.usedAsync = false;
+ this.module = config.currentModule;
+ this.stack = sourceFromStacktrace( 3 );
+
+ // Register unique strings
+ for ( i = 0, l = this.module.tests; i < l.length; i++ ) {
+ if ( this.module.tests[ i ].name === this.testName ) {
+ this.testName += " ";
+ }
+ }
+
+ this.testId = generateHash( this.module.name, this.testName );
+
+ this.module.tests.push({
+ name: this.testName,
+ testId: this.testId
+ });
+
+ if ( settings.skip ) {
+
+ // Skipped tests will fully ignore any sent callback
+ this.callback = function() {};
+ this.async = false;
+ this.expected = 0;
+ } else {
+ this.assert = new Assert( this );
+ }
+}
+
+Test.count = 0;
+
+Test.prototype = {
+ before: function() {
+ if (
+
+ // Emit moduleStart when we're switching from one module to another
+ this.module !== config.previousModule ||
+
+ // They could be equal (both undefined) but if the previousModule property doesn't
+ // yet exist it means this is the first test in a suite that isn't wrapped in a
+ // module, in which case we'll just emit a moduleStart event for 'undefined'.
+ // Without this, reporters can get testStart before moduleStart which is a problem.
+ !hasOwn.call( config, "previousModule" )
+ ) {
+ if ( hasOwn.call( config, "previousModule" ) ) {
+ runLoggingCallbacks( "moduleDone", {
+ name: config.previousModule.name,
+ tests: config.previousModule.tests,
+ failed: config.moduleStats.bad,
+ passed: config.moduleStats.all - config.moduleStats.bad,
+ total: config.moduleStats.all,
+ runtime: now() - config.moduleStats.started
+ });
+ }
+ config.previousModule = this.module;
+ config.moduleStats = { all: 0, bad: 0, started: now() };
+ runLoggingCallbacks( "moduleStart", {
+ name: this.module.name,
+ tests: this.module.tests
+ });
+ }
+
+ config.current = this;
+
+ this.testEnvironment = extend( {}, this.module.testEnvironment );
+ delete this.testEnvironment.beforeEach;
+ delete this.testEnvironment.afterEach;
+
+ this.started = now();
+ runLoggingCallbacks( "testStart", {
+ name: this.testName,
+ module: this.module.name,
+ testId: this.testId
+ });
+
+ if ( !config.pollution ) {
+ saveGlobal();
+ }
+ },
+
+ run: function() {
+ var promise;
+
+ config.current = this;
+
+ if ( this.async ) {
+ QUnit.stop();
+ }
+
+ this.callbackStarted = now();
+
+ if ( config.notrycatch ) {
+ promise = this.callback.call( this.testEnvironment, this.assert );
+ this.resolvePromise( promise );
+ return;
+ }
+
+ try {
+ promise = this.callback.call( this.testEnvironment, this.assert );
+ this.resolvePromise( promise );
+ } catch ( e ) {
+ this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " +
+ this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
+
+ // else next test will carry the responsibility
+ saveGlobal();
+
+ // Restart the tests if they're blocking
+ if ( config.blocking ) {
+ QUnit.start();
+ }
+ }
+ },
+
+ after: function() {
+ checkPollution();
+ },
+
+ queueHook: function( hook, hookName ) {
+ var promise,
+ test = this;
+ return function runHook() {
+ config.current = test;
+ if ( config.notrycatch ) {
+ promise = hook.call( test.testEnvironment, test.assert );
+ test.resolvePromise( promise, hookName );
+ return;
+ }
+ try {
+ promise = hook.call( test.testEnvironment, test.assert );
+ test.resolvePromise( promise, hookName );
+ } catch ( error ) {
+ test.pushFailure( hookName + " failed on " + test.testName + ": " +
+ ( error.message || error ), extractStacktrace( error, 0 ) );
+ }
+ };
+ },
+
+ // Currently only used for module level hooks, can be used to add global level ones
+ hooks: function( handler ) {
+ var hooks = [];
+
+ // Hooks are ignored on skipped tests
+ if ( this.skip ) {
+ return hooks;
+ }
+
+ if ( this.module.testEnvironment &&
+ QUnit.objectType( this.module.testEnvironment[ handler ] ) === "function" ) {
+ hooks.push( this.queueHook( this.module.testEnvironment[ handler ], handler ) );
+ }
+
+ return hooks;
+ },
+
+ finish: function() {
+ config.current = this;
+ if ( config.requireExpects && this.expected === null ) {
+ this.pushFailure( "Expected number of assertions to be defined, but expect() was " +
+ "not called.", this.stack );
+ } else if ( this.expected !== null && this.expected !== this.assertions.length ) {
+ this.pushFailure( "Expected " + this.expected + " assertions, but " +
+ this.assertions.length + " were run", this.stack );
+ } else if ( this.expected === null && !this.assertions.length ) {
+ this.pushFailure( "Expected at least one assertion, but none were run - call " +
+ "expect(0) to accept zero assertions.", this.stack );
+ }
+
+ var i,
+ bad = 0;
+
+ this.runtime = now() - this.started;
+ config.stats.all += this.assertions.length;
+ config.moduleStats.all += this.assertions.length;
+
+ for ( i = 0; i < this.assertions.length; i++ ) {
+ if ( !this.assertions[ i ].result ) {
+ bad++;
+ config.stats.bad++;
+ config.moduleStats.bad++;
+ }
+ }
+
+ runLoggingCallbacks( "testDone", {
+ name: this.testName,
+ module: this.module.name,
+ skipped: !!this.skip,
+ failed: bad,
+ passed: this.assertions.length - bad,
+ total: this.assertions.length,
+ runtime: this.runtime,
+
+ // HTML Reporter use
+ assertions: this.assertions,
+ testId: this.testId,
+
+ // DEPRECATED: this property will be removed in 2.0.0, use runtime instead
+ duration: this.runtime
+ });
+
+ // QUnit.reset() is deprecated and will be replaced for a new
+ // fixture reset function on QUnit 2.0/2.1.
+ // It's still called here for backwards compatibility handling
+ QUnit.reset();
+
+ config.current = undefined;
+ },
+
+ queue: function() {
+ var bad,
+ test = this;
+
+ if ( !this.valid() ) {
+ return;
+ }
+
+ function run() {
+
+ // each of these can by async
+ synchronize([
+ function() {
+ test.before();
+ },
+
+ test.hooks( "beforeEach" ),
+
+ function() {
+ test.run();
+ },
+
+ test.hooks( "afterEach" ).reverse(),
+
+ function() {
+ test.after();
+ },
+ function() {
+ test.finish();
+ }
+ ]);
+ }
+
+ // `bad` initialized at top of scope
+ // defer when previous test run passed, if storage is available
+ bad = QUnit.config.reorder && defined.sessionStorage &&
+ +sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName );
+
+ if ( bad ) {
+ run();
+ } else {
+ synchronize( run, true );
+ }
+ },
+
+ push: function( result, actual, expected, message ) {
+ var source,
+ details = {
+ module: this.module.name,
+ name: this.testName,
+ result: result,
+ message: message,
+ actual: actual,
+ expected: expected,
+ testId: this.testId,
+ runtime: now() - this.started
+ };
+
+ if ( !result ) {
+ source = sourceFromStacktrace();
+
+ if ( source ) {
+ details.source = source;
+ }
+ }
+
+ runLoggingCallbacks( "log", details );
+
+ this.assertions.push({
+ result: !!result,
+ message: message
+ });
+ },
+
+ pushFailure: function( message, source, actual ) {
+ if ( !this instanceof Test ) {
+ throw new Error( "pushFailure() assertion outside test context, was " +
+ sourceFromStacktrace( 2 ) );
+ }
+
+ var details = {
+ module: this.module.name,
+ name: this.testName,
+ result: false,
+ message: message || "error",
+ actual: actual || null,
+ testId: this.testId,
+ runtime: now() - this.started
+ };
+
+ if ( source ) {
+ details.source = source;
+ }
+
+ runLoggingCallbacks( "log", details );
+
+ this.assertions.push({
+ result: false,
+ message: message
+ });
+ },
+
+ resolvePromise: function( promise, phase ) {
+ var then, message,
+ test = this;
+ if ( promise != null ) {
+ then = promise.then;
+ if ( QUnit.objectType( then ) === "function" ) {
+ QUnit.stop();
+ then.call(
+ promise,
+ QUnit.start,
+ function( error ) {
+ message = "Promise rejected " +
+ ( !phase ? "during" : phase.replace( /Each$/, "" ) ) +
+ " " + test.testName + ": " + ( error.message || error );
+ test.pushFailure( message, extractStacktrace( error, 0 ) );
+
+ // else next test will carry the responsibility
+ saveGlobal();
+
+ // Unblock
+ QUnit.start();
+ }
+ );
+ }
+ }
+ },
+
+ valid: function() {
+ var include,
+ filter = config.filter && config.filter.toLowerCase(),
+ module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(),
+ fullName = ( this.module.name + ": " + this.testName ).toLowerCase();
+
+ // Internally-generated tests are always valid
+ if ( this.callback && this.callback.validTest ) {
+ return true;
+ }
+
+ if ( config.testId.length > 0 && inArray( this.testId, config.testId ) < 0 ) {
+ return false;
+ }
+
+ if ( module && ( !this.module.name || this.module.name.toLowerCase() !== module ) ) {
+ return false;
+ }
+
+ if ( !filter ) {
+ return true;
+ }
+
+ include = filter.charAt( 0 ) !== "!";
+ if ( !include ) {
+ filter = filter.slice( 1 );
+ }
+
+ // If the filter matches, we need to honour include
+ if ( fullName.indexOf( filter ) !== -1 ) {
+ return include;
+ }
+
+ // Otherwise, do the opposite
+ return !include;
+ }
+
+};
+
+// Resets the test setup. Useful for tests that modify the DOM.
+/*
+DEPRECATED: Use multiple tests instead of resetting inside a test.
+Use testStart or testDone for custom cleanup.
+This method will throw an error in 2.0, and will be removed in 2.1
+*/
+QUnit.reset = function() {
+
+ // Return on non-browser environments
+ // This is necessary to not break on node tests
+ if ( typeof window === "undefined" ) {
+ return;
+ }
+
+ var fixture = defined.document && document.getElementById &&
+ document.getElementById( "qunit-fixture" );
+
+ if ( fixture ) {
+ fixture.innerHTML = config.fixture;
+ }
+};
+
+QUnit.pushFailure = function() {
+ if ( !QUnit.config.current ) {
+ throw new Error( "pushFailure() assertion outside test context, in " +
+ sourceFromStacktrace( 2 ) );
+ }
+
+ // Gets current test obj
+ var currentTest = QUnit.config.current;
+
+ return currentTest.pushFailure.apply( currentTest, arguments );
+};
+
+// Based on Java's String.hashCode, a simple but not
+// rigorously collision resistant hashing function
+function generateHash( module, testName ) {
+ var hex,
+ i = 0,
+ hash = 0,
+ str = module + "\x1C" + testName,
+ len = str.length;
+
+ for ( ; i < len; i++ ) {
+ hash = ( ( hash << 5 ) - hash ) + str.charCodeAt( i );
+ hash |= 0;
+ }
+
+ // Convert the possibly negative integer hash code into an 8 character hex string, which isn't
+ // strictly necessary but increases user understanding that the id is a SHA-like hash
+ hex = ( 0x100000000 + hash ).toString( 16 );
+ if ( hex.length < 8 ) {
+ hex = "0000000" + hex;
+ }
+
+ return hex.slice( -8 );
+}
+
+function Assert( testContext ) {
+ this.test = testContext;
+}
+
+// Assert helpers
+QUnit.assert = Assert.prototype = {
+
+ // Specify the number of expected assertions to guarantee that failed test
+ // (no assertions are run at all) don't slip through.
+ expect: function( asserts ) {
+ if ( arguments.length === 1 ) {
+ this.test.expected = asserts;
+ } else {
+ return this.test.expected;
+ }
+ },
+
+ // Increment this Test's semaphore counter, then return a single-use function that
+ // decrements that counter a maximum of once.
+ async: function() {
+ var test = this.test,
+ popped = false;
+
+ test.semaphore += 1;
+ test.usedAsync = true;
+ pauseProcessing();
+
+ return function done() {
+ if ( !popped ) {
+ test.semaphore -= 1;
+ popped = true;
+ resumeProcessing();
+ } else {
+ test.pushFailure( "Called the callback returned from `assert.async` more than once",
+ sourceFromStacktrace( 2 ) );
+ }
+ };
+ },
+
+ // Exports test.push() to the user API
+ push: function( /* result, actual, expected, message */ ) {
+ var assert = this,
+ currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current;
+
+ // Backwards compatibility fix.
+ // Allows the direct use of global exported assertions and QUnit.assert.*
+ // Although, it's use is not recommended as it can leak assertions
+ // to other tests from async tests, because we only get a reference to the current test,
+ // not exactly the test where assertion were intended to be called.
+ if ( !currentTest ) {
+ throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) );
+ }
+
+ if ( currentTest.usedAsync === true && currentTest.semaphore === 0 ) {
+ currentTest.pushFailure( "Assertion after the final `assert.async` was resolved",
+ sourceFromStacktrace( 2 ) );
+
+ // Allow this assertion to continue running anyway...
+ }
+
+ if ( !( assert instanceof Assert ) ) {
+ assert = currentTest.assert;
+ }
+ return assert.test.push.apply( assert.test, arguments );
+ },
+
+ ok: function( result, message ) {
+ message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " +
+ QUnit.dump.parse( result ) );
+ this.push( !!result, result, true, message );
+ },
+
+ notOk: function( result, message ) {
+ message = message || ( !result ? "okay" : "failed, expected argument to be falsy, was: " +
+ QUnit.dump.parse( result ) );
+ this.push( !result, result, false, message );
+ },
+
+ equal: function( actual, expected, message ) {
+ /*jshint eqeqeq:false */
+ this.push( expected == actual, actual, expected, message );
+ },
+
+ notEqual: function( actual, expected, message ) {
+ /*jshint eqeqeq:false */
+ this.push( expected != actual, actual, expected, message );
+ },
+
+ propEqual: function( actual, expected, message ) {
+ actual = objectValues( actual );
+ expected = objectValues( expected );
+ this.push( QUnit.equiv( actual, expected ), actual, expected, message );
+ },
+
+ notPropEqual: function( actual, expected, message ) {
+ actual = objectValues( actual );
+ expected = objectValues( expected );
+ this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
+ },
+
+ deepEqual: function( actual, expected, message ) {
+ this.push( QUnit.equiv( actual, expected ), actual, expected, message );
+ },
+
+ notDeepEqual: function( actual, expected, message ) {
+ this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
+ },
+
+ strictEqual: function( actual, expected, message ) {
+ this.push( expected === actual, actual, expected, message );
+ },
+
+ notStrictEqual: function( actual, expected, message ) {
+ this.push( expected !== actual, actual, expected, message );
+ },
+
+ "throws": function( block, expected, message ) {
+ var actual, expectedType,
+ expectedOutput = expected,
+ ok = false,
+ currentTest = ( this instanceof Assert && this.test ) || QUnit.config.current;
+
+ // 'expected' is optional unless doing string comparison
+ if ( message == null && typeof expected === "string" ) {
+ message = expected;
+ expected = null;
+ }
+
+ currentTest.ignoreGlobalErrors = true;
+ try {
+ block.call( currentTest.testEnvironment );
+ } catch (e) {
+ actual = e;
+ }
+ currentTest.ignoreGlobalErrors = false;
+
+ if ( actual ) {
+ expectedType = QUnit.objectType( expected );
+
+ // we don't want to validate thrown error
+ if ( !expected ) {
+ ok = true;
+ expectedOutput = null;
+
+ // expected is a regexp
+ } else if ( expectedType === "regexp" ) {
+ ok = expected.test( errorString( actual ) );
+
+ // expected is a string
+ } else if ( expectedType === "string" ) {
+ ok = expected === errorString( actual );
+
+ // expected is a constructor, maybe an Error constructor
+ } else if ( expectedType === "function" && actual instanceof expected ) {
+ ok = true;
+
+ // expected is an Error object
+ } else if ( expectedType === "object" ) {
+ ok = actual instanceof expected.constructor &&
+ actual.name === expected.name &&
+ actual.message === expected.message;
+
+ // expected is a validation function which returns true if validation passed
+ } else if ( expectedType === "function" && expected.call( {}, actual ) === true ) {
+ expectedOutput = null;
+ ok = true;
+ }
+ }
+
+ currentTest.assert.push( ok, actual, expectedOutput, message );
+ }
+};
+
+// Provide an alternative to assert.throws(), for enviroments that consider throws a reserved word
+// Known to us are: Closure Compiler, Narwhal
+(function() {
+ /*jshint sub:true */
+ Assert.prototype.raises = Assert.prototype[ "throws" ];
+}());
+
// Test for equality any JavaScript type.
// Author: Philippe Rathé
QUnit.equiv = (function() {
@@ -1619,22 +1426,26 @@ QUnit.equiv = (function() {
// the real equiv function
var innerEquiv,
+
// stack to decide between skip/abort functions
callers = [],
+
// stack to avoiding loops from circular referencing
parents = [],
parentsB = [],
- getProto = Object.getPrototypeOf || function ( obj ) {
- /*jshint camelcase:false */
+ getProto = Object.getPrototypeOf || function( obj ) {
+ /* jshint camelcase: false, proto: true */
return obj.__proto__;
},
- callbacks = (function () {
+ callbacks = (function() {
// for string, boolean, number and null
function useStrictEquality( b, a ) {
+
/*jshint eqeqeq:false */
if ( b instanceof a.constructor || a instanceof b.constructor ) {
+
// to catch short annotation VS 'new' annotation of a
// declaration
// e.g. var i = 1;
@@ -1662,10 +1473,13 @@ QUnit.equiv = (function() {
"regexp": function( b, a ) {
return QUnit.objectType( b ) === "regexp" &&
+
// the regex itself
a.source === b.source &&
+
// and its modifiers
a.global === b.global &&
+
// (gmi) ...
a.ignoreCase === b.ignoreCase &&
a.multiline === b.multiline &&
@@ -1676,7 +1490,7 @@ QUnit.equiv = (function() {
// - abort otherwise,
// initial === would have catch identical references anyway
"function": function() {
- var caller = callers[callers.length - 1];
+ var caller = callers[ callers.length - 1 ];
return caller !== Object && typeof caller !== "undefined";
},
@@ -1700,10 +1514,10 @@ QUnit.equiv = (function() {
for ( i = 0; i < len; i++ ) {
loop = false;
for ( j = 0; j < parents.length; j++ ) {
- aCircular = parents[j] === a[i];
- bCircular = parentsB[j] === b[i];
+ aCircular = parents[ j ] === a[ i ];
+ bCircular = parentsB[ j ] === b[ i ];
if ( aCircular || bCircular ) {
- if ( a[i] === b[i] || aCircular && bCircular ) {
+ if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
loop = true;
} else {
parents.pop();
@@ -1712,7 +1526,7 @@ QUnit.equiv = (function() {
}
}
}
- if ( !loop && !innerEquiv(a[i], b[i]) ) {
+ if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
parents.pop();
parentsB.pop();
return false;
@@ -1724,6 +1538,7 @@ QUnit.equiv = (function() {
},
"object": function( b, a ) {
+
/*jshint forin:false */
var i, j, loop, aCircular, bCircular,
// Default to true
@@ -1734,11 +1549,12 @@ QUnit.equiv = (function() {
// comparing constructors is more strict than using
// instanceof
if ( a.constructor !== b.constructor ) {
+
// Allow objects with no prototype to be equivalent to
// objects with Object as their constructor.
- if ( !(( getProto(a) === null && getProto(b) === Object.prototype ) ||
- ( getProto(b) === null && getProto(a) === Object.prototype ) ) ) {
- return false;
+ if ( !( ( getProto( a ) === null && getProto( b ) === Object.prototype ) ||
+ ( getProto( b ) === null && getProto( a ) === Object.prototype ) ) ) {
+ return false;
}
}
@@ -1753,10 +1569,10 @@ QUnit.equiv = (function() {
for ( i in a ) {
loop = false;
for ( j = 0; j < parents.length; j++ ) {
- aCircular = parents[j] === a[i];
- bCircular = parentsB[j] === b[i];
+ aCircular = parents[ j ] === a[ i ];
+ bCircular = parentsB[ j ] === b[ i ];
if ( aCircular || bCircular ) {
- if ( a[i] === b[i] || aCircular && bCircular ) {
+ if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
loop = true;
} else {
eq = false;
@@ -1764,8 +1580,8 @@ QUnit.equiv = (function() {
}
}
}
- aProperties.push(i);
- if ( !loop && !innerEquiv(a[i], b[i]) ) {
+ aProperties.push( i );
+ if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
eq = false;
break;
}
@@ -1791,35 +1607,30 @@ QUnit.equiv = (function() {
return true; // end transition
}
- return (function( a, b ) {
+ return ( (function( a, b ) {
if ( a === b ) {
return true; // catch the most you can
} else if ( a === null || b === null || typeof a === "undefined" ||
typeof b === "undefined" ||
- QUnit.objectType(a) !== QUnit.objectType(b) ) {
- return false; // don't lose time with error prone cases
+ QUnit.objectType( a ) !== QUnit.objectType( b ) ) {
+
+ // don't lose time with error prone cases
+ return false;
} else {
- return bindCallbacks(a, callbacks, [ b, a ]);
+ return bindCallbacks( a, callbacks, [ b, a ] );
}
// apply transition with (1..n) arguments
- }( args[0], args[1] ) && innerEquiv.apply( this, args.splice(1, args.length - 1 )) );
+ }( args[ 0 ], args[ 1 ] ) ) &&
+ innerEquiv.apply( this, args.splice( 1, args.length - 1 ) ) );
};
return innerEquiv;
}());
-/**
- * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com |
- * http://flesler.blogspot.com Licensed under BSD
- * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008
- *
- * @projectDescription Advanced and extensible data dumping for Javascript.
- * @version 1.0.0
- * @author Ariel Flesler
- * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
- */
-QUnit.jsDump = (function() {
+// Based on jsDump by Ariel Flesler
+// http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
+QUnit.dump = (function() {
function quote( str ) {
return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\"";
}
@@ -1827,48 +1638,57 @@ QUnit.jsDump = (function() {
return o + "";
}
function join( pre, arr, post ) {
- var s = jsDump.separator(),
- base = jsDump.indent(),
- inner = jsDump.indent(1);
+ var s = dump.separator(),
+ base = dump.indent(),
+ inner = dump.indent( 1 );
if ( arr.join ) {
arr = arr.join( "," + s + inner );
}
if ( !arr ) {
return pre + post;
}
- return [ pre, inner + arr, base + post ].join(s);
+ return [ pre, inner + arr, base + post ].join( s );
}
function array( arr, stack ) {
- var i = arr.length, ret = new Array(i);
+ var i = arr.length,
+ ret = new Array( i );
+
+ if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
+ return "[object Array]";
+ }
+
this.up();
while ( i-- ) {
- ret[i] = this.parse( arr[i] , undefined , stack);
+ ret[ i ] = this.parse( arr[ i ], undefined, stack );
}
this.down();
return join( "[", ret, "]" );
}
var reName = /^function (\w+)/,
- jsDump = {
- // type is used mostly internally, you can fix a (custom)type in advance
- parse: function( obj, type, stack ) {
- stack = stack || [ ];
- var inStack, res,
- parser = this.parsers[ type || this.typeOf(obj) ];
+ dump = {
- type = typeof parser;
- inStack = inArray( obj, stack );
+ // objType is used mostly internally, you can fix a (custom) type in advance
+ parse: function( obj, objType, stack ) {
+ stack = stack || [];
+ var res, parser, parserType,
+ inStack = inArray( obj, stack );
if ( inStack !== -1 ) {
- return "recursion(" + (inStack - stack.length) + ")";
+ return "recursion(" + ( inStack - stack.length ) + ")";
}
- if ( type === "function" ) {
+
+ objType = objType || this.typeOf( obj );
+ parser = this.parsers[ objType ];
+ parserType = typeof parser;
+
+ if ( parserType === "function" ) {
stack.push( obj );
res = parser.call( this, obj, stack );
stack.pop();
return res;
}
- return ( type === "string" ) ? parser : this.parsers.error;
+ return ( parserType === "string" ) ? parser : this.parsers.error;
},
typeOf: function( obj ) {
var type;
@@ -1876,23 +1696,29 @@ QUnit.jsDump = (function() {
type = "null";
} else if ( typeof obj === "undefined" ) {
type = "undefined";
- } else if ( QUnit.is( "regexp", obj) ) {
+ } else if ( QUnit.is( "regexp", obj ) ) {
type = "regexp";
- } else if ( QUnit.is( "date", obj) ) {
+ } else if ( QUnit.is( "date", obj ) ) {
type = "date";
- } else if ( QUnit.is( "function", obj) ) {
+ } else if ( QUnit.is( "function", obj ) ) {
type = "function";
- } else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) {
+ } else if ( obj.setInterval !== undefined &&
+ obj.document !== undefined &&
+ obj.nodeType === undefined ) {
type = "window";
} else if ( obj.nodeType === 9 ) {
type = "document";
} else if ( obj.nodeType ) {
type = "node";
} else if (
+
// native arrays
toString.call( obj ) === "[object Array]" ||
+
// NodeList objects
- ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) )
+ ( typeof obj.length === "number" && obj.item !== undefined &&
+ ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null &&
+ obj[ 0 ] === undefined ) ) )
) {
type = "array";
} else if ( obj.constructor === Error.prototype.constructor ) {
@@ -1903,7 +1729,7 @@ QUnit.jsDump = (function() {
return type;
},
separator: function() {
- return this.multiline ? this.HTML ? " " : "\n" : this.HTML ? " " : " ";
+ return this.multiline ? this.HTML ? " " : "\n" : this.HTML ? " " : " ";
},
// extra can be a number, shortcut for increasing-calling-decreasing
indent: function( extra ) {
@@ -1912,9 +1738,9 @@ QUnit.jsDump = (function() {
}
var chr = this.indentChar;
if ( this.HTML ) {
- chr = chr.replace( /\t/g, " " ).replace( / /g, " " );
+ chr = chr.replace( /\t/g, " " ).replace( / /g, " " );
}
- return new Array( this.depth + ( extra || 0 ) ).join(chr);
+ return new Array( this.depth + ( extra || 0 ) ).join( chr );
},
up: function( a ) {
this.depth += a || 1;
@@ -1923,7 +1749,7 @@ QUnit.jsDump = (function() {
this.depth -= a || 1;
},
setParser: function( name, parser ) {
- this.parsers[name] = parser;
+ this.parsers[ name ] = parser;
},
// The next 3 are exposed so you can use them
quote: quote,
@@ -1931,11 +1757,13 @@ QUnit.jsDump = (function() {
join: join,
//
depth: 1,
- // This is the list of parsers, to modify them, use jsDump.setParser
+ maxDepth: QUnit.config.maxDepth,
+
+ // This is the list of parsers, to modify them, use dump.setParser
parsers: {
window: "[Window]",
document: "[Document]",
- error: function(error) {
+ error: function( error ) {
return "Error(\"" + error.message + "\")";
},
unknown: "[Unknown]",
@@ -1943,52 +1771,71 @@ QUnit.jsDump = (function() {
"undefined": "undefined",
"function": function( fn ) {
var ret = "function",
+
// functions never have name in IE
- name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1];
+ name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ];
if ( name ) {
ret += " " + name;
}
ret += "( ";
- ret = [ ret, QUnit.jsDump.parse( fn, "functionArgs" ), "){" ].join( "" );
- return join( ret, QUnit.jsDump.parse(fn,"functionCode" ), "}" );
+ ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" );
+ return join( ret, dump.parse( fn, "functionCode" ), "}" );
},
array: array,
nodelist: array,
"arguments": array,
object: function( map, stack ) {
- /*jshint forin:false */
- var ret = [ ], keys, key, val, i;
- QUnit.jsDump.up();
+ var keys, key, val, i, nonEnumerableProperties,
+ ret = [];
+
+ if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
+ return "[object Object]";
+ }
+
+ dump.up();
keys = [];
for ( key in map ) {
keys.push( key );
}
+
+ // Some properties are not always enumerable on Error objects.
+ nonEnumerableProperties = [ "message", "name" ];
+ for ( i in nonEnumerableProperties ) {
+ key = nonEnumerableProperties[ i ];
+ if ( key in map && inArray( key, keys ) < 0 ) {
+ keys.push( key );
+ }
+ }
keys.sort();
for ( i = 0; i < keys.length; i++ ) {
key = keys[ i ];
val = map[ key ];
- ret.push( QUnit.jsDump.parse( key, "key" ) + ": " + QUnit.jsDump.parse( val, undefined, stack ) );
+ ret.push( dump.parse( key, "key" ) + ": " +
+ dump.parse( val, undefined, stack ) );
}
- QUnit.jsDump.down();
+ dump.down();
return join( "{", ret, "}" );
},
node: function( node ) {
var len, i, val,
- open = QUnit.jsDump.HTML ? "<" : "<",
- close = QUnit.jsDump.HTML ? ">" : ">",
+ open = dump.HTML ? "<" : "<",
+ close = dump.HTML ? ">" : ">",
tag = node.nodeName.toLowerCase(),
ret = open + tag,
attrs = node.attributes;
if ( attrs ) {
for ( i = 0, len = attrs.length; i < len; i++ ) {
- val = attrs[i].nodeValue;
- // IE6 includes all attributes in .attributes, even ones not explicitly set.
- // Those have values like undefined, null, 0, false, "" or "inherit".
+ val = attrs[ i ].nodeValue;
+
+ // IE6 includes all attributes in .attributes, even ones not explicitly
+ // set. Those have values like undefined, null, 0, false, "" or
+ // "inherit".
if ( val && val !== "inherit" ) {
- ret += " " + attrs[i].nodeName + "=" + QUnit.jsDump.parse( val, "attribute" );
+ ret += " " + attrs[ i ].nodeName + "=" +
+ dump.parse( val, "attribute" );
}
}
}
@@ -2001,6 +1848,7 @@ QUnit.jsDump = (function() {
return ret + open + "/" + tag + close;
},
+
// function calls it internally, it's the arguments part of the function
functionArgs: function( fn ) {
var args,
@@ -2010,10 +1858,11 @@ QUnit.jsDump = (function() {
return "";
}
- args = new Array(l);
+ args = new Array( l );
while ( l-- ) {
+
// 97 is 'a'
- args[l] = String.fromCharCode(97+l);
+ args[ l ] = String.fromCharCode( 97 + l );
}
return " " + args.join( ", " ) + " ";
},
@@ -2037,176 +1886,1943 @@ QUnit.jsDump = (function() {
multiline: true
};
- return jsDump;
+ return dump;
}());
-// from jquery.js
-function inArray( elem, array ) {
- if ( array.indexOf ) {
- return array.indexOf( elem );
- }
+// back compat
+QUnit.jsDump = QUnit.dump;
- for ( var i = 0, length = array.length; i < length; i++ ) {
- if ( array[ i ] === elem ) {
- return i;
+// For browser, export only select globals
+if ( typeof window !== "undefined" ) {
+
+ // Deprecated
+ // Extend assert methods to QUnit and Global scope through Backwards compatibility
+ (function() {
+ var i,
+ assertions = Assert.prototype;
+
+ function applyCurrent( current ) {
+ return function() {
+ var assert = new Assert( QUnit.config.current );
+ current.apply( assert, arguments );
+ };
}
- }
- return -1;
+ for ( i in assertions ) {
+ QUnit[ i ] = applyCurrent( assertions[ i ] );
+ }
+ })();
+
+ (function() {
+ var i, l,
+ keys = [
+ "test",
+ "module",
+ "expect",
+ "asyncTest",
+ "start",
+ "stop",
+ "ok",
+ "notOk",
+ "equal",
+ "notEqual",
+ "propEqual",
+ "notPropEqual",
+ "deepEqual",
+ "notDeepEqual",
+ "strictEqual",
+ "notStrictEqual",
+ "throws"
+ ];
+
+ for ( i = 0, l = keys.length; i < l; i++ ) {
+ window[ keys[ i ] ] = QUnit[ keys[ i ] ];
+ }
+ })();
+
+ window.QUnit = QUnit;
}
+// For nodejs
+if ( typeof module !== "undefined" && module && module.exports ) {
+ module.exports = QUnit;
+
+ // For consistency with CommonJS environments' exports
+ module.exports.QUnit = QUnit;
+}
+
+// For CommonJS with exports, but without module.exports, like Rhino
+if ( typeof exports !== "undefined" && exports ) {
+ exports.QUnit = QUnit;
+}
+
+if ( typeof define === "function" && define.amd ) {
+ define( function() {
+ return QUnit;
+ } );
+ QUnit.config.autostart = false;
+}
+
+// Get a reference to the global object, like window in browsers
+}( (function() {
+ return this;
+})() ));
+
+/*istanbul ignore next */
+// jscs:disable maximumLineLength
/*
- * Javascript Diff Algorithm
- * By John Resig (http://ejohn.org/)
- * Modified by Chu Alan "sprite"
+ * This file is a modified version of google-diff-match-patch's JavaScript implementation
+ * (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js),
+ * modifications are licensed as more fully set forth in LICENSE.txt.
*
- * Released under the MIT license.
+ * The original source of google-diff-match-patch is attributable and licensed as follows:
+ *
+ * Copyright 2006 Google Inc.
+ * http://code.google.com/p/google-diff-match-patch/
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*
* More Info:
- * http://ejohn.org/projects/javascript-diff-algorithm/
+ * https://code.google.com/p/google-diff-match-patch/
*
* Usage: QUnit.diff(expected, actual)
*
- * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick brown fox jumped jumps over"
+ * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) === "the quick brown fox jumps ed 0; i-- ) {
- if ( n[i].text != null && n[ i - 1 ].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null &&
- n[ i - 1 ] == o[ n[i].row - 1 ]) {
+ /**
+ * Find the differences between two texts. Simplifies the problem by stripping
+ * any common prefix or suffix off the texts before diffing.
+ * @param {string} text1 Old string to be diffed.
+ * @param {string} text2 New string to be diffed.
+ * @param {boolean=} optChecklines Optional speedup flag. If present and false,
+ * then don't run a line-level diff first to identify the changed areas.
+ * Defaults to true, which does a faster, slightly less optimal diff.
+ * @param {number} optDeadline Optional time when the diff should be complete
+ * by. Used internally for recursive calls. Users should set DiffTimeout
+ * instead.
+ * @return {!Array.} Array of diff tuples.
+ */
+ DiffMatchPatch.prototype.DiffMain = function( text1, text2, optChecklines, optDeadline ) {
+ var deadline, checklines, commonlength,
+ commonprefix, commonsuffix, diffs;
+ // Set a deadline by which time the diff must be complete.
+ if ( typeof optDeadline === "undefined" ) {
+ if ( this.DiffTimeout <= 0 ) {
+ optDeadline = Number.MAX_VALUE;
+ } else {
+ optDeadline = ( new Date() ).getTime() + this.DiffTimeout * 1000;
+ }
+ }
+ deadline = optDeadline;
- n[ i - 1 ] = {
- text: n[ i - 1 ],
- row: n[i].row - 1
- };
- o[ n[i].row - 1 ] = {
- text: o[ n[i].row - 1 ],
- row: i - 1
- };
- }
- }
+ // Check for null inputs.
+ if ( text1 === null || text2 === null ) {
+ throw new Error( "Null input. (DiffMain)" );
+ }
- return {
- o: o,
- n: n
- };
+ // Check for equality (speedup).
+ if ( text1 === text2 ) {
+ if ( text1 ) {
+ return [
+ [ DIFF_EQUAL, text1 ]
+ ];
+ }
+ return [];
+ }
+
+ if ( typeof optChecklines === "undefined" ) {
+ optChecklines = true;
+ }
+
+ checklines = optChecklines;
+
+ // Trim off common prefix (speedup).
+ commonlength = this.diffCommonPrefix( text1, text2 );
+ commonprefix = text1.substring( 0, commonlength );
+ text1 = text1.substring( commonlength );
+ text2 = text2.substring( commonlength );
+
+ // Trim off common suffix (speedup).
+ /////////
+ commonlength = this.diffCommonSuffix( text1, text2 );
+ commonsuffix = text1.substring( text1.length - commonlength );
+ text1 = text1.substring( 0, text1.length - commonlength );
+ text2 = text2.substring( 0, text2.length - commonlength );
+
+ // Compute the diff on the middle block.
+ diffs = this.diffCompute( text1, text2, checklines, deadline );
+
+ // Restore the prefix and suffix.
+ if ( commonprefix ) {
+ diffs.unshift( [ DIFF_EQUAL, commonprefix ] );
+ }
+ if ( commonsuffix ) {
+ diffs.push( [ DIFF_EQUAL, commonsuffix ] );
+ }
+ this.diffCleanupMerge( diffs );
+ return diffs;
+ };
+
+ /**
+ * Reduce the number of edits by eliminating operationally trivial equalities.
+ * @param {!Array.} diffs Array of diff tuples.
+ */
+ DiffMatchPatch.prototype.diffCleanupEfficiency = function( diffs ) {
+ var changes, equalities, equalitiesLength, lastequality,
+ pointer, preIns, preDel, postIns, postDel;
+ changes = false;
+ equalities = []; // Stack of indices where equalities are found.
+ equalitiesLength = 0; // Keeping our own length var is faster in JS.
+ /** @type {?string} */
+ lastequality = null;
+ // Always equal to diffs[equalities[equalitiesLength - 1]][1]
+ pointer = 0; // Index of current position.
+ // Is there an insertion operation before the last equality.
+ preIns = false;
+ // Is there a deletion operation before the last equality.
+ preDel = false;
+ // Is there an insertion operation after the last equality.
+ postIns = false;
+ // Is there a deletion operation after the last equality.
+ postDel = false;
+ while ( pointer < diffs.length ) {
+ if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { // Equality found.
+ if ( diffs[ pointer ][ 1 ].length < this.DiffEditCost && ( postIns || postDel ) ) {
+ // Candidate found.
+ equalities[ equalitiesLength++ ] = pointer;
+ preIns = postIns;
+ preDel = postDel;
+ lastequality = diffs[ pointer ][ 1 ];
+ } else {
+ // Not a candidate, and can never become one.
+ equalitiesLength = 0;
+ lastequality = null;
+ }
+ postIns = postDel = false;
+ } else { // An insertion or deletion.
+ if ( diffs[ pointer ][ 0 ] === DIFF_DELETE ) {
+ postDel = true;
+ } else {
+ postIns = true;
+ }
+ /*
+ * Five types to be split:
+ * A BXYC D
+ * A XC D
+ * A BXC
+ * AXC D
+ * A BXC
+ */
+ if ( lastequality && ( ( preIns && preDel && postIns && postDel ) ||
+ ( ( lastequality.length < this.DiffEditCost / 2 ) &&
+ ( preIns + preDel + postIns + postDel ) === 3 ) ) ) {
+ // Duplicate record.
+ diffs.splice( equalities[equalitiesLength - 1], 0, [ DIFF_DELETE, lastequality ] );
+ // Change second copy to insert.
+ diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
+ equalitiesLength--; // Throw away the equality we just deleted;
+ lastequality = null;
+ if (preIns && preDel) {
+ // No changes made which could affect previous entry, keep going.
+ postIns = postDel = true;
+ equalitiesLength = 0;
+ } else {
+ equalitiesLength--; // Throw away the previous equality.
+ pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1;
+ postIns = postDel = false;
+ }
+ changes = true;
+ }
+ }
+ pointer++;
+ }
+
+ if ( changes ) {
+ this.diffCleanupMerge( diffs );
+ }
+ };
+
+ /**
+ * Convert a diff array into a pretty HTML report.
+ * @param {!Array.} diffs Array of diff tuples.
+ * @param {integer} string to be beautified.
+ * @return {string} HTML representation.
+ */
+ DiffMatchPatch.prototype.diffPrettyHtml = function( diffs ) {
+ var op, data, x, html = [];
+ for ( x = 0; x < diffs.length; x++ ) {
+ op = diffs[x][0]; // Operation (insert, delete, equal)
+ data = diffs[x][1]; // Text of change.
+ switch ( op ) {
+ case DIFF_INSERT:
+ html[x] = "" + data + " ";
+ break;
+ case DIFF_DELETE:
+ html[x] = "" + data + "";
+ break;
+ case DIFF_EQUAL:
+ html[x] = "" + data + " ";
+ break;
+ }
+ }
+ return html.join("");
+ };
+
+ /**
+ * Determine the common prefix of two strings.
+ * @param {string} text1 First string.
+ * @param {string} text2 Second string.
+ * @return {number} The number of characters common to the start of each
+ * string.
+ */
+ DiffMatchPatch.prototype.diffCommonPrefix = function( text1, text2 ) {
+ var pointermid, pointermax, pointermin, pointerstart;
+ // Quick check for common null cases.
+ if ( !text1 || !text2 || text1.charAt(0) !== text2.charAt(0) ) {
+ return 0;
+ }
+ // Binary search.
+ // Performance analysis: http://neil.fraser.name/news/2007/10/09/
+ pointermin = 0;
+ pointermax = Math.min( text1.length, text2.length );
+ pointermid = pointermax;
+ pointerstart = 0;
+ while ( pointermin < pointermid ) {
+ if ( text1.substring( pointerstart, pointermid ) === text2.substring( pointerstart, pointermid ) ) {
+ pointermin = pointermid;
+ pointerstart = pointermin;
+ } else {
+ pointermax = pointermid;
+ }
+ pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
+ }
+ return pointermid;
+ };
+
+ /**
+ * Determine the common suffix of two strings.
+ * @param {string} text1 First string.
+ * @param {string} text2 Second string.
+ * @return {number} The number of characters common to the end of each string.
+ */
+ DiffMatchPatch.prototype.diffCommonSuffix = function( text1, text2 ) {
+ var pointermid, pointermax, pointermin, pointerend;
+ // Quick check for common null cases.
+ if (!text1 || !text2 || text1.charAt(text1.length - 1) !== text2.charAt(text2.length - 1)) {
+ return 0;
+ }
+ // Binary search.
+ // Performance analysis: http://neil.fraser.name/news/2007/10/09/
+ pointermin = 0;
+ pointermax = Math.min(text1.length, text2.length);
+ pointermid = pointermax;
+ pointerend = 0;
+ while ( pointermin < pointermid ) {
+ if (text1.substring( text1.length - pointermid, text1.length - pointerend ) ===
+ text2.substring( text2.length - pointermid, text2.length - pointerend ) ) {
+ pointermin = pointermid;
+ pointerend = pointermin;
+ } else {
+ pointermax = pointermid;
+ }
+ pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
+ }
+ return pointermid;
+ };
+
+ /**
+ * Find the differences between two texts. Assumes that the texts do not
+ * have any common prefix or suffix.
+ * @param {string} text1 Old string to be diffed.
+ * @param {string} text2 New string to be diffed.
+ * @param {boolean} checklines Speedup flag. If false, then don't run a
+ * line-level diff first to identify the changed areas.
+ * If true, then run a faster, slightly less optimal diff.
+ * @param {number} deadline Time when the diff should be complete by.
+ * @return {!Array.} Array of diff tuples.
+ * @private
+ */
+ DiffMatchPatch.prototype.diffCompute = function( text1, text2, checklines, deadline ) {
+ var diffs, longtext, shorttext, i, hm,
+ text1A, text2A, text1B, text2B,
+ midCommon, diffsA, diffsB;
+
+ if ( !text1 ) {
+ // Just add some text (speedup).
+ return [
+ [ DIFF_INSERT, text2 ]
+ ];
+ }
+
+ if (!text2) {
+ // Just delete some text (speedup).
+ return [
+ [ DIFF_DELETE, text1 ]
+ ];
+ }
+
+ longtext = text1.length > text2.length ? text1 : text2;
+ shorttext = text1.length > text2.length ? text2 : text1;
+ i = longtext.indexOf( shorttext );
+ if ( i !== -1 ) {
+ // Shorter text is inside the longer text (speedup).
+ diffs = [
+ [ DIFF_INSERT, longtext.substring( 0, i ) ],
+ [ DIFF_EQUAL, shorttext ],
+ [ DIFF_INSERT, longtext.substring( i + shorttext.length ) ]
+ ];
+ // Swap insertions for deletions if diff is reversed.
+ if ( text1.length > text2.length ) {
+ diffs[0][0] = diffs[2][0] = DIFF_DELETE;
+ }
+ return diffs;
+ }
+
+ if ( shorttext.length === 1 ) {
+ // Single character string.
+ // After the previous speedup, the character can't be an equality.
+ return [
+ [ DIFF_DELETE, text1 ],
+ [ DIFF_INSERT, text2 ]
+ ];
+ }
+
+ // Check to see if the problem can be split in two.
+ hm = this.diffHalfMatch(text1, text2);
+ if (hm) {
+ // A half-match was found, sort out the return data.
+ text1A = hm[0];
+ text1B = hm[1];
+ text2A = hm[2];
+ text2B = hm[3];
+ midCommon = hm[4];
+ // Send both pairs off for separate processing.
+ diffsA = this.DiffMain(text1A, text2A, checklines, deadline);
+ diffsB = this.DiffMain(text1B, text2B, checklines, deadline);
+ // Merge the results.
+ return diffsA.concat([
+ [ DIFF_EQUAL, midCommon ]
+ ], diffsB);
+ }
+
+ if (checklines && text1.length > 100 && text2.length > 100) {
+ return this.diffLineMode(text1, text2, deadline);
+ }
+
+ return this.diffBisect(text1, text2, deadline);
+ };
+
+ /**
+ * Do the two texts share a substring which is at least half the length of the
+ * longer text?
+ * This speedup can produce non-minimal diffs.
+ * @param {string} text1 First string.
+ * @param {string} text2 Second string.
+ * @return {Array.} Five element Array, containing the prefix of
+ * text1, the suffix of text1, the prefix of text2, the suffix of
+ * text2 and the common middle. Or null if there was no match.
+ * @private
+ */
+ DiffMatchPatch.prototype.diffHalfMatch = function(text1, text2) {
+ var longtext, shorttext, dmp,
+ text1A, text2B, text2A, text1B, midCommon,
+ hm1, hm2, hm;
+ if (this.DiffTimeout <= 0) {
+ // Don't risk returning a non-optimal diff if we have unlimited time.
+ return null;
+ }
+ longtext = text1.length > text2.length ? text1 : text2;
+ shorttext = text1.length > text2.length ? text2 : text1;
+ if (longtext.length < 4 || shorttext.length * 2 < longtext.length) {
+ return null; // Pointless.
+ }
+ dmp = this; // 'this' becomes 'window' in a closure.
+
+ /**
+ * Does a substring of shorttext exist within longtext such that the substring
+ * is at least half the length of longtext?
+ * Closure, but does not reference any external variables.
+ * @param {string} longtext Longer string.
+ * @param {string} shorttext Shorter string.
+ * @param {number} i Start index of quarter length substring within longtext.
+ * @return {Array.} Five element Array, containing the prefix of
+ * longtext, the suffix of longtext, the prefix of shorttext, the suffix
+ * of shorttext and the common middle. Or null if there was no match.
+ * @private
+ */
+ function diffHalfMatchI(longtext, shorttext, i) {
+ var seed, j, bestCommon, prefixLength, suffixLength,
+ bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB;
+ // Start with a 1/4 length substring at position i as a seed.
+ seed = longtext.substring(i, i + Math.floor(longtext.length / 4));
+ j = -1;
+ bestCommon = "";
+ while ((j = shorttext.indexOf(seed, j + 1)) !== -1) {
+ prefixLength = dmp.diffCommonPrefix(longtext.substring(i),
+ shorttext.substring(j));
+ suffixLength = dmp.diffCommonSuffix(longtext.substring(0, i),
+ shorttext.substring(0, j));
+ if (bestCommon.length < suffixLength + prefixLength) {
+ bestCommon = shorttext.substring(j - suffixLength, j) +
+ shorttext.substring(j, j + prefixLength);
+ bestLongtextA = longtext.substring(0, i - suffixLength);
+ bestLongtextB = longtext.substring(i + prefixLength);
+ bestShorttextA = shorttext.substring(0, j - suffixLength);
+ bestShorttextB = shorttext.substring(j + prefixLength);
+ }
+ }
+ if (bestCommon.length * 2 >= longtext.length) {
+ return [ bestLongtextA, bestLongtextB,
+ bestShorttextA, bestShorttextB, bestCommon
+ ];
+ } else {
+ return null;
+ }
+ }
+
+ // First check if the second quarter is the seed for a half-match.
+ hm1 = diffHalfMatchI(longtext, shorttext,
+ Math.ceil(longtext.length / 4));
+ // Check again based on the third quarter.
+ hm2 = diffHalfMatchI(longtext, shorttext,
+ Math.ceil(longtext.length / 2));
+ if (!hm1 && !hm2) {
+ return null;
+ } else if (!hm2) {
+ hm = hm1;
+ } else if (!hm1) {
+ hm = hm2;
+ } else {
+ // Both matched. Select the longest.
+ hm = hm1[4].length > hm2[4].length ? hm1 : hm2;
+ }
+
+ // A half-match was found, sort out the return data.
+ text1A, text1B, text2A, text2B;
+ if (text1.length > text2.length) {
+ text1A = hm[0];
+ text1B = hm[1];
+ text2A = hm[2];
+ text2B = hm[3];
+ } else {
+ text2A = hm[0];
+ text2B = hm[1];
+ text1A = hm[2];
+ text1B = hm[3];
+ }
+ midCommon = hm[4];
+ return [ text1A, text1B, text2A, text2B, midCommon ];
+ };
+
+ /**
+ * Do a quick line-level diff on both strings, then rediff the parts for
+ * greater accuracy.
+ * This speedup can produce non-minimal diffs.
+ * @param {string} text1 Old string to be diffed.
+ * @param {string} text2 New string to be diffed.
+ * @param {number} deadline Time when the diff should be complete by.
+ * @return {!Array.} Array of diff tuples.
+ * @private
+ */
+ DiffMatchPatch.prototype.diffLineMode = function(text1, text2, deadline) {
+ var a, diffs, linearray, pointer, countInsert,
+ countDelete, textInsert, textDelete, j;
+ // Scan the text on a line-by-line basis first.
+ a = this.diffLinesToChars(text1, text2);
+ text1 = a.chars1;
+ text2 = a.chars2;
+ linearray = a.lineArray;
+
+ diffs = this.DiffMain(text1, text2, false, deadline);
+
+ // Convert the diff back to original text.
+ this.diffCharsToLines(diffs, linearray);
+ // Eliminate freak matches (e.g. blank lines)
+ this.diffCleanupSemantic(diffs);
+
+ // Rediff any replacement blocks, this time character-by-character.
+ // Add a dummy entry at the end.
+ diffs.push( [ DIFF_EQUAL, "" ] );
+ pointer = 0;
+ countDelete = 0;
+ countInsert = 0;
+ textDelete = "";
+ textInsert = "";
+ while (pointer < diffs.length) {
+ switch ( diffs[pointer][0] ) {
+ case DIFF_INSERT:
+ countInsert++;
+ textInsert += diffs[pointer][1];
+ break;
+ case DIFF_DELETE:
+ countDelete++;
+ textDelete += diffs[pointer][1];
+ break;
+ case DIFF_EQUAL:
+ // Upon reaching an equality, check for prior redundancies.
+ if (countDelete >= 1 && countInsert >= 1) {
+ // Delete the offending records and add the merged ones.
+ diffs.splice(pointer - countDelete - countInsert,
+ countDelete + countInsert);
+ pointer = pointer - countDelete - countInsert;
+ a = this.DiffMain(textDelete, textInsert, false, deadline);
+ for (j = a.length - 1; j >= 0; j--) {
+ diffs.splice( pointer, 0, a[j] );
+ }
+ pointer = pointer + a.length;
+ }
+ countInsert = 0;
+ countDelete = 0;
+ textDelete = "";
+ textInsert = "";
+ break;
+ }
+ pointer++;
+ }
+ diffs.pop(); // Remove the dummy entry at the end.
+
+ return diffs;
+ };
+
+ /**
+ * Find the 'middle snake' of a diff, split the problem in two
+ * and return the recursively constructed diff.
+ * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
+ * @param {string} text1 Old string to be diffed.
+ * @param {string} text2 New string to be diffed.
+ * @param {number} deadline Time at which to bail if not yet complete.
+ * @return {!Array.} Array of diff tuples.
+ * @private
+ */
+ DiffMatchPatch.prototype.diffBisect = function(text1, text2, deadline) {
+ var text1Length, text2Length, maxD, vOffset, vLength,
+ v1, v2, x, delta, front, k1start, k1end, k2start,
+ k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2;
+ // Cache the text lengths to prevent multiple calls.
+ text1Length = text1.length;
+ text2Length = text2.length;
+ maxD = Math.ceil((text1Length + text2Length) / 2);
+ vOffset = maxD;
+ vLength = 2 * maxD;
+ v1 = new Array(vLength);
+ v2 = new Array(vLength);
+ // Setting all elements to -1 is faster in Chrome & Firefox than mixing
+ // integers and undefined.
+ for (x = 0; x < vLength; x++) {
+ v1[x] = -1;
+ v2[x] = -1;
+ }
+ v1[vOffset + 1] = 0;
+ v2[vOffset + 1] = 0;
+ delta = text1Length - text2Length;
+ // If the total number of characters is odd, then the front path will collide
+ // with the reverse path.
+ front = (delta % 2 !== 0);
+ // Offsets for start and end of k loop.
+ // Prevents mapping of space beyond the grid.
+ k1start = 0;
+ k1end = 0;
+ k2start = 0;
+ k2end = 0;
+ for (d = 0; d < maxD; d++) {
+ // Bail out if deadline is reached.
+ if ((new Date()).getTime() > deadline) {
+ break;
+ }
+
+ // Walk the front path one step.
+ for (k1 = -d + k1start; k1 <= d - k1end; k1 += 2) {
+ k1Offset = vOffset + k1;
+ if ( k1 === -d || ( k1 !== d && v1[ k1Offset - 1 ] < v1[ k1Offset + 1 ] ) ) {
+ x1 = v1[k1Offset + 1];
+ } else {
+ x1 = v1[k1Offset - 1] + 1;
+ }
+ y1 = x1 - k1;
+ while (x1 < text1Length && y1 < text2Length &&
+ text1.charAt(x1) === text2.charAt(y1)) {
+ x1++;
+ y1++;
+ }
+ v1[k1Offset] = x1;
+ if (x1 > text1Length) {
+ // Ran off the right of the graph.
+ k1end += 2;
+ } else if (y1 > text2Length) {
+ // Ran off the bottom of the graph.
+ k1start += 2;
+ } else if (front) {
+ k2Offset = vOffset + delta - k1;
+ if (k2Offset >= 0 && k2Offset < vLength && v2[k2Offset] !== -1) {
+ // Mirror x2 onto top-left coordinate system.
+ x2 = text1Length - v2[k2Offset];
+ if (x1 >= x2) {
+ // Overlap detected.
+ return this.diffBisectSplit(text1, text2, x1, y1, deadline);
+ }
+ }
+ }
+ }
+
+ // Walk the reverse path one step.
+ for (k2 = -d + k2start; k2 <= d - k2end; k2 += 2) {
+ k2Offset = vOffset + k2;
+ if ( k2 === -d || (k2 !== d && v2[ k2Offset - 1 ] < v2[ k2Offset + 1 ] ) ) {
+ x2 = v2[k2Offset + 1];
+ } else {
+ x2 = v2[k2Offset - 1] + 1;
+ }
+ y2 = x2 - k2;
+ while (x2 < text1Length && y2 < text2Length &&
+ text1.charAt(text1Length - x2 - 1) ===
+ text2.charAt(text2Length - y2 - 1)) {
+ x2++;
+ y2++;
+ }
+ v2[k2Offset] = x2;
+ if (x2 > text1Length) {
+ // Ran off the left of the graph.
+ k2end += 2;
+ } else if (y2 > text2Length) {
+ // Ran off the top of the graph.
+ k2start += 2;
+ } else if (!front) {
+ k1Offset = vOffset + delta - k2;
+ if (k1Offset >= 0 && k1Offset < vLength && v1[k1Offset] !== -1) {
+ x1 = v1[k1Offset];
+ y1 = vOffset + x1 - k1Offset;
+ // Mirror x2 onto top-left coordinate system.
+ x2 = text1Length - x2;
+ if (x1 >= x2) {
+ // Overlap detected.
+ return this.diffBisectSplit(text1, text2, x1, y1, deadline);
+ }
+ }
+ }
+ }
+ }
+ // Diff took too long and hit the deadline or
+ // number of diffs equals number of characters, no commonality at all.
+ return [
+ [ DIFF_DELETE, text1 ],
+ [ DIFF_INSERT, text2 ]
+ ];
+ };
+
+ /**
+ * Given the location of the 'middle snake', split the diff in two parts
+ * and recurse.
+ * @param {string} text1 Old string to be diffed.
+ * @param {string} text2 New string to be diffed.
+ * @param {number} x Index of split point in text1.
+ * @param {number} y Index of split point in text2.
+ * @param {number} deadline Time at which to bail if not yet complete.
+ * @return {!Array.} Array of diff tuples.
+ * @private
+ */
+ DiffMatchPatch.prototype.diffBisectSplit = function( text1, text2, x, y, deadline ) {
+ var text1a, text1b, text2a, text2b, diffs, diffsb;
+ text1a = text1.substring(0, x);
+ text2a = text2.substring(0, y);
+ text1b = text1.substring(x);
+ text2b = text2.substring(y);
+
+ // Compute both diffs serially.
+ diffs = this.DiffMain(text1a, text2a, false, deadline);
+ diffsb = this.DiffMain(text1b, text2b, false, deadline);
+
+ return diffs.concat(diffsb);
+ };
+
+ /**
+ * Reduce the number of edits by eliminating semantically trivial equalities.
+ * @param {!Array.} diffs Array of diff tuples.
+ */
+ DiffMatchPatch.prototype.diffCleanupSemantic = function(diffs) {
+ var changes, equalities, equalitiesLength, lastequality,
+ pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1,
+ lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2;
+ changes = false;
+ equalities = []; // Stack of indices where equalities are found.
+ equalitiesLength = 0; // Keeping our own length var is faster in JS.
+ /** @type {?string} */
+ lastequality = null;
+ // Always equal to diffs[equalities[equalitiesLength - 1]][1]
+ pointer = 0; // Index of current position.
+ // Number of characters that changed prior to the equality.
+ lengthInsertions1 = 0;
+ lengthDeletions1 = 0;
+ // Number of characters that changed after the equality.
+ lengthInsertions2 = 0;
+ lengthDeletions2 = 0;
+ while (pointer < diffs.length) {
+ if (diffs[pointer][0] === DIFF_EQUAL) { // Equality found.
+ equalities[equalitiesLength++] = pointer;
+ lengthInsertions1 = lengthInsertions2;
+ lengthDeletions1 = lengthDeletions2;
+ lengthInsertions2 = 0;
+ lengthDeletions2 = 0;
+ lastequality = diffs[pointer][1];
+ } else { // An insertion or deletion.
+ if (diffs[pointer][0] === DIFF_INSERT) {
+ lengthInsertions2 += diffs[pointer][1].length;
+ } else {
+ lengthDeletions2 += diffs[pointer][1].length;
+ }
+ // Eliminate an equality that is smaller or equal to the edits on both
+ // sides of it.
+ if (lastequality && (lastequality.length <=
+ Math.max(lengthInsertions1, lengthDeletions1)) &&
+ (lastequality.length <= Math.max(lengthInsertions2,
+ lengthDeletions2))) {
+ // Duplicate record.
+ diffs.splice( equalities[ equalitiesLength - 1 ], 0, [ DIFF_DELETE, lastequality ] );
+ // Change second copy to insert.
+ diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT;
+ // Throw away the equality we just deleted.
+ equalitiesLength--;
+ // Throw away the previous equality (it needs to be reevaluated).
+ equalitiesLength--;
+ pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1;
+ lengthInsertions1 = 0; // Reset the counters.
+ lengthDeletions1 = 0;
+ lengthInsertions2 = 0;
+ lengthDeletions2 = 0;
+ lastequality = null;
+ changes = true;
+ }
+ }
+ pointer++;
+ }
+
+ // Normalize the diff.
+ if (changes) {
+ this.diffCleanupMerge(diffs);
+ }
+
+ // Find any overlaps between deletions and insertions.
+ // e.g: abcxxxxxxdef
+ // -> abcxxxdef
+ // e.g: xxxabcdefxxx
+ // -> def xxxabc
+ // Only extract an overlap if it is as big as the edit ahead or behind it.
+ pointer = 1;
+ while (pointer < diffs.length) {
+ if (diffs[pointer - 1][0] === DIFF_DELETE &&
+ diffs[pointer][0] === DIFF_INSERT) {
+ deletion = diffs[pointer - 1][1];
+ insertion = diffs[pointer][1];
+ overlapLength1 = this.diffCommonOverlap(deletion, insertion);
+ overlapLength2 = this.diffCommonOverlap(insertion, deletion);
+ if (overlapLength1 >= overlapLength2) {
+ if (overlapLength1 >= deletion.length / 2 ||
+ overlapLength1 >= insertion.length / 2) {
+ // Overlap found. Insert an equality and trim the surrounding edits.
+ diffs.splice( pointer, 0, [ DIFF_EQUAL, insertion.substring( 0, overlapLength1 ) ] );
+ diffs[pointer - 1][1] =
+ deletion.substring(0, deletion.length - overlapLength1);
+ diffs[pointer + 1][1] = insertion.substring(overlapLength1);
+ pointer++;
+ }
+ } else {
+ if (overlapLength2 >= deletion.length / 2 ||
+ overlapLength2 >= insertion.length / 2) {
+ // Reverse overlap found.
+ // Insert an equality and swap and trim the surrounding edits.
+ diffs.splice( pointer, 0, [ DIFF_EQUAL, deletion.substring( 0, overlapLength2 ) ] );
+ diffs[pointer - 1][0] = DIFF_INSERT;
+ diffs[pointer - 1][1] =
+ insertion.substring(0, insertion.length - overlapLength2);
+ diffs[pointer + 1][0] = DIFF_DELETE;
+ diffs[pointer + 1][1] =
+ deletion.substring(overlapLength2);
+ pointer++;
+ }
+ }
+ pointer++;
+ }
+ pointer++;
+ }
+ };
+
+ /**
+ * Determine if the suffix of one string is the prefix of another.
+ * @param {string} text1 First string.
+ * @param {string} text2 Second string.
+ * @return {number} The number of characters common to the end of the first
+ * string and the start of the second string.
+ * @private
+ */
+ DiffMatchPatch.prototype.diffCommonOverlap = function(text1, text2) {
+ var text1Length, text2Length, textLength,
+ best, length, pattern, found;
+ // Cache the text lengths to prevent multiple calls.
+ text1Length = text1.length;
+ text2Length = text2.length;
+ // Eliminate the null case.
+ if (text1Length === 0 || text2Length === 0) {
+ return 0;
+ }
+ // Truncate the longer string.
+ if (text1Length > text2Length) {
+ text1 = text1.substring(text1Length - text2Length);
+ } else if (text1Length < text2Length) {
+ text2 = text2.substring(0, text1Length);
+ }
+ textLength = Math.min(text1Length, text2Length);
+ // Quick check for the worst case.
+ if (text1 === text2) {
+ return textLength;
+ }
+
+ // Start by looking for a single character match
+ // and increase length until no match is found.
+ // Performance analysis: http://neil.fraser.name/news/2010/11/04/
+ best = 0;
+ length = 1;
+ while (true) {
+ pattern = text1.substring(textLength - length);
+ found = text2.indexOf(pattern);
+ if (found === -1) {
+ return best;
+ }
+ length += found;
+ if (found === 0 || text1.substring(textLength - length) ===
+ text2.substring(0, length)) {
+ best = length;
+ length++;
+ }
+ }
+ };
+
+ /**
+ * Split two texts into an array of strings. Reduce the texts to a string of
+ * hashes where each Unicode character represents one line.
+ * @param {string} text1 First string.
+ * @param {string} text2 Second string.
+ * @return {{chars1: string, chars2: string, lineArray: !Array.}}
+ * An object containing the encoded text1, the encoded text2 and
+ * the array of unique strings.
+ * The zeroth element of the array of unique strings is intentionally blank.
+ * @private
+ */
+ DiffMatchPatch.prototype.diffLinesToChars = function(text1, text2) {
+ var lineArray, lineHash, chars1, chars2;
+ lineArray = []; // e.g. lineArray[4] === 'Hello\n'
+ lineHash = {}; // e.g. lineHash['Hello\n'] === 4
+
+ // '\x00' is a valid character, but various debuggers don't like it.
+ // So we'll insert a junk entry to avoid generating a null character.
+ lineArray[0] = "";
+
+ /**
+ * Split a text into an array of strings. Reduce the texts to a string of
+ * hashes where each Unicode character represents one line.
+ * Modifies linearray and linehash through being a closure.
+ * @param {string} text String to encode.
+ * @return {string} Encoded string.
+ * @private
+ */
+ function diffLinesToCharsMunge(text) {
+ var chars, lineStart, lineEnd, lineArrayLength, line;
+ chars = "";
+ // Walk the text, pulling out a substring for each line.
+ // text.split('\n') would would temporarily double our memory footprint.
+ // Modifying text would create many large strings to garbage collect.
+ lineStart = 0;
+ lineEnd = -1;
+ // Keeping our own length variable is faster than looking it up.
+ lineArrayLength = lineArray.length;
+ while (lineEnd < text.length - 1) {
+ lineEnd = text.indexOf("\n", lineStart);
+ if (lineEnd === -1) {
+ lineEnd = text.length - 1;
+ }
+ line = text.substring(lineStart, lineEnd + 1);
+ lineStart = lineEnd + 1;
+
+ if (lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) :
+ (lineHash[line] !== undefined)) {
+ chars += String.fromCharCode( lineHash[ line ] );
+ } else {
+ chars += String.fromCharCode(lineArrayLength);
+ lineHash[line] = lineArrayLength;
+ lineArray[lineArrayLength++] = line;
+ }
+ }
+ return chars;
+ }
+
+ chars1 = diffLinesToCharsMunge(text1);
+ chars2 = diffLinesToCharsMunge(text2);
+ return {
+ chars1: chars1,
+ chars2: chars2,
+ lineArray: lineArray
+ };
+ };
+
+ /**
+ * Rehydrate the text in a diff from a string of line hashes to real lines of
+ * text.
+ * @param {!Array.} diffs Array of diff tuples.
+ * @param {!Array.} lineArray Array of unique strings.
+ * @private
+ */
+ DiffMatchPatch.prototype.diffCharsToLines = function( diffs, lineArray ) {
+ var x, chars, text, y;
+ for ( x = 0; x < diffs.length; x++ ) {
+ chars = diffs[x][1];
+ text = [];
+ for ( y = 0; y < chars.length; y++ ) {
+ text[y] = lineArray[chars.charCodeAt(y)];
+ }
+ diffs[x][1] = text.join("");
+ }
+ };
+
+ /**
+ * Reorder and merge like edit sections. Merge equalities.
+ * Any edit section can move as long as it doesn't cross an equality.
+ * @param {!Array.} diffs Array of diff tuples.
+ */
+ DiffMatchPatch.prototype.diffCleanupMerge = function(diffs) {
+ var pointer, countDelete, countInsert, textInsert, textDelete,
+ commonlength, changes;
+ diffs.push( [ DIFF_EQUAL, "" ] ); // Add a dummy entry at the end.
+ pointer = 0;
+ countDelete = 0;
+ countInsert = 0;
+ textDelete = "";
+ textInsert = "";
+ commonlength;
+ while (pointer < diffs.length) {
+ switch ( diffs[ pointer ][ 0 ] ) {
+ case DIFF_INSERT:
+ countInsert++;
+ textInsert += diffs[pointer][1];
+ pointer++;
+ break;
+ case DIFF_DELETE:
+ countDelete++;
+ textDelete += diffs[pointer][1];
+ pointer++;
+ break;
+ case DIFF_EQUAL:
+ // Upon reaching an equality, check for prior redundancies.
+ if (countDelete + countInsert > 1) {
+ if (countDelete !== 0 && countInsert !== 0) {
+ // Factor out any common prefixies.
+ commonlength = this.diffCommonPrefix(textInsert, textDelete);
+ if (commonlength !== 0) {
+ if ((pointer - countDelete - countInsert) > 0 &&
+ diffs[pointer - countDelete - countInsert - 1][0] ===
+ DIFF_EQUAL) {
+ diffs[pointer - countDelete - countInsert - 1][1] +=
+ textInsert.substring(0, commonlength);
+ } else {
+ diffs.splice( 0, 0, [ DIFF_EQUAL,
+ textInsert.substring( 0, commonlength )
+ ] );
+ pointer++;
+ }
+ textInsert = textInsert.substring(commonlength);
+ textDelete = textDelete.substring(commonlength);
+ }
+ // Factor out any common suffixies.
+ commonlength = this.diffCommonSuffix(textInsert, textDelete);
+ if (commonlength !== 0) {
+ diffs[pointer][1] = textInsert.substring(textInsert.length -
+ commonlength) + diffs[pointer][1];
+ textInsert = textInsert.substring(0, textInsert.length -
+ commonlength);
+ textDelete = textDelete.substring(0, textDelete.length -
+ commonlength);
+ }
+ }
+ // Delete the offending records and add the merged ones.
+ if (countDelete === 0) {
+ diffs.splice( pointer - countInsert,
+ countDelete + countInsert, [ DIFF_INSERT, textInsert ] );
+ } else if (countInsert === 0) {
+ diffs.splice( pointer - countDelete,
+ countDelete + countInsert, [ DIFF_DELETE, textDelete ] );
+ } else {
+ diffs.splice( pointer - countDelete - countInsert,
+ countDelete + countInsert, [ DIFF_DELETE, textDelete ], [ DIFF_INSERT, textInsert ] );
+ }
+ pointer = pointer - countDelete - countInsert +
+ (countDelete ? 1 : 0) + (countInsert ? 1 : 0) + 1;
+ } else if (pointer !== 0 && diffs[pointer - 1][0] === DIFF_EQUAL) {
+ // Merge this equality with the previous one.
+ diffs[pointer - 1][1] += diffs[pointer][1];
+ diffs.splice(pointer, 1);
+ } else {
+ pointer++;
+ }
+ countInsert = 0;
+ countDelete = 0;
+ textDelete = "";
+ textInsert = "";
+ break;
+ }
+ }
+ if (diffs[diffs.length - 1][1] === "") {
+ diffs.pop(); // Remove the dummy entry at the end.
+ }
+
+ // Second pass: look for single edits surrounded on both sides by equalities
+ // which can be shifted sideways to eliminate an equality.
+ // e.g: ABA C -> AB AC
+ changes = false;
+ pointer = 1;
+ // Intentionally ignore the first and last element (don't need checking).
+ while (pointer < diffs.length - 1) {
+ if (diffs[pointer - 1][0] === DIFF_EQUAL &&
+ diffs[pointer + 1][0] === DIFF_EQUAL) {
+ // This is a single edit surrounded by equalities.
+ if ( diffs[ pointer ][ 1 ].substring( diffs[ pointer ][ 1 ].length -
+ diffs[ pointer - 1 ][ 1 ].length ) === diffs[ pointer - 1 ][ 1 ] ) {
+ // Shift the edit over the previous equality.
+ diffs[pointer][1] = diffs[pointer - 1][1] +
+ diffs[pointer][1].substring(0, diffs[pointer][1].length -
+ diffs[pointer - 1][1].length);
+ diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1];
+ diffs.splice(pointer - 1, 1);
+ changes = true;
+ } else if ( diffs[ pointer ][ 1 ].substring( 0, diffs[ pointer + 1 ][ 1 ].length ) ===
+ diffs[ pointer + 1 ][ 1 ] ) {
+ // Shift the edit over the next equality.
+ diffs[pointer - 1][1] += diffs[pointer + 1][1];
+ diffs[pointer][1] =
+ diffs[pointer][1].substring(diffs[pointer + 1][1].length) +
+ diffs[pointer + 1][1];
+ diffs.splice(pointer + 1, 1);
+ changes = true;
+ }
+ }
+ pointer++;
+ }
+ // If shifts were made, the diff needs reordering and another shift sweep.
+ if (changes) {
+ this.diffCleanupMerge(diffs);
+ }
+ };
+
+ return function(o, n) {
+ var diff, output, text;
+ diff = new DiffMatchPatch();
+ output = diff.DiffMain(o, n);
+ //console.log(output);
+ diff.diffCleanupEfficiency(output);
+ text = diff.diffPrettyHtml(output);
+
+ return text;
+ };
+}());
+// jscs:enable
+
+(function() {
+
+// Deprecated QUnit.init - Ref #530
+// Re-initialize the configuration options
+QUnit.init = function() {
+ var tests, banner, result, qunit,
+ config = QUnit.config;
+
+ config.stats = { all: 0, bad: 0 };
+ config.moduleStats = { all: 0, bad: 0 };
+ config.started = 0;
+ config.updateRate = 1000;
+ config.blocking = false;
+ config.autostart = true;
+ config.autorun = false;
+ config.filter = "";
+ config.queue = [];
+
+ // Return on non-browser environments
+ // This is necessary to not break on node tests
+ if ( typeof window === "undefined" ) {
+ return;
}
- return function( o, n ) {
- o = o.replace( /\s+$/, "" );
- n = n.replace( /\s+$/, "" );
+ qunit = id( "qunit" );
+ if ( qunit ) {
+ qunit.innerHTML =
+ "" +
+ " " +
+ "
" +
+ " " +
+ " ";
+ }
- var i, pre,
- str = "",
- out = diff( o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/) ),
- oSpace = o.match(/\s+/g),
- nSpace = n.match(/\s+/g);
+ tests = id( "qunit-tests" );
+ banner = id( "qunit-banner" );
+ result = id( "qunit-testresult" );
- if ( oSpace == null ) {
- oSpace = [ " " ];
- }
- else {
- oSpace.push( " " );
- }
+ if ( tests ) {
+ tests.innerHTML = "";
+ }
- if ( nSpace == null ) {
- nSpace = [ " " ];
- }
- else {
- nSpace.push( " " );
- }
+ if ( banner ) {
+ banner.className = "";
+ }
- if ( out.n.length === 0 ) {
- for ( i = 0; i < out.o.length; i++ ) {
- str += "" + out.o[i] + oSpace[i] + "";
- }
- }
- else {
- if ( out.n[0].text == null ) {
- for ( n = 0; n < out.o.length && out.o[n].text == null; n++ ) {
- str += "" + out.o[n] + oSpace[n] + "";
- }
- }
+ if ( result ) {
+ result.parentNode.removeChild( result );
+ }
- for ( i = 0; i < out.n.length; i++ ) {
- if (out.n[i].text == null) {
- str += "" + out.n[i] + nSpace[i] + " ";
- }
- else {
- // `pre` initialized at top of scope
- pre = "";
+ if ( tests ) {
+ result = document.createElement( "p" );
+ result.id = "qunit-testresult";
+ result.className = "result";
+ tests.parentNode.insertBefore( result, tests );
+ result.innerHTML = "Running... ";
+ }
+};
- for ( n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) {
- pre += "" + out.o[n] + oSpace[n] + "";
- }
- str += " " + out.n[i].text + nSpace[i] + pre;
- }
- }
- }
-
- return str;
- };
-}());
-
-// for CommonJS environments, export everything
-if ( typeof exports !== "undefined" ) {
- extend( exports, QUnit.constructor.prototype );
+// Don't load the HTML Reporter on non-Browser environments
+if ( typeof window === "undefined" ) {
+ return;
}
-// get at whatever the global object is, like window in browsers
-}( (function() {return this;}.call()) ));
+var config = QUnit.config,
+ hasOwn = Object.prototype.hasOwnProperty,
+ defined = {
+ document: window.document !== undefined,
+ sessionStorage: (function() {
+ var x = "qunit-test-string";
+ try {
+ sessionStorage.setItem( x, x );
+ sessionStorage.removeItem( x );
+ return true;
+ } catch ( e ) {
+ return false;
+ }
+ }())
+ },
+ modulesList = [];
+
+/**
+* Escape text for attribute or text content.
+*/
+function escapeText( s ) {
+ if ( !s ) {
+ return "";
+ }
+ s = s + "";
+
+ // Both single quotes and double quotes (for attributes)
+ return s.replace( /['"<>&]/g, function( s ) {
+ switch ( s ) {
+ case "'":
+ return "'";
+ case "\"":
+ return """;
+ case "<":
+ return "<";
+ case ">":
+ return ">";
+ case "&":
+ return "&";
+ }
+ });
+}
+
+/**
+ * @param {HTMLElement} elem
+ * @param {string} type
+ * @param {Function} fn
+ */
+function addEvent( elem, type, fn ) {
+ if ( elem.addEventListener ) {
+
+ // Standards-based browsers
+ elem.addEventListener( type, fn, false );
+ } else if ( elem.attachEvent ) {
+
+ // support: IE <9
+ elem.attachEvent( "on" + type, function() {
+ var event = window.event;
+ if ( !event.target ) {
+ event.target = event.srcElement || document;
+ }
+
+ fn.call( elem, event );
+ });
+ }
+}
+
+/**
+ * @param {Array|NodeList} elems
+ * @param {string} type
+ * @param {Function} fn
+ */
+function addEvents( elems, type, fn ) {
+ var i = elems.length;
+ while ( i-- ) {
+ addEvent( elems[ i ], type, fn );
+ }
+}
+
+function hasClass( elem, name ) {
+ return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0;
+}
+
+function addClass( elem, name ) {
+ if ( !hasClass( elem, name ) ) {
+ elem.className += ( elem.className ? " " : "" ) + name;
+ }
+}
+
+function toggleClass( elem, name ) {
+ if ( hasClass( elem, name ) ) {
+ removeClass( elem, name );
+ } else {
+ addClass( elem, name );
+ }
+}
+
+function removeClass( elem, name ) {
+ var set = " " + elem.className + " ";
+
+ // Class name may appear multiple times
+ while ( set.indexOf( " " + name + " " ) >= 0 ) {
+ set = set.replace( " " + name + " ", " " );
+ }
+
+ // trim for prettiness
+ elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" );
+}
+
+function id( name ) {
+ return defined.document && document.getElementById && document.getElementById( name );
+}
+
+function getUrlConfigHtml() {
+ var i, j, val,
+ escaped, escapedTooltip,
+ selection = false,
+ len = config.urlConfig.length,
+ urlConfigHtml = "";
+
+ for ( i = 0; i < len; i++ ) {
+ val = config.urlConfig[ i ];
+ if ( typeof val === "string" ) {
+ val = {
+ id: val,
+ label: val
+ };
+ }
+
+ escaped = escapeText( val.id );
+ escapedTooltip = escapeText( val.tooltip );
+
+ if ( config[ val.id ] === undefined ) {
+ config[ val.id ] = QUnit.urlParams[ val.id ];
+ }
+
+ if ( !val.value || typeof val.value === "string" ) {
+ urlConfigHtml += "" + val.label + " ";
+ } else {
+ urlConfigHtml += "" + val.label +
+ ": ";
+
+ if ( QUnit.is( "array", val.value ) ) {
+ for ( j = 0; j < val.value.length; j++ ) {
+ escaped = escapeText( val.value[ j ] );
+ urlConfigHtml += "" + escaped + " ";
+ }
+ } else {
+ for ( j in val.value ) {
+ if ( hasOwn.call( val.value, j ) ) {
+ urlConfigHtml += "" + escapeText( val.value[ j ] ) + " ";
+ }
+ }
+ }
+ if ( config[ val.id ] && !selection ) {
+ escaped = escapeText( config[ val.id ] );
+ urlConfigHtml += "" + escaped + " ";
+ }
+ urlConfigHtml += " ";
+ }
+ }
+
+ return urlConfigHtml;
+}
+
+// Handle "click" events on toolbar checkboxes and "change" for select menus.
+// Updates the URL with the new state of `config.urlConfig` values.
+function toolbarChanged() {
+ var updatedUrl, value,
+ field = this,
+ params = {};
+
+ // Detect if field is a select menu or a checkbox
+ if ( "selectedIndex" in field ) {
+ value = field.options[ field.selectedIndex ].value || undefined;
+ } else {
+ value = field.checked ? ( field.defaultValue || true ) : undefined;
+ }
+
+ params[ field.name ] = value;
+ updatedUrl = setUrl( params );
+
+ if ( "hidepassed" === field.name && "replaceState" in window.history ) {
+ config[ field.name ] = value || false;
+ if ( value ) {
+ addClass( id( "qunit-tests" ), "hidepass" );
+ } else {
+ removeClass( id( "qunit-tests" ), "hidepass" );
+ }
+
+ // It is not necessary to refresh the whole page
+ window.history.replaceState( null, "", updatedUrl );
+ } else {
+ window.location = updatedUrl;
+ }
+}
+
+function setUrl( params ) {
+ var key,
+ querystring = "?";
+
+ params = QUnit.extend( QUnit.extend( {}, QUnit.urlParams ), params );
+
+ for ( key in params ) {
+ if ( hasOwn.call( params, key ) ) {
+ if ( params[ key ] === undefined ) {
+ continue;
+ }
+ querystring += encodeURIComponent( key );
+ if ( params[ key ] !== true ) {
+ querystring += "=" + encodeURIComponent( params[ key ] );
+ }
+ querystring += "&";
+ }
+ }
+ return location.protocol + "//" + location.host +
+ location.pathname + querystring.slice( 0, -1 );
+}
+
+function applyUrlParams() {
+ var selectedModule,
+ modulesList = id( "qunit-modulefilter" ),
+ filter = id( "qunit-filter-input" ).value;
+
+ selectedModule = modulesList ?
+ decodeURIComponent( modulesList.options[ modulesList.selectedIndex ].value ) :
+ undefined;
+
+ window.location = setUrl({
+ module: ( selectedModule === "" ) ? undefined : selectedModule,
+ filter: ( filter === "" ) ? undefined : filter,
+
+ // Remove testId filter
+ testId: undefined
+ });
+}
+
+function toolbarUrlConfigContainer() {
+ var urlConfigContainer = document.createElement( "span" );
+
+ urlConfigContainer.innerHTML = getUrlConfigHtml();
+ addClass( urlConfigContainer, "qunit-url-config" );
+
+ // For oldIE support:
+ // * Add handlers to the individual elements instead of the container
+ // * Use "click" instead of "change" for checkboxes
+ addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", toolbarChanged );
+ addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged );
+
+ return urlConfigContainer;
+}
+
+function toolbarLooseFilter() {
+ var filter = document.createElement( "form" ),
+ label = document.createElement( "label" ),
+ input = document.createElement( "input" ),
+ button = document.createElement( "button" );
+
+ addClass( filter, "qunit-filter" );
+
+ label.innerHTML = "Filter: ";
+
+ input.type = "text";
+ input.value = config.filter || "";
+ input.name = "filter";
+ input.id = "qunit-filter-input";
+
+ button.innerHTML = "Go";
+
+ label.appendChild( input );
+
+ filter.appendChild( label );
+ filter.appendChild( button );
+ addEvent( filter, "submit", function( ev ) {
+ applyUrlParams();
+
+ if ( ev && ev.preventDefault ) {
+ ev.preventDefault();
+ }
+
+ return false;
+ });
+
+ return filter;
+}
+
+function toolbarModuleFilterHtml() {
+ var i,
+ moduleFilterHtml = "";
+
+ if ( !modulesList.length ) {
+ return false;
+ }
+
+ modulesList.sort(function( a, b ) {
+ return a.localeCompare( b );
+ });
+
+ moduleFilterHtml += "Module: " +
+ "< All Modules > ";
+
+ for ( i = 0; i < modulesList.length; i++ ) {
+ moduleFilterHtml += "" + escapeText( modulesList[ i ] ) + " ";
+ }
+ moduleFilterHtml += " ";
+
+ return moduleFilterHtml;
+}
+
+function toolbarModuleFilter() {
+ var toolbar = id( "qunit-testrunner-toolbar" ),
+ moduleFilter = document.createElement( "span" ),
+ moduleFilterHtml = toolbarModuleFilterHtml();
+
+ if ( !toolbar || !moduleFilterHtml ) {
+ return false;
+ }
+
+ moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );
+ moduleFilter.innerHTML = moduleFilterHtml;
+
+ addEvent( moduleFilter.lastChild, "change", applyUrlParams );
+
+ toolbar.appendChild( moduleFilter );
+}
+
+function appendToolbar() {
+ var toolbar = id( "qunit-testrunner-toolbar" );
+
+ if ( toolbar ) {
+ toolbar.appendChild( toolbarUrlConfigContainer() );
+ toolbar.appendChild( toolbarLooseFilter() );
+ }
+}
+
+function appendHeader() {
+ var header = id( "qunit-header" );
+
+ if ( header ) {
+ header.innerHTML = "" + header.innerHTML + " ";
+ }
+}
+
+function appendBanner() {
+ var banner = id( "qunit-banner" );
+
+ if ( banner ) {
+ banner.className = "";
+ }
+}
+
+function appendTestResults() {
+ var tests = id( "qunit-tests" ),
+ result = id( "qunit-testresult" );
+
+ if ( result ) {
+ result.parentNode.removeChild( result );
+ }
+
+ if ( tests ) {
+ tests.innerHTML = "";
+ result = document.createElement( "p" );
+ result.id = "qunit-testresult";
+ result.className = "result";
+ tests.parentNode.insertBefore( result, tests );
+ result.innerHTML = "Running... ";
+ }
+}
+
+function storeFixture() {
+ var fixture = id( "qunit-fixture" );
+ if ( fixture ) {
+ config.fixture = fixture.innerHTML;
+ }
+}
+
+function appendUserAgent() {
+ var userAgent = id( "qunit-userAgent" );
+
+ if ( userAgent ) {
+ userAgent.innerHTML = "";
+ userAgent.appendChild(
+ document.createTextNode(
+ "QUnit " + QUnit.version + "; " + navigator.userAgent
+ )
+ );
+ }
+}
+
+function appendTestsList( modules ) {
+ var i, l, x, z, test, moduleObj;
+
+ for ( i = 0, l = modules.length; i < l; i++ ) {
+ moduleObj = modules[ i ];
+
+ if ( moduleObj.name ) {
+ modulesList.push( moduleObj.name );
+ }
+
+ for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) {
+ test = moduleObj.tests[ x ];
+
+ appendTest( test.name, test.testId, moduleObj.name );
+ }
+ }
+}
+
+function appendTest( name, testId, moduleName ) {
+ var title, rerunTrigger, testBlock, assertList,
+ tests = id( "qunit-tests" );
+
+ if ( !tests ) {
+ return;
+ }
+
+ title = document.createElement( "strong" );
+ title.innerHTML = getNameHtml( name, moduleName );
+
+ rerunTrigger = document.createElement( "a" );
+ rerunTrigger.innerHTML = "Rerun";
+ rerunTrigger.href = setUrl({ testId: testId });
+
+ testBlock = document.createElement( "li" );
+ testBlock.appendChild( title );
+ testBlock.appendChild( rerunTrigger );
+ testBlock.id = "qunit-test-output-" + testId;
+
+ assertList = document.createElement( "ol" );
+ assertList.className = "qunit-assert-list";
+
+ testBlock.appendChild( assertList );
+
+ tests.appendChild( testBlock );
+}
+
+// HTML Reporter initialization and load
+QUnit.begin(function( details ) {
+ var qunit = id( "qunit" );
+
+ // Fixture is the only one necessary to run without the #qunit element
+ storeFixture();
+
+ if ( qunit ) {
+ qunit.innerHTML =
+ "" +
+ " " +
+ "
" +
+ " " +
+ " ";
+ }
+
+ appendHeader();
+ appendBanner();
+ appendTestResults();
+ appendUserAgent();
+ appendToolbar();
+ appendTestsList( details.modules );
+ toolbarModuleFilter();
+
+ if ( qunit && config.hidepassed ) {
+ addClass( qunit.lastChild, "hidepass" );
+ }
+});
+
+QUnit.done(function( details ) {
+ var i, key,
+ banner = id( "qunit-banner" ),
+ tests = id( "qunit-tests" ),
+ html = [
+ "Tests completed in ",
+ details.runtime,
+ " milliseconds. ",
+ "",
+ details.passed,
+ " assertions of ",
+ details.total,
+ " passed, ",
+ details.failed,
+ " failed."
+ ].join( "" );
+
+ if ( banner ) {
+ banner.className = details.failed ? "qunit-fail" : "qunit-pass";
+ }
+
+ if ( tests ) {
+ id( "qunit-testresult" ).innerHTML = html;
+ }
+
+ if ( config.altertitle && defined.document && document.title ) {
+
+ // show ✖ for good, ✔ for bad suite result in title
+ // use escape sequences in case file gets loaded with non-utf-8-charset
+ document.title = [
+ ( details.failed ? "\u2716" : "\u2714" ),
+ document.title.replace( /^[\u2714\u2716] /i, "" )
+ ].join( " " );
+ }
+
+ // clear own sessionStorage items if all tests passed
+ if ( config.reorder && defined.sessionStorage && details.failed === 0 ) {
+ for ( i = 0; i < sessionStorage.length; i++ ) {
+ key = sessionStorage.key( i++ );
+ if ( key.indexOf( "qunit-test-" ) === 0 ) {
+ sessionStorage.removeItem( key );
+ }
+ }
+ }
+
+ // scroll back to top to show results
+ if ( config.scrolltop && window.scrollTo ) {
+ window.scrollTo( 0, 0 );
+ }
+});
+
+function getNameHtml( name, module ) {
+ var nameHtml = "";
+
+ if ( module ) {
+ nameHtml = "" + escapeText( module ) + " : ";
+ }
+
+ nameHtml += "" + escapeText( name ) + " ";
+
+ return nameHtml;
+}
+
+QUnit.testStart(function( details ) {
+ var running, testBlock, bad;
+
+ testBlock = id( "qunit-test-output-" + details.testId );
+ if ( testBlock ) {
+ testBlock.className = "running";
+ } else {
+
+ // Report later registered tests
+ appendTest( details.name, details.testId, details.module );
+ }
+
+ running = id( "qunit-testresult" );
+ if ( running ) {
+ bad = QUnit.config.reorder && defined.sessionStorage &&
+ +sessionStorage.getItem( "qunit-test-" + details.module + "-" + details.name );
+
+ running.innerHTML = ( bad ?
+ "Rerunning previously failed test: " :
+ "Running: " ) +
+ getNameHtml( details.name, details.module );
+ }
+
+});
+
+QUnit.log(function( details ) {
+ var assertList, assertLi,
+ message, expected, actual,
+ testItem = id( "qunit-test-output-" + details.testId );
+
+ if ( !testItem ) {
+ return;
+ }
+
+ message = escapeText( details.message ) || ( details.result ? "okay" : "failed" );
+ message = "" + message + " ";
+ message += "@ " + details.runtime + " ms ";
+
+ // pushFailure doesn't provide details.expected
+ // when it calls, it's implicit to also not show expected and diff stuff
+ // Also, we need to check details.expected existence, as it can exist and be undefined
+ if ( !details.result && hasOwn.call( details, "expected" ) ) {
+ expected = escapeText( QUnit.dump.parse( details.expected ) );
+ actual = escapeText( QUnit.dump.parse( details.actual ) );
+ message += "Expected: " +
+ expected +
+ " ";
+
+ if ( actual !== expected ) {
+ message += "Result: " +
+ actual + " " +
+ "Diff: " +
+ QUnit.diff( expected, actual ) + " ";
+ } else {
+ if ( expected.indexOf( "[object Array]" ) !== -1 ||
+ expected.indexOf( "[object Object]" ) !== -1 ) {
+ message += "Message: " +
+ "Diff suppressed as the depth of object is more than current max depth (" +
+ QUnit.config.maxDepth + ").Hint: Use QUnit.dump.maxDepth
to " +
+ " run with a higher max depth or " +
+ "Rerun without max depth.
";
+ }
+ }
+
+ if ( details.source ) {
+ message += "Source: " +
+ escapeText( details.source ) + " ";
+ }
+
+ message += "
";
+
+ // this occours when pushFailure is set and we have an extracted stack trace
+ } else if ( !details.result && details.source ) {
+ message += "" +
+ "Source: " +
+ escapeText( details.source ) + " " +
+ "
";
+ }
+
+ assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
+
+ assertLi = document.createElement( "li" );
+ assertLi.className = details.result ? "pass" : "fail";
+ assertLi.innerHTML = message;
+ assertList.appendChild( assertLi );
+});
+
+QUnit.testDone(function( details ) {
+ var testTitle, time, testItem, assertList,
+ good, bad, testCounts, skipped,
+ tests = id( "qunit-tests" );
+
+ if ( !tests ) {
+ return;
+ }
+
+ testItem = id( "qunit-test-output-" + details.testId );
+
+ assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
+
+ good = details.passed;
+ bad = details.failed;
+
+ // store result when possible
+ if ( config.reorder && defined.sessionStorage ) {
+ if ( bad ) {
+ sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad );
+ } else {
+ sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name );
+ }
+ }
+
+ if ( bad === 0 ) {
+ addClass( assertList, "qunit-collapsed" );
+ }
+
+ // testItem.firstChild is the test name
+ testTitle = testItem.firstChild;
+
+ testCounts = bad ?
+ "" + bad + " , " + "" + good + " , " :
+ "";
+
+ testTitle.innerHTML += " (" + testCounts +
+ details.assertions.length + ") ";
+
+ if ( details.skipped ) {
+ testItem.className = "skipped";
+ skipped = document.createElement( "em" );
+ skipped.className = "qunit-skipped-label";
+ skipped.innerHTML = "skipped";
+ testItem.insertBefore( skipped, testTitle );
+ } else {
+ addEvent( testTitle, "click", function() {
+ toggleClass( assertList, "qunit-collapsed" );
+ });
+
+ testItem.className = bad ? "fail" : "pass";
+
+ time = document.createElement( "span" );
+ time.className = "runtime";
+ time.innerHTML = details.runtime + " ms";
+ testItem.insertBefore( time, assertList );
+ }
+});
+
+if ( defined.document ) {
+ if ( document.readyState === "complete" ) {
+ QUnit.load();
+ } else {
+ addEvent( window, "load", QUnit.load );
+ }
+} else {
+ config.pageLoaded = true;
+ config.autorun = true;
+}
+
+})();
diff --git a/vendor/underscore/test/vendor/runner.js b/vendor/underscore/test/vendor/runner.js
deleted file mode 100644
index 4b1d38b1b..000000000
--- a/vendor/underscore/test/vendor/runner.js
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * QtWebKit-powered headless test runner using PhantomJS
- *
- * PhantomJS binaries: http://phantomjs.org/download.html
- * Requires PhantomJS 1.6+ (1.7+ recommended)
- *
- * Run with:
- * phantomjs runner.js [url-of-your-qunit-testsuite]
- *
- * e.g.
- * phantomjs runner.js http://localhost/qunit/test/index.html
- */
-
-/*jshint latedef:false */
-/*global phantom:false, require:false, console:false, window:false, QUnit:false */
-
-(function() {
- 'use strict';
-
- var args = require('system').args;
-
- // arg[0]: scriptName, args[1...]: arguments
- if (args.length !== 2) {
- console.error('Usage:\n phantomjs runner.js [url-of-your-qunit-testsuite]');
- phantom.exit(1);
- }
-
- var url = args[1],
- page = require('webpage').create();
-
- // Route `console.log()` calls from within the Page context to the main Phantom context (i.e. current `this`)
- page.onConsoleMessage = function(msg) {
- console.log(msg);
- };
-
- page.onInitialized = function() {
- page.evaluate(addLogging);
- };
-
- page.onCallback = function(message) {
- var result,
- failed;
-
- if (message) {
- if (message.name === 'QUnit.done') {
- result = message.data;
- failed = !result || result.failed;
-
- phantom.exit(failed ? 1 : 0);
- }
- }
- };
-
- page.open(url, function(status) {
- if (status !== 'success') {
- console.error('Unable to access network: ' + status);
- phantom.exit(1);
- } else {
- // Cannot do this verification with the 'DOMContentLoaded' handler because it
- // will be too late to attach it if a page does not have any script tags.
- var qunitMissing = page.evaluate(function() { return (typeof QUnit === 'undefined' || !QUnit); });
- if (qunitMissing) {
- console.error('The `QUnit` object is not present on this page.');
- phantom.exit(1);
- }
-
- // Do nothing... the callback mechanism will handle everything!
- }
- });
-
- function addLogging() {
- window.document.addEventListener('DOMContentLoaded', function() {
- var current_test_assertions = [];
-
- QUnit.log(function(details) {
- var response;
-
- // Ignore passing assertions
- if (details.result) {
- return;
- }
-
- response = details.message || '';
-
- if (typeof details.expected !== 'undefined') {
- if (response) {
- response += ', ';
- }
-
- response += 'expected: ' + details.expected + ', but was: ' + details.actual;
- if (details.source) {
- response += "\n" + details.source;
- }
- }
-
- current_test_assertions.push('Failed assertion: ' + response);
- });
-
- QUnit.testDone(function(result) {
- var i,
- len,
- name = result.module + ': ' + result.name;
-
- if (result.failed) {
- console.log('Test failed: ' + name);
-
- for (i = 0, len = current_test_assertions.length; i < len; i++) {
- console.log(' ' + current_test_assertions[i]);
- }
- }
-
- current_test_assertions.length = 0;
- });
-
- QUnit.done(function(result) {
- console.log('Took ' + result.runtime + 'ms to run ' + result.total + ' tests. ' + result.passed + ' passed, ' + result.failed + ' failed.');
-
- if (typeof window.callPhantom === 'function') {
- window.callPhantom({
- 'name': 'QUnit.done',
- 'data': result
- });
- }
- });
- }, false);
- }
-})();
diff --git a/vendor/underscore/underscore-min.js b/vendor/underscore/underscore-min.js
index d22f881bc..f01025b7b 100644
--- a/vendor/underscore/underscore-min.js
+++ b/vendor/underscore/underscore-min.js
@@ -1,6 +1,6 @@
-// Underscore.js 1.5.2
+// Underscore.js 1.8.3
// http://underscorejs.org
-// (c) 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+// (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
// Underscore may be freely distributed under the MIT license.
-(function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,h=e.reduce,v=e.reduceRight,g=e.filter,d=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,w=Object.keys,_=i.bind,j=function(n){return n instanceof j?n:this instanceof j?(this._wrapped=n,void 0):new j(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=j),exports._=j):n._=j,j.VERSION="1.5.2";var A=j.each=j.forEach=function(n,t,e){if(null!=n)if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a=j.keys(n),u=0,i=a.length;i>u;u++)if(t.call(e,n[a[u]],a[u],n)===r)return};j.map=j.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e.push(t.call(r,n,u,i))}),e)};var E="Reduce of empty array with no initial value";j.reduce=j.foldl=j.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduce===h)return e&&(t=j.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(E);return r},j.reduceRight=j.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduceRight===v)return e&&(t=j.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=j.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[--i]:--i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(E);return r},j.find=j.detect=function(n,t,r){var e;return O(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},j.filter=j.select=function(n,t,r){var e=[];return null==n?e:g&&n.filter===g?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&e.push(n)}),e)},j.reject=function(n,t,r){return j.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},j.every=j.all=function(n,t,e){t||(t=j.identity);var u=!0;return null==n?u:d&&n.every===d?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var O=j.some=j.any=function(n,t,e){t||(t=j.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};j.contains=j.include=function(n,t){return null==n?!1:y&&n.indexOf===y?n.indexOf(t)!=-1:O(n,function(n){return n===t})},j.invoke=function(n,t){var r=o.call(arguments,2),e=j.isFunction(t);return j.map(n,function(n){return(e?t:n[t]).apply(n,r)})},j.pluck=function(n,t){return j.map(n,function(n){return n[t]})},j.where=function(n,t,r){return j.isEmpty(t)?r?void 0:[]:j[r?"find":"filter"](n,function(n){for(var r in t)if(t[r]!==n[r])return!1;return!0})},j.findWhere=function(n,t){return j.where(n,t,!0)},j.max=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.max.apply(Math,n);if(!t&&j.isEmpty(n))return-1/0;var e={computed:-1/0,value:-1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;a>e.computed&&(e={value:n,computed:a})}),e.value},j.min=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.min.apply(Math,n);if(!t&&j.isEmpty(n))return 1/0;var e={computed:1/0,value:1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;ae||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var F=function(n){return function(t,r,e){var u={},i=null==r?j.identity:k(r);return A(t,function(r,a){var o=i.call(e,r,a,t);n(u,o,r)}),u}};j.groupBy=F(function(n,t,r){(j.has(n,t)?n[t]:n[t]=[]).push(r)}),j.indexBy=F(function(n,t,r){n[t]=r}),j.countBy=F(function(n,t){j.has(n,t)?n[t]++:n[t]=1}),j.sortedIndex=function(n,t,r,e){r=null==r?j.identity:k(r);for(var u=r.call(e,t),i=0,a=n.length;a>i;){var o=i+a>>>1;r.call(e,n[o])=0})})},j.difference=function(n){var t=c.apply(e,o.call(arguments,1));return j.filter(n,function(n){return!j.contains(t,n)})},j.zip=function(){for(var n=j.max(j.pluck(arguments,"length").concat(0)),t=new Array(n),r=0;n>r;r++)t[r]=j.pluck(arguments,""+r);return t},j.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},j.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=j.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},j.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u--;)if(n[u]===t)return u;return-1},j.range=function(n,t,r){arguments.length<=1&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=new Array(e);e>u;)i[u++]=n,n+=r;return i};var R=function(){};j.bind=function(n,t){var r,e;if(_&&n.bind===_)return _.apply(n,o.call(arguments,1));if(!j.isFunction(n))throw new TypeError;return r=o.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(o.call(arguments)));R.prototype=n.prototype;var u=new R;R.prototype=null;var i=n.apply(u,r.concat(o.call(arguments)));return Object(i)===i?i:u}},j.partial=function(n){var t=o.call(arguments,1);return function(){return n.apply(this,t.concat(o.call(arguments)))}},j.bindAll=function(n){var t=o.call(arguments,1);if(0===t.length)throw new Error("bindAll must be passed function names");return A(t,function(t){n[t]=j.bind(n[t],n)}),n},j.memoize=function(n,t){var r={};return t||(t=j.identity),function(){var e=t.apply(this,arguments);return j.has(r,e)?r[e]:r[e]=n.apply(this,arguments)}},j.delay=function(n,t){var r=o.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},j.defer=function(n){return j.delay.apply(j,[n,1].concat(o.call(arguments,1)))},j.throttle=function(n,t,r){var e,u,i,a=null,o=0;r||(r={});var c=function(){o=r.leading===!1?0:new Date,a=null,i=n.apply(e,u)};return function(){var l=new Date;o||r.leading!==!1||(o=l);var f=t-(l-o);return e=this,u=arguments,0>=f?(clearTimeout(a),a=null,o=l,i=n.apply(e,u)):a||r.trailing===!1||(a=setTimeout(c,f)),i}},j.debounce=function(n,t,r){var e,u,i,a,o;return function(){i=this,u=arguments,a=new Date;var c=function(){var l=new Date-a;t>l?e=setTimeout(c,t-l):(e=null,r||(o=n.apply(i,u)))},l=r&&!e;return e||(e=setTimeout(c,t)),l&&(o=n.apply(i,u)),o}},j.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},j.wrap=function(n,t){return function(){var r=[n];return a.apply(r,arguments),t.apply(this,r)}},j.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r--)t=[n[r].apply(this,t)];return t[0]}},j.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},j.keys=w||function(n){if(n!==Object(n))throw new TypeError("Invalid object");var t=[];for(var r in n)j.has(n,r)&&t.push(r);return t},j.values=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},j.pairs=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},j.invert=function(n){for(var t={},r=j.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},j.functions=j.methods=function(n){var t=[];for(var r in n)j.isFunction(n[r])&&t.push(r);return t.sort()},j.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},j.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},j.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)j.contains(r,u)||(t[u]=n[u]);return t},j.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]===void 0&&(n[r]=t[r])}),n},j.clone=function(n){return j.isObject(n)?j.isArray(n)?n.slice():j.extend({},n):n},j.tap=function(n,t){return t(n),n};var S=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof j&&(n=n._wrapped),t instanceof j&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case"[object String]":return n==String(t);case"[object Number]":return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case"[object Date]":case"[object Boolean]":return+n==+t;case"[object RegExp]":return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]==n)return e[i]==t;var a=n.constructor,o=t.constructor;if(a!==o&&!(j.isFunction(a)&&a instanceof a&&j.isFunction(o)&&o instanceof o))return!1;r.push(n),e.push(t);var c=0,f=!0;if("[object Array]"==u){if(c=n.length,f=c==t.length)for(;c--&&(f=S(n[c],t[c],r,e)););}else{for(var s in n)if(j.has(n,s)&&(c++,!(f=j.has(t,s)&&S(n[s],t[s],r,e))))break;if(f){for(s in t)if(j.has(t,s)&&!c--)break;f=!c}}return r.pop(),e.pop(),f};j.isEqual=function(n,t){return S(n,t,[],[])},j.isEmpty=function(n){if(null==n)return!0;if(j.isArray(n)||j.isString(n))return 0===n.length;for(var t in n)if(j.has(n,t))return!1;return!0},j.isElement=function(n){return!(!n||1!==n.nodeType)},j.isArray=x||function(n){return"[object Array]"==l.call(n)},j.isObject=function(n){return n===Object(n)},A(["Arguments","Function","String","Number","Date","RegExp"],function(n){j["is"+n]=function(t){return l.call(t)=="[object "+n+"]"}}),j.isArguments(arguments)||(j.isArguments=function(n){return!(!n||!j.has(n,"callee"))}),"function"!=typeof/./&&(j.isFunction=function(n){return"function"==typeof n}),j.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},j.isNaN=function(n){return j.isNumber(n)&&n!=+n},j.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"==l.call(n)},j.isNull=function(n){return null===n},j.isUndefined=function(n){return n===void 0},j.has=function(n,t){return f.call(n,t)},j.noConflict=function(){return n._=t,this},j.identity=function(n){return n},j.times=function(n,t,r){for(var e=Array(Math.max(0,n)),u=0;n>u;u++)e[u]=t.call(r,u);return e},j.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))};var I={escape:{"&":"&","<":"<",">":">",'"':""","'":"'"}};I.unescape=j.invert(I.escape);var T={escape:new RegExp("["+j.keys(I.escape).join("")+"]","g"),unescape:new RegExp("("+j.keys(I.unescape).join("|")+")","g")};j.each(["escape","unescape"],function(n){j[n]=function(t){return null==t?"":(""+t).replace(T[n],function(t){return I[n][t]})}}),j.result=function(n,t){if(null==n)return void 0;var r=n[t];return j.isFunction(r)?r.call(n):r},j.mixin=function(n){A(j.functions(n),function(t){var r=j[t]=n[t];j.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),z.call(this,r.apply(j,n))}})};var N=0;j.uniqueId=function(n){var t=++N+"";return n?n+t:t},j.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var q=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\t|\u2028|\u2029/g;j.template=function(n,t,r){var e;r=j.defaults({},r,j.templateSettings);var u=new RegExp([(r.escape||q).source,(r.interpolate||q).source,(r.evaluate||q).source].join("|")+"|$","g"),i=0,a="__p+='";n.replace(u,function(t,r,e,u,o){return a+=n.slice(i,o).replace(D,function(n){return"\\"+B[n]}),r&&(a+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'"),e&&(a+="'+\n((__t=("+e+"))==null?'':__t)+\n'"),u&&(a+="';\n"+u+"\n__p+='"),i=o+t.length,t}),a+="';\n",r.variable||(a="with(obj||{}){\n"+a+"}\n"),a="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+a+"return __p;\n";try{e=new Function(r.variable||"obj","_",a)}catch(o){throw o.source=a,o}if(t)return e(t,j);var c=function(n){return e.call(this,n,j)};return c.source="function("+(r.variable||"obj")+"){\n"+a+"}",c},j.chain=function(n){return j(n).chain()};var z=function(n){return this._chain?j(n).chain():n};j.mixin(j),A(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=e[n];j.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!=n&&"splice"!=n||0!==r.length||delete r[0],z.call(this,r)}}),A(["concat","join","slice"],function(n){var t=e[n];j.prototype[n]=function(){return z.call(this,t.apply(this._wrapped,arguments))}}),j.extend(j.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}).call(this);
+(function(){function n(n){function t(t,r,e,u,i,o){for(;i>=0&&o>i;i+=n){var a=u?u[i]:i;e=r(e,t[a],a,t)}return e}return function(r,e,u,i){e=b(e,i,4);var o=!k(r)&&m.keys(r),a=(o||r).length,c=n>0?0:a-1;return arguments.length<3&&(u=r[o?o[c]:c],c+=n),t(r,e,u,o,c,a)}}function t(n){return function(t,r,e){r=x(r,e);for(var u=O(t),i=n>0?0:u-1;i>=0&&u>i;i+=n)if(r(t[i],i,t))return i;return-1}}function r(n,t,r){return function(e,u,i){var o=0,a=O(e);if("number"==typeof i)n>0?o=i>=0?i:Math.max(i+a,o):a=i>=0?Math.min(i+1,a):i+a+1;else if(r&&i&&a)return i=r(e,u),e[i]===u?i:-1;if(u!==u)return i=t(l.call(e,o,a),m.isNaN),i>=0?i+o:-1;for(i=n>0?o:a-1;i>=0&&a>i;i+=n)if(e[i]===u)return i;return-1}}function e(n,t){var r=I.length,e=n.constructor,u=m.isFunction(e)&&e.prototype||a,i="constructor";for(m.has(n,i)&&!m.contains(t,i)&&t.push(i);r--;)i=I[r],i in n&&n[i]!==u[i]&&!m.contains(t,i)&&t.push(i)}var u=this,i=u._,o=Array.prototype,a=Object.prototype,c=Function.prototype,f=o.push,l=o.slice,s=a.toString,p=a.hasOwnProperty,h=Array.isArray,v=Object.keys,g=c.bind,y=Object.create,d=function(){},m=function(n){return n instanceof m?n:this instanceof m?void(this._wrapped=n):new m(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=m),exports._=m):u._=m,m.VERSION="1.8.3";var b=function(n,t,r){if(t===void 0)return n;switch(null==r?3:r){case 1:return function(r){return n.call(t,r)};case 2:return function(r,e){return n.call(t,r,e)};case 3:return function(r,e,u){return n.call(t,r,e,u)};case 4:return function(r,e,u,i){return n.call(t,r,e,u,i)}}return function(){return n.apply(t,arguments)}},x=function(n,t,r){return null==n?m.identity:m.isFunction(n)?b(n,t,r):m.isObject(n)?m.matcher(n):m.property(n)};m.iteratee=function(n,t){return x(n,t,1/0)};var _=function(n,t){return function(r){var e=arguments.length;if(2>e||null==r)return r;for(var u=1;e>u;u++)for(var i=arguments[u],o=n(i),a=o.length,c=0;a>c;c++){var f=o[c];t&&r[f]!==void 0||(r[f]=i[f])}return r}},j=function(n){if(!m.isObject(n))return{};if(y)return y(n);d.prototype=n;var t=new d;return d.prototype=null,t},w=function(n){return function(t){return null==t?void 0:t[n]}},A=Math.pow(2,53)-1,O=w("length"),k=function(n){var t=O(n);return"number"==typeof t&&t>=0&&A>=t};m.each=m.forEach=function(n,t,r){t=b(t,r);var e,u;if(k(n))for(e=0,u=n.length;u>e;e++)t(n[e],e,n);else{var i=m.keys(n);for(e=0,u=i.length;u>e;e++)t(n[i[e]],i[e],n)}return n},m.map=m.collect=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=Array(u),o=0;u>o;o++){var a=e?e[o]:o;i[o]=t(n[a],a,n)}return i},m.reduce=m.foldl=m.inject=n(1),m.reduceRight=m.foldr=n(-1),m.find=m.detect=function(n,t,r){var e;return e=k(n)?m.findIndex(n,t,r):m.findKey(n,t,r),e!==void 0&&e!==-1?n[e]:void 0},m.filter=m.select=function(n,t,r){var e=[];return t=x(t,r),m.each(n,function(n,r,u){t(n,r,u)&&e.push(n)}),e},m.reject=function(n,t,r){return m.filter(n,m.negate(x(t)),r)},m.every=m.all=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=0;u>i;i++){var o=e?e[i]:i;if(!t(n[o],o,n))return!1}return!0},m.some=m.any=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=0;u>i;i++){var o=e?e[i]:i;if(t(n[o],o,n))return!0}return!1},m.contains=m.includes=m.include=function(n,t,r,e){return k(n)||(n=m.values(n)),("number"!=typeof r||e)&&(r=0),m.indexOf(n,t,r)>=0},m.invoke=function(n,t){var r=l.call(arguments,2),e=m.isFunction(t);return m.map(n,function(n){var u=e?t:n[t];return null==u?u:u.apply(n,r)})},m.pluck=function(n,t){return m.map(n,m.property(t))},m.where=function(n,t){return m.filter(n,m.matcher(t))},m.findWhere=function(n,t){return m.find(n,m.matcher(t))},m.max=function(n,t,r){var e,u,i=-1/0,o=-1/0;if(null==t&&null!=n){n=k(n)?n:m.values(n);for(var a=0,c=n.length;c>a;a++)e=n[a],e>i&&(i=e)}else t=x(t,r),m.each(n,function(n,r,e){u=t(n,r,e),(u>o||u===-1/0&&i===-1/0)&&(i=n,o=u)});return i},m.min=function(n,t,r){var e,u,i=1/0,o=1/0;if(null==t&&null!=n){n=k(n)?n:m.values(n);for(var a=0,c=n.length;c>a;a++)e=n[a],i>e&&(i=e)}else t=x(t,r),m.each(n,function(n,r,e){u=t(n,r,e),(o>u||1/0===u&&1/0===i)&&(i=n,o=u)});return i},m.shuffle=function(n){for(var t,r=k(n)?n:m.values(n),e=r.length,u=Array(e),i=0;e>i;i++)t=m.random(0,i),t!==i&&(u[i]=u[t]),u[t]=r[i];return u},m.sample=function(n,t,r){return null==t||r?(k(n)||(n=m.values(n)),n[m.random(n.length-1)]):m.shuffle(n).slice(0,Math.max(0,t))},m.sortBy=function(n,t,r){return t=x(t,r),m.pluck(m.map(n,function(n,r,e){return{value:n,index:r,criteria:t(n,r,e)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var F=function(n){return function(t,r,e){var u={};return r=x(r,e),m.each(t,function(e,i){var o=r(e,i,t);n(u,e,o)}),u}};m.groupBy=F(function(n,t,r){m.has(n,r)?n[r].push(t):n[r]=[t]}),m.indexBy=F(function(n,t,r){n[r]=t}),m.countBy=F(function(n,t,r){m.has(n,r)?n[r]++:n[r]=1}),m.toArray=function(n){return n?m.isArray(n)?l.call(n):k(n)?m.map(n,m.identity):m.values(n):[]},m.size=function(n){return null==n?0:k(n)?n.length:m.keys(n).length},m.partition=function(n,t,r){t=x(t,r);var e=[],u=[];return m.each(n,function(n,r,i){(t(n,r,i)?e:u).push(n)}),[e,u]},m.first=m.head=m.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:m.initial(n,n.length-t)},m.initial=function(n,t,r){return l.call(n,0,Math.max(0,n.length-(null==t||r?1:t)))},m.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:m.rest(n,Math.max(0,n.length-t))},m.rest=m.tail=m.drop=function(n,t,r){return l.call(n,null==t||r?1:t)},m.compact=function(n){return m.filter(n,m.identity)};var S=function(n,t,r,e){for(var u=[],i=0,o=e||0,a=O(n);a>o;o++){var c=n[o];if(k(c)&&(m.isArray(c)||m.isArguments(c))){t||(c=S(c,t,r));var f=0,l=c.length;for(u.length+=l;l>f;)u[i++]=c[f++]}else r||(u[i++]=c)}return u};m.flatten=function(n,t){return S(n,t,!1)},m.without=function(n){return m.difference(n,l.call(arguments,1))},m.uniq=m.unique=function(n,t,r,e){m.isBoolean(t)||(e=r,r=t,t=!1),null!=r&&(r=x(r,e));for(var u=[],i=[],o=0,a=O(n);a>o;o++){var c=n[o],f=r?r(c,o,n):c;t?(o&&i===f||u.push(c),i=f):r?m.contains(i,f)||(i.push(f),u.push(c)):m.contains(u,c)||u.push(c)}return u},m.union=function(){return m.uniq(S(arguments,!0,!0))},m.intersection=function(n){for(var t=[],r=arguments.length,e=0,u=O(n);u>e;e++){var i=n[e];if(!m.contains(t,i)){for(var o=1;r>o&&m.contains(arguments[o],i);o++);o===r&&t.push(i)}}return t},m.difference=function(n){var t=S(arguments,!0,!0,1);return m.filter(n,function(n){return!m.contains(t,n)})},m.zip=function(){return m.unzip(arguments)},m.unzip=function(n){for(var t=n&&m.max(n,O).length||0,r=Array(t),e=0;t>e;e++)r[e]=m.pluck(n,e);return r},m.object=function(n,t){for(var r={},e=0,u=O(n);u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},m.findIndex=t(1),m.findLastIndex=t(-1),m.sortedIndex=function(n,t,r,e){r=x(r,e,1);for(var u=r(t),i=0,o=O(n);o>i;){var a=Math.floor((i+o)/2);r(n[a])i;i++,n+=r)u[i]=n;return u};var E=function(n,t,r,e,u){if(!(e instanceof t))return n.apply(r,u);var i=j(n.prototype),o=n.apply(i,u);return m.isObject(o)?o:i};m.bind=function(n,t){if(g&&n.bind===g)return g.apply(n,l.call(arguments,1));if(!m.isFunction(n))throw new TypeError("Bind must be called on a function");var r=l.call(arguments,2),e=function(){return E(n,e,t,this,r.concat(l.call(arguments)))};return e},m.partial=function(n){var t=l.call(arguments,1),r=function(){for(var e=0,u=t.length,i=Array(u),o=0;u>o;o++)i[o]=t[o]===m?arguments[e++]:t[o];for(;e=e)throw new Error("bindAll must be passed function names");for(t=1;e>t;t++)r=arguments[t],n[r]=m.bind(n[r],n);return n},m.memoize=function(n,t){var r=function(e){var u=r.cache,i=""+(t?t.apply(this,arguments):e);return m.has(u,i)||(u[i]=n.apply(this,arguments)),u[i]};return r.cache={},r},m.delay=function(n,t){var r=l.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},m.defer=m.partial(m.delay,m,1),m.throttle=function(n,t,r){var e,u,i,o=null,a=0;r||(r={});var c=function(){a=r.leading===!1?0:m.now(),o=null,i=n.apply(e,u),o||(e=u=null)};return function(){var f=m.now();a||r.leading!==!1||(a=f);var l=t-(f-a);return e=this,u=arguments,0>=l||l>t?(o&&(clearTimeout(o),o=null),a=f,i=n.apply(e,u),o||(e=u=null)):o||r.trailing===!1||(o=setTimeout(c,l)),i}},m.debounce=function(n,t,r){var e,u,i,o,a,c=function(){var f=m.now()-o;t>f&&f>=0?e=setTimeout(c,t-f):(e=null,r||(a=n.apply(i,u),e||(i=u=null)))};return function(){i=this,u=arguments,o=m.now();var f=r&&!e;return e||(e=setTimeout(c,t)),f&&(a=n.apply(i,u),i=u=null),a}},m.wrap=function(n,t){return m.partial(t,n)},m.negate=function(n){return function(){return!n.apply(this,arguments)}},m.compose=function(){var n=arguments,t=n.length-1;return function(){for(var r=t,e=n[t].apply(this,arguments);r--;)e=n[r].call(this,e);return e}},m.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},m.before=function(n,t){var r;return function(){return--n>0&&(r=t.apply(this,arguments)),1>=n&&(t=null),r}},m.once=m.partial(m.before,2);var M=!{toString:null}.propertyIsEnumerable("toString"),I=["valueOf","isPrototypeOf","toString","propertyIsEnumerable","hasOwnProperty","toLocaleString"];m.keys=function(n){if(!m.isObject(n))return[];if(v)return v(n);var t=[];for(var r in n)m.has(n,r)&&t.push(r);return M&&e(n,t),t},m.allKeys=function(n){if(!m.isObject(n))return[];var t=[];for(var r in n)t.push(r);return M&&e(n,t),t},m.values=function(n){for(var t=m.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},m.mapObject=function(n,t,r){t=x(t,r);for(var e,u=m.keys(n),i=u.length,o={},a=0;i>a;a++)e=u[a],o[e]=t(n[e],e,n);return o},m.pairs=function(n){for(var t=m.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},m.invert=function(n){for(var t={},r=m.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},m.functions=m.methods=function(n){var t=[];for(var r in n)m.isFunction(n[r])&&t.push(r);return t.sort()},m.extend=_(m.allKeys),m.extendOwn=m.assign=_(m.keys),m.findKey=function(n,t,r){t=x(t,r);for(var e,u=m.keys(n),i=0,o=u.length;o>i;i++)if(e=u[i],t(n[e],e,n))return e},m.pick=function(n,t,r){var e,u,i={},o=n;if(null==o)return i;m.isFunction(t)?(u=m.allKeys(o),e=b(t,r)):(u=S(arguments,!1,!1,1),e=function(n,t,r){return t in r},o=Object(o));for(var a=0,c=u.length;c>a;a++){var f=u[a],l=o[f];e(l,f,o)&&(i[f]=l)}return i},m.omit=function(n,t,r){if(m.isFunction(t))t=m.negate(t);else{var e=m.map(S(arguments,!1,!1,1),String);t=function(n,t){return!m.contains(e,t)}}return m.pick(n,t,r)},m.defaults=_(m.allKeys,!0),m.create=function(n,t){var r=j(n);return t&&m.extendOwn(r,t),r},m.clone=function(n){return m.isObject(n)?m.isArray(n)?n.slice():m.extend({},n):n},m.tap=function(n,t){return t(n),n},m.isMatch=function(n,t){var r=m.keys(t),e=r.length;if(null==n)return!e;for(var u=Object(n),i=0;e>i;i++){var o=r[i];if(t[o]!==u[o]||!(o in u))return!1}return!0};var N=function(n,t,r,e){if(n===t)return 0!==n||1/n===1/t;if(null==n||null==t)return n===t;n instanceof m&&(n=n._wrapped),t instanceof m&&(t=t._wrapped);var u=s.call(n);if(u!==s.call(t))return!1;switch(u){case"[object RegExp]":case"[object String]":return""+n==""+t;case"[object Number]":return+n!==+n?+t!==+t:0===+n?1/+n===1/t:+n===+t;case"[object Date]":case"[object Boolean]":return+n===+t}var i="[object Array]"===u;if(!i){if("object"!=typeof n||"object"!=typeof t)return!1;var o=n.constructor,a=t.constructor;if(o!==a&&!(m.isFunction(o)&&o instanceof o&&m.isFunction(a)&&a instanceof a)&&"constructor"in n&&"constructor"in t)return!1}r=r||[],e=e||[];for(var c=r.length;c--;)if(r[c]===n)return e[c]===t;if(r.push(n),e.push(t),i){if(c=n.length,c!==t.length)return!1;for(;c--;)if(!N(n[c],t[c],r,e))return!1}else{var f,l=m.keys(n);if(c=l.length,m.keys(t).length!==c)return!1;for(;c--;)if(f=l[c],!m.has(t,f)||!N(n[f],t[f],r,e))return!1}return r.pop(),e.pop(),!0};m.isEqual=function(n,t){return N(n,t)},m.isEmpty=function(n){return null==n?!0:k(n)&&(m.isArray(n)||m.isString(n)||m.isArguments(n))?0===n.length:0===m.keys(n).length},m.isElement=function(n){return!(!n||1!==n.nodeType)},m.isArray=h||function(n){return"[object Array]"===s.call(n)},m.isObject=function(n){var t=typeof n;return"function"===t||"object"===t&&!!n},m.each(["Arguments","Function","String","Number","Date","RegExp","Error"],function(n){m["is"+n]=function(t){return s.call(t)==="[object "+n+"]"}}),m.isArguments(arguments)||(m.isArguments=function(n){return m.has(n,"callee")}),"function"!=typeof/./&&"object"!=typeof Int8Array&&(m.isFunction=function(n){return"function"==typeof n||!1}),m.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},m.isNaN=function(n){return m.isNumber(n)&&n!==+n},m.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"===s.call(n)},m.isNull=function(n){return null===n},m.isUndefined=function(n){return n===void 0},m.has=function(n,t){return null!=n&&p.call(n,t)},m.noConflict=function(){return u._=i,this},m.identity=function(n){return n},m.constant=function(n){return function(){return n}},m.noop=function(){},m.property=w,m.propertyOf=function(n){return null==n?function(){}:function(t){return n[t]}},m.matcher=m.matches=function(n){return n=m.extendOwn({},n),function(t){return m.isMatch(t,n)}},m.times=function(n,t,r){var e=Array(Math.max(0,n));t=b(t,r,1);for(var u=0;n>u;u++)e[u]=t(u);return e},m.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))},m.now=Date.now||function(){return(new Date).getTime()};var B={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},T=m.invert(B),R=function(n){var t=function(t){return n[t]},r="(?:"+m.keys(n).join("|")+")",e=RegExp(r),u=RegExp(r,"g");return function(n){return n=null==n?"":""+n,e.test(n)?n.replace(u,t):n}};m.escape=R(B),m.unescape=R(T),m.result=function(n,t,r){var e=null==n?void 0:n[t];return e===void 0&&(e=r),m.isFunction(e)?e.call(n):e};var q=0;m.uniqueId=function(n){var t=++q+"";return n?n+t:t},m.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var K=/(.)^/,z={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\u2028|\u2029/g,L=function(n){return"\\"+z[n]};m.template=function(n,t,r){!t&&r&&(t=r),t=m.defaults({},t,m.templateSettings);var e=RegExp([(t.escape||K).source,(t.interpolate||K).source,(t.evaluate||K).source].join("|")+"|$","g"),u=0,i="__p+='";n.replace(e,function(t,r,e,o,a){return i+=n.slice(u,a).replace(D,L),u=a+t.length,r?i+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'":e?i+="'+\n((__t=("+e+"))==null?'':__t)+\n'":o&&(i+="';\n"+o+"\n__p+='"),t}),i+="';\n",t.variable||(i="with(obj||{}){\n"+i+"}\n"),i="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+i+"return __p;\n";try{var o=new Function(t.variable||"obj","_",i)}catch(a){throw a.source=i,a}var c=function(n){return o.call(this,n,m)},f=t.variable||"obj";return c.source="function("+f+"){\n"+i+"}",c},m.chain=function(n){var t=m(n);return t._chain=!0,t};var P=function(n,t){return n._chain?m(t).chain():t};m.mixin=function(n){m.each(m.functions(n),function(t){var r=m[t]=n[t];m.prototype[t]=function(){var n=[this._wrapped];return f.apply(n,arguments),P(this,r.apply(m,n))}})},m.mixin(m),m.each(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=o[n];m.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!==n&&"splice"!==n||0!==r.length||delete r[0],P(this,r)}}),m.each(["concat","join","slice"],function(n){var t=o[n];m.prototype[n]=function(){return P(this,t.apply(this._wrapped,arguments))}}),m.prototype.value=function(){return this._wrapped},m.prototype.valueOf=m.prototype.toJSON=m.prototype.value,m.prototype.toString=function(){return""+this._wrapped},"function"==typeof define&&define.amd&&define("underscore",[],function(){return m})}).call(this);
//# sourceMappingURL=underscore-min.map
\ No newline at end of file
diff --git a/vendor/underscore/underscore-min.map b/vendor/underscore/underscore-min.map
index 4fbe0ba3c..cf356bf9a 100644
--- a/vendor/underscore/underscore-min.map
+++ b/vendor/underscore/underscore-min.map
@@ -1 +1 @@
-{"version":3,"file":"underscore-min.js","sources":["underscore.js"],"names":["root","this","previousUnderscore","_","breaker","ArrayProto","Array","prototype","ObjProto","Object","FuncProto","Function","push","slice","concat","toString","hasOwnProperty","nativeForEach","forEach","nativeMap","map","nativeReduce","reduce","nativeReduceRight","reduceRight","nativeFilter","filter","nativeEvery","every","nativeSome","some","nativeIndexOf","indexOf","nativeLastIndexOf","lastIndexOf","nativeIsArray","isArray","nativeKeys","keys","nativeBind","bind","obj","_wrapped","exports","module","VERSION","each","iterator","context","length","i","call","collect","results","value","index","list","reduceError","foldl","inject","memo","initial","arguments","TypeError","foldr","find","detect","result","any","select","reject","all","identity","contains","include","target","invoke","method","args","isFunc","isFunction","apply","pluck","key","where","attrs","first","isEmpty","findWhere","max","Math","Infinity","computed","min","shuffle","rand","shuffled","random","sample","n","guard","lookupIterator","sortBy","criteria","sort","left","right","a","b","group","behavior","groupBy","has","indexBy","countBy","sortedIndex","array","low","high","mid","toArray","values","size","head","take","last","rest","tail","drop","compact","flatten","input","shallow","output","isArguments","without","difference","uniq","unique","isSorted","seen","union","intersection","item","other","zip","object","from","hasIndex","range","start","stop","step","ceil","idx","ctor","func","bound","self","partial","bindAll","funcs","Error","f","memoize","hasher","delay","wait","setTimeout","defer","throttle","options","timeout","previous","later","leading","Date","now","remaining","clearTimeout","trailing","debounce","immediate","timestamp","callNow","once","ran","wrap","wrapper","compose","after","times","pairs","invert","functions","methods","names","extend","source","prop","pick","copy","omit","defaults","clone","isObject","tap","interceptor","eq","aStack","bStack","className","String","global","multiline","ignoreCase","aCtor","constructor","bCtor","pop","isEqual","isString","isElement","nodeType","name","isFinite","isNaN","parseFloat","isNumber","isBoolean","isNull","isUndefined","noConflict","accum","floor","entityMap","escape","&","<",">","\"","'","unescape","entityRegexes","RegExp","join","string","replace","match","property","mixin","idCounter","uniqueId","prefix","id","templateSettings","evaluate","interpolate","noMatch","escapes","\\","\r","\n","\t","
","
","escaper","template","text","data","settings","render","matcher","offset","variable","e","chain","_chain"],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"underscore-min.js","sources":["underscore.js"],"names":["createReduce","dir","iterator","obj","iteratee","memo","keys","index","length","currentKey","context","optimizeCb","isArrayLike","_","arguments","createPredicateIndexFinder","array","predicate","cb","getLength","createIndexFinder","predicateFind","sortedIndex","item","idx","i","Math","max","min","slice","call","isNaN","collectNonEnumProps","nonEnumIdx","nonEnumerableProps","constructor","proto","isFunction","prototype","ObjProto","prop","has","contains","push","root","this","previousUnderscore","ArrayProto","Array","Object","FuncProto","Function","toString","hasOwnProperty","nativeIsArray","isArray","nativeKeys","nativeBind","bind","nativeCreate","create","Ctor","_wrapped","exports","module","VERSION","func","argCount","value","other","collection","accumulator","apply","identity","isObject","matcher","property","Infinity","createAssigner","keysFunc","undefinedOnly","source","l","key","baseCreate","result","MAX_ARRAY_INDEX","pow","each","forEach","map","collect","results","reduce","foldl","inject","reduceRight","foldr","find","detect","findIndex","findKey","filter","select","list","reject","negate","every","all","some","any","includes","include","fromIndex","guard","values","indexOf","invoke","method","args","isFunc","pluck","where","attrs","findWhere","computed","lastComputed","shuffle","rand","set","shuffled","random","sample","n","sortBy","criteria","sort","left","right","a","b","group","behavior","groupBy","indexBy","countBy","toArray","size","partition","pass","fail","first","head","take","initial","last","rest","tail","drop","compact","flatten","input","shallow","strict","startIndex","output","isArguments","j","len","without","difference","uniq","unique","isSorted","isBoolean","seen","union","intersection","argsLength","zip","unzip","object","findLastIndex","low","high","mid","floor","lastIndexOf","range","start","stop","step","ceil","executeBound","sourceFunc","boundFunc","callingContext","self","TypeError","bound","concat","partial","boundArgs","position","bindAll","Error","memoize","hasher","cache","address","delay","wait","setTimeout","defer","throttle","options","timeout","previous","later","leading","now","remaining","clearTimeout","trailing","debounce","immediate","timestamp","callNow","wrap","wrapper","compose","after","times","before","once","hasEnumBug","propertyIsEnumerable","allKeys","mapObject","pairs","invert","functions","methods","names","extend","extendOwn","assign","pick","oiteratee","omit","String","defaults","props","clone","tap","interceptor","isMatch","eq","aStack","bStack","className","areArrays","aCtor","bCtor","pop","isEqual","isEmpty","isString","isElement","nodeType","type","name","Int8Array","isFinite","parseFloat","isNumber","isNull","isUndefined","noConflict","constant","noop","propertyOf","matches","accum","Date","getTime","escapeMap","&","<",">","\"","'","`","unescapeMap","createEscaper","escaper","match","join","testRegexp","RegExp","replaceRegexp","string","test","replace","escape","unescape","fallback","idCounter","uniqueId","prefix","id","templateSettings","evaluate","interpolate","noMatch","escapes","\\","\r","\n","
","
","escapeChar","template","text","settings","oldSettings","offset","variable","render","e","data","argument","chain","instance","_chain","mixin","valueOf","toJSON","define","amd"],"mappings":"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"}
\ No newline at end of file
diff --git a/vendor/underscore/underscore.js b/vendor/underscore/underscore.js
index b50115df5..ca1cdd332 100644
--- a/vendor/underscore/underscore.js
+++ b/vendor/underscore/underscore.js
@@ -1,6 +1,6 @@
-// Underscore.js 1.5.2
+// Underscore.js 1.8.3
// http://underscorejs.org
-// (c) 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+// (c) 2009-2017 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
// Underscore may be freely distributed under the MIT license.
(function() {
@@ -8,41 +8,35 @@
// Baseline setup
// --------------
- // Establish the root object, `window` in the browser, or `exports` on the server.
- var root = this;
+ // Establish the root object, `window` (`self`) in the browser, `global`
+ // on the server, or `this` in some virtual machines. We use `self`
+ // instead of `window` for `WebWorker` support.
+ var root = typeof self == 'object' && self.self === self && self ||
+ typeof global == 'object' && global.global === global && global ||
+ this ||
+ {};
// Save the previous value of the `_` variable.
var previousUnderscore = root._;
- // Establish the object that gets returned to break out of a loop iteration.
- var breaker = {};
-
// Save bytes in the minified (but not gzipped) version:
- var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
+ var ArrayProto = Array.prototype, ObjProto = Object.prototype;
+ var SymbolProto = typeof Symbol !== 'undefined' ? Symbol.prototype : null;
// Create quick reference variables for speed access to core prototypes.
- var
- push = ArrayProto.push,
- slice = ArrayProto.slice,
- concat = ArrayProto.concat,
- toString = ObjProto.toString,
- hasOwnProperty = ObjProto.hasOwnProperty;
+ var push = ArrayProto.push,
+ slice = ArrayProto.slice,
+ toString = ObjProto.toString,
+ hasOwnProperty = ObjProto.hasOwnProperty;
// All **ECMAScript 5** native function implementations that we hope to use
// are declared here.
- var
- nativeForEach = ArrayProto.forEach,
- nativeMap = ArrayProto.map,
- nativeReduce = ArrayProto.reduce,
- nativeReduceRight = ArrayProto.reduceRight,
- nativeFilter = ArrayProto.filter,
- nativeEvery = ArrayProto.every,
- nativeSome = ArrayProto.some,
- nativeIndexOf = ArrayProto.indexOf,
- nativeLastIndexOf = ArrayProto.lastIndexOf,
- nativeIsArray = Array.isArray,
- nativeKeys = Object.keys,
- nativeBind = FuncProto.bind;
+ var nativeIsArray = Array.isArray,
+ nativeKeys = Object.keys,
+ nativeCreate = Object.create;
+
+ // Naked function reference for surrogate-prototype-swapping.
+ var Ctor = function(){};
// Create a safe reference to the Underscore object for use below.
var _ = function(obj) {
@@ -52,11 +46,12 @@
};
// 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) {
+ // backwards-compatibility for their old module API. If we're in
+ // the browser, add `_` as a global object.
+ // (`nodeType` is checked to ensure that `module`
+ // and `exports` are not HTML elements.)
+ if (typeof exports != 'undefined' && !exports.nodeType) {
+ if (typeof module != 'undefined' && !module.nodeType && module.exports) {
exports = module.exports = _;
}
exports._ = _;
@@ -65,261 +60,362 @@
}
// Current version.
- _.VERSION = '1.5.2';
+ _.VERSION = '1.8.3';
+
+ // Internal function that returns an efficient (for current engines) version
+ // of the passed-in callback, to be repeatedly applied in other Underscore
+ // functions.
+ var optimizeCb = function(func, context, argCount) {
+ if (context === void 0) return func;
+ switch (argCount) {
+ case 1: return function(value) {
+ return func.call(context, value);
+ };
+ // The 2-parameter case has been omitted only because no current consumers
+ // made use of it.
+ case null:
+ case 3: return function(value, index, collection) {
+ return func.call(context, value, index, collection);
+ };
+ case 4: return function(accumulator, value, index, collection) {
+ return func.call(context, accumulator, value, index, collection);
+ };
+ }
+ return function() {
+ return func.apply(context, arguments);
+ };
+ };
+
+ var builtinIteratee;
+
+ // An internal function to generate callbacks that can be applied to each
+ // element in a collection, returning the desired result — either `identity`,
+ // an arbitrary callback, a property matcher, or a property accessor.
+ var cb = function(value, context, argCount) {
+ if (_.iteratee !== builtinIteratee) return _.iteratee(value, context);
+ if (value == null) return _.identity;
+ if (_.isFunction(value)) return optimizeCb(value, context, argCount);
+ if (_.isObject(value) && !_.isArray(value)) return _.matcher(value);
+ return _.property(value);
+ };
+
+ // External wrapper for our callback generator. Users may customize
+ // `_.iteratee` if they want additional predicate/iteratee shorthand styles.
+ // This abstraction hides the internal-only argCount argument.
+ _.iteratee = builtinIteratee = function(value, context) {
+ return cb(value, context, Infinity);
+ };
+
+ // Similar to ES6's rest param (http://ariya.ofilabs.com/2013/03/es6-and-rest-parameter.html)
+ // This accumulates the arguments passed into an array, after a given index.
+ var restArgs = function(func, startIndex) {
+ startIndex = startIndex == null ? func.length - 1 : +startIndex;
+ return function() {
+ var length = Math.max(arguments.length - startIndex, 0),
+ rest = Array(length),
+ index = 0;
+ for (; index < length; index++) {
+ rest[index] = arguments[index + startIndex];
+ }
+ switch (startIndex) {
+ case 0: return func.call(this, rest);
+ case 1: return func.call(this, arguments[0], rest);
+ case 2: return func.call(this, arguments[0], arguments[1], rest);
+ }
+ var args = Array(startIndex + 1);
+ for (index = 0; index < startIndex; index++) {
+ args[index] = arguments[index];
+ }
+ args[startIndex] = rest;
+ return func.apply(this, args);
+ };
+ };
+
+ // An internal function for creating a new object that inherits from another.
+ var baseCreate = function(prototype) {
+ if (!_.isObject(prototype)) return {};
+ if (nativeCreate) return nativeCreate(prototype);
+ Ctor.prototype = prototype;
+ var result = new Ctor;
+ Ctor.prototype = null;
+ return result;
+ };
+
+ var shallowProperty = function(key) {
+ return function(obj) {
+ return obj == null ? void 0 : obj[key];
+ };
+ };
+
+ var deepGet = function(obj, path) {
+ var length = path.length;
+ for (var i = 0; i < length; i++) {
+ if (obj == null) return void 0;
+ obj = obj[path[i]];
+ }
+ return length ? obj : void 0;
+ };
+
+ // Helper for collection methods to determine whether a collection
+ // should be iterated as an array or as an object.
+ // Related: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength
+ // Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094
+ var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
+ var getLength = shallowProperty('length');
+ var isArrayLike = function(collection) {
+ var length = getLength(collection);
+ return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
+ };
// Collection Functions
// --------------------
// The cornerstone, an `each` implementation, aka `forEach`.
- // Handles objects with the built-in `forEach`, arrays, and raw objects.
- // Delegates to **ECMAScript 5**'s native `forEach` if available.
- var each = _.each = _.forEach = function(obj, iterator, context) {
- if (obj == null) return;
- if (nativeForEach && obj.forEach === nativeForEach) {
- obj.forEach(iterator, context);
- } else if (obj.length === +obj.length) {
- for (var i = 0, length = obj.length; i < length; i++) {
- if (iterator.call(context, obj[i], i, obj) === breaker) return;
+ // Handles raw objects in addition to array-likes. Treats all
+ // sparse array-likes as if they were dense.
+ _.each = _.forEach = function(obj, iteratee, context) {
+ iteratee = optimizeCb(iteratee, context);
+ var i, length;
+ if (isArrayLike(obj)) {
+ for (i = 0, length = obj.length; i < length; i++) {
+ iteratee(obj[i], i, obj);
}
} else {
var keys = _.keys(obj);
- for (var i = 0, length = keys.length; i < length; i++) {
- if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return;
+ for (i = 0, length = keys.length; i < length; i++) {
+ iteratee(obj[keys[i]], keys[i], obj);
}
}
+ return obj;
};
- // Return the results of applying the iterator to each element.
- // Delegates to **ECMAScript 5**'s native `map` if available.
- _.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.push(iterator.call(context, value, index, list));
- });
+ // Return the results of applying the iteratee to each element.
+ _.map = _.collect = function(obj, iteratee, context) {
+ iteratee = cb(iteratee, context);
+ var keys = !isArrayLike(obj) && _.keys(obj),
+ length = (keys || obj).length,
+ results = Array(length);
+ for (var index = 0; index < length; index++) {
+ var currentKey = keys ? keys[index] : index;
+ results[index] = iteratee(obj[currentKey], currentKey, obj);
+ }
return results;
};
- var reduceError = 'Reduce of empty array with no initial value';
+ // Create a reducing function iterating left or right.
+ var createReduce = function(dir) {
+ // Wrap code that reassigns argument variables in a separate function than
+ // the one that accesses `arguments.length` to avoid a perf hit. (#1991)
+ var reducer = function(obj, iteratee, memo, initial) {
+ var keys = !isArrayLike(obj) && _.keys(obj),
+ length = (keys || obj).length,
+ index = dir > 0 ? 0 : length - 1;
+ if (!initial) {
+ memo = obj[keys ? keys[index] : index];
+ index += dir;
+ }
+ for (; index >= 0 && index < length; index += dir) {
+ var currentKey = keys ? keys[index] : index;
+ memo = iteratee(memo, obj[currentKey], currentKey, obj);
+ }
+ return memo;
+ };
+
+ return function(obj, iteratee, memo, context) {
+ var initial = arguments.length >= 3;
+ return reducer(obj, optimizeCb(iteratee, context, 4), memo, initial);
+ };
+ };
// **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 = arguments.length > 2;
- if (obj == null) obj = [];
- if (nativeReduce && obj.reduce === nativeReduce) {
- if (context) iterator = _.bind(iterator, context);
- return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
- }
- each(obj, function(value, index, list) {
- if (!initial) {
- memo = value;
- initial = true;
- } else {
- memo = iterator.call(context, memo, value, index, list);
- }
- });
- if (!initial) throw new TypeError(reduceError);
- return memo;
- };
+ // or `foldl`.
+ _.reduce = _.foldl = _.inject = createReduce(1);
// 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 initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
- }
- var length = obj.length;
- if (length !== +length) {
- var keys = _.keys(obj);
- length = keys.length;
- }
- each(obj, function(value, index, list) {
- index = keys ? keys[--length] : --length;
- if (!initial) {
- memo = obj[index];
- initial = true;
- } else {
- memo = iterator.call(context, memo, obj[index], index, list);
- }
- });
- if (!initial) throw new TypeError(reduceError);
- return memo;
- };
+ _.reduceRight = _.foldr = createReduce(-1);
// Return the first value which passes a truth test. Aliased as `detect`.
- _.find = _.detect = function(obj, iterator, context) {
- var result;
- any(obj, function(value, index, list) {
- if (iterator.call(context, value, index, list)) {
- result = value;
- return true;
- }
- });
- return result;
+ _.find = _.detect = function(obj, predicate, context) {
+ var keyFinder = isArrayLike(obj) ? _.findIndex : _.findKey;
+ var key = keyFinder(obj, predicate, context);
+ if (key !== void 0 && key !== -1) return obj[key];
};
// Return all the elements that pass a truth test.
- // Delegates to **ECMAScript 5**'s native `filter` if available.
// Aliased as `select`.
- _.filter = _.select = function(obj, iterator, context) {
+ _.filter = _.select = function(obj, predicate, context) {
var results = [];
- if (obj == null) return results;
- if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
- each(obj, function(value, index, list) {
- if (iterator.call(context, value, index, list)) results.push(value);
+ predicate = cb(predicate, context);
+ _.each(obj, function(value, index, list) {
+ if (predicate(value, index, list)) results.push(value);
});
return results;
};
// Return all the elements for which a truth test fails.
- _.reject = function(obj, iterator, context) {
- return _.filter(obj, function(value, index, list) {
- return !iterator.call(context, value, index, list);
- }, context);
+ _.reject = function(obj, predicate, context) {
+ return _.filter(obj, _.negate(cb(predicate)), context);
};
// Determine whether all of the elements match a truth test.
- // Delegates to **ECMAScript 5**'s native `every` if available.
// Aliased as `all`.
- _.every = _.all = function(obj, iterator, context) {
- iterator || (iterator = _.identity);
- var result = true;
- if (obj == null) return result;
- if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
- each(obj, function(value, index, list) {
- if (!(result = result && iterator.call(context, value, index, list))) return breaker;
- });
- return !!result;
+ _.every = _.all = function(obj, predicate, context) {
+ predicate = cb(predicate, context);
+ var keys = !isArrayLike(obj) && _.keys(obj),
+ length = (keys || obj).length;
+ for (var index = 0; index < length; index++) {
+ var currentKey = keys ? keys[index] : index;
+ if (!predicate(obj[currentKey], currentKey, obj)) return false;
+ }
+ return true;
};
// 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);
- 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 || (result = iterator.call(context, value, index, list))) return breaker;
- });
- return !!result;
+ _.some = _.any = function(obj, predicate, context) {
+ predicate = cb(predicate, context);
+ var keys = !isArrayLike(obj) && _.keys(obj),
+ length = (keys || obj).length;
+ for (var index = 0; index < length; index++) {
+ var currentKey = keys ? keys[index] : index;
+ if (predicate(obj[currentKey], currentKey, obj)) return true;
+ }
+ return false;
};
- // Determine if the array or object contains a given value (using `===`).
- // Aliased as `include`.
- _.contains = _.include = function(obj, target) {
- if (obj == null) return false;
- if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
- return any(obj, function(value) {
- return value === target;
- });
+ // Determine if the array or object contains a given item (using `===`).
+ // Aliased as `includes` and `include`.
+ _.contains = _.includes = _.include = function(obj, item, fromIndex, guard) {
+ if (!isArrayLike(obj)) obj = _.values(obj);
+ if (typeof fromIndex != 'number' || guard) fromIndex = 0;
+ return _.indexOf(obj, item, fromIndex) >= 0;
};
// Invoke a method (with arguments) on every item in a collection.
- _.invoke = function(obj, method) {
- var args = slice.call(arguments, 2);
- var isFunc = _.isFunction(method);
- return _.map(obj, function(value) {
- return (isFunc ? method : value[method]).apply(value, args);
+ _.invoke = restArgs(function(obj, path, args) {
+ var contextPath, func;
+ if (_.isFunction(path)) {
+ func = path;
+ } else if (_.isArray(path)) {
+ contextPath = path.slice(0, -1);
+ path = path[path.length - 1];
+ }
+ return _.map(obj, function(context) {
+ var method = func;
+ if (!method) {
+ if (contextPath && contextPath.length) {
+ context = deepGet(context, contextPath);
+ }
+ if (context == null) return void 0;
+ method = context[path];
+ }
+ return method == null ? method : method.apply(context, args);
});
- };
+ });
// Convenience version of a common use case of `map`: fetching a property.
_.pluck = function(obj, key) {
- return _.map(obj, function(value){ return value[key]; });
+ return _.map(obj, _.property(key));
};
// Convenience version of a common use case of `filter`: selecting only objects
// containing specific `key:value` pairs.
- _.where = function(obj, attrs, first) {
- if (_.isEmpty(attrs)) return first ? void 0 : [];
- return _[first ? 'find' : 'filter'](obj, function(value) {
- for (var key in attrs) {
- if (attrs[key] !== value[key]) return false;
- }
- return true;
- });
+ _.where = function(obj, attrs) {
+ return _.filter(obj, _.matcher(attrs));
};
// Convenience version of a common use case of `find`: getting the first object
// containing specific `key:value` pairs.
_.findWhere = function(obj, attrs) {
- return _.where(obj, attrs, true);
+ return _.find(obj, _.matcher(attrs));
};
- // Return the maximum element or (element-based computation).
- // Can't optimize arrays of integers longer than 65,535 elements.
- // See [WebKit Bug 80797](https://bugs.webkit.org/show_bug.cgi?id=80797)
- _.max = function(obj, iterator, context) {
- if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
- return Math.max.apply(Math, obj);
+ // Return the maximum element (or element-based computation).
+ _.max = function(obj, iteratee, context) {
+ var result = -Infinity, lastComputed = -Infinity,
+ value, computed;
+ if (iteratee == null || (typeof iteratee == 'number' && typeof obj[0] != 'object') && obj != null) {
+ obj = isArrayLike(obj) ? obj : _.values(obj);
+ for (var i = 0, length = obj.length; i < length; i++) {
+ value = obj[i];
+ if (value != null && value > result) {
+ result = value;
+ }
+ }
+ } else {
+ iteratee = cb(iteratee, context);
+ _.each(obj, function(v, index, list) {
+ computed = iteratee(v, index, list);
+ if (computed > lastComputed || computed === -Infinity && result === -Infinity) {
+ result = v;
+ lastComputed = computed;
+ }
+ });
}
- if (!iterator && _.isEmpty(obj)) return -Infinity;
- var result = {computed : -Infinity, value: -Infinity};
- each(obj, function(value, index, list) {
- var computed = iterator ? iterator.call(context, value, index, list) : value;
- computed > result.computed && (result = {value : value, computed : computed});
- });
- return result.value;
+ return result;
};
// Return the minimum element (or element-based computation).
- _.min = function(obj, iterator, context) {
- if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
- return Math.min.apply(Math, obj);
+ _.min = function(obj, iteratee, context) {
+ var result = Infinity, lastComputed = Infinity,
+ value, computed;
+ if (iteratee == null || (typeof iteratee == 'number' && typeof obj[0] != 'object') && obj != null) {
+ obj = isArrayLike(obj) ? obj : _.values(obj);
+ for (var i = 0, length = obj.length; i < length; i++) {
+ value = obj[i];
+ if (value != null && value < result) {
+ result = value;
+ }
+ }
+ } else {
+ iteratee = cb(iteratee, context);
+ _.each(obj, function(v, index, list) {
+ computed = iteratee(v, index, list);
+ if (computed < lastComputed || computed === Infinity && result === Infinity) {
+ result = v;
+ lastComputed = computed;
+ }
+ });
}
- if (!iterator && _.isEmpty(obj)) return Infinity;
- var result = {computed : Infinity, value: Infinity};
- each(obj, function(value, index, list) {
- var computed = iterator ? iterator.call(context, value, index, list) : value;
- computed < result.computed && (result = {value : value, computed : computed});
- });
- return result.value;
+ return result;
};
- // Shuffle an array, using the modern version of the
- // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle).
+ // Shuffle a collection.
_.shuffle = function(obj) {
- var rand;
- var index = 0;
- var shuffled = [];
- each(obj, function(value) {
- rand = _.random(index++);
- shuffled[index - 1] = shuffled[rand];
- shuffled[rand] = value;
- });
- return shuffled;
+ return _.sample(obj, Infinity);
};
- // Sample **n** random values from an array.
- // If **n** is not specified, returns a single random element from the array.
+ // Sample **n** random values from a collection using the modern version of the
+ // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle).
+ // If **n** is not specified, returns a single random element.
// The internal `guard` argument allows it to work with `map`.
_.sample = function(obj, n, guard) {
- if (arguments.length < 2 || guard) {
+ if (n == null || guard) {
+ if (!isArrayLike(obj)) obj = _.values(obj);
return obj[_.random(obj.length - 1)];
}
- return _.shuffle(obj).slice(0, Math.max(0, n));
+ var sample = isArrayLike(obj) ? _.clone(obj) : _.values(obj);
+ var length = getLength(sample);
+ n = Math.max(Math.min(n, length), 0);
+ var last = length - 1;
+ for (var index = 0; index < n; index++) {
+ var rand = _.random(index, last);
+ var temp = sample[index];
+ sample[index] = sample[rand];
+ sample[rand] = temp;
+ }
+ return sample.slice(0, n);
};
- // An internal function to generate lookup iterators.
- var lookupIterator = function(value) {
- return _.isFunction(value) ? value : function(obj){ return obj[value]; };
- };
-
- // Sort the object's values by a criterion produced by an iterator.
- _.sortBy = function(obj, value, context) {
- var iterator = lookupIterator(value);
- return _.pluck(_.map(obj, function(value, index, list) {
+ // Sort the object's values by a criterion produced by an iteratee.
+ _.sortBy = function(obj, iteratee, context) {
+ var index = 0;
+ iteratee = cb(iteratee, context);
+ return _.pluck(_.map(obj, function(value, key, list) {
return {
value: value,
- index: index,
- criteria: iterator.call(context, value, index, list)
+ index: index++,
+ criteria: iteratee(value, key, list)
};
}).sort(function(left, right) {
var a = left.criteria;
@@ -333,13 +429,13 @@
};
// An internal function used for aggregate "group by" operations.
- var group = function(behavior) {
- return function(obj, value, context) {
- var result = {};
- var iterator = value == null ? _.identity : lookupIterator(value);
- each(obj, function(value, index) {
- var key = iterator.call(context, value, index, obj);
- behavior(result, key, value);
+ var group = function(behavior, partition) {
+ return function(obj, iteratee, context) {
+ var result = partition ? [[], []] : {};
+ iteratee = cb(iteratee, context);
+ _.each(obj, function(value, index) {
+ var key = iteratee(value, index, obj);
+ behavior(result, value, key);
});
return result;
};
@@ -347,50 +443,48 @@
// Groups the object's values by a criterion. Pass either a string attribute
// to group by, or a function that returns the criterion.
- _.groupBy = group(function(result, key, value) {
- (_.has(result, key) ? result[key] : (result[key] = [])).push(value);
+ _.groupBy = group(function(result, value, key) {
+ if (_.has(result, key)) result[key].push(value); else result[key] = [value];
});
// Indexes the object's values by a criterion, similar to `groupBy`, but for
// when you know that your index values will be unique.
- _.indexBy = group(function(result, key, value) {
+ _.indexBy = group(function(result, value, key) {
result[key] = value;
});
// Counts instances of an object that group by a certain criterion. Pass
// either a string attribute to count by, or a function that returns the
// criterion.
- _.countBy = group(function(result, key) {
- _.has(result, key) ? result[key]++ : result[key] = 1;
+ _.countBy = group(function(result, value, key) {
+ if (_.has(result, key)) result[key]++; else result[key] = 1;
});
- // Use a comparator function to figure out the smallest index at which
- // an object should be inserted so as to maintain order. Uses binary search.
- _.sortedIndex = function(array, obj, iterator, context) {
- iterator = iterator == null ? _.identity : lookupIterator(iterator);
- var value = iterator.call(context, obj);
- var low = 0, high = array.length;
- while (low < high) {
- var mid = (low + high) >>> 1;
- iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid;
- }
- return low;
- };
-
+ var reStrSymbol = /[^\ud800-\udfff]|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff]/g;
// Safely create a real, live array from anything iterable.
_.toArray = function(obj) {
if (!obj) return [];
if (_.isArray(obj)) return slice.call(obj);
- if (obj.length === +obj.length) return _.map(obj, _.identity);
+ if (_.isString(obj)) {
+ // Keep surrogate pair characters together
+ return obj.match(reStrSymbol);
+ }
+ if (isArrayLike(obj)) return _.map(obj, _.identity);
return _.values(obj);
};
// Return the number of elements in an object.
_.size = function(obj) {
if (obj == null) return 0;
- return (obj.length === +obj.length) ? obj.length : _.keys(obj).length;
+ return isArrayLike(obj) ? obj.length : _.keys(obj).length;
};
+ // Split a collection into two arrays: one whose elements all satisfy the given
+ // predicate, and one whose elements all do not satisfy the predicate.
+ _.partition = group(function(result, value, pass) {
+ result[pass ? 0 : 1].push(value);
+ }, true);
+
// Array Functions
// ---------------
@@ -398,130 +492,154 @@
// 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) {
- if (array == null) return void 0;
- return (n == null) || guard ? array[0] : slice.call(array, 0, n);
+ if (array == null || array.length < 1) return void 0;
+ if (n == null || guard) return array[0];
+ return _.initial(array, array.length - n);
};
// Returns everything but the last entry of the array. Especially useful on
// the arguments object. Passing **n** will return all the values in
- // the array, excluding the last N. The **guard** check allows it to work with
- // `_.map`.
+ // the array, excluding the last N.
_.initial = function(array, n, guard) {
- return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
+ return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n)));
};
// 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`.
+ // values in the array.
_.last = function(array, n, guard) {
- if (array == null) return void 0;
- if ((n == null) || guard) {
- return array[array.length - 1];
- } else {
- return slice.call(array, Math.max(array.length - n, 0));
- }
+ if (array == null || array.length < 1) return void 0;
+ if (n == null || guard) return array[array.length - 1];
+ return _.rest(array, Math.max(0, array.length - n));
};
// Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
// Especially useful on the arguments object. Passing an **n** will return
- // the rest N values in the array. The **guard**
- // check allows it to work with `_.map`.
+ // the rest N values in the array.
_.rest = _.tail = _.drop = function(array, n, guard) {
- return slice.call(array, (n == null) || guard ? 1 : n);
+ return slice.call(array, n == null || guard ? 1 : n);
};
// Trim out all falsy values from an array.
_.compact = function(array) {
- return _.filter(array, _.identity);
+ return _.filter(array, Boolean);
};
// Internal implementation of a recursive `flatten` function.
- var flatten = function(input, shallow, output) {
- if (shallow && _.every(input, _.isArray)) {
- return concat.apply(output, input);
- }
- each(input, function(value) {
- if (_.isArray(value) || _.isArguments(value)) {
- shallow ? push.apply(output, value) : flatten(value, shallow, output);
- } else {
- output.push(value);
+ var flatten = function(input, shallow, strict, output) {
+ output = output || [];
+ var idx = output.length;
+ for (var i = 0, length = getLength(input); i < length; i++) {
+ var value = input[i];
+ if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) {
+ // Flatten current level of array or arguments object.
+ if (shallow) {
+ var j = 0, len = value.length;
+ while (j < len) output[idx++] = value[j++];
+ } else {
+ flatten(value, shallow, strict, output);
+ idx = output.length;
+ }
+ } else if (!strict) {
+ output[idx++] = value;
}
- });
+ }
return output;
};
// Flatten out an array, either recursively (by default), or just one level.
_.flatten = function(array, shallow) {
- return flatten(array, shallow, []);
+ return flatten(array, shallow, false);
};
// Return a version of the array that does not contain the specified value(s).
- _.without = function(array) {
- return _.difference(array, slice.call(arguments, 1));
- };
+ _.without = restArgs(function(array, otherArrays) {
+ return _.difference(array, otherArrays);
+ });
// Produce a duplicate-free version of the array. If the array has already
// been sorted, you have the option of using a faster algorithm.
// Aliased as `unique`.
- _.uniq = _.unique = function(array, isSorted, iterator, context) {
- if (_.isFunction(isSorted)) {
- context = iterator;
- iterator = isSorted;
+ _.uniq = _.unique = function(array, isSorted, iteratee, context) {
+ if (!_.isBoolean(isSorted)) {
+ context = iteratee;
+ iteratee = isSorted;
isSorted = false;
}
- var initial = iterator ? _.map(array, iterator, context) : array;
- var results = [];
+ if (iteratee != null) iteratee = cb(iteratee, context);
+ var result = [];
var seen = [];
- each(initial, function(value, index) {
- if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) {
- seen.push(value);
- results.push(array[index]);
+ for (var i = 0, length = getLength(array); i < length; i++) {
+ var value = array[i],
+ computed = iteratee ? iteratee(value, i, array) : value;
+ if (isSorted) {
+ if (!i || seen !== computed) result.push(value);
+ seen = computed;
+ } else if (iteratee) {
+ if (!_.contains(seen, computed)) {
+ seen.push(computed);
+ result.push(value);
+ }
+ } else if (!_.contains(result, value)) {
+ result.push(value);
}
- });
- return results;
+ }
+ return result;
};
// Produce an array that contains the union: each distinct element from all of
// the passed-in arrays.
- _.union = function() {
- return _.uniq(_.flatten(arguments, true));
- };
+ _.union = restArgs(function(arrays) {
+ return _.uniq(flatten(arrays, true, true));
+ });
// Produce an array that contains every item shared between all the
// passed-in arrays.
_.intersection = function(array) {
- var rest = slice.call(arguments, 1);
- return _.filter(_.uniq(array), function(item) {
- return _.every(rest, function(other) {
- return _.indexOf(other, item) >= 0;
- });
- });
+ var result = [];
+ var argsLength = arguments.length;
+ for (var i = 0, length = getLength(array); i < length; i++) {
+ var item = array[i];
+ if (_.contains(result, item)) continue;
+ var j;
+ for (j = 1; j < argsLength; j++) {
+ if (!_.contains(arguments[j], item)) break;
+ }
+ if (j === argsLength) result.push(item);
+ }
+ return result;
};
// 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) {
- var rest = concat.apply(ArrayProto, slice.call(arguments, 1));
- return _.filter(array, function(value){ return !_.contains(rest, value); });
+ _.difference = restArgs(function(array, rest) {
+ rest = flatten(rest, true, true);
+ return _.filter(array, function(value){
+ return !_.contains(rest, value);
+ });
+ });
+
+ // Complement of _.zip. Unzip accepts an array of arrays and groups
+ // each array's elements on shared indices.
+ _.unzip = function(array) {
+ var length = array && _.max(array, getLength).length || 0;
+ var result = Array(length);
+
+ for (var index = 0; index < length; index++) {
+ result[index] = _.pluck(array, index);
+ }
+ return result;
};
// Zip together multiple lists into a single array -- elements that share
// an index go together.
- _.zip = function() {
- var length = _.max(_.pluck(arguments, "length").concat(0));
- var results = new Array(length);
- for (var i = 0; i < length; i++) {
- results[i] = _.pluck(arguments, '' + i);
- }
- return results;
- };
+ _.zip = restArgs(_.unzip);
// Converts lists into objects. Pass either a single array of `[key, value]`
// pairs, or two parallel arrays of the same length -- one of keys, and one of
- // the corresponding values.
+ // the corresponding values. Passing by pairs is the reverse of _.pairs.
_.object = function(list, values) {
- if (list == null) return {};
var result = {};
- for (var i = 0, length = list.length; i < length; i++) {
+ for (var i = 0, length = getLength(list); i < length; i++) {
if (values) {
result[list[i]] = values[i];
} else {
@@ -531,127 +649,183 @@
return result;
};
- // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
- // we need this function. Return the position of the first occurrence of an
- // item in an array, or -1 if the item is not included in the array.
- // Delegates to **ECMAScript 5**'s native `indexOf` if available.
- // If the array is large and already in sort order, pass `true`
- // for **isSorted** to use binary search.
- _.indexOf = function(array, item, isSorted) {
- if (array == null) return -1;
- var i = 0, length = array.length;
- if (isSorted) {
- if (typeof isSorted == 'number') {
- i = (isSorted < 0 ? Math.max(0, length + isSorted) : isSorted);
- } else {
- i = _.sortedIndex(array, item);
- return array[i] === item ? i : -1;
+ // Generator function to create the findIndex and findLastIndex functions.
+ var createPredicateIndexFinder = function(dir) {
+ return function(array, predicate, context) {
+ predicate = cb(predicate, context);
+ var length = getLength(array);
+ var index = dir > 0 ? 0 : length - 1;
+ for (; index >= 0 && index < length; index += dir) {
+ if (predicate(array[index], index, array)) return index;
}
- }
- if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted);
- for (; i < length; i++) if (array[i] === item) return i;
- return -1;
+ return -1;
+ };
};
- // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
- _.lastIndexOf = function(array, item, from) {
- if (array == null) return -1;
- var hasIndex = from != null;
- if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) {
- return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item);
+ // Returns the first index on an array-like that passes a predicate test.
+ _.findIndex = createPredicateIndexFinder(1);
+ _.findLastIndex = createPredicateIndexFinder(-1);
+
+ // Use a comparator function to figure out the smallest index at which
+ // an object should be inserted so as to maintain order. Uses binary search.
+ _.sortedIndex = function(array, obj, iteratee, context) {
+ iteratee = cb(iteratee, context, 1);
+ var value = iteratee(obj);
+ var low = 0, high = getLength(array);
+ while (low < high) {
+ var mid = Math.floor((low + high) / 2);
+ if (iteratee(array[mid]) < value) low = mid + 1; else high = mid;
}
- var i = (hasIndex ? from : array.length);
- while (i--) if (array[i] === item) return i;
- return -1;
+ return low;
};
+ // Generator function to create the indexOf and lastIndexOf functions.
+ var createIndexFinder = function(dir, predicateFind, sortedIndex) {
+ return function(array, item, idx) {
+ var i = 0, length = getLength(array);
+ if (typeof idx == 'number') {
+ if (dir > 0) {
+ i = idx >= 0 ? idx : Math.max(idx + length, i);
+ } else {
+ length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1;
+ }
+ } else if (sortedIndex && idx && length) {
+ idx = sortedIndex(array, item);
+ return array[idx] === item ? idx : -1;
+ }
+ if (item !== item) {
+ idx = predicateFind(slice.call(array, i, length), _.isNaN);
+ return idx >= 0 ? idx + i : -1;
+ }
+ for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) {
+ if (array[idx] === item) return idx;
+ }
+ return -1;
+ };
+ };
+
+ // Return the position of the first occurrence of an item in an array,
+ // or -1 if the item is not included in the array.
+ // If the array is large and already in sort order, pass `true`
+ // for **isSorted** to use binary search.
+ _.indexOf = createIndexFinder(1, _.findIndex, _.sortedIndex);
+ _.lastIndexOf = createIndexFinder(-1, _.findLastIndex);
+
// Generate an integer Array containing an arithmetic progression. A port of
// the native Python `range()` function. See
// [the Python documentation](http://docs.python.org/library/functions.html#range).
_.range = function(start, stop, step) {
- if (arguments.length <= 1) {
+ if (stop == null) {
stop = start || 0;
start = 0;
}
- step = arguments[2] || 1;
+ if (!step) {
+ step = stop < start ? -1 : 1;
+ }
var length = Math.max(Math.ceil((stop - start) / step), 0);
- var idx = 0;
- var range = new Array(length);
+ var range = Array(length);
- while(idx < length) {
- range[idx++] = start;
- start += step;
+ for (var idx = 0; idx < length; idx++, start += step) {
+ range[idx] = start;
}
return range;
};
+ // Split an **array** into several arrays containing **count** or less elements
+ // of initial array.
+ _.chunk = function(array, count) {
+ if (count == null || count < 1) return [];
+
+ var result = [];
+ var i = 0, length = array.length;
+ while (i < length) {
+ result.push(slice.call(array, i, i += count));
+ }
+ return result;
+ };
+
// Function (ahem) Functions
// ------------------
- // Reusable constructor function for prototype setting.
- var ctor = function(){};
+ // Determines whether to execute a function as a constructor
+ // or a normal function with the provided arguments.
+ var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) {
+ if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args);
+ var self = baseCreate(sourceFunc.prototype);
+ var result = sourceFunc.apply(self, args);
+ if (_.isObject(result)) return result;
+ return self;
+ };
// Create a function bound to a given object (assigning `this`, and arguments,
// optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
// available.
- _.bind = function(func, context) {
- var args, bound;
- if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
- if (!_.isFunction(func)) throw new TypeError;
- args = slice.call(arguments, 2);
- return bound = function() {
- if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
- ctor.prototype = func.prototype;
- var self = new ctor;
- ctor.prototype = null;
- var result = func.apply(self, args.concat(slice.call(arguments)));
- if (Object(result) === result) return result;
- return self;
- };
- };
+ _.bind = restArgs(function(func, context, args) {
+ if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function');
+ var bound = restArgs(function(callArgs) {
+ return executeBound(func, bound, context, this, args.concat(callArgs));
+ });
+ return bound;
+ });
// Partially apply a function by creating a version that has had some of its
- // arguments pre-filled, without changing its dynamic `this` context.
- _.partial = function(func) {
- var args = slice.call(arguments, 1);
- return function() {
- return func.apply(this, args.concat(slice.call(arguments)));
+ // arguments pre-filled, without changing its dynamic `this` context. _ acts
+ // as a placeholder by default, allowing any combination of arguments to be
+ // pre-filled. Set `_.partial.placeholder` for a custom placeholder argument.
+ _.partial = restArgs(function(func, boundArgs) {
+ var placeholder = _.partial.placeholder;
+ var bound = function() {
+ var position = 0, length = boundArgs.length;
+ var args = Array(length);
+ for (var i = 0; i < length; i++) {
+ args[i] = boundArgs[i] === placeholder ? arguments[position++] : boundArgs[i];
+ }
+ while (position < arguments.length) args.push(arguments[position++]);
+ return executeBound(func, bound, this, this, args);
};
- };
+ return bound;
+ });
- // Bind all of an object's methods to that object. Useful for ensuring that
- // all callbacks defined on an object belong to it.
- _.bindAll = function(obj) {
- var funcs = slice.call(arguments, 1);
- if (funcs.length === 0) throw new Error("bindAll must be passed function names");
- each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
- return obj;
- };
+ _.partial.placeholder = _;
+
+ // Bind a number of an object's methods to that object. Remaining arguments
+ // are the method names to be bound. Useful for ensuring that all callbacks
+ // defined on an object belong to it.
+ _.bindAll = restArgs(function(obj, keys) {
+ keys = flatten(keys, false, false);
+ var index = keys.length;
+ if (index < 1) throw new Error('bindAll must be passed function names');
+ while (index--) {
+ var key = keys[index];
+ obj[key] = _.bind(obj[key], obj);
+ }
+ });
// Memoize an expensive function by storing its results.
_.memoize = function(func, hasher) {
- var memo = {};
- hasher || (hasher = _.identity);
- return function() {
- var key = hasher.apply(this, arguments);
- return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
+ var memoize = function(key) {
+ var cache = memoize.cache;
+ var address = '' + (hasher ? hasher.apply(this, arguments) : key);
+ if (!_.has(cache, address)) cache[address] = func.apply(this, arguments);
+ return cache[address];
};
+ memoize.cache = {};
+ return memoize;
};
// Delays a function for the given number of milliseconds, and then calls
// it with the arguments supplied.
- _.delay = function(func, wait) {
- var args = slice.call(arguments, 2);
- return setTimeout(function(){ return func.apply(null, args); }, wait);
- };
+ _.delay = restArgs(function(func, wait, args) {
+ return setTimeout(function() {
+ return func.apply(null, args);
+ }, wait);
+ });
// Defers a function, scheduling it to run after the current call stack has
// cleared.
- _.defer = function(func) {
- return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
- };
+ _.defer = _.partial(_.delay, _, 1);
// Returns a function, that, when invoked, will only be triggered at most once
// during a given window of time. Normally, the throttled function will run
@@ -659,31 +833,44 @@
// but if you'd like to disable the execution on the leading edge, pass
// `{leading: false}`. To disable execution on the trailing edge, ditto.
_.throttle = function(func, wait, options) {
- var context, args, result;
- var timeout = null;
+ var timeout, context, args, result;
var previous = 0;
- options || (options = {});
+ if (!options) options = {};
+
var later = function() {
- previous = options.leading === false ? 0 : new Date;
+ previous = options.leading === false ? 0 : _.now();
timeout = null;
result = func.apply(context, args);
+ if (!timeout) context = args = null;
};
- return function() {
- var now = new Date;
+
+ var throttled = function() {
+ var now = _.now();
if (!previous && options.leading === false) previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
- if (remaining <= 0) {
- clearTimeout(timeout);
- timeout = null;
+ if (remaining <= 0 || remaining > wait) {
+ if (timeout) {
+ clearTimeout(timeout);
+ timeout = null;
+ }
previous = now;
result = func.apply(context, args);
+ if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
+
+ throttled.cancel = function() {
+ clearTimeout(timeout);
+ previous = 0;
+ timeout = context = args = null;
+ };
+
+ return throttled;
};
// Returns a function, that, as long as it continues to be invoked, will not
@@ -691,67 +878,62 @@
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
_.debounce = function(func, wait, immediate) {
- var timeout, args, context, timestamp, result;
- return function() {
- context = this;
- args = arguments;
- timestamp = new Date();
- var later = function() {
- var last = (new Date()) - timestamp;
- if (last < wait) {
- timeout = setTimeout(later, wait - last);
- } else {
- timeout = null;
- if (!immediate) result = func.apply(context, args);
- }
- };
- var callNow = immediate && !timeout;
- if (!timeout) {
- timeout = setTimeout(later, wait);
- }
- if (callNow) result = func.apply(context, args);
- return result;
- };
- };
+ var timeout, result;
- // Returns a function that will be executed at most one time, no matter how
- // often you call it. Useful for lazy initialization.
- _.once = function(func) {
- var ran = false, memo;
- return function() {
- if (ran) return memo;
- ran = true;
- memo = func.apply(this, arguments);
- func = null;
- return memo;
+ var later = function(context, args) {
+ timeout = null;
+ if (args) result = func.apply(context, args);
};
+
+ var debounced = restArgs(function(args) {
+ if (timeout) clearTimeout(timeout);
+ if (immediate) {
+ var callNow = !timeout;
+ timeout = setTimeout(later, wait);
+ if (callNow) result = func.apply(this, args);
+ } else {
+ timeout = _.delay(later, wait, this, args);
+ }
+
+ return result;
+ });
+
+ debounced.cancel = function() {
+ clearTimeout(timeout);
+ timeout = null;
+ };
+
+ return debounced;
};
// Returns the first function passed as an argument to the second,
// allowing you to adjust arguments, run code before and after, and
// conditionally execute the original function.
_.wrap = function(func, wrapper) {
+ return _.partial(wrapper, func);
+ };
+
+ // Returns a negated version of the passed-in predicate.
+ _.negate = function(predicate) {
return function() {
- var args = [func];
- push.apply(args, arguments);
- return wrapper.apply(this, args);
+ return !predicate.apply(this, arguments);
};
};
// 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 = arguments;
+ var args = arguments;
+ var start = args.length - 1;
return function() {
- var args = arguments;
- for (var i = funcs.length - 1; i >= 0; i--) {
- args = [funcs[i].apply(this, args)];
- }
- return args[0];
+ var i = start;
+ var result = args[start].apply(this, arguments);
+ while (i--) result = args[i].call(this, result);
+ return result;
};
};
- // Returns a function that will only be executed after being called N times.
+ // Returns a function that will only be executed on and after the Nth call.
_.after = function(times, func) {
return function() {
if (--times < 1) {
@@ -760,15 +942,68 @@
};
};
+ // Returns a function that will only be executed up to (but not including) the Nth call.
+ _.before = function(times, func) {
+ var memo;
+ return function() {
+ if (--times > 0) {
+ memo = func.apply(this, arguments);
+ }
+ if (times <= 1) func = null;
+ return memo;
+ };
+ };
+
+ // Returns a function that will be executed at most one time, no matter how
+ // often you call it. Useful for lazy initialization.
+ _.once = _.partial(_.before, 2);
+
+ _.restArgs = restArgs;
+
// Object Functions
// ----------------
- // Retrieve the names of an object's properties.
- // Delegates to **ECMAScript 5**'s native `Object.keys`
- _.keys = nativeKeys || function(obj) {
- if (obj !== Object(obj)) throw new TypeError('Invalid object');
+ // Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed.
+ var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString');
+ var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString',
+ 'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString'];
+
+ var collectNonEnumProps = function(obj, keys) {
+ var nonEnumIdx = nonEnumerableProps.length;
+ var constructor = obj.constructor;
+ var proto = _.isFunction(constructor) && constructor.prototype || ObjProto;
+
+ // Constructor is a special case.
+ var prop = 'constructor';
+ if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop);
+
+ while (nonEnumIdx--) {
+ prop = nonEnumerableProps[nonEnumIdx];
+ if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) {
+ keys.push(prop);
+ }
+ }
+ };
+
+ // Retrieve the names of an object's own properties.
+ // Delegates to **ECMAScript 5**'s native `Object.keys`.
+ _.keys = function(obj) {
+ if (!_.isObject(obj)) return [];
+ if (nativeKeys) return nativeKeys(obj);
var keys = [];
for (var key in obj) if (_.has(obj, key)) keys.push(key);
+ // Ahem, IE < 9.
+ if (hasEnumBug) collectNonEnumProps(obj, keys);
+ return keys;
+ };
+
+ // Retrieve all the property names of an object.
+ _.allKeys = function(obj) {
+ if (!_.isObject(obj)) return [];
+ var keys = [];
+ for (var key in obj) keys.push(key);
+ // Ahem, IE < 9.
+ if (hasEnumBug) collectNonEnumProps(obj, keys);
return keys;
};
@@ -776,18 +1011,33 @@
_.values = function(obj) {
var keys = _.keys(obj);
var length = keys.length;
- var values = new Array(length);
+ var values = Array(length);
for (var i = 0; i < length; i++) {
values[i] = obj[keys[i]];
}
return values;
};
+ // Returns the results of applying the iteratee to each element of the object.
+ // In contrast to _.map it returns an object.
+ _.mapObject = function(obj, iteratee, context) {
+ iteratee = cb(iteratee, context);
+ var keys = _.keys(obj),
+ length = keys.length,
+ results = {};
+ for (var index = 0; index < length; index++) {
+ var currentKey = keys[index];
+ results[currentKey] = iteratee(obj[currentKey], currentKey, obj);
+ }
+ return results;
+ };
+
// Convert an object into a list of `[key, value]` pairs.
+ // The opposite of _.object.
_.pairs = function(obj) {
var keys = _.keys(obj);
var length = keys.length;
- var pairs = new Array(length);
+ var pairs = Array(length);
for (var i = 0; i < length; i++) {
pairs[i] = [keys[i], obj[keys[i]]];
}
@@ -805,7 +1055,7 @@
};
// Return a sorted list of the function names available on the object.
- // Aliased as `methods`
+ // Aliased as `methods`.
_.functions = _.methods = function(obj) {
var names = [];
for (var key in obj) {
@@ -814,48 +1064,92 @@
return names.sort();
};
- // Extend a given object with all the properties in passed-in object(s).
- _.extend = function(obj) {
- each(slice.call(arguments, 1), function(source) {
- if (source) {
- for (var prop in source) {
- obj[prop] = source[prop];
+ // An internal function for creating assigner functions.
+ var createAssigner = function(keysFunc, defaults) {
+ return function(obj) {
+ var length = arguments.length;
+ if (defaults) obj = Object(obj);
+ if (length < 2 || obj == null) return obj;
+ for (var index = 1; index < length; index++) {
+ var source = arguments[index],
+ keys = keysFunc(source),
+ l = keys.length;
+ for (var i = 0; i < l; i++) {
+ var key = keys[i];
+ if (!defaults || obj[key] === void 0) obj[key] = source[key];
}
}
- });
- return obj;
+ return obj;
+ };
+ };
+
+ // Extend a given object with all the properties in passed-in object(s).
+ _.extend = createAssigner(_.allKeys);
+
+ // Assigns a given object with all the own properties in the passed-in object(s).
+ // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)
+ _.extendOwn = _.assign = createAssigner(_.keys);
+
+ // Returns the first key on an object that passes a predicate test.
+ _.findKey = function(obj, predicate, context) {
+ predicate = cb(predicate, context);
+ var keys = _.keys(obj), key;
+ for (var i = 0, length = keys.length; i < length; i++) {
+ key = keys[i];
+ if (predicate(obj[key], key, obj)) return key;
+ }
+ };
+
+ // Internal pick helper function to determine if `obj` has key `key`.
+ var keyInObj = function(value, key, obj) {
+ return key in obj;
};
// Return a copy of the object only containing the whitelisted properties.
- _.pick = function(obj) {
- var copy = {};
- var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
- each(keys, function(key) {
- if (key in obj) copy[key] = obj[key];
- });
- return copy;
- };
-
- // Return a copy of the object without the blacklisted properties.
- _.omit = function(obj) {
- var copy = {};
- var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
- for (var key in obj) {
- if (!_.contains(keys, key)) copy[key] = obj[key];
+ _.pick = restArgs(function(obj, keys) {
+ var result = {}, iteratee = keys[0];
+ if (obj == null) return result;
+ if (_.isFunction(iteratee)) {
+ if (keys.length > 1) iteratee = optimizeCb(iteratee, keys[1]);
+ keys = _.allKeys(obj);
+ } else {
+ iteratee = keyInObj;
+ keys = flatten(keys, false, false);
+ obj = Object(obj);
}
- return copy;
- };
+ for (var i = 0, length = keys.length; i < length; i++) {
+ var key = keys[i];
+ var value = obj[key];
+ if (iteratee(value, key, obj)) result[key] = value;
+ }
+ return result;
+ });
+
+ // Return a copy of the object without the blacklisted properties.
+ _.omit = restArgs(function(obj, keys) {
+ var iteratee = keys[0], context;
+ if (_.isFunction(iteratee)) {
+ iteratee = _.negate(iteratee);
+ if (keys.length > 1) context = keys[1];
+ } else {
+ keys = _.map(flatten(keys, false, false), String);
+ iteratee = function(value, key) {
+ return !_.contains(keys, key);
+ };
+ }
+ return _.pick(obj, iteratee, context);
+ });
// Fill in a given object with default properties.
- _.defaults = function(obj) {
- each(slice.call(arguments, 1), function(source) {
- if (source) {
- for (var prop in source) {
- if (obj[prop] === void 0) obj[prop] = source[prop];
- }
- }
- });
- return obj;
+ _.defaults = createAssigner(_.allKeys, true);
+
+ // Creates an object that inherits from the given prototype object.
+ // If additional properties are provided then they will be added to the
+ // created object.
+ _.create = function(prototype, props) {
+ var result = baseCreate(prototype);
+ if (props) _.extendOwn(result, props);
+ return result;
};
// Create a (shallow-cloned) duplicate of an object.
@@ -872,109 +1166,136 @@
return obj;
};
+ // Returns whether an object has a given set of `key:value` pairs.
+ _.isMatch = function(object, attrs) {
+ var keys = _.keys(attrs), length = keys.length;
+ if (object == null) return !length;
+ var obj = Object(object);
+ for (var i = 0; i < length; i++) {
+ var key = keys[i];
+ if (attrs[key] !== obj[key] || !(key in obj)) return false;
+ }
+ return true;
+ };
+
+
// Internal recursive comparison function for `isEqual`.
- var eq = function(a, b, aStack, bStack) {
+ var eq, deepEq;
+ eq = function(a, b, aStack, bStack) {
// Identical objects are equal. `0 === -0`, but they aren't identical.
// 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 === b) return a !== 0 || 1 / a === 1 / b;
+ // `null` or `undefined` only equal to itself (strict comparison).
+ if (a == null || b == null) return false;
+ // `NaN`s are equivalent, but non-reflexive.
+ if (a !== a) return b !== b;
+ // Exhaust primitive checks
+ var type = typeof a;
+ if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;
+ return deepEq(a, b, aStack, bStack);
+ };
+
+ // Internal recursive comparison function for `isEqual`.
+ deepEq = function(a, b, aStack, bStack) {
// Unwrap any wrapped objects.
if (a instanceof _) a = a._wrapped;
if (b instanceof _) b = b._wrapped;
// Compare `[[Class]]` names.
var className = toString.call(a);
- if (className != toString.call(b)) return false;
+ if (className !== toString.call(b)) return false;
switch (className) {
- // Strings, numbers, dates, and booleans are compared by value.
+ // Strings, numbers, regular expressions, dates, and booleans are compared by value.
+ case '[object RegExp]':
+ // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
case '[object String]':
// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
// equivalent to `new String("5")`.
- return a == String(b);
+ return '' + a === '' + 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);
+ // `NaN`s are equivalent, but non-reflexive.
+ // Object(NaN) is equivalent to NaN.
+ if (+a !== +a) return +b !== +b;
+ // An `egal` comparison is performed for other numeric values.
+ return +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;
+ return +a === +b;
+ case '[object Symbol]':
+ return SymbolProto.valueOf.call(a) === SymbolProto.valueOf.call(b);
+ }
+
+ var areArrays = className === '[object Array]';
+ if (!areArrays) {
+ if (typeof a != 'object' || typeof b != 'object') return false;
+
+ // Objects with different constructors are not equivalent, but `Object`s or `Array`s
+ // from different frames are.
+ var aCtor = a.constructor, bCtor = b.constructor;
+ if (aCtor !== bCtor && !(_.isFunction(aCtor) && aCtor instanceof aCtor &&
+ _.isFunction(bCtor) && bCtor instanceof bCtor)
+ && ('constructor' in a && 'constructor' in b)) {
+ 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`.
+
+ // Initializing stack of traversed objects.
+ // It's done here since we only need them for objects and arrays comparison.
+ aStack = aStack || [];
+ bStack = bStack || [];
var length = aStack.length;
while (length--) {
// Linear search. Performance is inversely proportional to the number of
// unique nested structures.
- if (aStack[length] == a) return bStack[length] == b;
- }
- // Objects with different constructors are not equivalent, but `Object`s
- // from different frames are.
- var aCtor = a.constructor, bCtor = b.constructor;
- if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) &&
- _.isFunction(bCtor) && (bCtor instanceof bCtor))) {
- return false;
+ if (aStack[length] === a) return bStack[length] === b;
}
+
// Add the first object to the stack of traversed objects.
aStack.push(a);
bStack.push(b);
- var size = 0, result = true;
+
// Recursively compare objects and arrays.
- if (className == '[object Array]') {
+ if (areArrays) {
// 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--) {
- if (!(result = eq(a[size], b[size], aStack, bStack))) break;
- }
+ length = a.length;
+ if (length !== b.length) return false;
+ // Deep compare the contents, ignoring non-numeric properties.
+ while (length--) {
+ if (!eq(a[length], b[length], aStack, bStack)) return false;
}
} else {
// 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], aStack, bStack))) 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;
+ var keys = _.keys(a), key;
+ length = keys.length;
+ // Ensure that both objects contain the same number of properties before comparing deep equality.
+ if (_.keys(b).length !== length) return false;
+ while (length--) {
+ // Deep compare each member
+ key = keys[length];
+ if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false;
}
}
// Remove the first object from the stack of traversed objects.
aStack.pop();
bStack.pop();
- return result;
+ return true;
};
// Perform a deep comparison to check if two objects are equal.
_.isEqual = function(a, b) {
- return eq(a, b, [], []);
+ return eq(a, b);
};
// 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 (_.has(obj, key)) return false;
- return true;
+ if (isArrayLike(obj) && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0;
+ return _.keys(obj).length === 0;
};
// Is a given value a DOM element?
@@ -985,49 +1306,52 @@
// Is a given value an array?
// Delegates to ECMA5's native Array.isArray
_.isArray = nativeIsArray || function(obj) {
- return toString.call(obj) == '[object Array]';
+ return toString.call(obj) === '[object Array]';
};
// Is a given variable an object?
_.isObject = function(obj) {
- return obj === Object(obj);
+ var type = typeof obj;
+ return type === 'function' || type === 'object' && !!obj;
};
- // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.
- each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {
+ // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError, isMap, isWeakMap, isSet, isWeakSet.
+ _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error', 'Symbol', 'Map', 'WeakMap', 'Set', 'WeakSet'], function(name) {
_['is' + name] = function(obj) {
- return toString.call(obj) == '[object ' + name + ']';
+ return toString.call(obj) === '[object ' + name + ']';
};
});
- // Define a fallback version of the method in browsers (ahem, IE), where
+ // Define a fallback version of the method in browsers (ahem, IE < 9), where
// there isn't any inspectable "Arguments" type.
if (!_.isArguments(arguments)) {
_.isArguments = function(obj) {
- return !!(obj && _.has(obj, 'callee'));
+ return _.has(obj, 'callee');
};
}
- // Optimize `isFunction` if appropriate.
- if (typeof (/./) !== 'function') {
+ // Optimize `isFunction` if appropriate. Work around some typeof bugs in old v8,
+ // IE 11 (#1621), Safari 8 (#1929), and PhantomJS (#2236).
+ var nodelist = root.document && root.document.childNodes;
+ if (typeof /./ != 'function' && typeof Int8Array != 'object' && typeof nodelist != 'function') {
_.isFunction = function(obj) {
- return typeof obj === 'function';
+ return typeof obj == 'function' || false;
};
}
// Is a given object a finite number?
_.isFinite = function(obj) {
- return isFinite(obj) && !isNaN(parseFloat(obj));
+ return !_.isSymbol(obj) && isFinite(obj) && !isNaN(parseFloat(obj));
};
- // Is the given value `NaN`? (NaN is the only number which does not equal itself).
+ // Is the given value `NaN`?
_.isNaN = function(obj) {
- return _.isNumber(obj) && obj != +obj;
+ return _.isNumber(obj) && isNaN(obj);
};
// Is a given value a boolean?
_.isBoolean = function(obj) {
- return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
+ return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
};
// Is a given value equal to null?
@@ -1042,8 +1366,19 @@
// Shortcut function for checking if an object has a given property directly
// on itself (in other words, not on a prototype).
- _.has = function(obj, key) {
- return hasOwnProperty.call(obj, key);
+ _.has = function(obj, path) {
+ if (!_.isArray(path)) {
+ return obj != null && hasOwnProperty.call(obj, path);
+ }
+ var length = path.length;
+ for (var i = 0; i < length; i++) {
+ var key = path[i];
+ if (obj == null || !hasOwnProperty.call(obj, key)) {
+ return false;
+ }
+ obj = obj[key];
+ }
+ return !!length;
};
// Utility Functions
@@ -1056,15 +1391,53 @@
return this;
};
- // Keep the identity function around for default iterators.
+ // Keep the identity function around for default iteratees.
_.identity = function(value) {
return value;
};
+ // Predicate-generating functions. Often useful outside of Underscore.
+ _.constant = function(value) {
+ return function() {
+ return value;
+ };
+ };
+
+ _.noop = function(){};
+
+ _.property = function(path) {
+ if (!_.isArray(path)) {
+ return shallowProperty(path);
+ }
+ return function(obj) {
+ return deepGet(obj, path);
+ };
+ };
+
+ // Generates a function for a given object that returns a given property.
+ _.propertyOf = function(obj) {
+ if (obj == null) {
+ return function(){};
+ }
+ return function(path) {
+ return !_.isArray(path) ? obj[path] : deepGet(obj, path);
+ };
+ };
+
+ // Returns a predicate for checking whether an object has a given set of
+ // `key:value` pairs.
+ _.matcher = _.matches = function(attrs) {
+ attrs = _.extendOwn({}, attrs);
+ return function(obj) {
+ return _.isMatch(obj, attrs);
+ };
+ };
+
// Run a function **n** times.
- _.times = function(n, iterator, context) {
+ _.times = function(n, iteratee, context) {
var accum = Array(Math.max(0, n));
- for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i);
+ iteratee = optimizeCb(iteratee, context, 1);
+ for (var i = 0; i < n; i++) accum[i] = iteratee(i);
return accum;
};
@@ -1077,52 +1450,57 @@
return min + Math.floor(Math.random() * (max - min + 1));
};
- // List of HTML entities for escaping.
- var entityMap = {
- escape: {
- '&': '&',
- '<': '<',
- '>': '>',
- '"': '"',
- "'": '''
- }
+ // A (possibly faster) way to get the current timestamp as an integer.
+ _.now = Date.now || function() {
+ return new Date().getTime();
};
- entityMap.unescape = _.invert(entityMap.escape);
- // Regexes containing the keys and values listed immediately above.
- var entityRegexes = {
- escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'),
- unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g')
+ // List of HTML entities for escaping.
+ var escapeMap = {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": ''',
+ '`': '`'
};
+ var unescapeMap = _.invert(escapeMap);
// Functions for escaping and unescaping strings to/from HTML interpolation.
- _.each(['escape', 'unescape'], function(method) {
- _[method] = function(string) {
- if (string == null) return '';
- return ('' + string).replace(entityRegexes[method], function(match) {
- return entityMap[method][match];
- });
+ var createEscaper = function(map) {
+ var escaper = function(match) {
+ return map[match];
+ };
+ // Regexes for identifying a key that needs to be escaped.
+ var source = '(?:' + _.keys(map).join('|') + ')';
+ var testRegexp = RegExp(source);
+ var replaceRegexp = RegExp(source, 'g');
+ return function(string) {
+ string = string == null ? '' : '' + string;
+ return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string;
};
- });
-
- // If the value of the named `property` is a function then invoke it with the
- // `object` as context; otherwise, return it.
- _.result = function(object, property) {
- if (object == null) return void 0;
- var value = object[property];
- return _.isFunction(value) ? value.call(object) : value;
};
+ _.escape = createEscaper(escapeMap);
+ _.unescape = createEscaper(unescapeMap);
- // Add your own custom functions to the Underscore object.
- _.mixin = function(obj) {
- each(_.functions(obj), function(name) {
- var func = _[name] = obj[name];
- _.prototype[name] = function() {
- var args = [this._wrapped];
- push.apply(args, arguments);
- return result.call(this, func.apply(_, args));
- };
- });
+ // Traverses the children of `obj` along `path`. If a child is a function, it
+ // is invoked with its parent as context. Returns the value of the final
+ // child, or `fallback` if any child is undefined.
+ _.result = function(obj, path, fallback) {
+ if (!_.isArray(path)) path = [path];
+ var length = path.length;
+ if (!length) {
+ return _.isFunction(fallback) ? fallback.call(obj) : fallback;
+ }
+ for (var i = 0; i < length; i++) {
+ var prop = obj == null ? void 0 : obj[path[i]];
+ if (prop === void 0) {
+ prop = fallback;
+ i = length; // Ensure we don't continue iterating.
+ }
+ obj = _.isFunction(prop) ? prop.call(obj) : prop;
+ }
+ return obj;
};
// Generate a unique integer id (unique within the entire client session).
@@ -1136,9 +1514,9 @@
// By default, Underscore uses ERB-style template delimiters, change the
// following template settings to use alternative delimiters.
_.templateSettings = {
- evaluate : /<%([\s\S]+?)%>/g,
- interpolate : /<%=([\s\S]+?)%>/g,
- escape : /<%-([\s\S]+?)%>/g
+ evaluate: /<%([\s\S]+?)%>/g,
+ interpolate: /<%=([\s\S]+?)%>/g,
+ escape: /<%-([\s\S]+?)%>/g
};
// When customizing `templateSettings`, if you don't want to define an
@@ -1149,26 +1527,30 @@
// Certain characters need to be escaped so that they can be put into a
// string literal.
var escapes = {
- "'": "'",
- '\\': '\\',
- '\r': 'r',
- '\n': 'n',
- '\t': 't',
+ "'": "'",
+ '\\': '\\',
+ '\r': 'r',
+ '\n': 'n',
'\u2028': 'u2028',
'\u2029': 'u2029'
};
- var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
+ var escapeRegExp = /\\|'|\r|\n|\u2028|\u2029/g;
+
+ var escapeChar = function(match) {
+ return '\\' + escapes[match];
+ };
// 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(text, data, settings) {
- var render;
+ // NB: `oldSettings` only exists for backwards compatibility.
+ _.template = function(text, settings, oldSettings) {
+ if (!settings && oldSettings) settings = oldSettings;
settings = _.defaults({}, settings, _.templateSettings);
// Combine delimiters into one regular expression via alternation.
- var matcher = new RegExp([
+ var matcher = RegExp([
(settings.escape || noMatch).source,
(settings.interpolate || noMatch).source,
(settings.evaluate || noMatch).source
@@ -1178,19 +1560,18 @@
var index = 0;
var source = "__p+='";
text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
- source += text.slice(index, offset)
- .replace(escaper, function(match) { return '\\' + escapes[match]; });
+ source += text.slice(index, offset).replace(escapeRegExp, escapeChar);
+ index = offset + match.length;
if (escape) {
source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
- }
- if (interpolate) {
+ } else if (interpolate) {
source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
- }
- if (evaluate) {
+ } else if (evaluate) {
source += "';\n" + evaluate + "\n__p+='";
}
- index = offset + match.length;
+
+ // Adobe VMs need the match returned to produce the correct offset.
return match;
});
source += "';\n";
@@ -1200,8 +1581,9 @@
source = "var __t,__p='',__j=Array.prototype.join," +
"print=function(){__p+=__j.call(arguments,'');};\n" +
- source + "return __p;\n";
+ source + 'return __p;\n';
+ var render;
try {
render = new Function(settings.variable || 'obj', '_', source);
} catch (e) {
@@ -1209,20 +1591,22 @@
throw e;
}
- if (data) return render(data, _);
var template = function(data) {
return render.call(this, data, _);
};
- // Provide the compiled function source as a convenience for precompilation.
- template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
+ // Provide the compiled source as a convenience for precompilation.
+ var argument = settings.variable || 'obj';
+ template.source = 'function(' + argument + '){\n' + source + '}';
return template;
};
- // Add a "chain" function, which will delegate to the wrapper.
+ // Add a "chain" function. Start chaining a wrapped Underscore object.
_.chain = function(obj) {
- return _(obj).chain();
+ var instance = _(obj);
+ instance._chain = true;
+ return instance;
};
// OOP
@@ -1232,45 +1616,68 @@
// underscore functions. Wrapped objects may be chained.
// Helper function to continue chaining intermediate results.
- var result = function(obj) {
- return this._chain ? _(obj).chain() : obj;
+ var chainResult = function(instance, obj) {
+ return instance._chain ? _(obj).chain() : obj;
+ };
+
+ // Add your own custom functions to the Underscore object.
+ _.mixin = function(obj) {
+ _.each(_.functions(obj), function(name) {
+ var func = _[name] = obj[name];
+ _.prototype[name] = function() {
+ var args = [this._wrapped];
+ push.apply(args, arguments);
+ return chainResult(this, func.apply(_, args));
+ };
+ });
+ return _;
};
// Add all of the Underscore functions to the wrapper object.
_.mixin(_);
// Add all mutator Array functions to the wrapper.
- each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
+ _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
var method = ArrayProto[name];
_.prototype[name] = function() {
var obj = this._wrapped;
method.apply(obj, arguments);
- if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0];
- return result.call(this, obj);
+ if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];
+ return chainResult(this, obj);
};
});
// Add all accessor Array functions to the wrapper.
- each(['concat', 'join', 'slice'], function(name) {
+ _.each(['concat', 'join', 'slice'], function(name) {
var method = ArrayProto[name];
_.prototype[name] = function() {
- return result.call(this, method.apply(this._wrapped, arguments));
+ return chainResult(this, method.apply(this._wrapped, arguments));
};
});
- _.extend(_.prototype, {
+ // Extracts the result from a wrapped and chained object.
+ _.prototype.value = function() {
+ return this._wrapped;
+ };
- // Start chaining a wrapped Underscore object.
- chain: function() {
- this._chain = true;
- return this;
- },
+ // Provide unwrapping proxy for some methods used in engine operations
+ // such as arithmetic and JSON stringification.
+ _.prototype.valueOf = _.prototype.toJSON = _.prototype.value;
- // Extracts the result from a wrapped and chained object.
- value: function() {
- return this._wrapped;
- }
+ _.prototype.toString = function() {
+ return String(this._wrapped);
+ };
- });
-
-}).call(this);
+ // AMD registration happens at the end for compatibility with AMD loaders
+ // that may not enforce next-turn semantics on modules. Even though general
+ // practice for AMD registration is to be anonymous, underscore registers
+ // as a named module because, like jQuery, it is a base library that is
+ // popular enough to be bundled in a third party lib, but not be part of
+ // an AMD load request. Those cases could generate an error when an
+ // anonymous define() is called outside of a loader request.
+ if (typeof define == 'function' && define.amd) {
+ define('underscore', [], function() {
+ return _;
+ });
+ }
+}());