Compare commits

...

85 Commits
0.6.0 ... 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
John-David Dalton
141c10f6fe Ensure noArgsClass references aren't removed for "underscore" builds.
Former-commit-id: 697e2d29d5fef32b0b10d775eee7a12d193ff1a9
2012-08-30 00:58:54 -07:00
John-David Dalton
4613ab9dc3 Update cdn links and tested browsers in README.md.
Former-commit-id: db952c5d72fa9e4d6ce3280574b9e4e26da8c928
2012-08-29 18:03:03 -07:00
John-David Dalton
061ad41b51 Cleanup link hashes in generated docs and update vendors.
Former-commit-id: 278366a7dacea91d965835b24ae7ec046f455110
2012-08-29 08:28:37 -07:00
John-David Dalton
3a0007cd18 Remove pseudo private properties from debug builds.
Former-commit-id: 839c2e47e19ba163f0c69d3c115ec9ee262d5d6b
2012-08-29 01:17:28 -07:00
John-David Dalton
0044917943 Ensure Closure Compiler removes isPlainObject when it's no longer needed in build.js.
Former-commit-id: 748de0f28f4e0733ef83a77fe2b1f6e40e4f8ce2
2012-08-29 00:34:58 -07:00
John-David Dalton
0337c04278 Bump to v0.6.1.
Former-commit-id: 68b59ea3e76d06d60acc81e30770b5899b8ea761
2012-08-29 00:14:40 -07:00
John-David Dalton
4585acf70b Optimize isPlainObject.
Former-commit-id: 37754e27d03929927d8b8653bd7e44ad3ce2b23c
2012-08-29 00:05:02 -07:00
John-David Dalton
2ad9bbae25 Avoid issues in IE with other code containing comments like //@todo xyz.
Former-commit-id: 92ec6258539bf48144e9af05d0e326651a5fedda
2012-08-28 23:58:07 -07:00
47 changed files with 4574 additions and 2368 deletions

161
README.md
View File

