Compare commits

...

77 Commits
0.6.1 ... 0.7.0

Author SHA1 Message Date
John-David Dalton
83c6fb089e Ensure mobile build has properties correctly minified.
Former-commit-id: 61da738afbcefc7ecd277190147041d884002af1
2012-09-11 09:15:32 -07:00
John-David Dalton
9d8d17b964 Update cdn link in README.md.
Former-commit-id: 5da17daf1a6d1d5c9f6e9ba7875f45bd2d763cda
2012-09-11 07:41:47 -07:00
John-David Dalton
39d4842ff5 Bump to v0.7.0.
Former-commit-id: 7c51a310c7c62bfe0ba9a2cdea4e074e633cee70
2012-09-11 02:53:12 -07:00
John-David Dalton
cad8473986 Re-add overwritten _.isEqual unit test.
Former-commit-id: 52d4e3bc02a6fd0ac30177c9da82dec60ee6eb81
2012-09-10 23:41:20 -07:00
John-David Dalton
5f085ccb52 Fix typo in _.isEqual.
Former-commit-id: 9d5065953c51d12f1308dd8c0c142b6505efe765
2012-09-10 23:04:42 -07:00
John-David Dalton
b406246689 Add "iife=.." command to build.js.
Former-commit-id: 85d8c7ea550403663a878f2713ce93ae8c2dbc6a
2012-09-10 22:52:28 -07:00
John-David Dalton
cbe46afdff Minor lodash.js cleanup.
Former-commit-id: 544f2a2690b48b52870b3ab62509221a82ed4173
2012-09-10 21:04:04 -07:00
John-David Dalton
a59d6dc3c7 Add minify.js Underscore unit test.
Former-commit-id: 1db7b19709ef953dd1996a082e73a2ba542f29f7
2012-09-10 20:35:27 -07:00
John-David Dalton
2afb2dd5fd Reduce temporary objects created in _.merge, _.clone, and _.isEqual.
Former-commit-id: e6696642505f39eefdf59075ff8a993ab033465a
2012-09-10 20:12:42 -07:00
John-David Dalton
4fc3c969d3 Add component.json for Bower.
Former-commit-id: d681c2300b4e79fb168793aed34b82c3021647d1
2012-09-10 00:48:12 -07:00
John-David Dalton
1796ce324b Cleanup build.js
Former-commit-id: 6a4502883d7431f5dcedbf7f7d3bfb871ce1c0f4
2012-09-09 23:30:38 -07:00
John-David Dalton
5e04c7f827 Add "output" and "stdout" build option unit tests.
Former-commit-id: 2adcdbff4cd1ef6319e33c69fd5ed3b07b205cfe
2012-09-09 20:33:08 -07:00
John-David Dalton
20fcede440 Add "-o" and "--output" build options.
Former-commit-id: 154c0a6b749ff2439c024602fb9ec781e293f511
2012-09-09 17:26:30 -07:00
John-David Dalton
012c1833f2 Added "-c" and "--stdout" options to build.js.
Former-commit-id: 388c529ca1836ee7cd65517d2e9f8533e480b8cd
2012-09-09 16:33:57 -07:00
John-David Dalton
32e8e03256 Add build "exports" unit tests.
Former-commit-id: afe0fe59933d272bfa597be835011b3c81b28dda
2012-09-09 16:00:11 -07:00
John-David Dalton
dca653cb92 Update minified build and docs.
Former-commit-id: a7719898e67aab04d37e775f4794b0be7a4a2e24
2012-09-09 14:13:18 -07:00
John-David Dalton
0805eca979 Remove Object.keys optimization from the "legacy" build iteratorTemplate.
Former-commit-id: 5a63b627c4982ca43a46a830722513cab2c7b633
2012-09-09 14:11:46 -07:00
John-David Dalton
17935a78ff Make previous _.isEqual fix pass Underscore unit tests.
Former-commit-id: 2b3563bb628b307ad2e4a2ef00ed5afec2f59506
2012-09-09 14:11:07 -07:00
John-David Dalton
e16918ee32 Add "exports" option to build.js.
Former-commit-id: cc1572dbe9d1367f806a44597cbcec8508f51ad6
2012-09-09 13:12:44 -07:00
John-David Dalton
78471b4595 Cleanup test files.
Former-commit-id: 3b138bc74c2f4c2c6d374893c0f90a8422a7248b
2012-09-09 11:55:24 -07:00
John-David Dalton
c30bcdd515 Ensure _.isEqual matches values with circular references correctly.
Former-commit-id: 07968aeb430f56c32aab22dfda919706da840680
2012-09-09 11:54:32 -07:00
John-David Dalton
ac78c5f4e5 Make minify.js support passing minify an array of command-line arguments.
Former-commit-id: fd67d3d6dd8b19c88c74529a33fd50b2fbd0db01
2012-09-08 23:43:36 -07:00
John-David Dalton
57a990ce25 Add "underscore" build test.
Former-commit-id: 8050e285fae94c96e7db1c8847ace45ae5cade33
2012-09-08 19:30:58 -07:00
John-David Dalton
24825b42a2 Cleanup and add test-build to run-test.sh.
Former-commit-id: 29d198ca03dbd23b864a96ea48348fb22728056a
2012-09-08 14:20:46 -07:00
John-David Dalton
4f7323f7fc Add test/test-build.js.
Former-commit-id: b0c28b814dec71095a927469cbbda766fd9fc701
2012-09-08 14:03:21 -07:00
John-David Dalton
a228be85e2 Cleanup compareAscending.
Former-commit-id: c11be9f8211242a8d25a2cd06e20efefa685c3ee
2012-09-07 23:56:28 -07:00
John-David Dalton
fa565bdbdf Hold off on the version bump until test-build.js is finished.
Former-commit-id: a627062b1133cdb5a06a3fd960bbeaddfd0f9a54
2012-09-07 22:00:27 -07:00
John-David Dalton
2dc53223e5 Ensure _.template works with "interpolate" delimiters containing ternary operators. [closes #68]
Former-commit-id: 287df2ef5802ea6db743da5f211e480d6b0f85c9
2012-09-07 21:17:00 -07:00
John-David Dalton
958ac72805 Ensure the internal stack argument of _.merge doesn't pave the 4th argument passed to it. [closes #69]
Former-commit-id: b33e1cb7795294b9481e2c9c6888d0f37419208d
2012-09-07 21:09:21 -07:00
John-David Dalton
f7297b84e7 Tweak text of build.js help message.
Former-commit-id: 755872c9ae5670cc3a33aa158be4478eafacc574
2012-09-06 23:27:00 -07:00
John-David Dalton
9a7d9e7bb8 Ensure _.sortBy is stable for undefined values.
Former-commit-id: bf250150d27de050ea7a6fa376aacdc8d1ba7716
2012-09-06 22:35:20 -07:00
John-David Dalton
fa9df75cf7 Rework build.js to work as a module and add a "silent" mode to minify.js.
Former-commit-id: cf62532b957d37da77a2d64aa64d2d388e6382ae
2012-09-06 22:19:43 -07:00
John-David Dalton
e3ec76418b Update Underscore/Backbone vendors.
Former-commit-id: beb38126acaebf1045c2676aeda037e35f0b99c8
2012-09-06 20:56:02 -07:00
John-David Dalton
102d6d8c84 Capture the result of the last func call in _.throttle and _.debounce.
Former-commit-id: 2e783fad2e86824bf098bdb24ca6911317576f32
2012-09-06 20:49:06 -07:00
John-David Dalton
a742b5f3e2 Rewrite build.js to be used as a module.
Former-commit-id: bf6425925e511a327b5297f9b17620a97ff53b67
2012-09-06 20:36:24 -07:00
John-David Dalton
a2a3bb291f Let build.js handle the "use strict" directive and not minify.js.
Former-commit-id: 741eb692b158e22aa688d6dac1b63fc2787cc426
2012-09-06 00:41:18 -07:00
John-David Dalton
b7c0ac7d67 Tweak test/test-ui.js for QUnit v1.10.0.
Former-commit-id: 6481cce305fb4d69bba22ba2186a30ee13bb2282
2012-09-06 00:40:26 -07:00
John-David Dalton
13b1fc6b44 Remove an unneeded _.object unit test.
Former-commit-id: 2334bda13fbd9bd683b3f650ff47f1a676139319
2012-09-06 00:38:22 -07:00
John-David Dalton
3939fcf6e7 Allow unit tests to run when testing custom builds without noConflict.
Former-commit-id: 2aee7eb872144583df1f22743f5d3f7102d14eae
2012-09-05 07:12:35 -07:00
John-David Dalton
13abbb81af Bump to v0.7.0.
Former-commit-id: 4ab5bfe3bba14182ffe24c05792b3b8f194afa0c
2012-09-05 01:12:57 -07:00
John-David Dalton
019f0153c8 Update alias style in docs.
Former-commit-id: d7b1eb9999f535c365ce3ea6251a359c7d901769
2012-09-04 23:47:27 -07:00
John-David Dalton
8abc2925e0 Fix invalid doc entry in doc/README.md.
Former-commit-id: 64721e5c3417a25b2f34f1f380b0cecc6561fa35
2012-09-04 22:29:06 -07:00
John-David Dalton
996c9a032a Update docs to include method aliases.
Former-commit-id: b93b13a42381ba28b84a3e279d5157673b20fdce
2012-09-04 21:40:29 -07:00
John-David Dalton
22d3794d22 Update vendors.
Former-commit-id: ad3284b1e77cfb0b17af99e0ddaf00618e4485b7
2012-09-04 21:37:01 -07:00
John-David Dalton
ba948a38e9 Ensure to escape exports property for Closure Compiler.
Former-commit-id: 9b6bd1201e74d9e85fbc340bcabce40039239a59
2012-09-04 15:25:47 -07:00
John-David Dalton
e8a522c4df Update README to reflect Underscore patches.
Former-commit-id: b0222d92b90e190c7c322e409ec877ca473f3594
2012-09-04 11:27:23 -07:00
John-David Dalton
c60f3da32e Reduce size of "mobile" and "underscore" builds.
Former-commit-id: 062dc03e3d3dd7a8e1ceb6a8b4ea155394a9b899
2012-09-03 23:20:18 -07:00
John-David Dalton
0c92d3cbb2 Fix how post-compile.js unescapes properties to avoid extra work in pre-compile.js.
Former-commit-id: f604f706af358288877763681243f4816d5cbe9e
2012-09-03 21:40:23 -07:00
John-David Dalton
e4fc8dd6fe Remove older Opera fixes from the "underscore" build.
Former-commit-id: a012ed6957b4d964b5f2dc1a636d7f5f19fbf307
2012-09-03 16:30:55 -07:00
John-David Dalton
1a849e2de0 Update docs and minified build.
Former-commit-id: 62f3293c13d5fa08f857ca455506b4762aa65416
2012-09-03 16:07:46 -07:00
John-David Dalton
ffdd79f86b Adjust how _.template handles compiled syntax errors for compatibility with Underscore.
Former-commit-id: ba84c5b468938a1be1a1fd0afd31cb83f563e1ca
2012-09-03 16:05:47 -07:00
John-David Dalton
e87e46b1b6 Remove deep clone from "underscore" build and fix how invalidArgs are detected in build.js.
Former-commit-id: 5038d1541fa7d0c062e5a48004a60fb9140778d7
2012-09-03 15:42:02 -07:00
John-David Dalton
3ca81a4ff7 Fix Underscore detection in post-compile.js.
Former-commit-id: ad3c5cd28bc9ac0f6c9e5801c3849acd4305c528
2012-09-03 15:40:47 -07:00
John-David Dalton
5477d3c292 Cleanup iteratorTemplate and isPlainObject.
Former-commit-id: a96b8716cfd0efbc46daf2307fae8f1ee5969862
2012-09-03 15:36:18 -07:00
John-David Dalton
08300183b3 Cleanup build.js help message.
Former-commit-id: 36edcf06e78580835f33872f0c72a233604a4adc
2012-09-03 12:58:46 -07:00
John-David Dalton
095c77f22c Inform users of invalid arguments passed to build.js.
Former-commit-id: 1b15dd2242387c7037678a3348931f5430612a8b
2012-09-03 10:50:25 -07:00
John-David Dalton
09926e63a3 Add 4th screencast link to README.md.
Former-commit-id: 07d890b65e7e4ed600173634f04a9783d18bc701
2012-09-02 16:15:56 -07:00
John-David Dalton
87d70f29a1 Make pre-compile.js and post-compile.js support underscore.js.
Former-commit-id: 76d040f630faf03bd5a8eb168259814f5662ba50
2012-09-02 16:11:29 -07:00
John-David Dalton
3a7661b111 Make _chain and _wrapped double underscored to further avoid conflicts.
Former-commit-id: 27f545d99cc383be05509ac7382e42fc727e0215
2012-09-02 12:57:53 -07:00
John-David Dalton
ec976953cd Simplify wrapper inference in _.isEqual.
Former-commit-id: b4fda683ebee4c3f7dddd0cb87201306c08fa7d5
2012-09-02 12:04:35 -07:00
John-David Dalton
1837562b50 Cleanup README.md, update Backbone, rebuild docs and minified build.
Former-commit-id: 8f54e0a4b76cf1a42c11ca37f3e8672a75178d30
2012-09-02 02:44:26 -07:00
John-David Dalton
f3bec4fc37 Ensure optimized isPlainObject works with objects from other documents.
Former-commit-id: 2f782b3dfc19e7ea3274132c31cd408ee2387021
2012-09-02 02:35:02 -07:00
John-David Dalton
c2117ef4fd Add _.result to "backbone" build.
Former-commit-id: c04ddcdbfb229440b19b268d51887bf31ae11296
2012-09-01 14:58:35 -07:00
John-David Dalton
910804ecd1 Avoid arguments object in _.random.
Former-commit-id: 24e54869ae03c0251f419c922f59f53f01b8fa35
2012-08-31 18:03:10 -07:00
John-David Dalton
ce5ae1dfdd Simplify _.size.
Former-commit-id: a7d3338cbd5784ec6b9b6a25e18acd9507f4b21c
2012-08-31 17:58:34 -07:00
John-David Dalton
2c31411ffb Optimize _.pairs.
Former-commit-id: 1de87609a8635fb8d48bc558fbdabc545da53b4b
2012-08-31 15:45:27 -07:00
John-David Dalton
84d69fa2a1 Update resolved issue count in README.md.
Former-commit-id: 41a22ec12d02a1cff5b172022c8260b80c63fe9b
2012-08-31 13:57:23 -07:00
John-David Dalton
2aea296d19 Update minified build and docs.
Former-commit-id: aed499a9ffee5fd7c1f29dad13a8c4e756b431d8
2012-08-31 13:53:04 -07:00
John-David Dalton
2b0bffc362 Ensure template delimiters are tokenized correctly. [closes #64]
Former-commit-id: 814f3f8a840a70a9b455e5f91da0e21174f08787
2012-08-31 13:50:10 -07:00
John-David Dalton
83356142c1 Update README.md with API changes.
Former-commit-id: 4571a2331d433af28ae33813780f8abc9477437e
2012-08-31 13:36:16 -07:00
John-David Dalton
24dcc6947c Update build.js and pre-compile.js for _.invert, _.pairs, and _.random.
Former-commit-id: 0dc281f6e1a07f0a4121f71c37e15a7ca0e18960
2012-08-31 13:35:40 -07:00
John-David Dalton
a7e3136a0b Add _.random.
Former-commit-id: cf720b9187b0b54b43773a9f5f02fb475d786bfa
2012-08-31 13:33:44 -07:00
John-David Dalton
71639cfea7 Cleanup _.times documentation.
Former-commit-id: 59ce4b689bb280650d28944664abd6a38f1c43a0
2012-08-31 13:05:12 -07:00
John-David Dalton
8c2d39fb82 Add _.invert and _.pairs.
Former-commit-id: b265ed3f148e5e951b8d061107bb376e0b2e651e
2012-08-31 13:02:55 -07:00
John-David Dalton
e060d29337 Rename _.drop and _.zipObject unit tests.
Former-commit-id: d68abda2400244b7801eac9ad90de88b2f99a1f4
2012-08-31 12:59:18 -07:00
John-David Dalton
79e9156d2f Make _.drop an alias of _.rest and rename _.zipObject to _.object.
Former-commit-id: 08cb9ec2d5009b9a9f959b2341f8b78f6bbd37a0
2012-08-31 12:57:52 -07:00
John-David Dalton
9100db55b0 Update vendors.
Former-commit-id: 88e9746e94e8ec899227b1a925bea4ab4d373fb0
2012-08-31 12:47:55 -07:00
46 changed files with 4544 additions and 2366 deletions

160
README.md
View File

@@ -1,14 +1,14 @@
# Lo-Dash <sup>v0.6.1</sup> # Lo-Dash <sup>v0.7.0</sup>
A drop-in replacement<sup>[*](https://github.com/bestiejs/lodash/wiki/Drop-in-Disclaimer)</sup> for Underscore.js, from the devs behind [jsPerf.com](http://jsperf.com), delivering [performance](http://lodash.com/benchmarks), [bug fixes](https://github.com/bestiejs/lodash#resolved-underscorejs-issues-30), and [additional features](https://github.com/bestiejs/lodash#features). A drop-in replacement<sup>[*](https://github.com/bestiejs/lodash/wiki/Drop-in-Disclaimer)</sup> for Underscore.js, from the devs behind [jsPerf.com](http://jsperf.com), delivering [performance](http://lodash.com/benchmarks), [bug fixes](https://github.com/bestiejs/lodash#resolved-underscorejs-issues-20), and [additional features](https://github.com/bestiejs/lodash#features).
Lo-Dashs performance is gained by avoiding slower native methods, instead opting for simplified non-ES5 compliant methods optimized for common usage, and by leveraging function compilation to reduce the number of overall function calls. Lo-Dashs performance is gained by avoiding slower native methods, instead opting for simplified non-ES5 compliant methods optimized for common usage, and by leveraging function compilation to reduce the number of overall function calls.
## Download ## Download
* [Development source](https://raw.github.com/bestiejs/lodash/v0.6.1/lodash.js) * [Development source](https://raw.github.com/bestiejs/lodash/v0.7.0/lodash.js)
* [Production source](https://raw.github.com/bestiejs/lodash/v0.6.1/lodash.min.js) * [Production source](https://raw.github.com/bestiejs/lodash/v0.7.0/lodash.min.js)
* CDN copies of ≤ [v0.6.1](http://cdnjs.cloudflare.com/ajax/libs/lodash.js/0.6.1/lodash.min.js) are available on [cdnjs](http://cdnjs.com/) thanks to [CloudFlare](http://www.cloudflare.com/) * CDN copies of ≤ [v0.7.0](http://cdnjs.cloudflare.com/ajax/libs/lodash.js/0.7.0/lodash.min.js) are available on [cdnjs](http://cdnjs.com/) thanks to [CloudFlare](http://www.cloudflare.com/)
* For optimal performance, [create a custom build](https://github.com/bestiejs/lodash#custom-builds) with only the features you need * For optimal performance, [create a custom build](https://github.com/bestiejs/lodash#custom-builds) with only the features you need
## Dive in ## Dive in
@@ -27,6 +27,7 @@ For more information check out these screencasts over Lo-Dash:
* [Lo-Dash optimizations and custom builds](https://vimeo.com/44154601) * [Lo-Dash optimizations and custom builds](https://vimeo.com/44154601)
* [Lo-Dashs origin and why its a better utility belt](https://vimeo.com/44154600) * [Lo-Dashs origin and why its a better utility belt](https://vimeo.com/44154600)
* [Unit testing in Lo-Dash](https://vimeo.com/45865290) * [Unit testing in Lo-Dash](https://vimeo.com/45865290)
* [Lo-Dashs approach to native method use](https://vimeo.com/48576012)
## Features ## Features
@@ -35,22 +36,24 @@ For more information check out these screencasts over Lo-Dash:
* [_.clone](http://lodash.com/docs#clone) supports *“deep”* cloning * [_.clone](http://lodash.com/docs#clone) supports *“deep”* cloning
* [_.countBy](http://lodash.com/docs#countBy) as a companion function for [_.groupBy](http://lodash.com/docs#groupBy) and [_.sortBy](http://lodash.com/docs#sortBy) * [_.countBy](http://lodash.com/docs#countBy) as a companion function for [_.groupBy](http://lodash.com/docs#groupBy) and [_.sortBy](http://lodash.com/docs#sortBy)
* [_.debounce](http://lodash.com/docs#debounce)ed functions match [_.throttle](http://lodash.com/docs#throttle)ed functions return value behavior * [_.debounce](http://lodash.com/docs#debounce)ed functions match [_.throttle](http://lodash.com/docs#throttle)ed functions return value behavior
* [_.drop](http://lodash.com/docs#drop) for the inverse functionality of [_.pick](http://lodash.com/docs#pick)
* [_.forEach](http://lodash.com/docs#forEach) is chainable and supports exiting iteration early * [_.forEach](http://lodash.com/docs#forEach) is chainable and supports exiting iteration early
* [_.forIn](http://lodash.com/docs#forIn) for iterating over an objects own and inherited properties * [_.forIn](http://lodash.com/docs#forIn) for iterating over an objects own and inherited properties
* [_.forOwn](http://lodash.com/docs#forOwn) for iterating over an objects own properties * [_.forOwn](http://lodash.com/docs#forOwn) for iterating over an objects own properties
* [_.groupBy](http://lodash.com/docs#groupBy), [_.sortedIndex](http://lodash.com/docs#sortedIndex), and [_.uniq](http://lodash.com/docs#uniq) accept a `thisArg` argument
* [_.indexOf](http://lodash.com/docs#indexOf) and [_.lastIndexOf](http://lodash.com/docs#lastIndexOf) accept a `fromIndex` argument * [_.indexOf](http://lodash.com/docs#indexOf) and [_.lastIndexOf](http://lodash.com/docs#lastIndexOf) accept a `fromIndex` argument
* [_.invert](http://lodash.com/docs#invert) to create inverted objects
* [_.merge](http://lodash.com/docs#merge) for a *“deep”* [_.extend](http://lodash.com/docs#extend) * [_.merge](http://lodash.com/docs#merge) for a *“deep”* [_.extend](http://lodash.com/docs#extend)
* [_.object](http://lodash.com/docs#object) and [_.pairs](http://lodash.com/docs#pairs) for composing objects
* [_.omit](http://lodash.com/docs#omit) for the inverse functionality of [_.pick](http://lodash.com/docs#pick)
* [_.partial](http://lodash.com/docs#partial) for partial application without `this` binding * [_.partial](http://lodash.com/docs#partial) for partial application without `this` binding
* [_.pick](http://lodash.com/docs#pick) and [_.drop](http://lodash.com/docs#drop) accept `callback` and `thisArg` arguments * [_.pick](http://lodash.com/docs#pick) and [_.omit](http://lodash.com/docs#omit) accept `callback` and `thisArg` arguments
* [_.random](http://lodash.com/docs#random) for generating random numbers within a given range
* [_.sortBy](http://lodash.com/docs#sortBy) performs a [stable](http://en.wikipedia.org/wiki/Sorting_algorithm#Stability) sort * [_.sortBy](http://lodash.com/docs#sortBy) performs a [stable](http://en.wikipedia.org/wiki/Sorting_algorithm#Stability) sort
* [_.template](http://lodash.com/docs#template) utilizes [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl) for easier debugging * [_.template](http://lodash.com/docs#template) utilizes [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl) for easier debugging
* [_.unescape](http://lodash.com/docs#unescape) to unescape strings escaped by [_.escape](http://lodash.com/docs#escape) * [_.unescape](http://lodash.com/docs#unescape) to unescape strings escaped by [_.escape](http://lodash.com/docs#escape)
* [_.where](http://lodash.com/docs#where) for filtering collections by contained properties * [_.where](http://lodash.com/docs#where) for filtering collections by contained properties
* [_.zipObject](http://lodash.com/docs#zipObject) for composing objects * [_.countBy](http://lodash.com/docs#countBy), [_.groupBy](http://lodash.com/docs#groupBy), [_.sortedIndex](http://lodash.com/docs#sortedIndex), and [_.uniq](http://lodash.com/docs#uniq) accept a `thisArg` argument
* [_.contains](http://lodash.com/docs#contains), [_.size](http://lodash.com/docs#size), [_.toArray](http://lodash.com/docs#toArray), * [_.contains](http://lodash.com/docs#contains), [_.size](http://lodash.com/docs#size), [_.toArray](http://lodash.com/docs#toArray),
[and more…](http://lodash.com/docs "_.every, _.filter, _.find, _.forEach, _.groupBy, _.invoke, _.map, _.pluck, _.reduce, _.reduceRight, _.reject, _.some, _sortBy") accept strings [and more…](http://lodash.com/docs "_.countBy, _.every, _.filter, _.find, _.forEach, _.groupBy, _.invoke, _.map, _.pluck, _.reduce, _.reduceRight, _.reject, _.some, _.sortBy, _.where") accept strings
## Support ## Support
@@ -59,9 +62,9 @@ Lo-Dash has been tested in at least Chrome 5-21, Firefox 1-15, IE 6-9, Opera 9.2
## Custom builds ## Custom builds
Custom builds make it easy to create lightweight versions of Lo-Dash containing only the methods you need. Custom builds make it easy to create lightweight versions of Lo-Dash containing only the methods you need.
We handle all the method dependency and alias mapping for you. To top it off, we handle all method dependency and alias mapping for you.
* Backbone builds, containing all methods required by Backbone, may be created using the `backbone` modifier argument. * Backbone builds, with only methods required by Backbone, may be created using the `backbone` modifier argument.
```bash ```bash
lodash backbone lodash backbone
``` ```
@@ -86,39 +89,59 @@ lodash mobile
lodash strict lodash strict
``` ```
* Underscore builds, containing only methods included in Underscore, may be created using the `underscore` modifier argument. * Underscore builds, with iteration fixes removed and only Underscores API, may be created using the `underscore` modifier argument.
```bash ```bash
lodash underscore lodash underscore
``` ```
Custom builds may be created in three ways: Custom builds may be created using the following commands:
1. Use the `category` argument to pass the categories of methods to include in the build.<br> * Use the `category` argument to pass comma separated categories of methods to include in the build.<br>
Valid categories are *“arrays”*, *“chaining”*, *“collections”*, *“functions”*, *“objects”*, and *“utilities”*. Valid categories are *“arrays”*, *“chaining”*, *“collections”*, *“functions”*, *“objects”*, and *“utilities”*.
```bash ```bash
lodash category=collections,functions lodash category=collections,functions
lodash category="collections, functions" lodash category="collections, functions"
``` ```
2. Use the `exclude` argument to pass the names of methods to exclude from the build. * Use the `exclude` argument to pass comma separated names of methods to exclude from the build.
```bash ```bash
lodash exclude=union,uniq,zip lodash exclude=union,uniq,zip
lodash exclude="union, uniq, zip" lodash exclude="union, uniq, zip"
``` ```
3. Use the `include` argument to pass the names of methods to include in the build. * Use the `exports` argument to pass comma separated names of ways to export the `LoDash` function.<br>
Valid exports are *“amd”*, *“commonjs”*, *“global”*, *“node”*, and *“none”*.
```bash
lodash exports=amd,commonjs,node
lodash include="amd, commonjs, node"
```
* Use the `iife` argument to specify code to replace the immediately-invoked function expression that wraps Lo-Dash.
```bash
lodash iife="!function(window,undefined){%output%}(this)"
```
* Use the `include` argument to pass comma separated names of methods to include in the build.
```bash ```bash
lodash include=each,filter,map lodash include=each,filter,map
lodash include="each, filter, map" lodash include="each, filter, map"
``` ```
All arguments, except `backbone` with `underscore`, `exclude` with `include`, and `legacy` with `csp`/`mobile`, may be combined. All arguments, except `exclude` with `include` and `legacy` with `csp`/`mobile`, may be combined.
```bash ```bash
lodash backbone legacy category=utilities exclude=first,last lodash backbone legacy exports=global category=utilities exclude=first,last
lodash underscore mobile strict category=functions include=pick,uniq lodash -s underscore mobile strict exports=amd category=functions include=pick,uniq
``` ```
The following options are also supported:
* `-c`, `--stdout` Write output to standard output
* `-h`, `--help` Display help information
* `-o`, `--output` Write output to a given path/filename
* `-s`, `--silent` Skip status updates normally logged to the console
* `-V`, `--version` Output current version of Lo-Dash
The `lodash` command-line utility is available when Lo-Dash is installed as a global package (i.e. `npm install -g lodash`). The `lodash` command-line utility is available when Lo-Dash is installed as a global package (i.e. `npm install -g lodash`).
Custom builds are saved to `lodash.custom.js` and `lodash.custom.min.js`. Custom builds are saved to `lodash.custom.js` and `lodash.custom.min.js`.
@@ -169,40 +192,33 @@ require({
}); });
``` ```
## Resolved Underscore.js issues <sup>(30+)</sup> ## Resolved Underscore.js issues <sup>(20+)</sup>
* Allow iteration of objects with a `length` property [[#148](https://github.com/documentcloud/underscore/issues/148), [#154](https://github.com/documentcloud/underscore/issues/154), [#252](https://github.com/documentcloud/underscore/issues/252), [#448](https://github.com/documentcloud/underscore/issues/448), [#659](https://github.com/documentcloud/underscore/issues/659), [test](https://github.com/bestiejs/lodash/blob/v0.6.1/test/test.js#L578-584)] * Allow iteration of objects with a `length` property [[#148](https://github.com/documentcloud/underscore/issues/148), [#154](https://github.com/documentcloud/underscore/issues/154), [#252](https://github.com/documentcloud/underscore/issues/252), [#448](https://github.com/documentcloud/underscore/issues/448), [#659](https://github.com/documentcloud/underscore/issues/659), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L543-549)]
* Ensure array-like objects with invalid `length` properties are treated like regular objects [[test](https://github.com/bestiejs/lodash/blob/v0.6.1/test/test.js#L526-536)] * Ensure array-like objects with invalid `length` properties are treated like regular objects [[#741](https://github.com/documentcloud/underscore/issues/741), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L491-501)]
* Ensure *“Arrays”*, “Collections”, and “Objects” methods dont error when passed falsey arguments [[#650](https://github.com/documentcloud/underscore/pull/650), [test](https://github.com/bestiejs/lodash/blob/v0.6.1/test/test.js#L1668-1703)] * Ensure *“Arrays”*, *“Collections”*, and *“Objects”* methods dont error when passed falsey arguments [[#650](https://github.com/documentcloud/underscore/pull/650), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L1719-1754)]
* Ensure *“Collections”* methods allow string `collection` arguments [[#247](https://github.com/documentcloud/underscore/issues/247), [#276](https://github.com/documentcloud/underscore/issues/276), [#561](https://github.com/documentcloud/underscore/pull/561), [test](https://github.com/bestiejs/lodash/blob/v0.6.1/test/test.js#L538-555)] * Ensure *“Collections”* methods allow string `collection` arguments [[#247](https://github.com/documentcloud/underscore/issues/247), [#276](https://github.com/documentcloud/underscore/issues/276), [#561](https://github.com/documentcloud/underscore/pull/561), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L503-520)]
* Ensure templates compiled with errors are inspectable [[#666](https://github.com/documentcloud/underscore/issues/666), [test](https://github.com/bestiejs/lodash/blob/v0.6.1/test/test.js#L1383-1386)] * Fix cross-browser object iteration bugs [[#60](https://github.com/documentcloud/underscore/issues/60), [#376](https://github.com/documentcloud/underscore/issues/376), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L554-579)]
* Fix cross-browser object iteration bugs [[#60](https://github.com/documentcloud/underscore/issues/60), [#376](https://github.com/documentcloud/underscore/issues/376), [test](https://github.com/bestiejs/lodash/blob/v0.6.1/test/test.js#L589-614)] * Methods should work on pages with incorrectly shimmed native methods [[#7](https://github.com/documentcloud/underscore/issues/7), [#742](https://github.com/documentcloud/underscore/issues/742), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L134-140)]
* Handle arrays with `undefined` values correctly in IE < 9 [[#601](https://github.com/documentcloud/underscore/issues/601)] * Register as an AMD module, but still export to global [[#431](https://github.com/documentcloud/underscore/pull/431), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L118-132)]
* Methods should work on pages with incorrectly shimmed native methods [[#7](https://github.com/documentcloud/underscore/issues/7), [test](https://github.com/bestiejs/lodash/blob/v0.6.1/test/test.js#L117-123)] * `_.clone` should allow `deep` cloning [[#595](https://github.com/documentcloud/underscore/pull/595), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L223-234)]
* Register as an AMD module, but still export to global [[#431](https://github.com/documentcloud/underscore/pull/431), [test](https://github.com/bestiejs/lodash/blob/v0.6.1/test/test.js#L101-115)] * `_.contains` should work with strings [[#667](https://github.com/documentcloud/underscore/pull/667), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L289-298)]
* `_(…)` should return passed wrapper instances [[test](https://github.com/bestiejs/lodash/blob/v0.6.1/test/test.js#L135-138)] * `_.countBy` and `_.groupBy` should only add values to own, not inherited, properties [[#736](https://github.com/documentcloud/underscore/issues/736), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L306-313)]
* `_.clone` should allow `deep` cloning [[#595](https://github.com/documentcloud/underscore/pull/595), [test](https://github.com/bestiejs/lodash/blob/v0.6.1/test/test.js#L205-220)] * `_.extend` should recursively extend objects [[#379](https://github.com/documentcloud/underscore/pull/379), [#718](https://github.com/documentcloud/underscore/issues/718), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L946-968)]
* `_.contains` should work with strings [[#667](https://github.com/documentcloud/underscore/pull/667), [test](https://github.com/bestiejs/lodash/blob/v0.6.1/test/test.js#L275-284)] * `_.forEach` should be chainable [[#142](https://github.com/documentcloud/underscore/issues/142), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L486-489)]
* `_.countBy` and `_.groupBy` should only add values to own, not inherited, properties [[test](https://github.com/bestiejs/lodash/blob/v0.6.1/test/test.js#L292-299)] * `_.forEach` should allow exiting iteration early [[#211](https://github.com/documentcloud/underscore/issues/211), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L581-600)]
* `_.escape` should return an empty string when passed `null` or `undefined` [[#427](https://github.com/documentcloud/underscore/issues/427), [test](https://github.com/bestiejs/lodash/blob/v0.6.1/test/test.js#L402-405)] * `_.isElement` should use strict equality for its duck type check [[#734](https://github.com/documentcloud/underscore/issues/734), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L696-705)]
* `_.extend` should recursively extend objects [[#379](https://github.com/documentcloud/underscore/pull/379), [#718](https://github.com/documentcloud/underscore/issues/718), [test](https://github.com/bestiejs/lodash/blob/v0.6.1/test/test.js#L979-1001)] * `_.isEmpty` should support jQuery/MooTools DOM query collections [[#690](https://github.com/documentcloud/underscore/pull/690), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L732-737)]
* `_.forEach` should be chainable [[#142](https://github.com/documentcloud/underscore/issues/142), [test](https://github.com/bestiejs/lodash/blob/v0.6.1/test/test.js#L521-524)] * `_.isEqual` should return `true` for like-objects from different documents [[#733](https://github.com/documentcloud/underscore/issues/733), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L784-795)]
* `_.forEach` should allow exiting iteration early [[#211](https://github.com/documentcloud/underscore/issues/211), [test](https://github.com/bestiejs/lodash/blob/v0.6.1/test/test.js#L616-635)] * `_.isEqual` should use custom `isEqual` methods before checking strict equality [[#748](https://github.com/documentcloud/underscore/issues/748), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L758-761)]
* `_.isElement` should use strict equality for its duck type check [[test](https://github.com/bestiejs/lodash/blob/v0.6.1/test/test.js#L731-740)] * `_.isObject` should avoid V8 bug [#2291](http://code.google.com/p/v8/issues/detail?id=2291) [[#605](https://github.com/documentcloud/underscore/issues/605), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L803-815)]
* `_.isEmpty` and `_.size` should support jQuery/MooTools DOM query collections [[#690](https://github.com/documentcloud/underscore/pull/690), [test](https://github.com/bestiejs/lodash/blob/v0.6.1/test/test.js#L767-772)] * `_.isNaN(new Number(NaN))` should return `true` [[#749](https://github.com/documentcloud/underscore/issues/749), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L823-825)]
* `_.isEqual` should return `true` for like-objects from different documents [[test](https://github.com/bestiejs/lodash/blob/v0.6.1/test/test.js#L808-828)] * `_.keys` should work with `arguments` objects cross-browser [[#396](https://github.com/documentcloud/underscore/issues/396), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L879-881)]
* `_.isObject` should avoid V8 bug [#2291](http://code.google.com/p/v8/issues/detail?id=2291) [[#605](https://github.com/documentcloud/underscore/issues/605), [test](https://github.com/bestiejs/lodash/blob/v0.6.1/test/test.js#L836-848)] * `_.range` should coerce arguments to numbers [[#634](https://github.com/documentcloud/underscore/issues/634), [#683](https://github.com/documentcloud/underscore/issues/683), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L1222-1225)]
* `_.isNaN(new Number(NaN))` should return `true` [[test](https://github.com/bestiejs/lodash/blob/v0.6.1/test/test.js#L856-858)] * `_.reduceRight` should pass correct callback arguments when iterating objects [[test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L1257-1271)]
* `_.keys` and `_.size` should work with `arguments` objects cross-browser [[#396](https://github.com/documentcloud/underscore/issues/396), [#653](https://github.com/documentcloud/underscore/issues/653), [test](https://github.com/bestiejs/lodash/blob/v0.6.1/test/test.js#L912-914)] * `_.sortedIndex` should support arrays with high `length` values [[#735](https://github.com/documentcloud/underscore/issues/735), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L1404-1413)]
* `_.once` should free the given function for garbage collection [[#693](https://github.com/documentcloud/underscore/pull/693)] * `_.throttle` should work when called in a loop [[#502](https://github.com/documentcloud/underscore/issues/502), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L1534-1544)]
* `_.range` should coerce arguments to numbers [[#634](https://github.com/documentcloud/underscore/issues/634), [#683](https://github.com/documentcloud/underscore/issues/683), [test](https://github.com/bestiejs/lodash/blob/v0.6.1/test/test.js#L1170-1173)] * `_.toArray` uses custom `toArray` methods of arrays and strings [[#747](https://github.com/documentcloud/underscore/issues/747), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L1571-1579)]
* `_.reduceRight` should pass correct callback arguments when iterating objects [[test](https://github.com/bestiejs/lodash/blob/v0.6.1/test/test.js#L1205-1219)]
* `_.size` should return the `length` of string values [[test](https://github.com/bestiejs/lodash/blob/v0.6.1/test/test.js#L1263-1265)]
* `_.sortedIndex` should support arrays with high `length` values [[test](https://github.com/bestiejs/lodash/blob/v0.6.1/test/test.js#L1353-1362)]
* `_.template` should not augment the `options` object [[test](https://github.com/bestiejs/lodash/blob/v0.6.1/test/test.js#L1377-1381)]
* `_.throttle` should work when called in a loop [[#502](https://github.com/documentcloud/underscore/issues/502), [test](https://github.com/bestiejs/lodash/blob/v0.6.1/test/test.js#L1473-1483)]
* `_.toArray` uses custom `toArray` methods of arrays and strings [[test](https://github.com/bestiejs/lodash/blob/v0.6.1/test/test.js#L1510-1518)]
* `_.zipObject` should accept less than two arguments [[test](https://github.com/bestiejs/lodash/blob/v0.6.1/test/test.js#L1630-1632)]
## Optimized methods <sup>(50+)</sup> ## Optimized methods <sup>(50+)</sup>
@@ -214,9 +230,7 @@ require({
* `_.defaults` * `_.defaults`
* `_.defer` * `_.defer`
* `_.difference` * `_.difference`
* `_.drop`, `_.omit`
* `_.each` * `_.each`
* `_.escape`
* `_.every`, `_.all` * `_.every`, `_.all`
* `_.extend` * `_.extend`
* `_.filter`, `_.select` * `_.filter`, `_.select`
@@ -227,6 +241,7 @@ require({
* `_.groupBy` * `_.groupBy`
* `_.indexOf` * `_.indexOf`
* `_.intersection` * `_.intersection`
* `_.invert`
* `_.invoke` * `_.invoke`
* `_.isArguments` * `_.isArguments`
* `_.isDate` * `_.isDate`
@@ -244,6 +259,8 @@ require({
* `_.memoize` * `_.memoize`
* `_.min` * `_.min`
* `_.mixin` * `_.mixin`
* `_.omit`
* `_.pairs`
* `_.pick` * `_.pick`
* `_.pluck` * `_.pluck`
* `_.reduce`, `_.foldl`, `_.inject` * `_.reduce`, `_.foldl`, `_.inject`
@@ -267,21 +284,30 @@ require({
## Release Notes ## Release Notes
### <sup>v0.6.1</sup> ### <sup>v0.7.0</sup>
* Ensured IE conditional compilation isnt enabled by the `useSourceURL` test #### Compatibility Warnings ####
* Optimized `isPlainObject`
### <sup>v0.6.0</sup> * Renamed `_.zipObject` to `_.object`
* Replaced `_.drop` with `_.omit`
* Made `_.drop` alias `_.rest`
* Added `callback` and `thisArg` arguments to `_.drop` and `_.pick` #### Changes ####
* Added `hasObjectSpliceBug` test to avoid `delete` operator use
* Added `_.omit` alias for `_.drop` * Added [_.invert](http://lodash.com/docs#invert), [_.pairs](http://lodash.com/docs#pairs), and [_.random](http://lodash.com/docs#random)
* Added [_.unescape](http://lodash.com/docs#unescape) * Added `_.result` to the `backbone` build
* Ensured `_.reduce` works with string objects in IE < 9 * Added `exports`, `iife`, `-c`/`--stdout`, `-o`/`--output`, and `-s`/`--silent` build options
* Made compiled methods take advantage of engines with strict mode optimizations * Ensured `isPlainObject` works with objects from other documements
* Optimized `_.intersection` and removed its dependency on `_.every` * Ensured `_.isEqual` compares values with circular references correctly
* Reduced the file size of the `underscore` build * Ensured `_.merge` work with four or more arguments
* Ensured `_.sortBy` performs a stable sort for `undefined` values
* Ensured `_.template` works with "interpolate" delimiters containing ternary operators
* Ensured the production build works in Node.js
* Ensured template delimiters are tokenized correctly
* Made pseudo private properties `_chain` and `_wrapped` double-underscored to avoid conflicts
* Made `minify.js` support `underscore.js`
* Reduced the size of `mobile` and `underscore` builds
* Simplified `_.isEqual` and `_.size`
The full changelog is available [here](https://github.com/bestiejs/lodash/wiki/Changelog). The full changelog is available [here](https://github.com/bestiejs/lodash/wiki/Changelog).

1537
build.js

File diff suppressed because it is too large Load Diff

View File

@@ -25,7 +25,6 @@
/** Closure Compiler command-line options */ /** Closure Compiler command-line options */
var closureOptions = [ var closureOptions = [
'--compilation_level=ADVANCED_OPTIMIZATIONS', '--compilation_level=ADVANCED_OPTIMIZATIONS',
'--language_in=ECMASCRIPT5_STRICT',
'--warning_level=QUIET' '--warning_level=QUIET'
]; ];
@@ -38,12 +37,33 @@
* The exposed `minify` function minifies a given Lo-Dash `source` and invokes * The exposed `minify` function minifies a given Lo-Dash `source` and invokes
* the `onComplete` callback when finished. * the `onComplete` callback when finished.
* *
* @param {String} source The source to minify. * @param {Array|String} source The array of command-line arguments or the
* @param {String} workingName The name to give temporary files creates during the minification process. * source to minify.
* @param {Function} onComplete A function called when minification has completed. * @param {Object} options The options object containing `onComplete`,
* `silent`, and `workingName`.
*/ */
function minify(source, workingName, onComplete) { function minify(source, options) {
new Minify(source, workingName, onComplete); options || (options = {});
if (Array.isArray(source)) {
// convert the command-line arguments to an options object
options = source;
var filePath = options[options.length - 1],
dirPath = path.dirname(filePath),
workingName = path.basename(filePath, '.js') + '.min',
outputPath = path.join(dirPath, workingName + '.js'),
isSilent = options.indexOf('-s') > -1 || options.indexOf('--silent') > -1;
source = fs.readFileSync(filePath, 'utf8');
options = {
'silent': isSilent,
'workingName': workingName,
'onComplete': function(source) {
fs.writeFileSync(outputPath, source, 'utf8');
}
};
}
new Minify(source, options);
} }
/** /**
@@ -52,10 +72,17 @@
* @private * @private
* @constructor * @constructor
* @param {String} source The source to minify. * @param {String} source The source to minify.
* @param {String} workingName The name to give temporary files creates during the minification process. * @param {Object} options The options object containing `onComplete`,
* @param {Function} onComplete A function called when minification has completed. * `silent`, and `workingName`.
*/ */
function Minify(source, workingName, onComplete) { function Minify(source, options) {
source || (source = '');
options || (options = {});
if (typeof source != 'string') {
options = source || options;
source = options.source || '';
}
// create the destination directory if it doesn't exist // create the destination directory if it doesn't exist
if (!fs.existsSync(distPath)) { if (!fs.existsSync(distPath)) {
// avoid errors when called as a npm executable // avoid errors when called as a npm executable
@@ -67,9 +94,12 @@
this.compiled = {}; this.compiled = {};
this.hybrid = {}; this.hybrid = {};
this.uglified = {}; this.uglified = {};
this.onComplete = onComplete; this.isSilent = !!options.silent;
this.source = source = preprocess(source); this.onComplete = options.onComplete || function() {};
this.workingName = workingName; this.workingName = options.workingName || 'temp';
source = preprocess(source);
this.source = source;
// begin the minification process // begin the minification process
closureCompile.call(this, source, onClosureCompile.bind(this)); closureCompile.call(this, source, onClosureCompile.bind(this));
@@ -98,10 +128,12 @@
message = null; message = null;
} }
console.log(message == null if (!this.isSilent) {
? 'Compressing ' + this.workingName + ' using the Closure Compiler...' console.log(message == null
: message ? 'Compressing ' + this.workingName + ' using the Closure Compiler...'
); : message
);
}
compiler.stdout.on('data', function(data) { compiler.stdout.on('data', function(data) {
// append the data to the output stream // append the data to the output stream
@@ -149,10 +181,12 @@
message = null; message = null;
} }
console.log(message == null if (!this.isSilent) {
? 'Compressing ' + this.workingName + ' using UglifyJS...' console.log(message == null
: message ? 'Compressing ' + this.workingName + ' using UglifyJS...'
); : message
);
}
try { try {
result = ugly.gen_code( result = ugly.gen_code(
@@ -203,9 +237,12 @@
if (exception) { if (exception) {
throw exception; throw exception;
} }
if (!this.isSilent) {
console.log('Done. Size: %d bytes.', result.length);
}
// store the gzipped result and report the size // store the gzipped result and report the size
this.compiled.gzip = result; this.compiled.gzip = result;
console.log('Done. Size: %d bytes.', result.length);
// next, minify the source using only UglifyJS // next, minify the source using only UglifyJS
uglify.call(this, this.source, onUglify.bind(this)); uglify.call(this, this.source, onUglify.bind(this));
@@ -238,11 +275,13 @@
if (exception) { if (exception) {
throw exception; throw exception;
} }
if (!this.isSilent) {
console.log('Done. Size: %d bytes.', result.length);
}
var message = 'Compressing ' + this.workingName + ' using hybrid minification...'; var message = 'Compressing ' + this.workingName + ' using hybrid minification...';
// store the gzipped result and report the size // store the gzipped result and report the size
this.uglified.gzip = result; this.uglified.gzip = result;
console.log('Done. Size: %d bytes.', result.length);
// next, minify the Closure Compiler minified source using UglifyJS // next, minify the Closure Compiler minified source using UglifyJS
uglify.call(this, this.compiled.source, message, onHybrid.bind(this)); uglify.call(this, this.compiled.source, message, onHybrid.bind(this));
@@ -275,9 +314,11 @@
if (exception) { if (exception) {
throw exception; throw exception;
} }
if (!this.isSilent) {
console.log('Done. Size: %d bytes.', result.length);
}
// store the gzipped result and report the size // store the gzipped result and report the size
this.hybrid.gzip = result; this.hybrid.gzip = result;
console.log('Done. Size: %d bytes.', result.length);
// finish by choosing the smallest compressed file // finish by choosing the smallest compressed file
onComplete.call(this); onComplete.call(this);
@@ -334,14 +375,11 @@
// was invoked directly (e.g. `node minify.js source.js`) and write to // was invoked directly (e.g. `node minify.js source.js`) and write to
// `<filename>.min.js` // `<filename>.min.js`
(function() { (function() {
var filePath = process.argv[2], var options = process.argv;
dirPath = path.dirname(filePath), if (options.length < 3) {
source = fs.readFileSync(filePath, 'utf8'), return;
workingName = path.basename(filePath, '.js') + '.min'; }
minify(options);
minify(source, workingName, function(result) {
fs.writeFileSync(path.join(dirPath, workingName + '.js'), result, 'utf8');
});
}()); }());
} }
}()); }());

View File

@@ -6,11 +6,15 @@
var fs = require('fs'); var fs = require('fs');
/** The minimal license/copyright template */ /** The minimal license/copyright template */
var licenseTemplate = var licenseTemplate = {
'/*!\n' + 'lodash':
' Lo-Dash @VERSION lodash.com/license\n' + '/*!\n' +
' Underscore.js 1.3.3 github.com/documentcloud/underscore/blob/master/LICENSE\n' + ' Lo-Dash @VERSION lodash.com/license\n' +
'*/'; ' Underscore.js 1.3.3 github.com/documentcloud/underscore/blob/master/LICENSE\n' +
'*/',
'underscore':
'/*! Underscore.js @VERSION github.com/documentcloud/underscore/blob/master/LICENSE */'
};
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
@@ -22,29 +26,29 @@
* @returns {String} Returns the processed source. * @returns {String} Returns the processed source.
*/ */
function postprocess(source) { function postprocess(source) {
// exit early if snippet isn't found
var snippet = /VERSION\s*[=:]\s*([\'"])(.*?)\1/.exec(source);
if (!snippet) {
return source;
}
// set the version
var license = licenseTemplate.replace('@VERSION', snippet[2]);
// move vars exposed by Closure Compiler into the IIFE // move vars exposed by Closure Compiler into the IIFE
source = source.replace(/^([^(\n]+)\s*(\(function[^)]+\){)/, '$2$1'); source = source.replace(/^([^(\n]+)\s*(\(function[^)]+\){)/, '$2$1');
// unescape properties (i.e. foo["bar"] => foo.bar) // unescape properties (i.e. foo["bar"] => foo.bar)
source = source.replace(/(\w)\["([^."]+)"\]/g, '$1.$2'); source = source.replace(/(\w)\["([^."]+)"\]/g, function(match, left, right) {
return /\W/.test(right) ? match : (left + '.' + right);
});
// correct AMD module definition for AMD build optimizers // correct AMD module definition for AMD build optimizers
source = source.replace(/("function")\s*==\s*(typeof define)\s*&&\s*\(?\s*("object")\s*==\s*(typeof define\.amd)\s*&&\s*(define\.amd)\s*\)?/, '$2==$1&&$4==$3&&$5'); source = source.replace(/("function")\s*==\s*(typeof define)\s*&&\s*\(?\s*("object")\s*==\s*(typeof define\.amd)\s*&&\s*(define\.amd)\s*\)?/, '$2==$1&&$4==$3&&$5');
// add license
source = license + '\n;' + source;
// add trailing semicolon // add trailing semicolon
return source.replace(/[\s;]*$/, ';'); if (source) {
source = source.replace(/[\s;]*$/, ';');
}
// exit early if version snippet isn't found
var snippet = /VERSION\s*[=:]\s*([\'"])(.*?)\1/.exec(source);
if (!snippet) {
return source;
}
// add license
return licenseTemplate[/call\(this\);?$/.test(source) ? 'underscore' : 'lodash']
.replace('@VERSION', snippet[2]) + '\n;' + source;
} }
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/

View File

@@ -44,7 +44,7 @@
'callee', 'callee',
'className', 'className',
'compareAscending', 'compareAscending',
'destValue', 'data',
'forIn', 'forIn',
'found', 'found',
'funcs', 'funcs',
@@ -66,10 +66,11 @@
'propsLength', 'propsLength',
'recursive', 'recursive',
'source', 'source',
'stack', 'sources',
'stackLength', 'stackLength',
'target', 'target',
'valueProp' 'valueProp',
'values'
]; ];
/** Used to minify `compileIterator` option properties */ /** Used to minify `compileIterator` option properties */
@@ -104,9 +105,9 @@
/** Used to protect the specified properties from getting minified */ /** Used to protect the specified properties from getting minified */
var propWhitelist = [ var propWhitelist = [
'_', '_',
'_chain', '__chain__',
'_wrapped',
'__proto__', '__proto__',
'__wrapped__',
'after', 'after',
'all', 'all',
'amd', 'amd',
@@ -117,6 +118,7 @@
'chain', 'chain',
'clearTimeout', 'clearTimeout',
'clone', 'clone',
'clones',
'collect', 'collect',
'compact', 'compact',
'compose', 'compose',
@@ -135,6 +137,7 @@
'escape', 'escape',
'evaluate', 'evaluate',
'every', 'every',
'exports',
'extend', 'extend',
'filter', 'filter',
'find', 'find',
@@ -157,6 +160,7 @@
'inject', 'inject',
'interpolate', 'interpolate',
'intersection', 'intersection',
'invert',
'invoke', 'invoke',
'isArguments', 'isArguments',
'isArray', 'isArray',
@@ -187,12 +191,15 @@
'min', 'min',
'mixin', 'mixin',
'noConflict', 'noConflict',
'object',
'omit', 'omit',
'once', 'once',
'opera', 'opera',
'pairs',
'partial', 'partial',
'pick', 'pick',
'pluck', 'pluck',
'random',
'range', 'range',
'reduce', 'reduce',
'reduceRight', 'reduceRight',
@@ -207,6 +214,9 @@
'sortBy', 'sortBy',
'sortedIndex', 'sortedIndex',
'source', 'source',
'sources',
'stackA',
'stackB',
'tail', 'tail',
'take', 'take',
'tap', 'tap',
@@ -228,7 +238,10 @@
'without', 'without',
'wrap', 'wrap',
'zip', 'zip',
'zipObject'
// properties used by underscore.js
'_chain',
'_wrapped'
]; ];
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
@@ -246,22 +259,6 @@
// remove unrecognized JSDoc tags so Closure Compiler won't complain // remove unrecognized JSDoc tags so Closure Compiler won't complain
source = source.replace(/@(?:alias|category)\b.*/g, ''); source = source.replace(/@(?:alias|category)\b.*/g, '');
// manually convert `arrayLikeClasses` property assignments because
// Closure Compiler errors trying to minify them
source = source.replace(/(arrayLikeClasses =)[\s\S]+?= *true/,
"$1{'[object Arguments]': true, '[object Array]': true, '[object Boolean]': false, " +
"'[object Date]': false, '[object Function]': false, '[object Number]': false, " +
"'[object Object]': false, '[object RegExp]': false, '[object String]': true }"
);
// manually convert `cloneableClasses` property assignments because
// Closure Compiler errors trying to minify them
source = source.replace(/(cloneableClasses =)[\s\S]+?= *true/,
"$1{'[object Arguments]': false, '[object Array]': true, '[object Boolean]': true, " +
"'[object Date]': true, '[object Function]': false, '[object Number]': true, " +
"'[object Object]': true, '[object RegExp]': true, '[object String]': true }"
);
// add brackets to whitelisted properties so Closure Compiler won't mung them // add brackets to whitelisted properties so Closure Compiler won't mung them
// http://code.google.com/closure/compiler/docs/api-tutorial3.html#export // http://code.google.com/closure/compiler/docs/api-tutorial3.html#export
source = source.replace(RegExp('\\.(' + propWhitelist.join('|') + ')\\b', 'g'), "['$1']"); source = source.replace(RegExp('\\.(' + propWhitelist.join('|') + ')\\b', 'g'), "['$1']");
@@ -269,6 +266,9 @@
// remove brackets from `_.escape()` in `_.template` // remove brackets from `_.escape()` in `_.template`
source = source.replace(/__e *= *_\['escape']/g, '__e=_.escape'); source = source.replace(/__e *= *_\['escape']/g, '__e=_.escape');
// remove brackets from `_.escape()` in underscore.js `_.template`
source = source.replace(/_\['escape'\]\(__t'/g, '_.escape(__t');
// remove brackets from `collection.indexOf` in `_.contains` // remove brackets from `collection.indexOf` in `_.contains`
source = source.replace("collection['indexOf'](target)", 'collection.indexOf(target)'); source = source.replace("collection['indexOf'](target)", 'collection.indexOf(target)');
@@ -283,6 +283,9 @@
}); });
}); });
// add newline to `+"__p+='"` in underscore.js `_.template`
source = source.replace(/\+"__p\+='"/g, '+"\\n__p+=\'"');
// remove whitespace from `_.template` related regexes // remove whitespace from `_.template` related regexes
source = source.replace(/(?:reDelimiterCode\w+|reEmptyString\w+|reInsertVariable) *=.+/g, function(match) { source = source.replace(/(?:reDelimiterCode\w+|reEmptyString\w+|reInsertVariable) *=.+/g, function(match) {
return match.replace(/ |\\n/g, ''); return match.replace(/ |\\n/g, '');
@@ -302,10 +305,10 @@
// remove debug sourceURL use in `_.template` // remove debug sourceURL use in `_.template`
source = source.replace(/(?:\s*\/\/.*\n)* *if *\(useSourceURL[^}]+}/, ''); source = source.replace(/(?:\s*\/\/.*\n)* *if *\(useSourceURL[^}]+}/, '');
// minify internal properties used by 'compareAscending', `_.clone`, `_.merge`, and `_.sortBy` // minify internal properties used by 'compareAscending', `_.clone`, `_.isEqual`, `_.merge`, and `_.sortBy`
(function() { (function() {
var properties = ['criteria', 'index', 'source', 'value'], var properties = ['clones', 'criteria', 'index', 'sources', 'thorough', 'value', 'values'],
snippets = source.match(/( +)(?:function clone|function compareAscending|var merge|var sortBy)\b[\s\S]+?\n\1}/g); snippets = source.match(/( +)(?:function (?:clone|compareAscending|isEqual)|var merge|var sortBy)\b[\s\S]+?\n\1}/g);
if (!snippets) { if (!snippets) {
return; return;
@@ -319,7 +322,7 @@
properties.forEach(function(property, index) { properties.forEach(function(property, index) {
var reBracketProp = RegExp("\\['(" + property + ")'\\]", 'g'), var reBracketProp = RegExp("\\['(" + property + ")'\\]", 'g'),
reDotProp = RegExp('\\.' + property + '\\b', 'g'), reDotProp = RegExp('\\.' + property + '\\b', 'g'),
rePropColon = RegExp("(')?\\b" + property + "\\1 *:", 'g'); rePropColon = RegExp("([^?\\s])\\s*([\"'])?\\b" + property + "\\2 *:", 'g');
if (isCompilable) { if (isCompilable) {
// add quotes around properties in the inlined `_.merge` and `_.sortBy` // add quotes around properties in the inlined `_.merge` and `_.sortBy`
@@ -328,20 +331,20 @@
modified = modified modified = modified
.replace(reBracketProp, "['" + minNames[index] + "']") .replace(reBracketProp, "['" + minNames[index] + "']")
.replace(reDotProp, "['" + minNames[index] + "']") .replace(reDotProp, "['" + minNames[index] + "']")
.replace(rePropColon, "'" + minNames[index] + "':"); .replace(rePropColon, "$1'" + minNames[index] + "':");
} }
else { else {
modified = modified modified = modified
.replace(reBracketProp, '.' + minNames[index]) .replace(reBracketProp, '.' + minNames[index])
.replace(reDotProp, '.' + minNames[index]) .replace(reDotProp, '.' + minNames[index])
.replace(rePropColon, minNames[index] + ':'); .replace(rePropColon, '$1' + minNames[index] + ':');
} }
} }
else { else {
modified = modified modified = modified
.replace(reBracketProp, "['" + minNames[index] + "']") .replace(reBracketProp, "['" + minNames[index] + "']")
.replace(reDotProp, '.' + minNames[index]) .replace(reDotProp, '.' + minNames[index])
.replace(rePropColon, "'" + minNames[index] + "':") .replace(rePropColon, "$1'" + minNames[index] + "':")
// correct `value.source` in regexp branch of `_.clone` // correct `value.source` in regexp branch of `_.clone`
if (property == 'source') { if (property == 'source') {

34
component.json Normal file
View File

@@ -0,0 +1,34 @@
{
"name": "lodash",
"version": "0.7.0",
"description": "A drop-in replacement for Underscore.js delivering performance, bug fixes, and additional features.",
"homepage": "http://lodash.com",
"main": [
"./lodash.js",
"./lodash.min.js"
],
"keywords": [
"browser",
"client",
"functional",
"performance",
"server",
"speed",
"util"
],
"licenses": [
{
"type": "MIT",
"url": "http://lodash.com/license"
}
],
"author": {
"name": "John-David Dalton",
"email": "john.david.dalton@gmail.com",
"web": "http://allyoucanleet.com/"
},
"repository": {
"type": "git",
"url": "https://github.com/bestiejs/lodash.git"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -21,7 +21,7 @@
// generate Markdown // generate Markdown
$markdown = docdown(array( $markdown = docdown(array(
'path' => '../' . $file, 'path' => '../' . $file,
'title' => 'Lo-Dash <sup>v0.6.1</sup>', 'title' => 'Lo-Dash <sup>v0.7.0</sup>',
'url' => 'https://github.com/bestiejs/lodash/blob/master/lodash.js' 'url' => 'https://github.com/bestiejs/lodash/blob/master/lodash.js'
)); ));

636
lodash.js

File diff suppressed because it is too large Load Diff

77
lodash.min.js vendored
View File

@@ -1,41 +1,42 @@
/*! /*!
Lo-Dash 0.6.1 lodash.com/license Lo-Dash 0.7.0 lodash.com/license
Underscore.js 1.3.3 github.com/documentcloud/underscore/blob/master/LICENSE Underscore.js 1.3.3 github.com/documentcloud/underscore/blob/master/LICENSE
*/ */
;(function(e,t){function s(e){return new o(e)}function o(e){if(e&&e._wrapped)return e;this._wrapped=e}function u(e,t,n){t||(t=0);var r=e.length,i=r-t>=(n||V),s=i?{}:e;if(i)for(var o=t-1;++o<r;)n=e[o]+"",(ft.call(s,n)?s[n]:s[n]=[]).push(e[o]);return function(e){if(i){var n=e+"";return ft.call(s,n)&&-1<k(s[n],e)}return-1<k(s,e,t)}}function a(){for(var e,t,n,s=-1,o=arguments.length,u={e:"",f:"",j:"",q:"",c:{d:""},m:{d:""}};++s<o;)for(t in e=arguments[s],e)n=(n=e[t])==r?"":n,/d|i/.test(t)? ;(function(e,t){function s(e){return new o(e)}function o(e){if(e&&e.__wrapped__)return e;this.__wrapped__=e}function u(e,t,n){t||(t=0);var r=e.length,i=r-t>=(n||V),s=i?{}:e;if(i)for(var o=t-1;++o<r;)n=e[o]+"",(lt.call(s,n)?s[n]:s[n]=[]).push(e[o]);return function(e){if(i){var n=e+"";return lt.call(s,n)&&-1<k(s[n],e)}return-1<k(s,e,t)}}function a(){for(var e,t,n,s=-1,o=arguments.length,u={e:"",f:"",j:"",q:"",c:{d:""},m:{d:""}};++s<o;)for(t in e=arguments[s],e)n=(n=e[t])==r?"":n,/d|i/.test(t)?("string"==typeof
("string"==typeof n&&(n={b:n,l:n}),u.c[t]=n.b||"",u.m[t]=n.l||""):u[t]=n;e=u.a,t=/^[^,]+/.exec(e)[0],n=u.s,u.g=t,u.h=Lt,u.k=Ft,u.n=Mt,u.p=st,u.r=u.r!==i,u.s=n==r?It:n,u.o==r&&(u.o=Pt),u.f||(u.f="if(!"+t+")return u");if("d"!=t||!u.c.i)u.c=r;t="",u.s&&(t+="'use strict';"),t+="var i,A,j="+u.g+",u",u.j&&(t+="="+u.j),t+=";"+u.f+";"+u.q+";",u.c&&(t+="var l=j.length;i=-1;",u.m&&(t+="if(l>-1&&l===l>>>0){"),u.o&&(t+="if(z.call(j)==x){j=j.split('')}"),t+=u.c.d+";while(++i<l){A=j[i];"+u.c.i+"}",u.m&&(t+="}" n&&(n={b:n,l:n}),u.c[t]=n.b||"",u.m[t]=n.l||""):u[t]=n;e=u.a,t=/^[^,]+/.exec(e)[0],n=u.s,u.g=t,u.h=Dt,u.k=zt,u.n=Bt,u.p=st,u.r=u.r!==i,u.s=n==r?Wt:n,u.o==r&&(u.o=It),u.f||(u.f="if(!"+t+")return u");if("d"!=t||!u.c.i)u.c=r;t="",u.s&&(t+="'use strict';"),t+="var i,A,j="+u.g+",u",u.j&&(t+="="+u.j),t+=";"+u.f+";"+u.q+";",u.c&&(t+="var l=j.length;i=-1;",u.m&&(t+="if(l>-1&&l===l>>>0){"),u.o&&(t+="if(z.call(j)==x){j=j.split('')}"),t+=u.c.d+";while(++i<l){A=j[i];"+u.c.i+"}",u.m&&(t+="}"));if(u.m){u.c?t+="else{"
));if(u.m){u.c?t+="else{":u.n&&(t+="var l=j.length;i=-1;if(l&&P(j)){while(++i<l){A=j[i+=''];"+u.m.i+"}}else{"),u.h||(t+="var v=typeof j=='function'&&r.call(j,'prototype');");if(u.k&&u.r)t+="var o=-1,p=Y[typeof j]?m(j):[],l=p.length;"+u.m.d+";while(++o<l){i=p[o];",u.h||(t+="if(!(v&&i=='prototype')){"),t+="A=j[i];"+u.m.i+"",u.h||(t+="}");else{t+=u.m.d+";for(i in j){";if(!u.h||u.r)t+="if(",u.h||(t+="!(v&&i=='prototype')"),!u.h&&u.r&&(t+="&&"),u.r&&(t+="g.call(j,i)"),t+="){";t+="A=j[i];"+u.m.i+";";if(! :u.n&&(t+="var l=j.length;i=-1;if(l&&P(j)){while(++i<l){A=j[i+=''];"+u.m.i+"}}else{"),u.h||(t+="var v=typeof j=='function'&&r.call(j,'prototype');");if(u.k&&u.r)t+="var o=-1,p=Y[typeof j]?m(j):[],l=p.length;"+u.m.d+";while(++o<l){i=p[o];",u.h||(t+="if(!(v&&i=='prototype')){"),t+="A=j[i];"+u.m.i+"",u.h||(t+="}");else{t+=u.m.d+";for(i in j){";if(!u.h||u.r)t+="if(",u.h||(t+="!(v&&i=='prototype')"),!u.h&&u.r&&(t+="&&"),u.r&&(t+="g.call(j,i)"),t+="){";t+="A=j[i];"+u.m.i+";";if(!u.h||u.r)t+="}"}t+="}";
u.h||u.r)t+="}"}t+="}";if(u.h){t+="var f=j.constructor;";for(n=0;7>n;n++)t+="i='"+u.p[n]+"';if(","constructor"==u.p[n]&&(t+="!(f&&f.prototype===j)&&"),t+="g.call(j,i)){A=j[i];"+u.m.i+"}"}if(u.c||u.n)t+="}"}return t+=u.e+";return u",Function("D,E,F,I,e,K,g,h,N,P,R,T,U,k,X,Y,m,r,w,x,z","var G=function("+e+"){"+t+"};return G")(qt,q,_,f,at,un,ft,D,k,b,tn,w,E,p,xt,Wt,gt,ct,ht,Nt,pt)}function f(e,n){var r=e.b,i=n.b,e=e.a,n=n.a;return e===t?1:n===t?-1:e<n?-1:e>n?1:r<i?-1:1}function l(e,t){return ut[t]}function c if(u.h){t+="var f=j.constructor;";for(n=0;7>n;n++)t+="i='"+u.p[n]+"';if(","constructor"==u.p[n]&&(t+="!(f&&f.prototype===j)&&"),t+="g.call(j,i)){A=j[i];"+u.m.i+"}"}if(u.c||u.n)t+="}"}return t+=u.e+";return u",Function("D,E,F,I,e,K,g,h,N,P,R,T,U,k,X,Y,m,r,w,x,z","var G=function("+e+"){"+t+"};return G")(Xt,q,_,f,ft,hn,lt,D,k,b,un,w,an,p,Lt,Kt,bt,ht,pt,Ot,dt)}function f(e,n){var r=e.c,i=n.c,e=e.b,n=n.b;if(e!==n){if(e>n||e===t)return 1;if(e<n||n===t)return-1}return r<i?-1:1}function l(e,t){return at[
(e){return"\\"+Xt[e]}function h(e){return Ut[e]}function p(e,t){return function(n,r,i){return e.call(t,n,r,i)}}function d(){}function v(e,t){if(e&&J.test(t))return"<e%-"+t+"%>";var n=ut.length;return ut[n]="'+__e("+t+")+'",ot+n}function m(e,t,n,i){return i?(e=ut.length,ut[e]="';"+i+";__p+='",ot+e):t?v(r,t):g(r,n)}function g(e,t){if(e&&J.test(t))return"<e%="+t+"%>";var n=ut.length;return ut[n]="'+((__t=("+t+"))==null?'':__t)+'",ot+n}function y(e){return zt[e]}function b(e){return pt.call(e)==yt}function w t]}function c(e){return"\\"+Qt[e]}function h(e){return $t[e]}function p(e,t){return function(n,r,i){return e.call(t,n,r,i)}}function d(){}function v(e,t){if(e&&J.test(t))return"<e%-"+t+"%>";var n=at.length;return at[n]="'+__e("+t+")+'",ot+n+ut}function m(e,t,n,i){return i?(e=at.length,at[e]="';"+i+";__p+='",ot+e+ut):t?v(r,t):g(r,n)}function g(e,t){if(e&&J.test(t))return"<e%="+t+"%>";var n=at.length;return at[n]="'+((__t=("+t+"))==null?'':__t)+'",ot+n+ut}function y(e){return Jt[e]}function b(e){return dt
(e){return"function"==typeof e}function E(e,t){return e?e==U||e.__proto__==U&&(t||!b(e)):i}function S(e,t,s,o,u){if(e==r)return e;s&&(t=i),u||(u={d:r}),u.d==r&&(u.d=!(!R.clone&&!z.clone&&!W.clone));if(((s=Wt[typeof e])||u.d)&&e.clone&&w(e.clone))return u.d=r,e.clone(t);if(s){var a=pt.call(e);if(!Rt[a]||_t&&b(e))return e;var f=a==bt,s=f||(a==xt?E(e,n):s)}if(!s||!t)return s?f?ht.call(e):on({},e):e;s=e.constructor;switch(a){case wt:return new s(e==n);case Et:return new s(+e);case St:case Nt:return new .call(e)==xt}function w(e){return"function"==typeof e}function E(e,t){var n=i;if(!e||"object"!=typeof e||!t&&b(e))return n;var r=e.constructor;return(!qt||"function"==typeof e.toString||"string"!=typeof (e+""))&&(!w(r)||r instanceof r)?Ht?(hn(e,function(e,t,r){return n=!lt.call(r,t),i}),n===i):(hn(e,function(e,t){n=t}),n===i||lt.call(e,n)):n}function S(e,t,s,o){if(e==r)return e;s&&(t=i),o||(o={e:r}),o.t==r&&(o.t=!(!R.clone&&!z.clone&&!W.clone));if(((s=Kt[typeof e])||o.t)&&e.clone&&w(e.clone))return o
s(e);case Tt:return s(e.source,Z.exec(e))}o||(o=[]);for(a=o.length;a--;)if(o[a].c==e)return o[a].d;var a=e.length,l=f?s(a):{};o.push({d:l,c:e});if(f)for(f=-1;++f<a;)l[f]=S(e[f],t,r,o,u);else an(e,function(e,n){l[n]=S(e,t,r,o,u)});return l}function x(e,t,s,o){if(e==r||t==r)return e===t;o||(o={value:r}),o.value==r&&(o.value=!(!R.isEqual&&!z.isEqual&&!W.isEqual));if(Wt[typeof e]||Wt[typeof t]||o.value){e._chain&&(e=e._wrapped),t._chain&&(t=t._wrapped);if(e.isEqual&&w(e.isEqual))return o.value=r,e.isEqual .t=r,e.clone(t);if(s){var u=dt.call(e);if(!Vt[u]||jt&&b(e))return e;var a=u==Tt,s=a||(u==Lt?an(e,n):s)}if(!s||!t)return s?a?pt.call(e):cn({},e):e;s=e.constructor;switch(u){case Nt:return new s(e==n);case Ct:return new s(+e);case kt:case Ot:return new s(e);case At:return s(e.source,Z.exec(e))}for(var f=o.a||(o.a=[]),l=o.d||(o.d=[]),u=f.length;u--;)if(l[u]==e)return f[u];var c=a?s(u=e.length):{};f.push(c),l.push(e);if(a)for(a=-1;++a<u;)c[a]=S(e[a],t,r,o);else pn(e,function(e,n){c[n]=S(e,t,r,o)});return c
(t);if(t.isEqual&&w(t.isEqual))return o.value=r,t.isEqual(e)}if(e===t)return 0!==e||1/e==1/t;var u=pt.call(e);if(u!=pt.call(t))return i;switch(u){case wt:case Et:return+e==+t;case St:return e!=+e?t!=+t:0==e?1/e==1/t:e==+t;case Tt:case Nt:return e==t+""}var a=qt[u];if(_t&&!a&&(a=b(e))&&!b(t)||!a&&(u!=xt||Ht&&("function"!=typeof e.toString&&"string"==typeof (e+"")||"function"!=typeof t.toString&&"string"==typeof (t+""))))return i;s||(s=[]);for(u=s.length;u--;)if(s[u]==e)return n;var u=-1,f=n,l=0;s. }function x(e,t,s){if(e==r||t==r)return e===t;s||(s={e:r}),s.t==r&&(s.t=!(!R.isEqual&&!z.isEqual&&!W.isEqual));if(Kt[typeof e]||Kt[typeof t]||s.t){e=e.__wrapped__||e,t=t.__wrapped__||t;if(e.isEqual&&w(e.isEqual))return s.t=r,e.isEqual(t);if(t.isEqual&&w(t.isEqual))return s.t=r,t.isEqual(e)}if(e===t)return 0!==e||1/e==1/t;var o=dt.call(e);if(o!=dt.call(t))return i;switch(o){case Nt:case Ct:return+e==+t;case kt:return e!=+e?t!=+t:0==e?1/e==1/t:e==+t;case At:case Ot:return e==t+""}var u=Xt[o];if(jt&&!
push(e);if(a){l=e.length;if(f=l==t.length)for(;l--&&(f=x(e[l],t[l],s,o)););return f}a=e.constructor,f=t.constructor;if(a!=f&&(!w(a)||!(a instanceof a&&w(f)&&f instanceof f)))return i;for(var c in e)if(ft.call(e,c)&&(l++,!ft.call(t,c)||!x(e[c],t[c],s,o)))return i;for(c in t)if(ft.call(t,c)&&!(l--))return i;if(Lt)for(;7>++u;)if(c=st[u],ft.call(e,c)&&(!ft.call(t,c)||!x(e[c],t[c],s,o)))return i;return n}function T(e,t,n,r){if(!e)return n;var i=e.length,s=3>arguments.length;r&&(t=p(t,r));if(-1<i&&i=== u&&(u=b(e))&&!b(t)||!u&&(o!=Lt||qt&&("function"!=typeof e.toString&&"string"==typeof (e+"")||"function"!=typeof t.toString&&"string"==typeof (t+""))))return i;for(var a=s.stackA||(s.stackA=[]),f=s.stackB||(s.stackB=[]),o=a.length;o--;)if(a[o]==e)return f[o]==t;var o=-1,l=n,c=0;a.push(e),f.push(t);if(u){c=e.length;if(l=c==t.length)for(;c--&&(l=x(e[c],t[c],s)););return l}u=e.constructor,a=t.constructor;if(u!=a&&(!w(u)||!(u instanceof u&&w(a)&&a instanceof a)))return i;for(var h in e)if(lt.call(e,h)&&
i>>>0){var o=Pt&&pt.call(e)==Nt?e.split(""):e;for(i&&s&&(n=o[--i]);i--;)n=t(n,o[i],i,e);return n}o=cn(e);for((i=o.length)&&s&&(n=e[o[--i]]);i--;)s=o[i],n=t(n,e[s],s,e);return n}function N(e,t,n){if(e)return t==r||n?e[0]:ht.call(e,0,t)}function C(e,t){var n=[];if(!e)return n;for(var r,i=-1,s=e.length;++i<s;)r=e[i],tn(r)?lt.apply(n,t?r:C(r)):n.push(r);return n}function k(e,t,n){if(!e)return-1;var r=-1,i=e.length;if(n){if("number"!=typeof n)return r=O(e,t),e[r]===t?r:-1;r=(0>n?Math.max(0,i+n):n)-1}for( (c++,!lt.call(t,h)||!x(e[h],t[h],s)))return i;for(h in t)if(lt.call(t,h)&&!(c--))return i;if(Dt)for(;7>++o;)if(h=st[o],lt.call(e,h)&&(!lt.call(t,h)||!x(e[h],t[h],s)))return i;return n}function T(e,t,n,r){if(!e)return n;var i=e.length,s=3>arguments.length;r&&(t=p(t,r));if(-1<i&&i===i>>>0){var o=It&&dt.call(e)==Ot?e.split(""):e;for(i&&s&&(n=o[--i]);i--;)n=t(n,o[i],i,e);return n}o=gn(e);for((i=o.length)&&s&&(n=e[o[--i]]);i--;)s=o[i],n=t(n,e[s],s,e);return n}function N(e,t,n){if(e)return t==r||n?e[0]
;++r<i;)if(e[r]===t)return r;return-1}function L(e,t,n){var r=-Infinity,i=r;if(!e)return i;var s=-1,o=e.length;if(!t){for(;++s<o;)e[s]>i&&(i=e[s]);return i}for(n&&(t=p(t,n));++s<o;)n=t(e[s],s,e),n>r&&(r=n,i=e[s]);return i}function A(e,t,n){return e?ht.call(e,t==r||n?1:t):[]}function O(e,t,n,r){if(!e)return 0;var i=0,s=e.length;if(n){r&&(n=_(n,r));for(t=n(t);i<s;)r=i+s>>>1,n(e[r])<t?i=r+1:s=r}else for(;i<s;)r=i+s>>>1,e[r]<t?i=r+1:s=r;return i}function M(e,t,n,r){var s=[];if(!e)return s;var o=-1,u= :pt.call(e,0,t)}function C(e,t){var n=[];if(!e)return n;for(var r,i=-1,s=e.length;++i<s;)r=e[i],un(r)?ct.apply(n,t?r:C(r)):n.push(r);return n}function k(e,t,n){if(!e)return-1;var r=-1,i=e.length;if(n){if("number"!=typeof n)return r=O(e,t),e[r]===t?r:-1;r=(0>n?wt(0,i+n):n)-1}for(;++r<i;)if(e[r]===t)return r;return-1}function L(e,t,n){var r=-Infinity,i=r;if(!e)return i;var s=-1,o=e.length;if(!t){for(;++s<o;)e[s]>i&&(i=e[s]);return i}for(n&&(t=p(t,n));++s<o;)n=t(e[s],s,e),n>r&&(r=n,i=e[s]);return i}
e.length,a=[];"function"==typeof t&&(r=n,n=t,t=i);for(n?r&&(n=p(n,r)):n=D;++o<u;)if(r=n(e[o],o,e),t?!o||a[a.length-1]!==r:0>k(a,r))a.push(r),s.push(e[o]);return s}function _(e,t){function n(){var o=arguments,u=t;return i||(e=t[r]),s.length&&(o=o.length?s.concat(ht.call(o)):s),this instanceof n?(d.prototype=e.prototype,u=new d,(o=e.apply(u,o))&&Wt[typeof o]?o:u):e.apply(u,o)}var r,i=w(e);if(i){if(jt||dt&&2<arguments.length)return dt.call.apply(dt,arguments)}else r=t,t=e;var s=ht.call(arguments,2); function A(e,t,n){return e?pt.call(e,t==r||n?1:t):[]}function O(e,t,n,r){if(!e)return 0;var i=0,s=e.length;if(n){r&&(n=_(n,r));for(t=n(t);i<s;)r=i+s>>>1,n(e[r])<t?i=r+1:s=r}else for(;i<s;)r=i+s>>>1,e[r]<t?i=r+1:s=r;return i}function M(e,t,n,r){var s=[];if(!e)return s;var o=-1,u=e.length,a=[];"function"==typeof t&&(r=n,n=t,t=i);for(n?r&&(n=p(n,r)):n=D;++o<u;)if(r=n(e[o],o,e),t?!o||a[a.length-1]!==r:0>k(a,r))a.push(r),s.push(e[o]);return s}function _(e,t){function n(){var o=arguments,u=t;return i||
return n}function D(e){return e}function P(e){wn(fn(e),function(t){var r=s[t]=e[t];o.prototype[t]=function(){var e=[this._wrapped];return arguments.length&&lt.apply(e,arguments),e=r.apply(s,e),this._chain&&(e=new o(e),e._chain=n),e}})}var n=!0,r=null,i=!1,H,B,j,F,I="object"==typeof exports&&exports&&("object"==typeof global&&global&&global==global.global&&(e=global),exports),q=Array.prototype,R=Boolean.prototype,U=Object.prototype,z=Number.prototype,W=String.prototype,X=0,V=30,$=e._,J=/[-+=!~*%&^<>|{(\/]|\[\D|\b(?:delete|in|instanceof|new|typeof|void)\b/ (e=t[r]),s.length&&(o=o.length?s.concat(pt.call(o)):s),this instanceof n?(d.prototype=e.prototype,u=new d,(o=e.apply(u,o))&&Kt[typeof o]?o:u):e.apply(u,o)}var r,i=w(e);if(i){if(Ut||vt&&2<arguments.length)return vt.call.apply(vt,arguments)}else r=t,t=e;var s=pt.call(arguments,2);return n}function D(e){return e}function P(e){Ln(dn(e),function(t){var r=s[t]=e[t];o.prototype[t]=function(){var e=[this.__wrapped__];return arguments.length&&ct.apply(e,arguments),e=r.apply(s,e),this.__chain__&&(e=new o(e
,K=/&(?:amp|lt|gt|quot|#x27);/g,Q=/\b__p\+='';/g,G=/\b(__p\+=)''\+/g,Y=/(__e\(.*?\)|\b__t\))\+'';/g,Z=/\w*$/,et=/(?:__e|__t=)\(\s*(?![\d\s"']|this\.)/g,tt=RegExp("^"+(U.valueOf+"").replace(/[.*+?^=!:${}()|[\]\/\\]/g,"\\$&").replace(/valueOf|for [^\]]+/g,".+?")+"$"),nt=/__token__(\d+)/g,rt=/[&<>"']/g,it=/['\n\r\t\u2028\u2029\\]/g,st="constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf".split(" "),ot="__token__",ut=[],at=q.concat,ft=U.hasOwnProperty,lt=q.push ),e.__chain__=n),e}})}var n=!0,r=null,i=!1,H,B,j,F,I="object"==typeof exports&&exports&&("object"==typeof global&&global&&global==global.global&&(e=global),exports),q=Array.prototype,R=Boolean.prototype,U=Object.prototype,z=Number.prototype,W=String.prototype,X=0,V=30,$=e._,J=/[-?+=!~*%&^<>|{(\/]|\[\D|\b(?:delete|in|instanceof|new|typeof|void)\b/,K=/&(?:amp|lt|gt|quot|#x27);/g,Q=/\b__p\+='';/g,G=/\b(__p\+=)''\+/g,Y=/(__e\(.*?\)|\b__t\))\+'';/g,Z=/\w*$/,et=/(?:__e|__t=)\(\s*(?![\d\s"']|this\.)/g,tt=
,ct=U.propertyIsEnumerable,ht=q.slice,pt=U.toString,dt=tt.test(dt=ht.bind)&&dt,vt=tt.test(vt=Array.isArray)&&vt,mt=e.isFinite,gt=tt.test(gt=Object.keys)&&gt,yt="[object Arguments]",bt="[object Array]",wt="[object Boolean]",Et="[object Date]",St="[object Number]",xt="[object Object]",Tt="[object RegExp]",Nt="[object String]",Ct=e.clearTimeout,kt=e.setTimeout,Lt,At,Ot,Mt=n;(function(){function e(){this.x=1}var t={0:1,length:1},n=[];e.prototype={valueOf:1,y:1};for(var r in new e)n.push(r);for(r in arguments RegExp("^"+(U.valueOf+"").replace(/[.*+?^=!:${}()|[\]\/\\]/g,"\\$&").replace(/valueOf|for [^\]]+/g,".+?")+"$"),nt=/__token(\d+)__/g,rt=/[&<>"']/g,it=/['\n\r\t\u2028\u2029\\]/g,st="constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf".split(" "),ot="__token",ut="__",at=[],ft=q.concat,lt=U.hasOwnProperty,ct=q.push,ht=U.propertyIsEnumerable,pt=q.slice,dt=U.toString,vt=tt.test(vt=pt.bind)&&vt,mt=Math.floor,gt=tt.test(gt=Array.isArray)&&gt,yt=e.isFinite,bt=tt.test
)Mt=!r;Lt=4>(n+"").length,Ot="x"!=n[0],At=(n.splice.call(t,0,1),t[0])})(1);var _t=!b(arguments),Dt="x"!=ht.call("x")[0],Pt="xx"!="x"[0]+Object("x")[0];try{var Ht=("[object Object]",pt.call(e.document||0)==xt)}catch(Bt){}var jt=dt&&/\n|Opera/.test(dt+pt.call(e.opera)),Ft=gt&&/^.+$|true/.test(gt+!!e.attachEvent),It=!jt,qt={"[object Arguments]":n,"[object Array]":n,"[object Boolean]":i,"[object Date]":i,"[object Function]":i,"[object Number]":i,"[object Object]":i,"[object RegExp]":i,"[object String]" (bt=Object.keys)&&bt,wt=Math.max,Et=Math.min,St=Math.random,xt="[object Arguments]",Tt="[object Array]",Nt="[object Boolean]",Ct="[object Date]",kt="[object Number]",Lt="[object Object]",At="[object RegExp]",Ot="[object String]",Mt=e.clearTimeout,_t=e.setTimeout,Dt,Pt,Ht,Bt=n;(function(){function e(){this.x=1}var t={0:1,length:1},n=[];e.prototype={valueOf:1,y:1};for(var r in new e)n.push(r);for(r in arguments)Bt=!r;Dt=4>(n+"").length,Ht="x"!=n[0],Pt=(n.splice.call(t,0,1),t[0])})(1);var jt=!b(arguments
:n},Rt={"[object Arguments]":i,"[object Array]":n,"[object Boolean]":n,"[object Date]":n,"[object Function]":i,"[object Number]":n,"[object Object]":n,"[object RegExp]":n,"[object String]":n},Ut={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;"},zt={"&amp;":"&","&lt;":"<","&gt;":">","&quot;":'"',"&#x27;":"'"},Wt={"boolean":i,"function":n,object:n,number:i,string:i,"undefined":i,unknown:n},Xt={"\\":"\\","'":"'","\n":"n","\r":"r"," ":"t","\u2028":"u2028","\u2029":"u2029"};s.templateSettings= ),Ft="x"!=pt.call("x")[0],It="xx"!="x"[0]+Object("x")[0];try{var qt=("[object Object]",dt.call(e.document||0)==Lt)}catch(Rt){}var Ut=vt&&/\n|Opera/.test(vt+dt.call(e.opera)),zt=bt&&/^.+$|true/.test(bt+!!e.attachEvent),Wt=!Ut,Xt={};Xt[Nt]=Xt[Ct]=Xt["[object Function]"]=Xt[kt]=Xt[Lt]=Xt[At]=i,Xt[xt]=Xt[Tt]=Xt[Ot]=n;var Vt={};Vt[xt]=Vt["[object Function]"]=i,Vt[Tt]=Vt[Nt]=Vt[Ct]=Vt[kt]=Vt[Lt]=Vt[At]=Vt[Ot]=n;var $t={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;"},Jt={"&amp;":"&","&lt;":"<"
{escape:/<%-([\s\S]+?)%>/g,evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,variable:""};var Vt={a:"d,c,y",j:"d",q:"if(!c)c=h;else if(y)c=k(c,y)",i:"if(c(A,i,d)===false)return u"},$t={j:"{}",q:"var q;if(typeof c!='function'){var ii=c;c=function(A){return A[ii]}}else if(y)c=k(c,y)",i:"q=c(A,i,d);(g.call(u,q)?u[q]++:u[q]=1)"},Jt={r:i,a:"n,c,y",j:"{}",q:"var S=typeof c=='function';if(!S){var t=e.apply(E,arguments)}else if(y)c=k(c,y)",i:"if(S?!c(A,i,n):N(t,i)<0)u[i]=A"},Kt={j:"true",i:"if(!c(A,i,d))return!u" ,"&gt;":">","&quot;":'"',"&#x27;":"'"},Kt={"boolean":i,"function":n,object:n,number:i,string:i,"undefined":i,unknown:n},Qt={"\\":"\\","'":"'","\n":"n","\r":"r"," ":"t","\u2028":"u2028","\u2029":"u2029"};s.templateSettings={escape:/<%-([\s\S]+?)%>/g,evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,variable:""};var Gt={a:"d,c,y",j:"d",q:"if(!c)c=h;else if(y)c=k(c,y)",i:"if(c(A,i,d)===false)return u"},Yt={j:"{}",q:"var q;if(typeof c!='function'){var ii=c;c=function(A){return A[ii]}}else if(y)c=k(c,y)"
},Qt={r:i,s:i,a:"n",j:"n",q:"for(var a=1,b=arguments.length;a<b;a++){if(j=arguments[a]){",i:"u[i]=A",e:"}}"},Gt={j:"[]",i:"c(A,i,d)&&u.push(A)"},Yt={q:"if(y)c=k(c,y)"},Zt={i:{l:Vt.i}},en={j:"",f:"if(!d)return[]",d:{b:"u=Array(l)",l:"u="+(Ft?"Array(l)":"[]")},i:{b:"u[i]=c(A,i,d)",l:"u"+(Ft?"[o]=":".push")+"(c(A,i,d))"}};_t&&(b=function(e){return!!e&&!!ft.call(e,"callee")});var tn=vt||function(e){return pt.call(e)==bt};w(/x/)&&(w=function(e){return"[object Function]"==pt.call(e)}),E(Wt)||(E=function( ,i:"q=c(A,i,d);(g.call(u,q)?u[q]++:u[q]=1)"},Zt={j:"true",i:"if(!c(A,i,d))return!u"},en={r:i,s:i,a:"n",j:"n",q:"for(var a=1,b=arguments.length;a<b;a++){if(j=arguments[a]){",i:"u[i]=A",e:"}}"},tn={j:"[]",i:"c(A,i,d)&&u.push(A)"},nn={q:"if(y)c=k(c,y)"},rn={i:{l:Gt.i}},sn={j:"",f:"if(!d)return[]",d:{b:"u=Array(l)",l:"u="+(zt?"Array(l)":"[]")},i:{b:"u[i]=c(A,i,d)",l:"u"+(zt?"[o]=":".push")+"(c(A,i,d))"}},on={r:i,a:"n,c,y",j:"{}",q:"var S=typeof c=='function';if(!S){var t=e.apply(E,arguments)}else if(y)c=k(c,y)"
e,t){var n=i;if(!e||"object"!=typeof e||!t&&b(e))return n;var r=e.constructor;return(!Ht||"function"==typeof e.toString||"string"!=typeof (e+""))&&(!w(r)||r instanceof r)?Ot?(un(e,function(t,r){return n=!ft.call(e,r),i}),n===i):(un(e,function(e,t){n=t}),n===i||ft.call(e,n)):n});var nn=a({a:"n",j:"[]",i:"u.push(i)"}),rn=a(Qt,{i:"if(u[i]==null)"+Qt.i}),sn=a(Jt),on=a(Qt),un=a(Vt,Yt,Zt,{r:i}),an=a(Vt,Yt,Zt),fn=a({r:i,a:"n",j:"[]",i:"if(T(A))u.push(i)",e:"u.sort()"}),ln=a({a:"A",j:"true",q:"var H=z.call(A),l=A.length;if(D[H]"+ ,i:"if(S?!c(A,i,n):N(t,i)<0)u[i]=A"};jt&&(b=function(e){return!!e&&!!lt.call(e,"callee")});var un=gt||function(e){return dt.call(e)==Tt};w(/x/)&&(w=function(e){return"[object Function]"==dt.call(e)});var an=Kt.__proto__!=U?E:function(e,t){if(!e)return i;var n=e.valueOf,r="function"==typeof n&&(r=n.__proto__)&&r.__proto__;return r?e==r||e.__proto__==r&&(t||!b(e)):E(e)},fn=a({a:"n",j:"[]",i:"u.push(i)"}),ln=a(en,{i:"if(u[i]==null)"+en.i}),cn=a(en),hn=a(Gt,nn,rn,{r:i}),pn=a(Gt,nn,rn),dn=a({r:i,a:"n"
(_t?"||P(A)":"")+"||(H==X&&l>-1&&l===l>>>0&&T(A.splice)))return!l",i:{l:"return false"}}),cn=gt?function(e){var t=typeof e;return"function"==t&&ct.call(e,"prototype")?nn(e):e&&Wt[t]?gt(e):[]}:nn,hn=a(Qt,{a:"n,ee,O,ff",q:"var J,L,Q,gg,dd=O==U;if(!dd)ff=[];for(var a=1,b=dd?2:arguments.length;a<b;a++){if(j=arguments[a]){",i:"if(A&&((Q=R(A))||U(A))){L=false;gg=ff.length;while(gg--)if(L=ff[gg].c==A)break;if(L){u[i]=ff[gg].d}else{J=(J=u[i])&&Q?(R(J)?J:[]):(U(J)?J:{});ff.push({d:J,c:A});u[i]=G(J,A,U,ff)}}else if(A!=null)u[i]=A" ,j:"[]",i:"if(T(A))u.push(i)",e:"u.sort()"}),vn=a({a:"n",j:"{}",i:"u[A]=i"}),mn=a({a:"A",j:"true",q:"var H=z.call(A),l=A.length;if(D[H]"+(jt?"||P(A)":"")+"||(H==X&&l>-1&&l===l>>>0&&T(A.splice)))return!l",i:{l:"return false"}}),gn=bt?function(e){var t=typeof e;return"function"==t&&ht.call(e,"prototype")?fn(e):e&&Kt[t]?bt(e):[]}:fn,yn=a(en,{a:"n,ee,O",q:"var Q,dd=O==U,J=dd?arguments[3]:{g:[],d:[]};for(var a=1,b=dd?2:arguments.length;a<b;a++){if(j=arguments[a]){",i:"if((ee=A)&&((Q=R(ee))||U(ee))){var L=false,jj=J.g,ff=J.d,gg=ff.length;while(gg--)if(L=ff[gg]==ee)break;if(L){u[i]=jj[gg]}else{jj.push(A=(A=u[i])&&Q?(R(A)?A:[]):(U(A)?A:{}));ff.push(ee);u[i]=G(A,ee,U,J)}}else if(ee!=null)u[i]=ee"
}),pn=a(Jt,{q:"if(typeof c!='function'){var q,t=e.apply(E,arguments),l=t.length;for(i=1;i<l;i++){q=t[i];if(q in n)u[q]=n[q]}}else{if(y)c=k(c,y)",i:"if(c(A,i,n))u[i]=A",e:"}"}),dn=a({a:"n",j:"[]",i:"u.push(A)"}),vn=a({a:"d,hh",j:"false",o:i,d:{b:"if(z.call(d)==x)return d.indexOf(hh)>-1"},i:"if(A===hh)return true"}),mn=a(Vt,$t),gn=a(Vt,Kt),yn=a(Vt,Gt),bn=a(Vt,Yt,{j:"",i:"if(c(A,i,d))return A"}),wn=a(Vt,Yt),En=a(Vt,$t,{i:"q=c(A,i,d);(g.call(u,q)?u[q]:u[q]=[]).push(A)"}),Sn=a(en,{a:"d,V",q:"var C=w.call(arguments,2),S=typeof V=='function'" }),bn=a(on),wn=a({a:"n",j:"[]",i:"u"+(zt?"[o]=":".push")+"([i,A])"}),En=a(on,{q:"if(typeof c!='function'){var q,t=e.apply(E,arguments),l=t.length;for(i=1;i<l;i++){q=t[i];if(q in n)u[q]=n[q]}}else{if(y)c=k(c,y)",i:"if(c(A,i,n))u[i]=A",e:"}"}),Sn=a({a:"n",j:"[]",i:"u.push(A)"}),xn=a({a:"d,hh",j:"false",o:i,d:{b:"if(z.call(d)==x)return d.indexOf(hh)>-1"},i:"if(A===hh)return true"}),Tn=a(Gt,Yt),Nn=a(Gt,Zt),Cn=a(Gt,tn),kn=a(Gt,nn,{j:"",i:"if(c(A,i,d))return A"}),Ln=a(Gt,nn),An=a(Gt,Yt,{i:"q=c(A,i,d);(g.call(u,q)?u[q]:u[q]=[]).push(A)"
,i:{b:"u[i]=(S?V:A[V]).apply(A,C)",l:"u"+(Ft?"[o]=":".push")+"((S?V:A[V]).apply(A,C))"}}),xn=a(Vt,en),Tn=a(en,{a:"d,bb",i:{b:"u[i]=A[bb]",l:"u"+(Ft?"[o]=":".push")+"(A[bb])"}}),Nn=a({a:"d,c,B,y",j:"B",q:"var W=arguments.length<3;if(y)c=k(c,y)",d:{b:"if(W)u=j[++i]"},i:{b:"u=c(u,A,i,d)",l:"u=W?(W=false,A):c(u,A,i,d)"}}),Cn=a(Vt,Gt,{i:"!"+Gt.i}),kn=a(Vt,Kt,{j:"false",i:Kt.i.replace("!","")}),Ln=a(Vt,$t,en,{i:{b:"u[i]={a:c(A,i,d),b:i,d:A}",l:"u"+(Ft?"[o]=":".push")+"({a:c(A,i,d),b:i,d:A})"},e:"u.sort(I);l=u.length;while(l--)u[l]=u[l].d" }),On=a(sn,{a:"d,V",q:"var C=w.call(arguments,2),S=typeof V=='function'",i:{b:"u[i]=(S?V:A[V]).apply(A,C)",l:"u"+(zt?"[o]=":".push")+"((S?V:A[V]).apply(A,C))"}}),Mn=a(Gt,sn),_n=a(sn,{a:"d,bb",i:{b:"u[i]=A[bb]",l:"u"+(zt?"[o]=":".push")+"(A[bb])"}}),Dn=a({a:"d,c,B,y",j:"B",q:"var W=arguments.length<3;if(y)c=k(c,y)",d:{b:"if(W)u=j[++i]"},i:{b:"u=c(u,A,i,d)",l:"u=W?(W=false,A):c(u,A,i,d)"}}),Pn=a(Gt,tn,{i:"!"+tn.i}),Hn=a(Gt,Zt,{j:"false",i:Zt.i.replace("!","")}),Bn=a(Gt,Yt,sn,{i:{b:"u[i]={b:c(A,i,d),c:i,f:A}"
}),An=a(Gt,{a:"d,aa",q:"var t=[];K(aa,function(A,q){t.push(q)});var cc=t.length",i:"for(var q,Z=true,s=0;s<cc;s++){q=t[s];if(!(Z=A[q]===aa[q]))break}Z&&u.push(A)"}),On=a({r:i,s:i,a:"n",j:"n",q:"var M=arguments,l=M.length;if(l>1){for(var i=1;i<l;i++)u[M[i]]=F(u[M[i]],u);return u}",i:"if(T(u[i]))u[i]=F(u[i],u)"});s.VERSION="0.6.1",s.after=function(e,t){return 1>e?t():function(){if(1>--e)return t.apply(this,arguments)}},s.bind=_,s.bindAll=On,s.chain=function(e){return e=new o(e),e._chain=n,e},s.clone= ,l:"u"+(zt?"[o]=":".push")+"({b:c(A,i,d),c:i,f:A})"},e:"u.sort(I);l=u.length;while(l--)u[l]=u[l].f"}),jn=a(tn,{a:"d,aa",q:"var t=[];K(aa,function(A,q){t.push(q)});var cc=t.length",i:"for(var q,Z=true,s=0;s<cc;s++){q=t[s];if(!(Z=A[q]===aa[q]))break}Z&&u.push(A)"}),Fn=a({r:i,s:i,a:"n",j:"n",q:"var M=arguments,l=M.length;if(l>1){for(var i=1;i<l;i++)u[M[i]]=F(u[M[i]],u);return u}",i:"if(T(u[i]))u[i]=F(u[i],u)"});s.VERSION="0.7.0",s.after=function(e,t){return 1>e?t():function(){if(1>--e)return t.apply
S,s.compact=function(e){var t=[];if(!e)return t;for(var n=-1,r=e.length;++n<r;)e[n]&&t.push(e[n]);return t},s.compose=function(){var e=arguments;return function(){for(var t=arguments,n=e.length;n--;)t=[e[n].apply(this,t)];return t[0]}},s.contains=vn,s.countBy=mn,s.debounce=function(e,t,n){function i(){a=r,n||e.apply(u,s)}var s,o,u,a;return function(){var r=n&&!a;return s=arguments,u=this,Ct(a),a=kt(i,t),r&&(o=e.apply(u,s)),o}},s.defaults=rn,s.defer=function(e){var n=ht.call(arguments,1);return kt (this,arguments)}},s.bind=_,s.bindAll=Fn,s.chain=function(e){return e=new o(e),e.__chain__=n,e},s.clone=S,s.compact=function(e){var t=[];if(!e)return t;for(var n=-1,r=e.length;++n<r;)e[n]&&t.push(e[n]);return t},s.compose=function(){var e=arguments;return function(){for(var t=arguments,n=e.length;n--;)t=[e[n].apply(this,t)];return t[0]}},s.contains=xn,s.countBy=Tn,s.debounce=function(e,t,n){function i(){a=r,n||(o=e.apply(u,s))}var s,o,u,a;return function(){var r=n&&!a;return s=arguments,u=this,Mt
(function(){return e.apply(t,n)},1)},s.delay=function(e,n){var r=ht.call(arguments,2);return kt(function(){return e.apply(t,r)},n)},s.difference=function(e){var t=[];if(!e)return t;for(var n=-1,r=e.length,i=at.apply(t,arguments),i=u(i,r);++n<r;)i(e[n])||t.push(e[n]);return t},s.drop=sn,s.escape=function(e){return e==r?"":(e+"").replace(rt,h)},s.every=gn,s.extend=on,s.filter=yn,s.find=bn,s.first=N,s.flatten=C,s.forEach=wn,s.forIn=un,s.forOwn=an,s.functions=fn,s.groupBy=En,s.has=function(e,t){return e? (a),a=_t(i,t),r&&(o=e.apply(u,s)),o}},s.defaults=ln,s.defer=function(e){var n=pt.call(arguments,1);return _t(function(){return e.apply(t,n)},1)},s.delay=function(e,n){var r=pt.call(arguments,2);return _t(function(){return e.apply(t,r)},n)},s.difference=function(e){var t=[];if(!e)return t;for(var n=-1,r=e.length,i=ft.apply(t,arguments),i=u(i,r);++n<r;)i(e[n])||t.push(e[n]);return t},s.escape=function(e){return e==r?"":(e+"").replace(rt,h)},s.every=Nn,s.extend=cn,s.filter=Cn,s.find=kn,s.first=N,s.flatten=
ft.call(e,t):i},s.identity=D,s.indexOf=k,s.initial=function(e,t,n){return e?ht.call(e,0,-(t==r||n?1:t)):[]},s.intersection=function(e){var t=[];if(!e)return t;var n,r=arguments.length,i=[],s=-1,o=e.length;e:for(;++s<o;)if(n=e[s],0>k(t,n)){for(var a=1;a<r;a++)if(!(i[a]||(i[a]=u(arguments[a])))(n))continue e;t.push(n)}return t},s.invoke=Sn,s.isArguments=b,s.isArray=tn,s.isBoolean=function(e){return e===n||e===i||pt.call(e)==wt},s.isElement=function(e){return e?1===e.nodeType:i},s.isEmpty=ln,s.isEqual= C,s.forEach=Ln,s.forIn=hn,s.forOwn=pn,s.functions=dn,s.groupBy=An,s.has=function(e,t){return e?lt.call(e,t):i},s.identity=D,s.indexOf=k,s.initial=function(e,t,n){return e?pt.call(e,0,-(t==r||n?1:t)):[]},s.intersection=function(e){var t=[];if(!e)return t;var n,r=arguments.length,i=[],s=-1,o=e.length;e:for(;++s<o;)if(n=e[s],0>k(t,n)){for(var a=1;a<r;a++)if(!(i[a]||(i[a]=u(arguments[a])))(n))continue e;t.push(n)}return t},s.invert=vn,s.invoke=On,s.isArguments=b,s.isArray=un,s.isBoolean=function(e){return e===
x,s.isFinite=function(e){return mt(e)&&pt.call(e)==St},s.isFunction=w,s.isNaN=function(e){return pt.call(e)==St&&e!=+e},s.isNull=function(e){return e===r},s.isObject=function(e){return e?Wt[typeof e]:i},s.isUndefined=function(e){return e===t},s.keys=cn,s.last=function(e,t,n){if(e){var i=e.length;return t==r||n?e[i-1]:ht.call(e,-t||i)}},s.lastIndexOf=function(e,t,n){if(!e)return-1;var r=e.length;for(n&&"number"==typeof n&&(r=(0>n?Math.max(0,r+n):Math.min(n,r-1))+1);r--;)if(e[r]===t)return r;return-1 n||e===i||dt.call(e)==Nt},s.isElement=function(e){return e?1===e.nodeType:i},s.isEmpty=mn,s.isEqual=x,s.isFinite=function(e){return yt(e)&&dt.call(e)==kt},s.isFunction=w,s.isNaN=function(e){return dt.call(e)==kt&&e!=+e},s.isNull=function(e){return e===r},s.isObject=function(e){return e?Kt[typeof e]:i},s.isUndefined=function(e){return e===t},s.keys=gn,s.last=function(e,t,n){if(e){var i=e.length;return t==r||n?e[i-1]:pt.call(e,-t||i)}},s.lastIndexOf=function(e,t,n){if(!e)return-1;var r=e.length;for(
},s.map=xn,s.max=L,s.memoize=function(e,t){var n={};return function(){var r=t?t.apply(this,arguments):arguments[0];return ft.call(n,r)?n[r]:n[r]=e.apply(this,arguments)}},s.merge=hn,s.min=function(e,t,n){var r=Infinity,i=r;if(!e)return i;var s=-1,o=e.length;if(!t){for(;++s<o;)e[s]<i&&(i=e[s]);return i}for(n&&(t=p(t,n));++s<o;)n=t(e[s],s,e),n<r&&(r=n,i=e[s]);return i},s.mixin=P,s.noConflict=function(){return e._=$,this},s.once=function(e){var t,s=i;return function(){return s?t:(s=n,t=e.apply(this, n&&"number"==typeof n&&(r=(0>n?wt(0,r+n):Et(n,r-1))+1);r--;)if(e[r]===t)return r;return-1},s.map=Mn,s.max=L,s.memoize=function(e,t){var n={};return function(){var r=t?t.apply(this,arguments):arguments[0];return lt.call(n,r)?n[r]:n[r]=e.apply(this,arguments)}},s.merge=yn,s.min=function(e,t,n){var r=Infinity,i=r;if(!e)return i;var s=-1,o=e.length;if(!t){for(;++s<o;)e[s]<i&&(i=e[s]);return i}for(n&&(t=p(t,n));++s<o;)n=t(e[s],s,e),n<r&&(r=n,i=e[s]);return i},s.mixin=P,s.noConflict=function(){return e
arguments),e=r,t)}},s.partial=function(e){var t=ht.call(arguments,1),n=t.length;return function(){var r;return r=arguments,r.length&&(t.length=n,lt.apply(t,r)),r=1==t.length?e.call(this,t[0]):e.apply(this,t),t.length=n,r}},s.pick=pn,s.pluck=Tn,s.range=function(e,t,n){e=+e||0,n=+n||1,t==r&&(t=e,e=0);for(var i=-1,t=Math.max(0,Math.ceil((t-e)/n)),s=Array(t);++i<t;)s[i]=e,e+=n;return s},s.reduce=Nn,s.reduceRight=T,s.reject=Cn,s.rest=A,s.result=function(e,t){if(!e)return r;var n=e[t];return w(n)?e[t]( ._=$,this},s.object=function(e,t){if(!e)return{};for(var n=-1,r=e.length,i={};++n<r;)t?i[e[n]]=t[n]:i[e[n][0]]=e[n][1];return i},s.omit=bn,s.once=function(e){var t,s=i;return function(){return s?t:(s=n,t=e.apply(this,arguments),e=r,t)}},s.pairs=wn,s.partial=function(e){var t=pt.call(arguments,1),n=t.length;return function(){var r;return r=arguments,r.length&&(t.length=n,ct.apply(t,r)),r=1==t.length?e.call(this,t[0]):e.apply(this,t),t.length=n,r}},s.pick=En,s.pluck=_n,s.random=function(e,t){return e==
):n},s.shuffle=function(e){if(!e)return[];for(var t,n=-1,r=e.length,i=Array(r);++n<r;)t=Math.floor(Math.random()*(n+1)),i[n]=i[t],i[t]=e[n];return i},s.size=function(e){if(!e)return 0;var t=pt.call(e),n=e.length;return qt[t]||_t&&b(e)||t==xt&&-1<n&&n===n>>>0&&w(e.splice)?n:cn(e).length},s.some=kn,s.sortBy=Ln,s.sortedIndex=O,s.tap=function(e,t){return t(e),e},s.template=function(e,t,n){n||(n={});var e=e+"",o,u;o=n.escape;var a=n.evaluate,f=n.interpolate,h=s.templateSettings,p=n=n.variable||h.variable r&&t==r?St():(e=+e||0,t==r&&(t=e,e=0),e+mt(St()*((+t||0)-e+1)))},s.range=function(e,t,n){e=+e||0,n=+n||1,t==r&&(t=e,e=0);for(var i=-1,t=wt(0,Math.ceil((t-e)/n)),s=Array(t);++i<t;)s[i]=e,e+=n;return s},s.reduce=Dn,s.reduceRight=T,s.reject=Pn,s.rest=A,s.result=function(e,t){if(!e)return r;var n=e[t];return w(n)?e[t]():n},s.shuffle=function(e){if(!e)return[];for(var t,n=-1,r=e.length,i=Array(r);++n<r;)t=mt(St()*(n+1)),i[n]=i[t],i[t]=e[n];return i},s.size=function(e){if(!e)return 0;var t=e.length;return-1<
;o==r&&(o=h.escape),a==r&&(a=h.evaluate||i),f==r&&(f=h.interpolate),o&&(e=e.replace(o,v)),f&&(e=e.replace(f,g)),a!=H&&(H=a,F=RegExp("<e%-([\\s\\S]+?)%>|<e%=([\\s\\S]+?)%>"+(a?"|"+a.source:""),"g")),o=ut.length,e=e.replace(F,m),o=o!=ut.length,e="__p += '"+e.replace(it,c).replace(nt,l)+"';",ut.length=0,p||(n=B||"obj",o?e="with("+n+"){"+e+"}":(n!=B&&(B=n,j=RegExp("(\\(\\s*)"+n+"\\."+n+"\\b","g")),e=e.replace(et,"$&"+n+".").replace(j,"$1__d"))),e=(o?e.replace(Q,""):e).replace(G,"$1").replace(Y,"$1;") t&&t===t>>>0?t:gn(e).length},s.some=Hn,s.sortBy=Bn,s.sortedIndex=O,s.tap=function(e,t){return t(e),e},s.template=function(e,t,n){n||(n={});var e=e+"",o,u;o=n.escape;var a=n.evaluate,f=n.interpolate,h=s.templateSettings,p=n=n.variable||h.variable;o==r&&(o=h.escape),a==r&&(a=h.evaluate||i),f==r&&(f=h.interpolate),o&&(e=e.replace(o,v)),f&&(e=e.replace(f,g)),a!=H&&(H=a,F=RegExp("<e%-([\\s\\S]+?)%>|<e%=([\\s\\S]+?)%>"+(a?"|"+a.source:""),"g")),o=at.length,e=e.replace(F,m),o=o!=at.length,e="__p += '"+e
,e="function("+n+"){"+(p?"":n+"||("+n+"={});")+"var __t,__p='',__e=_.escape"+(o?",__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}":(p?"":",__d="+n+"."+n+"||"+n)+";")+e+"return __p}";try{u=Function("_","return "+e)(s)}catch(d){u=function(){throw d}}return t?u(t):(u.source=e,u)},s.throttle=function(e,t){function n(){a=new Date,u=r,e.apply(o,i)}var i,s,o,u,a=0;return function(){var r=new Date,f=t-(r-a);return i=arguments,o=this,0>=f?(a=r,s=e.apply(o,i)):u||(u=kt(n,f)),s}},s.times= .replace(it,c).replace(nt,l)+"';",at.length=0,p||(n=B||"obj",o?e="with("+n+"){"+e+"}":(n!=B&&(B=n,j=RegExp("(\\(\\s*)"+n+"\\."+n+"\\b","g")),e=e.replace(et,"$&"+n+".").replace(j,"$1__d"))),e=(o?e.replace(Q,""):e).replace(G,"$1").replace(Y,"$1;"),e="function("+n+"){"+(p?"":n+"||("+n+"={});")+"var __t,__p='',__e=_.escape"+(o?",__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}":(p?"":",__d="+n+"."+n+"||"+n)+";")+e+"return __p}";try{u=Function("_","return "+e)(s)}catch(d){throw d
function(e,t,n){var r=-1;if(n)for(;++r<e;)t.call(n,r);else for(;++r<e;)t(r)},s.toArray=function(e){if(!e)return[];if(e.toArray&&w(e.toArray))return e.toArray();var t=e.length;return-1<t&&t===t>>>0?(Dt?pt.call(e)==Nt:"string"==typeof e)?e.split(""):ht.call(e):dn(e)},s.unescape=function(e){return e==r?"":(e+"").replace(K,y)},s.union=function(){for(var e=-1,t=[],n=at.apply(t,arguments),r=n.length;++e<r;)0>k(t,n[e])&&t.push(n[e]);return t},s.uniq=M,s.uniqueId=function(e){var t=X++;return e?e+t:t},s.values= .source=e,d}return t?u(t):(u.source=e,u)},s.throttle=function(e,t){function n(){a=new Date,u=r,s=e.apply(o,i)}var i,s,o,u,a=0;return function(){var r=new Date,f=t-(r-a);return i=arguments,o=this,0>=f?(a=r,s=e.apply(o,i)):u||(u=_t(n,f)),s}},s.times=function(e,t,n){var r=-1;if(n)for(;++r<e;)t.call(n,r);else for(;++r<e;)t(r)},s.toArray=function(e){if(!e)return[];if(e.toArray&&w(e.toArray))return e.toArray();var t=e.length;return-1<t&&t===t>>>0?(Ft?dt.call(e)==Ot:"string"==typeof e)?e.split(""):pt.call
dn,s.where=An,s.without=function(e){var t=[];if(!e)return t;for(var n=-1,r=e.length,i=u(arguments,1,20);++n<r;)i(e[n])||t.push(e[n]);return t},s.wrap=function(e,t){return function(){var n=[e];return arguments.length&&lt.apply(n,arguments),t.apply(this,n)}},s.zip=function(e){if(!e)return[];for(var t=-1,n=L(Tn(arguments,"length")),r=Array(n);++t<n;)r[t]=Tn(arguments,t);return r},s.zipObject=function(e,t){if(!e)return{};var n=-1,r=e.length,i={};for(t||(t=[]);++n<r;)i[e[n]]=t[n];return i},s.all=gn,s. (e):Sn(e)},s.unescape=function(e){return e==r?"":(e+"").replace(K,y)},s.union=function(){for(var e=-1,t=[],n=ft.apply(t,arguments),r=n.length;++e<r;)0>k(t,n[e])&&t.push(n[e]);return t},s.uniq=M,s.uniqueId=function(e){var t=X++;return e?e+t:t},s.values=Sn,s.where=jn,s.without=function(e){var t=[];if(!e)return t;for(var n=-1,r=e.length,i=u(arguments,1,20);++n<r;)i(e[n])||t.push(e[n]);return t},s.wrap=function(e,t){return function(){var n=[e];return arguments.length&&ct.apply(n,arguments),t.apply(this
any=kn,s.collect=xn,s.detect=bn,s.each=wn,s.foldl=Nn,s.foldr=T,s.head=N,s.include=vn,s.inject=Nn,s.methods=fn,s.omit=sn,s.select=yn,s.tail=A,s.take=N,s.unique=M,wn({Date:Et,Number:St,RegExp:Tt,String:Nt},function(e,t){s["is"+t]=function(t){return pt.call(t)==e}}),o.prototype=s.prototype,P(s),o.prototype.chain=function(){return this._chain=n,this},o.prototype.value=function(){return this._wrapped},wn("pop push reverse shift sort splice unshift".split(" "),function(e){var t=q[e];o.prototype[e]=function( ,n)}},s.zip=function(e){if(!e)return[];for(var t=-1,n=L(_n(arguments,"length")),r=Array(n);++t<n;)r[t]=_n(arguments,t);return r},s.all=Nn,s.any=Hn,s.collect=Mn,s.detect=kn,s.drop=A,s.each=Ln,s.foldl=Dn,s.foldr=T,s.head=N,s.include=xn,s.inject=Dn,s.methods=dn,s.select=Cn,s.tail=A,s.take=N,s.unique=M,Ln({Date:Ct,Number:kt,RegExp:At,String:Ot},function(e,t){s["is"+t]=function(t){return dt.call(t)==e}}),o.prototype=s.prototype,P(s),o.prototype.chain=function(){return this.__chain__=n,this},o.prototype
){var e=this._wrapped;return t.apply(e,arguments),At&&e.length===0&&delete e[0],this._chain&&(e=new o(e),e._chain=n),e}}),wn(["concat","join","slice"],function(e){var t=q[e];o.prototype[e]=function(){var e=t.apply(this._wrapped,arguments);return this._chain&&(e=new o(e),e._chain=n),e}}),typeof define=="function"&&typeof define.amd=="object"&&define.amd?(e._=s,define(function(){return s})):I?"object"==typeof module&&module&&module.t==I?(module.t=s)._=s:I._=s:e._=s})(this); .value=function(){return this.__wrapped__},Ln("pop push reverse shift sort splice unshift".split(" "),function(e){var t=q[e];o.prototype[e]=function(){var e=this.__wrapped__;return t.apply(e,arguments),Pt&&e.length===0&&delete e[0],this.__chain__&&(e=new o(e),e.__chain__=n),e}}),Ln(["concat","join","slice"],function(e){var t=q[e];o.prototype[e]=function(){var e=t.apply(this.__wrapped__,arguments);return this.__chain__&&(e=new o(e),e.__chain__=n),e}}),typeof define=="function"&&typeof define.amd=="object"&&
define.amd?(e._=s,define(function(){return s})):I?"object"==typeof module&&module&&module.exports==I?(module.exports=s)._=s:I._=s:e._=s})(this);

View File

@@ -1,6 +1,6 @@
{ {
"name": "lodash", "name": "lodash",
"version": "0.6.1", "version": "0.7.0",
"description": "A drop-in replacement for Underscore.js delivering performance, bug fixes, and additional features.", "description": "A drop-in replacement for Underscore.js delivering performance, bug fixes, and additional features.",
"homepage": "http://lodash.com", "homepage": "http://lodash.com",
"main": "lodash", "main": "lodash",
@@ -43,7 +43,7 @@
"rhino" "rhino"
], ],
"jam": { "jam": {
"main": "lodash.min.js" "main": "./lodash.min.js"
}, },
"scripts": { "scripts": {
"build": "node build", "build": "node build",

View File

@@ -864,6 +864,18 @@
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
suites.push(
Benchmark.Suite('`_.invert`')
.add('Lo-Dash', '\
lodash.invert(object)'
)
.add('Underscore', '\
_.invert(object)'
)
);
/*--------------------------------------------------------------------------*/
suites.push( suites.push(
Benchmark.Suite('`_.invoke` iterating an array') Benchmark.Suite('`_.invoke` iterating an array')
.add('Lo-Dash', '\ .add('Lo-Dash', '\
@@ -1104,6 +1116,18 @@
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
suites.push(
Benchmark.Suite('`_.pairs`')
.add('Lo-Dash', '\
lodash.pairs(object)'
)
.add('Underscore', '\
_.pairs(object)'
)
);
/*--------------------------------------------------------------------------*/
suites.push( suites.push(
Benchmark.Suite('`_.pick`') Benchmark.Suite('`_.pick`')
.add('Lo-Dash', '\ .add('Lo-Dash', '\

View File

@@ -23,13 +23,21 @@
</div> </div>
<script src="../vendor/backbone/test/vendor/json2.js"></script> <script src="../vendor/backbone/test/vendor/json2.js"></script>
<script src="../vendor/backbone/test/vendor/jquery-1.7.1.js"></script> <script src="../vendor/backbone/test/vendor/jquery-1.7.1.js"></script>
<script src="../vendor/backbone/test/vendor/qunit.js"></script>
<script src="../vendor/backbone/test/vendor/jslitmus.js"></script> <script src="../vendor/backbone/test/vendor/jslitmus.js"></script>
<script src="../vendor/platform.js/platform.js"></script>
<script>
// avoid syntax errors for `QUnit.throws` in older Firefoxes
document.write(platform.name == 'Firefox' && /^1\b/.test(platform.version)
? '<script src="../vendor/qunit/qunit/qunit-1.8.0.js"><\/script>'
: '<script src="../vendor/qunit/qunit/qunit.js"><\/script>'
);
</script>
<script src="test-ui.js"></script> <script src="test-ui.js"></script>
<script> <script>
document.write('<script src="../' + QUnit.config.lodashFilename + '.js"><\/script>'); document.write('<script src="../' + QUnit.config.lodashFilename + '.js"><\/script>');
</script> </script>
<script src="../vendor/backbone/backbone.js"></script> <script src="../vendor/backbone/backbone.js"></script>
<script src="../vendor/backbone/test/environment.js"></script>
<script src="../vendor/backbone/test/noconflict.js"></script> <script src="../vendor/backbone/test/noconflict.js"></script>
<script src="../vendor/backbone/test/events.js"></script> <script src="../vendor/backbone/test/events.js"></script>
<script src="../vendor/backbone/test/model.js"></script> <script src="../vendor/backbone/test/model.js"></script>

View File

@@ -32,9 +32,6 @@
Object.keys = Object._keys; Object.keys = Object._keys;
delete Object._keys; delete Object._keys;
// set to test `_.noConflict`
_ = 1;
// load Lo-Dash again to overwrite the existing `_` value // load Lo-Dash again to overwrite the existing `_` value
document.write('<script src="../' + QUnit.config.lodashFilename + '.js"><\/script>'); document.write('<script src="../' + QUnit.config.lodashFilename + '.js"><\/script>');
@@ -58,12 +55,14 @@
} }
}, },
['lodash', 'underscore'], function(lodash, underscore) { ['lodash', 'underscore'], function(lodash, underscore) {
lodashModule = lodash.noConflict(); if (lodash.noConflict) {
lodashModule.moduleName = 'lodash'; lodashModule = lodash.noConflict();
lodashModule.moduleName = 'lodash';
underscoreModule = underscore.noConflict(); }
underscoreModule.moduleName = 'underscore'; if (underscore.noConflict) {
underscoreModule = underscore.noConflict();
underscoreModule.moduleName = 'underscore';
}
require(['test.js']); require(['test.js']);
}); });

View File

@@ -1,9 +1,14 @@
cd "$(dirname "$0")" cd "$(dirname "$0")"
for cmd in rhino ringo narwhal node; do for cmd in rhino ringo narwhal node; do
echo "" echo ""
echo "Testing in $cmd..." echo "Testing in $cmd..."
$cmd test.js $cmd test.js
done done
echo ""
echo "Testing build..."
node test-build.js
echo "" echo ""
echo "Testing in a browser..." echo "Testing in a browser..."
open index.html open index.html

730
test/test-build.js Normal file
View File

@@ -0,0 +1,730 @@
#!/usr/bin/env node
;(function(undefined) {
'use strict';
/** Load modules */
var fs = require('fs'),
path = require('path'),
vm = require('vm'),
build = require('../build.js'),
minify = require('../build/minify'),
_ = require('../lodash.js');
/** The unit testing framework */
var QUnit = global.QUnit = require('../vendor/qunit/qunit/qunit.js');
require('../vendor/qunit-clib/qunit-clib.js');
/** Used to associate aliases with their real names */
var aliasToRealMap = {
'all': 'every',
'any': 'some',
'collect': 'map',
'detect': 'find',
'drop': 'rest',
'each': 'forEach',
'foldl': 'reduce',
'foldr': 'reduceRight',
'head': 'first',
'include': 'contains',
'inject': 'reduce',
'methods': 'functions',
'select': 'filter',
'tail': 'rest',
'take': 'first',
'unique': 'uniq'
};
/** Used to associate real names with their aliases */
var realToAliasMap = {
'contains': ['include'],
'every': ['all'],
'filter': ['select'],
'find': ['detect'],
'first': ['head', 'take'],
'forEach': ['each'],
'functions': ['methods'],
'map': ['collect'],
'reduce': ['foldl', 'inject'],
'reduceRight': ['foldr'],
'rest': ['drop', 'tail'],
'some': ['any'],
'uniq': ['unique']
};
/** List of all Lo-Dash methods */
var allMethods = _.functions(_).filter(function(methodName) {
return !/^_/.test(methodName);
});
/** List of "Arrays" category methods */
var arraysMethods = [
'compact',
'difference',
'drop',
'first',
'flatten',
'head',
'indexOf',
'initial',
'intersection',
'last',
'lastIndexOf',
'max',
'min',
'object',
'range',
'rest',
'shuffle',
'sortedIndex',
'tail',
'take',
'union',
'uniq',
'unique',
'without',
'zip'
];
/** List of "Chaining" category methods */
var chainingMethods = [
'chain',
'mixin',
'tap',
'value'
];
/** List of "Collections" category methods */
var collectionsMethods = [
'all',
'any',
'collect',
'contains',
'countBy',
'detect',
'each',
'every',
'filter',
'find',
'foldl',
'foldr',
'forEach',
'groupBy',
'include',
'inject',
'invoke',
'map',
'pluck',
'reduce',
'reduceRight',
'reject',
'select',
'size',
'some',
'sortBy',
'toArray',
'where'
];
/** List of "Functions" category methods */
var functionsMethods = [
'after',
'bind',
'bindAll',
'compose',
'debounce',
'defer',
'delay',
'memoize',
'once',
'partial',
'throttle',
'wrap'
];
/** List of "Objects" category methods */
var objectsMethods = [
'clone',
'defaults',
'extend',
'forIn',
'forOwn',
'functions',
'has',
'invert',
'isArguments',
'isArray',
'isBoolean',
'isDate',
'isElement',
'isEmpty',
'isEqual',
'isFinite',
'isFunction',
'isNaN',
'isNull',
'isNumber',
'isObject',
'isRegExp',
'isString',
'isUndefined',
'keys',
'methods',
'merge',
'omit',
'pairs',
'pick',
'values'
];
/** List of "Utilities" category methods */
var utilityMethods = [
'escape',
'identity',
'noConflict',
'random',
'result',
'template',
'times',
'unescape',
'uniqueId'
];
/** List of Backbone's Lo-Dash dependencies */
var backboneDependencies = [
'bind',
'bindAll',
'clone',
'contains',
'escape',
'every',
'extend',
'filter',
'find',
'first',
'forEach',
'groupBy',
'has',
'indexOf',
'initial',
'invoke',
'isArray',
'isEmpty',
'isEqual',
'isFunction',
'isObject',
'isRegExp',
'keys',
'last',
'lastIndexOf',
'map',
'max',
'min',
'mixin',
'reduce',
'reduceRight',
'reject',
'rest',
'result',
'shuffle',
'size',
'some',
'sortBy',
'sortedIndex',
'toArray',
'uniqueId',
'without'
];
/** List of methods used by Underscore */
var underscoreMethods = _.without.apply(_, [allMethods].concat([
'countBy',
'forIn',
'forOwn',
'invert',
'merge',
'object',
'omit',
'pairs',
'partial',
'random',
'unescape',
'where'
]));
/*--------------------------------------------------------------------------*/
/**
* Creates a context object to use with `vm.runInContext`.
*
* @private
* @returns {Object} Returns a new context object.
*/
function createContext() {
return vm.createContext({
'clearTimeout': clearTimeout,
'setTimeout': setTimeout
});
}
/**
* Expands a list of method names to include real and alias names.
*
* @private
* @param {Array} methodNames The array of method names to expand.
* @returns {Array} Returns a new array of expanded method names.
*/
function expandMethodNames(methodNames) {
return methodNames.reduce(function(result, methodName) {
var realName = getRealName(methodName);
result.push.apply(result, [realName].concat(getAliases(realName)));
return result;
}, []);
}
/**
* Gets the aliases associated with a given function name.
*
* @private
* @param {String} funcName The name of the function to get aliases for.
* @returns {Array} Returns an array of aliases.
*/
function getAliases(funcName) {
return realToAliasMap[funcName] || [];
}
/**
* Gets the real name, not alias, of a given function name.
*
* @private
* @param {String} funcName The name of the function to resolve.
* @returns {String} Returns the real name.
*/
function getRealName(funcName) {
return aliasToRealMap[funcName] || funcName;
}
/**
* Tests if a given method on the `lodash` object can be called successfully.
*
* @private
* @param {Object} lodash The built Lo-Dash object.
* @param {String} methodName The name of the Lo-Dash method to test.
* @param {String} message The unit test message.
*/
function testMethod(lodash, methodName, message) {
var pass = true,
array = [['a', 1], ['b', 2], ['c', 3]],
object = { 'a': 1, 'b': 2, 'c': 3 },
noop = function() {},
string = 'abc',
func = lodash[methodName];
try {
if (arraysMethods.indexOf(methodName) > -1) {
if (/(?:indexOf|sortedIndex|without)$/i.test(methodName)) {
func(array, string);
} else if (/^(?:difference|intersection|union|uniq|zip)/.test(methodName)) {
func(array, array);
} else if (methodName == 'range') {
func(2, 4);
} else {
func(array);
}
}
else if (chainingMethods.indexOf(methodName) > -1) {
if (methodName == 'chain') {
lodash.chain(array);
lodash(array).chain();
}
else if (methodName == 'mixin') {
lodash.mixin({});
}
else {
lodash(array)[methodName](noop);
}
}
else if (collectionsMethods.indexOf(methodName) > -1) {
if (/^(?:count|group|sort)By$/.test(methodName)) {
func(array, noop);
func(array, string);
func(object, noop);
func(object, string);
}
else if (/^(?:size|toArray)$/.test(methodName)) {
func(array);
func(object);
}
else if (methodName == 'invoke') {
func(array, 'slice');
func(object, 'toFixed');
}
else if (methodName == 'where') {
func(array, object);
func(object, object);
}
else {
func(array, noop, object);
func(object, noop, object);
}
}
else if (functionsMethods.indexOf(methodName) > -1) {
if (methodName == 'after') {
func(1, noop);
} else if (/^(?:bind|partial)$/.test(methodName)) {
func(noop, object, array, string);
} else if (/^(?:compose|memoize|wrap)$/.test(methodName)) {
func(noop, noop);
} else if (/^(?:debounce|throttle)$/.test(methodName)) {
func(noop, 100);
} else if (methodName == 'bindAll') {
func({ 'noop': noop });
} else {
func(noop);
}
}
else if (objectsMethods.indexOf(methodName) > -1) {
if (methodName == 'clone') {
func(object);
func(object, true);
}
else if (/^(?:defaults|extend|merge)$/.test(methodName)) {
func({}, object);
} else if (/^(?:forIn|forOwn)$/.test(methodName)) {
func(object, noop);
} else if (/^(?:omit|pick)$/.test(methodName)) {
func(object, 'b');
} else if (methodName == 'has') {
func(object, string);
} else {
func(object);
}
}
else if (utilityMethods.indexOf(methodName) > -1) {
if (methodName == 'result') {
func(object, 'b');
} else if (methodName == 'times') {
func(2, noop, object);
} else {
func(string, object);
}
}
}
catch(e) {
console.log(e);
pass = false;
}
equal(pass, true, methodName + ': ' + message);
}
/*--------------------------------------------------------------------------*/
QUnit.module('lodash build');
(function() {
var commands = [
'backbone',
'csp',
'legacy',
'mobile',
'strict',
'underscore',
'category=arrays',
'category=chaining',
'category=collections',
'category=functions',
'category=objects',
'category=utilities',
'exclude=union,uniq,zip',
'include=each,filter,map',
'category=collections,functions',
'underscore backbone',
'backbone legacy category=utilities exclude=first,last',
'underscore mobile strict category=functions exports=amd,global include=pick,uniq',
]
.concat(
allMethods.map(function(methodName) {
return 'include=' + methodName;
})
);
commands.forEach(function(command) {
var start = _.after(2, _.once(QUnit.start));
asyncTest('`lodash ' + command +'`', function() {
build(['--silent'].concat(command.split(' ')), function(source, filepath) {
var basename = path.basename(filepath, '.js'),
context = createContext(),
methodNames = [];
try {
vm.runInContext(source, context);
} catch(e) { }
if (/underscore/.test(command)) {
methodNames = underscoreMethods;
}
if (/backbone/.test(command)) {
methodNames = backboneDependencies;
}
if (/include/.test(command)) {
methodNames = methodNames.concat(command.match(/include=(\S*)/)[1].split(/, */));
}
if (/category/.test(command)) {
methodNames = command.match(/category=(\S*)/)[1].split(/, */).reduce(function(result, category) {
switch (category) {
case 'arrays':
return result.concat(arraysMethods);
case 'chaining':
return result.concat(chainingMethods);
case 'collections':
return result.concat(collectionsMethods);
case 'functions':
return result.concat(functionsMethods);
case 'objects':
return result.concat(objectsMethods);
case 'utilities':
return result.concat(utilityMethods);
}
return result;
}, methodNames);
}
if (!methodNames.length) {
methodNames = allMethods;
}
if (/exclude/.test(command)) {
methodNames = _.without.apply(_, [methodNames].concat(
expandMethodNames(command.match(/exclude=(\S*)/)[1].split(/, */))
));
} else {
methodNames = expandMethodNames(methodNames);
}
var lodash = context._ || {};
methodNames = _.unique(methodNames);
methodNames.forEach(function(methodName) {
testMethod(lodash, methodName, basename);
});
start();
});
});
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('strict modifier');
(function() {
var object = Object.create(Object.prototype, {
'a': { 'value': _.identify },
'b': { 'value': null }
});
['non-strict', 'strict'].forEach(function(strictMode, index) {
var start = _.after(2, _.once(QUnit.start));
asyncTest(strictMode + ' should ' + (index ? 'error': 'silently fail') + ' attempting to overwrite read-only properties', function() {
var commands = ['-s', 'include=bindAll,defaults,extend'];
if (index) {
commands.push('strict');
}
build(commands, function(source, filepath) {
var basename = path.basename(filepath, '.js'),
context = createContext(),
pass = !index;
vm.runInContext(source, context);
var lodash = context._;
try {
lodash.bindAll(object);
lodash.extend(object, { 'a': 1 });
lodash.defaults(object, { 'b': 2 });
} catch(e) {
pass = !!index;
}
equal(pass, true, basename);
start();
});
});
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('underscore modifier');
(function() {
var start = _.once(QUnit.start);
asyncTest('should not have deep clone', function() {
build(['-s', 'underscore'], function(source, filepath) {
var array = [{ 'a': 1 }],
basename = path.basename(filepath, '.js'),
context = createContext();
vm.runInContext(source, context);
var lodash = context._;
ok(lodash.clone(array, true)[0] === array[0], basename);
start();
});
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('exports command');
(function() {
var commands = [
'exports=amd',
'exports=commonjs',
'exports=global',
'exports=node',
'exports=none'
];
commands.forEach(function(command, index) {
var start = _.after(2, _.once(QUnit.start));
asyncTest('`lodash ' + command +'`', function() {
build(['-s', command], function(source, filepath) {
var basename = path.basename(filepath, '.js'),
context = createContext(),
pass = false;
switch(index) {
case 0:
context.define = function(fn) {
pass = true;
context._ = fn();
};
context.define.amd = {};
vm.runInContext(source, context);
ok(pass, basename);
break;
case 1:
context.exports = {};
vm.runInContext(source, context);
ok(context._ === undefined, basename);
ok(_.isFunction(context.exports._), basename)
break;
case 2:
vm.runInContext(source, context);
ok(_.isFunction(context._), basename);
break;
case 3:
context.exports = {};
context.module = { 'exports': context.exports };
vm.runInContext(source, context);
ok(context._ === undefined, basename);
ok(_.isFunction(context.module.exports), basename);
break;
case 4:
vm.runInContext(source, context);
ok(context._ === undefined, basename);
}
start();
});
});
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('iife command');
(function() {
var start = _.after(2, _.once(QUnit.start));
asyncTest('`lodash iife=...`', function() {
build(['-s', 'iife=!function(window,undefined){%output%}(this)'], function(source, filepath) {
var basename = path.basename(filepath, '.js'),
context = createContext();
try {
vm.runInContext(source, context);
} catch(e) { }
var lodash = context._ || {};
ok(_.isString(lodash.VERSION), basename);
ok(/!function/.test(source), basename);
start();
});
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('output options');
(function() {
['-o a.js', '--output a.js'].forEach(function(command, index) {
var start = _.once(QUnit.start);
asyncTest('`lodash ' + command +'`', function() {
build(['-s'].concat(command.split(' ')), function(source, filepath) {
equal(filepath, 'a.js', command);
start();
});
});
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('stdout options');
(function() {
['-c', '--stdout'].forEach(function(command, index) {
var descriptor = Object.getOwnPropertyDescriptor(global, 'console'),
start = _.once(QUnit.start);
asyncTest('`lodash ' + command +'`', function() {
build([command, 'exports=', 'include='], function(source, filepath) {
equal(source, '');
equal(arguments.length, 1);
start();
});
});
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('minify underscore');
(function() {
var start = _.once(QUnit.start);
asyncTest('`node minify underscore.js`', function() {
var source = fs.readFileSync(path.join(__dirname, '..', 'vendor', 'underscore', 'underscore.js'), 'utf8');
minify(source, {
'silent': true,
'workingName': 'underscore.min',
'onComplete': function(result) {
var context = createContext();
try {
vm.runInContext(result, context);
} catch(e) { }
var underscore = context._ || {};
ok(_.isString(underscore.VERSION));
ok(result.match(/\n/g).length < source.match(/\n/g).length);
start();
}
});
});
}());
}());

View File

@@ -47,8 +47,8 @@
function init() { function init() {
var toolbar = document.getElementById('qunit-testrunner-toolbar'); var toolbar = document.getElementById('qunit-testrunner-toolbar');
if (toolbar) { if (toolbar) {
toolbar.appendChild(label1); toolbar.appendChild(span1);
toolbar.appendChild(label2); toolbar.appendChild(span2);
dropdown.selectedIndex = (function() { dropdown.selectedIndex = (function() {
switch (build) { switch (build) {
@@ -68,21 +68,24 @@
} }
} }
var label1 = document.createElement('label'); var span1 = document.createElement('span');
label1.innerHTML = span1.innerHTML =
'<input name="norequire" type="checkbox">No RequireJS'; '<input id="qunit-norequire" type="checkbox">' +
'<label for="qunit-norequire">No RequireJS</label>';
var label2 = document.createElement('label'); var span2 = document.createElement('span');
label2.innerHTML = '&nbsp;' + span2.style.cssText = 'float:right';
'<select name="build">' + span2.innerHTML =
'<label for="qunit-build">Build: </label>' +
'<select id="qunit-build">' +
'<option value="dev">Developement</option>' + '<option value="dev">Developement</option>' +
'<option value="prod">Production</option>' + '<option value="prod">Production</option>' +
'<option value="custom">Custom</option>' + '<option value="custom">Custom</option>' +
'<option value="custom-debug">Custom (debug)</option>' + '<option value="custom-debug">Custom (debug)</option>' +
'</select> Build'; '</select>';
var checkbox = label1.firstChild, var checkbox = span1.firstChild,
dropdown = label2.getElementsByTagName('select')[0]; dropdown = span2.lastChild;
init(); init();
}); });

View File

@@ -83,6 +83,23 @@
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
// add object from iframe
(function() {
if (!window.document) {
return;
}
var body = document.body,
iframe = document.createElement('iframe');
iframe.frameBorder = iframe.height = iframe.width = 0;
body.appendChild(iframe);
var idoc = (idoc = iframe.contentDocument || iframe.contentWindow).document || idoc;
idoc.write("<script>parent._._object = { 'a': 1, 'b': 2, 'c': 3 };<\/script>");
idoc.close();
}());
/*--------------------------------------------------------------------------*/
// explicitly call `QUnit.module()` instead of `module()` // explicitly call `QUnit.module()` instead of `module()`
// in case we are in a CLI environment // in case we are in a CLI environment
QUnit.module('lodash'); QUnit.module('lodash');
@@ -191,6 +208,7 @@
'boolean object': Object(false), 'boolean object': Object(false),
'an object': { 'a': 0, 'b': 1, 'c': 3 }, 'an object': { 'a': 0, 'b': 1, 'c': 3 },
'an object with object values': { 'a': /a/, 'b': ['B'], 'c': { 'C': 1 } }, 'an object with object values': { 'a': /a/, 'b': ['B'], 'c': { 'C': 1 } },
'an object from another document': _._object || {},
'null': null, 'null': null,
'a number': 3, 'a number': 3,
'a number object': Object(3), 'a number object': Object(3),
@@ -205,12 +223,8 @@
_.forOwn(objects, function(object, key) { _.forOwn(objects, function(object, key) {
test('should deep clone ' + key + ' correctly', function() { test('should deep clone ' + key + ' correctly', function() {
var clone = _.clone(object, true); var clone = _.clone(object, true);
ok(_.isEqual(object, clone));
if (object == null) {
equal(clone, object);
} else {
deepEqual(clone.valueOf(), object.valueOf());
}
if (_.isObject(object)) { if (_.isObject(object)) {
ok(clone !== object); ok(clone !== object);
} else { } else {
@@ -304,7 +318,17 @@
QUnit.module('lodash.debounce'); QUnit.module('lodash.debounce');
(function() { (function() {
test('subsequent "immediate" debounced calls should return the result of the first call', function() { asyncTest('subsequent debounced calls return the last `func` result', function() {
var debounced = _.debounce(function(value) { return value; }, 100);
debounced('x');
setTimeout(function() {
equal(debounced('y'), 'x');
QUnit.start();
}, 220);
});
test('subsequent "immediate" debounced calls return the last `func` result', function() {
var debounced = _.debounce(function(value) { return value; }, 100, true), var debounced = _.debounce(function(value) { return value; }, 100, true),
result = [debounced('x'), debounced('y')]; result = [debounced('x'), debounced('y')];
@@ -333,65 +357,6 @@
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
QUnit.module('lodash.drop');
(function() {
var object = { 'a': 1, 'b': 2 },
actual = { 'b': 2 };
test('should accept individual property names', function() {
deepEqual(_.drop(object, 'a'), actual);
});
test('should accept an array of property names', function() {
deepEqual(_.drop(object, ['a', 'c']), actual);
});
test('should accept mixes of individual and arrays of property names', function() {
deepEqual(_.drop(object, ['a'], 'c'), actual);
});
test('should iterate over inherited properties', function() {
function Foo() {}
Foo.prototype = object;
deepEqual(_.drop(new Foo, 'a'), actual);
});
test('should work with a `callback` argument', function() {
var actual = _.drop(object, function(value) {
return value == 1;
});
deepEqual(actual, { 'b': 2 });
});
test('should pass the correct `callback` arguments', function() {
var args,
lastKey = _.keys(object).pop();
var expected = lastKey == 'b'
? [1, 'a', object]
: [2, 'b', object];
_.drop(object, function() {
args || (args = slice.call(arguments));
});
deepEqual(args, expected);
});
test('should correct set the `this` binding', function() {
var actual = _.drop(object, function(value) {
return value == this.a;
}, { 'a': 1 });
deepEqual(actual, { 'b': 2 });
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('lodash.escape'); QUnit.module('lodash.escape');
(function() { (function() {
@@ -806,26 +771,28 @@
}); });
test('should return `true` for like-objects from different documents', function() { test('should return `true` for like-objects from different documents', function() {
if (window.document) {
var body = document.body,
iframe = document.createElement('iframe'),
object = { 'a': 1, 'b': 2, 'c': 3 };
body.appendChild(iframe);
var idoc = (idoc = iframe.contentDocument || iframe.contentWindow).document || idoc;
idoc.write("<script>parent._._object = { 'a': 1, 'b': 2, 'c': 3 };<\/script>");
idoc.close();
}
// ensure `_._object` is assigned (unassigned in Opera 10.00) // ensure `_._object` is assigned (unassigned in Opera 10.00)
if (_._object) { if (_._object) {
var object = { 'a': 1, 'b': 2, 'c': 3 };
equal(_.isEqual(object, _._object), true); equal(_.isEqual(object, _._object), true);
body.removeChild(iframe);
delete _._object;
} }
else { else {
skipTest(); skipTest();
} }
}); });
test('should return `false` when comparing values with circular references to unlike values', function() {
var array1 = ['a', null, 'c'],
array2 = ['a', [], 'c'],
object1 = { 'a': 1, 'b': null, 'c': 3 },
object2 = { 'a': 1, 'b': {}, 'c': 3 };
array1[1] = array1;
equal(_.isEqual(array1, array2), false);
object1.b = object1;
equal(_.isEqual(object1, object2), false);
});
}()); }());
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
@@ -1048,10 +1015,74 @@
var actual = _.merge(object, source); var actual = _.merge(object, source);
equal(_.isArguments(actual.args), false); equal(_.isArguments(actual.args), false);
}); });
test('should work with four arguments', function() {
var expected = { 'a': 4 };
deepEqual(_.merge({ 'a': 1 }, { 'a': 2 }, { 'a': 3 }, expected), expected);
});
}(1, 2, 3)); }(1, 2, 3));
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
QUnit.module('lodash.omit');
(function() {
var object = { 'a': 1, 'b': 2 },
actual = { 'b': 2 };
test('should accept individual property names', function() {
deepEqual(_.omit(object, 'a'), actual);
});
test('should accept an array of property names', function() {
deepEqual(_.omit(object, ['a', 'c']), actual);
});
test('should accept mixes of individual and arrays of property names', function() {
deepEqual(_.omit(object, ['a'], 'c'), actual);
});
test('should iterate over inherited properties', function() {
function Foo() {}
Foo.prototype = object;
deepEqual(_.omit(new Foo, 'a'), actual);
});
test('should work with a `callback` argument', function() {
var actual = _.omit(object, function(value) {
return value == 1;
});
deepEqual(actual, { 'b': 2 });
});
test('should pass the correct `callback` arguments', function() {
var args,
lastKey = _.keys(object).pop();
var expected = lastKey == 'b'
? [1, 'a', object]
: [2, 'b', object];
_.omit(object, function() {
args || (args = slice.call(arguments));
});
deepEqual(args, expected);
});
test('should correct set the `this` binding', function() {
var actual = _.omit(object, function(value) {
return value == this.a;
}, { 'a': 1 });
deepEqual(actual, { 'b': 2 });
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('lodash.partial'); QUnit.module('lodash.partial');
(function() { (function() {
@@ -1151,6 +1182,27 @@
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
QUnit.module('lodash.random');
(function() {
test('should work like `Math.random` if no arguments are passed', function() {
var actual = _.random();
ok(actual >= 0 && actual < 1);
});
test('supports not passing a `max` argument', function() {
var actual = _.random(5),
start = new Date;
while ((new Date - start) < 50 && actual == 5) {
actual = _.random(5);
}
ok(actual != 5);
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('lodash.range'); QUnit.module('lodash.range');
(function() { (function() {
@@ -1273,10 +1325,6 @@
}) })
}); });
test('should work with an object that has a `length` property', function() {
equal(_.size({ 'length': 3 }), 1);
});
test('should work with jQuery/MooTools DOM query collections', function() { test('should work with jQuery/MooTools DOM query collections', function() {
function Foo(elements) { Array.prototype.push.apply(this, elements); } function Foo(elements) { Array.prototype.push.apply(this, elements); }
Foo.prototype = { 'length': 0, 'splice': Array.prototype.splice }; Foo.prototype = { 'length': 0, 'splice': Array.prototype.splice };
@@ -1310,7 +1358,10 @@
new Pair(1, 5), new Pair(1, 6), new Pair(1, 5), new Pair(1, 6),
new Pair(2, 1), new Pair(2, 2), new Pair(2, 1), new Pair(2, 2),
new Pair(2, 3), new Pair(2, 4), new Pair(2, 3), new Pair(2, 4),
new Pair(2, 5), new Pair(2, 6) new Pair(2, 5), new Pair(2, 6),
new Pair(undefined, 1), new Pair(undefined, 2),
new Pair(undefined, 3), new Pair(undefined, 4),
new Pair(undefined, 5), new Pair(undefined, 6)
]; ];
var actual = _.sortBy(collection, function(pair) { var actual = _.sortBy(collection, function(pair) {
@@ -1380,13 +1431,13 @@
deepEqual(options, {}); deepEqual(options, {});
}); });
test('should be debuggable if compiled with errors', function() { test('should provide the template source when a SyntaxError occurs', function() {
var source = _.template('<% if x %>').source; try {
ok(source.indexOf('__p') > -1); _.template('<% if x %>');
}); } catch(e) {
var source = e.source;
test('should raise an error if a template, compiled with errors, is executed', function() { }
raises(_.template('<% if x %>')); ok((source + '').indexOf('__p') > -1);
}); });
test('should work with complex "interpolate" delimiters', function() { test('should work with complex "interpolate" delimiters', function() {
@@ -1456,6 +1507,16 @@
} }
ok(pass); ok(pass);
}); });
test('should tokenize delimiters correctly', function() {
var compiled = _.template('<span class="icon-<%= type %>2"></span>');
equal(compiled({ 'type': 1 }), '<span class="icon-12"></span>');
});
test('should work with "interpolate" delimiters containing ternary operators', function() {
var compiled = _.template('<%= value ? value : "b" %>');
equal(compiled({ 'value': 'a' }), 'a');
});
}()); }());
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
@@ -1624,16 +1685,6 @@
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
QUnit.module('lodash.zipObject');
(function() {
test('supports not passing a `values` argument', function() {
deepEqual(_.zipObject(['a', 'b', 'c']), { 'a': undefined, 'b': undefined, 'c': undefined });
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('lodash(...).shift'); QUnit.module('lodash(...).shift');
(function() { (function() {

View File

@@ -20,8 +20,15 @@
</div> </div>
<script src="../vendor/backbone/test/vendor/json2.js"></script> <script src="../vendor/backbone/test/vendor/json2.js"></script>
<script src="../vendor/underscore/test/vendor/jquery.js"></script> <script src="../vendor/underscore/test/vendor/jquery.js"></script>
<script src="../vendor/underscore/test/vendor/qunit.js"></script>
<script src="../vendor/underscore/test/vendor/jslitmus.js"></script> <script src="../vendor/underscore/test/vendor/jslitmus.js"></script>
<script src="../vendor/platform.js/platform.js"></script>
<script>
// avoid syntax errors for `QUnit.throws` in older Firefoxes
document.write(platform.name == 'Firefox' && /^1\b/.test(platform.version)
? '<script src="../vendor/qunit/qunit/qunit-1.8.0.js"><\/script>'
: '<script src="../vendor/qunit/qunit/qunit.js"><\/script>'
);
</script>
<script src="test-ui.js"></script> <script src="test-ui.js"></script>
<script> <script>
document.write('<script src="../' + QUnit.config.lodashFilename + '.js"><\/script>'); document.write('<script src="../' + QUnit.config.lodashFilename + '.js"><\/script>');

View File

@@ -18,8 +18,10 @@
// restored later on, if `noConflict` is used. // restored later on, if `noConflict` is used.
var previousBackbone = root.Backbone; var previousBackbone = root.Backbone;
// Create a local reference to splice. // Create a local reference to array methods.
var splice = Array.prototype.splice; var ArrayProto = Array.prototype;
var slice = ArrayProto.slice;
var splice = ArrayProto.splice;
// The top-level namespace. All public Backbone classes and modules will // The top-level namespace. All public Backbone classes and modules will
// be attached to this. Exported for both CommonJS and the browser. // be attached to this. Exported for both CommonJS and the browser.
@@ -183,7 +185,7 @@
attributes || (attributes = {}); attributes || (attributes = {});
if (options && options.collection) this.collection = options.collection; if (options && options.collection) this.collection = options.collection;
if (options && options.parse) attributes = this.parse(attributes); if (options && options.parse) attributes = this.parse(attributes);
if (defaults = getValue(this, 'defaults')) { if (defaults = _.result(this, 'defaults')) {
attributes = _.extend({}, defaults, attributes); attributes = _.extend({}, defaults, attributes);
} }
this.attributes = {}; this.attributes = {};
@@ -336,9 +338,7 @@
options.success = function(resp, status, xhr) { options.success = function(resp, status, xhr) {
if (!model.set(model.parse(resp, xhr), options)) return false; if (!model.set(model.parse(resp, xhr), options)) return false;
if (success) success(model, resp, options); if (success) success(model, resp, options);
model.trigger('sync', model, resp, options);
}; };
options.error = Backbone.wrapError(options.error, model, options);
return this.sync('read', this, options); return this.sync('read', this, options);
}, },
@@ -383,11 +383,9 @@
if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs); if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
if (!model.set(serverAttrs, options)) return false; if (!model.set(serverAttrs, options)) return false;
if (success) success(model, resp, options); if (success) success(model, resp, options);
model.trigger('sync', model, resp, options);
}; };
// Finish configuring and sending the Ajax request. // Finish configuring and sending the Ajax request.
options.error = Backbone.wrapError(options.error, model, options);
var xhr = this.sync(this.isNew() ? 'create' : 'update', this, options); var xhr = this.sync(this.isNew() ? 'create' : 'update', this, options);
// When using `wait`, reset attributes to original values unless // When using `wait`, reset attributes to original values unless
@@ -415,7 +413,6 @@
options.success = function(resp) { options.success = function(resp) {
if (options.wait || model.isNew()) destroy(); if (options.wait || model.isNew()) destroy();
if (success) success(model, resp, options); if (success) success(model, resp, options);
if (!model.isNew()) model.trigger('sync', model, resp, options);
}; };
if (this.isNew()) { if (this.isNew()) {
@@ -423,7 +420,6 @@
return false; return false;
} }
options.error = Backbone.wrapError(options.error, model, options);
var xhr = this.sync('delete', this, options); var xhr = this.sync('delete', this, options);
if (!options.wait) destroy(); if (!options.wait) destroy();
return xhr; return xhr;
@@ -433,7 +429,7 @@
// using Backbone's restful methods, override this to change the endpoint // using Backbone's restful methods, override this to change the endpoint
// that will be called. // that will be called.
url: function() { url: function() {
var base = getValue(this, 'urlRoot') || getValue(this.collection, 'url') || urlError(); var base = _.result(this, 'urlRoot') || _.result(this.collection, 'url') || urlError();
if (this.isNew()) return base; if (this.isNew()) return base;
return base + (base.charAt(base.length - 1) === '/' ? '' : '/') + encodeURIComponent(this.id); return base + (base.charAt(base.length - 1) === '/' ? '' : '/') + encodeURIComponent(this.id);
}, },
@@ -527,8 +523,8 @@
// Check if the model is currently in a valid state. It's only possible to // Check if the model is currently in a valid state. It's only possible to
// get into an *invalid* state if you're using silent changes. // get into an *invalid* state if you're using silent changes.
isValid: function() { isValid: function(options) {
return !this.validate || !this.validate(this.attributes); return !this.validate || !this.validate(this.attributes, options);
}, },
// Run validation against the next complete set of model attributes, // Run validation against the next complete set of model attributes,
@@ -539,11 +535,8 @@
attrs = _.extend({}, this.attributes, attrs); attrs = _.extend({}, this.attributes, attrs);
var error = this.validate(attrs, options); var error = this.validate(attrs, options);
if (!error) return true; if (!error) return true;
if (options && options.error) { if (options && options.error) options.error(this, error, options);
options.error(this, error, options); this.trigger('error', this, error, options);
} else {
this.trigger('error', this, error, options);
}
return false; return false;
} }
@@ -781,9 +774,7 @@
options.success = function(resp, status, xhr) { options.success = function(resp, status, xhr) {
collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options); collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
if (success) success(collection, resp, options); if (success) success(collection, resp, options);
collection.trigger('sync', collection, resp, options);
}; };
options.error = Backbone.wrapError(options.error, collection, options);
return this.sync('read', this, options); return this.sync('read', this, options);
}, },
@@ -877,7 +868,9 @@
// Mix in each Underscore method as a proxy to `Collection#models`. // Mix in each Underscore method as a proxy to `Collection#models`.
_.each(methods, function(method) { _.each(methods, function(method) {
Collection.prototype[method] = function() { Collection.prototype[method] = function() {
return _[method].apply(_, [this.models].concat(_.toArray(arguments))); var args = slice.call(arguments);
args.unshift(this.models);
return _[method].apply(_, args);
}; };
}); });
@@ -913,7 +906,6 @@
// }); // });
// //
route: function(route, name, callback) { route: function(route, name, callback) {
Backbone.history || (Backbone.history = new History);
if (!_.isRegExp(route)) route = this._routeToRegExp(route); if (!_.isRegExp(route)) route = this._routeToRegExp(route);
if (!callback) callback = this[name]; if (!callback) callback = this[name];
Backbone.history.route(route, _.bind(function(fragment) { Backbone.history.route(route, _.bind(function(fragment) {
@@ -973,9 +965,12 @@
this.history = options && options.history || root.history; this.history = options && options.history || root.history;
}; };
// Cached regex for cleaning leading hashes and slashes . // Cached regex for cleaning leading hashes and slashes.
var routeStripper = /^[#\/]/; var routeStripper = /^[#\/]/;
// Cached regex for stripping leading and trailing slashes.
var rootStripper = /^\/+|\/+$/g;
// Cached regex for detecting MSIE. // Cached regex for detecting MSIE.
var isExplorer = /msie [\w.]+/; var isExplorer = /msie [\w.]+/;
@@ -1005,7 +1000,7 @@
if (fragment == null) { if (fragment == null) {
if (this._hasPushState || !this._wantsHashChange || forcePushState) { if (this._hasPushState || !this._wantsHashChange || forcePushState) {
fragment = this.location.pathname; fragment = this.location.pathname;
var root = this.options.root.replace(trailingSlash, ''); var root = this.root.replace(trailingSlash, '');
if (!fragment.indexOf(root)) fragment = fragment.substr(root.length); if (!fragment.indexOf(root)) fragment = fragment.substr(root.length);
} else { } else {
fragment = this.getHash(); fragment = this.getHash();
@@ -1023,6 +1018,7 @@
// Figure out the initial configuration. Do we need an iframe? // Figure out the initial configuration. Do we need an iframe?
// Is pushState desired ... is it available? // Is pushState desired ... is it available?
this.options = _.extend({}, {root: '/'}, this.options, options); this.options = _.extend({}, {root: '/'}, this.options, options);
this.root = this.options.root;
this._wantsHashChange = this.options.hashChange !== false; this._wantsHashChange = this.options.hashChange !== false;
this._wantsPushState = !!this.options.pushState; this._wantsPushState = !!this.options.pushState;
this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState); this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState);
@@ -1030,8 +1026,8 @@
var docMode = document.documentMode; var docMode = document.documentMode;
var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7)); var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
// Normalize root to always include trailing slash // Normalize root to always include a leading and trailing slash.
if (!trailingSlash.test(this.options.root)) this.options.root += '/'; this.root = ('/' + this.root + '/').replace(rootStripper, '/');
if (oldIE && this._wantsHashChange) { if (oldIE && this._wantsHashChange) {
this.iframe = Backbone.$('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow; this.iframe = Backbone.$('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
@@ -1052,13 +1048,13 @@
// opened by a non-pushState browser. // opened by a non-pushState browser.
this.fragment = fragment; this.fragment = fragment;
var loc = this.location; var loc = this.location;
var atRoot = (loc.pathname.replace(/[^/]$/, '$&/') === this.options.root) && !loc.search; var atRoot = (loc.pathname.replace(/[^/]$/, '$&/') === this.root) && !loc.search;
// If we've started off with a route from a `pushState`-enabled browser, // If we've started off with a route from a `pushState`-enabled browser,
// but we're currently in a browser that doesn't support it... // but we're currently in a browser that doesn't support it...
if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) { if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) {
this.fragment = this.getFragment(null, true); this.fragment = this.getFragment(null, true);
this.location.replace(this.options.root + this.location.search + '#' + this.fragment); this.location.replace(this.root + this.location.search + '#' + this.fragment);
// Return immediately as browser will do redirect to new url // Return immediately as browser will do redirect to new url
return true; return true;
@@ -1066,7 +1062,7 @@
// in a browser where it could be `pushState`-based instead... // in a browser where it could be `pushState`-based instead...
} else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) { } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
this.fragment = this.getHash().replace(routeStripper, ''); this.fragment = this.getHash().replace(routeStripper, '');
this.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment); this.history.replaceState({}, document.title, this.root + this.fragment);
} }
if (!this.options.silent) return this.loadUrl(); if (!this.options.silent) return this.loadUrl();
@@ -1122,10 +1118,10 @@
navigate: function(fragment, options) { navigate: function(fragment, options) {
if (!History.started) return false; if (!History.started) return false;
if (!options || options === true) options = {trigger: options}; if (!options || options === true) options = {trigger: options};
var frag = (fragment || '').replace(routeStripper, ''); fragment = this.getFragment(fragment || '');
if (this.fragment === frag) return; if (this.fragment === fragment) return;
this.fragment = frag; this.fragment = fragment;
var url = (frag.indexOf(this.options.root) !== 0 ? this.options.root : '') + frag; var url = (fragment.indexOf(this.root) !== 0 ? this.root : '') + fragment;
// If pushState is available, we use it to set the fragment as a real URL. // If pushState is available, we use it to set the fragment as a real URL.
if (this._hasPushState) { if (this._hasPushState) {
@@ -1134,13 +1130,13 @@
// If hash changes haven't been explicitly disabled, update the hash // If hash changes haven't been explicitly disabled, update the hash
// fragment to store history. // fragment to store history.
} else if (this._wantsHashChange) { } else if (this._wantsHashChange) {
this._updateHash(this.location, frag, options.replace); this._updateHash(this.location, fragment, options.replace);
if (this.iframe && (frag !== this.getFragment(this.getHash(this.iframe)))) { if (this.iframe && (fragment !== this.getFragment(this.getHash(this.iframe)))) {
// Opening and closing the iframe tricks IE7 and earlier to push a // Opening and closing the iframe tricks IE7 and earlier to push a
// history entry on hash-tag change. When replace is true, we don't // history entry on hash-tag change. When replace is true, we don't
// want this. // want this.
if(!options.replace) this.iframe.document.open().close(); if(!options.replace) this.iframe.document.open().close();
this._updateHash(this.iframe.location, frag, options.replace); this._updateHash(this.iframe.location, fragment, options.replace);
} }
// If you've told us that you explicitly don't want fallback hashchange- // If you've told us that you explicitly don't want fallback hashchange-
@@ -1163,6 +1159,9 @@
}); });
// Create the default Backbone.history.
Backbone.history = new History;
// Backbone.View // Backbone.View
// ------------- // -------------
@@ -1260,7 +1259,7 @@
// This only works for delegate-able events: not `focus`, `blur`, and // This only works for delegate-able events: not `focus`, `blur`, and
// not `change`, `submit`, and `reset` in Internet Explorer. // not `change`, `submit`, and `reset` in Internet Explorer.
delegateEvents: function(events) { delegateEvents: function(events) {
if (!(events || (events = getValue(this, 'events')))) return; if (!(events || (events = _.result(this, 'events')))) return;
this.undelegateEvents(); this.undelegateEvents();
for (var key in events) { for (var key in events) {
var method = events[key]; var method = events[key];
@@ -1303,10 +1302,10 @@
// an element from the `id`, `className` and `tagName` properties. // an element from the `id`, `className` and `tagName` properties.
_ensureElement: function() { _ensureElement: function() {
if (!this.el) { if (!this.el) {
var attrs = _.extend({}, getValue(this, 'attributes')); var attrs = _.extend({}, _.result(this, 'attributes'));
if (this.id) attrs.id = getValue(this, 'id'); if (this.id) attrs.id = _.result(this, 'id');
if (this.className) attrs['class'] = getValue(this, 'className'); if (this.className) attrs['class'] = _.result(this, 'className');
this.setElement(this.make(getValue(this, 'tagName'), attrs), false); this.setElement(this.make(_.result(this, 'tagName'), attrs), false);
} else { } else {
this.setElement(this.el, false); this.setElement(this.el, false);
} }
@@ -1351,7 +1350,7 @@
// Ensure that we have a URL. // Ensure that we have a URL.
if (!options.url) { if (!options.url) {
params.url = getValue(model, 'url') || urlError(); params.url = _.result(model, 'url') || urlError();
} }
// Ensure that we have the appropriate request data. // Ensure that we have the appropriate request data.
@@ -1383,6 +1382,18 @@
params.processData = false; params.processData = false;
} }
var success = options.success;
options.success = function(resp, status, xhr) {
if (success) success(resp, status, xhr);
model.trigger('sync', model, resp, options);
};
var error = options.error;
options.error = function(xhr, status, thrown) {
if (error) error(model, xhr, options);
model.trigger('error', model, xhr, options);
};
// Make the request, allowing the user to override any Ajax options. // Make the request, allowing the user to override any Ajax options.
return Backbone.ajax(_.extend(params, options)); return Backbone.ajax(_.extend(params, options));
}; };
@@ -1392,24 +1403,9 @@
return Backbone.$.ajax.apply(Backbone.$, arguments); return Backbone.$.ajax.apply(Backbone.$, arguments);
}; };
// Wrap an optional error callback with a fallback error event.
Backbone.wrapError = function(onError, originalModel, options) {
return function(model, resp) {
resp = model === originalModel ? resp : model;
if (onError) {
onError(originalModel, resp, options);
} else {
originalModel.trigger('error', originalModel, resp, options);
}
};
};
// Helpers // Helpers
// ------- // -------
// Shared empty constructor function to aid in prototype-chain creation.
var ctor = function(){};
// Helper function to correctly set up the prototype chain, for subclasses. // Helper function to correctly set up the prototype chain, for subclasses.
// Similar to `goog.inherits`, but uses a hash of prototype properties and // Similar to `goog.inherits`, but uses a hash of prototype properties and
// class properties to be extended. // class properties to be extended.
@@ -1420,46 +1416,35 @@
// The constructor function for the new subclass is either defined by you // The constructor function for the new subclass is either defined by you
// (the "constructor" property in your `extend` definition), or defaulted // (the "constructor" property in your `extend` definition), or defaulted
// by us to simply call the parent's constructor. // by us to simply call the parent's constructor.
if (protoProps && protoProps.hasOwnProperty('constructor')) { if (protoProps && _.has(protoProps, 'constructor')) {
child = protoProps.constructor; child = protoProps.constructor;
} else { } else {
child = function(){ parent.apply(this, arguments); }; child = function(){ parent.apply(this, arguments); };
} }
// Inherit class (static) properties from parent.
_.extend(child, parent);
// Set the prototype chain to inherit from `parent`, without calling // Set the prototype chain to inherit from `parent`, without calling
// `parent`'s constructor function. // `parent`'s constructor function.
ctor.prototype = parent.prototype; function Surrogate(){ this.constructor = child; };
child.prototype = new ctor(); Surrogate.prototype = parent.prototype;
child.prototype = new Surrogate;
// Add prototype properties (instance properties) to the subclass, // Add prototype properties (instance properties) to the subclass,
// if supplied. // if supplied.
if (protoProps) _.extend(child.prototype, protoProps); if (protoProps) _.extend(child.prototype, protoProps);
// Add static properties to the constructor function, if supplied. // Add static properties to the constructor function, if supplied.
if (staticProps) _.extend(child, staticProps); _.extend(child, parent, staticProps);
// Correctly set child's `prototype.constructor`. // Set a convenience property in case the parent's prototype is needed
child.prototype.constructor = child; // later.
// Set a convenience property in case the parent's prototype is needed later.
child.__super__ = parent.prototype; child.__super__ = parent.prototype;
return child; return child;
}; };
// Set up inheritance for the model, collection, and view. // Set up inheritance for the model, collection, router, and view.
Model.extend = Collection.extend = Router.extend = View.extend = extend; Model.extend = Collection.extend = Router.extend = View.extend = extend;
// Helper function to get a value from a Backbone object as a property
// or as a function.
var getValue = function(object, prop) {
if (!(object && object[prop])) return null;
return _.isFunction(object[prop]) ? object[prop]() : object[prop];
};
// Throw an error when a URL is needed, and none is supplied. // Throw an error when a URL is needed, and none is supplied.
var urlError = function() { var urlError = function() {
throw new Error('A "url" property or function must be specified'); throw new Error('A "url" property or function must be specified');

View File

@@ -1,13 +1,12 @@
$(document).ready(function() { $(document).ready(function() {
var lastRequest = null;
var sync = Backbone.sync;
var a, b, c, d, e, col, otherCol; var a, b, c, d, e, col, otherCol;
module("Backbone.Collection", { module("Backbone.Collection", _.extend(new Environment, {
setup: function() { setup: function() {
Environment.prototype.setup.apply(this, arguments);
a = new Backbone.Model({id: 3, label: 'a'}); a = new Backbone.Model({id: 3, label: 'a'});
b = new Backbone.Model({id: 2, label: 'b'}); b = new Backbone.Model({id: 2, label: 'b'});
c = new Backbone.Model({id: 1, label: 'c'}); c = new Backbone.Model({id: 1, label: 'c'});
@@ -15,23 +14,11 @@ $(document).ready(function() {
e = null; e = null;
col = new Backbone.Collection([a,b,c,d]); col = new Backbone.Collection([a,b,c,d]);
otherCol = new Backbone.Collection(); otherCol = new Backbone.Collection();
Backbone.sync = function(method, model, options) {
lastRequest = {
method: method,
model: model,
options: options
};
};
},
teardown: function() {
Backbone.sync = sync;
} }
}); }));
test("Collection: new and sort", 7, function() { test("new and sort", 7, function() {
equal(col.first(), a, "a should be first"); equal(col.first(), a, "a should be first");
equal(col.last(), d, "d should be last"); equal(col.last(), d, "d should be last");
col.comparator = function(a, b) { col.comparator = function(a, b) {
@@ -47,36 +34,28 @@ $(document).ready(function() {
equal(col.length, 4); equal(col.length, 4);
}); });
test("Collection: new and parse", 3, function() { test("new and parse", 3, function() {
var MyCol = Backbone.Collection.extend({ var Collection = Backbone.Collection.extend({
// only save the models that have an even value.
parse : function(data) { parse : function(data) {
var onlyEven = []; return _.filter(data, function(datum) {
_.each(data, function(datum) { return datum.a % 2 === 0;
if (datum.a % 2 === 0) {
onlyEven.push(datum);
}
}); });
return onlyEven;
} }
}); });
anotherCol = new MyCol([ var models = [{a: 1}, {a: 2}, {a: 3}, {a: 4}];
{ a : 1 },{ a : 2 },{ a : 3 },{ a : 4 } var collection = new Collection(models, {parse: true});
], { parse : true }); strictEqual(collection.length, 2);
strictEqual(collection.first().get('a'), 2);
equal(anotherCol.length, 2); strictEqual(collection.last().get('a'), 4);
equal(anotherCol.first().get('a'), 2)
equal(anotherCol.last().get('a'), 4);
}); });
test("Collection: get, getByCid", 3, function() { test("get, getByCid", 3, function() {
equal(col.get(0), d); equal(col.get(0), d);
equal(col.get(2), b); equal(col.get(2), b);
equal(col.getByCid(col.first().cid), col.first()); equal(col.getByCid(col.first().cid), col.first());
}); });
test("Collection: get with non-default ids", 2, function() { test("get with non-default ids", 2, function() {
var col = new Backbone.Collection(); var col = new Backbone.Collection();
var MongoModel = Backbone.Model.extend({ var MongoModel = Backbone.Model.extend({
idAttribute: '_id' idAttribute: '_id'
@@ -88,7 +67,7 @@ $(document).ready(function() {
equal(col.get(101), model); equal(col.get(101), model);
}); });
test("Collection: update index when id changes", 3, function() { test("update index when id changes", 3, function() {
var col = new Backbone.Collection(); var col = new Backbone.Collection();
col.add([ col.add([
{id : 0, name : 'one'}, {id : 0, name : 'one'},
@@ -101,15 +80,15 @@ $(document).ready(function() {
equal(col.get(101).get('name'), 'one'); equal(col.get(101).get('name'), 'one');
}); });
test("Collection: at", 1, function() { test("at", 1, function() {
equal(col.at(2), c); equal(col.at(2), c);
}); });
test("Collection: pluck", 1, function() { test("pluck", 1, function() {
equal(col.pluck('label').join(' '), 'a b c d'); equal(col.pluck('label').join(' '), 'a b c d');
}); });
test("Collection: add", 11, function() { test("add", 11, function() {
var added, opts, secondAdded; var added, opts, secondAdded;
added = opts = secondAdded = null; added = opts = secondAdded = null;
e = new Backbone.Model({id: 10, label : 'e'}); e = new Backbone.Model({id: 10, label : 'e'});
@@ -141,7 +120,7 @@ $(document).ready(function() {
equal(atCol.last(), h); equal(atCol.last(), h);
}); });
test("Collection: add multiple models", 6, function() { test("add multiple models", 6, function() {
var col = new Backbone.Collection([{at: 0}, {at: 1}, {at: 9}]); var col = new Backbone.Collection([{at: 0}, {at: 1}, {at: 9}]);
col.add([{at: 2}, {at: 3}, {at: 4}, {at: 5}, {at: 6}, {at: 7}, {at: 8}], {at: 2}); col.add([{at: 2}, {at: 3}, {at: 4}, {at: 5}, {at: 6}, {at: 7}, {at: 8}], {at: 2});
for (var i = 0; i <= 5; i++) { for (var i = 0; i <= 5; i++) {
@@ -149,7 +128,7 @@ $(document).ready(function() {
} }
}); });
test("Collection: add; at should have preference over comparator", 1, function() { test("add; at should have preference over comparator", 1, function() {
var Col = Backbone.Collection.extend({ var Col = Backbone.Collection.extend({
comparator: function(a,b) { comparator: function(a,b) {
return a.id > b.id ? -1 : 1; return a.id > b.id ? -1 : 1;
@@ -162,19 +141,19 @@ $(document).ready(function() {
equal(col.pluck('id').join(' '), '3 1 2'); equal(col.pluck('id').join(' '), '3 1 2');
}); });
test("Collection: can't add model to collection twice", function() { test("can't add model to collection twice", function() {
var col = new Backbone.Collection([{id: 1}, {id: 2}, {id: 1}, {id: 2}, {id: 3}]); var col = new Backbone.Collection([{id: 1}, {id: 2}, {id: 1}, {id: 2}, {id: 3}]);
equal(col.pluck('id').join(' '), '1 2 3'); equal(col.pluck('id').join(' '), '1 2 3');
}); });
test("Collection: can't add different model with same id to collection twice", 1, function() { test("can't add different model with same id to collection twice", 1, function() {
var col = new Backbone.Collection; var col = new Backbone.Collection;
col.unshift({id: 101}); col.unshift({id: 101});
col.add({id: 101}); col.add({id: 101});
equal(col.length, 1); equal(col.length, 1);
}); });
test("Collection: merge in duplicate models with {merge: true}", 3, function() { test("merge in duplicate models with {merge: true}", 3, function() {
var col = new Backbone.Collection; var col = new Backbone.Collection;
col.add([{id: 1, name: 'Moe'}, {id: 2, name: 'Curly'}, {id: 3, name: 'Larry'}]); col.add([{id: 1, name: 'Moe'}, {id: 2, name: 'Curly'}, {id: 3, name: 'Larry'}]);
col.add({id: 1, name: 'Moses'}); col.add({id: 1, name: 'Moses'});
@@ -185,7 +164,7 @@ $(document).ready(function() {
equal(col.first().get('name'), 'Tim'); equal(col.first().get('name'), 'Tim');
}); });
test("Collection: add model to multiple collections", 10, function() { test("add model to multiple collections", 10, function() {
var counter = 0; var counter = 0;
var e = new Backbone.Model({id: 10, label : 'e'}); var e = new Backbone.Model({id: 10, label : 'e'});
e.on('add', function(model, collection) { e.on('add', function(model, collection) {
@@ -213,7 +192,7 @@ $(document).ready(function() {
equal(e.collection, colE); equal(e.collection, colE);
}); });
test("Collection: add model with parse", 1, function() { test("add model with parse", 1, function() {
var Model = Backbone.Model.extend({ var Model = Backbone.Model.extend({
parse: function(obj) { parse: function(obj) {
obj.value += 1; obj.value += 1;
@@ -227,7 +206,7 @@ $(document).ready(function() {
equal(col.at(0).get('value'), 2); equal(col.at(0).get('value'), 2);
}); });
test("Collection: add model to collection with sort()-style comparator", 3, function() { test("add model to collection with sort()-style comparator", 3, function() {
var col = new Backbone.Collection; var col = new Backbone.Collection;
col.comparator = function(a, b) { col.comparator = function(a, b) {
return a.get('name') < b.get('name') ? -1 : 1; return a.get('name') < b.get('name') ? -1 : 1;
@@ -243,7 +222,7 @@ $(document).ready(function() {
equal(col.indexOf(tom), 2); equal(col.indexOf(tom), 2);
}); });
test("Collection: comparator that depends on `this`", 1, function() { test("comparator that depends on `this`", 1, function() {
var col = new Backbone.Collection; var col = new Backbone.Collection;
col.negative = function(num) { col.negative = function(num) {
return -num; return -num;
@@ -255,7 +234,7 @@ $(document).ready(function() {
equal(col.pluck('id').join(' '), '3 2 1'); equal(col.pluck('id').join(' '), '3 2 1');
}); });
test("Collection: remove", 5, function() { test("remove", 5, function() {
var removed = null; var removed = null;
var otherRemoved = null; var otherRemoved = null;
col.on('remove', function(model, col, options) { col.on('remove', function(model, col, options) {
@@ -272,20 +251,20 @@ $(document).ready(function() {
equal(otherRemoved, null); equal(otherRemoved, null);
}); });
test("Collection: shift and pop", 2, function() { test("shift and pop", 2, function() {
var col = new Backbone.Collection([{a: 'a'}, {b: 'b'}, {c: 'c'}]); var col = new Backbone.Collection([{a: 'a'}, {b: 'b'}, {c: 'c'}]);
equal(col.shift().get('a'), 'a'); equal(col.shift().get('a'), 'a');
equal(col.pop().get('c'), 'c'); equal(col.pop().get('c'), 'c');
}); });
test("Collection: slice", 2, function() { test("slice", 2, function() {
var col = new Backbone.Collection([{a: 'a'}, {b: 'b'}, {c: 'c'}]); var col = new Backbone.Collection([{a: 'a'}, {b: 'b'}, {c: 'c'}]);
var array = col.slice(1, 3); var array = col.slice(1, 3);
equal(array.length, 2); equal(array.length, 2);
equal(array[0].get('b'), 'b'); equal(array[0].get('b'), 'b');
}); });
test("Collection: events are unbound on remove", 3, function() { test("events are unbound on remove", 3, function() {
var counter = 0; var counter = 0;
var dj = new Backbone.Model(); var dj = new Backbone.Model();
var emcees = new Backbone.Collection([dj]); var emcees = new Backbone.Collection([dj]);
@@ -298,7 +277,7 @@ $(document).ready(function() {
equal(counter, 1); equal(counter, 1);
}); });
test("Collection: remove in multiple collections", 7, function() { test("remove in multiple collections", 7, function() {
var modelData = { var modelData = {
id : 5, id : 5,
title : 'Othello' title : 'Othello'
@@ -322,7 +301,7 @@ $(document).ready(function() {
equal(passed, true); equal(passed, true);
}); });
test("Collection: remove same model in multiple collection", 16, function() { test("remove same model in multiple collection", 16, function() {
var counter = 0; var counter = 0;
var e = new Backbone.Model({id: 5, title: 'Othello'}); var e = new Backbone.Model({id: 5, title: 'Othello'});
e.on('remove', function(model, collection) { e.on('remove', function(model, collection) {
@@ -356,7 +335,7 @@ $(document).ready(function() {
equal(counter, 2); equal(counter, 2);
}); });
test("Collection: model destroy removes from all collections", 3, function() { test("model destroy removes from all collections", 3, function() {
var e = new Backbone.Model({id: 5, title: 'Othello'}); var e = new Backbone.Model({id: 5, title: 'Othello'});
e.sync = function(method, model, options) { options.success({}); }; e.sync = function(method, model, options) { options.success({}); };
var colE = new Backbone.Collection([e]); var colE = new Backbone.Collection([e]);
@@ -378,25 +357,29 @@ $(document).ready(function() {
equal(undefined, e.collection); equal(undefined, e.collection);
}); });
test("Collection: fetch", 4, function() { test("fetch", 4, function() {
col.fetch(); var collection = new Backbone.Collection;
equal(lastRequest.method, 'read'); collection.url = '/test';
equal(lastRequest.model, col); collection.fetch();
equal(lastRequest.options.parse, true); equal(this.syncArgs.method, 'read');
equal(this.syncArgs.model, collection);
equal(this.syncArgs.options.parse, true);
col.fetch({parse: false}); collection.fetch({parse: false});
equal(lastRequest.options.parse, false); equal(this.syncArgs.options.parse, false);
}); });
test("Collection: create", 4, function() { test("create", 4, function() {
var model = col.create({label: 'f'}, {wait: true}); var collection = new Backbone.Collection;
equal(lastRequest.method, 'create'); collection.url = '/test';
equal(lastRequest.model, model); var model = collection.create({label: 'f'}, {wait: true});
equal(this.syncArgs.method, 'create');
equal(this.syncArgs.model, model);
equal(model.get('label'), 'f'); equal(model.get('label'), 'f');
equal(model.collection, col); equal(model.collection, collection);
}); });
test("Collection: create enforces validation", 1, function() { test("create enforces validation", 1, function() {
var ValidatingModel = Backbone.Model.extend({ var ValidatingModel = Backbone.Model.extend({
validate: function(attrs) { validate: function(attrs) {
return "fail"; return "fail";
@@ -409,7 +392,7 @@ $(document).ready(function() {
equal(col.create({"foo":"bar"}), false); equal(col.create({"foo":"bar"}), false);
}); });
test("Collection: a failing create runs the error callback", 1, function() { test("a failing create runs the error callback", 1, function() {
var ValidatingModel = Backbone.Model.extend({ var ValidatingModel = Backbone.Model.extend({
validate: function(attrs) { validate: function(attrs) {
return "fail"; return "fail";
@@ -425,7 +408,7 @@ $(document).ready(function() {
equal(flag, true); equal(flag, true);
}); });
test("collection: initialize", 1, function() { test("initialize", 1, function() {
var Collection = Backbone.Collection.extend({ var Collection = Backbone.Collection.extend({
initialize: function() { initialize: function() {
this.one = 1; this.one = 1;
@@ -435,11 +418,11 @@ $(document).ready(function() {
equal(coll.one, 1); equal(coll.one, 1);
}); });
test("Collection: toJSON", 1, function() { test("toJSON", 1, function() {
equal(JSON.stringify(col), '[{"id":3,"label":"a"},{"id":2,"label":"b"},{"id":1,"label":"c"},{"id":0,"label":"d"}]'); equal(JSON.stringify(col), '[{"id":3,"label":"a"},{"id":2,"label":"b"},{"id":1,"label":"c"},{"id":0,"label":"d"}]');
}); });
test("Collection: where", 6, function() { test("where", 6, function() {
var coll = new Backbone.Collection([ var coll = new Backbone.Collection([
{a: 1}, {a: 1},
{a: 1}, {a: 1},
@@ -455,7 +438,7 @@ $(document).ready(function() {
equal(coll.where({a: 1, b: 2}).length, 1); equal(coll.where({a: 1, b: 2}).length, 1);
}); });
test("Collection: Underscore methods", 13, function() { test("Underscore methods", 13, function() {
equal(col.map(function(model){ return model.get('label'); }).join(' '), 'a b c d'); equal(col.map(function(model){ return model.get('label'); }).join(' '), 'a b c d');
equal(col.any(function(model){ return model.id === 100; }), false); equal(col.any(function(model){ return model.id === 100; }), false);
equal(col.any(function(model){ return model.id === 0; }), true); equal(col.any(function(model){ return model.id === 0; }), true);
@@ -475,7 +458,7 @@ $(document).ready(function() {
[4, 0]); [4, 0]);
}); });
test("Collection: reset", 10, function() { test("reset", 10, function() {
var resetCount = 0; var resetCount = 0;
var models = col.models; var models = col.models;
col.on('reset', function() { resetCount += 1; }); col.on('reset', function() { resetCount += 1; });
@@ -494,7 +477,7 @@ $(document).ready(function() {
ok(_.isEqual(col.last().attributes, d.attributes)); ok(_.isEqual(col.last().attributes, d.attributes));
}); });
test("Collection: reset passes caller options", 3, function() { test("reset passes caller options", 3, function() {
var Model = Backbone.Model.extend({ var Model = Backbone.Model.extend({
initialize: function(attrs, options) { initialize: function(attrs, options) {
this.model_parameter = options.model_parameter; this.model_parameter = options.model_parameter;
@@ -508,14 +491,14 @@ $(document).ready(function() {
}); });
}); });
test("Collection: trigger custom events on models", 1, function() { test("trigger custom events on models", 1, function() {
var fired = null; var fired = null;
a.on("custom", function() { fired = true; }); a.on("custom", function() { fired = true; });
a.trigger("custom"); a.trigger("custom");
equal(fired, true); equal(fired, true);
}); });
test("Collection: add does not alter arguments", 2, function(){ test("add does not alter arguments", 2, function(){
var attrs = {}; var attrs = {};
var models = [attrs]; var models = [attrs];
new Backbone.Collection().add(models); new Backbone.Collection().add(models);
@@ -524,16 +507,17 @@ $(document).ready(function() {
}); });
test("#714: access `model.collection` in a brand new model.", 2, function() { test("#714: access `model.collection` in a brand new model.", 2, function() {
var col = new Backbone.Collection; var collection = new Backbone.Collection;
collection.url = '/test';
var Model = Backbone.Model.extend({ var Model = Backbone.Model.extend({
set: function(attrs) { set: function(attrs) {
equal(attrs.prop, 'value'); equal(attrs.prop, 'value');
equal(this.collection, col); equal(this.collection, collection);
return this; return this;
} }
}); });
col.model = Model; collection.model = Model;
col.create({prop: 'value'}); collection.create({prop: 'value'});
}); });
test("#574, remove its own reference to the .models array.", 2, function() { test("#574, remove its own reference to the .models array.", 2, function() {
@@ -565,7 +549,7 @@ $(document).ready(function() {
}); });
}); });
test("Collection: index with comparator", 4, function() { test("index with comparator", 4, function() {
var counter = 0; var counter = 0;
var col = new Backbone.Collection([{id: 2}, {id: 4}], { var col = new Backbone.Collection([{id: 2}, {id: 4}], {
comparator: function(model){ return model.id; } comparator: function(model){ return model.id; }
@@ -582,7 +566,7 @@ $(document).ready(function() {
col.add([{id: 3}, {id: 1}]); col.add([{id: 3}, {id: 1}]);
}); });
test("Collection: throwing during add leaves consistent state", 4, function() { test("throwing during add leaves consistent state", 4, function() {
var col = new Backbone.Collection(); var col = new Backbone.Collection();
col.on('test', function() { ok(false); }); col.on('test', function() { ok(false); });
col.model = Backbone.Model.extend({ col.model = Backbone.Model.extend({
@@ -596,7 +580,7 @@ $(document).ready(function() {
equal(col.length, 0); equal(col.length, 0);
}); });
test("Collection: multiple copies of the same model", 3, function() { test("multiple copies of the same model", 3, function() {
var col = new Backbone.Collection(); var col = new Backbone.Collection();
var model = new Backbone.Model(); var model = new Backbone.Model();
col.add([model, model]); col.add([model, model]);
@@ -629,7 +613,7 @@ $(document).ready(function() {
ok(!collection.get('undefined')); ok(!collection.get('undefined'));
}); });
test("Collection: falsy comparator", 4, function(){ test("falsy comparator", 4, function(){
var Col = Backbone.Collection.extend({ var Col = Backbone.Collection.extend({
comparator: function(model){ return model.id; } comparator: function(model){ return model.id; }
}); });
@@ -659,15 +643,10 @@ $(document).ready(function() {
}); });
test("#1412 - Trigger 'sync' event.", 2, function() { test("#1412 - Trigger 'sync' event.", 2, function() {
var collection = new Backbone.Collection([], { var collection = new Backbone.Collection;
model: Backbone.Model.extend({ collection.url = '/test';
sync: function(method, model, options) {
options.success();
}
})
});
collection.sync = function(method, model, options) { options.success(); };
collection.on('sync', function() { ok(true); }); collection.on('sync', function() { ok(true); });
Backbone.ajax = function(settings){ settings.success(); };
collection.fetch(); collection.fetch();
collection.create({id: 1}); collection.create({id: 1});
}); });

39
vendor/backbone/test/environment.js vendored Normal file
View File

@@ -0,0 +1,39 @@
(function() {
var Environment = this.Environment = function(){};
_.extend(Environment.prototype, {
ajax: Backbone.ajax,
sync: Backbone.sync,
setup: function() {
var env = this;
// Capture ajax settings for comparison.
Backbone.ajax = function(settings) {
env.ajaxSettings = settings;
};
// Capture the arguments to Backbone.sync for comparison.
Backbone.sync = function(method, model, options) {
env.syncArgs = {
method: method,
model: model,
options: options
};
env.sync.apply(this, arguments);
};
},
teardown: function() {
this.syncArgs = null;
this.ajaxSettings = null;
Backbone.sync = this.sync;
Backbone.ajax = this.ajax;
}
});
})();

View File

@@ -2,7 +2,7 @@ $(document).ready(function() {
module("Backbone.Events"); module("Backbone.Events");
test("Events: on and trigger", 2, function() { test("on and trigger", 2, function() {
var obj = { counter: 0 }; var obj = { counter: 0 };
_.extend(obj,Backbone.Events); _.extend(obj,Backbone.Events);
obj.on('event', function() { obj.counter += 1; }); obj.on('event', function() { obj.counter += 1; });
@@ -15,7 +15,7 @@ $(document).ready(function() {
equal(obj.counter, 5, 'counter should be incremented five times.'); equal(obj.counter, 5, 'counter should be incremented five times.');
}); });
test("Events: binding and triggering multiple events", 4, function() { test("binding and triggering multiple events", 4, function() {
var obj = { counter: 0 }; var obj = { counter: 0 };
_.extend(obj,Backbone.Events); _.extend(obj,Backbone.Events);
@@ -35,7 +35,7 @@ $(document).ready(function() {
equal(obj.counter, 5); equal(obj.counter, 5);
}); });
test("Events: trigger all for each event", 3, function() { test("trigger all for each event", 3, function() {
var a, b, obj = { counter: 0 }; var a, b, obj = { counter: 0 };
_.extend(obj, Backbone.Events); _.extend(obj, Backbone.Events);
obj.on('all', function(event) { obj.on('all', function(event) {
@@ -49,7 +49,7 @@ $(document).ready(function() {
equal(obj.counter, 2); equal(obj.counter, 2);
}); });
test("Events: on, then unbind all functions", 1, function() { test("on, then unbind all functions", 1, function() {
var obj = { counter: 0 }; var obj = { counter: 0 };
_.extend(obj,Backbone.Events); _.extend(obj,Backbone.Events);
var callback = function() { obj.counter += 1; }; var callback = function() { obj.counter += 1; };
@@ -60,7 +60,7 @@ $(document).ready(function() {
equal(obj.counter, 1, 'counter should have only been incremented once.'); equal(obj.counter, 1, 'counter should have only been incremented once.');
}); });
test("Events: bind two callbacks, unbind only one", 2, function() { test("bind two callbacks, unbind only one", 2, function() {
var obj = { counterA: 0, counterB: 0 }; var obj = { counterA: 0, counterB: 0 };
_.extend(obj,Backbone.Events); _.extend(obj,Backbone.Events);
var callback = function() { obj.counterA += 1; }; var callback = function() { obj.counterA += 1; };
@@ -73,7 +73,7 @@ $(document).ready(function() {
equal(obj.counterB, 2, 'counterB should have been incremented twice.'); equal(obj.counterB, 2, 'counterB should have been incremented twice.');
}); });
test("Events: unbind a callback in the midst of it firing", 1, function() { test("unbind a callback in the midst of it firing", 1, function() {
var obj = {counter: 0}; var obj = {counter: 0};
_.extend(obj, Backbone.Events); _.extend(obj, Backbone.Events);
var callback = function() { var callback = function() {
@@ -87,7 +87,7 @@ $(document).ready(function() {
equal(obj.counter, 1, 'the callback should have been unbound.'); equal(obj.counter, 1, 'the callback should have been unbound.');
}); });
test("Events: two binds that unbind themeselves", 2, function() { test("two binds that unbind themeselves", 2, function() {
var obj = { counterA: 0, counterB: 0 }; var obj = { counterA: 0, counterB: 0 };
_.extend(obj,Backbone.Events); _.extend(obj,Backbone.Events);
var incrA = function(){ obj.counterA += 1; obj.off('event', incrA); }; var incrA = function(){ obj.counterA += 1; obj.off('event', incrA); };
@@ -101,7 +101,7 @@ $(document).ready(function() {
equal(obj.counterB, 1, 'counterB should have only been incremented once.'); equal(obj.counterB, 1, 'counterB should have only been incremented once.');
}); });
test("Events: bind a callback with a supplied context", 1, function () { test("bind a callback with a supplied context", 1, function () {
var TestClass = function () { var TestClass = function () {
return this; return this;
}; };
@@ -114,7 +114,7 @@ $(document).ready(function() {
obj.trigger('event'); obj.trigger('event');
}); });
test("Events: nested trigger with unbind", 1, function () { test("nested trigger with unbind", 1, function () {
var obj = { counter: 0 }; var obj = { counter: 0 };
_.extend(obj, Backbone.Events); _.extend(obj, Backbone.Events);
var incr1 = function(){ obj.counter += 1; obj.off('event', incr1); obj.trigger('event'); }; var incr1 = function(){ obj.counter += 1; obj.off('event', incr1); obj.trigger('event'); };
@@ -125,7 +125,7 @@ $(document).ready(function() {
equal(obj.counter, 3, 'counter should have been incremented three times'); equal(obj.counter, 3, 'counter should have been incremented three times');
}); });
test("Events: callback list is not altered during trigger", 2, function () { test("callback list is not altered during trigger", 2, function () {
var counter = 0, obj = _.extend({}, Backbone.Events); var counter = 0, obj = _.extend({}, Backbone.Events);
var incr = function(){ counter++; }; var incr = function(){ counter++; };
obj.on('event', function(){ obj.on('event', incr).on('all', incr); }) obj.on('event', function(){ obj.on('event', incr).on('all', incr); })

View File

@@ -1,22 +1,15 @@
$(document).ready(function() { $(document).ready(function() {
// Variable to catch the last request.
var lastRequest = null;
// Variable to catch ajax params.
var ajaxParams = null;
var sync = Backbone.sync;
var ajax = Backbone.ajax;
var urlRoot = null;
var proxy = Backbone.Model.extend(); var proxy = Backbone.Model.extend();
var klass = Backbone.Collection.extend({ var klass = Backbone.Collection.extend({
url : function() { return '/collection'; } url : function() { return '/collection'; }
}); });
var doc, collection; var doc, collection;
module("Backbone.Model", { module("Backbone.Model", _.extend(new Environment, {
setup: function() { setup: function() {
Environment.prototype.setup.apply(this, arguments);
doc = new proxy({ doc = new proxy({
id : '1-the-tempest', id : '1-the-tempest',
title : "The Tempest", title : "The Tempest",
@@ -25,29 +18,11 @@ $(document).ready(function() {
}); });
collection = new klass(); collection = new klass();
collection.add(doc); collection.add(doc);
Backbone.sync = function(method, model, options) {
lastRequest = {
method: method,
model: model,
options: options
};
sync.apply(this, arguments);
};
Backbone.ajax = function(params) { ajaxParams = params; };
urlRoot = Backbone.Model.prototype.urlRoot;
Backbone.Model.prototype.urlRoot = '/';
},
teardown: function() {
Backbone.sync = sync;
Backbone.ajax = ajax;
Backbone.Model.prototype.urlRoot = urlRoot;
} }
}); }));
test("Model: initialize", 3, function() { test("initialize", 3, function() {
var Model = Backbone.Model.extend({ var Model = Backbone.Model.extend({
initialize: function() { initialize: function() {
this.one = 1; this.one = 1;
@@ -59,7 +34,7 @@ $(document).ready(function() {
equal(model.collection, collection); equal(model.collection, collection);
}); });
test("Model: initialize with attributes and options", 1, function() { test("initialize with attributes and options", 1, function() {
var Model = Backbone.Model.extend({ var Model = Backbone.Model.extend({
initialize: function(attributes, options) { initialize: function(attributes, options) {
this.one = options.one; this.one = options.one;
@@ -69,7 +44,7 @@ $(document).ready(function() {
equal(model.one, 1); equal(model.one, 1);
}); });
test("Model: initialize with parsed attributes", 1, function() { test("initialize with parsed attributes", 1, function() {
var Model = Backbone.Model.extend({ var Model = Backbone.Model.extend({
parse: function(obj) { parse: function(obj) {
obj.value += 1; obj.value += 1;
@@ -80,7 +55,7 @@ $(document).ready(function() {
equal(model.get('value'), 2); equal(model.get('value'), 2);
}); });
test("Model: url", 3, function() { test("url", 3, function() {
doc.urlRoot = null; doc.urlRoot = null;
equal(doc.url(), '/collection/1-the-tempest'); equal(doc.url(), '/collection/1-the-tempest');
doc.collection.url = '/collection/'; doc.collection.url = '/collection/';
@@ -90,7 +65,7 @@ $(document).ready(function() {
doc.collection = collection; doc.collection = collection;
}); });
test("Model: url when using urlRoot, and uri encoding", 2, function() { test("url when using urlRoot, and uri encoding", 2, function() {
var Model = Backbone.Model.extend({ var Model = Backbone.Model.extend({
urlRoot: '/collection' urlRoot: '/collection'
}); });
@@ -100,7 +75,7 @@ $(document).ready(function() {
equal(model.url(), '/collection/%2B1%2B'); equal(model.url(), '/collection/%2B1%2B');
}); });
test("Model: url when using urlRoot as a function to determine urlRoot at runtime", 2, function() { test("url when using urlRoot as a function to determine urlRoot at runtime", 2, function() {
var Model = Backbone.Model.extend({ var Model = Backbone.Model.extend({
urlRoot: function() { urlRoot: function() {
return '/nested/' + this.get('parent_id') + '/collection'; return '/nested/' + this.get('parent_id') + '/collection';
@@ -113,7 +88,7 @@ $(document).ready(function() {
equal(model.url(), '/nested/1/collection/2'); equal(model.url(), '/nested/1/collection/2');
}); });
test("Model: clone", 8, function() { test("clone", 8, function() {
var a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3}); var a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3});
var b = a.clone(); var b = a.clone();
equal(a.get('foo'), 1); equal(a.get('foo'), 1);
@@ -127,7 +102,7 @@ $(document).ready(function() {
equal(b.get('foo'), 1, "Changing a parent attribute does not change the clone."); equal(b.get('foo'), 1, "Changing a parent attribute does not change the clone.");
}); });
test("Model: isNew", 6, function() { test("isNew", 6, function() {
var a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3}); var a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3});
ok(a.isNew(), "it should be new"); ok(a.isNew(), "it should be new");
a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3, 'id': -5 }); a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3, 'id': -5 });
@@ -139,12 +114,12 @@ $(document).ready(function() {
ok(!new Backbone.Model({ 'id': -5 }).isNew(), "is false for a negative integer"); ok(!new Backbone.Model({ 'id': -5 }).isNew(), "is false for a negative integer");
}); });
test("Model: get", 2, function() { test("get", 2, function() {
equal(doc.get('title'), 'The Tempest'); equal(doc.get('title'), 'The Tempest');
equal(doc.get('author'), 'Bill Shakespeare'); equal(doc.get('author'), 'Bill Shakespeare');
}); });
test("Model: escape", 5, function() { test("escape", 5, function() {
equal(doc.escape('title'), 'The Tempest'); equal(doc.escape('title'), 'The Tempest');
doc.set({audience: 'Bill & Bob'}); doc.set({audience: 'Bill & Bob'});
equal(doc.escape('audience'), 'Bill &amp; Bob'); equal(doc.escape('audience'), 'Bill &amp; Bob');
@@ -156,7 +131,7 @@ $(document).ready(function() {
equal(doc.escape('audience'), ''); equal(doc.escape('audience'), '');
}); });
test("Model: has", 10, function() { test("has", 10, function() {
var model = new Backbone.Model(); var model = new Backbone.Model();
strictEqual(model.has('name'), false); strictEqual(model.has('name'), false);
@@ -186,7 +161,7 @@ $(document).ready(function() {
strictEqual(model.has('undefined'), false); strictEqual(model.has('undefined'), false);
}); });
test("Model: set and unset", 8, function() { test("set and unset", 8, function() {
var a = new Backbone.Model({id: 'id', foo: 1, bar: 2, baz: 3}); var a = new Backbone.Model({id: 'id', foo: 1, bar: 2, baz: 3});
var changeCount = 0; var changeCount = 0;
a.on("change:foo", function() { changeCount += 1; }); a.on("change:foo", function() { changeCount += 1; });
@@ -209,7 +184,7 @@ $(document).ready(function() {
equal(a.id, undefined, "Unsetting the id should remove the id property."); equal(a.id, undefined, "Unsetting the id should remove the id property.");
}); });
test("Model: multiple unsets", 1, function() { test("multiple unsets", 1, function() {
var i = 0; var i = 0;
var counter = function(){ i++; }; var counter = function(){ i++; };
var model = new Backbone.Model({a: 1}); var model = new Backbone.Model({a: 1});
@@ -220,7 +195,7 @@ $(document).ready(function() {
equal(i, 2, 'Unset does not fire an event for missing attributes.'); equal(i, 2, 'Unset does not fire an event for missing attributes.');
}); });
test("Model: unset and changedAttributes", 2, function() { test("unset and changedAttributes", 2, function() {
var model = new Backbone.Model({a: 1}); var model = new Backbone.Model({a: 1});
model.unset('a', {silent: true}); model.unset('a', {silent: true});
var changedAttributes = model.changedAttributes(); var changedAttributes = model.changedAttributes();
@@ -230,7 +205,7 @@ $(document).ready(function() {
ok('a' in changedAttributes, 'changedAttributes should contain unset properties when running changedAttributes again after an unset.'); ok('a' in changedAttributes, 'changedAttributes should contain unset properties when running changedAttributes again after an unset.');
}); });
test("Model: using a non-default id attribute.", 5, function() { test("using a non-default id attribute.", 5, function() {
var MongoModel = Backbone.Model.extend({idAttribute : '_id'}); var MongoModel = Backbone.Model.extend({idAttribute : '_id'});
var model = new MongoModel({id: 'eye-dee', _id: 25, title: 'Model'}); var model = new MongoModel({id: 'eye-dee', _id: 25, title: 'Model'});
equal(model.get('id'), 'eye-dee'); equal(model.get('id'), 'eye-dee');
@@ -241,13 +216,13 @@ $(document).ready(function() {
equal(model.isNew(), true); equal(model.isNew(), true);
}); });
test("Model: set an empty string", 1, function() { test("set an empty string", 1, function() {
var model = new Backbone.Model({name : "Model"}); var model = new Backbone.Model({name : "Model"});
model.set({name : ''}); model.set({name : ''});
equal(model.get('name'), ''); equal(model.get('name'), '');
}); });
test("Model: clear", 3, function() { test("clear", 3, function() {
var changed; var changed;
var model = new Backbone.Model({id: 1, name : "Model"}); var model = new Backbone.Model({id: 1, name : "Model"});
model.on("change:name", function(){ changed = true; }); model.on("change:name", function(){ changed = true; });
@@ -260,7 +235,7 @@ $(document).ready(function() {
equal(model.get('name'), undefined); equal(model.get('name'), undefined);
}); });
test("Model: defaults", 4, function() { test("defaults", 4, function() {
var Defaulted = Backbone.Model.extend({ var Defaulted = Backbone.Model.extend({
defaults: { defaults: {
"one": 1, "one": 1,
@@ -283,7 +258,7 @@ $(document).ready(function() {
equal(model.get('two'), null); equal(model.get('two'), null);
}); });
test("Model: change, hasChanged, changedAttributes, previous, previousAttributes", 12, function() { test("change, hasChanged, changedAttributes, previous, previousAttributes", 12, function() {
var model = new Backbone.Model({name : "Tim", age : 10}); var model = new Backbone.Model({name : "Tim", age : 10});
equal(model.changedAttributes(), false); equal(model.changedAttributes(), false);
model.on('change', function() { model.on('change', function() {
@@ -304,14 +279,14 @@ $(document).ready(function() {
}); });
test("Model: changedAttributes", 3, function() { test("changedAttributes", 3, function() {
var model = new Backbone.Model({a: 'a', b: 'b'}); var model = new Backbone.Model({a: 'a', b: 'b'});
equal(model.changedAttributes(), false); equal(model.changedAttributes(), false);
equal(model.changedAttributes({a: 'a'}), false); equal(model.changedAttributes({a: 'a'}), false);
equal(model.changedAttributes({a: 'b'}).a, 'b'); equal(model.changedAttributes({a: 'b'}).a, 'b');
}); });
test("Model: change with options", 2, function() { test("change with options", 2, function() {
var value; var value;
var model = new Backbone.Model({name: 'Rob'}); var model = new Backbone.Model({name: 'Rob'});
model.on('change', function(model, options) { model.on('change', function(model, options) {
@@ -324,7 +299,7 @@ $(document).ready(function() {
equal(value, 'Ms. Sue'); equal(value, 'Ms. Sue');
}); });
test("Model: change after initialize", 1, function () { test("change after initialize", 1, function () {
var changed = 0; var changed = 0;
var attrs = {id: 1, label: 'c'}; var attrs = {id: 1, label: 'c'};
var obj = new Backbone.Model(attrs); var obj = new Backbone.Model(attrs);
@@ -333,16 +308,18 @@ $(document).ready(function() {
equal(changed, 0); equal(changed, 0);
}); });
test("Model: save within change event", 1, function () { test("save within change event", 1, function () {
var env = this;
var model = new Backbone.Model({firstName : "Taylor", lastName: "Swift"}); var model = new Backbone.Model({firstName : "Taylor", lastName: "Swift"});
model.url = '/test';
model.on('change', function () { model.on('change', function () {
model.save(); model.save();
ok(_.isEqual(lastRequest.model, model)); ok(_.isEqual(env.syncArgs.model, model));
}); });
model.set({lastName: 'Hicks'}); model.set({lastName: 'Hicks'});
}); });
test("Model: validate after save", 1, function() { test("validate after save", 1, function() {
var lastError, model = new Backbone.Model(); var lastError, model = new Backbone.Model();
model.validate = function(attrs) { model.validate = function(attrs) {
if (attrs.admin) return "Can't change admin status."; if (attrs.admin) return "Can't change admin status.";
@@ -357,7 +334,7 @@ $(document).ready(function() {
equal(lastError, "Can't change admin status."); equal(lastError, "Can't change admin status.");
}); });
test("Model: isValid", 5, function() { test("isValid", 5, function() {
var model = new Backbone.Model({valid: true}); var model = new Backbone.Model({valid: true});
model.validate = function(attrs) { model.validate = function(attrs) {
if (!attrs.valid) return "invalid"; if (!attrs.valid) return "invalid";
@@ -369,13 +346,13 @@ $(document).ready(function() {
equal(model.isValid(), false); equal(model.isValid(), false);
}); });
test("Model: save", 2, function() { test("save", 2, function() {
doc.save({title : "Henry V"}); doc.save({title : "Henry V"});
equal(lastRequest.method, 'update'); equal(this.syncArgs.method, 'update');
ok(_.isEqual(lastRequest.model, doc)); ok(_.isEqual(this.syncArgs.model, doc));
}); });
test("Model: save in positional style", 1, function() { test("save in positional style", 1, function() {
var model = new Backbone.Model(); var model = new Backbone.Model();
model.sync = function(method, model, options) { model.sync = function(method, model, options) {
options.success(); options.success();
@@ -386,29 +363,29 @@ $(document).ready(function() {
test("Model: fetch", 2, function() { test("fetch", 2, function() {
doc.fetch(); doc.fetch();
equal(lastRequest.method, 'read'); equal(this.syncArgs.method, 'read');
ok(_.isEqual(lastRequest.model, doc)); ok(_.isEqual(this.syncArgs.model, doc));
}); });
test("Model: destroy", 3, function() { test("destroy", 3, function() {
doc.destroy(); doc.destroy();
equal(lastRequest.method, 'delete'); equal(this.syncArgs.method, 'delete');
ok(_.isEqual(lastRequest.model, doc)); ok(_.isEqual(this.syncArgs.model, doc));
var newModel = new Backbone.Model; var newModel = new Backbone.Model;
equal(newModel.destroy(), false); equal(newModel.destroy(), false);
}); });
test("Model: non-persisted destroy", 1, function() { test("non-persisted destroy", 1, function() {
var a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3}); var a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3});
a.sync = function() { throw "should not be called"; }; a.sync = function() { throw "should not be called"; };
a.destroy(); a.destroy();
ok(true, "non-persisted model should not call sync"); ok(true, "non-persisted model should not call sync");
}); });
test("Model: validate", 7, function() { test("validate", 7, function() {
var lastError; var lastError;
var model = new Backbone.Model(); var model = new Backbone.Model();
model.validate = function(attrs) { model.validate = function(attrs) {
@@ -429,7 +406,7 @@ $(document).ready(function() {
equal(model.get('a'), 100); equal(model.get('a'), 100);
}); });
test("Model: validate on unset and clear", 6, function() { test("validate on unset and clear", 6, function() {
var error; var error;
var model = new Backbone.Model({name: "One"}); var model = new Backbone.Model({name: "One"});
model.validate = function(attrs) { model.validate = function(attrs) {
@@ -451,7 +428,7 @@ $(document).ready(function() {
equal(model.get('name'), undefined); equal(model.get('name'), undefined);
}); });
test("Model: validate with error callback", 8, function() { test("validate with error callback", 8, function() {
var lastError, boundError; var lastError, boundError;
var model = new Backbone.Model(); var model = new Backbone.Model();
model.validate = function(attrs) { model.validate = function(attrs) {
@@ -472,10 +449,10 @@ $(document).ready(function() {
equal(result, false); equal(result, false);
equal(model.get('a'), 100); equal(model.get('a'), 100);
equal(lastError, "Can't change admin status."); equal(lastError, "Can't change admin status.");
equal(boundError, undefined); equal(boundError, true);
}); });
test("Model: defaults always extend attrs (#459)", 2, function() { test("defaults always extend attrs (#459)", 2, function() {
var Defaulted = Backbone.Model.extend({ var Defaulted = Backbone.Model.extend({
defaults: {one: 1}, defaults: {one: 1},
initialize : function(attrs, opts) { initialize : function(attrs, opts) {
@@ -486,7 +463,7 @@ $(document).ready(function() {
var emptyattrs = new Defaulted(); var emptyattrs = new Defaulted();
}); });
test("Model: Inherit class properties", 6, function() { test("Inherit class properties", 6, function() {
var Parent = Backbone.Model.extend({ var Parent = Backbone.Model.extend({
instancePropSame: function() {}, instancePropSame: function() {},
instancePropDiff: function() {} instancePropDiff: function() {}
@@ -510,7 +487,7 @@ $(document).ready(function() {
notEqual(Child.prototype.instancePropDiff, undefined); notEqual(Child.prototype.instancePropDiff, undefined);
}); });
test("Model: Nested change events don't clobber previous attributes", 4, function() { test("Nested change events don't clobber previous attributes", 4, function() {
new Backbone.Model() new Backbone.Model()
.on('change:state', function(model, newState) { .on('change:state', function(model, newState) {
equal(model.previous('state'), undefined); equal(model.previous('state'), undefined);
@@ -595,8 +572,9 @@ $(document).ready(function() {
test("save with `wait` succeeds without `validate`", 1, function() { test("save with `wait` succeeds without `validate`", 1, function() {
var model = new Backbone.Model(); var model = new Backbone.Model();
model.url = '/test';
model.save({x: 1}, {wait: true}); model.save({x: 1}, {wait: true});
ok(lastRequest.model === model); ok(this.syncArgs.model === model);
}); });
test("`hasChanged` for falsey keys", 2, function() { test("`hasChanged` for falsey keys", 2, function() {
@@ -616,18 +594,20 @@ $(document).ready(function() {
test("`save` with `wait` sends correct attributes", 5, function() { test("`save` with `wait` sends correct attributes", 5, function() {
var changed = 0; var changed = 0;
var model = new Backbone.Model({x: 1, y: 2}); var model = new Backbone.Model({x: 1, y: 2});
model.url = '/test';
model.on('change:x', function() { changed++; }); model.on('change:x', function() { changed++; });
model.save({x: 3}, {wait: true}); model.save({x: 3}, {wait: true});
deepEqual(JSON.parse(ajaxParams.data), {x: 3, y: 2}); deepEqual(JSON.parse(this.ajaxSettings.data), {x: 3, y: 2});
equal(model.get('x'), 1); equal(model.get('x'), 1);
equal(changed, 0); equal(changed, 0);
lastRequest.options.success({}); this.syncArgs.options.success({});
equal(model.get('x'), 3); equal(model.get('x'), 3);
equal(changed, 1); equal(changed, 1);
}); });
test("a failed `save` with `wait` doesn't leave attributes behind", 1, function() { test("a failed `save` with `wait` doesn't leave attributes behind", 1, function() {
var model = new Backbone.Model; var model = new Backbone.Model;
model.url = '/test';
model.save({x: 1}, {wait: true}); model.save({x: 1}, {wait: true});
equal(model.get('x'), void 0); equal(model.get('x'), void 0);
}); });
@@ -644,6 +624,7 @@ $(document).ready(function() {
test("save with wait validates attributes", 1, function() { test("save with wait validates attributes", 1, function() {
var model = new Backbone.Model(); var model = new Backbone.Model();
model.url = '/test';
model.validate = function() { ok(true); }; model.validate = function() { ok(true); };
model.save({x: 1}, {wait: true}); model.save({x: 1}, {wait: true});
}); });
@@ -776,24 +757,6 @@ $(document).ready(function() {
model.set({a: true}); model.set({a: true});
}); });
test("Backbone.wrapError triggers `'error'`", 12, function() {
var resp = {};
var options = {};
var model = new Backbone.Model();
model.on('error', error);
var callback = Backbone.wrapError(null, model, options);
callback(model, resp);
callback(resp);
callback = Backbone.wrapError(error, model, options);
callback(model, resp);
callback(resp);
function error(_model, _resp, _options) {
ok(model === _model);
ok(resp === _resp);
ok(options === _options);
}
});
test("#1179 - isValid returns true in the absence of validate.", 1, function() { test("#1179 - isValid returns true in the absence of validate.", 1, function() {
var model = new Backbone.Model(); var model = new Backbone.Model();
model.validate = null; model.validate = null;
@@ -831,8 +794,9 @@ $(document).ready(function() {
test("#1412 - Trigger 'sync' event.", 3, function() { test("#1412 - Trigger 'sync' event.", 3, function() {
var model = new Backbone.Model({id: 1}); var model = new Backbone.Model({id: 1});
model.sync = function(method, model, options) { options.success(); }; model.url = '/test';
model.on('sync', function() { ok(true); }); model.on('sync', function(){ ok(true); });
Backbone.ajax = function(settings){ settings.success(); };
model.fetch(); model.fetch();
model.save(); model.save();
model.destroy(); model.destroy();

View File

@@ -2,7 +2,7 @@ $(document).ready(function() {
module("Backbone.noConflict"); module("Backbone.noConflict");
test('Backbone.noConflict', 2, function() { test('noConflict', 2, function() {
var noconflictBackbone = Backbone.noConflict(); var noconflictBackbone = Backbone.noConflict();
equal(window.Backbone, undefined, 'Returned window.Backbone'); equal(window.Backbone, undefined, 'Returned window.Backbone');
window.Backbone = noconflictBackbone; window.Backbone = noconflictBackbone;

View File

@@ -126,11 +126,11 @@ $(document).ready(function() {
}); });
test("Router: initialize", 1, function() { test("initialize", 1, function() {
equal(router.testing, 101); equal(router.testing, 101);
}); });
test("Router: routes (simple)", 4, function() { test("routes (simple)", 4, function() {
location.replace('http://example.com#search/news'); location.replace('http://example.com#search/news');
Backbone.history.checkUrl(); Backbone.history.checkUrl();
equal(router.query, 'news'); equal(router.query, 'news');
@@ -139,26 +139,26 @@ $(document).ready(function() {
equal(lastArgs[0], 'news'); equal(lastArgs[0], 'news');
}); });
test("Router: routes (two part)", 2, function() { test("routes (two part)", 2, function() {
location.replace('http://example.com#search/nyc/p10'); location.replace('http://example.com#search/nyc/p10');
Backbone.history.checkUrl(); Backbone.history.checkUrl();
equal(router.query, 'nyc'); equal(router.query, 'nyc');
equal(router.page, '10'); equal(router.page, '10');
}); });
test("Router: routes via navigate", 2, function() { test("routes via navigate", 2, function() {
Backbone.history.navigate('search/manhattan/p20', {trigger: true}); Backbone.history.navigate('search/manhattan/p20', {trigger: true});
equal(router.query, 'manhattan'); equal(router.query, 'manhattan');
equal(router.page, '20'); equal(router.page, '20');
}); });
test("Router: routes via navigate for backwards-compatibility", 2, function() { test("routes via navigate for backwards-compatibility", 2, function() {
Backbone.history.navigate('search/manhattan/p20', true); Backbone.history.navigate('search/manhattan/p20', true);
equal(router.query, 'manhattan'); equal(router.query, 'manhattan');
equal(router.page, '20'); equal(router.page, '20');
}); });
test("Router: route precedence via navigate", 6, function(){ test("route precedence via navigate", 6, function(){
// check both 0.9.x and backwards-compatibility options // check both 0.9.x and backwards-compatibility options
_.each([ { trigger: true }, true ], function( options ){ _.each([ { trigger: true }, true ], function( options ){
Backbone.history.navigate('contacts', options); Backbone.history.navigate('contacts', options);
@@ -178,13 +178,13 @@ $(document).ready(function() {
Backbone.history.navigate('/route'); Backbone.history.navigate('/route');
}); });
test("Router: use implicit callback if none provided", 1, function() { test("use implicit callback if none provided", 1, function() {
router.count = 0; router.count = 0;
router.navigate('implicit', {trigger: true}); router.navigate('implicit', {trigger: true});
equal(router.count, 1); equal(router.count, 1);
}); });
test("Router: routes via navigate with {replace: true}", 1, function() { test("routes via navigate with {replace: true}", 1, function() {
location.replace('http://example.com#start_here'); location.replace('http://example.com#start_here');
Backbone.history.checkUrl(); Backbone.history.checkUrl();
location.replace = function(href) { location.replace = function(href) {
@@ -193,13 +193,13 @@ $(document).ready(function() {
Backbone.history.navigate('end_here', {replace: true}); Backbone.history.navigate('end_here', {replace: true});
}); });
test("Router: routes (splats)", 1, function() { test("routes (splats)", 1, function() {
location.replace('http://example.com#splat/long-list/of/splatted_99args/end'); location.replace('http://example.com#splat/long-list/of/splatted_99args/end');
Backbone.history.checkUrl(); Backbone.history.checkUrl();
equal(router.args, 'long-list/of/splatted_99args'); equal(router.args, 'long-list/of/splatted_99args');
}); });
test("Router: routes (complex)", 3, function() { test("routes (complex)", 3, function() {
location.replace('http://example.com#one/two/three/complex-part/four/five/six/seven'); location.replace('http://example.com#one/two/three/complex-part/four/five/six/seven');
Backbone.history.checkUrl(); Backbone.history.checkUrl();
equal(router.first, 'one/two/three'); equal(router.first, 'one/two/three');
@@ -207,7 +207,7 @@ $(document).ready(function() {
equal(router.rest, 'four/five/six/seven'); equal(router.rest, 'four/five/six/seven');
}); });
test("Router: routes (query)", 5, function() { test("routes (query)", 5, function() {
location.replace('http://example.com#mandel?a=b&c=d'); location.replace('http://example.com#mandel?a=b&c=d');
Backbone.history.checkUrl(); Backbone.history.checkUrl();
equal(router.entity, 'mandel'); equal(router.entity, 'mandel');
@@ -217,13 +217,13 @@ $(document).ready(function() {
equal(lastArgs[1], 'a=b&c=d'); equal(lastArgs[1], 'a=b&c=d');
}); });
test("Router: routes (anything)", 1, function() { test("routes (anything)", 1, function() {
location.replace('http://example.com#doesnt-match-a-route'); location.replace('http://example.com#doesnt-match-a-route');
Backbone.history.checkUrl(); Backbone.history.checkUrl();
equal(router.anything, 'doesnt-match-a-route'); equal(router.anything, 'doesnt-match-a-route');
}); });
test("Router: fires event when router doesn't have callback on it", 1, function() { test("fires event when router doesn't have callback on it", 1, function() {
router.on("route:noCallback", function(){ ok(true); }); router.on("route:noCallback", function(){ ok(true); });
location.replace('http://example.com#noCallback'); location.replace('http://example.com#noCallback');
Backbone.history.checkUrl(); Backbone.history.checkUrl();
@@ -244,17 +244,14 @@ $(document).ready(function() {
}); });
test("#1003 - History is started before navigate is called", 1, function() { test("#1003 - History is started before navigate is called", 1, function() {
var history = new Backbone.History();
history.navigate = function(){
ok(Backbone.History.started);
};
Backbone.history.stop(); Backbone.history.stop();
history.start(); Backbone.history.navigate = function(){ ok(Backbone.History.started); };
Backbone.history.start();
// If this is not an old IE navigate will not be called. // If this is not an old IE navigate will not be called.
if (!history.iframe) ok(true); if (!Backbone.history.iframe) ok(true);
}); });
test("Router: route callback gets passed decoded values", 3, function() { test("route callback gets passed decoded values", 3, function() {
var route = 'has%2Fslash/complex-has%23hash/has%20space'; var route = 'has%2Fslash/complex-has%23hash/has%20space';
Backbone.history.navigate(route, {trigger: true}); Backbone.history.navigate(route, {trigger: true});
equal(router.first, 'has/slash'); equal(router.first, 'has/slash');
@@ -262,7 +259,7 @@ $(document).ready(function() {
equal(router.rest, 'has space'); equal(router.rest, 'has space');
}); });
test("Router: correctly handles URLs with % (#868)", 3, function() { test("correctly handles URLs with % (#868)", 3, function() {
location.replace('http://example.com#search/fat%3A1.5%25'); location.replace('http://example.com#search/fat%3A1.5%25');
Backbone.history.checkUrl(); Backbone.history.checkUrl();
location.replace('http://example.com#search/fat'); location.replace('http://example.com#search/fat');
@@ -320,7 +317,7 @@ $(document).ready(function() {
strictEqual(Backbone.history.fragment, 'x'); strictEqual(Backbone.history.fragment, 'x');
}); });
test("Router: Normalize root.", 1, function() { test("Normalize root.", 1, function() {
Backbone.history.stop(); Backbone.history.stop();
location.replace('http://example.com/root'); location.replace('http://example.com/root');
Backbone.history = new Backbone.History({ Backbone.history = new Backbone.History({
@@ -339,7 +336,7 @@ $(document).ready(function() {
Backbone.history.navigate('fragment'); Backbone.history.navigate('fragment');
}); });
test("Router: Normalize root.", 1, function() { test("Normalize root.", 1, function() {
Backbone.history.stop(); Backbone.history.stop();
location.replace('http://example.com/root#fragment'); location.replace('http://example.com/root#fragment');
Backbone.history = new Backbone.History({ Backbone.history = new Backbone.History({
@@ -347,7 +344,7 @@ $(document).ready(function() {
history: { history: {
pushState: function(state, title, url) {}, pushState: function(state, title, url) {},
replaceState: function(state, title, url) { replaceState: function(state, title, url) {
strictEqual(url, 'http://example.com/root/fragment'); strictEqual(url, '/root/fragment');
} }
} }
}); });
@@ -357,7 +354,7 @@ $(document).ready(function() {
}); });
}); });
test("Router: Normalize root.", 1, function() { test("Normalize root.", 1, function() {
Backbone.history.stop(); Backbone.history.stop();
location.replace('http://example.com/root'); location.replace('http://example.com/root');
Backbone.history = new Backbone.History({location: location}); Backbone.history = new Backbone.History({location: location});
@@ -368,4 +365,88 @@ $(document).ready(function() {
}); });
}); });
test("Normalize root - leading slash.", 1, function() {
Backbone.history.stop();
location.replace('http://example.com/root');
Backbone.history = new Backbone.History({
location: location,
history: {
pushState: function(){},
replaceState: function(){}
}
});
Backbone.history.start({root: 'root'});
strictEqual(Backbone.history.root, '/root/');
});
test("Transition from hashChange to pushState.", 1, function() {
Backbone.history.stop();
location.replace('http://example.com/root#x/y');
Backbone.history = new Backbone.History({
location: location,
history: {
pushState: function(){},
replaceState: function(state, title, url){
strictEqual(url, '/root/x/y');
}
}
});
Backbone.history.start({
root: 'root',
pushState: true
});
});
test("#1619: Router: Normalize empty root", 1, function() {
Backbone.history.stop();
location.replace('http://example.com/');
Backbone.history = new Backbone.History({
location: location,
history: {
pushState: function(){},
replaceState: function(){}
}
});
Backbone.history.start({root: ''});
strictEqual(Backbone.history.root, '/');
});
test("#1619: Router: nagivate with empty root", 1, function() {
Backbone.history.stop();
location.replace('http://example.com/');
Backbone.history = new Backbone.History({
location: location,
history: {
pushState: function(state, title, url) {
strictEqual(url, '/fragment');
}
}
});
Backbone.history.start({
pushState: true,
root: '',
hashChange: false
});
Backbone.history.navigate('fragment');
});
test("Transition from pushState to hashChange.", 1, function() {
Backbone.history.stop();
location.replace('http://example.com/root/x/y?a=b');
location.replace = function(url) {
strictEqual(url, '/root/?a=b#x/y');
};
Backbone.history = new Backbone.History({
location: location,
history: {
pushState: null,
replaceState: null
}
});
Backbone.history.start({
root: 'root',
pushState: true
});
});
}); });

View File

@@ -1,8 +1,5 @@
$(document).ready(function() { $(document).ready(function() {
var ajax = Backbone.ajax;
var lastRequest = null;
var Library = Backbone.Collection.extend({ var Library = Backbone.Collection.extend({
url : function() { return '/library'; } url : function() { return '/library'; }
}); });
@@ -14,132 +11,126 @@ $(document).ready(function() {
length : 123 length : 123
}; };
module("Backbone.sync", { module("Backbone.sync", _.extend(new Environment, {
setup : function() { setup : function() {
library = new Library(); Environment.prototype.setup.apply(this, arguments);
Backbone.ajax = function(obj) { library = new Library;
lastRequest = obj;
};
library.create(attrs, {wait: false}); library.create(attrs, {wait: false});
},
teardown: function() {
Backbone.ajax = ajax;
} }
}); }));
test("sync: read", 4, function() { test("read", 4, function() {
library.fetch(); library.fetch();
equal(lastRequest.url, '/library'); equal(this.ajaxSettings.url, '/library');
equal(lastRequest.type, 'GET'); equal(this.ajaxSettings.type, 'GET');
equal(lastRequest.dataType, 'json'); equal(this.ajaxSettings.dataType, 'json');
ok(_.isEmpty(lastRequest.data)); ok(_.isEmpty(this.ajaxSettings.data));
}); });
test("sync: passing data", 3, function() { test("passing data", 3, function() {
library.fetch({data: {a: 'a', one: 1}}); library.fetch({data: {a: 'a', one: 1}});
equal(lastRequest.url, '/library'); equal(this.ajaxSettings.url, '/library');
equal(lastRequest.data.a, 'a'); equal(this.ajaxSettings.data.a, 'a');
equal(lastRequest.data.one, 1); equal(this.ajaxSettings.data.one, 1);
}); });
test("sync: create", 6, function() { test("create", 6, function() {
equal(lastRequest.url, '/library'); equal(this.ajaxSettings.url, '/library');
equal(lastRequest.type, 'POST'); equal(this.ajaxSettings.type, 'POST');
equal(lastRequest.dataType, 'json'); equal(this.ajaxSettings.dataType, 'json');
var data = JSON.parse(lastRequest.data); var data = JSON.parse(this.ajaxSettings.data);
equal(data.title, 'The Tempest'); equal(data.title, 'The Tempest');
equal(data.author, 'Bill Shakespeare'); equal(data.author, 'Bill Shakespeare');
equal(data.length, 123); equal(data.length, 123);
}); });
test("sync: update", 7, function() { test("update", 7, function() {
library.first().save({id: '1-the-tempest', author: 'William Shakespeare'}); library.first().save({id: '1-the-tempest', author: 'William Shakespeare'});
equal(lastRequest.url, '/library/1-the-tempest'); equal(this.ajaxSettings.url, '/library/1-the-tempest');
equal(lastRequest.type, 'PUT'); equal(this.ajaxSettings.type, 'PUT');
equal(lastRequest.dataType, 'json'); equal(this.ajaxSettings.dataType, 'json');
var data = JSON.parse(lastRequest.data); var data = JSON.parse(this.ajaxSettings.data);
equal(data.id, '1-the-tempest'); equal(data.id, '1-the-tempest');
equal(data.title, 'The Tempest'); equal(data.title, 'The Tempest');
equal(data.author, 'William Shakespeare'); equal(data.author, 'William Shakespeare');
equal(data.length, 123); equal(data.length, 123);
}); });
test("sync: update with emulateHTTP and emulateJSON", 7, function() { test("update with emulateHTTP and emulateJSON", 7, function() {
Backbone.emulateHTTP = Backbone.emulateJSON = true; Backbone.emulateHTTP = Backbone.emulateJSON = true;
library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}); library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'});
equal(lastRequest.url, '/library/2-the-tempest'); equal(this.ajaxSettings.url, '/library/2-the-tempest');
equal(lastRequest.type, 'POST'); equal(this.ajaxSettings.type, 'POST');
equal(lastRequest.dataType, 'json'); equal(this.ajaxSettings.dataType, 'json');
equal(lastRequest.data._method, 'PUT'); equal(this.ajaxSettings.data._method, 'PUT');
var data = JSON.parse(lastRequest.data.model); var data = JSON.parse(this.ajaxSettings.data.model);
equal(data.id, '2-the-tempest'); equal(data.id, '2-the-tempest');
equal(data.author, 'Tim Shakespeare'); equal(data.author, 'Tim Shakespeare');
equal(data.length, 123); equal(data.length, 123);
Backbone.emulateHTTP = Backbone.emulateJSON = false; Backbone.emulateHTTP = Backbone.emulateJSON = false;
}); });
test("sync: update with just emulateHTTP", 6, function() { test("update with just emulateHTTP", 6, function() {
Backbone.emulateHTTP = true; Backbone.emulateHTTP = true;
library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}); library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'});
equal(lastRequest.url, '/library/2-the-tempest'); equal(this.ajaxSettings.url, '/library/2-the-tempest');
equal(lastRequest.type, 'POST'); equal(this.ajaxSettings.type, 'POST');
equal(lastRequest.contentType, 'application/json'); equal(this.ajaxSettings.contentType, 'application/json');
var data = JSON.parse(lastRequest.data); var data = JSON.parse(this.ajaxSettings.data);
equal(data.id, '2-the-tempest'); equal(data.id, '2-the-tempest');
equal(data.author, 'Tim Shakespeare'); equal(data.author, 'Tim Shakespeare');
equal(data.length, 123); equal(data.length, 123);
Backbone.emulateHTTP = false; Backbone.emulateHTTP = false;
}); });
test("sync: update with just emulateJSON", 6, function() { test("update with just emulateJSON", 6, function() {
Backbone.emulateJSON = true; Backbone.emulateJSON = true;
library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}); library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'});
equal(lastRequest.url, '/library/2-the-tempest'); equal(this.ajaxSettings.url, '/library/2-the-tempest');
equal(lastRequest.type, 'PUT'); equal(this.ajaxSettings.type, 'PUT');
equal(lastRequest.contentType, 'application/x-www-form-urlencoded'); equal(this.ajaxSettings.contentType, 'application/x-www-form-urlencoded');
var data = JSON.parse(lastRequest.data.model); var data = JSON.parse(this.ajaxSettings.data.model);
equal(data.id, '2-the-tempest'); equal(data.id, '2-the-tempest');
equal(data.author, 'Tim Shakespeare'); equal(data.author, 'Tim Shakespeare');
equal(data.length, 123); equal(data.length, 123);
Backbone.emulateJSON = false; Backbone.emulateJSON = false;
}); });
test("sync: read model", 3, function() { test("read model", 3, function() {
library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}); library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'});
library.first().fetch(); library.first().fetch();
equal(lastRequest.url, '/library/2-the-tempest'); equal(this.ajaxSettings.url, '/library/2-the-tempest');
equal(lastRequest.type, 'GET'); equal(this.ajaxSettings.type, 'GET');
ok(_.isEmpty(lastRequest.data)); ok(_.isEmpty(this.ajaxSettings.data));
}); });
test("sync: destroy", 3, function() { test("destroy", 3, function() {
library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}); library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'});
library.first().destroy({wait: true}); library.first().destroy({wait: true});
equal(lastRequest.url, '/library/2-the-tempest'); equal(this.ajaxSettings.url, '/library/2-the-tempest');
equal(lastRequest.type, 'DELETE'); equal(this.ajaxSettings.type, 'DELETE');
equal(lastRequest.data, null); equal(this.ajaxSettings.data, null);
}); });
test("sync: destroy with emulateHTTP", 3, function() { test("destroy with emulateHTTP", 3, function() {
library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}); library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'});
Backbone.emulateHTTP = Backbone.emulateJSON = true; Backbone.emulateHTTP = Backbone.emulateJSON = true;
library.first().destroy(); library.first().destroy();
equal(lastRequest.url, '/library/2-the-tempest'); equal(this.ajaxSettings.url, '/library/2-the-tempest');
equal(lastRequest.type, 'POST'); equal(this.ajaxSettings.type, 'POST');
equal(JSON.stringify(lastRequest.data), '{"_method":"DELETE"}'); equal(JSON.stringify(this.ajaxSettings.data), '{"_method":"DELETE"}');
Backbone.emulateHTTP = Backbone.emulateJSON = false; Backbone.emulateHTTP = Backbone.emulateJSON = false;
}); });
test("sync: urlError", 2, function() { test("urlError", 2, function() {
var model = new Backbone.Model(); var model = new Backbone.Model();
raises(function() { raises(function() {
model.fetch(); model.fetch();
}); });
model.fetch({url: '/one/two'}); model.fetch({url: '/one/two'});
equal(lastRequest.url, '/one/two'); equal(this.ajaxSettings.url, '/one/two');
}); });
test("#1052 - `options` is optional.", 0, function() { test("#1052 - `options` is optional.", 0, function() {
@@ -157,4 +148,13 @@ $(document).ready(function() {
Backbone.sync('create', model); Backbone.sync('create', model);
}); });
test("Call provided error callback on error.", 1, function() {
var model = new Backbone.Model;
model.url = '/test';
Backbone.sync('read', model, {
error: function() { ok(true); }
});
this.ajaxSettings.error();
});
}); });

View File

@@ -1,11 +1,11 @@
/** /**
* QUnit v1.8.0 - A JavaScript Unit Testing Framework * QUnit v1.10.0 - A JavaScript Unit Testing Framework
* *
* http://docs.jquery.com/QUnit * http://qunitjs.com
* *
* Copyright (c) 2012 John Resig, Jörn Zaefferer * Copyright 2012 jQuery Foundation and other contributors
* Dual licensed under the MIT (MIT-LICENSE.txt) * Released under the MIT license.
* or GPL (GPL-LICENSE.txt) licenses. * http://jquery.org/license
*/ */
/** Font Family and Sizes */ /** Font Family and Sizes */
@@ -20,7 +20,7 @@
/** Resets */ /** Resets */
#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { #qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
@@ -38,10 +38,10 @@
line-height: 1em; line-height: 1em;
font-weight: normal; font-weight: normal;
border-radius: 15px 15px 0 0; border-radius: 5px 5px 0 0;
-moz-border-radius: 15px 15px 0 0; -moz-border-radius: 5px 5px 0 0;
-webkit-border-top-right-radius: 15px; -webkit-border-top-right-radius: 5px;
-webkit-border-top-left-radius: 15px; -webkit-border-top-left-radius: 5px;
} }
#qunit-header a { #qunit-header a {
@@ -54,9 +54,9 @@
color: #fff; color: #fff;
} }
#qunit-header label { #qunit-testrunner-toolbar label {
display: inline-block; display: inline-block;
padding-left: 0.5em; padding: 0 .5em 0 .1em;
} }
#qunit-banner { #qunit-banner {
@@ -67,6 +67,7 @@
padding: 0.5em 0 0.5em 2em; padding: 0.5em 0 0.5em 2em;
color: #5E740B; color: #5E740B;
background-color: #eee; background-color: #eee;
overflow: hidden;
} }
#qunit-userAgent { #qunit-userAgent {
@@ -76,6 +77,9 @@
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
} }
#qunit-modulefilter-container {
float: right;
}
/** Tests: Pass/Fail */ /** Tests: Pass/Fail */
@@ -113,13 +117,9 @@
background-color: #fff; background-color: #fff;
border-radius: 15px; border-radius: 5px;
-moz-border-radius: 15px; -moz-border-radius: 5px;
-webkit-border-radius: 15px; -webkit-border-radius: 5px;
box-shadow: inset 0px 2px 13px #999;
-moz-box-shadow: inset 0px 2px 13px #999;
-webkit-box-shadow: inset 0px 2px 13px #999;
} }
#qunit-tests table { #qunit-tests table {
@@ -162,8 +162,7 @@
#qunit-tests b.failed { color: #710909; } #qunit-tests b.failed { color: #710909; }
#qunit-tests li li { #qunit-tests li li {
margin: 0.5em; padding: 5px;
padding: 0.4em 0.5em 0.4em 0.5em;
background-color: #fff; background-color: #fff;
border-bottom: none; border-bottom: none;
list-style-position: inside; list-style-position: inside;
@@ -172,9 +171,9 @@
/*** Passing Styles */ /*** Passing Styles */
#qunit-tests li li.pass { #qunit-tests li li.pass {
color: #5E740B; color: #3c510c;
background-color: #fff; background-color: #fff;
border-left: 26px solid #C6E746; border-left: 10px solid #C6E746;
} }
#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
@@ -190,15 +189,15 @@
#qunit-tests li li.fail { #qunit-tests li li.fail {
color: #710909; color: #710909;
background-color: #fff; background-color: #fff;
border-left: 26px solid #EE5757; border-left: 10px solid #EE5757;
white-space: pre; white-space: pre;
} }
#qunit-tests > li:last-child { #qunit-tests > li:last-child {
border-radius: 0 0 15px 15px; border-radius: 0 0 5px 5px;
-moz-border-radius: 0 0 15px 15px; -moz-border-radius: 0 0 5px 5px;
-webkit-border-bottom-right-radius: 15px; -webkit-border-bottom-right-radius: 5px;
-webkit-border-bottom-left-radius: 15px; -webkit-border-bottom-left-radius: 5px;
} }
#qunit-tests .fail { color: #000000; background-color: #EE5757; } #qunit-tests .fail { color: #000000; background-color: #EE5757; }

View File

@@ -1,11 +1,11 @@
/** /**
* QUnit v1.8.0 - A JavaScript Unit Testing Framework * QUnit v1.10.0 - A JavaScript Unit Testing Framework
* *
* http://docs.jquery.com/QUnit * http://qunitjs.com
* *
* Copyright (c) 2012 John Resig, Jörn Zaefferer * Copyright 2012 jQuery Foundation and other contributors
* Dual licensed under the MIT (MIT-LICENSE.txt) * Released under the MIT license.
* or GPL (GPL-LICENSE.txt) licenses. * http://jquery.org/license
*/ */
(function( window ) { (function( window ) {
@@ -17,6 +17,8 @@ var QUnit,
fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""), fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""),
toString = Object.prototype.toString, toString = Object.prototype.toString,
hasOwn = Object.prototype.hasOwnProperty, hasOwn = Object.prototype.hasOwnProperty,
// Keep a local reference to Date (GH-283)
Date = window.Date,
defined = { defined = {
setTimeout: typeof window.setTimeout !== "undefined", setTimeout: typeof window.setTimeout !== "undefined",
sessionStorage: (function() { sessionStorage: (function() {
@@ -304,7 +306,8 @@ QUnit = {
// call on start of module test to prepend name to all tests // call on start of module test to prepend name to all tests
module: function( name, testEnvironment ) { module: function( name, testEnvironment ) {
config.currentModule = name; config.currentModule = name;
config.currentModuleTestEnviroment = testEnvironment; config.currentModuleTestEnvironment = testEnvironment;
config.modules[name] = true;
}, },
asyncTest: function( testName, expected, callback ) { asyncTest: function( testName, expected, callback ) {
@@ -336,7 +339,7 @@ QUnit = {
async: async, async: async,
callback: callback, callback: callback,
module: config.currentModule, module: config.currentModule,
moduleTestEnvironment: config.currentModuleTestEnviroment, moduleTestEnvironment: config.currentModuleTestEnvironment,
stack: sourceFromStacktrace( 2 ) stack: sourceFromStacktrace( 2 )
}); });
@@ -349,7 +352,11 @@ QUnit = {
// Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. // Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
expect: function( asserts ) { expect: function( asserts ) {
config.current.expected = asserts; if (arguments.length === 1) {
config.current.expected = asserts;
} else {
return config.current.expected;
}
}, },
start: function( count ) { start: function( count ) {
@@ -403,6 +410,8 @@ QUnit = {
QUnit.assert = { QUnit.assert = {
/** /**
* Asserts rough true-ish result. * Asserts rough true-ish result.
* @name ok
* @function
* @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
*/ */
ok: function( result, msg ) { ok: function( result, msg ) {
@@ -413,6 +422,8 @@ QUnit.assert = {
var source, var source,
details = { details = {
module: config.current.module,
name: config.current.testName,
result: result, result: result,
message: msg message: msg
}; };
@@ -437,36 +448,59 @@ QUnit.assert = {
/** /**
* Assert that the first two arguments are equal, with an optional message. * Assert that the first two arguments are equal, with an optional message.
* Prints out both actual and expected values. * 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" ); * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" );
*/ */
equal: function( actual, expected, message ) { equal: function( actual, expected, message ) {
QUnit.push( expected == actual, actual, expected, message ); QUnit.push( expected == actual, actual, expected, message );
}, },
/**
* @name notEqual
* @function
*/
notEqual: function( actual, expected, message ) { notEqual: function( actual, expected, message ) {
QUnit.push( expected != actual, actual, expected, message ); QUnit.push( expected != actual, actual, expected, message );
}, },
/**
* @name deepEqual
* @function
*/
deepEqual: function( actual, expected, message ) { deepEqual: function( actual, expected, message ) {
QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
}, },
/**
* @name notDeepEqual
* @function
*/
notDeepEqual: function( actual, expected, message ) { notDeepEqual: function( actual, expected, message ) {
QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message );
}, },
/**
* @name strictEqual
* @function
*/
strictEqual: function( actual, expected, message ) { strictEqual: function( actual, expected, message ) {
QUnit.push( expected === actual, actual, expected, message ); QUnit.push( expected === actual, actual, expected, message );
}, },
/**
* @name notStrictEqual
* @function
*/
notStrictEqual: function( actual, expected, message ) { notStrictEqual: function( actual, expected, message ) {
QUnit.push( expected !== actual, actual, expected, message ); QUnit.push( expected !== actual, actual, expected, message );
}, },
raises: function( block, expected, message ) { throws: function( block, expected, message ) {
var actual, var actual,
ok = false; ok = false;
// 'expected' is optional
if ( typeof expected === "string" ) { if ( typeof expected === "string" ) {
message = expected; message = expected;
expected = null; expected = null;
@@ -494,18 +528,29 @@ QUnit.assert = {
} else if ( expected.call( {}, actual ) === true ) { } else if ( expected.call( {}, actual ) === true ) {
ok = true; ok = true;
} }
}
QUnit.push( ok, actual, null, message ); QUnit.push( ok, actual, null, message );
} else {
QUnit.pushFailure( message, null, 'No exception was thrown.' );
}
} }
}; };
// @deprecated: Kept assertion helpers in root for backwards compatibility /**
* @deprecate since 1.8.0
* Kept assertion helpers in root for backwards compatibility
*/
extend( QUnit, QUnit.assert ); extend( QUnit, QUnit.assert );
/** /**
* @deprecated: Kept for backwards compatibility * @deprecated since 1.9.0
* next step: remove entirely * Kept global "raises()" for backwards compatibility
*/
QUnit.raises = QUnit.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.equals = function() {
QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" ); QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" );
@@ -549,7 +594,23 @@ config = {
// when enabled, all tests must call expect() // when enabled, all tests must call expect()
requireExpects: false, requireExpects: false,
urlConfig: [ "noglobals", "notrycatch" ], // add checkboxes that are persisted in the query-string
// when enabled, the id is set to `true` as a `QUnit.config` property
urlConfig: [
{
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."
},
{
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."
}
],
// Set of all modules.
modules: {},
// logging callback queues // logging callback queues
begin: [], begin: [],
@@ -661,17 +722,10 @@ extend( QUnit, {
}, },
// Resets the test setup. Useful for tests that modify the DOM. // Resets the test setup. Useful for tests that modify the DOM.
// If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
reset: function() { reset: function() {
var fixture; var fixture = id( "qunit-fixture" );
if ( fixture ) {
if ( window.jQuery ) { fixture.innerHTML = config.fixture;
jQuery( "#qunit-fixture" ).html( config.fixture );
} else {
fixture = id( "qunit-fixture" );
if ( fixture ) {
fixture.innerHTML = config.fixture;
}
} }
}, },
@@ -732,6 +786,8 @@ extend( QUnit, {
var output, source, var output, source,
details = { details = {
module: config.current.module,
name: config.current.testName,
result: result, result: result,
message: message, message: message,
actual: actual, actual: actual,
@@ -770,26 +826,36 @@ extend( QUnit, {
}); });
}, },
pushFailure: function( message, source ) { pushFailure: function( message, source, actual ) {
if ( !config.current ) { if ( !config.current ) {
throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) ); throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) );
} }
var output, var output,
details = { details = {
module: config.current.module,
name: config.current.testName,
result: false, result: false,
message: message message: message
}; };
message = escapeInnerText(message ) || "error"; message = escapeInnerText( message ) || "error";
message = "<span class='test-message'>" + message + "</span>"; message = "<span class='test-message'>" + message + "</span>";
output = message; output = message;
output += "<table>";
if ( actual ) {
output += "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeInnerText( actual ) + "</pre></td></tr>";
}
if ( source ) { if ( source ) {
details.source = source; details.source = source;
output += "<table><tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr></table>"; output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr>";
} }
output += "</table>";
runLoggingCallbacks( "log", QUnit, details ); runLoggingCallbacks( "log", QUnit, details );
config.current.assertions.push({ config.current.assertions.push({
@@ -859,7 +925,9 @@ QUnit.load = function() {
runLoggingCallbacks( "begin", QUnit, {} ); runLoggingCallbacks( "begin", QUnit, {} );
// Initialize the config, saving the execution queue // Initialize the config, saving the execution queue
var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, urlConfigCheckboxes, moduleFilter,
numModules = 0,
moduleFilterHtml = "",
urlConfigHtml = "", urlConfigHtml = "",
oldconfig = extend( {}, config ); oldconfig = extend( {}, config );
@@ -872,10 +940,26 @@ QUnit.load = function() {
for ( i = 0; i < len; i++ ) { for ( i = 0; i < len; i++ ) {
val = config.urlConfig[i]; val = config.urlConfig[i];
config[val] = QUnit.urlParams[val]; if ( typeof val === "string" ) {
urlConfigHtml += "<label><input name='" + val + "' type='checkbox'" + ( config[val] ? " checked='checked'" : "" ) + ">" + val + "</label>"; val = {
id: val,
label: val,
tooltip: "[no tooltip available]"
};
}
config[ val.id ] = QUnit.urlParams[ val.id ];
urlConfigHtml += "<input id='qunit-urlconfig-" + val.id + "' name='" + val.id + "' type='checkbox'" + ( config[ val.id ] ? " checked='checked'" : "" ) + " title='" + val.tooltip + "'><label for='qunit-urlconfig-" + val.id + "' title='" + val.tooltip + "'>" + val.label + "</label>";
} }
moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label><select id='qunit-modulefilter' name='modulefilter'><option value='' " + ( config.module === undefined ? "selected" : "" ) + ">< All Modules ></option>";
for ( i in config.modules ) {
if ( config.modules.hasOwnProperty( i ) ) {
numModules += 1;
moduleFilterHtml += "<option value='" + encodeURIComponent(i) + "' " + ( config.module === i ? "selected" : "" ) + ">" + i + "</option>";
}
}
moduleFilterHtml += "</select>";
// `userAgent` initialized at top of scope // `userAgent` initialized at top of scope
userAgent = id( "qunit-userAgent" ); userAgent = id( "qunit-userAgent" );
if ( userAgent ) { if ( userAgent ) {
@@ -885,12 +969,7 @@ QUnit.load = function() {
// `banner` initialized at top of scope // `banner` initialized at top of scope
banner = id( "qunit-header" ); banner = id( "qunit-header" );
if ( banner ) { if ( banner ) {
banner.innerHTML = "<a href='" + QUnit.url({ filter: undefined }) + "'>" + banner.innerHTML + "</a> " + urlConfigHtml; banner.innerHTML = "<a href='" + QUnit.url({ filter: undefined, module: undefined, testNumber: undefined }) + "'>" + banner.innerHTML + "</a> ";
addEvent( banner, "change", function( event ) {
var params = {};
params[ event.target.name ] = event.target.checked ? true : undefined;
window.location = QUnit.url( params );
});
} }
// `toolbar` initialized at top of scope // `toolbar` initialized at top of scope
@@ -931,8 +1010,31 @@ QUnit.load = function() {
// `label` initialized at top of scope // `label` initialized at top of scope
label = document.createElement( "label" ); label = document.createElement( "label" );
label.setAttribute( "for", "qunit-filter-pass" ); label.setAttribute( "for", "qunit-filter-pass" );
label.setAttribute( "title", "Only show tests and assertons that fail. Stored in sessionStorage." );
label.innerHTML = "Hide passed tests"; label.innerHTML = "Hide passed tests";
toolbar.appendChild( label ); toolbar.appendChild( label );
urlConfigCheckboxes = document.createElement( 'span' );
urlConfigCheckboxes.innerHTML = urlConfigHtml;
addEvent( urlConfigCheckboxes, "change", function( event ) {
var params = {};
params[ event.target.name ] = event.target.checked ? true : undefined;
window.location = QUnit.url( params );
});
toolbar.appendChild( urlConfigCheckboxes );
if (numModules > 1) {
moduleFilter = document.createElement( 'span' );
moduleFilter.setAttribute( 'id', 'qunit-modulefilter-container' );
moduleFilter.innerHTML = moduleFilterHtml;
addEvent( moduleFilter, "change", function() {
var selectBox = moduleFilter.getElementsByTagName("select")[0],
selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value);
window.location = QUnit.url( { module: ( selectedModule === "" ) ? undefined : selectedModule } );
});
toolbar.appendChild(moduleFilter);
}
} }
// `main` initialized at top of scope // `main` initialized at top of scope
@@ -970,9 +1072,9 @@ window.onerror = function ( error, filePath, linerNr ) {
} }
QUnit.pushFailure( error, filePath + ":" + linerNr ); QUnit.pushFailure( error, filePath + ":" + linerNr );
} else { } else {
QUnit.test( "global failure", function() { QUnit.test( "global failure", extend( function() {
QUnit.pushFailure( error, filePath + ":" + linerNr ); QUnit.pushFailure( error, filePath + ":" + linerNr );
}); }, { validTest: validTest } ) );
} }
return false; return false;
} }
@@ -1039,6 +1141,11 @@ function done() {
} }
} }
// scroll back to top to show results
if ( window.scrollTo ) {
window.scrollTo(0, 0);
}
runLoggingCallbacks( "done", QUnit, { runLoggingCallbacks( "done", QUnit, {
failed: config.stats.bad, failed: config.stats.bad,
passed: passed, passed: passed,
@@ -1051,14 +1158,20 @@ function done() {
function validTest( test ) { function validTest( test ) {
var include, var include,
filter = config.filter && config.filter.toLowerCase(), filter = config.filter && config.filter.toLowerCase(),
module = config.module, module = config.module && config.module.toLowerCase(),
fullName = (test.module + ": " + test.testName).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 ) { if ( config.testNumber ) {
return test.testNumber === config.testNumber; return test.testNumber === config.testNumber;
} }
if ( module && test.module !== module ) { if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) {
return false; return false;
} }
@@ -1335,7 +1448,8 @@ QUnit.equiv = (function() {
a.global === b.global && a.global === b.global &&
// (gmi) ... // (gmi) ...
a.ignoreCase === b.ignoreCase && a.ignoreCase === b.ignoreCase &&
a.multiline === b.multiline; a.multiline === b.multiline &&
a.sticky === b.sticky;
}, },
// - skip when the property is a method of an instance (OOP) // - skip when the property is a method of an instance (OOP)

View File

@@ -13,27 +13,28 @@ $(document).ready(function() {
}); });
test("View: constructor", 4, function() { test("constructor", 4, function() {
equal(view.el.id, 'test-view'); equal(view.el.id, 'test-view');
equal(view.el.className, 'test-view'); equal(view.el.className, 'test-view');
equal(view.options.id, 'test-view'); equal(view.options.id, 'test-view');
equal(view.options.className, 'test-view'); equal(view.options.className, 'test-view');
}); });
test("View: jQuery", 2, function() { test("jQuery", 1, function() {
view.setElement(document.body); var view = new Backbone.View;
ok(view.$('#qunit-header a').get(0).innerHTML.match(/Backbone Test Suite/)); view.setElement('<p><a><b>test</b></a></p>');
ok(view.$('#qunit-header a').get(1).innerHTML.match(/Backbone Speed Suite/)); strictEqual(view.$('a b').html(), 'test');
}); });
test("View: make", 3, function() { test("make", 3, function() {
var div = view.make('div', {id: 'test-div'}, "one two three"); var div = view.make('div', {id: 'test-div'}, "one two three");
equal(div.tagName.toLowerCase(), 'div'); equal(div.tagName.toLowerCase(), 'div');
equal(div.id, 'test-div'); equal(div.id, 'test-div');
equal($(div).text(), 'one two three'); equal($(div).text(), 'one two three');
}); });
test("View: make can take falsy values for content", 2, function() { test("make can take falsy values for content", 2, function() {
var div = view.make('div', {id: 'test-div'}, 0); var div = view.make('div', {id: 'test-div'}, 0);
equal($(div).text(), '0'); equal($(div).text(), '0');
@@ -41,101 +42,113 @@ $(document).ready(function() {
equal($(div).text(), ''); equal($(div).text(), '');
}); });
test("View: initialize", 1, function() { test("initialize", 1, function() {
var View = Backbone.View.extend({ var View = Backbone.View.extend({
initialize: function() { initialize: function() {
this.one = 1; this.one = 1;
} }
}); });
var view = new View;
equal(view.one, 1); strictEqual(new View().one, 1);
}); });
test("View: delegateEvents", 6, function() { test("delegateEvents", 6, function() {
var counter = 0; var counter1 = 0, counter2 = 0;
var counter2 = 0;
view.setElement(document.body); var view = new Backbone.View({el: '<p><a id="test"></a></p>'});
view.increment = function(){ counter++; }; view.increment = function(){ counter1++; };
view.$el.bind('click', function(){ counter2++; }); view.$el.on('click', function(){ counter2++; });
var events = {"click #qunit-banner": "increment"};
var events = {'click #test': 'increment'};
view.delegateEvents(events); view.delegateEvents(events);
$('#qunit-banner').trigger('click'); view.$('#test').trigger('click');
equal(counter, 1); equal(counter1, 1);
equal(counter2, 1); equal(counter2, 1);
$('#qunit-banner').trigger('click');
equal(counter, 2); view.$('#test').trigger('click');
equal(counter1, 2);
equal(counter2, 2); equal(counter2, 2);
view.delegateEvents(events); view.delegateEvents(events);
$('#qunit-banner').trigger('click'); view.$('#test').trigger('click');
equal(counter, 3); equal(counter1, 3);
equal(counter2, 3); equal(counter2, 3);
}); });
test("View: delegateEvents allows functions for callbacks", 3, function() { test("delegateEvents allows functions for callbacks", 3, function() {
var view = new Backbone.View({el: '<p></p>'});
view.counter = 0; view.counter = 0;
view.setElement("#qunit-banner");
var events = {"click": function() { this.counter++; }}; var events = {
click: function() {
this.counter++;
}
};
view.delegateEvents(events); view.delegateEvents(events);
$('#qunit-banner').trigger('click'); view.$el.trigger('click');
equal(view.counter, 1); equal(view.counter, 1);
$('#qunit-banner').trigger('click');
view.$el.trigger('click');
equal(view.counter, 2); equal(view.counter, 2);
view.delegateEvents(events); view.delegateEvents(events);
$('#qunit-banner').trigger('click'); view.$el.trigger('click');
equal(view.counter, 3); equal(view.counter, 3);
}); });
test("View: undelegateEvents", 6, function() { test("undelegateEvents", 6, function() {
var counter = 0; var counter1 = 0, counter2 = 0;
var counter2 = 0;
view.setElement(document.body); var view = new Backbone.View({el: '<p><a id="test"></a></p>'});
view.increment = function(){ counter++; }; view.increment = function(){ counter1++; };
$(view.el).unbind('click'); view.$el.on('click', function(){ counter2++; });
$(view.el).bind('click', function(){ counter2++; });
var events = {"click #qunit-userAgent": "increment"}; var events = {'click #test': 'increment'};
view.delegateEvents(events); view.delegateEvents(events);
$('#qunit-userAgent').trigger('click'); view.$('#test').trigger('click');
equal(counter, 1); equal(counter1, 1);
equal(counter2, 1); equal(counter2, 1);
view.undelegateEvents(); view.undelegateEvents();
$('#qunit-userAgent').trigger('click'); view.$('#test').trigger('click');
equal(counter, 1); equal(counter1, 1);
equal(counter2, 2); equal(counter2, 2);
view.delegateEvents(events); view.delegateEvents(events);
$('#qunit-userAgent').trigger('click'); view.$('#test').trigger('click');
equal(counter, 2); equal(counter1, 2);
equal(counter2, 3); equal(counter2, 3);
}); });
test("View: _ensureElement with DOM node el", 1, function() { test("_ensureElement with DOM node el", 1, function() {
var ViewClass = Backbone.View.extend({ var View = Backbone.View.extend({
el: document.body el: document.body
}); });
var view = new ViewClass;
equal(view.el, document.body); equal(new View().el, document.body);
}); });
test("View: _ensureElement with string el", 3, function() { test("_ensureElement with string el", 3, function() {
var ViewClass = Backbone.View.extend({ var View = Backbone.View.extend({
el: "body" el: "body"
}); });
var view = new ViewClass; strictEqual(new View().el, document.body);
strictEqual(view.el, document.body);
ViewClass = Backbone.View.extend({ View = Backbone.View.extend({
el: "#testElement > h1" el: "#testElement > h1"
}); });
view = new ViewClass; strictEqual(new View().el, $("#testElement > h1").get(0));
strictEqual(view.el, $("#testElement > h1").get(0));
ViewClass = Backbone.View.extend({ View = Backbone.View.extend({
el: "#nonexistent" el: "#nonexistent"
}); });
view = new ViewClass; ok(!new View().el);
ok(!view.el);
}); });
test("View: with className and id functions", 2, function() { test("with className and id functions", 2, function() {
var View = Backbone.View.extend({ var View = Backbone.View.extend({
className: function() { className: function() {
return 'className'; return 'className';
@@ -144,53 +157,63 @@ $(document).ready(function() {
return 'id'; return 'id';
} }
}); });
var view = new View();
strictEqual(view.el.className, 'className'); strictEqual(new View().el.className, 'className');
strictEqual(view.el.id, 'id'); strictEqual(new View().el.id, 'id');
}); });
test("View: with attributes", 2, function() { test("with attributes", 2, function() {
var view = new Backbone.View({attributes : {'class': 'one', id: 'two'}}); var View = Backbone.View.extend({
equal(view.el.className, 'one'); attributes: {
equal(view.el.id, 'two'); id: 'id',
'class': 'class'
}
});
strictEqual(new View().el.className, 'class');
strictEqual(new View().el.id, 'id');
}); });
test("View: with attributes as a function", 1, function() { test("with attributes as a function", 1, function() {
var viewClass = Backbone.View.extend({ var View = Backbone.View.extend({
attributes: function() { attributes: function() {
return {'class': 'dynamic'}; return {'class': 'dynamic'};
} }
}); });
equal((new viewClass).el.className, 'dynamic');
strictEqual(new View().el.className, 'dynamic');
}); });
test("View: multiple views per element", 3, function() { test("multiple views per element", 3, function() {
var count = 0, ViewClass = Backbone.View.extend({ var count = 0;
el: $("body"), var $el = $('<p></p>');
var View = Backbone.View.extend({
el: $el,
events: { events: {
"click": "click" click: function() {
}, count++;
click: function() { }
count++;
} }
}); });
var view1 = new ViewClass; var view1 = new View;
$("body").trigger("click"); $el.trigger("click");
equal(1, count); equal(1, count);
var view2 = new ViewClass; var view2 = new View;
$("body").trigger("click"); $el.trigger("click");
equal(3, count); equal(3, count);
view1.delegateEvents(); view1.delegateEvents();
$("body").trigger("click"); $el.trigger("click");
equal(5, count); equal(5, count);
}); });
test("View: custom events, with namespaces", 2, function() { test("custom events, with namespaces", 2, function() {
var count = 0; var count = 0;
var ViewClass = Backbone.View.extend({
var View = Backbone.View.extend({
el: $('body'), el: $('body'),
events: function() { events: function() {
return {"fake$event.namespaced": "run"}; return {"fake$event.namespaced": "run"};
@@ -200,9 +223,10 @@ $(document).ready(function() {
} }
}); });
var view = new ViewClass; var view = new View;
$('body').trigger('fake$event').trigger('fake$event'); $('body').trigger('fake$event').trigger('fake$event');
equal(count, 2); equal(count, 2);
$('body').unbind('.namespaced'); $('body').unbind('.namespaced');
$('body').trigger('fake$event'); $('body').trigger('fake$event');
equal(count, 2); equal(count, 2);
@@ -210,49 +234,71 @@ $(document).ready(function() {
test("#1048 - setElement uses provided object.", 2, function() { test("#1048 - setElement uses provided object.", 2, function() {
var $el = $('body'); var $el = $('body');
var view = new Backbone.View({el: $el}); var view = new Backbone.View({el: $el});
ok(view.$el === $el); ok(view.$el === $el);
view.setElement($el = $($el)); view.setElement($el = $($el));
ok(view.$el === $el); ok(view.$el === $el);
}); });
test("#986 - Undelegate before changing element.", 1, function() { test("#986 - Undelegate before changing element.", 1, function() {
var a = $('<button></button>'); var button1 = $('<button></button>');
var b = $('<button></button>'); var button2 = $('<button></button>');
var View = Backbone.View.extend({ var View = Backbone.View.extend({
events: {click: function(e) { ok(view.el === e.target); }} events: {
click: function(e) {
ok(view.el === e.target);
}
}
}); });
var view = new View({el: a});
view.setElement(b); var view = new View({el: button1});
a.trigger('click'); view.setElement(button2);
b.trigger('click');
button1.trigger('click');
button2.trigger('click');
}); });
test("#1172 - Clone attributes object", 2, function() { test("#1172 - Clone attributes object", 2, function() {
var View = Backbone.View.extend({attributes: {foo: 'bar'}}); var View = Backbone.View.extend({
var v1 = new View({id: 'foo'}); attributes: {foo: 'bar'}
strictEqual(v1.el.id, 'foo'); });
var v2 = new View();
ok(!v2.el.id); var view1 = new View({id: 'foo'});
strictEqual(view1.el.id, 'foo');
var view2 = new View();
ok(!view2.el.id);
}); });
test("#1228 - tagName can be provided as a function", 1, function() { test("#1228 - tagName can be provided as a function", 1, function() {
var View = Backbone.View.extend({tagName: function(){ return 'p'; }}); var View = Backbone.View.extend({
tagName: function() {
return 'p';
}
});
ok(new View().$el.is('p')); ok(new View().$el.is('p'));
}); });
test("dispose", 0, function() { test("dispose", 0, function() {
var View = Backbone.View.extend({ var View = Backbone.View.extend({
events: {click: function(){ ok(false); }}, events: {
click: function() { ok(false); }
},
initialize: function() { initialize: function() {
this.model.on('all x', function(){ ok(false); }, this); this.model.on('all x', function(){ ok(false); }, this);
this.collection.on('all x', function(){ ok(false); }, this); this.collection.on('all x', function(){ ok(false); }, this);
} }
}); });
var view = new View({ var view = new View({
model: new Backbone.Model, model: new Backbone.Model,
collection: new Backbone.Collection collection: new Backbone.Collection
}); });
view.dispose(); view.dispose();
view.model.trigger('x'); view.model.trigger('x');
view.collection.trigger('x'); view.collection.trigger('x');
@@ -261,6 +307,7 @@ $(document).ready(function() {
test("view#remove calls dispose.", 1, function() { test("view#remove calls dispose.", 1, function() {
var view = new Backbone.View(); var view = new Backbone.View();
view.dispose = function() { ok(true); }; view.dispose = function() { ok(true); };
view.remove(); view.remove();
}); });

204
vendor/docdown/src/DocDown/Alias.php vendored Normal file
View File

@@ -0,0 +1,204 @@
<?php
/**
* A class to represent a JSDoc entry alias.
*/
class Alias {
/**
* The alias owner.
*
* @memberOf Alias
* @type Object
*/
public $owner;
/*--------------------------------------------------------------------------*/
/**
* The Alias constructor.
*
* @constructor
* @param {String} $name The alias name.
* @param {Object} $owner The alias owner.
*/
public function __construct($name, $owner) {
$this->owner = $owner;
$this->_name = $name;
$this->_call = $owner->getCall();
$this->_desc = $owner->getDesc();
$this->_example = $owner->getExample();
$this->_lineNumber = $owner->getLineNumber();
$this->_members = $owner->getMembers();
$this->_params = $owner->getParams();
$this->_returns = $owner->getReturns();
$this->_type = $owner->getType();
$this->_isCtor = $owner->isCtor();
$this->_isPlugin = $owner->isPlugin();
$this->_isPrivate = $owner->isPrivate();
$this->_isStatic = $owner->isStatic();
}
/*--------------------------------------------------------------------------*/
/**
* Extracts the entry's `alias` objects.
*
* @memberOf Alias
* @param {Number} $index The index of the array value to return.
* @returns {Array|String} The entry's `alias` objects.
*/
public function getAliases( $index = null ) {
$result = array();
return $index !== null
? @$result[$index]
: $result;
}
/**
* Extracts the function call from the owner entry.
*
* @memberOf Alias
* @returns {String} The function call.
*/
public function getCall() {
return $this->_call;
}
/**
* Extracts the owner entry's description.
*
* @memberOf Alias
* @returns {String} The owner entry's description.
*/
public function getDesc() {
return $this->_desc;
}
/**
* Extracts the owner entry's `example` data.
*
* @memberOf Alias
* @returns {String} The owner entry's `example` data.
*/
public function getExample() {
return $this->_example;
}
/**
* Resolves the owner entry's line number.
*
* @memberOf Alias
* @returns {Number} The owner entry's line number.
*/
public function getLineNumber() {
return $this->_lineNumber;
}
/**
* Extracts the owner entry's `member` data.
*
* @memberOf Alias
* @param {Number} $index The index of the array value to return.
* @returns {Array|String} The owner entry's `member` data.
*/
public function getMembers( $index = null ) {
return $index !== null
? @$this->_members[$index]
: $this->_members;
}
/**
* Extracts the owner entry's `name` data.
*
* @memberOf Alias
* @returns {String} The owner entry's `name` data.
*/
public function getName() {
return $this->_name;
}
/**
* Extracts the owner entry's `param` data.
*
* @memberOf Alias
* @param {Number} $index The index of the array value to return.
* @returns {Array} The owner entry's `param` data.
*/
public function getParams( $index = null ) {
return $index !== null
? @$this->_params[$index]
: $this->_params;
}
/**
* Extracts the owner entry's `returns` data.
*
* @memberOf Alias
* @returns {String} The owner entry's `returns` data.
*/
public function getReturns() {
return $this->_returns;
}
/**
* Extracts the owner entry's `type` data.
*
* @memberOf Alias
* @returns {String} The owner entry's `type` data.
*/
public function getType() {
return $this->_type;
}
/**
* Checks if the entry is an alias.
*
* @memberOf Alias
* @returns {Boolean} Returns `true`.
*/
public function isAlias() {
return true;
}
/**
* Checks if the owner entry is a constructor.
*
* @memberOf Alias
* @returns {Boolean} Returns `true` if a constructor, else `false`.
*/
public function isCtor() {
return $this->_isCtor;
}
/**
* Checks if the owner entry *is* assigned to a prototype.
*
* @memberOf Alias
* @returns {Boolean} Returns `true` if assigned to a prototype, else `false`.
*/
public function isPlugin() {
return $this->_isPlugin;
}
/**
* Checks if the owner entry is private.
*
* @memberOf Alias
* @returns {Boolean} Returns `true` if private, else `false`.
*/
public function isPrivate() {
return $this->_isPrivate;
}
/**
* Checks if the owner entry is *not* assigned to a prototype.
*
* @memberOf Alias
* @returns {Boolean} Returns `true` if not assigned to a prototype, else `false`.
*/
public function isStatic() {
return $this->_isStatic;
}
}
?>

View File

@@ -1,5 +1,7 @@
<?php <?php
require(dirname(__FILE__) . "/Alias.php");
/** /**
* A class to simplify parsing a single JSDoc entry. * A class to simplify parsing a single JSDoc entry.
*/ */
@@ -37,7 +39,7 @@ class Entry {
* @constructor * @constructor
* @param {String} $entry The documentation entry to analyse. * @param {String} $entry The documentation entry to analyse.
* @param {String} $source The source code. * @param {String} $source The source code.
* @param {String} $lang The language highlighter used for code examples. * @param {String} [$lang ='js'] The language highlighter used for code examples.
*/ */
public function __construct( $entry, $source, $lang = 'js' ) { public function __construct( $entry, $source, $lang = 'js' ) {
$this->entry = $entry; $this->entry = $entry;
@@ -70,16 +72,46 @@ class Entry {
* @returns {Boolean} Returns `true` if the entry is a function reference, else `false`. * @returns {Boolean} Returns `true` if the entry is a function reference, else `false`.
*/ */
private function isFunction() { private function isFunction() {
return !!( if (!isset($this->_isFunction)) {
$this->isCtor() || $this->_isFunction = !!(
count($this->getParams()) || $this->isCtor() ||
count($this->getReturns()) || count($this->getParams()) ||
preg_match('/\*\s*@function\b/', $this->entry) count($this->getReturns()) ||
); preg_match('/\* *@function\b/', $this->entry)
);
}
return $this->_isFunction;
} }
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
/**
* Extracts the entry's `alias` objects.
*
* @memberOf Entry
* @param {Number} $index The index of the array value to return.
* @returns {Array|String} The entry's `alias` objects.
*/
public function getAliases( $index = null ) {
if (!isset($this->_aliases)) {
preg_match('#\* *@alias\s+([^\n]+)#', $this->entry, $result);
if (count($result)) {
$result = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1]));
$result = preg_split('/,\s*/', $result);
natsort($result);
foreach ($result as $resultIndex => $value) {
$result[$resultIndex] = new Alias($value, $this);
}
}
$this->_aliases = $result;
}
return $index !== null
? @$this->_aliases[$index]
: $this->_aliases;
}
/** /**
* Extracts the function call from the entry. * Extracts the function call from the entry.
* *
@@ -87,13 +119,17 @@ class Entry {
* @returns {String} The function call. * @returns {String} The function call.
*/ */
public function getCall() { public function getCall() {
if (isset($this->_call)) {
return $this->_call;
}
preg_match('#\*/\s*(?:function ([^(]*)|(.*?)(?=[:=,]|return\b))#', $this->entry, $result); preg_match('#\*/\s*(?:function ([^(]*)|(.*?)(?=[:=,]|return\b))#', $this->entry, $result);
if ($result = array_pop($result)) { if ($result = array_pop($result)) {
$result = array_pop(explode('var ', trim(trim(array_pop(explode('.', $result))), "'"))); $result = array_pop(explode('var ', trim(trim(array_pop(explode('.', $result))), "'")));
} }
// resolve name // resolve name
// avoid $this->getName() because it calls $this->getCall() // avoid $this->getName() because it calls $this->getCall()
preg_match('#\*\s*@name\s+([^\n]+)#', $this->entry, $name); preg_match('#\* *@name\s+([^\n]+)#', $this->entry, $name);
if (count($name)) { if (count($name)) {
$name = trim($name[1]); $name = trim($name[1]);
} else { } else {
@@ -111,178 +147,242 @@ class Entry {
$result = $name .'('. implode(array_slice($result, 1), ', ') .')'; $result = $name .'('. implode(array_slice($result, 1), ', ') .')';
$result = str_replace(', [', ' [, ', str_replace('], [', ', ', $result)); $result = str_replace(', [', ' [, ', str_replace('], [', ', ', $result));
} }
return $result ? $result : $name;
$this->_call = $result ? $result : $name;
return $this->_call;
} }
/** /**
* Extracts the entry description. * Extracts the entry's description.
* *
* @memberOf Entry * @memberOf Entry
* @returns {String} The entry description. * @returns {String} The entry's description.
*/ */
public function getDesc() { public function getDesc() {
if (isset($this->_desc)) {
return $this->_desc;
}
preg_match('#/\*\*(?:\s*\*)?([\s\S]*?)(?=\*\s\@[a-z]|\*/)#', $this->entry, $result); preg_match('#/\*\*(?:\s*\*)?([\s\S]*?)(?=\*\s\@[a-z]|\*/)#', $this->entry, $result);
if (count($result)) { if (count($result)) {
$type = $this->getType(); $type = $this->getType();
$result = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1])); $result = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1]));
$result = ($type == 'Function' ? '' : '(' . str_replace('|', ', ', trim($type, '{}')) . '): ') . $result; $result = ($type == 'Function' ? '' : '(' . str_replace('|', ', ', trim($type, '{}')) . '): ') . $result;
} }
$this->_desc = $result;
return $result; return $result;
} }
/** /**
* Extracts the entry `example` data. * Extracts the entry's `example` data.
* *
* @memberOf Entry * @memberOf Entry
* @returns {String} The entry `example` data. * @returns {String} The entry's `example` data.
*/ */
public function getExample() { public function getExample() {
preg_match('#\*\s*@example\s+([\s\S]*?)(?=\*\s\@[a-z]|\*/)#', $this->entry, $result); if (isset($this->_example)) {
return $this->_example;
}
preg_match('#\* *@example\s+([\s\S]*?)(?=\*\s\@[a-z]|\*/)#', $this->entry, $result);
if (count($result)) { if (count($result)) {
$result = trim(preg_replace('/(?:^|\n)\s*\* ?/', "\n", $result[1])); $result = trim(preg_replace('/(?:^|\n)\s*\* ?/', "\n", $result[1]));
$result = '```' . $this->lang . "\n" . $result . "\n```"; $result = '```' . $this->lang . "\n" . $result . "\n```";
} }
$this->_example = $result;
return $result; return $result;
} }
/** /**
* Resolves the line number of the entry. * Resolves the entry's line number.
* *
* @memberOf Entry * @memberOf Entry
* @returns {Number} The line number. * @returns {Number} The entry's line number.
*/ */
public function getLineNumber() { public function getLineNumber() {
preg_match_all('/\n/', substr($this->source, 0, strrpos($this->source, $this->entry) + strlen($this->entry)), $lines); if (!isset($this->_lineNumber)) {
return count(array_pop($lines)) + 1; preg_match_all('/\n/', substr($this->source, 0, strrpos($this->source, $this->entry) + strlen($this->entry)), $lines);
$this->_lineNumber = count(array_pop($lines)) + 1;
}
return $this->_lineNumber;
} }
/** /**
* Extracts the entry `member` data. * Extracts the entry's `member` data.
* *
* @memberOf Entry * @memberOf Entry
* @param {Number} $index The index of the array value to return. * @param {Number} $index The index of the array value to return.
* @returns {Array|String} The entry `member` data. * @returns {Array|String} The entry's `member` data.
*/ */
public function getMembers( $index = null ) { public function getMembers( $index = null ) {
preg_match('#\*\s*@member(?:Of)?\s+([^\n]+)#', $this->entry, $result); if (!isset($this->_members)) {
if (count($result)) { preg_match('#\* *@member(?:Of)?\s+([^\n]+)#', $this->entry, $result);
$result = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1])); if (count($result)) {
$result = preg_split('/,\s*/', $result); $result = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1]));
$result = preg_split('/,\s*/', $result);
natsort($result);
}
$this->_members = $result;
} }
return $index !== null ? @$result[$index] : $result; return $index !== null
? @$this->_members[$index]
: $this->_members;
} }
/** /**
* Extracts the entry `name` data. * Extracts the entry's `name` data.
* *
* @memberOf Entry * @memberOf Entry
* @returns {String} The entry `name` data. * @returns {String} The entry's `name` data.
*/ */
public function getName() { public function getName() {
preg_match('#\*\s*@name\s+([^\n]+)#', $this->entry, $result); if (isset($this->_name)) {
return $this->_name;
}
preg_match('#\* *@name\s+([^\n]+)#', $this->entry, $result);
if (count($result)) { if (count($result)) {
$result = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1])); $result = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1]));
} else { } else {
$result = array_shift(explode('(', $this->getCall())); $result = array_shift(explode('(', $this->getCall()));
} }
$this->_name = $result;
return $result; return $result;
} }
/** /**
* Extracts the entry `param` data. * Extracts the entry's `param` data.
* *
* @memberOf Entry * @memberOf Entry
* @param {Number} $index The index of the array value to return. * @param {Number} $index The index of the array value to return.
* @returns {Array} The entry `param` data. * @returns {Array} The entry's `param` data.
*/ */
public function getParams( $index = null ) { public function getParams( $index = null ) {
preg_match_all('#\*\s*@param\s+\{([^}]+)\}\s+(\[.+\]|[$\w|]+(?:\[.+\])?)\s+([\s\S]*?)(?=\*\s\@[a-z]|\*/)#i', $this->entry, $result); if (!isset($this->_params)) {
if (count($result = array_filter(array_slice($result, 1)))) { preg_match_all('#\* *@param\s+\{([^}]+)\}\s+(\[.+\]|[$\w|]+(?:\[.+\])?)\s+([\s\S]*?)(?=\*\s\@[a-z]|\*/)#i', $this->entry, $result);
// repurpose array if (count($result = array_filter(array_slice($result, 1)))) {
foreach ($result as $param) { // repurpose array
foreach ($param as $key => $value) { foreach ($result as $param) {
if (!is_array($result[0][$key])) { foreach ($param as $key => $value) {
$result[0][$key] = array(); if (!is_array($result[0][$key])) {
$result[0][$key] = array();
}
$result[0][$key][] = trim(preg_replace('/(?:^|\n)\s*\* */', ' ', $value));
} }
$result[0][$key][] = trim(preg_replace('/(?:^|\n)\s*\* */', ' ', $value));
} }
$result = $result[0];
} }
$result = $result[0]; $this->_params = $result;
} }
return $index !== null ? @$result[$index] : $result; return $index !== null
? @$this->_params[$index]
: $this->_params;
} }
/** /**
* Extracts the entry `returns` data. * Extracts the entry's `returns` data.
* *
* @memberOf Entry * @memberOf Entry
* @returns {String} The entry `returns` data. * @returns {String} The entry's `returns` data.
*/ */
public function getReturns() { public function getReturns() {
preg_match('#\*\s*@returns\s+\{([^}]+)\}\s+([\s\S]*?)(?=\*\s\@[a-z]|\*/)#', $this->entry, $result); if (isset($this->_returns)) {
return $this->_returns;
}
preg_match('#\* *@returns\s+\{([^}]+)\}\s+([\s\S]*?)(?=\*\s\@[a-z]|\*/)#', $this->entry, $result);
if (count($result)) { if (count($result)) {
$result = array_map('trim', array_slice($result, 1)); $result = array_map('trim', array_slice($result, 1));
$result[0] = str_replace('|', ', ', $result[0]); $result[0] = str_replace('|', ', ', $result[0]);
$result[1] = preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1]); $result[1] = preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1]);
} }
$this->_returns = $result;
return $result; return $result;
} }
/** /**
* Extracts the entry `type` data. * Extracts the entry's `type` data.
* *
* @memberOf Entry * @memberOf Entry
* @returns {String} The entry `type` data. * @returns {String} The entry's `type` data.
*/ */
public function getType() { public function getType() {
preg_match('#\*\s*@type\s+([^\n]+)#', $this->entry, $result); if (isset($this->_type)) {
return $this->_type;
}
preg_match('#\* *@type\s+([^\n]+)#', $this->entry, $result);
if (count($result)) { if (count($result)) {
$result = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1])); $result = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1]));
} else { } else {
$result = $this->isFunction() ? 'Function' : 'Unknown'; $result = $this->isFunction() ? 'Function' : 'Unknown';
} }
$this->_type = $result;
return $result; return $result;
} }
/** /**
* Checks if an entry is a constructor. * Checks if the entry is an alias.
* *
* @memberOf Entry * @memberOf Entry
* @returns {Boolean} Returns true if a constructor, else false. * @returns {Boolean} Returns `false`.
*/
public function isAlias() {
return false;
}
/**
* Checks if the entry is a constructor.
*
* @memberOf Entry
* @returns {Boolean} Returns `true` if a constructor, else `false`.
*/ */
public function isCtor() { public function isCtor() {
return !!preg_match('/\*\s*@constructor\b/', $this->entry); if (!isset($this->_isCtor)) {
$this->_isCtor = !!preg_match('/\* *@constructor\b/', $this->entry);
}
return $this->_isCtor;
} }
/** /**
* Checks if an entry *is* assigned to a prototype. * Checks if the entry *is* assigned to a prototype.
* *
* @memberOf Entry * @memberOf Entry
* @returns {Boolean} Returns true if assigned to a prototype, else false. * @returns {Boolean} Returns `true` if assigned to a prototype, else `false`.
*/ */
public function isPlugin() { public function isPlugin() {
return !$this->isCtor() && !$this->isPrivate() && !$this->isStatic(); if (!isset($this->_isPlugin)) {
$this->_isPlugin = !$this->isCtor() && !$this->isPrivate() && !$this->isStatic();
}
return $this->_isPlugin;
} }
/** /**
* Checks if an entry is private. * Checks if the entry is private.
* *
* @memberOf Entry * @memberOf Entry
* @returns {Boolean} Returns true if private, else false. * @returns {Boolean} Returns `true` if private, else `false`.
*/ */
public function isPrivate() { public function isPrivate() {
return !!preg_match('/\*\s*@private\b/', $this->entry) || strrpos($this->entry, '@') === false; if (!isset($this->_isPrivate)) {
$this->_isPrivate = !!preg_match('/\* *@private\b/', $this->entry) || !preg_match('/\* *@[a-z]+\b/', $this->entry);
}
return $this->_isPrivate;
} }
/** /**
* Checks if an entry is *not* assigned to a prototype. * Checks if the entry is *not* assigned to a prototype.
* *
* @memberOf Entry * @memberOf Entry
* @returns {Boolean} Returns true if not assigned to a prototype, else false. * @returns {Boolean} Returns `true` if not assigned to a prototype, else `false`.
*/ */
public function isStatic() { public function isStatic() {
if (isset($this->_isStatic)) {
return $this->_isStatic;
}
$public = !$this->isPrivate(); $public = !$this->isPrivate();
$result = $public && !!preg_match('/\*\s*@static\b/', $this->entry); $result = $public && !!preg_match('/\* *@static\b/', $this->entry);
// set in cases where it isn't explicitly stated // set in cases where it isn't explicitly stated
if ($public && !$result) { if ($public && !$result) {
@@ -298,6 +398,7 @@ class Entry {
$result = true; $result = true;
} }
} }
$this->_isStatic = $result;
return $result; return $result;
} }
} }

View File

@@ -212,31 +212,50 @@ class Generator {
// initialize $api array // initialize $api array
foreach ($this->entries as $entry) { foreach ($this->entries as $entry) {
// skip invalid or private entries
$name = $entry->getName();
if (!$name || $entry->isPrivate()) {
continue;
}
if (!$entry->isPrivate()) { $members = $entry->getMembers();
$name = $entry->getName(); $members = count($members) ? $members : array('');
$members = $entry->getMembers();
$members = count($members) ? $members : array('');
foreach ($members as $member) { foreach ($members as $member) {
// create api category arrays // create api category arrays
if (!isset($api[$member]) && $member) { if (!isset($api[$member]) && $member) {
$api[$member] = new Entry('', '', $entry->lang); // create temporary entry to be replaced later
$api[$member]->static = array(); $api[$member] = new Entry('', '', $entry->lang);
$api[$member]->plugin = array(); $api[$member]->static = array();
$api[$member]->plugin = array();
}
// append entry to api category
if (!$member || $entry->isCtor() || ($entry->getType() == 'Object' &&
!preg_match('/[=:]\s*(?:null|undefined)\s*[,;]?$/', $entry->entry))) {
// assign the real entry, replacing the temporary entry if it exist
$member = ($member ? $member . ($entry->isPlugin() ? '#' : '.') : '') . $name;
$entry->static = @$api[$member] ? $api[$member]->static : array();
$entry->plugin = @$api[$member] ? $api[$member]->plugin : array();
$api[$member] = $entry;
foreach ($entry->getAliases() as $alias) {
$api[$member] = $alias;
$alias->static = array();
$alias->plugin = array();
} }
// append entry to api category }
if (!$member || $entry->isCtor() || ($entry->getType() == 'Object' && else if ($entry->isStatic()) {
!preg_match('/[=:]\s*null\s*[,;]?$/', $entry->entry))) { $api[$member]->static[] = $entry;
$member = ($member ? $member . ($entry->isPlugin() ? '#' : '.') : '') . $name; foreach ($entry->getAliases() as $alias) {
$entry->static = @$api[$member] ? $api[$member]->static : array(); $api[$member]->static[] = $alias;
$entry->plugin = @$api[$member] ? $api[$member]->plugin : array();
$api[$member] = $entry;
} }
else if ($entry->isStatic()) { }
$api[$member]->static[] = $entry; else if (!$entry->isCtor()) {
} else if (!$entry->isCtor()) { $api[$member]->plugin[] = $entry;
$api[$member]->plugin[] = $entry; foreach ($entry->getAliases() as $alias) {
$api[$member]->plugin[] = $alias;
} }
} }
} }
@@ -346,6 +365,11 @@ class Generator {
$result[] = $openTag; $result[] = $openTag;
foreach ($api as $entry) { foreach ($api as $entry) {
// skip aliases
if ($entry->isAlias()) {
continue;
}
// add root entry // add root entry
$member = $entry->member . $entry->getName(); $member = $entry->member . $entry->getName();
$compiling = $compiling ? ($result[] = $closeTag) : true; $compiling = $compiling ? ($result[] = $closeTag) : true;
@@ -370,6 +394,11 @@ class Generator {
// body // body
foreach ($subentries as $subentry) { foreach ($subentries as $subentry) {
// skip aliases
if ($subentry->isAlias()) {
continue;
}
// description // description
array_push( array_push(
$result, $result,
@@ -377,6 +406,14 @@ class Generator {
Generator::interpolate("### <a id=\"#{hash}\"></a>`#{member}#{separator}#{call}`\n<a href=\"##{hash}\">#</a> [&#x24C8;](#{href} \"View in source\") [&#x24C9;][1]\n\n#{desc}", $subentry) Generator::interpolate("### <a id=\"#{hash}\"></a>`#{member}#{separator}#{call}`\n<a href=\"##{hash}\">#</a> [&#x24C8;](#{href} \"View in source\") [&#x24C9;][1]\n\n#{desc}", $subentry)
); );
// @alias
if (count($aliases = $subentry->getAliases())) {
array_push($result, '', '#### Aliases');
foreach ($aliases as $index => $alias) {
$aliases[$index] = $alias->getName();
}
$result[] = '*' . implode(', ', $aliases) . '*';
}
// @param // @param
if (count($params = $subentry->getParams())) { if (count($params = $subentry->getParams())) {
array_push($result, '', '#### Arguments'); array_push($result, '', '#### Arguments');

View File

@@ -1,4 +1,4 @@
[QUnit](http://docs.jquery.com/QUnit) - A JavaScript Unit Testing framework. [QUnit](http://qunitjs.com) - A JavaScript Unit Testing framework.
================================ ================================
QUnit is a powerful, easy-to-use, JavaScript test suite. It's used by the jQuery QUnit is a powerful, easy-to-use, JavaScript test suite. It's used by the jQuery
@@ -35,7 +35,8 @@ the change, run `grunt` to lint and test it, then commit, push and create a pull
Include some background for the change in the commit message and `Fixes #nnn`, referring Include some background for the change in the commit message and `Fixes #nnn`, referring
to the issue number you're addressing. to the issue number you're addressing.
To run `grunt`, you need `node` and `npm`, then `npm install grunt -g`. To run `grunt`, you need `node` and `npm`, then `npm install grunt -g`. That gives you a global
grunt binary. For additional grunt tasks, also run `npm install`.
Releases Releases
-------- --------
@@ -47,3 +48,12 @@ tag, update them again to the next version, commit and push commits and tags
Put the 'v' in front of the tag, e.g. `v1.8.0`. Clean up the changelog, removing merge commits Put the 'v' in front of the tag, e.g. `v1.8.0`. Clean up the changelog, removing merge commits
or whitespace cleanups. or whitespace cleanups.
To upload to code.jquery.com (replace $version accordingly):
scp -q qunit/qunit.js jqadmin@code.origin.jquery.com:/var/www/html/code.jquery.com/qunit/qunit-$version.js
scp -q qunit/qunit.css jqadmin@code.origin.jquery.com:/var/www/html/code.jquery.com/qunit/qunit-$version.css
Then update /var/www/html/code.jquery.com/index.html and purge it with:
curl -s http://code.origin.jquery.com/?reload

View File

@@ -1,11 +1,11 @@
/** /**
* QUnit v1.9.0 - A JavaScript Unit Testing Framework * QUnit v1.10.0 - A JavaScript Unit Testing Framework
* *
* http://docs.jquery.com/QUnit * http://qunitjs.com
* *
* Copyright (c) 2012 John Resig, Jörn Zaefferer * Copyright 2012 jQuery Foundation and other contributors
* Dual licensed under the MIT (MIT-LICENSE.txt) * Released under the MIT license.
* or GPL (GPL-LICENSE.txt) licenses. * http://jquery.org/license
*/ */
/** Font Family and Sizes */ /** Font Family and Sizes */
@@ -20,7 +20,7 @@
/** Resets */ /** Resets */
#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { #qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
@@ -67,6 +67,7 @@
padding: 0.5em 0 0.5em 2em; padding: 0.5em 0 0.5em 2em;
color: #5E740B; color: #5E740B;
background-color: #eee; background-color: #eee;
overflow: hidden;
} }
#qunit-userAgent { #qunit-userAgent {
@@ -76,6 +77,9 @@
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
} }
#qunit-modulefilter-container {
float: right;
}
/** Tests: Pass/Fail */ /** Tests: Pass/Fail */

View File

@@ -1,11 +1,11 @@
/** /**
* QUnit v1.9.0 - A JavaScript Unit Testing Framework * QUnit v1.10.0 - A JavaScript Unit Testing Framework
* *
* http://docs.jquery.com/QUnit * http://qunitjs.com
* *
* Copyright (c) 2012 John Resig, Jörn Zaefferer * Copyright 2012 jQuery Foundation and other contributors
* Dual licensed under the MIT (MIT-LICENSE.txt) * Released under the MIT license.
* or GPL (GPL-LICENSE.txt) licenses. * http://jquery.org/license
*/ */
(function( window ) { (function( window ) {
@@ -17,6 +17,8 @@ var QUnit,
fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""), fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""),
toString = Object.prototype.toString, toString = Object.prototype.toString,
hasOwn = Object.prototype.hasOwnProperty, hasOwn = Object.prototype.hasOwnProperty,
// Keep a local reference to Date (GH-283)
Date = window.Date,
defined = { defined = {
setTimeout: typeof window.setTimeout !== "undefined", setTimeout: typeof window.setTimeout !== "undefined",
sessionStorage: (function() { sessionStorage: (function() {
@@ -304,7 +306,8 @@ QUnit = {
// call on start of module test to prepend name to all tests // call on start of module test to prepend name to all tests
module: function( name, testEnvironment ) { module: function( name, testEnvironment ) {
config.currentModule = name; config.currentModule = name;
config.currentModuleTestEnviroment = testEnvironment; config.currentModuleTestEnvironment = testEnvironment;
config.modules[name] = true;
}, },
asyncTest: function( testName, expected, callback ) { asyncTest: function( testName, expected, callback ) {
@@ -336,7 +339,7 @@ QUnit = {
async: async, async: async,
callback: callback, callback: callback,
module: config.currentModule, module: config.currentModule,
moduleTestEnvironment: config.currentModuleTestEnviroment, moduleTestEnvironment: config.currentModuleTestEnvironment,
stack: sourceFromStacktrace( 2 ) stack: sourceFromStacktrace( 2 )
}); });
@@ -349,7 +352,11 @@ QUnit = {
// Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. // Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
expect: function( asserts ) { expect: function( asserts ) {
config.current.expected = asserts; if (arguments.length === 1) {
config.current.expected = asserts;
} else {
return config.current.expected;
}
}, },
start: function( count ) { start: function( count ) {
@@ -415,6 +422,8 @@ QUnit.assert = {
var source, var source,
details = { details = {
module: config.current.module,
name: config.current.testName,
result: result, result: result,
message: msg message: msg
}; };
@@ -600,6 +609,9 @@ config = {
} }
], ],
// Set of all modules.
modules: {},
// logging callback queues // logging callback queues
begin: [], begin: [],
done: [], done: [],
@@ -710,17 +722,10 @@ extend( QUnit, {
}, },
// Resets the test setup. Useful for tests that modify the DOM. // Resets the test setup. Useful for tests that modify the DOM.
// If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
reset: function() { reset: function() {
var fixture; var fixture = id( "qunit-fixture" );
if ( fixture ) {
if ( window.jQuery ) { fixture.innerHTML = config.fixture;
jQuery( "#qunit-fixture" ).html( config.fixture );
} else {
fixture = id( "qunit-fixture" );
if ( fixture ) {
fixture.innerHTML = config.fixture;
}
} }
}, },
@@ -781,6 +786,8 @@ extend( QUnit, {
var output, source, var output, source,
details = { details = {
module: config.current.module,
name: config.current.testName,
result: result, result: result,
message: message, message: message,
actual: actual, actual: actual,
@@ -826,6 +833,8 @@ extend( QUnit, {
var output, var output,
details = { details = {
module: config.current.module,
name: config.current.testName,
result: false, result: false,
message: message message: message
}; };
@@ -916,7 +925,9 @@ QUnit.load = function() {
runLoggingCallbacks( "begin", QUnit, {} ); runLoggingCallbacks( "begin", QUnit, {} );
// Initialize the config, saving the execution queue // Initialize the config, saving the execution queue
var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, urlConfigCheckboxes, var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, urlConfigCheckboxes, moduleFilter,
numModules = 0,
moduleFilterHtml = "",
urlConfigHtml = "", urlConfigHtml = "",
oldconfig = extend( {}, config ); oldconfig = extend( {}, config );
@@ -940,6 +951,15 @@ QUnit.load = function() {
urlConfigHtml += "<input id='qunit-urlconfig-" + val.id + "' name='" + val.id + "' type='checkbox'" + ( config[ val.id ] ? " checked='checked'" : "" ) + " title='" + val.tooltip + "'><label for='qunit-urlconfig-" + val.id + "' title='" + val.tooltip + "'>" + val.label + "</label>"; urlConfigHtml += "<input id='qunit-urlconfig-" + val.id + "' name='" + val.id + "' type='checkbox'" + ( config[ val.id ] ? " checked='checked'" : "" ) + " title='" + val.tooltip + "'><label for='qunit-urlconfig-" + val.id + "' title='" + val.tooltip + "'>" + val.label + "</label>";
} }
moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label><select id='qunit-modulefilter' name='modulefilter'><option value='' " + ( config.module === undefined ? "selected" : "" ) + ">< All Modules ></option>";
for ( i in config.modules ) {
if ( config.modules.hasOwnProperty( i ) ) {
numModules += 1;
moduleFilterHtml += "<option value='" + encodeURIComponent(i) + "' " + ( config.module === i ? "selected" : "" ) + ">" + i + "</option>";
}
}
moduleFilterHtml += "</select>";
// `userAgent` initialized at top of scope // `userAgent` initialized at top of scope
userAgent = id( "qunit-userAgent" ); userAgent = id( "qunit-userAgent" );
if ( userAgent ) { if ( userAgent ) {
@@ -1002,6 +1022,19 @@ QUnit.load = function() {
window.location = QUnit.url( params ); window.location = QUnit.url( params );
}); });
toolbar.appendChild( urlConfigCheckboxes ); toolbar.appendChild( urlConfigCheckboxes );
if (numModules > 1) {
moduleFilter = document.createElement( 'span' );
moduleFilter.setAttribute( 'id', 'qunit-modulefilter-container' );
moduleFilter.innerHTML = moduleFilterHtml;
addEvent( moduleFilter, "change", function() {
var selectBox = moduleFilter.getElementsByTagName("select")[0],
selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value);
window.location = QUnit.url( { module: ( selectedModule === "" ) ? undefined : selectedModule } );
});
toolbar.appendChild(moduleFilter);
}
} }
// `main` initialized at top of scope // `main` initialized at top of scope
@@ -1039,9 +1072,9 @@ window.onerror = function ( error, filePath, linerNr ) {
} }
QUnit.pushFailure( error, filePath + ":" + linerNr ); QUnit.pushFailure( error, filePath + ":" + linerNr );
} else { } else {
QUnit.test( "global failure", function() { QUnit.test( "global failure", extend( function() {
QUnit.pushFailure( error, filePath + ":" + linerNr ); QUnit.pushFailure( error, filePath + ":" + linerNr );
}); }, { validTest: validTest } ) );
} }
return false; return false;
} }
@@ -1108,6 +1141,11 @@ function done() {
} }
} }
// scroll back to top to show results
if ( window.scrollTo ) {
window.scrollTo(0, 0);
}
runLoggingCallbacks( "done", QUnit, { runLoggingCallbacks( "done", QUnit, {
failed: config.stats.bad, failed: config.stats.bad,
passed: passed, passed: passed,
@@ -1123,6 +1161,12 @@ function validTest( test ) {
module = config.module && config.module.toLowerCase(), module = config.module && config.module.toLowerCase(),
fullName = (test.module + ": " + test.testName).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 ) { if ( config.testNumber ) {
return test.testNumber === config.testNumber; return test.testNumber === config.testNumber;
} }
@@ -1404,7 +1448,8 @@ QUnit.equiv = (function() {
a.global === b.global && a.global === b.global &&
// (gmi) ... // (gmi) ...
a.ignoreCase === b.ignoreCase && a.ignoreCase === b.ignoreCase &&
a.multiline === b.multiline; a.multiline === b.multiline &&
a.sticky === b.sticky;
}, },
// - skip when the property is a method of an instance (OOP) // - skip when the property is a method of an instance (OOP)

View File

@@ -2,7 +2,7 @@ $(document).ready(function() {
module("Arrays"); module("Arrays");
test("arrays: first", function() { test("first", function() {
equal(_.first([1,2,3]), 1, 'can pull out the first element of an array'); equal(_.first([1,2,3]), 1, 'can pull out the first element of an array');
equal(_([1, 2, 3]).first(), 1, 'can perform OO-style "first()"'); equal(_([1, 2, 3]).first(), 1, 'can perform OO-style "first()"');
equal(_.first([1,2,3], 0).join(', '), "", 'can pass an index to first'); equal(_.first([1,2,3], 0).join(', '), "", 'can pass an index to first');
@@ -16,7 +16,7 @@ $(document).ready(function() {
equal(result.join(','), '1,2', 'aliased as take'); equal(result.join(','), '1,2', 'aliased as take');
}); });
test("arrays: rest", function() { test("rest", function() {
var numbers = [1, 2, 3, 4]; var numbers = [1, 2, 3, 4];
equal(_.rest(numbers).join(", "), "2, 3, 4", 'working rest()'); equal(_.rest(numbers).join(", "), "2, 3, 4", 'working rest()');
equal(_.rest(numbers, 0).join(", "), "1, 2, 3, 4", 'working rest(0)'); equal(_.rest(numbers, 0).join(", "), "1, 2, 3, 4", 'working rest(0)');
@@ -25,9 +25,11 @@ $(document).ready(function() {
equal(result.join(', '), '2, 3, 4', 'aliased as tail and works on arguments object'); equal(result.join(', '), '2, 3, 4', 'aliased as tail and works on arguments object');
result = _.map([[1,2,3],[1,2,3]], _.rest); result = _.map([[1,2,3],[1,2,3]], _.rest);
equal(_.flatten(result).join(','), '2,3,2,3', 'works well with _.map'); equal(_.flatten(result).join(','), '2,3,2,3', 'works well with _.map');
result = (function(){ return _(arguments).drop(); })(1, 2, 3, 4);
equal(result.join(', '), '2, 3, 4', 'aliased as drop and works on arguments object');
}); });
test("arrays: initial", function() { test("initial", function() {
equal(_.initial([1,2,3,4,5]).join(", "), "1, 2, 3, 4", 'working initial()'); equal(_.initial([1,2,3,4,5]).join(", "), "1, 2, 3, 4", 'working initial()');
equal(_.initial([1,2,3,4],2).join(", "), "1, 2", 'initial can take an index'); equal(_.initial([1,2,3,4],2).join(", "), "1, 2", 'initial can take an index');
var result = (function(){ return _(arguments).initial(); })(1, 2, 3, 4); var result = (function(){ return _(arguments).initial(); })(1, 2, 3, 4);
@@ -36,7 +38,7 @@ $(document).ready(function() {
equal(_.flatten(result).join(','), '1,2,1,2', 'initial works with _.map'); equal(_.flatten(result).join(','), '1,2,1,2', 'initial works with _.map');
}); });
test("arrays: last", function() { test("last", function() {
equal(_.last([1,2,3]), 3, 'can pull out the last element of an array'); equal(_.last([1,2,3]), 3, 'can pull out the last element of an array');
equal(_.last([1,2,3], 0).join(', '), "", 'can pass an index to last'); equal(_.last([1,2,3], 0).join(', '), "", 'can pass an index to last');
equal(_.last([1,2,3], 2).join(', '), '2, 3', 'can pass an index to last'); equal(_.last([1,2,3], 2).join(', '), '2, 3', 'can pass an index to last');
@@ -47,13 +49,13 @@ $(document).ready(function() {
equal(result.join(','), '3,3', 'works well with _.map'); equal(result.join(','), '3,3', 'works well with _.map');
}); });
test("arrays: compact", function() { test("compact", function() {
equal(_.compact([0, 1, false, 2, false, 3]).length, 3, 'can trim out all falsy values'); equal(_.compact([0, 1, false, 2, false, 3]).length, 3, 'can trim out all falsy values');
var result = (function(){ return _(arguments).compact().length; })(0, 1, false, 2, false, 3); var result = (function(){ return _(arguments).compact().length; })(0, 1, false, 2, false, 3);
equal(result, 3, 'works on an arguments object'); equal(result, 3, 'works on an arguments object');
}); });
test("arrays: flatten", function() { test("flatten", function() {
if (window.JSON) { if (window.JSON) {
var list = [1, [2], [3, [[[4]]]]]; var list = [1, [2], [3, [[[4]]]]];
equal(JSON.stringify(_.flatten(list)), '[1,2,3,4]', 'can flatten nested arrays'); equal(JSON.stringify(_.flatten(list)), '[1,2,3,4]', 'can flatten nested arrays');
@@ -63,7 +65,7 @@ $(document).ready(function() {
} }
}); });
test("arrays: without", function() { test("without", function() {
var list = [1, 2, 1, 0, 3, 1, 4]; var list = [1, 2, 1, 0, 3, 1, 4];
equal(_.without(list, 0, 1).join(', '), '2, 3, 4', 'can remove all instances of an object'); equal(_.without(list, 0, 1).join(', '), '2, 3, 4', 'can remove all instances of an object');
var result = (function(){ return _.without(arguments, 0, 1); })(1, 2, 1, 0, 3, 1, 4); var result = (function(){ return _.without(arguments, 0, 1); })(1, 2, 1, 0, 3, 1, 4);
@@ -74,7 +76,7 @@ $(document).ready(function() {
ok(_.without(list, list[0]).length == 1, 'ditto.'); ok(_.without(list, list[0]).length == 1, 'ditto.');
}); });
test("arrays: uniq", function() { test("uniq", function() {
var list = [1, 2, 1, 3, 1, 4]; var list = [1, 2, 1, 3, 1, 4];
equal(_.uniq(list).join(', '), '1, 2, 3, 4', 'can find the unique values of an unsorted array'); equal(_.uniq(list).join(', '), '1, 2, 3, 4', 'can find the unique values of an unsorted array');
@@ -93,7 +95,7 @@ $(document).ready(function() {
equal(result.join(', '), '1, 2, 3, 4', 'works on an arguments object'); equal(result.join(', '), '1, 2, 3, 4', 'works on an arguments object');
}); });
test("arrays: intersection", function() { test("intersection", function() {
var stooges = ['moe', 'curly', 'larry'], leaders = ['moe', 'groucho']; var stooges = ['moe', 'curly', 'larry'], leaders = ['moe', 'groucho'];
equal(_.intersection(stooges, leaders).join(''), 'moe', 'can take the set intersection of two arrays'); equal(_.intersection(stooges, leaders).join(''), 'moe', 'can take the set intersection of two arrays');
equal(_(stooges).intersection(leaders).join(''), 'moe', 'can perform an OO-style intersection'); equal(_(stooges).intersection(leaders).join(''), 'moe', 'can perform an OO-style intersection');
@@ -101,7 +103,7 @@ $(document).ready(function() {
equal(result.join(''), 'moe', 'works on an arguments object'); equal(result.join(''), 'moe', 'works on an arguments object');
}); });
test("arrays: union", function() { test("union", function() {
var result = _.union([1, 2, 3], [2, 30, 1], [1, 40]); var result = _.union([1, 2, 3], [2, 30, 1], [1, 40]);
equal(result.join(' '), '1 2 3 30 40', 'takes the union of a list of arrays'); equal(result.join(' '), '1 2 3 30 40', 'takes the union of a list of arrays');
@@ -109,7 +111,7 @@ $(document).ready(function() {
equal(result.join(' '), '1 2 3 30 40 1', 'takes the union of a list of nested arrays'); equal(result.join(' '), '1 2 3 30 40 1', 'takes the union of a list of nested arrays');
}); });
test("arrays: difference", function() { test("difference", function() {
var result = _.difference([1, 2, 3], [2, 30, 40]); var result = _.difference([1, 2, 3], [2, 30, 40]);
equal(result.join(' '), '1 3', 'takes the difference of two arrays'); equal(result.join(' '), '1 3', 'takes the difference of two arrays');
@@ -117,19 +119,26 @@ $(document).ready(function() {
equal(result.join(' '), '3 4', 'takes the difference of three arrays'); equal(result.join(' '), '3 4', 'takes the difference of three arrays');
}); });
test('arrays: zip', function() { test('zip', function() {
var names = ['moe', 'larry', 'curly'], ages = [30, 40, 50], leaders = [true]; var names = ['moe', 'larry', 'curly'], ages = [30, 40, 50], leaders = [true];
var stooges = _.zip(names, ages, leaders); var stooges = _.zip(names, ages, leaders);
equal(String(stooges), 'moe,30,true,larry,40,,curly,50,', 'zipped together arrays of different lengths'); equal(String(stooges), 'moe,30,true,larry,40,,curly,50,', 'zipped together arrays of different lengths');
}); });
test('arrays: zipObject', function() { test('object', function() {
var result = _.zipObject(['moe', 'larry', 'curly'], [30, 40, 50]); var result = _.object(['moe', 'larry', 'curly'], [30, 40, 50]);
var shouldBe = {moe: 30, larry: 40, curly: 50}; var shouldBe = {moe: 30, larry: 40, curly: 50};
ok(_.isEqual(result, shouldBe), 'two arrays zipped together into an object'); ok(_.isEqual(result, shouldBe), 'two arrays zipped together into an object');
result = _.object([['one', 1], ['two', 2], ['three', 3]]);
shouldBe = {one: 1, two: 2, three: 3};
ok(_.isEqual(result, shouldBe), 'an array of pairs zipped together into an object');
var stooges = {moe: 30, larry: 40, curly: 50};
ok(_.isEqual(_.object(_.pairs(stooges)), stooges), 'an object converted to pairs and back to an object');
}); });
test("arrays: indexOf", function() { test("indexOf", function() {
var numbers = [1, 2, 3]; var numbers = [1, 2, 3];
numbers.indexOf = null; numbers.indexOf = null;
equal(_.indexOf(numbers, 2), 1, 'can compute indexOf, even without the native function'); equal(_.indexOf(numbers, 2), 1, 'can compute indexOf, even without the native function');
@@ -150,7 +159,7 @@ $(document).ready(function() {
equal(index, 1, '40 is in the list'); equal(index, 1, '40 is in the list');
}); });
test("arrays: lastIndexOf", function() { test("lastIndexOf", function() {
var numbers = [1, 0, 1, 0, 0, 1, 0, 0, 0]; var numbers = [1, 0, 1, 0, 0, 1, 0, 0, 0];
numbers.lastIndexOf = null; numbers.lastIndexOf = null;
equal(_.lastIndexOf(numbers, 1), 5, 'can compute lastIndexOf, even without the native function'); equal(_.lastIndexOf(numbers, 1), 5, 'can compute lastIndexOf, even without the native function');
@@ -160,7 +169,7 @@ $(document).ready(function() {
equal(_.indexOf(null, 2), -1, 'handles nulls properly'); equal(_.indexOf(null, 2), -1, 'handles nulls properly');
}); });
test("arrays: range", function() { test("range", function() {
equal(_.range(0).join(''), '', 'range with 0 as a first argument generates an empty array'); equal(_.range(0).join(''), '', 'range with 0 as a first argument generates an empty array');
equal(_.range(4).join(' '), '0 1 2 3', 'range with a single positive argument generates an array of elements 0,1,2,...,n-1'); equal(_.range(4).join(' '), '0 1 2 3', 'range with a single positive argument generates an array of elements 0,1,2,...,n-1');
equal(_.range(5, 8).join(' '), '5 6 7', 'range with two arguments a &amp; b, a&lt;b generates an array of elements a,a+1,a+2,...,b-2,b-1'); equal(_.range(5, 8).join(' '), '5 6 7', 'range with two arguments a &amp; b, a&lt;b generates an array of elements a,a+1,a+2,...,b-2,b-1');

View File

@@ -2,7 +2,7 @@ $(document).ready(function() {
module("Chaining"); module("Chaining");
test("chaining: map/flatten/reduce", function() { test("map/flatten/reduce", function() {
var lyrics = [ var lyrics = [
"I'm a lumberjack and I'm okay", "I'm a lumberjack and I'm okay",
"I sleep all night and I work all day", "I sleep all night and I work all day",
@@ -20,7 +20,7 @@ $(document).ready(function() {
ok(counts['a'] == 16 && counts['e'] == 10, 'counted all the letters in the song'); ok(counts['a'] == 16 && counts['e'] == 10, 'counted all the letters in the song');
}); });
test("chaining: select/reject/sortBy", function() { test("select/reject/sortBy", function() {
var numbers = [1,2,3,4,5,6,7,8,9,10]; var numbers = [1,2,3,4,5,6,7,8,9,10];
numbers = _(numbers).chain().select(function(n) { numbers = _(numbers).chain().select(function(n) {
return n % 2 == 0; return n % 2 == 0;
@@ -32,7 +32,7 @@ $(document).ready(function() {
equal(numbers.join(', '), "10, 6, 2", "filtered and reversed the numbers"); equal(numbers.join(', '), "10, 6, 2", "filtered and reversed the numbers");
}); });
test("chaining: select/reject/sortBy in functional style", function() { test("select/reject/sortBy in functional style", function() {
var numbers = [1,2,3,4,5,6,7,8,9,10]; var numbers = [1,2,3,4,5,6,7,8,9,10];
numbers = _.chain(numbers).select(function(n) { numbers = _.chain(numbers).select(function(n) {
return n % 2 == 0; return n % 2 == 0;
@@ -44,7 +44,7 @@ $(document).ready(function() {
equal(numbers.join(', '), "10, 6, 2", "filtered and reversed the numbers"); equal(numbers.join(', '), "10, 6, 2", "filtered and reversed the numbers");
}); });
test("chaining: reverse/concat/unshift/pop/map", function() { test("reverse/concat/unshift/pop/map", function() {
var numbers = [1,2,3,4,5]; var numbers = [1,2,3,4,5];
numbers = _(numbers).chain() numbers = _(numbers).chain()
.reverse() .reverse()

View File

@@ -2,7 +2,7 @@ $(document).ready(function() {
module("Collections"); module("Collections");
test("collections: each", function() { test("each", function() {
_.each([1, 2, 3], function(num, i) { _.each([1, 2, 3], function(num, i) {
equal(num, i + 1, 'each iterators provide value and iteration count'); equal(num, i + 1, 'each iterators provide value and iteration count');
}); });
@@ -31,7 +31,7 @@ $(document).ready(function() {
equal(answers, 0, 'handles a null properly'); equal(answers, 0, 'handles a null properly');
}); });
test('collections: map', function() { test('map', function() {
var doubled = _.map([1, 2, 3], function(num){ return num * 2; }); var doubled = _.map([1, 2, 3], function(num){ return num * 2; });
equal(doubled.join(', '), '2, 4, 6', 'doubled numbers'); equal(doubled.join(', '), '2, 4, 6', 'doubled numbers');
@@ -54,7 +54,7 @@ $(document).ready(function() {
ok(_.isArray(ifnull) && ifnull.length === 0, 'handles a null properly'); ok(_.isArray(ifnull) && ifnull.length === 0, 'handles a null properly');
}); });
test('collections: reduce', function() { test('reduce', function() {
var sum = _.reduce([1, 2, 3], function(sum, num){ return sum + num; }, 0); var sum = _.reduce([1, 2, 3], function(sum, num){ return sum + num; }, 0);
equal(sum, 6, 'can sum up an array'); equal(sum, 6, 'can sum up an array');
@@ -84,7 +84,7 @@ $(document).ready(function() {
raises(function() { _.reduce([], function(){}); }, TypeError, 'throws an error for empty arrays with no initial value'); raises(function() { _.reduce([], function(){}); }, TypeError, 'throws an error for empty arrays with no initial value');
}); });
test('collections: reduceRight', function() { test('reduceRight', function() {
var list = _.reduceRight(["foo", "bar", "baz"], function(memo, str){ return memo + str; }, ''); var list = _.reduceRight(["foo", "bar", "baz"], function(memo, str){ return memo + str; }, '');
equal(list, 'bazbarfoo', 'can perform right folds'); equal(list, 'bazbarfoo', 'can perform right folds');
@@ -108,18 +108,18 @@ $(document).ready(function() {
raises(function() { _.reduceRight([], function(){}); }, TypeError, 'throws an error for empty arrays with no initial value'); raises(function() { _.reduceRight([], function(){}); }, TypeError, 'throws an error for empty arrays with no initial value');
}); });
test('collections: find', function() { test('find', function() {
var array = [1, 2, 3, 4]; var array = [1, 2, 3, 4];
strictEqual(_.find(array, function(n) { return n > 2; }), 3, 'should return first found `value`'); strictEqual(_.find(array, function(n) { return n > 2; }), 3, 'should return first found `value`');
strictEqual(_.find(array, function() { return false; }), void 0, 'should return `undefined` if `value` is not found'); strictEqual(_.find(array, function() { return false; }), void 0, 'should return `undefined` if `value` is not found');
}); });
test('collections: detect', function() { test('detect', function() {
var result = _.detect([1, 2, 3], function(num){ return num * 2 == 4; }); var result = _.detect([1, 2, 3], function(num){ return num * 2 == 4; });
equal(result, 2, 'found the first "2" and broke the loop'); equal(result, 2, 'found the first "2" and broke the loop');
}); });
test('collections: select', function() { test('select', function() {
var evens = _.select([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; }); var evens = _.select([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
equal(evens.join(', '), '2, 4, 6', 'selected each even number'); equal(evens.join(', '), '2, 4, 6', 'selected each even number');
@@ -127,12 +127,12 @@ $(document).ready(function() {
equal(evens.join(', '), '2, 4, 6', 'aliased as "filter"'); equal(evens.join(', '), '2, 4, 6', 'aliased as "filter"');
}); });
test('collections: reject', function() { test('reject', function() {
var odds = _.reject([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; }); var odds = _.reject([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
equal(odds.join(', '), '1, 3, 5', 'rejected each even number'); equal(odds.join(', '), '1, 3, 5', 'rejected each even number');
}); });
test('collections: all', function() { test('all', function() {
ok(_.all([], _.identity), 'the empty set'); ok(_.all([], _.identity), 'the empty set');
ok(_.all([true, true, true], _.identity), 'all true values'); ok(_.all([true, true, true], _.identity), 'all true values');
ok(!_.all([true, false, true], _.identity), 'one false value'); ok(!_.all([true, false, true], _.identity), 'one false value');
@@ -141,9 +141,10 @@ $(document).ready(function() {
ok(_.all([1], _.identity) === true, 'cast to boolean - true'); ok(_.all([1], _.identity) === true, 'cast to boolean - true');
ok(_.all([0], _.identity) === false, 'cast to boolean - false'); ok(_.all([0], _.identity) === false, 'cast to boolean - false');
ok(_.every([true, true, true], _.identity), 'aliased as "every"'); ok(_.every([true, true, true], _.identity), 'aliased as "every"');
ok(!_.all([undefined, undefined, undefined], _.identity), 'works with arrays of undefined');
}); });
test('collections: any', function() { test('any', function() {
var nativeSome = Array.prototype.some; var nativeSome = Array.prototype.some;
Array.prototype.some = null; Array.prototype.some = null;
ok(!_.any([]), 'the empty set'); ok(!_.any([]), 'the empty set');
@@ -159,21 +160,21 @@ $(document).ready(function() {
Array.prototype.some = nativeSome; Array.prototype.some = nativeSome;
}); });
test('collections: include', function() { test('include', function() {
ok(_.include([1,2,3], 2), 'two is in the array'); ok(_.include([1,2,3], 2), 'two is in the array');
ok(!_.include([1,3,9], 2), 'two is not in the array'); ok(!_.include([1,3,9], 2), 'two is not in the array');
ok(_.contains({moe:1, larry:3, curly:9}, 3) === true, '_.include on objects checks their values'); ok(_.contains({moe:1, larry:3, curly:9}, 3) === true, '_.include on objects checks their values');
ok(_([1,2,3]).include(2), 'OO-style include'); ok(_([1,2,3]).include(2), 'OO-style include');
}); });
test('collections: invoke', function() { test('invoke', function() {
var list = [[5, 1, 7], [3, 2, 1]]; var list = [[5, 1, 7], [3, 2, 1]];
var result = _.invoke(list, 'sort'); var result = _.invoke(list, 'sort');
equal(result[0].join(', '), '1, 5, 7', 'first array sorted'); equal(result[0].join(', '), '1, 5, 7', 'first array sorted');
equal(result[1].join(', '), '1, 2, 3', 'second array sorted'); equal(result[1].join(', '), '1, 2, 3', 'second array sorted');
}); });
test('collections: invoke w/ function reference', function() { test('invoke w/ function reference', function() {
var list = [[5, 1, 7], [3, 2, 1]]; var list = [[5, 1, 7], [3, 2, 1]];
var result = _.invoke(list, Array.prototype.sort); var result = _.invoke(list, Array.prototype.sort);
equal(result[0].join(', '), '1, 5, 7', 'first array sorted'); equal(result[0].join(', '), '1, 5, 7', 'first array sorted');
@@ -181,7 +182,7 @@ $(document).ready(function() {
}); });
// Relevant when using ClojureScript // Relevant when using ClojureScript
test('collections: invoke when strings have a call method', function() { test('invoke when strings have a call method', function() {
String.prototype.call = function() { String.prototype.call = function() {
return 42; return 42;
}; };
@@ -195,12 +196,12 @@ $(document).ready(function() {
equal(s.call, undefined, "call function removed"); equal(s.call, undefined, "call function removed");
}); });
test('collections: pluck', function() { test('pluck', function() {
var people = [{name : 'moe', age : 30}, {name : 'curly', age : 50}]; var people = [{name : 'moe', age : 30}, {name : 'curly', age : 50}];
equal(_.pluck(people, 'name').join(', '), 'moe, curly', 'pulls names out of objects'); equal(_.pluck(people, 'name').join(', '), 'moe, curly', 'pulls names out of objects');
}); });
test('collections: max', function() { test('max', function() {
equal(3, _.max([1, 2, 3]), 'can perform a regular Math.max'); equal(3, _.max([1, 2, 3]), 'can perform a regular Math.max');
var neg = _.max([1, 2, 3], function(num){ return -num; }); var neg = _.max([1, 2, 3], function(num){ return -num; });
@@ -212,7 +213,7 @@ $(document).ready(function() {
equal(299999, _.max(_.range(1,300000)), "Maximum value of a too-big array"); equal(299999, _.max(_.range(1,300000)), "Maximum value of a too-big array");
}); });
test('collections: min', function() { test('min', function() {
equal(1, _.min([1, 2, 3]), 'can perform a regular Math.min'); equal(1, _.min([1, 2, 3]), 'can perform a regular Math.min');
var neg = _.min([1, 2, 3], function(num){ return -num; }); var neg = _.min([1, 2, 3], function(num){ return -num; });
@@ -228,7 +229,7 @@ $(document).ready(function() {
equal(1, _.min(_.range(1,300000)), "Minimum value of a too-big array"); equal(1, _.min(_.range(1,300000)), "Minimum value of a too-big array");
}); });
test('collections: sortBy', function() { test('sortBy', function() {
var people = [{name : 'curly', age : 50}, {name : 'moe', age : 30}]; var people = [{name : 'curly', age : 50}, {name : 'moe', age : 30}];
people = _.sortBy(people, function(person){ return person.age; }); people = _.sortBy(people, function(person){ return person.age; });
equal(_.pluck(people, 'name').join(', '), 'moe, curly', 'stooges sorted by age'); equal(_.pluck(people, 'name').join(', '), 'moe, curly', 'stooges sorted by age');
@@ -241,7 +242,7 @@ $(document).ready(function() {
equal(sorted.join(' '), 'one two four five three', 'sorted by length'); equal(sorted.join(' '), 'one two four five three', 'sorted by length');
}); });
test('collections: groupBy', function() { test('groupBy', function() {
var parity = _.groupBy([1, 2, 3, 4, 5, 6], function(num){ return num % 2; }); var parity = _.groupBy([1, 2, 3, 4, 5, 6], function(num){ return num % 2; });
ok('0' in parity && '1' in parity, 'created a group for each value'); ok('0' in parity && '1' in parity, 'created a group for each value');
equal(parity[0].join(', '), '2, 4, 6', 'put each even number in the right group'); equal(parity[0].join(', '), '2, 4, 6', 'put each even number in the right group');
@@ -253,7 +254,7 @@ $(document).ready(function() {
equal(grouped['5'].join(' '), 'three seven eight'); equal(grouped['5'].join(' '), 'three seven eight');
}); });
test('collections: countBy', function() { test('countBy', function() {
var parity = _.countBy([1, 2, 3, 4, 5], function(num){ return num % 2 == 0; }); var parity = _.countBy([1, 2, 3, 4, 5], function(num){ return num % 2 == 0; });
equal(parity['true'], 2); equal(parity['true'], 2);
equal(parity['false'], 3); equal(parity['false'], 3);
@@ -265,7 +266,7 @@ $(document).ready(function() {
equal(grouped['5'], 3); equal(grouped['5'], 3);
}); });
test('collections: sortedIndex', function() { test('sortedIndex', function() {
var numbers = [10, 20, 30, 40, 50], num = 35; var numbers = [10, 20, 30, 40, 50], num = 35;
var indexForNum = _.sortedIndex(numbers, num); var indexForNum = _.sortedIndex(numbers, num);
equal(indexForNum, 3, '35 should be inserted at index 3'); equal(indexForNum, 3, '35 should be inserted at index 3');
@@ -274,14 +275,14 @@ $(document).ready(function() {
equal(indexFor30, 2, '30 should be inserted at index 2'); equal(indexFor30, 2, '30 should be inserted at index 2');
}); });
test('collections: shuffle', function() { test('shuffle', function() {
var numbers = _.range(10); var numbers = _.range(10);
var shuffled = _.shuffle(numbers).sort(); var shuffled = _.shuffle(numbers).sort();
notStrictEqual(numbers, shuffled, 'original object is unmodified'); notStrictEqual(numbers, shuffled, 'original object is unmodified');
equal(shuffled.join(','), numbers.join(','), 'contains the same members before and after shuffle'); equal(shuffled.join(','), numbers.join(','), 'contains the same members before and after shuffle');
}); });
test('collections: toArray', function() { test('toArray', function() {
ok(!_.isArray(arguments), 'arguments object is not an array'); ok(!_.isArray(arguments), 'arguments object is not an array');
ok(_.isArray(_.toArray(arguments)), 'arguments object converted into array'); ok(_.isArray(_.toArray(arguments)), 'arguments object converted into array');
var a = [1,2,3]; var a = [1,2,3];
@@ -300,9 +301,17 @@ $(document).ready(function() {
equal(_.toArray(objectWithToArrayValue).join(', '), '1', 'toArray property ignored if not a function'); equal(_.toArray(objectWithToArrayValue).join(', '), '1', 'toArray property ignored if not a function');
}); });
test('collections: size', function() { test('size', function() {
equal(_.size({one : 1, two : 2, three : 3}), 3, 'can compute the size of an object'); equal(_.size({one : 1, two : 2, three : 3}), 3, 'can compute the size of an object');
equal(_.size([1, 2, 3]), 3, 'can compute the size of an array'); equal(_.size([1, 2, 3]), 3, 'can compute the size of an array');
var func = function() {
return _.size(arguments);
};
equal(func(1, 2, 3, 4), 4, 'can test the size of the arguments object');
equal(_.size('hello'), 5, 'can compute the size of a string');
}); });
}); });

View File

@@ -2,7 +2,7 @@ $(document).ready(function() {
module("Functions"); module("Functions");
test("functions: bind", function() { test("bind", function() {
var context = {name : 'moe'}; var context = {name : 'moe'};
var func = function(arg) { return "name: " + (this.name || arg); }; var func = function(arg) { return "name: " + (this.name || arg); };
var bound = _.bind(func, context); var bound = _.bind(func, context);
@@ -38,7 +38,7 @@ $(document).ready(function() {
equal(Boundf().hello, "moe curly", "When called without the new operator, it's OK to be bound to the context"); equal(Boundf().hello, "moe curly", "When called without the new operator, it's OK to be bound to the context");
}); });
test("functions: bindAll", function() { test("bindAll", function() {
var curly = {name : 'curly'}, moe = { var curly = {name : 'curly'}, moe = {
name : 'moe', name : 'moe',
getName : function() { return 'name: ' + this.name; }, getName : function() { return 'name: ' + this.name; },
@@ -61,7 +61,7 @@ $(document).ready(function() {
equal(curly.sayHi(), 'hi: moe', 'calling bindAll with no arguments binds all functions to the object'); equal(curly.sayHi(), 'hi: moe', 'calling bindAll with no arguments binds all functions to the object');
}); });
test("functions: memoize", function() { test("memoize", function() {
var fib = function(n) { var fib = function(n) {
return n < 2 ? n : fib(n - 1) + fib(n - 2); return n < 2 ? n : fib(n - 1) + fib(n - 2);
}; };
@@ -77,20 +77,20 @@ $(document).ready(function() {
equal(fastO('toString'), 'toString', 'checks hasOwnProperty'); equal(fastO('toString'), 'toString', 'checks hasOwnProperty');
}); });
asyncTest("functions: delay", 2, function() { asyncTest("delay", 2, function() {
var delayed = false; var delayed = false;
_.delay(function(){ delayed = true; }, 100); _.delay(function(){ delayed = true; }, 100);
setTimeout(function(){ ok(!delayed, "didn't delay the function quite yet"); }, 50); setTimeout(function(){ ok(!delayed, "didn't delay the function quite yet"); }, 50);
setTimeout(function(){ ok(delayed, 'delayed the function'); start(); }, 150); setTimeout(function(){ ok(delayed, 'delayed the function'); start(); }, 150);
}); });
asyncTest("functions: defer", 1, function() { asyncTest("defer", 1, function() {
var deferred = false; var deferred = false;
_.defer(function(bool){ deferred = bool; }, true); _.defer(function(bool){ deferred = bool; }, true);
_.delay(function(){ ok(deferred, "deferred the function"); start(); }, 50); _.delay(function(){ ok(deferred, "deferred the function"); start(); }, 50);
}); });
asyncTest("functions: throttle", 2, function() { asyncTest("throttle", 2, function() {
var counter = 0; var counter = 0;
var incr = function(){ counter++; }; var incr = function(){ counter++; };
var throttledIncr = _.throttle(incr, 100); var throttledIncr = _.throttle(incr, 100);
@@ -105,7 +105,7 @@ $(document).ready(function() {
_.delay(function(){ equal(counter, 4, "incr was throttled"); start(); }, 400); _.delay(function(){ equal(counter, 4, "incr was throttled"); start(); }, 400);
}); });
asyncTest("functions: throttle arguments", 2, function() { asyncTest("throttle arguments", 2, function() {
var value = 0; var value = 0;
var update = function(val){ value = val; }; var update = function(val){ value = val; };
var throttledUpdate = _.throttle(update, 100); var throttledUpdate = _.throttle(update, 100);
@@ -117,7 +117,7 @@ $(document).ready(function() {
_.delay(function(){ equal(value, 6, "updated to latest value"); start(); }, 400); _.delay(function(){ equal(value, 6, "updated to latest value"); start(); }, 400);
}); });
asyncTest("functions: throttle once", 2, function() { asyncTest("throttle once", 2, function() {
var counter = 0; var counter = 0;
var incr = function(){ return ++counter; }; var incr = function(){ return ++counter; };
var throttledIncr = _.throttle(incr, 100); var throttledIncr = _.throttle(incr, 100);
@@ -128,7 +128,7 @@ $(document).ready(function() {
}, 220); }, 220);
}); });
asyncTest("functions: throttle twice", 1, function() { asyncTest("throttle twice", 1, function() {
var counter = 0; var counter = 0;
var incr = function(){ counter++; }; var incr = function(){ counter++; };
var throttledIncr = _.throttle(incr, 100); var throttledIncr = _.throttle(incr, 100);
@@ -136,7 +136,34 @@ $(document).ready(function() {
_.delay(function(){ equal(counter, 2, "incr was called twice"); start(); }, 220); _.delay(function(){ equal(counter, 2, "incr was called twice"); start(); }, 220);
}); });
asyncTest("functions: debounce", 1, function() { asyncTest("throttle repeatedly with results", 9, function() {
var counter = 0;
var incr = function(){ return ++counter; };
var throttledIncr = _.throttle(incr, 100);
var results = [];
var saveResult = function() { results.push(throttledIncr()); }
saveResult(); saveResult(); saveResult();
setTimeout(saveResult, 70);
setTimeout(saveResult, 120);
setTimeout(saveResult, 140);
setTimeout(saveResult, 190);
setTimeout(saveResult, 240);
setTimeout(saveResult, 260);
_.delay(function() {
equal(results[0], 1, "incr was called once");
equal(results[1], 1, "incr was throttled");
equal(results[2], 1, "incr was throttled");
equal(results[3], 1, "incr was throttled");
equal(results[4], 2, "incr was called twice");
equal(results[5], 2, "incr was throttled");
equal(results[6], 2, "incr was throttled");
equal(results[7], 3, "incr was called thrice");
equal(results[8], 3, "incr was throttled");
start();
}, 400);
});
asyncTest("debounce", 1, function() {
var counter = 0; var counter = 0;
var incr = function(){ counter++; }; var incr = function(){ counter++; };
var debouncedIncr = _.debounce(incr, 50); var debouncedIncr = _.debounce(incr, 50);
@@ -149,7 +176,7 @@ $(document).ready(function() {
_.delay(function(){ equal(counter, 1, "incr was debounced"); start(); }, 220); _.delay(function(){ equal(counter, 1, "incr was debounced"); start(); }, 220);
}); });
asyncTest("functions: debounce asap", 2, function() { asyncTest("debounce asap", 2, function() {
var counter = 0; var counter = 0;
var incr = function(){ counter++; }; var incr = function(){ counter++; };
var debouncedIncr = _.debounce(incr, 50, true); var debouncedIncr = _.debounce(incr, 50, true);
@@ -163,7 +190,7 @@ $(document).ready(function() {
_.delay(function(){ equal(counter, 1, "incr was debounced"); start(); }, 220); _.delay(function(){ equal(counter, 1, "incr was debounced"); start(); }, 220);
}); });
asyncTest("functions: debounce asap recursively", 2, function() { asyncTest("debounce asap recursively", 2, function() {
var counter = 0; var counter = 0;
var debouncedIncr = _.debounce(function(){ var debouncedIncr = _.debounce(function(){
counter++; counter++;
@@ -174,7 +201,7 @@ $(document).ready(function() {
_.delay(function(){ equal(counter, 1, "incr was debounced"); start(); }, 70); _.delay(function(){ equal(counter, 1, "incr was debounced"); start(); }, 70);
}); });
test("functions: once", function() { test("once", function() {
var num = 0; var num = 0;
var increment = _.once(function(){ num++; }); var increment = _.once(function(){ num++; });
increment(); increment();
@@ -182,7 +209,7 @@ $(document).ready(function() {
equal(num, 1); equal(num, 1);
}); });
test("functions: wrap", function() { test("wrap", function() {
var greet = function(name){ return "hi: " + name; }; var greet = function(name){ return "hi: " + name; };
var backwards = _.wrap(greet, function(func, name){ return func(name) + ' ' + name.split('').reverse().join(''); }); var backwards = _.wrap(greet, function(func, name){ return func(name) + ' ' + name.split('').reverse().join(''); });
equal(backwards('moe'), 'hi: moe eom', 'wrapped the saluation function'); equal(backwards('moe'), 'hi: moe eom', 'wrapped the saluation function');
@@ -198,7 +225,7 @@ $(document).ready(function() {
deepEqual(ret, [noop, ['whats', 'your'], 'vector', 'victor']); deepEqual(ret, [noop, ['whats', 'your'], 'vector', 'victor']);
}); });
test("functions: compose", function() { test("compose", function() {
var greet = function(name){ return "hi: " + name; }; var greet = function(name){ return "hi: " + name; };
var exclaim = function(sentence){ return sentence + '!'; }; var exclaim = function(sentence){ return sentence + '!'; };
var composed = _.compose(exclaim, greet); var composed = _.compose(exclaim, greet);
@@ -208,7 +235,7 @@ $(document).ready(function() {
equal(composed('moe'), 'hi: moe!', 'in this case, the functions are also commutative'); equal(composed('moe'), 'hi: moe!', 'in this case, the functions are also commutative');
}); });
test("functions: after", function() { test("after", function() {
var testAfter = function(afterAmount, timesCalled) { var testAfter = function(afterAmount, timesCalled) {
var afterCalled = 0; var afterCalled = 0;
var after = _.after(afterAmount, function() { var after = _.after(afterAmount, function() {

View File

@@ -2,7 +2,7 @@ $(document).ready(function() {
module("Objects"); module("Objects");
test("objects: keys", function() { test("keys", function() {
equal(_.keys({one : 1, two : 2}).join(', '), 'one, two', 'can extract the keys from an object'); equal(_.keys({one : 1, two : 2}).join(', '), 'one, two', 'can extract the keys from an object');
// the test above is not safe because it relies on for-in enumeration order // the test above is not safe because it relies on for-in enumeration order
var a = []; a[1] = 0; var a = []; a[1] = 0;
@@ -14,11 +14,21 @@ $(document).ready(function() {
raises(function() { _.keys(true); }, TypeError, 'throws an error for boolean primitives'); raises(function() { _.keys(true); }, TypeError, 'throws an error for boolean primitives');
}); });
test("objects: values", function() { test("values", function() {
equal(_.values({one : 1, two : 2}).join(', '), '1, 2', 'can extract the values from an object'); equal(_.values({one : 1, two : 2}).join(', '), '1, 2', 'can extract the values from an object');
}); });
test("objects: functions", function() { test("pairs", function() {
deepEqual(_.pairs({one: 1, two: 2}), [['one', 1], ['two', 2]], 'can convert an object into pairs');
});
test("invert", function() {
var obj = {first: 'Moe', second: 'Larry', third: 'Curly'};
equal(_.keys(_.invert(obj)).join(' '), 'Moe Larry Curly', 'can invert an object');
ok(_.isEqual(_.invert(_.invert(obj)), obj), 'two inverts gets you back where you started');
});
test("functions", function() {
var obj = {a : 'dash', b : _.map, c : (/yo/), d : _.reduce}; var obj = {a : 'dash', b : _.map, c : (/yo/), d : _.reduce};
ok(_.isEqual(['b', 'd'], _.functions(obj)), 'can grab the function names of any passed-in object'); ok(_.isEqual(['b', 'd'], _.functions(obj)), 'can grab the function names of any passed-in object');
@@ -27,7 +37,7 @@ $(document).ready(function() {
equal(_.functions(new Animal).join(''), 'run', 'also looks up functions on the prototype'); equal(_.functions(new Animal).join(''), 'run', 'also looks up functions on the prototype');
}); });
test("objects: extend", function() { test("extend", function() {
var result; var result;
equal(_.extend({}, {a:'b'}).a, 'b', 'can extend an object with the attributes of another'); equal(_.extend({}, {a:'b'}).a, 'b', 'can extend an object with the attributes of another');
equal(_.extend({a:'x'}, {a:'b'}).a, 'b', 'properties in source override destination'); equal(_.extend({a:'x'}, {a:'b'}).a, 'b', 'properties in source override destination');
@@ -40,7 +50,7 @@ $(document).ready(function() {
equal(_.keys(result).join(''), 'ab', 'extend does not copy undefined values'); equal(_.keys(result).join(''), 'ab', 'extend does not copy undefined values');
}); });
test("objects: pick", function() { test("pick", function() {
var result; var result;
result = _.pick({a:1, b:2, c:3}, 'a', 'c'); result = _.pick({a:1, b:2, c:3}, 'a', 'c');
ok(_.isEqual(result, {a:1, c:3}), 'can restrict properties to those named'); ok(_.isEqual(result, {a:1, c:3}), 'can restrict properties to those named');
@@ -54,7 +64,7 @@ $(document).ready(function() {
ok(_.isEqual(_.pick(new Obj, 'a', 'c'), {a:1, c: 3}), 'include prototype props'); ok(_.isEqual(_.pick(new Obj, 'a', 'c'), {a:1, c: 3}), 'include prototype props');
}); });
test("objects: omit", function() { test("omit", function() {
var result; var result;
result = _.omit({a:1, b:2, c:3}, 'b'); result = _.omit({a:1, b:2, c:3}, 'b');
ok(_.isEqual(result, {a:1, c:3}), 'can omit a single named property'); ok(_.isEqual(result, {a:1, c:3}), 'can omit a single named property');
@@ -68,7 +78,7 @@ $(document).ready(function() {
ok(_.isEqual(_.omit(new Obj, 'b'), {a:1, c: 3}), 'include prototype props'); ok(_.isEqual(_.omit(new Obj, 'b'), {a:1, c: 3}), 'include prototype props');
}); });
test("objects: defaults", function() { test("defaults", function() {
var result; var result;
var options = {zero: 0, one: 1, empty: "", nan: NaN, string: "string"}; var options = {zero: 0, one: 1, empty: "", nan: NaN, string: "string"};
@@ -83,7 +93,7 @@ $(document).ready(function() {
equal(options.word, "word", 'new value is added, first one wins'); equal(options.word, "word", 'new value is added, first one wins');
}); });
test("objects: clone", function() { test("clone", function() {
var moe = {name : 'moe', lucky : [13, 27, 34]}; var moe = {name : 'moe', lucky : [13, 27, 34]};
var clone = _.clone(moe); var clone = _.clone(moe);
equal(clone.name, 'moe', 'the clone as the attributes of the original'); equal(clone.name, 'moe', 'the clone as the attributes of the original');
@@ -99,7 +109,7 @@ $(document).ready(function() {
equal(_.clone(null), null, 'non objects should not be changed by clone'); equal(_.clone(null), null, 'non objects should not be changed by clone');
}); });
test("objects: isEqual", function() { test("isEqual", function() {
function First() { function First() {
this.value = 1; this.value = 1;
} }
@@ -283,6 +293,12 @@ $(document).ready(function() {
b.push("Curly"); b.push("Curly");
ok(!_.isEqual(a, b), "Arrays containing circular references and different properties are not equal"); ok(!_.isEqual(a, b), "Arrays containing circular references and different properties are not equal");
// More circular arrays #767.
a = ["everything is checked but", "this", "is not"];
a[1] = a;
b = ["everything is checked but", ["this", "array"], "is not"];
ok(!_.isEqual(a, b), "Comparison of circular references with non-circular references are not equal");
// Circular Objects. // Circular Objects.
a = {abc: null}; a = {abc: null};
b = {abc: null}; b = {abc: null};
@@ -296,6 +312,12 @@ $(document).ready(function() {
b.def = new Number(63); b.def = new Number(63);
ok(!_.isEqual(a, b), "Objects containing circular references and different properties are not equal"); ok(!_.isEqual(a, b), "Objects containing circular references and different properties are not equal");
// More circular objects #767.
a = {everything: "is checked", but: "this", is: "not"};
a.but = a;
b = {everything: "is checked", but: {that:"object"}, is: "not"};
ok(!_.isEqual(a, b), "Comparison of circular references with non-circular object references are not equal");
// Cyclic Structures. // Cyclic Structures.
a = [{abc: null}]; a = [{abc: null}];
b = [{abc: null}]; b = [{abc: null}];
@@ -382,7 +404,7 @@ $(document).ready(function() {
ok(_.isEqual(date, date_json), 'date matches serialized date'); ok(_.isEqual(date, date_json), 'date matches serialized date');
}); });
test("objects: isEmpty", function() { test("isEmpty", function() {
ok(!_([1]).isEmpty(), '[1] is not empty'); ok(!_([1]).isEmpty(), '[1] is not empty');
ok(_.isEmpty([]), '[] is empty'); ok(_.isEmpty([]), '[] is empty');
ok(!_.isEmpty({one : 1}), '{one : 1} is not empty'); ok(!_.isEmpty({one : 1}), '{one : 1} is not empty');
@@ -420,13 +442,13 @@ $(document).ready(function() {
); );
iDoc.close(); iDoc.close();
test("objects: isElement", function() { test("isElement", function() {
ok(!_.isElement('div'), 'strings are not dom elements'); ok(!_.isElement('div'), 'strings are not dom elements');
ok(_.isElement($('html')[0]), 'the html tag is a DOM element'); ok(_.isElement($('html')[0]), 'the html tag is a DOM element');
ok(_.isElement(iElement), 'even from another frame'); ok(_.isElement(iElement), 'even from another frame');
}); });
test("objects: isArguments", function() { test("isArguments", function() {
var args = (function(){ return arguments; })(1, 2, 3); var args = (function(){ return arguments; })(1, 2, 3);
ok(!_.isArguments('string'), 'a string is not an arguments object'); ok(!_.isArguments('string'), 'a string is not an arguments object');
ok(!_.isArguments(_.isArguments), 'a function is not an arguments object'); ok(!_.isArguments(_.isArguments), 'a function is not an arguments object');
@@ -436,7 +458,7 @@ $(document).ready(function() {
ok(_.isArguments(iArguments), 'even from another frame'); ok(_.isArguments(iArguments), 'even from another frame');
}); });
test("objects: isObject", function() { test("isObject", function() {
ok(_.isObject(arguments), 'the arguments object is object'); ok(_.isObject(arguments), 'the arguments object is object');
ok(_.isObject([1, 2, 3]), 'and arrays'); ok(_.isObject([1, 2, 3]), 'and arrays');
ok(_.isObject($('html')[0]), 'and DOM element'); ok(_.isObject($('html')[0]), 'and DOM element');
@@ -451,19 +473,19 @@ $(document).ready(function() {
ok(_.isObject(new String('string')), 'but new String()'); ok(_.isObject(new String('string')), 'but new String()');
}); });
test("objects: isArray", function() { test("isArray", function() {
ok(!_.isArray(arguments), 'the arguments object is not an array'); ok(!_.isArray(arguments), 'the arguments object is not an array');
ok(_.isArray([1, 2, 3]), 'but arrays are'); ok(_.isArray([1, 2, 3]), 'but arrays are');
ok(_.isArray(iArray), 'even from another frame'); ok(_.isArray(iArray), 'even from another frame');
}); });
test("objects: isString", function() { test("isString", function() {
ok(!_.isString(document.body), 'the document body is not a string'); ok(!_.isString(document.body), 'the document body is not a string');
ok(_.isString([1, 2, 3].join(', ')), 'but strings are'); ok(_.isString([1, 2, 3].join(', ')), 'but strings are');
ok(_.isString(iString), 'even from another frame'); ok(_.isString(iString), 'even from another frame');
}); });
test("objects: isNumber", function() { test("isNumber", function() {
ok(!_.isNumber('string'), 'a string is not a number'); ok(!_.isNumber('string'), 'a string is not a number');
ok(!_.isNumber(arguments), 'the arguments object is not a number'); ok(!_.isNumber(arguments), 'the arguments object is not a number');
ok(!_.isNumber(undefined), 'undefined is not a number'); ok(!_.isNumber(undefined), 'undefined is not a number');
@@ -474,7 +496,7 @@ $(document).ready(function() {
ok(!_.isNumber('1'), 'numeric strings are not numbers'); ok(!_.isNumber('1'), 'numeric strings are not numbers');
}); });
test("objects: isBoolean", function() { test("isBoolean", function() {
ok(!_.isBoolean(2), 'a number is not a boolean'); ok(!_.isBoolean(2), 'a number is not a boolean');
ok(!_.isBoolean("string"), 'a string is not a boolean'); ok(!_.isBoolean("string"), 'a string is not a boolean');
ok(!_.isBoolean("false"), 'the string "false" is not a boolean'); ok(!_.isBoolean("false"), 'the string "false" is not a boolean');
@@ -488,27 +510,27 @@ $(document).ready(function() {
ok(_.isBoolean(iBoolean), 'even from another frame'); ok(_.isBoolean(iBoolean), 'even from another frame');
}); });
test("objects: isFunction", function() { test("isFunction", function() {
ok(!_.isFunction([1, 2, 3]), 'arrays are not functions'); ok(!_.isFunction([1, 2, 3]), 'arrays are not functions');
ok(!_.isFunction('moe'), 'strings are not functions'); ok(!_.isFunction('moe'), 'strings are not functions');
ok(_.isFunction(_.isFunction), 'but functions are'); ok(_.isFunction(_.isFunction), 'but functions are');
ok(_.isFunction(iFunction), 'even from another frame'); ok(_.isFunction(iFunction), 'even from another frame');
}); });
test("objects: isDate", function() { test("isDate", function() {
ok(!_.isDate(100), 'numbers are not dates'); ok(!_.isDate(100), 'numbers are not dates');
ok(!_.isDate({}), 'objects are not dates'); ok(!_.isDate({}), 'objects are not dates');
ok(_.isDate(new Date()), 'but dates are'); ok(_.isDate(new Date()), 'but dates are');
ok(_.isDate(iDate), 'even from another frame'); ok(_.isDate(iDate), 'even from another frame');
}); });
test("objects: isRegExp", function() { test("isRegExp", function() {
ok(!_.isRegExp(_.identity), 'functions are not RegExps'); ok(!_.isRegExp(_.identity), 'functions are not RegExps');
ok(_.isRegExp(/identity/), 'but RegExps are'); ok(_.isRegExp(/identity/), 'but RegExps are');
ok(_.isRegExp(iRegExp), 'even from another frame'); ok(_.isRegExp(iRegExp), 'even from another frame');
}); });
test("objects: isFinite", function() { test("isFinite", function() {
ok(!_.isFinite(undefined), 'undefined is not Finite'); ok(!_.isFinite(undefined), 'undefined is not Finite');
ok(!_.isFinite(null), 'null is not Finite'); ok(!_.isFinite(null), 'null is not Finite');
ok(!_.isFinite(NaN), 'NaN is not Finite'); ok(!_.isFinite(NaN), 'NaN is not Finite');
@@ -522,7 +544,7 @@ $(document).ready(function() {
ok(_.isFinite(-12.44), 'Floats are Finite'); ok(_.isFinite(-12.44), 'Floats are Finite');
}); });
test("objects: isNaN", function() { test("isNaN", function() {
ok(!_.isNaN(undefined), 'undefined is not NaN'); ok(!_.isNaN(undefined), 'undefined is not NaN');
ok(!_.isNaN(null), 'null is not NaN'); ok(!_.isNaN(null), 'null is not NaN');
ok(!_.isNaN(0), '0 is not NaN'); ok(!_.isNaN(0), '0 is not NaN');
@@ -530,14 +552,14 @@ $(document).ready(function() {
ok(_.isNaN(iNaN), 'even from another frame'); ok(_.isNaN(iNaN), 'even from another frame');
}); });
test("objects: isNull", function() { test("isNull", function() {
ok(!_.isNull(undefined), 'undefined is not null'); ok(!_.isNull(undefined), 'undefined is not null');
ok(!_.isNull(NaN), 'NaN is not null'); ok(!_.isNull(NaN), 'NaN is not null');
ok(_.isNull(null), 'but null is'); ok(_.isNull(null), 'but null is');
ok(_.isNull(iNull), 'even from another frame'); ok(_.isNull(iNull), 'even from another frame');
}); });
test("objects: isUndefined", function() { test("isUndefined", function() {
ok(!_.isUndefined(1), 'numbers are defined'); ok(!_.isUndefined(1), 'numbers are defined');
ok(!_.isUndefined(null), 'null is defined'); ok(!_.isUndefined(null), 'null is defined');
ok(!_.isUndefined(false), 'false is defined'); ok(!_.isUndefined(false), 'false is defined');
@@ -548,7 +570,7 @@ $(document).ready(function() {
}); });
if (window.ActiveXObject) { if (window.ActiveXObject) {
test("objects: IE host objects", function() { test("IE host objects", function() {
var xml = new ActiveXObject("Msxml2.DOMDocument.3.0"); var xml = new ActiveXObject("Msxml2.DOMDocument.3.0");
ok(!_.isNumber(xml)); ok(!_.isNumber(xml));
ok(!_.isBoolean(xml)); ok(!_.isBoolean(xml));
@@ -559,7 +581,7 @@ $(document).ready(function() {
}); });
} }
test("objects: tap", function() { test("tap", function() {
var intercepted = null; var intercepted = null;
var interceptor = function(obj) { intercepted = obj; }; var interceptor = function(obj) { intercepted = obj; };
var returned = _.tap(1, interceptor); var returned = _.tap(1, interceptor);

View File

@@ -14,18 +14,24 @@ $(document).ready(function() {
}); });
test("utility: identity", function() { test("#750 - Return _ instance.", 2, function() {
var instance = _([]);
ok(_(instance) === instance);
ok(new _(instance) === instance);
});
test("identity", function() {
var moe = {name : 'moe'}; var moe = {name : 'moe'};
equal(_.identity(moe), moe, 'moe is the same as his identity'); equal(_.identity(moe), moe, 'moe is the same as his identity');
}); });
test("utility: uniqueId", function() { test("uniqueId", function() {
var ids = [], i = 0; var ids = [], i = 0;
while(i++ < 100) ids.push(_.uniqueId()); while(i++ < 100) ids.push(_.uniqueId());
equal(_.uniq(ids).length, ids.length, 'can generate a globally-unique stream of ids'); equal(_.uniq(ids).length, ids.length, 'can generate a globally-unique stream of ids');
}); });
test("utility: times", function() { test("times", function() {
var vals = []; var vals = [];
_.times(3, function (i) { vals.push(i); }); _.times(3, function (i) { vals.push(i); });
ok(_.isEqual(vals, [0,1,2]), "is 0 indexed"); ok(_.isEqual(vals, [0,1,2]), "is 0 indexed");
@@ -35,7 +41,7 @@ $(document).ready(function() {
ok(_.isEqual(vals, [0,1,2]), "works as a wrapper"); ok(_.isEqual(vals, [0,1,2]), "works as a wrapper");
}); });
test("utility: mixin", function() { test("mixin", function() {
_.mixin({ _.mixin({
myReverse: function(string) { myReverse: function(string) {
return string.split('').reverse().join(''); return string.split('').reverse().join('');
@@ -45,12 +51,21 @@ $(document).ready(function() {
equal(_('champ').myReverse(), 'pmahc', 'mixed in a function to the OOP wrapper'); equal(_('champ').myReverse(), 'pmahc', 'mixed in a function to the OOP wrapper');
}); });
test("utility: _.escape", function() { test("_.escape", function() {
equal(_.escape("Curly & Moe"), "Curly &amp; Moe"); equal(_.escape("Curly & Moe"), "Curly &amp; Moe");
equal(_.escape("Curly &amp; Moe"), "Curly &amp;amp; Moe"); equal(_.escape("Curly &amp; Moe"), "Curly &amp;amp; Moe");
equal(_.escape(null), '');
}); });
test("utility: template", function() { test("_.unescape", function() {
var string = "Curly & Moe";
equal(_.unescape("Curly &amp; Moe"), string);
equal(_.unescape("Curly &amp;amp; Moe"), "Curly &amp; Moe");
equal(_.unescape(null), '');
equal(_.unescape(_.escape(string)), string);
});
test("template", function() {
var basicTemplate = _.template("<%= thing %> is gettin' on my noives!"); var basicTemplate = _.template("<%= thing %> is gettin' on my noives!");
var result = basicTemplate({thing : 'This'}); var result = basicTemplate({thing : 'This'});
equal(result, "This is gettin' on my noives!", 'can do basic attribute interpolation'); equal(result, "This is gettin' on my noives!", 'can do basic attribute interpolation');
@@ -156,6 +171,14 @@ $(document).ready(function() {
equal(templateWithNull({planet : "world"}), "a null undefined world", "can handle missing escape and evaluate settings"); equal(templateWithNull({planet : "world"}), "a null undefined world", "can handle missing escape and evaluate settings");
}); });
test('_.template provides the generated function source, when a SyntaxError occurs', function() {
try {
_.template('<b><%= if %></b>');
} catch (e) {
ok(e.source.indexOf('( if )') > 0);
}
});
test('_.template handles \\u2028 & \\u2029', function() { test('_.template handles \\u2028 & \\u2029', function() {
var tmpl = _.template('<p>\u2028<%= "\\u2028\\u2029" %>\u2029</p>'); var tmpl = _.template('<p>\u2028<%= "\\u2028\\u2029" %>\u2029</p>');
strictEqual(tmpl(), '<p>\u2028\u2028\u2029\u2029</p>'); strictEqual(tmpl(), '<p>\u2028\u2028\u2029\u2029</p>');
@@ -212,4 +235,10 @@ $(document).ready(function() {
templateEscaped({f: function(){ ok(!(countEscaped++)); }}); templateEscaped({f: function(){ ok(!(countEscaped++)); }});
}); });
test('#746 - _.template settings are not modified.', 1, function() {
var settings = {};
_.template('', null, settings);
deepEqual(settings, {});
});
}); });

View File

@@ -1,11 +1,11 @@
/** /**
* QUnit v1.8.0 - A JavaScript Unit Testing Framework * QUnit v1.10.0 - A JavaScript Unit Testing Framework
* *
* http://docs.jquery.com/QUnit * http://qunitjs.com
* *
* Copyright (c) 2012 John Resig, Jörn Zaefferer * Copyright 2012 jQuery Foundation and other contributors
* Dual licensed under the MIT (MIT-LICENSE.txt) * Released under the MIT license.
* or GPL (GPL-LICENSE.txt) licenses. * http://jquery.org/license
*/ */
/** Font Family and Sizes */ /** Font Family and Sizes */
@@ -20,7 +20,7 @@
/** Resets */ /** Resets */
#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { #qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
@@ -38,10 +38,10 @@
line-height: 1em; line-height: 1em;
font-weight: normal; font-weight: normal;
border-radius: 15px 15px 0 0; border-radius: 5px 5px 0 0;
-moz-border-radius: 15px 15px 0 0; -moz-border-radius: 5px 5px 0 0;
-webkit-border-top-right-radius: 15px; -webkit-border-top-right-radius: 5px;
-webkit-border-top-left-radius: 15px; -webkit-border-top-left-radius: 5px;
} }
#qunit-header a { #qunit-header a {
@@ -54,9 +54,9 @@
color: #fff; color: #fff;
} }
#qunit-header label { #qunit-testrunner-toolbar label {
display: inline-block; display: inline-block;
padding-left: 0.5em; padding: 0 .5em 0 .1em;
} }
#qunit-banner { #qunit-banner {
@@ -67,6 +67,7 @@
padding: 0.5em 0 0.5em 2em; padding: 0.5em 0 0.5em 2em;
color: #5E740B; color: #5E740B;
background-color: #eee; background-color: #eee;
overflow: hidden;
} }
#qunit-userAgent { #qunit-userAgent {
@@ -76,6 +77,9 @@
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
} }
#qunit-modulefilter-container {
float: right;
}
/** Tests: Pass/Fail */ /** Tests: Pass/Fail */
@@ -113,13 +117,9 @@
background-color: #fff; background-color: #fff;
border-radius: 15px; border-radius: 5px;
-moz-border-radius: 15px; -moz-border-radius: 5px;
-webkit-border-radius: 15px; -webkit-border-radius: 5px;
box-shadow: inset 0px 2px 13px #999;
-moz-box-shadow: inset 0px 2px 13px #999;
-webkit-box-shadow: inset 0px 2px 13px #999;
} }
#qunit-tests table { #qunit-tests table {
@@ -162,8 +162,7 @@
#qunit-tests b.failed { color: #710909; } #qunit-tests b.failed { color: #710909; }
#qunit-tests li li { #qunit-tests li li {
margin: 0.5em; padding: 5px;
padding: 0.4em 0.5em 0.4em 0.5em;
background-color: #fff; background-color: #fff;
border-bottom: none; border-bottom: none;
list-style-position: inside; list-style-position: inside;
@@ -172,9 +171,9 @@
/*** Passing Styles */ /*** Passing Styles */
#qunit-tests li li.pass { #qunit-tests li li.pass {
color: #5E740B; color: #3c510c;
background-color: #fff; background-color: #fff;
border-left: 26px solid #C6E746; border-left: 10px solid #C6E746;
} }
#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
@@ -190,15 +189,15 @@
#qunit-tests li li.fail { #qunit-tests li li.fail {
color: #710909; color: #710909;
background-color: #fff; background-color: #fff;
border-left: 26px solid #EE5757; border-left: 10px solid #EE5757;
white-space: pre; white-space: pre;
} }
#qunit-tests > li:last-child { #qunit-tests > li:last-child {
border-radius: 0 0 15px 15px; border-radius: 0 0 5px 5px;
-moz-border-radius: 0 0 15px 15px; -moz-border-radius: 0 0 5px 5px;
-webkit-border-bottom-right-radius: 15px; -webkit-border-bottom-right-radius: 5px;
-webkit-border-bottom-left-radius: 15px; -webkit-border-bottom-left-radius: 5px;
} }
#qunit-tests .fail { color: #000000; background-color: #EE5757; } #qunit-tests .fail { color: #000000; background-color: #EE5757; }

View File

@@ -1,11 +1,11 @@
/** /**
* QUnit v1.8.0 - A JavaScript Unit Testing Framework * QUnit v1.10.0 - A JavaScript Unit Testing Framework
* *
* http://docs.jquery.com/QUnit * http://qunitjs.com
* *
* Copyright (c) 2012 John Resig, Jörn Zaefferer * Copyright 2012 jQuery Foundation and other contributors
* Dual licensed under the MIT (MIT-LICENSE.txt) * Released under the MIT license.
* or GPL (GPL-LICENSE.txt) licenses. * http://jquery.org/license
*/ */
(function( window ) { (function( window ) {
@@ -17,6 +17,8 @@ var QUnit,
fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""), fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""),
toString = Object.prototype.toString, toString = Object.prototype.toString,
hasOwn = Object.prototype.hasOwnProperty, hasOwn = Object.prototype.hasOwnProperty,
// Keep a local reference to Date (GH-283)
Date = window.Date,
defined = { defined = {
setTimeout: typeof window.setTimeout !== "undefined", setTimeout: typeof window.setTimeout !== "undefined",
sessionStorage: (function() { sessionStorage: (function() {
@@ -304,7 +306,8 @@ QUnit = {
// call on start of module test to prepend name to all tests // call on start of module test to prepend name to all tests
module: function( name, testEnvironment ) { module: function( name, testEnvironment ) {
config.currentModule = name; config.currentModule = name;
config.currentModuleTestEnviroment = testEnvironment; config.currentModuleTestEnvironment = testEnvironment;
config.modules[name] = true;
}, },
asyncTest: function( testName, expected, callback ) { asyncTest: function( testName, expected, callback ) {
@@ -336,7 +339,7 @@ QUnit = {
async: async, async: async,
callback: callback, callback: callback,
module: config.currentModule, module: config.currentModule,
moduleTestEnvironment: config.currentModuleTestEnviroment, moduleTestEnvironment: config.currentModuleTestEnvironment,
stack: sourceFromStacktrace( 2 ) stack: sourceFromStacktrace( 2 )
}); });
@@ -349,7 +352,11 @@ QUnit = {
// Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. // Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
expect: function( asserts ) { expect: function( asserts ) {
config.current.expected = asserts; if (arguments.length === 1) {
config.current.expected = asserts;
} else {
return config.current.expected;
}
}, },
start: function( count ) { start: function( count ) {
@@ -403,6 +410,8 @@ QUnit = {
QUnit.assert = { QUnit.assert = {
/** /**
* Asserts rough true-ish result. * Asserts rough true-ish result.
* @name ok
* @function
* @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
*/ */
ok: function( result, msg ) { ok: function( result, msg ) {
@@ -413,6 +422,8 @@ QUnit.assert = {
var source, var source,
details = { details = {
module: config.current.module,
name: config.current.testName,
result: result, result: result,
message: msg message: msg
}; };
@@ -437,36 +448,59 @@ QUnit.assert = {
/** /**
* Assert that the first two arguments are equal, with an optional message. * Assert that the first two arguments are equal, with an optional message.
* Prints out both actual and expected values. * 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" ); * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" );
*/ */
equal: function( actual, expected, message ) { equal: function( actual, expected, message ) {
QUnit.push( expected == actual, actual, expected, message ); QUnit.push( expected == actual, actual, expected, message );
}, },
/**
* @name notEqual
* @function
*/
notEqual: function( actual, expected, message ) { notEqual: function( actual, expected, message ) {
QUnit.push( expected != actual, actual, expected, message ); QUnit.push( expected != actual, actual, expected, message );
}, },
/**
* @name deepEqual
* @function
*/
deepEqual: function( actual, expected, message ) { deepEqual: function( actual, expected, message ) {
QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
}, },
/**
* @name notDeepEqual
* @function
*/
notDeepEqual: function( actual, expected, message ) { notDeepEqual: function( actual, expected, message ) {
QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message );
}, },
/**
* @name strictEqual
* @function
*/
strictEqual: function( actual, expected, message ) { strictEqual: function( actual, expected, message ) {
QUnit.push( expected === actual, actual, expected, message ); QUnit.push( expected === actual, actual, expected, message );
}, },
/**
* @name notStrictEqual
* @function
*/
notStrictEqual: function( actual, expected, message ) { notStrictEqual: function( actual, expected, message ) {
QUnit.push( expected !== actual, actual, expected, message ); QUnit.push( expected !== actual, actual, expected, message );
}, },
raises: function( block, expected, message ) { throws: function( block, expected, message ) {
var actual, var actual,
ok = false; ok = false;
// 'expected' is optional
if ( typeof expected === "string" ) { if ( typeof expected === "string" ) {
message = expected; message = expected;
expected = null; expected = null;
@@ -494,18 +528,29 @@ QUnit.assert = {
} else if ( expected.call( {}, actual ) === true ) { } else if ( expected.call( {}, actual ) === true ) {
ok = true; ok = true;
} }
}
QUnit.push( ok, actual, null, message ); QUnit.push( ok, actual, null, message );
} else {
QUnit.pushFailure( message, null, 'No exception was thrown.' );
}
} }
}; };
// @deprecated: Kept assertion helpers in root for backwards compatibility /**
* @deprecate since 1.8.0
* Kept assertion helpers in root for backwards compatibility
*/
extend( QUnit, QUnit.assert ); extend( QUnit, QUnit.assert );
/** /**
* @deprecated: Kept for backwards compatibility * @deprecated since 1.9.0
* next step: remove entirely * Kept global "raises()" for backwards compatibility
*/
QUnit.raises = QUnit.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.equals = function() {
QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" ); QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" );
@@ -549,7 +594,23 @@ config = {
// when enabled, all tests must call expect() // when enabled, all tests must call expect()
requireExpects: false, requireExpects: false,
urlConfig: [ "noglobals", "notrycatch" ], // add checkboxes that are persisted in the query-string
// when enabled, the id is set to `true` as a `QUnit.config` property
urlConfig: [
{
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."
},
{
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."
}
],
// Set of all modules.
modules: {},
// logging callback queues // logging callback queues
begin: [], begin: [],
@@ -661,17 +722,10 @@ extend( QUnit, {
}, },
// Resets the test setup. Useful for tests that modify the DOM. // Resets the test setup. Useful for tests that modify the DOM.
// If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
reset: function() { reset: function() {
var fixture; var fixture = id( "qunit-fixture" );
if ( fixture ) {
if ( window.jQuery ) { fixture.innerHTML = config.fixture;
jQuery( "#qunit-fixture" ).html( config.fixture );
} else {
fixture = id( "qunit-fixture" );
if ( fixture ) {
fixture.innerHTML = config.fixture;
}
} }
}, },
@@ -732,6 +786,8 @@ extend( QUnit, {
var output, source, var output, source,
details = { details = {
module: config.current.module,
name: config.current.testName,
result: result, result: result,
message: message, message: message,
actual: actual, actual: actual,
@@ -770,26 +826,36 @@ extend( QUnit, {
}); });
}, },
pushFailure: function( message, source ) { pushFailure: function( message, source, actual ) {
if ( !config.current ) { if ( !config.current ) {
throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) ); throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) );
} }
var output, var output,
details = { details = {
module: config.current.module,
name: config.current.testName,
result: false, result: false,
message: message message: message
}; };
message = escapeInnerText(message ) || "error"; message = escapeInnerText( message ) || "error";
message = "<span class='test-message'>" + message + "</span>"; message = "<span class='test-message'>" + message + "</span>";
output = message; output = message;
output += "<table>";
if ( actual ) {
output += "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeInnerText( actual ) + "</pre></td></tr>";
}
if ( source ) { if ( source ) {
details.source = source; details.source = source;
output += "<table><tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr></table>"; output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr>";
} }
output += "</table>";
runLoggingCallbacks( "log", QUnit, details ); runLoggingCallbacks( "log", QUnit, details );
config.current.assertions.push({ config.current.assertions.push({
@@ -859,7 +925,9 @@ QUnit.load = function() {
runLoggingCallbacks( "begin", QUnit, {} ); runLoggingCallbacks( "begin", QUnit, {} );
// Initialize the config, saving the execution queue // Initialize the config, saving the execution queue
var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, urlConfigCheckboxes, moduleFilter,
numModules = 0,
moduleFilterHtml = "",
urlConfigHtml = "", urlConfigHtml = "",
oldconfig = extend( {}, config ); oldconfig = extend( {}, config );
@@ -872,10 +940,26 @@ QUnit.load = function() {
for ( i = 0; i < len; i++ ) { for ( i = 0; i < len; i++ ) {
val = config.urlConfig[i]; val = config.urlConfig[i];
config[val] = QUnit.urlParams[val]; if ( typeof val === "string" ) {
urlConfigHtml += "<label><input name='" + val + "' type='checkbox'" + ( config[val] ? " checked='checked'" : "" ) + ">" + val + "</label>"; val = {
id: val,
label: val,
tooltip: "[no tooltip available]"
};
}
config[ val.id ] = QUnit.urlParams[ val.id ];
urlConfigHtml += "<input id='qunit-urlconfig-" + val.id + "' name='" + val.id + "' type='checkbox'" + ( config[ val.id ] ? " checked='checked'" : "" ) + " title='" + val.tooltip + "'><label for='qunit-urlconfig-" + val.id + "' title='" + val.tooltip + "'>" + val.label + "</label>";
} }
moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label><select id='qunit-modulefilter' name='modulefilter'><option value='' " + ( config.module === undefined ? "selected" : "" ) + ">< All Modules ></option>";
for ( i in config.modules ) {
if ( config.modules.hasOwnProperty( i ) ) {
numModules += 1;
moduleFilterHtml += "<option value='" + encodeURIComponent(i) + "' " + ( config.module === i ? "selected" : "" ) + ">" + i + "</option>";
}
}
moduleFilterHtml += "</select>";
// `userAgent` initialized at top of scope // `userAgent` initialized at top of scope
userAgent = id( "qunit-userAgent" ); userAgent = id( "qunit-userAgent" );
if ( userAgent ) { if ( userAgent ) {
@@ -885,12 +969,7 @@ QUnit.load = function() {
// `banner` initialized at top of scope // `banner` initialized at top of scope
banner = id( "qunit-header" ); banner = id( "qunit-header" );
if ( banner ) { if ( banner ) {
banner.innerHTML = "<a href='" + QUnit.url({ filter: undefined }) + "'>" + banner.innerHTML + "</a> " + urlConfigHtml; banner.innerHTML = "<a href='" + QUnit.url({ filter: undefined, module: undefined, testNumber: undefined }) + "'>" + banner.innerHTML + "</a> ";
addEvent( banner, "change", function( event ) {
var params = {};
params[ event.target.name ] = event.target.checked ? true : undefined;
window.location = QUnit.url( params );
});
} }
// `toolbar` initialized at top of scope // `toolbar` initialized at top of scope
@@ -931,8 +1010,31 @@ QUnit.load = function() {
// `label` initialized at top of scope // `label` initialized at top of scope
label = document.createElement( "label" ); label = document.createElement( "label" );
label.setAttribute( "for", "qunit-filter-pass" ); label.setAttribute( "for", "qunit-filter-pass" );
label.setAttribute( "title", "Only show tests and assertons that fail. Stored in sessionStorage." );
label.innerHTML = "Hide passed tests"; label.innerHTML = "Hide passed tests";
toolbar.appendChild( label ); toolbar.appendChild( label );
urlConfigCheckboxes = document.createElement( 'span' );
urlConfigCheckboxes.innerHTML = urlConfigHtml;
addEvent( urlConfigCheckboxes, "change", function( event ) {
var params = {};
params[ event.target.name ] = event.target.checked ? true : undefined;
window.location = QUnit.url( params );
});
toolbar.appendChild( urlConfigCheckboxes );
if (numModules > 1) {
moduleFilter = document.createElement( 'span' );
moduleFilter.setAttribute( 'id', 'qunit-modulefilter-container' );
moduleFilter.innerHTML = moduleFilterHtml;
addEvent( moduleFilter, "change", function() {
var selectBox = moduleFilter.getElementsByTagName("select")[0],
selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value);
window.location = QUnit.url( { module: ( selectedModule === "" ) ? undefined : selectedModule } );
});
toolbar.appendChild(moduleFilter);
}
} }
// `main` initialized at top of scope // `main` initialized at top of scope
@@ -970,9 +1072,9 @@ window.onerror = function ( error, filePath, linerNr ) {
} }
QUnit.pushFailure( error, filePath + ":" + linerNr ); QUnit.pushFailure( error, filePath + ":" + linerNr );
} else { } else {
QUnit.test( "global failure", function() { QUnit.test( "global failure", extend( function() {
QUnit.pushFailure( error, filePath + ":" + linerNr ); QUnit.pushFailure( error, filePath + ":" + linerNr );
}); }, { validTest: validTest } ) );
} }
return false; return false;
} }
@@ -1039,6 +1141,11 @@ function done() {
} }
} }
// scroll back to top to show results
if ( window.scrollTo ) {
window.scrollTo(0, 0);
}
runLoggingCallbacks( "done", QUnit, { runLoggingCallbacks( "done", QUnit, {
failed: config.stats.bad, failed: config.stats.bad,
passed: passed, passed: passed,
@@ -1051,14 +1158,20 @@ function done() {
function validTest( test ) { function validTest( test ) {
var include, var include,
filter = config.filter && config.filter.toLowerCase(), filter = config.filter && config.filter.toLowerCase(),
module = config.module, module = config.module && config.module.toLowerCase(),
fullName = (test.module + ": " + test.testName).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 ) { if ( config.testNumber ) {
return test.testNumber === config.testNumber; return test.testNumber === config.testNumber;
} }
if ( module && test.module !== module ) { if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) {
return false; return false;
} }
@@ -1335,7 +1448,8 @@ QUnit.equiv = (function() {
a.global === b.global && a.global === b.global &&
// (gmi) ... // (gmi) ...
a.ignoreCase === b.ignoreCase && a.ignoreCase === b.ignoreCase &&
a.multiline === b.multiline; a.multiline === b.multiline &&
a.sticky === b.sticky;
}, },
// - skip when the property is a method of an instance (OOP) // - skip when the property is a method of an instance (OOP)

View File

@@ -5,28 +5,29 @@
// Oliver Steele's Functional, and John Resig's Micro-Templating. // Oliver Steele's Functional, and John Resig's Micro-Templating.
// For all details and documentation: // For all details and documentation:
// http://documentcloud.github.com/underscore // http://documentcloud.github.com/underscore
(function(){var s=this,L=s._,o={},k=Array.prototype,p=Object.prototype,M=k.push,h=k.slice,N=k.unshift,m=p.toString,O=p.hasOwnProperty,z=k.forEach,A=k.map,B=k.reduce,C=k.reduceRight,D=k.filter,E=k.every,F=k.some,q=k.indexOf,G=k.lastIndexOf,p=Array.isArray,P=Object.keys,t=Function.prototype.bind,b=function(a){return new l(a)};"undefined"!==typeof exports?("undefined"!==typeof module&&module.exports&&(exports=module.exports=b),exports._=b):s._=b;b.VERSION="1.3.3";var i=b.each=b.forEach=function(a,c, (function(){var s=this,K=s._,o={},k=Array.prototype,p=Object.prototype,L=k.push,g=k.slice,l=p.toString,M=p.hasOwnProperty,y=k.forEach,z=k.map,A=k.reduce,B=k.reduceRight,C=k.filter,D=k.every,E=k.some,q=k.indexOf,F=k.lastIndexOf,p=Array.isArray,N=Object.keys,t=Function.prototype.bind,b=function(a){if(a instanceof b)return a;if(!(this instanceof b))return new b(a);this._wrapped=a};"undefined"!==typeof exports?("undefined"!==typeof module&&module.exports&&(exports=module.exports=b),exports._=b):s._=b;
d){if(a!=null)if(z&&a.forEach===z)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e<f;e++){if(c.call(d,a[e],e,a)===o)break}else for(e in a)if(b.has(a,e)&&c.call(d,a[e],e,a)===o)break};b.map=b.collect=function(a,c,d){var b=[];if(a==null)return b;if(A&&a.map===A)return a.map(c,d);i(a,function(a,g,j){b[b.length]=c.call(d,a,g,j)});return b};b.reduce=b.foldl=b.inject=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(B&&a.reduce===B){e&&(c=b.bind(c,e));return f?a.reduce(c, b.VERSION="1.3.3";var j=b.each=b.forEach=function(a,c,d){if(a!=null)if(y&&a.forEach===y)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,h=a.length;e<h;e++){if(c.call(d,a[e],e,a)===o)break}else for(e in a)if(b.has(a,e)&&c.call(d,a[e],e,a)===o)break};b.map=b.collect=function(a,c,b){var e=[];if(a==null)return e;if(z&&a.map===z)return a.map(c,b);j(a,function(a,f,i){e[e.length]=c.call(b,a,f,i)});return e};b.reduce=b.foldl=b.inject=function(a,c,d,e){var h=arguments.length>2;a==null&&(a=[]);if(A&&
d):a.reduce(c)}i(a,function(a,b,h){if(f)d=c.call(e,d,a,b,h);else{d=a;f=true}});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(C&&a.reduceRight===C){e&&(c=b.bind(c,e));return f?a.reduceRight(c,d):a.reduceRight(c)}var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect=function(a,c,b){var e;H(a,function(a,g,j){if(c.call(b,a,g,j)){e= a.reduce===A){e&&(c=b.bind(c,e));return h?a.reduce(c,d):a.reduce(c)}j(a,function(a,b,g){if(h)d=c.call(e,d,a,b,g);else{d=a;h=true}});if(!h)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var h=arguments.length>2;a==null&&(a=[]);if(B&&a.reduceRight===B){e&&(c=b.bind(c,e));return h?a.reduceRight(c,d):a.reduceRight(c)}var f=b.toArray(a).reverse();e&&!h&&(c=b.bind(c,e));return h?b.reduce(f,c,d,e):b.reduce(f,c)};b.find=b.detect=function(a,
a;return true}});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(D&&a.filter===D)return a.filter(c,b);i(a,function(a,g,j){c.call(b,a,g,j)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;i(a,function(a,g,j){c.call(b,a,g,j)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(E&&a.every===E)return a.every(c,b);i(a,function(a,g,j){if(!(e=e&&c.call(b,a,g,j)))return o});return!!e};var H=b.some=b.any= c,b){var e;G(a,function(a,f,i){if(c.call(b,a,f,i)){e=a;return true}});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(C&&a.filter===C)return a.filter(c,b);j(a,function(a,f,i){c.call(b,a,f,i)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,f,i){c.call(b,a,f,i)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,d){c||(c=b.identity);var e=true;if(a==null)return e;if(D&&a.every===D)return a.every(c,d);j(a,function(a,b,
function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(F&&a.some===F)return a.some(c,d);i(a,function(a,b,j){if(e||(e=c.call(d,a,b,j)))return o});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;if(q&&a.indexOf===q)return a.indexOf(c)!=-1;return b=H(a,function(a){return a===c})};b.invoke=function(a,c){var d=h.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c:a[c]).apply(a,d)})};b.pluck=function(a,c){return b.map(a,function(a){return a[c]})}; i){if(!(e=e&&c.call(d,a,b,i)))return o});return!!e};var G=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(E&&a.some===E)return a.some(c,d);j(a,function(a,b,i){if(e||(e=c.call(d,a,b,i)))return o});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;if(q&&a.indexOf===q)return a.indexOf(c)!=-1;return b=G(a,function(a){return a===c})};b.invoke=function(a,c){var d=g.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c:a[c]).apply(a,
b.max=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0]&&a.length<65535)return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};i(a,function(a,b,j){b=c?c.call(d,a,b,j):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0]&&a.length<65535)return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};i(a,function(a,b,j){b=c?c.call(d,a,b,j):a;b<e.computed&&(e={value:a,computed:b})}); d)})};b.pluck=function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0]&&a.length<65535)return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,i){b=c?c.call(d,a,b,i):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0]&&a.length<65535)return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,
return e.value};b.shuffle=function(a){var c,b=0,e=[];i(a,function(a){c=Math.floor(Math.random()*++b);e[b-1]=e[c];e[c]=a});return e};b.sortBy=function(a,c,d){var e=I(a,c);return b.pluck(b.map(a,function(a,c,b){return{value:a,criteria:e.call(d,a,c,b)}}).sort(function(a,c){var b=a.criteria,d=c.criteria;return b===void 0?1:d===void 0?-1:b<d?-1:b>d?1:0}),"value")};var I=function(a,c){return b.isFunction(c)?c:function(a){return a[c]}},J=function(a,c,b){var e={},f=I(a,c);i(a,function(a,c){var h=f(a,c);b(e, function(a,b,i){b=c?c.call(d,a,b,i):a;b<e.computed&&(e={value:a,computed:b})});return e.value};b.shuffle=function(a){var c,b=0,e=[];j(a,function(a){c=Math.floor(Math.random()*++b);e[b-1]=e[c];e[c]=a});return e};b.sortBy=function(a,c,d){var e=H(a,c);return b.pluck(b.map(a,function(a,c,b){return{value:a,criteria:e.call(d,a,c,b)}}).sort(function(a,c){var b=a.criteria,d=c.criteria;return b===void 0?1:d===void 0?-1:b<d?-1:b>d?1:0}),"value")};var H=function(a,c){return b.isFunction(c)?c:function(a){return a[c]}},
h,a)});return e};b.groupBy=function(a,c){return J(a,c,function(a,c,b){(a[c]||(a[c]=[])).push(b)})};b.countBy=function(a,c){return J(a,c,function(a,c){a[c]||(a[c]=0);a[c]++})};b.sortedIndex=function(a,c,d){d||(d=b.identity);for(var c=d(c),e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<c?e=g+1:f=g}return e};b.toArray=function(a){return!a?[]:b.isArray(a)||b.isArguments(a)?h.call(a):b.isFunction(a.toArray)?a.toArray():b.values(a)};b.size=function(a){return b.isArray(a)?a.length:b.keys(a).length};b.first=b.head= I=function(a,c,b){var e={},h=H(a,c);j(a,function(a,c){var g=h(a,c);b(e,g,a)});return e};b.groupBy=function(a,c){return I(a,c,function(a,c,b){(a[c]||(a[c]=[])).push(b)})};b.countBy=function(a,c){return I(a,c,function(a,c){a[c]||(a[c]=0);a[c]++})};b.sortedIndex=function(a,c,d){d||(d=b.identity);for(var c=d(c),e=0,h=a.length;e<h;){var f=e+h>>1;d(a[f])<c?e=f+1:h=f}return e};b.toArray=function(a){return!a?[]:b.isArray(a)||b.isArguments(a)?g.call(a):b.isFunction(a.toArray)?a.toArray():b.values(a)};b.size=
b.take=function(a,c,b){return c!=null&&!b?h.call(a,0,c):a[0]};b.initial=function(a,c,b){return h.call(a,0,a.length-(c==null||b?1:c))};b.last=function(a,c,b){return c!=null&&!b?h.call(a,Math.max(a.length-c,0)):a[a.length-1]};b.rest=b.tail=function(a,c,b){return h.call(a,c==null||b?1:c)};b.compact=function(a){return b.filter(a,function(a){return!!a})};var r=function(a,c,d){i(a,function(a){b.isArray(a)?c?M.apply(d,a):r(a,c,d):d.push(a)});return d};b.flatten=function(a,c){return r(a,c,[])};b.without= function(a){return a.length===+a.length?a.length:b.keys(a).length};b.first=b.head=b.take=function(a,c,b){return c!=null&&!b?g.call(a,0,c):a[0]};b.initial=function(a,c,b){return g.call(a,0,a.length-(c==null||b?1:c))};b.last=function(a,c,b){return c!=null&&!b?g.call(a,Math.max(a.length-c,0)):a[a.length-1]};b.rest=b.tail=b.drop=function(a,c,b){return g.call(a,c==null||b?1:c)};b.compact=function(a){return b.filter(a,function(a){return!!a})};var r=function(a,c,d){j(a,function(a){b.isArray(a)?c?L.apply(d,
function(a){return b.difference(a,h.call(arguments,1))};b.uniq=b.unique=function(a,c,d){var d=d?b.map(a,d):a,e=[];b.reduce(d,function(d,g,j){if(c?b.last(d)!==g||!d.length:!b.include(d,g)){d.push(g);e.push(a[j])}return d},[]);return e};b.union=function(){return b.uniq(r(arguments,true,[]))};b.intersection=function(a){var c=h.call(arguments,1);return b.filter(b.uniq(a),function(a){return b.every(c,function(c){return b.indexOf(c,a)>=0})})};b.difference=function(a){var c=r(h.call(arguments,1),true,[]); a):r(a,c,d):d.push(a)});return d};b.flatten=function(a,b){return r(a,b,[])};b.without=function(a){return b.difference(a,g.call(arguments,1))};b.uniq=b.unique=function(a,c,d){var d=d?b.map(a,d):a,e=[];b.reduce(d,function(d,f,i){if(c?b.last(d)!==f||!d.length:!b.include(d,f)){d.push(f);e.push(a[i])}return d},[]);return e};b.union=function(){return b.uniq(r(arguments,true,[]))};b.intersection=function(a){var c=g.call(arguments,1);return b.filter(b.uniq(a),function(a){return b.every(c,function(c){return b.indexOf(c,
return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=h.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.zipObject=function(a,c){for(var b={},e=0,f=a.length;e<f;e++)b[a[e]]=c[e];return b};b.indexOf=function(a,c,d){if(a==null)return-1;var e;if(d){d=b.sortedIndex(a,c);return a[d]===c?d:-1}if(q&&a.indexOf===q)return a.indexOf(c);d=0;for(e=a.length;d<e;d++)if(a[d]===c)return d;return-1};b.lastIndexOf=function(a,c){if(a==null)return-1; a)>=0})})};b.difference=function(a){var c=r(g.call(arguments,1),true,[]);return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=g.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.object=function(a,b){for(var d={},e=0,h=a.length;e<h;e++)b?d[a[e]]=b[e]:d[a[e][0]]=a[e][1];return d};b.indexOf=function(a,c,d){if(a==null)return-1;var e;if(d){d=b.sortedIndex(a,c);return a[d]===c?d:-1}if(q&&a.indexOf===q)return a.indexOf(c);d=
if(G&&a.lastIndexOf===G)return a.lastIndexOf(c);for(var b=a.length;b--;)if(a[b]===c)return b;return-1};b.range=function(a,c,b){if(arguments.length<=1){c=a||0;a=0}for(var b=arguments[2]||1,e=Math.max(Math.ceil((c-a)/b),0),f=0,g=Array(e);f<e;){g[f++]=a;a=a+b}return g};var K=function(){};b.bind=function(a,c){var d,e;if(a.bind===t&&t)return t.apply(a,h.call(arguments,1));if(!b.isFunction(a))throw new TypeError;e=h.call(arguments,2);return d=function(){if(!(this instanceof d))return a.apply(c,e.concat(h.call(arguments))); 0;for(e=a.length;d<e;d++)if(a[d]===c)return d;return-1};b.lastIndexOf=function(a,b){if(a==null)return-1;if(F&&a.lastIndexOf===F)return a.lastIndexOf(b);for(var d=a.length;d--;)if(a[d]===b)return d;return-1};b.range=function(a,b,d){if(arguments.length<=1){b=a||0;a=0}for(var d=arguments[2]||1,e=Math.max(Math.ceil((b-a)/d),0),h=0,f=Array(e);h<e;){f[h++]=a;a=a+d}return f};var J=function(){};b.bind=function(a,c){var d,e;if(a.bind===t&&t)return t.apply(a,g.call(arguments,1));if(!b.isFunction(a))throw new TypeError;
K.prototype=a.prototype;var b=new K,g=a.apply(b,e.concat(h.call(arguments)));return Object(g)===g?g:b}};b.bindAll=function(a){var c=h.call(arguments,1);c.length==0&&(c=b.functions(a));i(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a,c){var d={};c||(c=b.identity);return function(){var e=c.apply(this,arguments);return b.has(d,e)?d[e]:d[e]=a.apply(this,arguments)}};b.delay=function(a,b){var d=h.call(arguments,2);return setTimeout(function(){return a.apply(null,d)},b)};b.defer=function(a){return b.delay.apply(b, e=g.call(arguments,2);return d=function(){if(!(this instanceof d))return a.apply(c,e.concat(g.call(arguments)));J.prototype=a.prototype;var b=new J,f=a.apply(b,e.concat(g.call(arguments)));return Object(f)===f?f:b}};b.bindAll=function(a){var c=g.call(arguments,1);c.length==0&&(c=b.functions(a));j(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a,c){var d={};c||(c=b.identity);return function(){var e=c.apply(this,arguments);return b.has(d,e)?d[e]:d[e]=a.apply(this,arguments)}};b.delay=
[a,1].concat(h.call(arguments,1)))};b.throttle=function(a,c){var d,e,f,g,j,h,i=b.debounce(function(){j=g=false},c);return function(){d=this;e=arguments;f||(f=setTimeout(function(){f=null;j&&a.apply(d,e);i()},c));if(g)j=true;else{g=true;h=a.apply(d,e)}i();return h}};b.debounce=function(a,b,d){var e;return function(){var f=this,g=arguments,h=d&&!e;clearTimeout(e);e=setTimeout(function(){e=null;d||a.apply(f,g)},b);h&&a.apply(f,g)}};b.once=function(a){var b=false,d;return function(){if(b)return d;b=true; function(a,b){var d=g.call(arguments,2);return setTimeout(function(){return a.apply(null,d)},b)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(g.call(arguments,1)))};b.throttle=function(a,c){var d,e,h,f,i,g,j=b.debounce(function(){i=f=false},c);return function(){d=this;e=arguments;h||(h=setTimeout(function(){h=null;i&&(g=a.apply(d,e));j()},c));if(f)i=true;else{f=true;g=a.apply(d,e)}j();return g}};b.debounce=function(a,b,d){var e;return function(){var h=this,f=arguments,i=d&&!e;clearTimeout(e);
return d=a.apply(this,arguments)}};b.wrap=function(a,b){return function(){var d=[a].concat(h.call(arguments,0));return b.apply(this,d)}};b.compose=function(){var a=arguments;return function(){for(var b=arguments,d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=P||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values= e=setTimeout(function(){e=null;d||a.apply(h,f)},b);i&&a.apply(h,f)}};b.once=function(a){var b=false,d;return function(){if(b)return d;b=true;d=a.apply(this,arguments);a=null;return d}};b.wrap=function(a,b){return function(){var d=[a].concat(g.call(arguments,0));return b.apply(this,d)}};b.compose=function(){var a=arguments;return function(){for(var b=arguments,d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};
function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){i(h.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.pick=function(a){var c={},d=b.flatten(h.call(arguments,1));i(d,function(b){b in a&&(c[b]=a[b])});return c};b.omit=function(a){var c={},d=b.flatten(h.call(arguments,1)),e;for(e in a)b.include(d,e)||(c[e]=a[e]);return c};b.defaults=function(a){i(h.call(arguments,1), b.keys=N||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.pairs=function(a){return b.map(a,function(a,b){return[b,a]})};b.invert=function(a){return b.reduce(a,function(a,b,e){a[b]=e;return a},{})};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(g.call(arguments,1),function(b){for(var d in b)a[d]=
function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};var u=function(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=m.call(a);if(e!=m.call(c))return false;switch(e){case "[object String]":return a== b[d]});return a};b.pick=function(a){var c={},d=b.flatten(g.call(arguments,1));j(d,function(b){b in a&&(c[b]=a[b])});return c};b.omit=function(a){var c={},d=b.flatten(g.call(arguments,1)),e;for(e in a)b.include(d,e)||(c[e]=a[e]);return c};b.defaults=function(a){j(g.call(arguments,1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};var u=function(a,c,d,e){if(a===c)return a!==
""+c;case "[object Number]":return a!=+a?c!=+c:a==0?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source==c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){f=a.length;if(g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&u(a[f],c[f],d)))break}else{if("constructor"in 0||1/a==1/c;if(a==null||c==null)return a===c;if(a instanceof b)a=a._wrapped;if(c instanceof b)c=c._wrapped;if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var h=l.call(a);if(h!=l.call(c))return false;switch(h){case "[object String]":return a==""+c;case "[object Number]":return a!=+a?c!=+c:a==0?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source==c.source&&a.global==c.global&&
a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(b.has(a,h)){f++;if(!(g=b.has(c,h)&&u(a[h],c[h],d)))break}if(g){for(h in c)if(b.has(c,h)&&!f--)break;g=!f}}d.pop();return g};b.isEqual=function(a,b){return u(a,b,[])};b.isEmpty=function(a){if(a==null)return true;if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=p||function(a){return m.call(a)=="[object Array]"}; a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return e[f]==c;d.push(a);e.push(c);var f=0,i=true;if(h=="[object Array]"){f=a.length;if(i=f==c.length)for(;f--;)if(!(i=f in a==f in c&&u(a[f],c[f],d,e)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var g in a)if(b.has(a,g)){f++;if(!(i=b.has(c,g)&&u(a[g],c[g],d,e)))break}if(i){for(g in c)if(b.has(c,g)&&!f--)break;
b.isObject=function(a){return a===Object(a)};i("Arguments,Function,String,Number,Date,RegExp".split(","),function(a){b["is"+a]=function(b){return m.call(b)=="[object "+a+"]"}});b.isArguments(arguments)||(b.isArguments=function(a){return!(!a||!b.has(a,"callee"))});b.isFinite=function(a){return b.isNumber(a)&&isFinite(a)};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||m.call(a)=="[object Boolean]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a=== i=!f}}d.pop();e.pop();return i};b.isEqual=function(a,b){return u(a,b,[],[])};b.isEmpty=function(a){if(a==null)return true;if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=p||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)};j("Arguments,Function,String,Number,Date,RegExp".split(","),function(a){b["is"+a]=function(b){return l.call(b)=="[object "+
void 0};b.has=function(a,b){return O.call(a,b)};b.noConflict=function(){s._=L;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e<a;e++)b.call(d,e)};var Q={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;","/":"&#x2F;"},R=/[&<>"'\/]/g;b.escape=function(a){return(""+a).replace(R,function(a){return Q[a]})};b.result=function(a,c){if(a==null)return null;var d=a[c];return b.isFunction(d)?d.call(a):d};b.mixin=function(a){i(b.functions(a),function(c){S(c,b[c]=a[c])})}; a+"]"}});b.isArguments(arguments)||(b.isArguments=function(a){return!(!a||!b.has(a,"callee"))});b.isFinite=function(a){return b.isNumber(a)&&isFinite(a)};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,b){return M.call(a,b)};b.noConflict=function(){s._=K;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=
var T=0;b.uniqueId=function(a){var b=T++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var v=/.^/,n={"\\":"\\","'":"'",r:"\r",n:"\n",t:"\t",u2028:"\u2028",u2029:"\u2029"},w;for(w in n)n[n[w]]=w;var U=/\\|'|\r|\n|\t|\u2028|\u2029/g,V=/\\(\\|'|r|n|t|u2028|u2029)/g,x=function(a){return a.replace(V,function(a,b){return n[b]})};b.template=function(a,c,d){d=b.defaults(d||{},b.templateSettings);a="__p+='"+a.replace(U,function(a){return"\\"+ 0;e<a;e++)b.call(d,e)};b.random=function(a,b){return a+(0|Math.random()*(b-a+1))};var m={escape:{"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;","/":"&#x2F;"}};m.unescape=b.invert(m.escape);var O={escape:RegExp("["+b.keys(m.escape).join("")+"]","g"),unescape:RegExp("("+b.keys(m.unescape).join("|")+")","g")};b.each(["escape","unescape"],function(a){b[a]=function(b){return b==null?"":(""+b).replace(O[a],function(b){return m[a][b]})}});b.result=function(a,c){if(a==null)return null;var d=
n[a]}).replace(d.escape||v,function(a,b){return"'+\n((__t=("+x(b)+"))==null?'':_.escape(__t))+\n'"}).replace(d.interpolate||v,function(a,b){return"'+\n((__t=("+x(b)+"))==null?'':__t)+\n'"}).replace(d.evaluate||v,function(a,b){return"';\n"+x(b)+"\n__p+='"})+"';\n";d.variable||(a="with(obj||{}){\n"+a+"}\n");var a="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'')};\n"+a+"return __p;\n",e=new Function(d.variable||"obj","_",a);if(c)return e(c,b);c=function(a){return e.call(this, a[c];return b.isFunction(d)?d.call(a):d};b.mixin=function(a){j(b.functions(a),function(c){var d=b[c]=a[c];b.prototype[c]=function(){var a=g.call(arguments);a.unshift(this._wrapped);a=d.apply(b,a);return this._chain?b(a).chain():a}})};var P=0;b.uniqueId=function(a){var b=P++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var v=/.^/,n={"\\":"\\","'":"'",r:"\r",n:"\n",t:"\t",u2028:"\u2028",u2029:"\u2029"},w;for(w in n)n[n[w]]=
a,b)};c.source="function("+(d.variable||"obj")+"){\n"+a+"}";return c};b.chain=function(a){return b(a).chain()};var l=function(a){this._wrapped=a};b.prototype=l.prototype;var y=function(a,c){return c?b(a).chain():a},S=function(a,c){l.prototype[a]=function(){var a=h.call(arguments);N.call(a,this._wrapped);return y(c.apply(b,a),this._chain)}};b.mixin(b);i("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];l.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments); w;var Q=/\\|'|\r|\n|\t|\u2028|\u2029/g,R=/\\(\\|'|r|n|t|u2028|u2029)/g,x=function(a){return a.replace(R,function(a,b){return n[b]})};b.template=function(a,c,d){d=b.defaults({},d,b.templateSettings);a="__p+='"+a.replace(Q,function(a){return"\\"+n[a]}).replace(d.escape||v,function(a,b){return"'+\n((__t=("+x(b)+"))==null?'':_.escape(__t))+\n'"}).replace(d.interpolate||v,function(a,b){return"'+\n((__t=("+x(b)+"))==null?'':__t)+\n'"}).replace(d.evaluate||v,function(a,b){return"';\n"+x(b)+"\n__p+='"})+
(a=="shift"||a=="splice")&&d.length===0&&delete d[0];return y(d,this._chain)}});i(["concat","join","slice"],function(a){var b=k[a];l.prototype[a]=function(){return y(b.apply(this._wrapped,arguments),this._chain)}});l.prototype.chain=function(){this._chain=true;return this};l.prototype.value=function(){return this._wrapped}}).call(this); "';\n";d.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{var e=new Function(d.variable||"obj","_",a)}catch(g){g.source=a;throw g;}if(c)return e(c,b);c=function(a){return e.call(this,a,b)};c.source="function("+(d.variable||"obj")+"){\n"+a+"}";return c};b.chain=function(a){return b(a).chain()};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var c=k[a];b.prototype[a]=
function(){var d=this._wrapped;c.apply(d,arguments);(a=="shift"||a=="splice")&&d.length===0&&delete d[0];return this._chain?b(d).chain():d}});j(["concat","join","slice"],function(a){var c=k[a];b.prototype[a]=function(){var a=c.apply(this._wrapped,arguments);return this._chain?b(a).chain():a}});b.extend(b.prototype,{chain:function(){this._chain=true;return this},value:function(){return this._wrapped}})}).call(this);

View File

@@ -47,7 +47,11 @@
nativeBind = FuncProto.bind; nativeBind = FuncProto.bind;
// Create a safe reference to the Underscore object for use below. // Create a safe reference to the Underscore object for use below.
var _ = function(obj) { return new wrapper(obj); }; var _ = function(obj) {
if (obj instanceof _) return obj;
if (!(this instanceof _)) return new _(obj);
this._wrapped = obj;
};
// Export the Underscore object for **Node.js**, with // Export the Underscore object for **Node.js**, with
// backwards-compatibility for the old `require()` API. If we're in // backwards-compatibility for the old `require()` API. If we're in
@@ -174,6 +178,7 @@
// Delegates to **ECMAScript 5**'s native `every` if available. // Delegates to **ECMAScript 5**'s native `every` if available.
// Aliased as `all`. // Aliased as `all`.
_.every = _.all = function(obj, iterator, context) { _.every = _.all = function(obj, iterator, context) {
iterator || (iterator = _.identity);
var result = true; var result = true;
if (obj == null) return result; if (obj == null) return result;
if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
@@ -338,7 +343,7 @@
// Return the number of elements in an object. // Return the number of elements in an object.
_.size = function(obj) { _.size = function(obj) {
return _.isArray(obj) ? obj.length : _.keys(obj).length; return (obj.length === +obj.length) ? obj.length : _.keys(obj).length;
}; };
// Array Functions // Array Functions
@@ -369,12 +374,12 @@
} }
}; };
// Returns everything but the first entry of the array. Aliased as `tail`. // Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
// Especially useful on the arguments object. Passing an **index** will return // Especially useful on the arguments object. Passing an **n** will return
// the rest of the values in the array from that index onward. The **guard** // the rest N values in the array. The **guard**
// check allows it to work with `_.map`. // check allows it to work with `_.map`.
_.rest = _.tail = function(array, index, guard) { _.rest = _.tail = _.drop = function(array, n, guard) {
return slice.call(array, (index == null) || guard ? 1 : index); return slice.call(array, (n == null) || guard ? 1 : n);
}; };
// Trim out all falsy values from an array. // Trim out all falsy values from an array.
@@ -456,12 +461,17 @@
return results; return results;
}; };
// Zip together two arrays -- an array of keys and an array of values -- into // Converts lists into objects. Pass either a single array of `[key, value]`
// a single object. // pairs, or two parallel arrays of the same length -- one of keys, and one of
_.zipObject = function(keys, values) { // the corresponding values.
_.object = function(list, values) {
var result = {}; var result = {};
for (var i = 0, l = keys.length; i < l; i++) { for (var i = 0, l = list.length; i < l; i++) {
result[keys[i]] = values[i]; if (values) {
result[list[i]] = values[i];
} else {
result[list[i][0]] = list[i][1];
}
} }
return result; return result;
}; };
@@ -581,7 +591,9 @@
context = this; args = arguments; context = this; args = arguments;
var later = function() { var later = function() {
timeout = null; timeout = null;
if (more) func.apply(context, args); if (more) {
result = func.apply(context, args);
}
whenDone(); whenDone();
}; };
if (!timeout) timeout = setTimeout(later, wait); if (!timeout) timeout = setTimeout(later, wait);
@@ -622,7 +634,9 @@
return function() { return function() {
if (ran) return memo; if (ran) return memo;
ran = true; ran = true;
return memo = func.apply(this, arguments); memo = func.apply(this, arguments);
func = null;
return memo;
}; };
}; };
@@ -676,6 +690,21 @@
return _.map(obj, _.identity); return _.map(obj, _.identity);
}; };
// Convert an object into a list of `[key, value]` pairs.
_.pairs = function(obj) {
return _.map(obj, function(value, key) {
return [key, value];
});
};
// Invert the keys and values of an object. The values must be serializable.
_.invert = function(obj) {
return _.reduce(obj, function(memo, value, key) {
memo[value] = key;
return memo;
}, {});
};
// Return a sorted list of the function names available on the object. // Return a sorted list of the function names available on the object.
// Aliased as `methods` // Aliased as `methods`
_.functions = _.methods = function(obj) { _.functions = _.methods = function(obj) {
@@ -741,15 +770,15 @@
}; };
// Internal recursive comparison function for `isEqual`. // Internal recursive comparison function for `isEqual`.
var eq = function(a, b, stack) { var eq = function(a, b, aStack, bStack) {
// Identical objects are equal. `0 === -0`, but they aren't identical. // 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. // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
if (a === b) return a !== 0 || 1 / a == 1 / b; if (a === b) return a !== 0 || 1 / a == 1 / b;
// A strict comparison is necessary because `null == undefined`. // A strict comparison is necessary because `null == undefined`.
if (a == null || b == null) return a === b; if (a == null || b == null) return a === b;
// Unwrap any wrapped objects. // Unwrap any wrapped objects.
if (a._chain) a = a._wrapped; if (a instanceof _) a = a._wrapped;
if (b._chain) b = b._wrapped; if (b instanceof _) b = b._wrapped;
// Invoke a custom `isEqual` method if one is provided. // Invoke a custom `isEqual` method if one is provided.
if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b); if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b);
if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a); if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a);
@@ -782,14 +811,15 @@
if (typeof a != 'object' || typeof b != 'object') return false; if (typeof a != 'object' || typeof b != 'object') return false;
// Assume equality for cyclic structures. The algorithm for detecting cyclic // Assume equality for cyclic structures. The algorithm for detecting cyclic
// structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
var length = stack.length; var length = aStack.length;
while (length--) { while (length--) {
// Linear search. Performance is inversely proportional to the number of // Linear search. Performance is inversely proportional to the number of
// unique nested structures. // unique nested structures.
if (stack[length] == a) return true; if (aStack[length] == a) return bStack[length] == b;
} }
// Add the first object to the stack of traversed objects. // Add the first object to the stack of traversed objects.
stack.push(a); aStack.push(a);
bStack.push(b);
var size = 0, result = true; var size = 0, result = true;
// Recursively compare objects and arrays. // Recursively compare objects and arrays.
if (className == '[object Array]') { if (className == '[object Array]') {
@@ -800,7 +830,7 @@
// Deep compare the contents, ignoring non-numeric properties. // Deep compare the contents, ignoring non-numeric properties.
while (size--) { while (size--) {
// Ensure commutative equality for sparse arrays. // Ensure commutative equality for sparse arrays.
if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break; if (!(result = size in a == size in b && eq(a[size], b[size], aStack, bStack))) break;
} }
} }
} else { } else {
@@ -812,7 +842,7 @@
// Count the expected number of properties. // Count the expected number of properties.
size++; size++;
// Deep compare each member. // Deep compare each member.
if (!(result = _.has(b, key) && eq(a[key], b[key], stack))) break; if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break;
} }
} }
// Ensure that both objects contain the same number of properties. // Ensure that both objects contain the same number of properties.
@@ -824,13 +854,14 @@
} }
} }
// Remove the first object from the stack of traversed objects. // Remove the first object from the stack of traversed objects.
stack.pop(); aStack.pop();
bStack.pop();
return result; return result;
}; };
// Perform a deep comparison to check if two objects are equal. // Perform a deep comparison to check if two objects are equal.
_.isEqual = function(a, b) { _.isEqual = function(a, b) {
return eq(a, b, []); return eq(a, b, [], []);
}; };
// Is a given array, string, or object empty? // Is a given array, string, or object empty?
@@ -925,25 +956,39 @@
for (var i = 0; i < n; i++) iterator.call(context, i); for (var i = 0; i < n; i++) iterator.call(context, i);
}; };
// Return a random integer between min and max (inclusive).
_.random = function(min, max) {
return min + (0 | Math.random() * (max - min + 1));
};
// List of HTML entities for escaping. // List of HTML entities for escaping.
var htmlEscapes = { var entityMap = {
'&': '&amp;', escape: {
'<': '&lt;', '&': '&amp;',
'>': '&gt;', '<': '&lt;',
'"': '&quot;', '>': '&gt;',
"'": '&#x27;', '"': '&quot;',
'/': '&#x2F;' "'": '&#x27;',
'/': '&#x2F;'
}
};
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')
}; };
// Regex containing the keys listed immediately above. // Functions for escaping and unescaping strings to/from HTML interpolation.
var htmlEscaper = /[&<>"'\/]/g; _.each(['escape', 'unescape'], function(method) {
_[method] = function(string) {
// Escape a string for HTML interpolation. if (string == null) return '';
_.escape = function(string) { return ('' + string).replace(entityRegexes[method], function(match) {
return ('' + string).replace(htmlEscaper, function(match) { return entityMap[method][match];
return htmlEscapes[match]; });
}); };
}; });
// If the value of the named property is a function then invoke it; // If the value of the named property is a function then invoke it;
// otherwise, return it. // otherwise, return it.
@@ -953,11 +998,15 @@
return _.isFunction(value) ? value.call(object) : value; return _.isFunction(value) ? value.call(object) : value;
}; };
// Add your own custom functions to the Underscore object, ensuring that // Add your own custom functions to the Underscore object.
// they're correctly added to the OOP wrapper as well.
_.mixin = function(obj) { _.mixin = function(obj) {
each(_.functions(obj), function(name){ each(_.functions(obj), function(name){
addToWrapper(name, _[name] = obj[name]); var func = _[name] = obj[name];
_.prototype[name] = function() {
var args = slice.call(arguments);
args.unshift(this._wrapped);
return result.call(this, func.apply(_, args));
};
}); });
}; };
@@ -1010,7 +1059,7 @@
// Underscore templating handles arbitrary delimiters, preserves whitespace, // Underscore templating handles arbitrary delimiters, preserves whitespace,
// and correctly escapes quotes within interpolated code. // and correctly escapes quotes within interpolated code.
_.template = function(text, data, settings) { _.template = function(text, data, settings) {
settings = _.defaults(settings || {}, _.templateSettings); settings = _.defaults({}, settings, _.templateSettings);
// Compile the template source, taking care to escape characters that // Compile the template source, taking care to escape characters that
// cannot be included in a string literal and then unescape them in code // cannot be included in a string literal and then unescape them in code
@@ -1033,10 +1082,16 @@
if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
source = "var __t,__p='',__j=Array.prototype.join," + source = "var __t,__p='',__j=Array.prototype.join," +
"print=function(){__p+=__j.call(arguments,'')};\n" + "print=function(){__p+=__j.call(arguments,'');};\n" +
source + "return __p;\n"; source + "return __p;\n";
var render = new Function(settings.variable || 'obj', '_', source); try {
var render = new Function(settings.variable || 'obj', '_', source);
} catch (e) {
e.source = source;
throw e;
}
if (data) return render(data, _); if (data) return render(data, _);
var template = function(data) { var template = function(data) {
return render.call(this, data, _); return render.call(this, data, _);
@@ -1053,29 +1108,15 @@
return _(obj).chain(); return _(obj).chain();
}; };
// The OOP Wrapper // OOP
// --------------- // ---------------
// If Underscore is called as a function, it returns a wrapped object that // 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 // can be used OO-style. This wrapper holds altered versions of all the
// underscore functions. Wrapped objects may be chained. // underscore functions. Wrapped objects may be chained.
var wrapper = function(obj) { this._wrapped = obj; };
// Expose `wrapper.prototype` as `_.prototype`
_.prototype = wrapper.prototype;
// Helper function to continue chaining intermediate results. // Helper function to continue chaining intermediate results.
var result = function(obj, chain) { var result = function(obj) {
return chain ? _(obj).chain() : obj; return this._chain ? _(obj).chain() : obj;
};
// A method to easily add functions to the OOP wrapper.
var addToWrapper = function(name, func) {
wrapper.prototype[name] = function() {
var args = slice.call(arguments);
unshift.call(args, this._wrapped);
return result(func.apply(_, args), this._chain);
};
}; };
// Add all of the Underscore functions to the wrapper object. // Add all of the Underscore functions to the wrapper object.
@@ -1084,31 +1125,35 @@
// Add all mutator Array functions to the wrapper. // 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]; var method = ArrayProto[name];
wrapper.prototype[name] = function() { _.prototype[name] = function() {
var obj = this._wrapped; var obj = this._wrapped;
method.apply(obj, arguments); method.apply(obj, arguments);
if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0]; if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0];
return result(obj, this._chain); return result.call(this, obj);
}; };
}); });
// Add all accessor Array functions to the wrapper. // Add all accessor Array functions to the wrapper.
each(['concat', 'join', 'slice'], function(name) { each(['concat', 'join', 'slice'], function(name) {
var method = ArrayProto[name]; var method = ArrayProto[name];
wrapper.prototype[name] = function() { _.prototype[name] = function() {
return result(method.apply(this._wrapped, arguments), this._chain); return result.call(this, method.apply(this._wrapped, arguments));
}; };
}); });
// Start chaining a wrapped Underscore object. _.extend(_.prototype, {
wrapper.prototype.chain = function() {
this._chain = true;
return this;
};
// Extracts the result from a wrapped and chained object. // Start chaining a wrapped Underscore object.
wrapper.prototype.value = function() { chain: function() {
return this._wrapped; this._chain = true;
}; return this;
},
// Extracts the result from a wrapped and chained object.
value: function() {
return this._wrapped;
}
});
}).call(this); }).call(this);