@@ -1,14 +1,14 @@
# Lo-Dash <sup>v0.6.0</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.
## Download
* [Development source](https://raw.github.com/bestiejs/lodash/v0.6.0/lodash.js)
* [Production source](https://raw.github.com/bestiejs/lodash/v0.6.0/lodash.min.js)
* CDN copies of ≤ [v0.6.0](http://cdnjs.cloudflare.com/ajax/libs/lodash.js/0.6.0/lodash.min.js) are available on [cdnjs](http://cdnjs.com/) thanks to [CloudFlare](http://www.cloudflare.com/)
* [Development source](https://raw.github.com/bestiejs/lodash/v0.7.0/lodash.js)
* [Production source](https://raw.github.com/bestiejs/lodash/v0.7.0/lodash.min.js)
* 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
## 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-Dashs origin and why its a better utility belt](https://vimeo.com/44154600)
* [Unit testing in Lo-Dash](https://vimeo.com/45865290)
* [Lo-Dashs approach to native method use](https://vimeo.com/48576012)
## Features
@@ -35,33 +36,35 @@ For more information check out these screencasts over Lo-Dash:
* [_.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)
* [_.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
* [_.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
* [_.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
* [_.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)
* [_.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
* [_.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
* [_.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)
* [_.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),
[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
Lo-Dash has been tested in at least Chrome 5-21, Firefox 1-14, IE 6-9, Opera 9.25-12, Safari 3-6, Node.js 0.4.8-0.8.8, Narwhal 0.3.2, RingoJS 0.8, and Rhino 1.7RC5.
Lo-Dash has been tested in at least Chrome 5-21, Firefox 1-15, IE 6-9, Opera 9.25-12, Safari 3-6, Node.js 0.4.8-0.8.8, Narwhal 0.3.2, RingoJS 0.8, and Rhino 1.7RC5.
## Custom builds
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
lodash backbone
```
@@ -86,39 +89,59 @@ lodash mobile
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
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”*.
```bash
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
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
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
lodash backbone legacy category=utilities exclude=first,last
lodash underscore mobile strict category=functions include=pick,uniq
lodash backbone legacy exports=global category=utilities exclude=first,last
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`).
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.0/test/test.js#L578-584)]
* Ensure array-like objects with invalid `length` properties are treated like regular objects [[test](https://github.com/bestiejs/lodash/blob/v0.6.0/test/test.js#L526-536)]
* 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.0/test/test.js#L1668-1703)]
* 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.0/test/test.js#L538-555)]
* Ensure templates compiled with errors are inspectable [[#666](https://github.com/documentcloud/underscore/issues/666), [test](https://github.com/bestiejs/lodash/blob/v0.6.0/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.6.0/test/test.js#L589-614)]
* Handle arrays with `undefined` values correctly in IE < 9 [[#601](https://github.com/documentcloud/underscore/issues/601)]
* 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.0/test/test.js#L117-123)]
* 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.0/test/test.js#L101-115)]
* `_(…)` should return passed wrapper instances [[test](https://github.com/bestiejs/lodash/blob/v0.6.0/test/test.js#L135-138)]
* `_.clone` should allow `deep` cloning [[#595](https://github.com/documentcloud/underscore/pull/595), [test](https://github.com/bestiejs/lodash/blob/v0.6.0/test/test.js#L205-220)]
* `_.contains` should work with strings [[#667](https://github.com/documentcloud/underscore/pull/667), [test](https://github.com/bestiejs/lodash/blob/v0.6.0/test/test.js#L275-284)]
* `_.countBy` and `_.groupBy` should only add values to own, not inherited, properties [[test](https://github.com/bestiejs/lodash/blob/v0.6.0/test/test.js#L292-299)]
* `_.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.0/test/test.js#L402-405)]
* `_.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.0/test/test.js#L979-1001)]
* `_.forEach` should be chainable [[#142](https://github.com/documentcloud/underscore/issues/142), [test](https://github.com/bestiejs/lodash/blob/v0.6.0/test/test.js#L521-524)]
* `_.forEach` should allow exiting iteration early [[#211](https://github.com/documentcloud/underscore/issues/211), [test](https://github.com/bestiejs/lodash/blob/v0.6.0/test/test.js#L616-635)]
* `_.isElement` should use strict equality for its duck type check [[test](https://github.com/bestiejs/lodash/blob/v0.6.0/test/test.js#L731-740)]
* `_.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.0/test/test.js#L767-772)]
* `_.isEqual` should return `true` for like-objects from different documents [[test](https://github.com/bestiejs/lodash/blob/v0.6.0/test/test.js#L808-828)]
* `_.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.0/test/test.js#L836-848)]
* `_.isNaN(new Number(NaN))` should return `true` [[test](https://github.com/bestiejs/lodash/blob/v0.6.0/test/test.js#L856-858)]
* `_.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.0/test/test.js#L912-914)]
* `_.once` should free the given function for garbage collection [[#693](https://github.com/documentcloud/underscore/pull/693)]
* `_.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.0/test/test.js#L1170-1173)]
* `_.reduceRight` should pass correct callback arguments when iterating objects [[test](https://github.com/bestiejs/lodash/blob/v0.6.0/test/test.js#L1205-1219)]
* `_.size` should return the `length` of string values [[test](https://github.com/bestiejs/lodash/blob/v0.6.0/test/test.js#L1263-1265)]
* `_.sortedIndex` should support arrays with high `length` values [[test](https://github.com/bestiejs/lodash/blob/v0.6.0/test/test.js#L1353-1362)]
* `_.template` should not augment the `options` object [[test](https://github.com/bestiejs/lodash/blob/v0.6.0/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.0/test/test.js#L1473-1483)]
* `_.toArray` uses custom `toArray` methods of arrays and strings [[test](https://github.com/bestiejs/lodash/blob/v0.6.0/test/test.js#L1510-1518)]
* `_.zipObject` should accept less than two arguments [[test](https://github.com/bestiejs/lodash/blob/v0.6.0/test/test.js#L1630-1632)]
* 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 [[#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.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.7.0/test/test.js#L503-520)]
* 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)]
* 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)]
* 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)]
* `_.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)]
* `_.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)]
* `_.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)]
* `_.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)]
* `_.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)]
* `_.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)]
* `_.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)]
* `_.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)]
* `_.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)]
* `_.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)]
* `_.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)]
* `_.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)]
* `_.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)]
* `_.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)]
* `_.reduceRight` should pass correct callback arguments when iterating objects [[test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L1257-1271)]
* `_.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)]
* `_.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)]
* `_.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)]
## Optimized methods <sup>(50+)</sup>
@@ -214,9 +230,7 @@ require({
* `_.defaults`
* `_.defer`
* `_.difference`
* `_.drop`, `_.omit`
* `_.each`
* `_.escape`
* `_.every`, `_.all`
* `_.extend`
* `_.filter`, `_.select`
@@ -227,6 +241,7 @@ require({
* `_.groupBy`
* `_.indexOf`
* `_.intersection`
* `_.invert`
* `_.invoke`
* `_.isArguments`
* `_.isDate`
@@ -244,6 +259,8 @@ require({
* `_.memoize`
* `_.min`
* `_.mixin`
* `_.omit`
* `_.pairs`
* `_.pick`
* `_.pluck`
* `_.reduce`, `_.foldl`, `_.inject`
@@ -267,16 +284,30 @@ require({
## Release Notes
### <sup>v0.6.0</sup>
### <sup>v0.7.0</sup>
* Added `callback` and `thisArg` arguments to `_.drop` and `_.pick`
* Added `hasObjectSpliceBug` test to avoid `delete` operator use
* Added `_.omit` alias for `_.drop`
* Added [_.unescape](http://lodash.com/docs#unescape)
* Ensured `_.reduce` works with string objects in IE < 9
* Made compiled methods take advantage of engines with strict mode optimizations
* Optimized `_.intersection` and removed its dependency on `_.every`
* Reduced the file size of the `underscore` build
#### Compatibility Warnings ####
* Renamed `_.zipObject` to `_.object`
* Replaced `_.drop` with `_.omit`
* Made `_.drop` alias `_.rest`
#### Changes ####
* Added [_.invert](http://lodash.com/docs#invert), [_.pairs](http://lodash.com/docs#pairs), and [_.random](http://lodash.com/docs#random)
* Added `_.result` to the `backbone` build
* Added `exports`, `iife`, `-c`/`--stdout`, `-o`/`--output`, and `-s`/`--silent` build options
* Ensured `isPlainObject` works with objects from other documements
* Ensured `_.isEqual` compares values with circular references correctly
* 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).

1525
build.js

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -6,11 +6,15 @@
var fs = require('fs');
/** The minimal license/copyright template */
var licenseTemplate =
'/*!\n' +
' Lo-Dash @VERSION lodash.com/license\n' +
' Underscore.js 1.3.3 github.com/documentcloud/underscore/blob/master/LICENSE\n' +
'*/';
var licenseTemplate = {
'lodash':
'/*!\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.
*/
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
source = source.replace(/^([^(\n]+)\s*(\(function[^)]+\){)/, '$2$1');
// 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
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
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',
'className',
'compareAscending',
'destValue',
'data',
'forIn',
'found',
'funcs',
@@ -66,10 +66,11 @@
'propsLength',
'recursive',
'source',
'stack',
'sources',
'stackLength',
'target',
'valueProp'
'valueProp',
'values'
];
/** Used to minify `compileIterator` option properties */
@@ -104,8 +105,9 @@
/** Used to protect the specified properties from getting minified */
var propWhitelist = [
'_',
'_chain',
'_wrapped',
'__chain__',
'__proto__',
'__wrapped__',
'after',
'all',
'amd',
@@ -116,6 +118,7 @@
'chain',
'clearTimeout',
'clone',
'clones',
'collect',
'compact',
'compose',
@@ -134,6 +137,7 @@
'escape',
'evaluate',
'every',
'exports',
'extend',
'filter',
'find',
@@ -156,6 +160,7 @@
'inject',
'interpolate',
'intersection',
'invert',
'invoke',
'isArguments',
'isArray',
@@ -186,12 +191,15 @@
'min',
'mixin',
'noConflict',
'object',
'omit',
'once',
'opera',
'pairs',
'partial',
'pick',
'pluck',
'random',
'range',
'reduce',
'reduceRight',
@@ -206,6 +214,9 @@
'sortBy',
'sortedIndex',
'source',
'sources',
'stackA',
'stackB',
'tail',
'take',
'tap',
@@ -227,7 +238,10 @@
'without',
'wrap',
'zip',
'zipObject'
// properties used by underscore.js
'_chain',
'_wrapped'
];
/*--------------------------------------------------------------------------*/
@@ -245,22 +259,6 @@
// remove unrecognized JSDoc tags so Closure Compiler won't complain
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
// http://code.google.com/closure/compiler/docs/api-tutorial3.html#export
source = source.replace(RegExp('\\.(' + propWhitelist.join('|') + ')\\b', 'g'), "['$1']");
@@ -268,6 +266,9 @@
// remove brackets from `_.escape()` in `_.template`
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`
source = source.replace("collection['indexOf'](target)", 'collection.indexOf(target)');
@@ -282,6 +283,9 @@
});
});
// add newline to `+"__p+='"` in underscore.js `_.template`
source = source.replace(/\+"__p\+='"/g, '+"\\n__p+=\'"');
// remove whitespace from `_.template` related regexes
source = source.replace(/(?:reDelimiterCode\w+|reEmptyString\w+|reInsertVariable) *=.+/g, function(match) {
return match.replace(/ |\\n/g, '');
@@ -301,10 +305,10 @@
// remove debug sourceURL use in `_.template`
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() {
var properties = ['criteria', 'index', 'source', 'value'],
snippets = source.match(/( +)(?:function clone|function compareAscending|var merge|var sortBy)\b[\s\S]+?\n\1}/g);
var properties = ['clones', 'criteria', 'index', 'sources', 'thorough', 'value', 'values'],
snippets = source.match(/( +)(?:function (?:clone|compareAscending|isEqual)|var merge|var sortBy)\b[\s\S]+?\n\1}/g);
if (!snippets) {
return;
@@ -318,7 +322,7 @@
properties.forEach(function(property, index) {
var reBracketProp = RegExp("\\['(" + property + ")'\\]", 'g'),
reDotProp = RegExp('\\.' + property + '\\b', 'g'),
rePropColon = RegExp("(')?\\b" + property + "\\1 *:", 'g');
rePropColon = RegExp("([^?\\s])\\s*([\"'])?\\b" + property + "\\2 *:", 'g');
if (isCompilable) {
// add quotes around properties in the inlined `_.merge` and `_.sortBy`
@@ -327,20 +331,20 @@
modified = modified
.replace(reBracketProp, "['" + minNames[index] + "']")
.replace(reDotProp, "['" + minNames[index] + "']")
.replace(rePropColon, "'" + minNames[index] + "':");
.replace(rePropColon, "$1'" + minNames[index] + "':");
}
else {
modified = modified
.replace(reBracketProp, '.' + minNames[index])
.replace(reDotProp, '.' + minNames[index])
.replace(rePropColon, minNames[index] + ':');
.replace(rePropColon, '$1' + minNames[index] + ':');
}
}
else {
modified = modified
.replace(reBracketProp, "['" + minNames[index] + "']")
.replace(reDotProp, '.' + minNames[index])
.replace(rePropColon, "'" + minNames[index] + "':")
.replace(rePropColon, "$1'" + minNames[index] + "':")
// correct `value.source` in regexp branch of `_.clone`
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
$markdown = docdown(array(
'path' => '../' . $file,
'title' => 'Lo-Dash <sup>v0.6.0</sup>',
'title' => 'Lo-Dash <sup>v0.7.0</sup>',
'url' => 'https://github.com/bestiejs/lodash/blob/master/lodash.js'
));

658
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.0 lodash.com/license
Lo-Dash 0.7.0 lodash.com/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)?
("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+="}"
));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.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,w,tn,E,p,d,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
(e){return"\\"+Xt[e]}function h(e){return Ut[e]}function p(e,t){var n=i;if(!e||"object"!=typeof e||!t&&w(e))return n;var r=e.constructor;return(!Ht||"function"==typeof e.toString||"string"!=typeof (e+""))&&(!E(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}function d(e,t){return function(n,r,i){return e.call(t,n,r,i)}}function v(){}function m(e,t){if(e&&J.test(t))return"<e%-"+t+"%>";var n=ut.length;return ut[n]="'+__e("+
t+")+'",ot+n}function g(e,t,n,i){return i?(e=ut.length,ut[e]="';"+i+";__p+='",ot+e):t?m(r,t):y(r,n)}function y(e,t){if(e&&J.test(t))return"<e%="+t+"%>";var n=ut.length;return ut[n]="'+((__t=("+t+"))==null?'':__t)+'",ot+n}function b(e){return zt[e]}function w(e){return pt.call(e)==yt}function E(e){return"function"==typeof e}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&&E(e.clone))return u.d=r,e.clone(
t);if(s){var a=pt.call(e);if(!Rt[a]||_t&&w(e))return e;var f=a==bt,s=f||(a==xt?p(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 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&&E(e.isEqual))return o.value=r,e.isEqual(t);if(t.isEqual&&E(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=w(e))&&!w(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.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&&(!E(a)||!(a instanceof a&&E(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=d(t,r));if(-1<i&&i===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(;++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=d(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=e.length,a=[];"function"==typeof t&&(r=n,n=t,t=i);for(n?r&&(n=d(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?(v.prototype=e.prototype,u=new v,(o=e.apply(u,o))&&Wt[typeof o]?o:u):e.apply(u,o)}var r,i=E(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);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/,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,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)Mt=!r;Lt=4>(n+"").length,Ot="x"!=n[0],At=(n.splice.call(t,0,1),t[0])})(1);var _t=!w(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]":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={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"},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&&(w=function(e){return!!e&&!!ft.call(e,"callee")});var tn=vt||function(e){return pt.call(e)==bt};E(/x/)&&(E=function(e){return"[object Function]"==pt.call(e)});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]"+(_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"}),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'",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"}),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.0",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=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(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?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=w,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=x,s.isFinite=function(e){return mt(e)&&pt.call(e)==St},s.isFunction=E,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},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=d(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,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 E(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=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&&w(e)||t==xt&&-1<n&&n===n>>>0&&E(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;o==r&&(o=h.escape),a==r&&(a=h.evaluate||i),f==r&&(f=h.interpolate),o&&(e=e.replace
(o,m)),f&&(e=e.replace(f,y)),a!=H&&(H=a,F=RegExp("<e%-([\\s\\S]+?)%>|<e%=([\\s\\S]+?)%>"+(a?"|"+a.source:""),"g")),o=ut.length,e=e.replace(F,g),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;"),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=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&&E(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,b)},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=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.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(){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);
;(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
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{"
: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+="}";
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[
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
.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
.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
}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&&!
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)&&
(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]
: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}
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||
(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
),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=
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
(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
),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;":"<"
,"&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)"
,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)"
,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"
,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"
}),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)"
}),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}"
,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
(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
(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=
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===
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(
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
._=$,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==
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<
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
.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
.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
(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
,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
.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",
"version": "0.6.0",
"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",
@@ -43,7 +43,7 @@
"rhino"
],
"jam": {
"main": "lodash.min.js"
"main": "./lodash.min.js"
},
"scripts": {
"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(
Benchmark.Suite('`_.invoke` iterating an array')
.add('Lo-Dash', '\
@@ -1104,6 +1116,18 @@
/*--------------------------------------------------------------------------*/
suites.push(
Benchmark.Suite('`_.pairs`')
.add('Lo-Dash', '\
lodash.pairs(object)'
)
.add('Underscore', '\
_.pairs(object)'
)
);
/*--------------------------------------------------------------------------*/
suites.push(
Benchmark.Suite('`_.pick`')
.add('Lo-Dash', '\

View File

@@ -23,13 +23,21 @@
</div>
<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/qunit.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>
document.write('<script src="../' + QUnit.config.lodashFilename + '.js"><\/script>');
</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/events.js"></script>
<script src="../vendor/backbone/test/model.js"></script>

View File

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

View File

@@ -1,9 +1,14 @@
cd "$(dirname "$0")"
for cmd in rhino ringo narwhal node; do
echo ""
echo "Testing in $cmd..."
$cmd test.js
echo ""
echo "Testing in $cmd..."
$cmd test.js
done
echo ""
echo "Testing build..."
node test-build.js
echo ""
echo "Testing in a browser..."
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() {
var toolbar = document.getElementById('qunit-testrunner-toolbar');
if (toolbar) {
toolbar.appendChild(label1);
toolbar.appendChild(label2);
toolbar.appendChild(span1);
toolbar.appendChild(span2);
dropdown.selectedIndex = (function() {
switch (build) {
@@ -68,21 +68,24 @@
}
}
var label1 = document.createElement('label');
label1.innerHTML =
'<input name="norequire" type="checkbox">No RequireJS';
var span1 = document.createElement('span');
span1.innerHTML =
'<input id="qunit-norequire" type="checkbox">' +
'<label for="qunit-norequire">No RequireJS</label>';
var label2 = document.createElement('label');
label2.innerHTML = '&nbsp;' +
'<select name="build">' +
var span2 = document.createElement('span');
span2.style.cssText = 'float:right';
span2.innerHTML =
'<label for="qunit-build">Build: </label>' +
'<select id="qunit-build">' +
'<option value="dev">Developement</option>' +
'<option value="prod">Production</option>' +
'<option value="custom">Custom</option>' +
'<option value="custom-debug">Custom (debug)</option>' +
'</select> Build';
'</select>';
var checkbox = label1.firstChild,
dropdown = label2.getElementsByTagName('select')[0];
var checkbox = span1.firstChild,
dropdown = span2.lastChild;
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()`
// in case we are in a CLI environment
QUnit.module('lodash');
@@ -191,6 +208,7 @@
'boolean object': Object(false),
'an object': { 'a': 0, 'b': 1, 'c': 3 },
'an object with object values': { 'a': /a/, 'b': ['B'], 'c': { 'C': 1 } },
'an object from another document': _._object || {},
'null': null,
'a number': 3,
'a number object': Object(3),
@@ -205,12 +223,8 @@
_.forOwn(objects, function(object, key) {
test('should deep clone ' + key + ' correctly', function() {
var clone = _.clone(object, true);
ok(_.isEqual(object, clone));
if (object == null) {
equal(clone, object);
} else {
deepEqual(clone.valueOf(), object.valueOf());
}
if (_.isObject(object)) {
ok(clone !== object);
} else {
@@ -304,7 +318,17 @@
QUnit.module('lodash.debounce');
(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),
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');
(function() {
@@ -806,26 +771,28 @@
});
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)
if (_._object) {
var object = { 'a': 1, 'b': 2, 'c': 3 };
equal(_.isEqual(object, _._object), true);
body.removeChild(iframe);
delete _._object;
}
else {
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);
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));
/*--------------------------------------------------------------------------*/
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');
(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');
(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() {
function Foo(elements) { Array.prototype.push.apply(this, elements); }
Foo.prototype = { 'length': 0, 'splice': Array.prototype.splice };
@@ -1310,7 +1358,10 @@
new Pair(1, 5), new Pair(1, 6),
new Pair(2, 1), new Pair(2, 2),
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) {
@@ -1380,13 +1431,13 @@
deepEqual(options, {});
});
test('should be debuggable if compiled with errors', function() {
var source = _.template('<% if x %>').source;
ok(source.indexOf('__p') > -1);
});
test('should raise an error if a template, compiled with errors, is executed', function() {
raises(_.template('<% if x %>'));
test('should provide the template source when a SyntaxError occurs', function() {
try {
_.template('<% if x %>');
} catch(e) {
var source = e.source;
}
ok((source + '').indexOf('__p') > -1);
});
test('should work with complex "interpolate" delimiters', function() {
@@ -1456,6 +1507,16 @@
}
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');
(function() {

View File

@@ -20,8 +20,15 @@
</div>
<script src="../vendor/backbone/test/vendor/json2.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/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>
document.write('<script src="../' + QUnit.config.lodashFilename + '.js"><\/script>');

View File

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

View File

@@ -1,13 +1,12 @@
$(document).ready(function() {
var lastRequest = null;
var sync = Backbone.sync;
var a, b, c, d, e, col, otherCol;
module("Backbone.Collection", {
module("Backbone.Collection", _.extend(new Environment, {
setup: function() {
Environment.prototype.setup.apply(this, arguments);
a = new Backbone.Model({id: 3, label: 'a'});
b = new Backbone.Model({id: 2, label: 'b'});
c = new Backbone.Model({id: 1, label: 'c'});
@@ -15,23 +14,11 @@ $(document).ready(function() {
e = null;
col = new Backbone.Collection([a,b,c,d]);
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.last(), d, "d should be last");
col.comparator = function(a, b) {
@@ -47,36 +34,28 @@ $(document).ready(function() {
equal(col.length, 4);
});
test("Collection: new and parse", 3, function() {
var MyCol = Backbone.Collection.extend({
// only save the models that have an even value.
test("new and parse", 3, function() {
var Collection = Backbone.Collection.extend({
parse : function(data) {
var onlyEven = [];
_.each(data, function(datum) {
if (datum.a % 2 === 0) {
onlyEven.push(datum);
}
return _.filter(data, function(datum) {
return datum.a % 2 === 0;
});
return onlyEven;
}
});
anotherCol = new MyCol([
{ a : 1 },{ a : 2 },{ a : 3 },{ a : 4 }
], { parse : true });
equal(anotherCol.length, 2);
equal(anotherCol.first().get('a'), 2)
equal(anotherCol.last().get('a'), 4);
var models = [{a: 1}, {a: 2}, {a: 3}, {a: 4}];
var collection = new Collection(models, {parse: true});
strictEqual(collection.length, 2);
strictEqual(collection.first().get('a'), 2);
strictEqual(collection.last().get('a'), 4);
});
test("Collection: get, getByCid", 3, function() {
test("get, getByCid", 3, function() {
equal(col.get(0), d);
equal(col.get(2), b);
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 MongoModel = Backbone.Model.extend({
idAttribute: '_id'
@@ -88,7 +67,7 @@ $(document).ready(function() {
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();
col.add([
{id : 0, name : 'one'},
@@ -101,15 +80,15 @@ $(document).ready(function() {
equal(col.get(101).get('name'), 'one');
});
test("Collection: at", 1, function() {
test("at", 1, function() {
equal(col.at(2), c);
});
test("Collection: pluck", 1, function() {
test("pluck", 1, function() {
equal(col.pluck('label').join(' '), 'a b c d');
});
test("Collection: add", 11, function() {
test("add", 11, function() {
var added, opts, secondAdded;
added = opts = secondAdded = null;
e = new Backbone.Model({id: 10, label : 'e'});
@@ -141,7 +120,7 @@ $(document).ready(function() {
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}]);
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++) {
@@ -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({
comparator: function(a,b) {
return a.id > b.id ? -1 : 1;
@@ -162,19 +141,19 @@ $(document).ready(function() {
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}]);
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;
col.unshift({id: 101});
col.add({id: 101});
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;
col.add([{id: 1, name: 'Moe'}, {id: 2, name: 'Curly'}, {id: 3, name: 'Larry'}]);
col.add({id: 1, name: 'Moses'});
@@ -185,7 +164,7 @@ $(document).ready(function() {
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 e = new Backbone.Model({id: 10, label : 'e'});
e.on('add', function(model, collection) {
@@ -213,7 +192,7 @@ $(document).ready(function() {
equal(e.collection, colE);
});
test("Collection: add model with parse", 1, function() {
test("add model with parse", 1, function() {
var Model = Backbone.Model.extend({
parse: function(obj) {
obj.value += 1;
@@ -227,7 +206,7 @@ $(document).ready(function() {
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;
col.comparator = function(a, b) {
return a.get('name') < b.get('name') ? -1 : 1;
@@ -243,7 +222,7 @@ $(document).ready(function() {
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;
col.negative = function(num) {
return -num;
@@ -255,7 +234,7 @@ $(document).ready(function() {
equal(col.pluck('id').join(' '), '3 2 1');
});
test("Collection: remove", 5, function() {
test("remove", 5, function() {
var removed = null;
var otherRemoved = null;
col.on('remove', function(model, col, options) {
@@ -272,20 +251,20 @@ $(document).ready(function() {
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'}]);
equal(col.shift().get('a'), 'a');
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 array = col.slice(1, 3);
equal(array.length, 2);
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 dj = new Backbone.Model();
var emcees = new Backbone.Collection([dj]);
@@ -298,7 +277,7 @@ $(document).ready(function() {
equal(counter, 1);
});
test("Collection: remove in multiple collections", 7, function() {
test("remove in multiple collections", 7, function() {
var modelData = {
id : 5,
title : 'Othello'
@@ -322,7 +301,7 @@ $(document).ready(function() {
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 e = new Backbone.Model({id: 5, title: 'Othello'});
e.on('remove', function(model, collection) {
@@ -356,7 +335,7 @@ $(document).ready(function() {
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'});
e.sync = function(method, model, options) { options.success({}); };
var colE = new Backbone.Collection([e]);
@@ -378,25 +357,29 @@ $(document).ready(function() {
equal(undefined, e.collection);
});
test("Collection: fetch", 4, function() {
col.fetch();
equal(lastRequest.method, 'read');
equal(lastRequest.model, col);
equal(lastRequest.options.parse, true);
test("fetch", 4, function() {
var collection = new Backbone.Collection;
collection.url = '/test';
collection.fetch();
equal(this.syncArgs.method, 'read');
equal(this.syncArgs.model, collection);
equal(this.syncArgs.options.parse, true);
col.fetch({parse: false});
equal(lastRequest.options.parse, false);
collection.fetch({parse: false});
equal(this.syncArgs.options.parse, false);
});
test("Collection: create", 4, function() {
var model = col.create({label: 'f'}, {wait: true});
equal(lastRequest.method, 'create');
equal(lastRequest.model, model);
test("create", 4, function() {
var collection = new Backbone.Collection;
collection.url = '/test';
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.collection, col);
equal(model.collection, collection);
});
test("Collection: create enforces validation", 1, function() {
test("create enforces validation", 1, function() {
var ValidatingModel = Backbone.Model.extend({
validate: function(attrs) {
return "fail";
@@ -409,7 +392,7 @@ $(document).ready(function() {
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({
validate: function(attrs) {
return "fail";
@@ -425,7 +408,7 @@ $(document).ready(function() {
equal(flag, true);
});
test("collection: initialize", 1, function() {
test("initialize", 1, function() {
var Collection = Backbone.Collection.extend({
initialize: function() {
this.one = 1;
@@ -435,11 +418,11 @@ $(document).ready(function() {
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"}]');
});
test("Collection: where", 6, function() {
test("where", 6, function() {
var coll = new Backbone.Collection([
{a: 1},
{a: 1},
@@ -455,7 +438,7 @@ $(document).ready(function() {
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.any(function(model){ return model.id === 100; }), false);
equal(col.any(function(model){ return model.id === 0; }), true);
@@ -475,7 +458,7 @@ $(document).ready(function() {
[4, 0]);
});
test("Collection: reset", 10, function() {
test("reset", 10, function() {
var resetCount = 0;
var models = col.models;
col.on('reset', function() { resetCount += 1; });
@@ -494,7 +477,7 @@ $(document).ready(function() {
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({
initialize: function(attrs, options) {
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;
a.on("custom", function() { fired = true; });
a.trigger("custom");
equal(fired, true);
});
test("Collection: add does not alter arguments", 2, function(){
test("add does not alter arguments", 2, function(){
var attrs = {};
var models = [attrs];
new Backbone.Collection().add(models);
@@ -524,16 +507,17 @@ $(document).ready(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({
set: function(attrs) {
equal(attrs.prop, 'value');
equal(this.collection, col);
equal(this.collection, collection);
return this;
}
});
col.model = Model;
col.create({prop: 'value'});
collection.model = Model;
collection.create({prop: 'value'});
});
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 col = new Backbone.Collection([{id: 2}, {id: 4}], {
comparator: function(model){ return model.id; }
@@ -582,7 +566,7 @@ $(document).ready(function() {
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();
col.on('test', function() { ok(false); });
col.model = Backbone.Model.extend({
@@ -596,7 +580,7 @@ $(document).ready(function() {
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 model = new Backbone.Model();
col.add([model, model]);
@@ -629,7 +613,7 @@ $(document).ready(function() {
ok(!collection.get('undefined'));
});
test("Collection: falsy comparator", 4, function(){
test("falsy comparator", 4, function(){
var Col = Backbone.Collection.extend({
comparator: function(model){ return model.id; }
});
@@ -659,15 +643,10 @@ $(document).ready(function() {
});
test("#1412 - Trigger 'sync' event.", 2, function() {
var collection = new Backbone.Collection([], {
model: Backbone.Model.extend({
sync: function(method, model, options) {
options.success();
}
})
});
collection.sync = function(method, model, options) { options.success(); };
var collection = new Backbone.Collection;
collection.url = '/test';
collection.on('sync', function() { ok(true); });
Backbone.ajax = function(settings){ settings.success(); };
collection.fetch();
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");
test("Events: on and trigger", 2, function() {
test("on and trigger", 2, function() {
var obj = { counter: 0 };
_.extend(obj,Backbone.Events);
obj.on('event', function() { obj.counter += 1; });
@@ -15,7 +15,7 @@ $(document).ready(function() {
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 };
_.extend(obj,Backbone.Events);
@@ -35,7 +35,7 @@ $(document).ready(function() {
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 };
_.extend(obj, Backbone.Events);
obj.on('all', function(event) {
@@ -49,7 +49,7 @@ $(document).ready(function() {
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 };
_.extend(obj,Backbone.Events);
var callback = function() { obj.counter += 1; };
@@ -60,7 +60,7 @@ $(document).ready(function() {
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 };
_.extend(obj,Backbone.Events);
var callback = function() { obj.counterA += 1; };
@@ -73,7 +73,7 @@ $(document).ready(function() {
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};
_.extend(obj, Backbone.Events);
var callback = function() {
@@ -87,7 +87,7 @@ $(document).ready(function() {
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 };
_.extend(obj,Backbone.Events);
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.');
});
test("Events: bind a callback with a supplied context", 1, function () {
test("bind a callback with a supplied context", 1, function () {
var TestClass = function () {
return this;
};
@@ -114,7 +114,7 @@ $(document).ready(function() {
obj.trigger('event');
});
test("Events: nested trigger with unbind", 1, function () {
test("nested trigger with unbind", 1, function () {
var obj = { counter: 0 };
_.extend(obj, Backbone.Events);
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');
});
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 incr = function(){ counter++; };
obj.on('event', function(){ obj.on('event', incr).on('all', incr); })

View File

@@ -1,22 +1,15 @@
$(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 klass = Backbone.Collection.extend({
url : function() { return '/collection'; }
});
var doc, collection;
module("Backbone.Model", {
module("Backbone.Model", _.extend(new Environment, {
setup: function() {
Environment.prototype.setup.apply(this, arguments);
doc = new proxy({
id : '1-the-tempest',
title : "The Tempest",
@@ -25,29 +18,11 @@ $(document).ready(function() {
});
collection = new klass();
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({
initialize: function() {
this.one = 1;
@@ -59,7 +34,7 @@ $(document).ready(function() {
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({
initialize: function(attributes, options) {
this.one = options.one;
@@ -69,7 +44,7 @@ $(document).ready(function() {
equal(model.one, 1);
});
test("Model: initialize with parsed attributes", 1, function() {
test("initialize with parsed attributes", 1, function() {
var Model = Backbone.Model.extend({
parse: function(obj) {
obj.value += 1;
@@ -80,7 +55,7 @@ $(document).ready(function() {
equal(model.get('value'), 2);
});
test("Model: url", 3, function() {
test("url", 3, function() {
doc.urlRoot = null;
equal(doc.url(), '/collection/1-the-tempest');
doc.collection.url = '/collection/';
@@ -90,7 +65,7 @@ $(document).ready(function() {
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({
urlRoot: '/collection'
});
@@ -100,7 +75,7 @@ $(document).ready(function() {
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({
urlRoot: function() {
return '/nested/' + this.get('parent_id') + '/collection';
@@ -113,7 +88,7 @@ $(document).ready(function() {
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 b = a.clone();
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.");
});
test("Model: isNew", 6, function() {
test("isNew", 6, function() {
var a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3});
ok(a.isNew(), "it should be new");
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");
});
test("Model: get", 2, function() {
test("get", 2, function() {
equal(doc.get('title'), 'The Tempest');
equal(doc.get('author'), 'Bill Shakespeare');
});
test("Model: escape", 5, function() {
test("escape", 5, function() {
equal(doc.escape('title'), 'The Tempest');
doc.set({audience: 'Bill & Bob'});
equal(doc.escape('audience'), 'Bill &amp; Bob');
@@ -156,7 +131,7 @@ $(document).ready(function() {
equal(doc.escape('audience'), '');
});
test("Model: has", 10, function() {
test("has", 10, function() {
var model = new Backbone.Model();
strictEqual(model.has('name'), false);
@@ -186,7 +161,7 @@ $(document).ready(function() {
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 changeCount = 0;
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.");
});
test("Model: multiple unsets", 1, function() {
test("multiple unsets", 1, function() {
var i = 0;
var counter = function(){ i++; };
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.');
});
test("Model: unset and changedAttributes", 2, function() {
test("unset and changedAttributes", 2, function() {
var model = new Backbone.Model({a: 1});
model.unset('a', {silent: true});
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.');
});
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 model = new MongoModel({id: 'eye-dee', _id: 25, title: 'Model'});
equal(model.get('id'), 'eye-dee');
@@ -241,13 +216,13 @@ $(document).ready(function() {
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"});
model.set({name : ''});
equal(model.get('name'), '');
});
test("Model: clear", 3, function() {
test("clear", 3, function() {
var changed;
var model = new Backbone.Model({id: 1, name : "Model"});
model.on("change:name", function(){ changed = true; });
@@ -260,7 +235,7 @@ $(document).ready(function() {
equal(model.get('name'), undefined);
});
test("Model: defaults", 4, function() {
test("defaults", 4, function() {
var Defaulted = Backbone.Model.extend({
defaults: {
"one": 1,
@@ -283,7 +258,7 @@ $(document).ready(function() {
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});
equal(model.changedAttributes(), false);
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'});
equal(model.changedAttributes(), false);
equal(model.changedAttributes({a: 'a'}), false);
equal(model.changedAttributes({a: 'b'}).a, 'b');
});
test("Model: change with options", 2, function() {
test("change with options", 2, function() {
var value;
var model = new Backbone.Model({name: 'Rob'});
model.on('change', function(model, options) {
@@ -324,7 +299,7 @@ $(document).ready(function() {
equal(value, 'Ms. Sue');
});
test("Model: change after initialize", 1, function () {
test("change after initialize", 1, function () {
var changed = 0;
var attrs = {id: 1, label: 'c'};
var obj = new Backbone.Model(attrs);
@@ -333,16 +308,18 @@ $(document).ready(function() {
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"});
model.url = '/test';
model.on('change', function () {
model.save();
ok(_.isEqual(lastRequest.model, model));
ok(_.isEqual(env.syncArgs.model, model));
});
model.set({lastName: 'Hicks'});
});
test("Model: validate after save", 1, function() {
test("validate after save", 1, function() {
var lastError, model = new Backbone.Model();
model.validate = function(attrs) {
if (attrs.admin) return "Can't change admin status.";
@@ -357,7 +334,7 @@ $(document).ready(function() {
equal(lastError, "Can't change admin status.");
});
test("Model: isValid", 5, function() {
test("isValid", 5, function() {
var model = new Backbone.Model({valid: true});
model.validate = function(attrs) {
if (!attrs.valid) return "invalid";
@@ -369,13 +346,13 @@ $(document).ready(function() {
equal(model.isValid(), false);
});
test("Model: save", 2, function() {
test("save", 2, function() {
doc.save({title : "Henry V"});
equal(lastRequest.method, 'update');
ok(_.isEqual(lastRequest.model, doc));
equal(this.syncArgs.method, 'update');
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();
model.sync = function(method, model, options) {
options.success();
@@ -386,29 +363,29 @@ $(document).ready(function() {
test("Model: fetch", 2, function() {
test("fetch", 2, function() {
doc.fetch();
equal(lastRequest.method, 'read');
ok(_.isEqual(lastRequest.model, doc));
equal(this.syncArgs.method, 'read');
ok(_.isEqual(this.syncArgs.model, doc));
});
test("Model: destroy", 3, function() {
test("destroy", 3, function() {
doc.destroy();
equal(lastRequest.method, 'delete');
ok(_.isEqual(lastRequest.model, doc));
equal(this.syncArgs.method, 'delete');
ok(_.isEqual(this.syncArgs.model, doc));
var newModel = new Backbone.Model;
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});
a.sync = function() { throw "should not be called"; };
a.destroy();
ok(true, "non-persisted model should not call sync");
});
test("Model: validate", 7, function() {
test("validate", 7, function() {
var lastError;
var model = new Backbone.Model();
model.validate = function(attrs) {
@@ -429,7 +406,7 @@ $(document).ready(function() {
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 model = new Backbone.Model({name: "One"});
model.validate = function(attrs) {
@@ -451,7 +428,7 @@ $(document).ready(function() {
equal(model.get('name'), undefined);
});
test("Model: validate with error callback", 8, function() {
test("validate with error callback", 8, function() {
var lastError, boundError;
var model = new Backbone.Model();
model.validate = function(attrs) {
@@ -472,10 +449,10 @@ $(document).ready(function() {
equal(result, false);
equal(model.get('a'), 100);
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({
defaults: {one: 1},
initialize : function(attrs, opts) {
@@ -486,7 +463,7 @@ $(document).ready(function() {
var emptyattrs = new Defaulted();
});
test("Model: Inherit class properties", 6, function() {
test("Inherit class properties", 6, function() {
var Parent = Backbone.Model.extend({
instancePropSame: function() {},
instancePropDiff: function() {}
@@ -510,7 +487,7 @@ $(document).ready(function() {
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()
.on('change:state', function(model, newState) {
equal(model.previous('state'), undefined);
@@ -595,8 +572,9 @@ $(document).ready(function() {
test("save with `wait` succeeds without `validate`", 1, function() {
var model = new Backbone.Model();
model.url = '/test';
model.save({x: 1}, {wait: true});
ok(lastRequest.model === model);
ok(this.syncArgs.model === model);
});
test("`hasChanged` for falsey keys", 2, function() {
@@ -616,18 +594,20 @@ $(document).ready(function() {
test("`save` with `wait` sends correct attributes", 5, function() {
var changed = 0;
var model = new Backbone.Model({x: 1, y: 2});
model.url = '/test';
model.on('change:x', function() { changed++; });
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(changed, 0);
lastRequest.options.success({});
this.syncArgs.options.success({});
equal(model.get('x'), 3);
equal(changed, 1);
});
test("a failed `save` with `wait` doesn't leave attributes behind", 1, function() {
var model = new Backbone.Model;
model.url = '/test';
model.save({x: 1}, {wait: true});
equal(model.get('x'), void 0);
});
@@ -644,6 +624,7 @@ $(document).ready(function() {
test("save with wait validates attributes", 1, function() {
var model = new Backbone.Model();
model.url = '/test';
model.validate = function() { ok(true); };
model.save({x: 1}, {wait: true});
});
@@ -776,24 +757,6 @@ $(document).ready(function() {
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() {
var model = new Backbone.Model();
model.validate = null;
@@ -831,8 +794,9 @@ $(document).ready(function() {
test("#1412 - Trigger 'sync' event.", 3, function() {
var model = new Backbone.Model({id: 1});
model.sync = function(method, model, options) { options.success(); };
model.on('sync', function() { ok(true); });
model.url = '/test';
model.on('sync', function(){ ok(true); });
Backbone.ajax = function(settings){ settings.success(); };
model.fetch();
model.save();
model.destroy();

View File

@@ -2,7 +2,7 @@ $(document).ready(function() {
module("Backbone.noConflict");
test('Backbone.noConflict', 2, function() {
test('noConflict', 2, function() {
var noconflictBackbone = Backbone.noConflict();
equal(window.Backbone, undefined, 'Returned window.Backbone');
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);
});
test("Router: routes (simple)", 4, function() {
test("routes (simple)", 4, function() {
location.replace('http://example.com#search/news');
Backbone.history.checkUrl();
equal(router.query, 'news');
@@ -139,26 +139,26 @@ $(document).ready(function() {
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');
Backbone.history.checkUrl();
equal(router.query, 'nyc');
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});
equal(router.query, 'manhattan');
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);
equal(router.query, 'manhattan');
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
_.each([ { trigger: true }, true ], function( options ){
Backbone.history.navigate('contacts', options);
@@ -178,13 +178,13 @@ $(document).ready(function() {
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.navigate('implicit', {trigger: true});
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');
Backbone.history.checkUrl();
location.replace = function(href) {
@@ -193,13 +193,13 @@ $(document).ready(function() {
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');
Backbone.history.checkUrl();
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');
Backbone.history.checkUrl();
equal(router.first, 'one/two/three');
@@ -207,7 +207,7 @@ $(document).ready(function() {
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');
Backbone.history.checkUrl();
equal(router.entity, 'mandel');
@@ -217,13 +217,13 @@ $(document).ready(function() {
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');
Backbone.history.checkUrl();
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); });
location.replace('http://example.com#noCallback');
Backbone.history.checkUrl();
@@ -244,17 +244,14 @@ $(document).ready(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();
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 (!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';
Backbone.history.navigate(route, {trigger: true});
equal(router.first, 'has/slash');
@@ -262,7 +259,7 @@ $(document).ready(function() {
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');
Backbone.history.checkUrl();
location.replace('http://example.com#search/fat');
@@ -320,7 +317,7 @@ $(document).ready(function() {
strictEqual(Backbone.history.fragment, 'x');
});
test("Router: Normalize root.", 1, function() {
test("Normalize root.", 1, function() {
Backbone.history.stop();
location.replace('http://example.com/root');
Backbone.history = new Backbone.History({
@@ -339,7 +336,7 @@ $(document).ready(function() {
Backbone.history.navigate('fragment');
});
test("Router: Normalize root.", 1, function() {
test("Normalize root.", 1, function() {
Backbone.history.stop();
location.replace('http://example.com/root#fragment');
Backbone.history = new Backbone.History({
@@ -347,7 +344,7 @@ $(document).ready(function() {
history: {
pushState: 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();
location.replace('http://example.com/root');
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() {
var ajax = Backbone.ajax;
var lastRequest = null;
var Library = Backbone.Collection.extend({
url : function() { return '/library'; }
});
@@ -14,132 +11,126 @@ $(document).ready(function() {
length : 123
};
module("Backbone.sync", {
module("Backbone.sync", _.extend(new Environment, {
setup : function() {
library = new Library();
Backbone.ajax = function(obj) {
lastRequest = obj;
};
Environment.prototype.setup.apply(this, arguments);
library = new Library;
library.create(attrs, {wait: false});
},
teardown: function() {
Backbone.ajax = ajax;
}
});
}));
test("sync: read", 4, function() {
test("read", 4, function() {
library.fetch();
equal(lastRequest.url, '/library');
equal(lastRequest.type, 'GET');
equal(lastRequest.dataType, 'json');
ok(_.isEmpty(lastRequest.data));
equal(this.ajaxSettings.url, '/library');
equal(this.ajaxSettings.type, 'GET');
equal(this.ajaxSettings.dataType, 'json');
ok(_.isEmpty(this.ajaxSettings.data));
});
test("sync: passing data", 3, function() {
test("passing data", 3, function() {
library.fetch({data: {a: 'a', one: 1}});
equal(lastRequest.url, '/library');
equal(lastRequest.data.a, 'a');
equal(lastRequest.data.one, 1);
equal(this.ajaxSettings.url, '/library');
equal(this.ajaxSettings.data.a, 'a');
equal(this.ajaxSettings.data.one, 1);
});
test("sync: create", 6, function() {
equal(lastRequest.url, '/library');
equal(lastRequest.type, 'POST');
equal(lastRequest.dataType, 'json');
var data = JSON.parse(lastRequest.data);
test("create", 6, function() {
equal(this.ajaxSettings.url, '/library');
equal(this.ajaxSettings.type, 'POST');
equal(this.ajaxSettings.dataType, 'json');
var data = JSON.parse(this.ajaxSettings.data);
equal(data.title, 'The Tempest');
equal(data.author, 'Bill Shakespeare');
equal(data.length, 123);
});
test("sync: update", 7, function() {
test("update", 7, function() {
library.first().save({id: '1-the-tempest', author: 'William Shakespeare'});
equal(lastRequest.url, '/library/1-the-tempest');
equal(lastRequest.type, 'PUT');
equal(lastRequest.dataType, 'json');
var data = JSON.parse(lastRequest.data);
equal(this.ajaxSettings.url, '/library/1-the-tempest');
equal(this.ajaxSettings.type, 'PUT');
equal(this.ajaxSettings.dataType, 'json');
var data = JSON.parse(this.ajaxSettings.data);
equal(data.id, '1-the-tempest');
equal(data.title, 'The Tempest');
equal(data.author, 'William Shakespeare');
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;
library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'});
equal(lastRequest.url, '/library/2-the-tempest');
equal(lastRequest.type, 'POST');
equal(lastRequest.dataType, 'json');
equal(lastRequest.data._method, 'PUT');
var data = JSON.parse(lastRequest.data.model);
equal(this.ajaxSettings.url, '/library/2-the-tempest');
equal(this.ajaxSettings.type, 'POST');
equal(this.ajaxSettings.dataType, 'json');
equal(this.ajaxSettings.data._method, 'PUT');
var data = JSON.parse(this.ajaxSettings.data.model);
equal(data.id, '2-the-tempest');
equal(data.author, 'Tim Shakespeare');
equal(data.length, 123);
Backbone.emulateHTTP = Backbone.emulateJSON = false;
});
test("sync: update with just emulateHTTP", 6, function() {
test("update with just emulateHTTP", 6, function() {
Backbone.emulateHTTP = true;
library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'});
equal(lastRequest.url, '/library/2-the-tempest');
equal(lastRequest.type, 'POST');
equal(lastRequest.contentType, 'application/json');
var data = JSON.parse(lastRequest.data);
equal(this.ajaxSettings.url, '/library/2-the-tempest');
equal(this.ajaxSettings.type, 'POST');
equal(this.ajaxSettings.contentType, 'application/json');
var data = JSON.parse(this.ajaxSettings.data);
equal(data.id, '2-the-tempest');
equal(data.author, 'Tim Shakespeare');
equal(data.length, 123);
Backbone.emulateHTTP = false;
});
test("sync: update with just emulateJSON", 6, function() {
test("update with just emulateJSON", 6, function() {
Backbone.emulateJSON = true;
library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'});
equal(lastRequest.url, '/library/2-the-tempest');
equal(lastRequest.type, 'PUT');
equal(lastRequest.contentType, 'application/x-www-form-urlencoded');
var data = JSON.parse(lastRequest.data.model);
equal(this.ajaxSettings.url, '/library/2-the-tempest');
equal(this.ajaxSettings.type, 'PUT');
equal(this.ajaxSettings.contentType, 'application/x-www-form-urlencoded');
var data = JSON.parse(this.ajaxSettings.data.model);
equal(data.id, '2-the-tempest');
equal(data.author, 'Tim Shakespeare');
equal(data.length, 123);
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().fetch();
equal(lastRequest.url, '/library/2-the-tempest');
equal(lastRequest.type, 'GET');
ok(_.isEmpty(lastRequest.data));
equal(this.ajaxSettings.url, '/library/2-the-tempest');
equal(this.ajaxSettings.type, 'GET');
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().destroy({wait: true});
equal(lastRequest.url, '/library/2-the-tempest');
equal(lastRequest.type, 'DELETE');
equal(lastRequest.data, null);
equal(this.ajaxSettings.url, '/library/2-the-tempest');
equal(this.ajaxSettings.type, 'DELETE');
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'});
Backbone.emulateHTTP = Backbone.emulateJSON = true;
library.first().destroy();
equal(lastRequest.url, '/library/2-the-tempest');
equal(lastRequest.type, 'POST');
equal(JSON.stringify(lastRequest.data), '{"_method":"DELETE"}');
equal(this.ajaxSettings.url, '/library/2-the-tempest');
equal(this.ajaxSettings.type, 'POST');
equal(JSON.stringify(this.ajaxSettings.data), '{"_method":"DELETE"}');
Backbone.emulateHTTP = Backbone.emulateJSON = false;
});
test("sync: urlError", 2, function() {
test("urlError", 2, function() {
var model = new Backbone.Model();
raises(function() {
model.fetch();
});
model.fetch({url: '/one/two'});
equal(lastRequest.url, '/one/two');
equal(this.ajaxSettings.url, '/one/two');
});
test("#1052 - `options` is optional.", 0, function() {
@@ -157,4 +148,13 @@ $(document).ready(function() {
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
* Dual licensed under the MIT (MIT-LICENSE.txt)
* or GPL (GPL-LICENSE.txt) licenses.
* Copyright 2012 jQuery Foundation and other contributors
* Released under the MIT license.
* http://jquery.org/license
*/
/** Font Family and Sizes */
@@ -20,7 +20,7 @@
/** 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;
padding: 0;
}
@@ -38,10 +38,10 @@
line-height: 1em;
font-weight: normal;
border-radius: 15px 15px 0 0;
-moz-border-radius: 15px 15px 0 0;
-webkit-border-top-right-radius: 15px;
-webkit-border-top-left-radius: 15px;
border-radius: 5px 5px 0 0;
-moz-border-radius: 5px 5px 0 0;
-webkit-border-top-right-radius: 5px;
-webkit-border-top-left-radius: 5px;
}
#qunit-header a {
@@ -54,9 +54,9 @@
color: #fff;
}
#qunit-header label {
#qunit-testrunner-toolbar label {
display: inline-block;
padding-left: 0.5em;
padding: 0 .5em 0 .1em;
}
#qunit-banner {
@@ -67,6 +67,7 @@
padding: 0.5em 0 0.5em 2em;
color: #5E740B;
background-color: #eee;
overflow: hidden;
}
#qunit-userAgent {
@@ -76,6 +77,9 @@
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
}
#qunit-modulefilter-container {
float: right;
}
/** Tests: Pass/Fail */
@@ -113,13 +117,9 @@
background-color: #fff;
border-radius: 15px;
-moz-border-radius: 15px;
-webkit-border-radius: 15px;
box-shadow: inset 0px 2px 13px #999;
-moz-box-shadow: inset 0px 2px 13px #999;
-webkit-box-shadow: inset 0px 2px 13px #999;
border-radius: 5px;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
}
#qunit-tests table {
@@ -162,8 +162,7 @@
#qunit-tests b.failed { color: #710909; }
#qunit-tests li li {
margin: 0.5em;
padding: 0.4em 0.5em 0.4em 0.5em;
padding: 5px;
background-color: #fff;
border-bottom: none;
list-style-position: inside;
@@ -172,9 +171,9 @@
/*** Passing Styles */
#qunit-tests li li.pass {
color: #5E740B;
color: #3c510c;
background-color: #fff;
border-left: 26px solid #C6E746;
border-left: 10px solid #C6E746;
}
#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
@@ -190,15 +189,15 @@
#qunit-tests li li.fail {
color: #710909;
background-color: #fff;
border-left: 26px solid #EE5757;
border-left: 10px solid #EE5757;
white-space: pre;
}
#qunit-tests > li:last-child {
border-radius: 0 0 15px 15px;
-moz-border-radius: 0 0 15px 15px;
-webkit-border-bottom-right-radius: 15px;
-webkit-border-bottom-left-radius: 15px;
border-radius: 0 0 5px 5px;
-moz-border-radius: 0 0 5px 5px;
-webkit-border-bottom-right-radius: 5px;
-webkit-border-bottom-left-radius: 5px;
}
#qunit-tests .fail { color: #000000; background-color: #EE5757; }

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
* Dual licensed under the MIT (MIT-LICENSE.txt)
* or GPL (GPL-LICENSE.txt) licenses.
* Copyright 2012 jQuery Foundation and other contributors
* Released under the MIT license.
* http://jquery.org/license
*/
(function( window ) {
@@ -17,6 +17,8 @@ var QUnit,
fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""),
toString = Object.prototype.toString,
hasOwn = Object.prototype.hasOwnProperty,
// Keep a local reference to Date (GH-283)
Date = window.Date,
defined = {
setTimeout: typeof window.setTimeout !== "undefined",
sessionStorage: (function() {
@@ -304,7 +306,8 @@ QUnit = {
// call on start of module test to prepend name to all tests
module: function( name, testEnvironment ) {
config.currentModule = name;
config.currentModuleTestEnviroment = testEnvironment;
config.currentModuleTestEnvironment = testEnvironment;
config.modules[name] = true;
},
asyncTest: function( testName, expected, callback ) {
@@ -336,7 +339,7 @@ QUnit = {
async: async,
callback: callback,
module: config.currentModule,
moduleTestEnvironment: config.currentModuleTestEnviroment,
moduleTestEnvironment: config.currentModuleTestEnvironment,
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.
expect: function( asserts ) {
config.current.expected = asserts;
if (arguments.length === 1) {
config.current.expected = asserts;
} else {
return config.current.expected;
}
},
start: function( count ) {
@@ -403,6 +410,8 @@ QUnit = {
QUnit.assert = {
/**
* Asserts rough true-ish result.
* @name ok
* @function
* @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
*/
ok: function( result, msg ) {
@@ -413,6 +422,8 @@ QUnit.assert = {
var source,
details = {
module: config.current.module,
name: config.current.testName,
result: result,
message: msg
};
@@ -437,36 +448,59 @@ QUnit.assert = {
/**
* Assert that the first two arguments are equal, with an optional message.
* Prints out both actual and expected values.
* @name equal
* @function
* @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" );
*/
equal: function( actual, expected, message ) {
QUnit.push( expected == actual, actual, expected, message );
},
/**
* @name notEqual
* @function
*/
notEqual: function( actual, expected, message ) {
QUnit.push( expected != actual, actual, expected, message );
},
/**
* @name deepEqual
* @function
*/
deepEqual: function( actual, expected, message ) {
QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
},
/**
* @name notDeepEqual
* @function
*/
notDeepEqual: function( actual, expected, message ) {
QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message );
},
/**
* @name strictEqual
* @function
*/
strictEqual: function( actual, expected, message ) {
QUnit.push( expected === actual, actual, expected, message );
},
/**
* @name notStrictEqual
* @function
*/
notStrictEqual: function( actual, expected, message ) {
QUnit.push( expected !== actual, actual, expected, message );
},
raises: function( block, expected, message ) {
throws: function( block, expected, message ) {
var actual,
ok = false;
// 'expected' is optional
if ( typeof expected === "string" ) {
message = expected;
expected = null;
@@ -494,18 +528,29 @@ QUnit.assert = {
} else if ( expected.call( {}, actual ) === 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 );
/**
* @deprecated: Kept for backwards compatibility
* next step: remove entirely
* @deprecated since 1.9.0
* 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.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()
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
begin: [],
@@ -661,17 +722,10 @@ extend( QUnit, {
},
// Resets the test setup. Useful for tests that modify the DOM.
// If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
reset: function() {
var fixture;
if ( window.jQuery ) {
jQuery( "#qunit-fixture" ).html( config.fixture );
} else {
fixture = id( "qunit-fixture" );
if ( fixture ) {
fixture.innerHTML = config.fixture;
}
var fixture = id( "qunit-fixture" );
if ( fixture ) {
fixture.innerHTML = config.fixture;
}
},
@@ -732,6 +786,8 @@ extend( QUnit, {
var output, source,
details = {
module: config.current.module,
name: config.current.testName,
result: result,
message: message,
actual: actual,
@@ -770,26 +826,36 @@ extend( QUnit, {
});
},
pushFailure: function( message, source ) {
pushFailure: function( message, source, actual ) {
if ( !config.current ) {
throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) );
}
var output,
details = {
module: config.current.module,
name: config.current.testName,
result: false,
message: message
};
message = escapeInnerText(message ) || "error";
message = escapeInnerText( message ) || "error";
message = "<span class='test-message'>" + message + "</span>";
output = message;
output += "<table>";
if ( actual ) {
output += "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeInnerText( actual ) + "</pre></td></tr>";
}
if ( 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 );
config.current.assertions.push({
@@ -859,7 +925,9 @@ QUnit.load = function() {
runLoggingCallbacks( "begin", QUnit, {} );
// 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 = "",
oldconfig = extend( {}, config );
@@ -872,10 +940,26 @@ QUnit.load = function() {
for ( i = 0; i < len; i++ ) {
val = config.urlConfig[i];
config[val] = QUnit.urlParams[val];
urlConfigHtml += "<label><input name='" + val + "' type='checkbox'" + ( config[val] ? " checked='checked'" : "" ) + ">" + val + "</label>";
if ( typeof val === "string" ) {
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 = id( "qunit-userAgent" );
if ( userAgent ) {
@@ -885,12 +969,7 @@ QUnit.load = function() {
// `banner` initialized at top of scope
banner = id( "qunit-header" );
if ( banner ) {
banner.innerHTML = "<a href='" + QUnit.url({ filter: undefined }) + "'>" + banner.innerHTML + "</a> " + urlConfigHtml;
addEvent( banner, "change", function( event ) {
var params = {};
params[ event.target.name ] = event.target.checked ? true : undefined;
window.location = QUnit.url( params );
});
banner.innerHTML = "<a href='" + QUnit.url({ filter: undefined, module: undefined, testNumber: undefined }) + "'>" + banner.innerHTML + "</a> ";
}
// `toolbar` initialized at top of scope
@@ -931,8 +1010,31 @@ QUnit.load = function() {
// `label` initialized at top of scope
label = document.createElement( "label" );
label.setAttribute( "for", "qunit-filter-pass" );
label.setAttribute( "title", "Only show tests and assertons that fail. Stored in sessionStorage." );
label.innerHTML = "Hide passed tests";
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
@@ -970,9 +1072,9 @@ window.onerror = function ( error, filePath, linerNr ) {
}
QUnit.pushFailure( error, filePath + ":" + linerNr );
} else {
QUnit.test( "global failure", function() {
QUnit.test( "global failure", extend( function() {
QUnit.pushFailure( error, filePath + ":" + linerNr );
});
}, { validTest: validTest } ) );
}
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, {
failed: config.stats.bad,
passed: passed,
@@ -1051,14 +1158,20 @@ function done() {
function validTest( test ) {
var include,
filter = config.filter && config.filter.toLowerCase(),
module = config.module,
module = config.module && config.module.toLowerCase(),
fullName = (test.module + ": " + test.testName).toLowerCase();
// Internally-generated tests are always valid
if ( test.callback && test.callback.validTest === validTest ) {
delete test.callback.validTest;
return true;
}
if ( config.testNumber ) {
return test.testNumber === config.testNumber;
}
if ( module && test.module !== module ) {
if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) {
return false;
}
@@ -1335,7 +1448,8 @@ QUnit.equiv = (function() {
a.global === b.global &&
// (gmi) ...
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)

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

View File

@@ -14,7 +14,7 @@ For a list of upcoming features, check out our [roadmap](https://github.com/best
## Support
Benchmark.js has been tested in at least Adobe AIR 3.1, Chrome 5-21, Firefox 1-14, IE 6-9, Opera 9.25-12, Safari 3-6, Node.js 0.8.7, Narwhal 0.3.2, RingoJS 0.8, and Rhino 1.7RC5.
Benchmark.js has been tested in at least Adobe AIR 3.1, Chrome 5-21, Firefox 1-15, IE 6-9, Opera 9.25-12, Safari 3-6, Node.js 0.8.8, Narwhal 0.3.2, RingoJS 0.8, and Rhino 1.7RC5.
## Installation and usage

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
require(dirname(__FILE__) . "/Alias.php");
/**
* A class to simplify parsing a single JSDoc entry.
*/
@@ -37,7 +39,7 @@ class Entry {
* @constructor
* @param {String} $entry The documentation entry to analyse.
* @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' ) {
$this->entry = $entry;
@@ -70,16 +72,46 @@ class Entry {
* @returns {Boolean} Returns `true` if the entry is a function reference, else `false`.
*/
private function isFunction() {
return !!(
$this->isCtor() ||
count($this->getParams()) ||
count($this->getReturns()) ||
preg_match('/\*\s*@function\b/', $this->entry)
);
if (!isset($this->_isFunction)) {
$this->_isFunction = !!(
$this->isCtor() ||
count($this->getParams()) ||
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.
*
@@ -87,13 +119,17 @@ class Entry {
* @returns {String} The function call.
*/
public function getCall() {
if (isset($this->_call)) {
return $this->_call;
}
preg_match('#\*/\s*(?:function ([^(]*)|(.*?)(?=[:=,]|return\b))#', $this->entry, $result);
if ($result = array_pop($result)) {
$result = array_pop(explode('var ', trim(trim(array_pop(explode('.', $result))), "'")));
}
// resolve name
// 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)) {
$name = trim($name[1]);
} else {
@@ -111,178 +147,242 @@ class Entry {
$result = $name .'('. implode(array_slice($result, 1), ', ') .')';
$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
* @returns {String} The entry description.
* @returns {String} The entry's description.
*/
public function getDesc() {
if (isset($this->_desc)) {
return $this->_desc;
}
preg_match('#/\*\*(?:\s*\*)?([\s\S]*?)(?=\*\s\@[a-z]|\*/)#', $this->entry, $result);
if (count($result)) {
$type = $this->getType();
$result = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1]));
$result = ($type == 'Function' ? '' : '(' . str_replace('|', ', ', trim($type, '{}')) . '): ') . $result;
}
$this->_desc = $result;
return $result;
}
/**
* Extracts the entry `example` data.
* Extracts the entry's `example` data.
*
* @memberOf Entry
* @returns {String} The entry `example` data.
* @returns {String} The entry's `example` data.
*/
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)) {
$result = trim(preg_replace('/(?:^|\n)\s*\* ?/', "\n", $result[1]));
$result = '```' . $this->lang . "\n" . $result . "\n```";
}
$this->_example = $result;
return $result;
}
/**
* Resolves the line number of the entry.
* Resolves the entry's line number.
*
* @memberOf Entry
* @returns {Number} The line number.
* @returns {Number} The entry's line number.
*/
public function getLineNumber() {
preg_match_all('/\n/', substr($this->source, 0, strrpos($this->source, $this->entry) + strlen($this->entry)), $lines);
return count(array_pop($lines)) + 1;
if (!isset($this->_lineNumber)) {
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
* @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 ) {
preg_match('#\*\s*@member(?:Of)?\s+([^\n]+)#', $this->entry, $result);
if (count($result)) {
$result = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1]));
$result = preg_split('/,\s*/', $result);
if (!isset($this->_members)) {
preg_match('#\* *@member(?:Of)?\s+([^\n]+)#', $this->entry, $result);
if (count($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
* @returns {String} The entry `name` data.
* @returns {String} The entry's `name` data.
*/
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)) {
$result = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1]));
} else {
$result = array_shift(explode('(', $this->getCall()));
}
$this->_name = $result;
return $result;
}
/**
* Extracts the entry `param` data.
* Extracts the entry's `param` data.
*
* @memberOf Entry
* @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 ) {
preg_match_all('#\*\s*@param\s+\{([^}]+)\}\s+(\[.+\]|[$\w|]+(?:\[.+\])?)\s+([\s\S]*?)(?=\*\s\@[a-z]|\*/)#i', $this->entry, $result);
if (count($result = array_filter(array_slice($result, 1)))) {
// repurpose array
foreach ($result as $param) {
foreach ($param as $key => $value) {
if (!is_array($result[0][$key])) {
$result[0][$key] = array();
if (!isset($this->_params)) {
preg_match_all('#\* *@param\s+\{([^}]+)\}\s+(\[.+\]|[$\w|]+(?:\[.+\])?)\s+([\s\S]*?)(?=\*\s\@[a-z]|\*/)#i', $this->entry, $result);
if (count($result = array_filter(array_slice($result, 1)))) {
// repurpose array
foreach ($result as $param) {
foreach ($param as $key => $value) {
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
* @returns {String} The entry `returns` data.
* @returns {String} The entry's `returns` data.
*/
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)) {
$result = array_map('trim', array_slice($result, 1));
$result[0] = str_replace('|', ', ', $result[0]);
$result[1] = preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1]);
}
$this->_returns = $result;
return $result;
}
/**
* Extracts the entry `type` data.
* Extracts the entry's `type` data.
*
* @memberOf Entry
* @returns {String} The entry `type` data.
* @returns {String} The entry's `type` data.
*/
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)) {
$result = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1]));
} else {
$result = $this->isFunction() ? 'Function' : 'Unknown';
}
$this->_type = $result;
return $result;
}
/**
* Checks if an entry is a constructor.
* Checks if the entry is an alias.
*
* @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() {
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
* @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() {
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
* @returns {Boolean} Returns true if private, else false.
* @returns {Boolean} Returns `true` if private, else `false`.
*/
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
* @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() {
if (isset($this->_isStatic)) {
return $this->_isStatic;
}
$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
if ($public && !$result) {
@@ -298,6 +398,7 @@ class Entry {
$result = true;
}
}
$this->_isStatic = $result;
return $result;
}
}

View File

@@ -163,7 +163,7 @@ class Generator {
$member = !$member ? $entry->getMembers(0) : $member;
$result = ($member ? $member . ($entry->isPlugin() ? 'prototype' : '') : '') . $entry->getCall();
$result = preg_replace('/\(\[|\[\]/', '', $result);
$result = preg_replace('/[ =\'"{}.()\]]/', '', $result);
$result = preg_replace('/[ =|\'"{}.()\]]/', '', $result);
$result = preg_replace('/[[#,]/', '-', $result);
return strtolower($result);
}
@@ -212,31 +212,50 @@ class Generator {
// initialize $api array
foreach ($this->entries as $entry) {
// skip invalid or private entries
$name = $entry->getName();
if (!$name || $entry->isPrivate()) {
continue;
}
if (!$entry->isPrivate()) {
$name = $entry->getName();
$members = $entry->getMembers();
$members = count($members) ? $members : array('');
$members = $entry->getMembers();
$members = count($members) ? $members : array('');
foreach ($members as $member) {
// create api category arrays
if (!isset($api[$member]) && $member) {
$api[$member] = new Entry('', '', $entry->lang);
$api[$member]->static = array();
$api[$member]->plugin = array();
foreach ($members as $member) {
// create api category arrays
if (!isset($api[$member]) && $member) {
// create temporary entry to be replaced later
$api[$member] = new Entry('', '', $entry->lang);
$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' &&
!preg_match('/[=:]\s*null\s*[,;]?$/', $entry->entry))) {
$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;
}
else if ($entry->isStatic()) {
$api[$member]->static[] = $entry;
foreach ($entry->getAliases() as $alias) {
$api[$member]->static[] = $alias;
}
else if ($entry->isStatic()) {
$api[$member]->static[] = $entry;
} else if (!$entry->isCtor()) {
$api[$member]->plugin[] = $entry;
}
else if (!$entry->isCtor()) {
$api[$member]->plugin[] = $entry;
foreach ($entry->getAliases() as $alias) {
$api[$member]->plugin[] = $alias;
}
}
}
@@ -346,6 +365,11 @@ class Generator {
$result[] = $openTag;
foreach ($api as $entry) {
// skip aliases
if ($entry->isAlias()) {
continue;
}
// add root entry
$member = $entry->member . $entry->getName();
$compiling = $compiling ? ($result[] = $closeTag) : true;
@@ -370,6 +394,11 @@ class Generator {
// body
foreach ($subentries as $subentry) {
// skip aliases
if ($subentry->isAlias()) {
continue;
}
// description
array_push(
$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)
);
// @alias
if (count($aliases = $subentry->getAliases())) {
array_push($result, '', '#### Aliases');
foreach ($aliases as $index => $alias) {
$aliases[$index] = $alias->getName();
}
$result[] = '*' . implode(', ', $aliases) . '*';
}
// @param
if (count($params = $subentry->getParams())) {
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
@@ -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
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
--------
@@ -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
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
* Dual licensed under the MIT (MIT-LICENSE.txt)
* or GPL (GPL-LICENSE.txt) licenses.
* Copyright 2012 jQuery Foundation and other contributors
* Released under the MIT license.
* http://jquery.org/license
*/
/** Font Family and Sizes */
@@ -20,7 +20,7 @@
/** 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;
padding: 0;
}
@@ -67,6 +67,7 @@
padding: 0.5em 0 0.5em 2em;
color: #5E740B;
background-color: #eee;
overflow: hidden;
}
#qunit-userAgent {
@@ -76,6 +77,9 @@
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
}
#qunit-modulefilter-container {
float: right;
}
/** 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
* Dual licensed under the MIT (MIT-LICENSE.txt)
* or GPL (GPL-LICENSE.txt) licenses.
* Copyright 2012 jQuery Foundation and other contributors
* Released under the MIT license.
* http://jquery.org/license
*/
(function( window ) {
@@ -17,6 +17,8 @@ var QUnit,
fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""),
toString = Object.prototype.toString,
hasOwn = Object.prototype.hasOwnProperty,
// Keep a local reference to Date (GH-283)
Date = window.Date,
defined = {
setTimeout: typeof window.setTimeout !== "undefined",
sessionStorage: (function() {
@@ -304,7 +306,8 @@ QUnit = {
// call on start of module test to prepend name to all tests
module: function( name, testEnvironment ) {
config.currentModule = name;
config.currentModuleTestEnviroment = testEnvironment;
config.currentModuleTestEnvironment = testEnvironment;
config.modules[name] = true;
},
asyncTest: function( testName, expected, callback ) {
@@ -336,7 +339,7 @@ QUnit = {
async: async,
callback: callback,
module: config.currentModule,
moduleTestEnvironment: config.currentModuleTestEnviroment,
moduleTestEnvironment: config.currentModuleTestEnvironment,
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.
expect: function( asserts ) {
config.current.expected = asserts;
if (arguments.length === 1) {
config.current.expected = asserts;
} else {
return config.current.expected;
}
},
start: function( count ) {
@@ -415,6 +422,8 @@ QUnit.assert = {
var source,
details = {
module: config.current.module,
name: config.current.testName,
result: result,
message: msg
};
@@ -600,6 +609,9 @@ config = {
}
],
// Set of all modules.
modules: {},
// logging callback queues
begin: [],
done: [],
@@ -710,17 +722,10 @@ extend( QUnit, {
},
// Resets the test setup. Useful for tests that modify the DOM.
// If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
reset: function() {
var fixture;
if ( window.jQuery ) {
jQuery( "#qunit-fixture" ).html( config.fixture );
} else {
fixture = id( "qunit-fixture" );
if ( fixture ) {
fixture.innerHTML = config.fixture;
}
var fixture = id( "qunit-fixture" );
if ( fixture ) {
fixture.innerHTML = config.fixture;
}
},
@@ -781,6 +786,8 @@ extend( QUnit, {
var output, source,
details = {
module: config.current.module,
name: config.current.testName,
result: result,
message: message,
actual: actual,
@@ -826,6 +833,8 @@ extend( QUnit, {
var output,
details = {
module: config.current.module,
name: config.current.testName,
result: false,
message: message
};
@@ -916,7 +925,9 @@ QUnit.load = function() {
runLoggingCallbacks( "begin", QUnit, {} );
// 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 = "",
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>";
}
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 = id( "qunit-userAgent" );
if ( userAgent ) {
@@ -1002,6 +1022,19 @@ QUnit.load = function() {
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
@@ -1039,9 +1072,9 @@ window.onerror = function ( error, filePath, linerNr ) {
}
QUnit.pushFailure( error, filePath + ":" + linerNr );
} else {
QUnit.test( "global failure", function() {
QUnit.test( "global failure", extend( function() {
QUnit.pushFailure( error, filePath + ":" + linerNr );
});
}, { validTest: validTest } ) );
}
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, {
failed: config.stats.bad,
passed: passed,
@@ -1123,6 +1161,12 @@ function validTest( test ) {
module = config.module && config.module.toLowerCase(),
fullName = (test.module + ": " + test.testName).toLowerCase();
// Internally-generated tests are always valid
if ( test.callback && test.callback.validTest === validTest ) {
delete test.callback.validTest;
return true;
}
if ( config.testNumber ) {
return test.testNumber === config.testNumber;
}
@@ -1404,7 +1448,8 @@ QUnit.equiv = (function() {
a.global === b.global &&
// (gmi) ...
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)

View File

@@ -2,7 +2,7 @@ $(document).ready(function() {
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(_([1, 2, 3]).first(), 1, 'can perform OO-style "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');
});
test("arrays: rest", function() {
test("rest", function() {
var numbers = [1, 2, 3, 4];
equal(_.rest(numbers).join(", "), "2, 3, 4", 'working rest()');
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');
result = _.map([[1,2,3],[1,2,3]], _.rest);
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],2).join(", "), "1, 2", 'initial can take an index');
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');
});
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], 0).join(', '), "", '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');
});
test("arrays: compact", function() {
test("compact", function() {
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);
equal(result, 3, 'works on an arguments object');
});
test("arrays: flatten", function() {
test("flatten", function() {
if (window.JSON) {
var list = [1, [2], [3, [[[4]]]]];
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];
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);
@@ -74,7 +76,7 @@ $(document).ready(function() {
ok(_.without(list, list[0]).length == 1, 'ditto.');
});
test("arrays: uniq", function() {
test("uniq", function() {
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');
@@ -93,7 +95,7 @@ $(document).ready(function() {
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'];
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');
@@ -101,7 +103,7 @@ $(document).ready(function() {
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]);
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');
});
test("arrays: difference", function() {
test("difference", function() {
var result = _.difference([1, 2, 3], [2, 30, 40]);
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');
});
test('arrays: zip', function() {
test('zip', function() {
var names = ['moe', 'larry', 'curly'], ages = [30, 40, 50], leaders = [true];
var stooges = _.zip(names, ages, leaders);
equal(String(stooges), 'moe,30,true,larry,40,,curly,50,', 'zipped together arrays of different lengths');
});
test('arrays: zipObject', function() {
var result = _.zipObject(['moe', 'larry', 'curly'], [30, 40, 50]);
test('object', function() {
var result = _.object(['moe', 'larry', 'curly'], [30, 40, 50]);
var shouldBe = {moe: 30, larry: 40, curly: 50};
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];
numbers.indexOf = null;
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');
});
test("arrays: lastIndexOf", function() {
test("lastIndexOf", function() {
var numbers = [1, 0, 1, 0, 0, 1, 0, 0, 0];
numbers.lastIndexOf = null;
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');
});
test("arrays: range", function() {
test("range", function() {
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(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");
test("chaining: map/flatten/reduce", function() {
test("map/flatten/reduce", function() {
var lyrics = [
"I'm a lumberjack and I'm okay",
"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');
});
test("chaining: select/reject/sortBy", function() {
test("select/reject/sortBy", function() {
var numbers = [1,2,3,4,5,6,7,8,9,10];
numbers = _(numbers).chain().select(function(n) {
return n % 2 == 0;
@@ -32,7 +32,7 @@ $(document).ready(function() {
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];
numbers = _.chain(numbers).select(function(n) {
return n % 2 == 0;
@@ -44,7 +44,7 @@ $(document).ready(function() {
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];
numbers = _(numbers).chain()
.reverse()

View File

@@ -2,7 +2,7 @@ $(document).ready(function() {
module("Collections");
test("collections: each", function() {
test("each", function() {
_.each([1, 2, 3], function(num, i) {
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');
});
test('collections: map', function() {
test('map', function() {
var doubled = _.map([1, 2, 3], function(num){ return num * 2; });
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');
});
test('collections: reduce', function() {
test('reduce', function() {
var sum = _.reduce([1, 2, 3], function(sum, num){ return sum + num; }, 0);
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');
});
test('collections: reduceRight', function() {
test('reduceRight', function() {
var list = _.reduceRight(["foo", "bar", "baz"], function(memo, str){ return memo + str; }, '');
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');
});
test('collections: find', function() {
test('find', function() {
var array = [1, 2, 3, 4];
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');
});
test('collections: detect', function() {
test('detect', function() {
var result = _.detect([1, 2, 3], function(num){ return num * 2 == 4; });
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; });
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"');
});
test('collections: reject', function() {
test('reject', function() {
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');
});
test('collections: all', function() {
test('all', function() {
ok(_.all([], _.identity), 'the empty set');
ok(_.all([true, true, true], _.identity), 'all true values');
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([0], _.identity) === false, 'cast to boolean - false');
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;
Array.prototype.some = null;
ok(!_.any([]), 'the empty set');
@@ -159,21 +160,21 @@ $(document).ready(function() {
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,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(_([1,2,3]).include(2), 'OO-style include');
});
test('collections: invoke', function() {
test('invoke', function() {
var list = [[5, 1, 7], [3, 2, 1]];
var result = _.invoke(list, 'sort');
equal(result[0].join(', '), '1, 5, 7', 'first 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 result = _.invoke(list, Array.prototype.sort);
equal(result[0].join(', '), '1, 5, 7', 'first array sorted');
@@ -181,7 +182,7 @@ $(document).ready(function() {
});
// 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() {
return 42;
};
@@ -195,12 +196,12 @@ $(document).ready(function() {
equal(s.call, undefined, "call function removed");
});
test('collections: pluck', function() {
test('pluck', function() {
var people = [{name : 'moe', age : 30}, {name : 'curly', age : 50}];
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');
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");
});
test('collections: min', function() {
test('min', function() {
equal(1, _.min([1, 2, 3]), 'can perform a regular Math.min');
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");
});
test('collections: sortBy', function() {
test('sortBy', function() {
var people = [{name : 'curly', age : 50}, {name : 'moe', age : 30}];
people = _.sortBy(people, function(person){ return person.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');
});
test('collections: groupBy', function() {
test('groupBy', function() {
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');
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');
});
test('collections: countBy', function() {
test('countBy', function() {
var parity = _.countBy([1, 2, 3, 4, 5], function(num){ return num % 2 == 0; });
equal(parity['true'], 2);
equal(parity['false'], 3);
@@ -265,7 +266,7 @@ $(document).ready(function() {
equal(grouped['5'], 3);
});
test('collections: sortedIndex', function() {
test('sortedIndex', function() {
var numbers = [10, 20, 30, 40, 50], num = 35;
var indexForNum = _.sortedIndex(numbers, num);
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');
});
test('collections: shuffle', function() {
test('shuffle', function() {
var numbers = _.range(10);
var shuffled = _.shuffle(numbers).sort();
notStrictEqual(numbers, shuffled, 'original object is unmodified');
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(_.toArray(arguments)), 'arguments object converted into array');
var a = [1,2,3];
@@ -300,9 +301,17 @@ $(document).ready(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([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");
test("functions: bind", function() {
test("bind", function() {
var context = {name : 'moe'};
var func = function(arg) { return "name: " + (this.name || arg); };
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");
});
test("functions: bindAll", function() {
test("bindAll", function() {
var curly = {name : 'curly'}, moe = {
name : 'moe',
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');
});
test("functions: memoize", function() {
test("memoize", function() {
var fib = function(n) {
return n < 2 ? n : fib(n - 1) + fib(n - 2);
};
@@ -77,20 +77,20 @@ $(document).ready(function() {
equal(fastO('toString'), 'toString', 'checks hasOwnProperty');
});
asyncTest("functions: delay", 2, function() {
asyncTest("delay", 2, function() {
var delayed = false;
_.delay(function(){ delayed = true; }, 100);
setTimeout(function(){ ok(!delayed, "didn't delay the function quite yet"); }, 50);
setTimeout(function(){ ok(delayed, 'delayed the function'); start(); }, 150);
});
asyncTest("functions: defer", 1, function() {
asyncTest("defer", 1, function() {
var deferred = false;
_.defer(function(bool){ deferred = bool; }, true);
_.delay(function(){ ok(deferred, "deferred the function"); start(); }, 50);
});
asyncTest("functions: throttle", 2, function() {
asyncTest("throttle", 2, function() {
var counter = 0;
var incr = function(){ counter++; };
var throttledIncr = _.throttle(incr, 100);
@@ -105,7 +105,7 @@ $(document).ready(function() {
_.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 update = function(val){ value = val; };
var throttledUpdate = _.throttle(update, 100);
@@ -117,7 +117,7 @@ $(document).ready(function() {
_.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 incr = function(){ return ++counter; };
var throttledIncr = _.throttle(incr, 100);
@@ -128,7 +128,7 @@ $(document).ready(function() {
}, 220);
});
asyncTest("functions: throttle twice", 1, function() {
asyncTest("throttle twice", 1, function() {
var counter = 0;
var incr = function(){ counter++; };
var throttledIncr = _.throttle(incr, 100);
@@ -136,7 +136,34 @@ $(document).ready(function() {
_.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 incr = function(){ counter++; };
var debouncedIncr = _.debounce(incr, 50);
@@ -149,7 +176,7 @@ $(document).ready(function() {
_.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 incr = function(){ counter++; };
var debouncedIncr = _.debounce(incr, 50, true);
@@ -163,7 +190,7 @@ $(document).ready(function() {
_.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 debouncedIncr = _.debounce(function(){
counter++;
@@ -174,7 +201,7 @@ $(document).ready(function() {
_.delay(function(){ equal(counter, 1, "incr was debounced"); start(); }, 70);
});
test("functions: once", function() {
test("once", function() {
var num = 0;
var increment = _.once(function(){ num++; });
increment();
@@ -182,7 +209,7 @@ $(document).ready(function() {
equal(num, 1);
});
test("functions: wrap", function() {
test("wrap", function() {
var greet = function(name){ return "hi: " + name; };
var backwards = _.wrap(greet, function(func, name){ return func(name) + ' ' + name.split('').reverse().join(''); });
equal(backwards('moe'), 'hi: moe eom', 'wrapped the saluation function');
@@ -198,7 +225,7 @@ $(document).ready(function() {
deepEqual(ret, [noop, ['whats', 'your'], 'vector', 'victor']);
});
test("functions: compose", function() {
test("compose", function() {
var greet = function(name){ return "hi: " + name; };
var exclaim = function(sentence){ return sentence + '!'; };
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');
});
test("functions: after", function() {
test("after", function() {
var testAfter = function(afterAmount, timesCalled) {
var afterCalled = 0;
var after = _.after(afterAmount, function() {

View File

@@ -2,7 +2,7 @@ $(document).ready(function() {
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');
// the test above is not safe because it relies on for-in enumeration order
var a = []; a[1] = 0;
@@ -14,11 +14,21 @@ $(document).ready(function() {
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');
});
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};
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');
});
test("objects: extend", function() {
test("extend", function() {
var result;
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');
@@ -40,7 +50,7 @@ $(document).ready(function() {
equal(_.keys(result).join(''), 'ab', 'extend does not copy undefined values');
});
test("objects: pick", function() {
test("pick", function() {
var result;
result = _.pick({a:1, b:2, c:3}, 'a', 'c');
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');
});
test("objects: omit", function() {
test("omit", function() {
var result;
result = _.omit({a:1, b:2, c:3}, 'b');
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');
});
test("objects: defaults", function() {
test("defaults", function() {
var result;
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');
});
test("objects: clone", function() {
test("clone", function() {
var moe = {name : 'moe', lucky : [13, 27, 34]};
var clone = _.clone(moe);
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');
});
test("objects: isEqual", function() {
test("isEqual", function() {
function First() {
this.value = 1;
}
@@ -283,6 +293,12 @@ $(document).ready(function() {
b.push("Curly");
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.
a = {abc: null};
b = {abc: null};
@@ -296,6 +312,12 @@ $(document).ready(function() {
b.def = new Number(63);
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.
a = [{abc: null}];
b = [{abc: null}];
@@ -382,7 +404,7 @@ $(document).ready(function() {
ok(_.isEqual(date, date_json), 'date matches serialized date');
});
test("objects: isEmpty", function() {
test("isEmpty", function() {
ok(!_([1]).isEmpty(), '[1] is not empty');
ok(_.isEmpty([]), '[] is empty');
ok(!_.isEmpty({one : 1}), '{one : 1} is not empty');
@@ -420,13 +442,13 @@ $(document).ready(function() {
);
iDoc.close();
test("objects: isElement", function() {
test("isElement", function() {
ok(!_.isElement('div'), 'strings are not dom elements');
ok(_.isElement($('html')[0]), 'the html tag is a DOM element');
ok(_.isElement(iElement), 'even from another frame');
});
test("objects: isArguments", function() {
test("isArguments", function() {
var args = (function(){ return arguments; })(1, 2, 3);
ok(!_.isArguments('string'), 'a string 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');
});
test("objects: isObject", function() {
test("isObject", function() {
ok(_.isObject(arguments), 'the arguments object is object');
ok(_.isObject([1, 2, 3]), 'and arrays');
ok(_.isObject($('html')[0]), 'and DOM element');
@@ -451,19 +473,19 @@ $(document).ready(function() {
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([1, 2, 3]), 'but arrays are');
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([1, 2, 3].join(', ')), 'but strings are');
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(arguments), 'the arguments object 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');
});
test("objects: isBoolean", function() {
test("isBoolean", function() {
ok(!_.isBoolean(2), 'a number is not a boolean');
ok(!_.isBoolean("string"), 'a string 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');
});
test("objects: isFunction", function() {
test("isFunction", function() {
ok(!_.isFunction([1, 2, 3]), 'arrays are not functions');
ok(!_.isFunction('moe'), 'strings are not functions');
ok(_.isFunction(_.isFunction), 'but functions are');
ok(_.isFunction(iFunction), 'even from another frame');
});
test("objects: isDate", function() {
test("isDate", function() {
ok(!_.isDate(100), 'numbers are not dates');
ok(!_.isDate({}), 'objects are not dates');
ok(_.isDate(new Date()), 'but dates are');
ok(_.isDate(iDate), 'even from another frame');
});
test("objects: isRegExp", function() {
test("isRegExp", function() {
ok(!_.isRegExp(_.identity), 'functions are not RegExps');
ok(_.isRegExp(/identity/), 'but RegExps are');
ok(_.isRegExp(iRegExp), 'even from another frame');
});
test("objects: isFinite", function() {
test("isFinite", function() {
ok(!_.isFinite(undefined), 'undefined is not Finite');
ok(!_.isFinite(null), 'null is not Finite');
ok(!_.isFinite(NaN), 'NaN is not Finite');
@@ -522,7 +544,7 @@ $(document).ready(function() {
ok(_.isFinite(-12.44), 'Floats are Finite');
});
test("objects: isNaN", function() {
test("isNaN", function() {
ok(!_.isNaN(undefined), 'undefined is not NaN');
ok(!_.isNaN(null), 'null is not NaN');
ok(!_.isNaN(0), '0 is not NaN');
@@ -530,14 +552,14 @@ $(document).ready(function() {
ok(_.isNaN(iNaN), 'even from another frame');
});
test("objects: isNull", function() {
test("isNull", function() {
ok(!_.isNull(undefined), 'undefined is not null');
ok(!_.isNull(NaN), 'NaN is not null');
ok(_.isNull(null), 'but null is');
ok(_.isNull(iNull), 'even from another frame');
});
test("objects: isUndefined", function() {
test("isUndefined", function() {
ok(!_.isUndefined(1), 'numbers are defined');
ok(!_.isUndefined(null), 'null is defined');
ok(!_.isUndefined(false), 'false is defined');
@@ -548,7 +570,7 @@ $(document).ready(function() {
});
if (window.ActiveXObject) {
test("objects: IE host objects", function() {
test("IE host objects", function() {
var xml = new ActiveXObject("Msxml2.DOMDocument.3.0");
ok(!_.isNumber(xml));
ok(!_.isBoolean(xml));
@@ -559,7 +581,7 @@ $(document).ready(function() {
});
}
test("objects: tap", function() {
test("tap", function() {
var intercepted = null;
var interceptor = function(obj) { intercepted = obj; };
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'};
equal(_.identity(moe), moe, 'moe is the same as his identity');
});
test("utility: uniqueId", function() {
test("uniqueId", function() {
var ids = [], i = 0;
while(i++ < 100) ids.push(_.uniqueId());
equal(_.uniq(ids).length, ids.length, 'can generate a globally-unique stream of ids');
});
test("utility: times", function() {
test("times", function() {
var vals = [];
_.times(3, function (i) { vals.push(i); });
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");
});
test("utility: mixin", function() {
test("mixin", function() {
_.mixin({
myReverse: function(string) {
return string.split('').reverse().join('');
@@ -45,12 +51,21 @@ $(document).ready(function() {
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 &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 result = basicTemplate({thing : 'This'});
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");
});
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() {
var tmpl = _.template('<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++)); }});
});
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
* Dual licensed under the MIT (MIT-LICENSE.txt)
* or GPL (GPL-LICENSE.txt) licenses.
* Copyright 2012 jQuery Foundation and other contributors
* Released under the MIT license.
* http://jquery.org/license
*/
/** Font Family and Sizes */
@@ -20,7 +20,7 @@
/** 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;
padding: 0;
}
@@ -38,10 +38,10 @@
line-height: 1em;
font-weight: normal;
border-radius: 15px 15px 0 0;
-moz-border-radius: 15px 15px 0 0;
-webkit-border-top-right-radius: 15px;
-webkit-border-top-left-radius: 15px;
border-radius: 5px 5px 0 0;
-moz-border-radius: 5px 5px 0 0;
-webkit-border-top-right-radius: 5px;
-webkit-border-top-left-radius: 5px;
}
#qunit-header a {
@@ -54,9 +54,9 @@
color: #fff;
}
#qunit-header label {
#qunit-testrunner-toolbar label {
display: inline-block;
padding-left: 0.5em;
padding: 0 .5em 0 .1em;
}
#qunit-banner {
@@ -67,6 +67,7 @@
padding: 0.5em 0 0.5em 2em;
color: #5E740B;
background-color: #eee;
overflow: hidden;
}
#qunit-userAgent {
@@ -76,6 +77,9 @@
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
}
#qunit-modulefilter-container {
float: right;
}
/** Tests: Pass/Fail */
@@ -113,13 +117,9 @@
background-color: #fff;
border-radius: 15px;
-moz-border-radius: 15px;
-webkit-border-radius: 15px;
box-shadow: inset 0px 2px 13px #999;
-moz-box-shadow: inset 0px 2px 13px #999;
-webkit-box-shadow: inset 0px 2px 13px #999;
border-radius: 5px;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
}
#qunit-tests table {
@@ -162,8 +162,7 @@
#qunit-tests b.failed { color: #710909; }
#qunit-tests li li {
margin: 0.5em;
padding: 0.4em 0.5em 0.4em 0.5em;
padding: 5px;
background-color: #fff;
border-bottom: none;
list-style-position: inside;
@@ -172,9 +171,9 @@
/*** Passing Styles */
#qunit-tests li li.pass {
color: #5E740B;
color: #3c510c;
background-color: #fff;
border-left: 26px solid #C6E746;
border-left: 10px solid #C6E746;
}
#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
@@ -190,15 +189,15 @@
#qunit-tests li li.fail {
color: #710909;
background-color: #fff;
border-left: 26px solid #EE5757;
border-left: 10px solid #EE5757;
white-space: pre;
}
#qunit-tests > li:last-child {
border-radius: 0 0 15px 15px;
-moz-border-radius: 0 0 15px 15px;
-webkit-border-bottom-right-radius: 15px;
-webkit-border-bottom-left-radius: 15px;
border-radius: 0 0 5px 5px;
-moz-border-radius: 0 0 5px 5px;
-webkit-border-bottom-right-radius: 5px;
-webkit-border-bottom-left-radius: 5px;
}
#qunit-tests .fail { color: #000000; background-color: #EE5757; }

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
* Dual licensed under the MIT (MIT-LICENSE.txt)
* or GPL (GPL-LICENSE.txt) licenses.
* Copyright 2012 jQuery Foundation and other contributors
* Released under the MIT license.
* http://jquery.org/license
*/
(function( window ) {
@@ -17,6 +17,8 @@ var QUnit,
fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""),
toString = Object.prototype.toString,
hasOwn = Object.prototype.hasOwnProperty,
// Keep a local reference to Date (GH-283)
Date = window.Date,
defined = {
setTimeout: typeof window.setTimeout !== "undefined",
sessionStorage: (function() {
@@ -304,7 +306,8 @@ QUnit = {
// call on start of module test to prepend name to all tests
module: function( name, testEnvironment ) {
config.currentModule = name;
config.currentModuleTestEnviroment = testEnvironment;
config.currentModuleTestEnvironment = testEnvironment;
config.modules[name] = true;
},
asyncTest: function( testName, expected, callback ) {
@@ -336,7 +339,7 @@ QUnit = {
async: async,
callback: callback,
module: config.currentModule,
moduleTestEnvironment: config.currentModuleTestEnviroment,
moduleTestEnvironment: config.currentModuleTestEnvironment,
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.
expect: function( asserts ) {
config.current.expected = asserts;
if (arguments.length === 1) {
config.current.expected = asserts;
} else {
return config.current.expected;
}
},
start: function( count ) {
@@ -403,6 +410,8 @@ QUnit = {
QUnit.assert = {
/**
* Asserts rough true-ish result.
* @name ok
* @function
* @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
*/
ok: function( result, msg ) {
@@ -413,6 +422,8 @@ QUnit.assert = {
var source,
details = {
module: config.current.module,
name: config.current.testName,
result: result,
message: msg
};
@@ -437,36 +448,59 @@ QUnit.assert = {
/**
* Assert that the first two arguments are equal, with an optional message.
* Prints out both actual and expected values.
* @name equal
* @function
* @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" );
*/
equal: function( actual, expected, message ) {
QUnit.push( expected == actual, actual, expected, message );
},
/**
* @name notEqual
* @function
*/
notEqual: function( actual, expected, message ) {
QUnit.push( expected != actual, actual, expected, message );
},
/**
* @name deepEqual
* @function
*/
deepEqual: function( actual, expected, message ) {
QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
},
/**
* @name notDeepEqual
* @function
*/
notDeepEqual: function( actual, expected, message ) {
QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message );
},
/**
* @name strictEqual
* @function
*/
strictEqual: function( actual, expected, message ) {
QUnit.push( expected === actual, actual, expected, message );
},
/**
* @name notStrictEqual
* @function
*/
notStrictEqual: function( actual, expected, message ) {
QUnit.push( expected !== actual, actual, expected, message );
},
raises: function( block, expected, message ) {
throws: function( block, expected, message ) {
var actual,
ok = false;
// 'expected' is optional
if ( typeof expected === "string" ) {
message = expected;
expected = null;
@@ -494,18 +528,29 @@ QUnit.assert = {
} else if ( expected.call( {}, actual ) === 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 );
/**
* @deprecated: Kept for backwards compatibility
* next step: remove entirely
* @deprecated since 1.9.0
* 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.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()
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
begin: [],
@@ -661,17 +722,10 @@ extend( QUnit, {
},
// Resets the test setup. Useful for tests that modify the DOM.
// If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
reset: function() {
var fixture;
if ( window.jQuery ) {
jQuery( "#qunit-fixture" ).html( config.fixture );
} else {
fixture = id( "qunit-fixture" );
if ( fixture ) {
fixture.innerHTML = config.fixture;
}
var fixture = id( "qunit-fixture" );
if ( fixture ) {
fixture.innerHTML = config.fixture;
}
},
@@ -732,6 +786,8 @@ extend( QUnit, {
var output, source,
details = {
module: config.current.module,
name: config.current.testName,
result: result,
message: message,
actual: actual,
@@ -770,26 +826,36 @@ extend( QUnit, {
});
},
pushFailure: function( message, source ) {
pushFailure: function( message, source, actual ) {
if ( !config.current ) {
throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) );
}
var output,
details = {
module: config.current.module,
name: config.current.testName,
result: false,
message: message
};
message = escapeInnerText(message ) || "error";
message = escapeInnerText( message ) || "error";
message = "<span class='test-message'>" + message + "</span>";
output = message;
output += "<table>";
if ( actual ) {
output += "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeInnerText( actual ) + "</pre></td></tr>";
}
if ( 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 );
config.current.assertions.push({
@@ -859,7 +925,9 @@ QUnit.load = function() {
runLoggingCallbacks( "begin", QUnit, {} );
// 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 = "",
oldconfig = extend( {}, config );
@@ -872,10 +940,26 @@ QUnit.load = function() {
for ( i = 0; i < len; i++ ) {
val = config.urlConfig[i];
config[val] = QUnit.urlParams[val];
urlConfigHtml += "<label><input name='" + val + "' type='checkbox'" + ( config[val] ? " checked='checked'" : "" ) + ">" + val + "</label>";
if ( typeof val === "string" ) {
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 = id( "qunit-userAgent" );
if ( userAgent ) {
@@ -885,12 +969,7 @@ QUnit.load = function() {
// `banner` initialized at top of scope
banner = id( "qunit-header" );
if ( banner ) {
banner.innerHTML = "<a href='" + QUnit.url({ filter: undefined }) + "'>" + banner.innerHTML + "</a> " + urlConfigHtml;
addEvent( banner, "change", function( event ) {
var params = {};
params[ event.target.name ] = event.target.checked ? true : undefined;
window.location = QUnit.url( params );
});
banner.innerHTML = "<a href='" + QUnit.url({ filter: undefined, module: undefined, testNumber: undefined }) + "'>" + banner.innerHTML + "</a> ";
}
// `toolbar` initialized at top of scope
@@ -931,8 +1010,31 @@ QUnit.load = function() {
// `label` initialized at top of scope
label = document.createElement( "label" );
label.setAttribute( "for", "qunit-filter-pass" );
label.setAttribute( "title", "Only show tests and assertons that fail. Stored in sessionStorage." );
label.innerHTML = "Hide passed tests";
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
@@ -970,9 +1072,9 @@ window.onerror = function ( error, filePath, linerNr ) {
}
QUnit.pushFailure( error, filePath + ":" + linerNr );
} else {
QUnit.test( "global failure", function() {
QUnit.test( "global failure", extend( function() {
QUnit.pushFailure( error, filePath + ":" + linerNr );
});
}, { validTest: validTest } ) );
}
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, {
failed: config.stats.bad,
passed: passed,
@@ -1051,14 +1158,20 @@ function done() {
function validTest( test ) {
var include,
filter = config.filter && config.filter.toLowerCase(),
module = config.module,
module = config.module && config.module.toLowerCase(),
fullName = (test.module + ": " + test.testName).toLowerCase();
// Internally-generated tests are always valid
if ( test.callback && test.callback.validTest === validTest ) {
delete test.callback.validTest;
return true;
}
if ( config.testNumber ) {
return test.testNumber === config.testNumber;
}
if ( module && test.module !== module ) {
if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) {
return false;
}
@@ -1335,7 +1448,8 @@ QUnit.equiv = (function() {
a.global === b.global &&
// (gmi) ...
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)

View File

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