Compare commits

...

193 Commits
0.2.0 ... 0.4.0

Author SHA1 Message Date
John-David Dalton
971a26c123 Remove noArraySliceOnStrings from the mobile build.
Former-commit-id: 943f907672f211be4f3d1c2e8fe8d5769a0a569e
2012-07-11 12:48:37 -04:00
John-David Dalton
0976a3b721 Bump version to 0.4.0.
Former-commit-id: e3606a629e44711ad9174ac7c6690f2a53f088b9
2012-07-11 11:59:04 -04:00
John-David Dalton
d496361555 Fix failing unit test in Opera < 10.52 and add _.toArray benchmarks.
Former-commit-id: 0ed6d5c52b2486be4ccc212da7bbc7cb6d67cf7f
2012-07-11 11:29:44 -04:00
John-David Dalton
60ed65a73a Add utf8 encoding to the generated minified file in minify.js.
Former-commit-id: 2765e28a177ae2703d3b1a6328a35cf5c92fa6ef
2012-07-11 05:54:19 -04:00
John-David Dalton
d1407b3bd0 Add unneeded vendor folders to .npmignore.
Former-commit-id: 7be56b8081be64d41cc66914dce577695d9d0747
2012-07-11 05:40:25 -04:00
John-David Dalton
5ca2da76df Make build.js work as a npm executable.
Former-commit-id: fff327957854aa85a5abfe80994b59f3d0d24370
2012-07-11 05:39:30 -04:00
John-David Dalton
2ac887ff74 Make compiled templates more debuggable.
Former-commit-id: aba1aa3efcea6695632aae0eb61148aa3174debd
2012-07-11 01:52:50 -04:00
John-David Dalton
cbdc9c0be1 Make _.contains work with strings similar ES6 draft String#contains.
Former-commit-id: 3cfffdcddec3e1e8175da95043ec86ac3c6a85fe
2012-07-11 01:11:25 -04:00
John-David Dalton
b9bade8d5a Remove unused UglifyJS file.
Former-commit-id: cfefdb6796583cc55a16ce02533541e6395e4356
2012-07-10 23:22:01 -04:00
John-David Dalton
eb43786641 Cleanup unit tests.
Former-commit-id: 460ffc3bd38c98dd86fc8c74d4720eed5d50433d
2012-07-10 22:04:40 -04:00
John-David Dalton
0515db3d7c Add more false values unit tests for _.size and "Arrays"/"Collections" methods.
Former-commit-id: 475941321404ceec4eabb56fb9ee10855a653e12
2012-07-10 21:41:38 -04:00
John-David Dalton
db257778c0 Update vendor folder.
Former-commit-id: dde7c53eee254e2f50608e65540893764ab8d9cb
2012-07-10 18:14:15 -04:00
John-David Dalton
3b37d7489f Add legacy build info to README.md and rebuild minified file and documentation.
Former-commit-id: 83560976f173a3c797128b08fb184cc7c1e77319
2012-07-10 01:27:07 -04:00
John-David Dalton
329c7e8e05 Add custom-debug build to unit test runners and add unit tests for passing strings to "Collections" methods.
Former-commit-id: cb6c66d01a9f30908a27e689f8ffba5fa9f04299
2012-07-10 01:06:06 -04:00
John-David Dalton
fad9b4fa72 Add support for strings in "Collections" methods.
Former-commit-id: 1abb252101d20c9a01291f4cef19db8a7eeda743
2012-07-10 01:05:22 -04:00
John-David Dalton
c1a81279ed Rename test/ui.js to test/test-ui.js and add to .npmignore. [closes #40]
Former-commit-id: 80165ec342e8b833a28bbe66f59e074c8f4f5185
2012-07-09 07:54:35 -04:00
John-David Dalton
8bb35a17d2 Allow different builds to be tested more easily with a dropdown menu.
Former-commit-id: a692bda9708523aa0443acb35dd8fcc5a342ef3f
2012-07-09 03:56:43 -04:00
John-David Dalton
36415054ea Update minified build and documentation.
Former-commit-id: fc680301b9ae378139e5174e48eb8e4708fddba0
2012-07-08 18:10:10 -04:00
John-David Dalton
3e84cbae69 Add removeKeysOptimization function to build.js.
Former-commit-id: ce13e7b7a1f12199cdddcccbf0b7e0d98d6438ec
2012-07-08 18:08:41 -04:00
John-David Dalton
f5f2bf7f46 Cleanup iteratorTemplate and optimize methods that use mapIteratorOptions.
Former-commit-id: a0f876250a1c7875745e9080bf75546e16fbf6e7
2012-07-08 10:05:04 -04:00
John-David Dalton
acb6656958 Update minified build and documentation.
Former-commit-id: 19462dc946eca093f4265620b8e063b8c162c27d
2012-07-08 03:40:51 -04:00
John-David Dalton
9d70d7c27e Fix _.sortBy in the mobile build attempt two.
Former-commit-id: 8f54b16a901acc4ce91f071c5b36c632334f7dde
2012-07-08 03:39:38 -04:00
John-David Dalton
648a6afb25 Add object iteration benchmarks for _.filter, _.find, _.groupBy, and _.map to perf.js.
Former-commit-id: dc30e2c0e4f88b2a0164f02bf3bf92fd40a87012
2012-07-08 03:31:40 -04:00
John-David Dalton
57ae1925b1 Fix _.sortBy in the mobile build.
Former-commit-id: 0af3778e89e61effbe60347d6f0bcb33d8c37a2e
2012-07-08 03:30:16 -04:00
John-David Dalton
d49318582f Adjust how "mobile" build is created and add first pass at "legacy" build.
Former-commit-id: 740cc40c21d33353f34796ae6da3bc8ce015ab6c
2012-07-08 03:28:18 -04:00
John-David Dalton
83e3f830e6 Rename useNativeBind and useNativeKeys to isBindFast and isKeysFast.
Former-commit-id: 9f001b030dc49146b177678443406720c436ac0b
2012-07-08 02:47:01 -04:00
John-David Dalton
04d4353c0f Optimize object iteration using Object.keys where faster than for-in loops.
Former-commit-id: 8826f75cf6eaf4233758684e3aae2ccdc3c9f262
2012-07-08 02:04:57 -04:00
John-David Dalton
6d217fc097 Add propertyIsEnumerable check to _.keys.
Former-commit-id: 1dcd532d29b3e99ce54a18f4489c766652d56d83
2012-07-06 14:14:44 -04:00
John-David Dalton
9d7136c63c Add more checks to the reComplexDelimiter regexp.
Former-commit-id: 87fba2813619882d388261a1226e75503ec9b8d0
2012-07-05 15:04:09 -04:00
John-David Dalton
51a679d60a Add _.template unit test to ensure complex "interpolate" delimiters work.
Former-commit-id: bdfdbf00b1a1838e104919214012a9cfb39dda19
2012-07-05 13:19:45 -04:00
John-David Dalton
7d4d28614a Remove more unnecessary code from custom builds.
Former-commit-id: 7df2ebc805072456b9f0565a0a33fc1bcf2a4049
2012-07-05 11:51:40 -04:00
John-David Dalton
c75cfaf692 Update minified build and documentation.
Former-commit-id: 401c87b9755301440d2c86f7bfe618cc68db7e1e
2012-07-05 11:29:28 -04:00
John-David Dalton
fe6aa8a6fc Further optimize _.template by controlling how the with-statement is inserted.
Former-commit-id: a62331e771302f9390d5aca04f878b839fe11b69
2012-07-05 11:28:41 -04:00
John-David Dalton
a5fe1eb5fb Optimize how with-statements are inserted into compiled templates.
Former-commit-id: aabb0b1f8c6e910532464b7a007767801c00a640
2012-07-04 20:07:25 -04:00
John-David Dalton
5afd37c92c Update package.json and .npmignore to allow build.js.
Former-commit-id: 782a448163baa27275ca87da209db53441bdcb08
2012-07-04 20:06:35 -04:00
John-David Dalton
293d0409c6 Remove Cloning this repo from the README.md.
Former-commit-id: 4f95f8b9a6781b5b6207a95082415f97e4d4236b
2012-07-03 12:06:14 -04:00
John-David Dalton
4352f18dd3 Cleanup perf.js.
Former-commit-id: 53285a24dcb27857a05b07aa7a6a8840026f8c42
2012-07-02 17:59:09 -04:00
John-David Dalton
0ae7fe9df5 Add non-submodule vendor directory.
Former-commit-id: 392cdade55487e774e959013d8d25ae0b1dfe93b
2012-07-02 15:35:36 -04:00
John-David Dalton
5048f0422d Delete vendor directory.
Former-commit-id: 3d866435290966ec2f0d00561dcd99d0dfad29e0
2012-07-02 15:31:27 -04:00
John-David Dalton
c46a36f8ed Remove submodules and cleanup repo.
Former-commit-id: f3950601f2001ceb4f36db2a7f2efa28acc8472d
2012-07-02 15:22:48 -04:00
John-David Dalton
90e2bd0372 Correct variable declaration order.
Former-commit-id: 5f5a36057b065799dcc05b0054a2c88f00fad8c0
2012-07-02 03:43:01 -04:00
John-David Dalton
10fbc8a04b Update minified build and documentation.
Former-commit-id: 03b6f5fdf71ab1dab48f3fb06d60b6ec8a8e494c
2012-07-02 03:01:03 -04:00
John-David Dalton
fb818f3775 Cleanup lodash.js.
Former-commit-id: a1b7dc187dbd562b62d3446ce731674e0bb539d9
2012-07-02 03:00:01 -04:00
John-David Dalton
434e23c209 Add _.template benchmarks.
Former-commit-id: 97c5a90825d6a9a0b876d8c9f90e621f00389766
2012-07-02 02:59:33 -04:00
John-David Dalton
fe53bd6475 Fix build for _.bind.
Former-commit-id: 59ef841bca271bed05555c4b40e32b85ee1a4d37
2012-07-02 00:26:16 -04:00
John-David Dalton
268ce91c65 Add _.bind and _.size benchmarks.
Former-commit-id: bb3517e9517db81de56e3794abc256cd2dac59b5
2012-07-02 00:07:55 -04:00
John-David Dalton
3b4074bfc7 Optimize _.template when no evaluate delimiters are used and optimize _.bind when partially applied in V8.
Former-commit-id: 25489d41ba3cac7ac3f1414e09f1971a11a779be
2012-07-02 00:07:22 -04:00
John-David Dalton
6af4652161 Optimize inlining the iteratorTemplate for builds.
Former-commit-id: 8b7ac4622e51dbe5c0364f3c6781aa474dcfa96e
2012-06-30 03:06:21 -04:00
John-David Dalton
3717d30188 Update Backbone and Underscore submodules.
Former-commit-id: 0cdf988c89269ff95eb0d7fbd0bc51cbe95d3524
2012-06-29 20:41:51 -04:00
John-David Dalton
4cf2e83418 Add _.zipObject.
Former-commit-id: 0fe17adc359fbc608025dced32f6dd509d019413
2012-06-29 20:41:12 -04:00
John-David Dalton
1228639103 Cleanup build.js.
Former-commit-id: 01f5488ddba9f51344561c8df493d4f1bc6b0ef9
2012-06-29 19:33:36 -04:00
John-David Dalton
e12d67de94 Update minified build and documentation.
Former-commit-id: f4c5987a37d5c22f9705495e3a49a497f009d01c
2012-06-29 19:09:13 -04:00
John-David Dalton
10bcb37ca5 Inline more functionality into _.sortBy.
Former-commit-id: 6549e86881b7a93d96854a9ac1b04f0f5a5db0f1
2012-06-29 19:07:05 -04:00
John-David Dalton
e973598bff DRY out isType methods and simplify templates during the build process.
Former-commit-id: cd982282a5de63fddc36d1dc4759c8982e1c5ad4
2012-06-29 19:05:30 -04:00
John-David Dalton
3a5129694d Update documentation and minified build.
Former-commit-id: 193568c6f99736971ef93f9c4d63bc72195b8b2b
2012-06-29 04:03:02 -04:00
John-David Dalton
a1e0fbea8b Add issue reference to README.md.
Former-commit-id: 09ed4e386b55f657d8710f40c14fb3b283d66b7c
2012-06-29 04:01:58 -04:00
John-David Dalton
0bf374454e Add scripts entry to package.json.
Former-commit-id: 9ab2b915ea8aa6c3b8c28b5d14eaa32613db7ed0
2012-06-29 04:00:35 -04:00
John-David Dalton
5e592fbf29 Merge branch 'master' of github.com:bestiejs/lodash
Former-commit-id: a4de2b320b871d2bfd689890fc526605215091bf
2012-06-29 03:58:54 -04:00
John-David Dalton
ad4101bc99 Optimize _.sortBy and remove the _.map dependency from _.sortBy.
Former-commit-id: f6a133f0d27e7a00cb54e2e8478066dcfbe05659
2012-06-29 03:11:31 -04:00
John-David Dalton
2d057d92cf Merge pull request #38 from davidmurdoch/00d1cc9bf84355f35268d24201e02feaec6a00b1 [formerly d387147685f95cf88a437e678c873181cbcb907f]
Use `null` when comparing `null`/`undefined`.

Former-commit-id: 16c513648279e37a72eb433bd9b5fbe840854376
2012-06-28 22:21:18 -07:00
David Murdoch
00d1cc9bf8 Use null when comparing null/undefined
for non-strict equals. Saves 2 gzipped bytes.


Former-commit-id: d387147685f95cf88a437e678c873181cbcb907f
2012-06-28 09:56:10 -04:00
David Murdoch
2c1ec5fe75 Save 2 gzipped bytes by flipping args in math.max
Also, remove some stray whitespace.


Former-commit-id: 98b4711bc7d978ef66a693593d6be108f6bcfd8d
2012-06-28 09:45:14 -04:00
John-David Dalton
81b28d005d Remove arguments object from _.range.
Former-commit-id: d3da531e33bcd00a00ff80986f56196ac1b6f2c5
2012-06-27 13:51:46 -04:00
John-David Dalton
6a90616b99 Cleanup README.md.
Former-commit-id: 1a07361430f1d20382beb277db17e94967fd791d
2012-06-26 04:08:11 -04:00
John-David Dalton
d5be43695a Merge pull request #35 from tomByrer/master
Add CDN and jsPerf links to README.md.

Former-commit-id: b2ba93fb493536060c9abb13f406db9fd84d0ea4
2012-06-26 00:31:43 -07:00
John-David Dalton
0a93b81ae7 Update minified build and documentation.
Former-commit-id: 3b2d6cbd0c91933043c64b6f15a4c990a5c400c5
2012-06-26 03:28:08 -04:00
John-David Dalton
65f8da1654 Make _.size work consistently cross-browser with arguments objects and avoid erroring when falsey values are passed.
Former-commit-id: 76dc852a4e1fd84218f9c57f44c93e483a3680d9
2012-06-26 03:23:53 -04:00
John-David Dalton
911014db95 Avoid compiling unnecessary _.template code.
Former-commit-id: 74006fd2df2b5d2ba8a222baf21574f729f34bcb
2012-06-26 02:32:43 -04:00
John-David Dalton
313ee13f18 Move _.groupBy and _.sortBy back to the "Collections" category. [closes #34]
Former-commit-id: ce0f7f906758ce13cc2ea927520ac401e6bba9f6
2012-06-26 02:18:44 -04:00
tomByrer
addad04c44 Update master
Former-commit-id: c61735bdbf4dbad6dfd15a03660901fca9043327
2012-06-19 20:34:28 -06:00
tomByrer
e3789e5b64 merged tests with Dive in section
Former-commit-id: 1b32a93390d9b806bd66a298d558e68588ba7574
2012-06-19 20:32:49 -06:00
tomByrer
410315ce35 + CDN usage link
+ testing links

Former-commit-id: 79740ec7b2ac8c088545ca585ab5d3d94c804e2a
2012-06-19 20:23:58 -06:00
tomByrer
90998da308 + cdn js' hosting
Former-commit-id: 4276796f0fa31602060860da9c45c43dce34e8c4
2012-06-19 19:53:50 -06:00
John-David Dalton
6fa5b13e10 Add comment explaining a V8 optimization.
Former-commit-id: 733f0985509467291287e1e0a95fcd22114cdbcf
2012-06-19 11:42:27 +02:00
John-David Dalton
93067c2d52 Update documentation and minified build.
Former-commit-id: 156cdcff42eb4fef4a982e5b3016030246ba9f63
2012-06-19 10:56:05 +02:00
John-David Dalton
b2079b7007 Leverage _.indexOf's fromIndex in _.difference and _.without.
Former-commit-id: 0fd092d2ee109b99a50791899e7f2d690b3852af
2012-06-19 10:54:23 +02:00
John-David Dalton
b643dd074c Clarify benchmark descriptions.
Former-commit-id: 79c71d7d2c034ebe31f20f5d10251a8ba175cfd8
2012-06-16 20:35:55 -04:00
John-David Dalton
a3932c75e1 Move screencast links to Vimeo.
Former-commit-id: d9f48294d88ff50889c0859b3c1956d0e49a3bd4
2012-06-16 09:34:36 -04:00
John-David Dalton
1cd3b9ec47 Add 3rd screencast link to README.md.
Former-commit-id: c714180beab13c911f9d1676cb65117186a9524a
2012-06-15 17:21:45 -04:00
John-David Dalton
0c77f42080 Fix documentation typo, s/uiq/uniq. [closes #32] [cheeaun]
Former-commit-id: 343ff0d9a7238ab7eec6d5cc75b5c7dff3de1925
2012-06-15 09:12:03 -04:00
John-David Dalton
cea122db38 Update submodules.
Former-commit-id: 53394993a20cacf965de52e382ff72399d2b89ad
2012-06-15 00:12:06 -04:00
John-David Dalton
966f9fcd07 Update unit test link in README.md.
Former-commit-id: 0c11e69795cd0a28f0b370c40b7a6197fe712d0a
2012-06-14 15:30:37 -04:00
John-David Dalton
6ce9df8504 Bump to v0.3.2.
Former-commit-id: ff3cac0ce890b46a45c8738d559f68ac63b80caf
2012-06-14 15:19:09 -04:00
John-David Dalton
2a0b223896 Simplify dependencies in build.js.
Former-commit-id: 2d154d1dfb4d639aa32c5e9aeaeea6942965aee7
2012-06-14 02:17:09 -04:00
John-David Dalton
c2db180829 Tweak _.sortedIndex for Chrome optimizations.
Former-commit-id: d1d9f1bd1ecfd3bcde98aa51423e05e128f6ffd4
2012-06-14 01:06:08 -04:00
John-David Dalton
3223e4ffa5 Update documentation and minified build.
Former-commit-id: 5472dccceb33c3575c538190472dde0e82bcfcde
2012-06-14 00:29:14 -04:00
John-David Dalton
24c9b6e211 Move _.pluck and _.invoke back to the "Collections" category and optimize _.sortedIndex when a callback is passed.
Former-commit-id: d16763e7d35660d8ba9ea976c8b2a4dc20f1211f
2012-06-14 00:28:36 -04:00
John-David Dalton
e5555dd26a Move _.tap to the "Chaining" category, and deprecate _(…).chain, _.isNull, _.isUndefined, _.result, and _.size.
Former-commit-id: 64ee67758b8730fd70cbb28af4230bb336b91214
2012-06-13 15:26:46 -04:00
John-David Dalton
ee2d0ddf8a Ensure custom builds generate lodash.custom files.
Former-commit-id: c89ee640688f6f598bb2d3834f714f0edc4ce7a4
2012-06-13 15:15:20 -04:00
John-David Dalton
aef130b396 Update submodules.
Former-commit-id: 801d3c7413cb5906b4a517639098a6a32d015cba
2012-06-13 00:21:03 -04:00
John-David Dalton
fbdadec5e5 Add _.throttle unit test for recursive calls.
Former-commit-id: 7208516b56905c83df73aef6b02cee0101602349
2012-06-12 23:53:03 -04:00
John-David Dalton
a068339079 Indicate slow paths in perf.js.
Former-commit-id: 561e4f0957934b25422f7f515678705be4af726f
2012-06-12 23:44:21 -04:00
John-David Dalton
3f07d3ec55 Remove the useSourceURL variable from the minified build.
Former-commit-id: c0f02e7e87c533220a99ebe99c9ee08516fb9deb
2012-06-12 20:15:52 -04:00
John-David Dalton
62e7da9d8a Update docs and minified build.
Former-commit-id: d0673323dc850f4e7936f00374905cd29c5d6e24
2012-06-12 19:29:01 -04:00
John-David Dalton
b60d43e5f6 Ensure sourceURL support doesn't cause errors in Adobe's JS engine.
Former-commit-id: 2ad1214e6a58832c732499b272ebfc434953213b
2012-06-12 19:28:47 -04:00
John-David Dalton
a5873a3158 Add comments, documentation, and update the minified build.
Former-commit-id: c65665063f254b1ba9dab9c6948fedf29f1e795f
2012-06-11 22:49:24 -04:00
John-David Dalton
3a698eb0ed Make _.escape match _.template's escape delimiter results for null and undefined values.
Former-commit-id: b6717c6debf3bc308cf12b778916f5a46dbb954d
2012-06-11 22:37:54 -04:00
John-David Dalton
1e2ef542e4 Bump to v0.3.1.
Former-commit-id: 2e1e3b7e47b1f98d2ca20a89a4570a0b958d4323
2012-06-11 00:09:29 -04:00
John-David Dalton
94e8af4d93 Update submodules.
Former-commit-id: 0658ae9b9e49c468f839738f34c9f6361b90897e
2012-06-10 23:58:43 -04:00
John-David Dalton
250a0ab5eb Add _.shuffle benchmark.
Former-commit-id: 13a0a7392108571d4ebced5e6e6ff66048091983
2012-06-10 13:46:45 -04:00
John-David Dalton
2da05d88a0 Add another code example to _.invoke documentation.
Former-commit-id: 0bea74a470771ebc5cf2110243b38db3698cde52
2012-06-10 12:59:42 -04:00
John-David Dalton
7faa849a89 Cleanup README.md.
Former-commit-id: 1cc58476c5737624b97801aabc0dc7368c43c0d3
2012-06-09 03:23:39 -04:00
John-David Dalton
c5bceb8fc8 Update documentation and minified build.
Former-commit-id: edc6264f5721cbdb080c4f7772263848c125b3e2
2012-06-09 03:11:36 -04:00
John-David Dalton
1e94b93ce6 Add Backbone build and correct dependencies for _.bindAll, _.mixin, and _.sortBy.
Former-commit-id: 3d4413d6ab3b41471be6336d138a4b4bfa97fde7
2012-06-09 03:10:57 -04:00
John-David Dalton
3f7bccf2e6 Update Backbone and Underscore submodules.
Former-commit-id: 6f938216d0bb26347ce7fd42169f926e2ae04e0e
2012-06-09 00:31:03 -04:00
John-David Dalton
181b869109 Remove _.map and _.pluck dependency from _.sortBy and simplify method wrappers.
Former-commit-id: 915af96abd41e8da7bba88cd57eb703f8129107f
2012-06-09 00:29:51 -04:00
John-David Dalton
2332245be1 Update minified build and documentation.
Former-commit-id: 6790d25b2164f0df2dcce63d5a5780343f3b5e63
2012-06-07 12:44:53 -04:00
John-David Dalton
3c6999f3a4 Ensure all "Arrays" category methods allow a falsey array argument. [closes #23, #24]
Former-commit-id: 66d09d3c8f3c045daf310c46581afa085daa57de
2012-06-07 12:42:33 -04:00
John-David Dalton
5f2f15b976 Remove _.isArguments fallback from mobile build.
Former-commit-id: d98ac9953e9b403e17bdcef099caafe09873980f
2012-06-07 03:27:01 -04:00
John-David Dalton
47dfb5b6b7 Fix typo in _.values benchmark and tweak how percents are displayed in perf.js.
Former-commit-id: 49be4600561e55e134d3152b00c765e305af98b5
2012-06-07 00:07:33 -04:00
John-David Dalton
c410c1293e Update minified build and documentation.
Former-commit-id: e489b826c65d908b934e13d032ed866d68edd576
2012-06-06 23:48:41 -04:00
John-David Dalton
ca1c732f31 Move _.values to Objects category.
Former-commit-id: e85229b53a7697c11f76eae02aef8a4fce3aec3a
2012-06-06 23:45:30 -04:00
John-David Dalton
5e8c373bf4 Move _.pluck to the Arrays category.
Former-commit-id: a3ba36c1c320c8685c25fcb0bbe16ba2baeb6731
2012-06-06 23:11:25 -04:00
John-David Dalton
51a459fe48 Simplify function checks and cleanup documentation.
Former-commit-id: 460f34725e212ced27831b9aad1e9e7e2fc02cf1
2012-06-06 15:56:40 -04:00
John-David Dalton
03c07abbc3 Update README.md.
Former-commit-id: 64ce8f122be084fd93fd711a6ca207fc4ae6b870
2012-06-06 15:05:52 -04:00
John-David Dalton
5eabe1a172 Add sourceURL support to _.template.
Former-commit-id: 31d40d57f903098fe1678777aa7921e72d1dca9e
2012-06-06 15:05:41 -04:00
John-David Dalton
5b6ea7afb2 Bump to v0.3.0.
Former-commit-id: 2b713b3d926e3dbba80eab6e1e14d080f169bf39
2012-06-06 00:52:57 -04:00
John-David Dalton
7c1c5e70ca Update Backbone and RequireJS submodules.
Former-commit-id: 145455767d22eb1b03a751024e69826e2387fd04
2012-06-06 00:50:31 -04:00
John-David Dalton
d475f8f965 Fix documentation typo.
Former-commit-id: 85e89c893d358d196a3f0f04b95acb10bbca2771
2012-06-05 01:44:50 -04:00
John-David Dalton
fd239076dd Fix minified build for _.forIn.
Former-commit-id: 1d5d41fe63a10ccae8fcf4a91e7e77526a7c0d9c
2012-06-05 01:07:46 -04:00
John-David Dalton
5f786bbe47 Add category build option.
Former-commit-id: 4adea9367949985a1218cff58ff856184fd11db7
2012-06-05 00:41:20 -04:00
John-David Dalton
f66dc6bed8 Fix typo in iteratorTemplate.
Former-commit-id: b787391db088e672c55ca56ca246147557b3694f
2012-06-04 22:49:33 -04:00
John-David Dalton
52c36ac445 Fix documentation typo.
Former-commit-id: defc89d6a8267a4a6626ca6ac7345db1d55cb9b6
2012-06-04 16:54:47 -04:00
John-David Dalton
8f6a78cba5 Update documentation and package.json.
Former-commit-id: 4ab7d954b7abc5f73269eb7c74a39b02fa04970d
2012-06-04 16:38:37 -04:00
John-David Dalton
7053e9113f Add unit test for _.sortedIndex to ensure it supports arrays with high length values.
Former-commit-id: 8956495dfa40c75dcb3c0585e78913607bf92e99
2012-06-04 16:18:23 -04:00
John-David Dalton
89f9ab9940 Bump to v0.3.0-pre.
Former-commit-id: 1e7958b4b1cdffd962a51edf3e3695cea960d8c9
2012-06-04 15:44:54 -04:00
John-David Dalton
240bc40b39 Ensure collection methods treat array-like objects with invalid length properties as regular objects.
Former-commit-id: dd8b382635bc30dc6e417cd9b47c36abfdf5ddcb
2012-06-04 15:36:27 -04:00
John-David Dalton
74649b5f28 Add _.forOwn and _.forIn.
Former-commit-id: f4e94a0fd15318063eec16c464435b07f419fa6a
2012-06-04 14:45:06 -04:00
John-David Dalton
b484d4b2dc Update minified build and documentation.
Former-commit-id: 592319b69487858794529d789383af1b38d55632
2012-06-04 02:23:16 -04:00
John-David Dalton
da1124dd37 Switch to an htmlEscapes object for use in _.escape.
Former-commit-id: bc449b5d6868c846d599840e5c0d90d0314fe4b8
2012-06-04 02:12:41 -04:00
John-David Dalton
210485d0be Update Benchmark.js and UglifyJS submodules.
Former-commit-id: 78611de2f992dc4d6cc4d336bfb761b98b22ce79
2012-06-04 00:08:26 -04:00
John-David Dalton
d9aee5ae60 Update minified build, documentation, and Backbone submodule.
Former-commit-id: cca2cf2f55e2750486d1d32b8da26fbc5576a65b
2012-06-03 21:58:35 -04:00
John-David Dalton
9ac64623fc Added thisArg argument to _.sortedIndex and _.uniq, benchmarks, unit tests, and adjusted related dependencies in build.js.
Former-commit-id: a97aa769d760c7cc4d0df6307cebc860345a0da0
2012-06-03 21:56:36 -04:00
John-David Dalton
6ea4226680 Remove unneeded Closure Compiler export around the template string _.escape(__t) in pre-compile.js.
Former-commit-id: 812bd220cd3baca30a100f07e1d47cd6745a3602
2012-06-03 20:36:31 -04:00
John-David Dalton
c1416bba39 Cleanup console messages in perf.js.
Former-commit-id: b3e669d46f21d39f96873167557b4ede80458beb
2012-06-03 20:35:22 -04:00
John-David Dalton
ddb3ab3238 Tweak indexOf isSorted benchmark.
Former-commit-id: 0183b35929a2c1f7113747a67f55abbc797fab8c
2012-06-03 01:06:12 -04:00
John-David Dalton
5e7c9698c7 Use typeof == 'number' more, and default callback values to docs.
Former-commit-id: 02786903a510d0cc837eeeb7e9f42db2ecd634a4
2012-06-03 01:05:40 -04:00
John-David Dalton
4c66b95516 Make Firebug Lite console fullscreen in more browsers.
Former-commit-id: a42e7d187a582a1caea721860cf3ed4bd0f94368
2012-06-01 07:41:29 -06:00
John-David Dalton
2d03060a0d Make previous build.js regexp simplification non-greedy.
Former-commit-id: e58cfb5e5143e12721bb08445dfd4a6fec119cd1
2012-05-31 23:18:44 -06:00
John-David Dalton
3313b0aa42 Update minified build and docs.
Former-commit-id: 3c0463d77c9091c1e66c7d68240de8b7ab2e499f
2012-05-31 22:54:50 -06:00
John-David Dalton
5d2928d2b3 Simplify regexps in build.js.
Former-commit-id: 7640f90b6aeca0438579c15c9eef270904c2c523
2012-05-31 22:54:37 -06:00
John-David Dalton
f6e2ae41d6 Add more benchmarks.
Former-commit-id: bddb4a26073ecd5aaf622f9726be940b6340cc07
2012-05-31 22:11:07 -06:00
John-David Dalton
7ccb038b6d Add _.isEmpty unit test.
Former-commit-id: 066961a929da280421083d3f9d66b838d3d29dfd
2012-05-31 17:40:47 -06:00
John-David Dalton
7d62bbf74f Optimize _.times.
Former-commit-id: beae48853d0e9be1c3016c11bee86a272964dfa2
2012-05-31 17:37:01 -06:00
John-David Dalton
3d8cc32302 Add fromIndex to _.indexOf and _.lastIndexOf. [closes #20]
Former-commit-id: 3ab67c318a5a7fc2e521a9a2573b694e6920b14d
2012-05-31 17:26:45 -06:00
John-David Dalton
861eea5148 Fix "prototype" iteration bug with _.keys.
Former-commit-id: 1e072b2639e21a5c0a920db0ac27693ade34b009
2012-05-31 10:29:33 -05:00
John-David Dalton
b432721fe5 Optimize this binding in iterator methods and remove _.bind as a dependency for several methods.
Former-commit-id: 60af002cd80758fea81fbff9c2b20b1ccf3ccffd
2012-05-31 10:21:38 -05:00
John-David Dalton
f13a0cc7e0 Format numbers and scroll down results panel in perf.js.
Former-commit-id: 9f80b5534e3b46be7ad9c84ebb7e9ed5afdc35b8
2012-05-30 09:19:58 -04:00
John-David Dalton
1f7e37a1a3 Change performance link in README.md to lodash.com benchmarks.
Former-commit-id: 5c851961fd1b8e46599c28967d782551722a03d4
2012-05-30 04:03:14 -04:00
John-David Dalton
548e9cac26 Update documentation.
Former-commit-id: 6d1aa50111b34e0548f96a55fe3dcca0b7e91b5e
2012-05-30 03:52:59 -04:00
John-David Dalton
0d2a1641c9 Add percent faster than to pref.js results and link to benchmarks in README.md.
Former-commit-id: 1c5fb820fe070599aba780f1765538d408105af3
2012-05-30 03:51:44 -04:00
John-David Dalton
a67281f8e7 Cleanup README.md.
Former-commit-id: b0d0a22ff427b6a5754e2850e2a420e764a1eb39
2012-05-30 01:48:28 -04:00
John-David Dalton
281475e6ef Bump to v0.2.2.
Former-commit-id: c4516d1e2dd2ab4359233121a7f86c356425cebf
2012-05-30 00:21:06 -04:00
John-David Dalton
a5712fc873 Cleanup build.js and fix regression in minified build. [closes #19]
Former-commit-id: 3b455cb277fa8c3fc70efca7b54c6746cde2ea6b
2012-05-29 15:52:02 -04:00
John-David Dalton
205ded45e2 Update submodules, minified build, and documentation.
Former-commit-id: 526a3d4e7b68da2072163a656566ff777b591b2d
2012-05-29 10:18:32 -04:00
John-David Dalton
8ef039b9db Update unit test with _.templateSettings.variable change.
Former-commit-id: e29718c70b0b0bcfeaf54909a32366d12663984d
2012-05-29 10:17:55 -04:00
John-David Dalton
3d2428b278 Merge branch 'master' of github.com:bestiejs/lodash
Former-commit-id: a2b22208dc08b404bd1fbdde9f1d05253c0fc047
2012-05-29 10:14:54 -04:00
John-David Dalton
35d5704e3f Change the default value of _.templateSettings.variable to obj for Underscore.js compatibility. [closes #16]
Former-commit-id: da91e5c881e6b3f9e2108cc231e57c023884b251
2012-05-29 10:14:40 -04:00
Mathias Bynens
c1860d30d6 Optimize escape() by not needlessly escaping the / character
Ref. #18.


Former-commit-id: 82a29019daa15c83cb2159685ca5d575265cd902
2012-05-29 16:11:08 +02:00
Mathias Bynens
e0fba5cb51 Fix typo in build script
Former-commit-id: 1f5327ea167ba88c00c6fcff5bbad3d64be6049b
2012-05-29 14:40:03 +03:00
John-David Dalton
570ba189ed Update docs and minified build.
Former-commit-id: d07b191ef424d6dce31e690e4af5293eb1525313
2012-05-26 03:40:27 -04:00
John-David Dalton
e335e0fd72 Add mobile build support. [closes #14]
Former-commit-id: a73e3ea444f04027e28beeb9d007a19c169c4fa9
2012-05-26 03:40:06 -04:00
John-David Dalton
74a2d8dcb1 Update Backbone and Underscore submodules.
Former-commit-id: 13de1bae42bbbae7ca138bc5d26b83fd66367343
2012-05-26 00:12:43 -04:00
John-David Dalton
43ea0c9072 Reword unit test and add entry to README.md.
Former-commit-id: e5d68317bb8f2688c256de096c58e1b49014a68c
2012-05-26 00:09:41 -04:00
John-David Dalton
dde3eb2e36 Update _.find dependencies.
Former-commit-id: 88e6c8dec2cf62d348bc570c0f3f9bfa23a9dc5c
2012-05-26 00:09:11 -04:00
John-David Dalton
2008bf90af Tweak _.find to avoid using _.identity and add benchmark.
Former-commit-id: a881f90d00828ed6af5b2b0bc80c9ae6e2cb2da8
2012-05-26 00:07:50 -04:00
John-David Dalton
06ffa93bd0 Update minified build and docs.
Former-commit-id: f9c076e5254c563c714bccf7cd5d65b6716cd427
2012-05-25 15:46:29 -04:00
John-David Dalton
5da03cac79 Add pseudo private property in preparation for mobile builds.
Former-commit-id: d5812d968b7694c9778f1be7efa8d05f95789ec8
2012-05-25 15:16:39 -04:00
John-David Dalton
8a5eb89aa8 Ensure _.find returns undefinedwhen a value cannot be found. [closes #15]
Former-commit-id: e6dc89f98a1df81e1b1d67c5e8f5725e4df3bc5a
2012-05-25 15:15:16 -04:00
John-David Dalton
f81ede2fd6 Update changelog with intersect removal.
Former-commit-id: 5902c720c1403b856ac213e293619c31ea6834f5
2012-05-24 18:02:35 -04:00
John-David Dalton
6f7df67ded Cleanup README.md.
Former-commit-id: 6ad747a8ae56f32fd558bd141af6fe95847569ad
2012-05-24 17:50:20 -04:00
John-David Dalton
bb9ad69219 Bump to v0.2.1.
Former-commit-id: d433d39ac7269479f1b9ac3803e62c5021f41e11
2012-05-24 17:08:26 -04:00
John-David Dalton
8f7667a524 Simplify _.max, _.min, and _.bind.
Former-commit-id: 1da0b013d0c47748d757e90248eebe9ed51918ae
2012-05-24 10:02:32 -04:00
John-David Dalton
86e125a6f3 Update minified build and docs.
Former-commit-id: 8c96c3812064f93d404e6d6dda68ab7a865d4228
2012-05-24 02:24:40 -04:00
John-David Dalton
8b3ba13ff0 Cleanup perf.js.
Former-commit-id: 8799b7d384cc48bc1c8ea3289aa12e021be2b97b
2012-05-24 02:23:55 -04:00
John-David Dalton
82f062caf2 Sync with Underscore.
Former-commit-id: 2e3aacd53fef6d22d830dbc2d6c220808aea8739
2012-05-24 02:06:10 -04:00
John-David Dalton
67303eb9fb Update Backbone and Underscore submodules.
Former-commit-id: 43178330e273531286982e34acf96dadf9586f03
2012-05-24 01:43:54 -04:00
John-David Dalton
af3ded68c4 Cleanup _.bind.
Former-commit-id: d974cdfa52c3f6c175e57f0970380bcc9276c35d
2012-05-24 01:40:24 -04:00
John-David Dalton
b94eb44e18 Update minified build and docs.
Former-commit-id: 525d891914a86b1f7167c9dd82b9deaaad453058
2012-05-24 01:30:48 -04:00
John-David Dalton
c62b24b024 Make _.bind follow ES5 spec so it will work with a common Backbone pattern. [closes #11]
Former-commit-id: 8d5e399ca9727a32604601f81fffd9134104c8f4
2012-05-24 01:25:08 -04:00
John-David Dalton
baa37450cc Tweak export order for r.js.
Former-commit-id: 81f8f3feae9228e99978710c0193a9d26bb9b519
2012-05-23 17:06:21 -04:00
John-David Dalton
231fd46cd2 Ensure applet is hidden in perf/index.html.
Former-commit-id: ee23b966eca9148bbecd8d0826d954e755d232fe
2012-05-23 17:00:49 -04:00
John-David Dalton
b2728d9902 Update docs.
Former-commit-id: 462a64eeb8f8afb0951d8449f0f7b766abbcf5cc
2012-05-23 14:52:15 -04:00
John-David Dalton
451a33f526 Add _.difference, _.pick, and _.union benchmarks.
Former-commit-id: 1dd1e577520dfc1a69876eeccadce871f1f05916
2012-05-23 14:30:45 -04:00
John-David Dalton
3670bce918 Add _.flatten test to ensure consistent behavior with sparse arrays.
Former-commit-id: 5505f4d0542596e4c6469aa038fa1bfecaa79800
2012-05-23 14:28:51 -04:00
John-David Dalton
afb041b23c Update docs and minified build.
Former-commit-id: 8ece30dc91c7cd9fe9f936fdf09e6d673566eaef
2012-05-23 02:20:25 -04:00
John-David Dalton
5315058e2d Simplify _.flatten and add benchmarks.
Former-commit-id: f541328bf680a75abea68bce813820def375f4a0
2012-05-23 02:19:35 -04:00
John-David Dalton
26d9cc972e Add unit test to test to ensure _.groupBy only adds elements to own, not inherited, properties of the result object.
Former-commit-id: 61dcdd0f6172db66d62e97873c1bc3053e339342
2012-05-23 01:25:34 -04:00
John-David Dalton
52ae87812e Update docs and minified build.
Former-commit-id: f019b90d5e866bf2a6da1c003001d0e9345bb0b1
2012-05-23 01:01:35 -04:00
John-David Dalton
f8af24b383 Remove unneeded variable references.
Former-commit-id: ab2f5f4408db25dc63b5d85b4adf156f9f978711
2012-05-23 00:52:39 -04:00
John-David Dalton
b38947146e Move _.groupBy and _.size to the "Objects" category and shuffle method order to avoid extra private variables.
Former-commit-id: 8dc89136a3bf2ed94f594f5c1c1f97a0dd7291c4
2012-05-23 00:43:23 -04:00
John-David Dalton
5c48abff4b Add links to relevant sections in the description.
Former-commit-id: da9e65fadae73792cfb2ec885318bc073d4468c3
2012-05-22 13:10:43 -04:00
John-David Dalton
3994c7fc2b Update docdown submodule and README.md.
Former-commit-id: 745eccb03a7eed1f3f2ef696002c57d6fcc44d25
2012-05-22 13:01:14 -04:00
75 changed files with 44887 additions and 1837 deletions

2
.gitignore vendored
View File

@@ -1,4 +1,4 @@
*.custom.*
.DS_Store
dist/
node_modules/
node_modules/

24
.gitmodules vendored
View File

@@ -1,24 +0,0 @@
[submodule "vendor/benchmark.js"]
path = vendor/benchmark.js
url = git://github.com/bestiejs/benchmark.js.git
[submodule "vendor/docdown"]
path = vendor/docdown
url = git://github.com/jdalton/docdown.git
[submodule "vendor/qunit"]
path = vendor/qunit
url = git://github.com/jquery/qunit.git
[submodule "vendor/qunit-clib"]
path = vendor/qunit-clib
url = git://github.com/jdalton/qunit-clib.git
[submodule "vendor/requirejs"]
path = vendor/requirejs
url = git://github.com/jrburke/requirejs.git
[submodule "vendor/uglifyjs"]
path = vendor/uglifyjs
url = git://github.com/mishoo/UglifyJS.git
[submodule "vendor/underscore"]
path = vendor/underscore
url = git://github.com/documentcloud/underscore.git
[submodule "vendor/backbone"]
path = vendor/backbone
url = git://github.com/documentcloud/backbone.git

View File

@@ -1,19 +1,17 @@
*.custom.*
*.min.*
.*
build.*
build/
dist/
doc/*.php
node_modules/
perf/*.html
perf/*.sh
test/*.html
test/*-ui.js
test/*.sh
vendor/backbone/
vendor/closure-compiler/
vendor/docdown/
vendor/firebug-lite/
vendor/qunit/qunit/*.css
vendor/underscore/
vendor/requirejs/
vendor/uglifyjs/
vendor/underscore/
vendor/firebug-lite/

187
README.md
View File

@@ -1,12 +1,21 @@
# Lo-Dash <sup>v0.2.0</sup>
# Lo-Dash <sup>v0.4.0</sup>
A drop-in replacement for Underscore.js, from the devs behind [jsPerf.com](http://jsperf.com), that delivers [performance improvements](http://jsperf.com/lodash-underscore#filterby=family), bug fixes, and additional features.
A drop-in replacement for Underscore.js, from the devs behind [jsPerf.com](http://jsperf.com), that delivers [performance improvements](http://lodash.com/benchmarks), [bug fixes](https://github.com/bestiejs/lodash#closed-underscorejs-issues), 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.4.0/lodash.js)
* [Production source](https://raw.github.com/bestiejs/lodash/v0.4.0/lodash.min.js)
* CDN copies of ≤ [v0.3.2](http://cdnjs.cloudflare.com/ajax/libs/lodash.js/0.3.2/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
Weve got [API docs](http://lodash.com/docs) and [unit tests](http://lodash.com/tests).
Weve got [API docs](http://lodash.com/docs), [benchmarks](http://lodash.com/benchmarks), and [unit tests](http://lodash.com/tests).
Create your own benchmarks at [jsPerf](http://jsperf.com), or [search](http://jsperf.com/search?q=lodash) for existing ones.
For a list of upcoming features, check out our [roadmap](https://github.com/bestiejs/lodash/wiki/Roadmap).
@@ -14,42 +23,79 @@ For a list of upcoming features, check out our [roadmap](https://github.com/best
For more information check out these screencasts over Lo-Dash:
* [Introducing Lo-Dash](http://dl.dropbox.com/u/513327/allyoucanleet/post/20/file/screencast.mp4)
* [Optimizations and custom builds](http://dl.dropbox.com/u/513327/allyoucanleet/post/21/file/screencast.mp4)
* [Introducing Lo-Dash](https://vimeo.com/44154599)
* [Optimizations and custom builds](https://vimeo.com/44154601)
* [Lo-Dashs origin and why its a better utility belt](https://vimeo.com/44154600)
## Features
* AMD loader support
* [_.bind](http://lodash.com/docs#_bindfunc--arg1-arg2-) supports *"lazy"* binding
* [_.debounce](http://lodash.com/docs#_debouncefunc-wait-immediate)ed functions match [_.throttle](http://lodash.com/docs#_throttlefunc-wait)ed functions return value behavior
* [_.forEach](http://lodash.com/docs#_foreachcollection-callback--thisarg) is chainable
* [_.groupBy](http://lodash.com/docs#_groupbycollection-callback--thisarg) accepts a third `thisArg` argument
* [_.partial](http://lodash.com/docs#_partialfunc--arg1-arg2-) for more functional fun
* [_.size](http://lodash.com/docs#_sizecollection) returns the `length` of string values
* AMD loader support ([RequireJS](http://requirejs.org/), [curl.js](https://github.com/cujojs/curl), etc.)
* [_.bind](http://lodash.com/docs#bind) supports *"lazy"* binding
* [_.debounce](http://lodash.com/docs#debounce)ed functions match [_.throttle](http://lodash.com/docs#throttle)ed functions return value behavior
* [_.forEach](http://lodash.com/docs#forEach) is chainable
* [_.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
* [_.partial](http://lodash.com/docs#partial) for more functional fun
* [_.template](http://lodash.com/docs#template) utilizes [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl) for easier debugging
* [_.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
## Support
Lo-Dash has been tested in at least Chrome 5-19, Firefox 1.5-12, IE 6-9, Opera 9.25-11.64, Safari 3.0.4-5.1.3, Node.js 0.4.8-0.6.18, Narwhal 0.3.2, RingoJS 0.8, and Rhino 1.7RC3.
Lo-Dash has been tested in at least Chrome 5-20, Firefox 1.5-13, IE 6-9, Opera 9.25-12, Safari 3.0.4-5.1.7, Node.js 0.4.8-0.8.2, Narwhal 0.3.2, RingoJS 0.8, and Rhino 1.7RC3.
## 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.
Custom builds may be created in two ways:
1. Use the`include` argument to pass the names of the methods to include in the build.
* Backbone builds, containing all methods required by Backbone, may be created using the `backbone` modifier argument.
~~~ bash
node build include=each,filter,map,noConflict
node build include="each, filter, map, noConflict"
lodash backbone
~~~
2. Use the `exclude` argument to pass the names of the methods to exclude from the build.
* Legacy builds, tailored for older browsers without [ES5 support](http://es5.github.com/), may be created using the `legacy` modifier argument.
~~~ bash
node build exclude=isNaN,isUndefined,union,zip
node build exclude="isNaN, isUndefined, union, zip"
lodash legacy
~~~
* Mobile builds, with IE bug fixes and method compilation removed, may be created using the `mobile` modifier argument.
~~~ bash
lodash mobile
~~~
Custom builds may be created in three ways:
1. Use the `category` argument to pass the 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 `include` argument to pass the names of methods to include in the build.
~~~ bash
lodash include=each,filter,map
lodash include="each, filter, map"
~~~
3. Use the `exclude` argument to pass the names of methods to exclude from the build.
~~~ bash
lodash exclude=union,uniq,zip
lodash exclude="union, uniq, zip"
~~~
All arguments, except `include` with `exclude` and `mobile` with `legacy`, may be combined.
~~~ bash
lodash backbone mobile category=functions include=pick,uniq
lodash backbone legacy category=utilities exclude=first,last
~~~
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`.
## Installation and usage
@@ -64,6 +110,7 @@ Using [npm](http://npmjs.org/):
~~~ bash
npm install lodash
npm install -g lodash
~~~
In [Node.js](http://nodejs.org/) and [RingoJS v0.8.0+](http://ringojs.org/):
@@ -97,33 +144,31 @@ require({
});
~~~
## Cloning this repo
## Closed Underscore.js issues <sup>(20+)</sup>
To clone this repository including all submodules, using Git 1.6.5 or later:
~~~ bash
git clone --recursive https://github.com/bestiejs/lodash.git
cd lodash.js
~~~
For older Git versions, just use:
~~~ bash
git clone https://github.com/bestiejs/lodash.git
cd lodash
git submodule update --init
~~~
## Closed Underscore.js issues
* Fix Firefox, IE, Opera, and Safari object iteration bugs [#376](https://github.com/documentcloud/underscore/issues/376)
* 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)
* Register as AMD module, but still export to global [#431](https://github.com/documentcloud/underscore/pull/431)
* `_.forEach` should be chainable [#142](https://github.com/documentcloud/underscore/issues/142)
* `_isNaN(new Number(NaN))` should return `true`
* `_.reduceRight` should pass correct callback arguments when iterating objects
* `_.size` should return the `length` of string values
* 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.4.0/test/test.js#L306-312)]
* Ensure array-like objects with invalid `length` properties are treated like regular objects [[test](https://github.com/bestiejs/lodash/blob/v0.4.0/test/test.js#L257-263), [test](https://github.com/bestiejs/lodash/blob/v0.4.0/test/test.js#L607-621), [test](https://github.com/bestiejs/lodash/blob/v0.4.0/test/test.js#L840-843)]
* Ensure *"Arrays"* methods allow falsey `array` arguments [[test](https://github.com/bestiejs/lodash/blob/v0.4.0/test/test.js#L908-947)]
* 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.4.0/test/test.js#L265-283), [test](https://github.com/bestiejs/lodash/blob/v0.4.0/test/test.js#L623-640), [test](https://github.com/bestiejs/lodash/blob/v0.4.0/test/test.js#L845-848)]
* Ensure templates compiled with errors are inspectable [[#666](https://github.com/documentcloud/underscore/issues/666), [test](https://github.com/bestiejs/lodash/blob/v0.4.0/test/test.js#L737-744)]
* Fix cross-browser object iteration bugs [[#376](https://github.com/documentcloud/underscore/issues/376), [test](https://github.com/bestiejs/lodash/blob/v0.4.0/test/test.js#L195-207), [test](https://github.com/bestiejs/lodash/blob/v0.4.0/test/test.js#L317-342), [test](https://github.com/bestiejs/lodash/blob/v0.4.0/test/test.js#L438-449), [test](https://github.com/bestiejs/lodash/blob/v0.4.0/test/test.js#L457-459), [test](https://github.com/bestiejs/lodash/blob/v0.4.0/test/test.js#L477-497), [test](https://github.com/bestiejs/lodash/blob/v0.4.0/test/test.js#L667-669)]
* 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.4.0/test/test.js#L86-92)]
* 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.4.0/test/test.js#L70-84)]
* `_(…)` should return passed wrapper instances [[test](https://github.com/bestiejs/lodash/blob/v0.4.0/test/test.js#L104-107)]
* `_.contains` should work with strings [[#667](https://github.com/documentcloud/underscore/pull/667), [test](https://github.com/bestiejs/lodash/blob/v0.4.0/test/test.js#L138-147)]
* `_.escape` should return an empty string when passed `null` or `undefined` [[#407](https://github.com/documentcloud/underscore/issues/427), [test](https://github.com/bestiejs/lodash/blob/v0.4.0/test/test.js#L176-179)]
* `_.forEach` should be chainable [[#142](https://github.com/documentcloud/underscore/issues/142), [test](https://github.com/bestiejs/lodash/blob/v0.4.0/test/test.js#L252-255)]
* `_.groupBy` should add values to own, not inherited, properties [[test](https://github.com/bestiejs/lodash/blob/v0.4.0/test/test.js#L357-364)]
* `_isNaN(new Number(NaN))` should return `true` [[test](https://github.com/bestiejs/lodash/blob/v0.4.0/test/test.js#L467-469)]
* `_.reduceRight` should pass correct callback arguments when iterating objects [[test](https://github.com/bestiejs/lodash/blob/v0.4.0/test/test.js#L591-605)]
* `_.size` should return the `length` of string values [[test](https://github.com/bestiejs/lodash/blob/v0.4.0/test/test.js#L650-652)]
* `_.size` shouldn't error on falsey values [[#650](https://github.com/documentcloud/underscore/pull/650), [test](https://github.com/bestiejs/lodash/blob/v0.4.0/test/test.js#L654-661)]
* `_.size` should work with `arguments` objects cross-browser [[#653](https://github.com/documentcloud/underscore/issues/653), [test](https://github.com/bestiejs/lodash/blob/v0.4.0/test/test.js#L663-665)]
* `_.sortedIndex` should support arrays with high `length` values [[test](https://github.com/bestiejs/lodash/blob/v0.4.0/test/test.js#L707-716)]
* `_.template` should not augment the `options` object [[test](https://github.com/bestiejs/lodash/blob/v0.4.0/test/test.js#L731-735)]
* `_.throttle` should work when called in a tight loop [[#502](https://github.com/documentcloud/underscore/issues/502), [test](https://github.com/bestiejs/lodash/blob/v0.4.0/test/test.js#L791-801)]
* `_.zipObject` should accept less than two arguments [[test](https://github.com/bestiejs/lodash/blob/v0.4.0/test/test.js#L870-872)]
## Optimized methods <sup>(50+)</sup>
@@ -146,12 +191,17 @@ git submodule update --init
* `_.functions`, `_.methods`
* `_.groupBy`
* `_.indexOf`
* `_.intersection`, `_.intersect`
* `_.intersection`
* `_.invoke`
* `_.isArguments`
* `_.isDate`
* `_.isEmpty`
* `_.isEqual`
* `_.isFinite`
* `_.isFunction`
* `_.isObject`
* `_.isNumber`
* `_.isRegExp`
* `_.isString`
* `_.keys`
* `_.lastIndexOf`
@@ -171,6 +221,7 @@ git submodule update --init
* `_.sortedIndex`
* `_.template`
* `_.throttle`
* `_.times`
* `_.toArray`
* `_.union`
* `_.uniq`, `_.unique`
@@ -178,33 +229,27 @@ git submodule update --init
* `_.without`
* `_.wrap`
* `_.zip`
* plus all `_(...)` method wrappers
* plus all `_()` method wrappers
## Changelog
## Release Notes
### <sup>v0.2.0</sup>
### <sup>v0.4.0</sup>
* Added custom build options
* Added default `_.templateSettings.variable` value
* Added *"lazy bind"* support to `_.bind`
* Added native method overwrite detection to avoid bad native shims
* Added support for more AMD build optimizers and aliasing as the *"underscore"* module
* Added `thisArg` to `_.groupBy`
* Added whitespace to compiled strings
* Added `_.partial`
* Commented the `iterationFactory` options object
* Ensured `_.max` and `_.min` support extremely large arrays
* Fixed IE < 9 `[DontEnum]` bug and Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1s prototype property iteration bug
* Inlined `_.isFunction` calls.
* Made `_.debounce`ed functions match `_.throttle`ed functions return value behavior
* Made `_.escape` no longer translate the *">"* character
* Fixed `clearTimeout` typo
* Simplified all methods in the *"Arrays"* category
* Optimized `_.debounce`, `_.escape`, `_.flatten`, `_.forEach`, `_.groupBy`, `_.intersection`, `_.invoke`, `_.isObject`, `_.max`, `_.min`, `_.pick`, `_.shuffle`, `_.sortedIndex`, `_.template`, `_.throttle`, `_.union`, `_.uniq`
* Added `bin` and `scripts` entries to package.json
* Added `legacy` build option
* Added cross-browser support for passing strings to *"Collections"* methods
* Added `_.zipObject`
* Leveraged `_.indexOf`'s `fromIndex` in `_.difference` and `_.without`
* Optimized compiled templates
* Optimized inlining the `iteratorTemplate` for builds
* Optimized object iteration for *"Collections"* methods
* Optimized partially applied `_.bind` in V8
* Made compiled templates more debuggable
* Made `_.size` work with falsey values and consistent cross-browser with `arguments` objects
* Moved `_.groupBy` and `_.sortBy` back to the *"Collections"* category
* Removed `arguments` object from `_.range`
### <sup>v0.1.0</sup>
* Initial release
The full changelog is available [here](https://github.com/bestiejs/lodash/wiki/Changelog).
## BestieJS

678
build.js
View File

@@ -2,16 +2,23 @@
;(function() {
'use strict';
/** The Node filesystem and path modules */
/** Load modules */
var fs = require('fs'),
path = require('path');
/** Load other modules */
var lodash = require(path.join(__dirname, 'lodash')),
path = require('path'),
vm = require('vm'),
minify = require(path.join(__dirname, 'build', 'minify'));
/** Flag used to specify a custom build */
var isCustom = false;
/** The current working directory */
var cwd = process.cwd();
/** Flag used to specify a backbone build */
var isBackbone = process.argv.indexOf('backbone') > -1;
/** Flag used to specify a legacy build */
var isLegacy = process.argv.indexOf('legacy') > -1;
/** Flag used to specify a mobile build */
var isMobile = !isLegacy && process.argv.indexOf('mobile') > -1;
/** Shortcut used to convert array-like objects to arrays */
var slice = [].slice;
@@ -19,6 +26,22 @@
/** The lodash.js source */
var source = fs.readFileSync(path.join(__dirname, 'lodash.js'), 'utf8');
/** Load customized Lo-Dash module */
var lodash = (function() {
var sandbox = {};
if (isLegacy) {
['isBindFast', 'isKeysFast', 'nativeBind', 'nativeIsArray', 'nativeKeys'].forEach(function(varName) {
source = replaceVar(source, varName, 'false');
});
}
else if (isMobile) {
source = replaceVar(source, 'isKeysFast', 'false');
}
vm.runInNewContext(source, sandbox);
return sandbox._;
}());
/** Used to associate aliases with their real names */
var aliasToRealMap = {
'all': 'every',
@@ -31,7 +54,6 @@
'head': 'first',
'include': 'contains',
'inject': 'reduce',
'intersect': 'intersection',
'methods': 'functions',
'select': 'filter',
'tail': 'rest',
@@ -48,7 +70,6 @@
'first': ['head', 'take'],
'forEach': ['each'],
'functions': ['methods'],
'intersection': ['intersect'],
'map': ['collect'],
'reduce': ['foldl', 'inject'],
'reduceRight': ['foldr'],
@@ -57,32 +78,78 @@
'uniq': ['unique']
};
/** Used to track 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',
'shuffle',
'size',
'some',
'sortBy',
'sortedIndex',
'toArray',
'uniqueId',
'without'
];
/** Used to track function dependencies */
var dependencyMap = {
'after': [],
'bind': [],
'bindAll': ['bind'],
'chain': [],
'bindAll': ['bind', 'functions'],
'chain': ['mixin'],
'clone': ['extend', 'isArray'],
'compact': [],
'compose': [],
'contains': ['createIterator'],
'createIterator': [],
'contains': [],
'debounce': [],
'defaults': ['createIterator'],
'defaults': [],
'defer': [],
'delay': [],
'difference': ['indexOf'],
'escape': [],
'every': ['bind', 'createIterator', 'identity'],
'extend': ['createIterator'],
'filter': ['bind', 'createIterator', 'identity'],
'find': ['createIterator'],
'every': ['identity'],
'extend': [],
'filter': ['identity'],
'find': [],
'first': [],
'flatten': ['isArray'],
'forEach': ['bind', 'createIterator'],
'functions': ['createIterator'],
'groupBy': ['bind', 'createIterator'],
'forEach': [],
'forIn': [],
'forOwn': [],
'functions': [],
'groupBy': [],
'has': [],
'identity': [],
'indexOf': ['sortedIndex'],
@@ -94,7 +161,7 @@
'isBoolean': [],
'isDate': [],
'isElement': [],
'isEmpty': ['createIterator'],
'isEmpty': [],
'isEqual': [],
'isFinite': [],
'isFunction': [],
@@ -105,48 +172,97 @@
'isRegExp': [],
'isString': [],
'isUndefined': [],
'keys': ['createIterator'],
'keys': [],
'last': [],
'lastIndexOf': [],
'map': ['bind', 'createIterator', 'identity'],
'max': ['bind'],
'map': ['identity'],
'max': [],
'memoize': [],
'min': ['bind'],
'mixin': ['forEach'],
'min': [],
'mixin': ['forEach', 'functions'],
'noConflict': [],
'once': [],
'partial': [],
'pick': [],
'pluck': ['createIterator'],
'pluck': [],
'range': [],
'reduce': ['bind', 'createIterator'],
'reduceRight': ['bind', 'keys'],
'reject': ['bind', 'createIterator', 'identity'],
'reduce': [],
'reduceRight': ['keys'],
'reject': ['identity'],
'rest': [],
'result': [],
'shuffle': [],
'size': ['keys'],
'some': ['bind', 'createIterator', 'identity'],
'sortBy': ['bind', 'map', 'pluck'],
'sortedIndex': [],
'some': ['identity'],
'sortBy': [],
'sortedIndex': ['bind'],
'tap': [],
'template': ['escape'],
'throttle': [],
'times': ['bind'],
'times': [],
'toArray': ['values'],
'union': ['indexOf'],
'uniq': ['indexOf'],
'uniq': ['identity', 'indexOf'],
'uniqueId': [],
'values': ['createIterator'],
'values': [],
'without': ['indexOf'],
'wrap': [],
'zip': ['max', 'pluck']
'zip': ['max', 'pluck'],
'zipObject': []
};
/** Used to `iteratorTemplate` */
var iteratorOptions = [
'args',
'array',
'arrayBranch',
'beforeLoop',
'bottom',
'exit',
'firstArg',
'hasDontEnumBug',
'inLoop',
'init',
'isKeysFast',
'iteratee',
'object',
'objectBranch',
'noCharByIndex',
'shadowed',
'top',
'useHas'
];
/** Collections of method names */
var excludeMethods,
includeMethods,
allMethods = Object.keys(dependencyMap);
/** Used to specify whether filtering is for exclusion or inclusion */
var filterType = process.argv.reduce(function(result, value) {
if (result) {
return result;
}
var pair = value.match(/^(exclude|include)=(.*)$/);
if (!pair) {
return result;
}
// remove nonexistent method names
var methodNames = lodash.intersection(allMethods, pair[2].split(/, */).map(getRealName));
if (pair[1] == 'exclude') {
excludeMethods = methodNames;
} else {
includeMethods = methodNames;
}
// return `filterType`
return pair[1];
}, '');
/*--------------------------------------------------------------------------*/
/**
* Gets the aliases associated with a given `funcName`.
* Gets the aliases associated with a given function name.
*
* @private
* @param {String} funcName The name of the function to get aliases for.
@@ -157,7 +273,7 @@
}
/**
* Gets an array of depenants for a function by the given `funcName`.
* Gets an array of depenants for a function by a given name.
*
* @private
* @param {String} funcName The name of the function to query.
@@ -167,7 +283,7 @@
// iterate over `dependencyMap`, adding the names of functions that
// have `funcName` as a dependency
return lodash.reduce(dependencyMap, function(result, dependencies, otherName) {
if (dependencies.indexOf(funcName) > -1) {
if (lodash.contains(dependencies, funcName)) {
result.push(otherName);
}
return result;
@@ -175,29 +291,60 @@
}
/**
* Gets an array of dependencies for a function of the given `funcName`.
* Gets an array of dependencies for a given function name. If passed an array
* of dependencies it will return an array containing the given dependencies
* plus any additional detected sub-dependencies.
*
* @private
* @param {String} funcName The name of the function to query.
* @param {Array|String} funcName A single function name or array of
* dependencies to query.
* @returns {Array} Returns an array of function dependencies.
*/
function getDependencies(funcName) {
var dependencies = dependencyMap[funcName],
result = [];
var dependencies = Array.isArray(funcName) ? funcName : dependencyMap[funcName];
if (!dependencies) {
return result;
return [];
}
// recursively accumulate the dependencies of the `funcName` function, and
// the dependencies of its dependencies, and so on.
return dependencies.reduce(function(result, otherName) {
return lodash.uniq(dependencies.reduce(function(result, otherName) {
result.push.apply(result, getDependencies(otherName).concat(otherName));
return result;
}, result);
}, []));
}
/**
* Gets the real name, not alias, of a given `funcName`.
* Gets the formatted source of the given function.
*
* @private
* @param {Function} func The function to process.
* @returns {String} Returns the formatted source.
*/
function getFunctionSource(func) {
var source = func.source || (func + '');
// all leading whitespace
return source.replace(/\n(?:.*)/g, function(match, index) {
match = match.slice(1);
return (
match == '}' && source.indexOf('}', index + 2) == -1 ? '\n ' : '\n '
) + match;
});
}
/**
* Gets the `_.isArguments` fallback snippet from `source`.
*
* @private
* @param {String} source The source to inspect.
* @returns {String} Returns the `isArguments` fallback snippet.
*/
function getIsArgumentsFallback(source) {
return (source.match(/(?:\s*\/\/.*)*\s*if *\(!(?:lodash\.)?isArguments[^)]+\)[\s\S]+?};\s*}/) || [''])[0];
}
/**
* Gets the real name, not alias, of a given function name.
*
* @private
* @param {String} funcName The name of the function to resolve.
@@ -208,7 +355,7 @@
}
/**
* Determines if all functions of the given names have been removed from the `source`.
* Determines if all functions of the given names have been removed from `source`.
*
* @private
* @param {String} source The source to inspect.
@@ -222,7 +369,7 @@
}
/**
* Searches the `source` for a `funcName` function declaration, expression, or
* Searches `source` for a `funcName` function declaration, expression, or
* assignment and returns the matched snippet.
*
* @private
@@ -239,9 +386,9 @@
// match a function declaration
'( +)function ' + funcName + '\\b[\\s\\S]+?\\n\\1}|' +
// match a variable declaration with `createIterator`
' +var ' + funcName + ' *= *(?:[a-zA-Z]+ *\\|\\| *)?createIterator\\((?:{|[a-zA-Z])[\\s\\S]+?\\);|' +
' +var ' + funcName + ' *=.*?createIterator\\((?:{|[a-zA-Z])[\\s\\S]+?\\);|' +
// match a variable declaration with function expression
'( +)var ' + funcName + ' *= *(?:[a-zA-Z]+ *\\|\\| *)?function[\\s\\S]+?\\n\\2};' +
'( +)var ' + funcName + ' *=.*?function[\\s\\S]+?\\n\\2};' +
// end non-capturing group
')\\n'
));
@@ -250,7 +397,7 @@
}
/**
* Removes the all references to `refName` from the `createIterator` source.
* Removes the all references to `refName` from `createIterator` in `source`.
*
* @private
* @param {String} source The source to process.
@@ -258,7 +405,7 @@
* @returns {String} Returns the modified source.
*/
function removeFromCreateIterator(source, refName) {
var snippet = matchFunction(source, 'createIterator'),
var snippet = matchFunction(source, 'createIterator').match(/Function\([\s\S]+$/)[0],
modified = snippet.replace(RegExp('\\b' + refName + '\\b,? *', 'g'), '');
return source.replace(snippet, modified);
@@ -266,7 +413,7 @@
/**
* Removes the `funcName` function declaration, expression, or assignment and
* associated code from the `source`.
* associated code from `source`.
*
* @private
* @param {String} source The source to process.
@@ -281,7 +428,6 @@
if (!snippet) {
return source;
}
// remove function
source = source.replace(matchFunction(source, funcName), '');
@@ -300,7 +446,37 @@
}
/**
* Removes a given variable from the `source`.
* Removes the `_.isArguments` fallback from `source`.
*
* @private
* @param {String} source The source to process.
* @returns {String} Returns the source with the `isArguments` fallback removed.
*/
function removeIsArgumentsFallback(source) {
return source.replace(getIsArgumentsFallback(source), '');
}
/**
* Removes the `Object.keys` object iteration optimization from `source`.
*
* @private
* @param {String} source The source to process.
* @returns {String} Returns the modified source.
*/
function removeKeysOptimization(source) {
return removeVar(source, 'isKeysFast')
// remove `isKeysFast` from `beforeLoop.object` of `mapIteratorOptions`
.replace(/=\s*'\s*\+\s*\(isKeysFast.+/, "= []'")
// remove `isKeysFast` from `inLoop.object` of `mapIteratorOptions`, `invoke`, `pluck`, and `sortBy`
.replace(/'\s*\+\s*\(isKeysFast[^)]+?\)\s*\+\s*'/g, '.push')
// remove data object property assignment in `createIterator`
.replace(/\s*.+?\.isKeysFast *=.+/, '')
// remove optimized branch in `iteratorTemplate`
.replace(/(?: *\/\/.*\n)*\s*'( *)<% *if *\(isKeysFast[\s\S]+?'\1<% *} *else *\{ *%>.+\n([\s\S]+?) *'\1<% *} *%>.+/, '$2');
}
/**
* Removes a given variable from `source`.
*
* @private
* @param {String} source The source to process.
@@ -314,9 +490,9 @@
// match multi-line comment block
'(?:\\n +/\\*[^*]*\\*+(?:[^/][^*]*\\*+)*/)?\\n' +
// match a variable declaration that's not part of a declaration list
'( +)var ' + varName + ' *= *(?:.*?;|(?:Function\\(.+?|.*?[^,])\\n[\\s\\S]+?\\n\\1.+?;)\\n|' +
'( +)var ' + varName + ' *= *(?:.+?;|(?:Function\\(.+?|.*?[^,])\\n[\\s\\S]+?\\n\\1.+?;)\\n|' +
// match a variable in a declaration list
'\\n +' + varName + ' *=.*?,' +
'\\n +' + varName + ' *=.+?,' +
// end non-capturing group
')'
), '');
@@ -325,84 +501,120 @@
source = source.replace(RegExp('(var +)' + varName + ' *=.+?,\\s+'), '$1');
// remove a variable at the end of a variable declaration list
source = source.replace(RegExp(',\\s*' + varName + ' *=.*?;'), ';');
source = source.replace(RegExp(',\\s*' + varName + ' *=.+?;'), ';');
return removeFromCreateIterator(source, varName);
}
/**
* Searches `source` for a `varName` variable declaration and replaces its
* assigned value with `varValue`.
*
* @private
* @param {String} source The source to inspect.
* @param {String} varName The name of the variable to replace.
* @returns {String} Returns the source with the variable replaced.
*/
function replaceVar(source, varName, varValue) {
// replace a variable that's not part of a declaration list
source = source.replace(RegExp(
'(( +)var ' + varName + ' *= *)' +
'(?:.+?;|(?:Function\\(.+?|.*?[^,])\\n[\\s\\S]+?\\n\\2.+?;)\\n'
), '$1' + varValue + ';\n');
// replace a varaible at the start or middle of a declaration list
source = source.replace(RegExp('((?:var|\\n) +' + varName + ' *=).+?,'), '$1 ' + varValue + ',');
// replace a variable at the end of a variable declaration list
source = source.replace(RegExp('(,\\s*' + varName + ' *=).+?;'), '$1 ' + varValue + ';');
return source;
}
/*--------------------------------------------------------------------------*/
// inline `iteratorTemplate`
(function() {
var iteratorTemplate = lodash._iteratorTemplate,
code = /^function[^{]+{([\s\S]+?)}$/.exec(iteratorTemplate)[1];
// Backbone build
if (isBackbone) {
// add any additional sub-dependencies
backboneDependencies = getDependencies(backboneDependencies);
// remove whitespace from template
code = code.replace(/\[object |else if|function | in |return\s+[\w']|throw |typeof |var |\\\\n|\\n|\s+/g, function(match) {
return match == false || match == '\\n' ? '' : match;
});
if (filterType == 'exclude') {
// remove excluded methods from `backboneDependencies`
includeMethods = lodash.without.apply(lodash, [backboneDependencies].concat(excludeMethods));
}
else if (filterType) {
// merge backbone dependencies into `includeMethods`
includeMethods = lodash.union(includeMethods, backboneDependencies);
}
else {
// include only the Backbone dependencies
includeMethods = backboneDependencies;
}
filterType = 'include';
}
// remove unnecessary code
code = code
.replace(/\|\|\{\}|,__t,__j=Array.prototype.join|function print[^}]+}|\+''/g, '')
.replace(/(\{);|;(\})/g, '$1$2')
.replace(/\(\(__t=\(([^)]+)\)\)==null\?'':__t\)/g, '$1');
/*--------------------------------------------------------------------------*/
// ensure escaped characters are interpreted correctly inside the `Function()` string
code = code.replace(/\\/g, '\\\\');
// add category methods
process.argv.some(function(value) {
var categories = value.match(/^category=(.*)$/);
if (!categories) {
return false;
}
// resolve method names belonging to each category
var categoryMethods = categories.reduce(function(result, category) {
return result.concat(allMethods.filter(function(funcName) {
return RegExp('@category ' + category + '\\b', 'i').test(matchFunction(source, funcName));
}));
}, []);
// add `code` to `Function()`
code = '$1Function(\'object\',\n$2 "' + code + '"\n$2);\n';
// replace `template()` with `Function()`
source = source.replace(/(( +)var iteratorTemplate *= *)([\s\S]+?\n\2.+?);\n/, code);
// remove pseudo private property `_iteratorTemplate`
source = source.replace(/(?:\s*\/\/.*)*\s*lodash\._iteratorTemplate\b.+\n/, '\n');
}());
if (filterType == 'exclude') {
// remove excluded methods from `categoryMethods`
includeMethods = lodash.without.apply(lodash, [categoryMethods].concat(excludeMethods));
}
else if (filterType) {
// merge `categoryMethods` into `includeMethods`
includeMethods = lodash.union(includeMethods, categoryMethods);
}
else {
// include only the `categoryMethods`
includeMethods = categoryMethods;
}
filterType = 'include';
return true;
});
/*--------------------------------------------------------------------------*/
// custom build
process.argv.some(function(arg) {
// exit early if not the "exclude" or "include" command option
var pair = arg.match(/^(exclude|include)=(.*)$/);
if (!pair) {
return false;
(function() {
// exit early if "exclude" or "include" options aren't specified
if (!filterType) {
return;
}
var filterType = pair[1],
filterNames = lodash.intersection(Object.keys(dependencyMap), pair[2].split(/, */).map(getRealName));
// set custom build flag
isCustom = true;
// remove the specified functions and their dependants
if (filterType == 'exclude') {
filterNames.forEach(function(funcName) {
// remove methods that are named in `excludeMethods` and their dependants
excludeMethods.forEach(function(funcName) {
getDependants(funcName).concat(funcName).forEach(function(otherName) {
source = removeFunction(source, otherName);
});
});
}
// else remove all but the specified functions and their dependencies
else {
filterNames = lodash.uniq(filterNames.reduce(function(result, funcName) {
result.push.apply(result, getDependencies(funcName).concat(funcName));
return result;
}, []));
// add dependencies to `includeMethods`
includeMethods = getDependencies(includeMethods);
lodash.each(dependencyMap, function(dependencies, otherName) {
if (filterNames.indexOf(otherName) < 0) {
// remove methods that aren't named in `includeMethods`
lodash.each(allMethods, function(otherName) {
if (!lodash.contains(includeMethods, otherName)) {
source = removeFunction(source, otherName);
}
});
}
// remove associated functions, variables and code snippets
// remove associated functions, variables, and code snippets
if (isRemoved(source, 'isArguments')) {
// remove `isArguments` if-statement
source = source.replace(/(?:\s*\/\/.*)*\s*if *\(!isArguments[^)]+\)[\s\S]+?};?\s*}\n/, '');
source = removeIsArgumentsFallback(source);
}
if (isRemoved(source, 'mixin')) {
// remove `LoDash` constructor
@@ -412,24 +624,34 @@
// remove `LoDash.prototype` additions
source = source.replace(/(?:\s*\/\/.*)*\s*LoDash.prototype *=[\s\S]+?\/\*-+\*\//, '');
}
if (isRemoved(source, 'sortBy')) {
source = removeFunction(source, 'compareAscending');
}
if (isRemoved(source, 'template')) {
// remove `templateSettings` assignment
source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *lodash\.templateSettings[\s\S]+?};\n/, '');
}
if (isRemoved(source, 'max', 'min')) {
source = removeVar(source, 'argsLimit');
// remove `argsLimit` try-catch
source = source.replace(/\n *try\s*\{[\s\S]+?argsLimit *=[\s\S]+?catch[^}]+}\n/, '');
}
if (isRemoved(source, 'isArray', 'isEmpty', 'isEqual', 'size')) {
source = removeVar(source, 'arrayClass');
}
if (isRemoved(source, 'bind', 'functions', 'groupBy', 'invoke', 'isEqual', 'isFunction', 'result', 'sortBy', 'toArray')) {
source = removeVar(source, 'funcClass');
}
if (isRemoved(source, 'bind')) {
source = removeVar(source, 'nativeBind');
source = removeVar(source, 'isBindFast');
}
if (isRemoved(source, 'isArray')) {
source = removeVar(source, 'nativeIsArray');
}
if (isRemoved(source, 'keys')) {
source = removeFunction(source, 'shimKeys');
}
if (isRemoved(source, 'clone', 'isObject', 'keys')) {
source = removeVar(source, 'objectTypes');
source = removeFromCreateIterator(source, 'objectTypes');
}
if (isRemoved(source, 'bind', 'isArray', 'keys')) {
source = removeVar(source, 'reNative');
}
if (isRemoved(source, 'isEmpty', 'isEqual', 'isString', 'size')) {
source = removeVar(source, 'stringClass');
@@ -439,20 +661,232 @@
source = source.replace(/(?:\s*\/\*-+\*\/\s*){2,}/g, function(separators) {
return separators.match(/^\s*/)[0] + separators.slice(separators.lastIndexOf('/*'));
});
}());
return true;
});
/*--------------------------------------------------------------------------*/
// simplify template snippets
source = source.replace(
RegExp(
"'else if \\(thisArg\\) \\{\\\\n' \\+\\s*" +
"' callback = iteratorBind\\(callback, thisArg\\)\\\\n' \\+\\s*" +
"'}'"
, 'g'),
"'else if (thisArg) callback = iteratorBind(callback, thisArg)'"
);
/*--------------------------------------------------------------------------*/
// DRY out isType functions
(function() {
var iteratorName = lodash.find(['forEach', 'forOwn'], function(funcName) {
return !isRemoved(source, funcName);
});
// skip this optimization if there are no iteration methods to use
if (!iteratorName) {
return;
}
var snippet,
funcNames = [],
objectSnippets = [],
token = '__isTypeToken__';
// build replacement code
lodash.forOwn({
'Arguments': "'[object Arguments]'",
'Date': 'dateClass',
'Function': 'funcClass',
'Number': 'numberClass',
'RegExp': 'regexpClass',
'String': 'stringClass'
}, function(value, key) {
// skip `isArguments` if a legacy build
if (isLegacy && key == 'Arguments') {
return;
}
var funcName = 'is' + key,
funcCode = matchFunction(source, funcName);
if (funcCode) {
if (!snippet) {
// use snippet to mark the insert position
snippet = funcCode;
}
funcNames.push(funcName);
objectSnippets.push("'" + key + "': " + value);
}
});
// skip this optimization if there are less than 2 isType functions
if (funcNames.length < 2) {
return;
}
// add a token to mark the position to insert new code
source = source.replace(snippet, '\n' + token + '\n' + snippet);
// remove existing isType functions
funcNames.forEach(function(funcName) {
source = removeFunction(source, funcName);
});
// replace token with new DRY code
source = source.replace(token,
' // add `_.' + funcNames.join('`, `_.') + '`\n' +
' ' + iteratorName + '({\n ' + objectSnippets.join(',\n ') + '\n }, function(className, key) {\n' +
" lodash['is' + key] = function(value) {\n" +
' return toString.call(value) == className;\n' +
' };\n' +
' });'
);
// tweak `isArguments` fallback
snippet = !isLegacy && getIsArgumentsFallback(source);
if (snippet) {
var modified = '\n' + snippet.replace(/isArguments/g, 'lodash.$&');
source = source.replace(snippet, modified);
}
}());
/*--------------------------------------------------------------------------*/
if (isLegacy) {
['isBindFast', 'nativeBind', 'nativeIsArray', 'nativeKeys'].forEach(function(varName) {
source = removeVar(source, varName);
});
['bind', 'isArray'].forEach(function(funcName) {
var snippet = matchFunction(source, funcName),
modified = snippet;
// remove native `Function#bind` branch in `_.bind`
if (funcName == 'bind' ) {
modified = modified.replace(/(?:\s*\/\/.*)*\s*else if *\(isBindFast[^}]+}/, '');
}
// remove native `Array.isArray` branch in `_.isArray`
else if (funcName == 'isArray') {
modified = modified.replace(/nativeIsArray * \|\|/, '');
}
source = source.replace(snippet, modified);
});
// replace `_.keys` with `shimKeys`
if (!isRemoved(source, 'keys')) {
source = source.replace(
matchFunction(source, 'keys').replace(/[\s\S]+?var keys *=/, ''),
matchFunction(source, 'shimKeys').replace(/[\s\S]+?var shimKeys *=/, '')
);
source = removeFunction(source, 'shimKeys');
}
// replace `_.isArguments` with fallback
if (!isRemoved(source, 'isArguments')) {
source = source.replace(
matchFunction(source, 'isArguments').replace(/[\s\S]+?var isArguments *=/, ''),
getIsArgumentsFallback(source).match(/isArguments *=([\s\S]+?) *};/)[1] + ' };\n'
);
source = removeIsArgumentsFallback(source);
}
source = removeVar(source, 'reNative');
source = removeFromCreateIterator(source, 'nativeKeys');
source = removeKeysOptimization(source);
}
if (isMobile) {
// inline all functions defined with `createIterator`
lodash.functions(lodash).forEach(function(funcName) {
// match `funcName` with pseudo private `_` prefixes removed to allow matching `shimKeys`
var reFunc = RegExp('(\\bvar ' + funcName.replace(/^_/, '') + ' *= *)createIterator\\(((?:{|[a-zA-Z])[\\s\\S]+?)\\);\\n');
// skip if not defined with `createIterator`
if (!reFunc.test(source)) {
return;
}
// extract, format, and inject the compiled function's source code
source = source.replace(reFunc, '$1' + getFunctionSource(lodash[funcName]) + ';\n');
});
// remove JScript [[DontEnum]] fix from `_.isEqual`
source = source.replace(/(?:\s*\/\/.*)*\n( +)if *\(result *&& *hasDontEnumBug[\s\S]+?\n\1}/, '');
// remove IE `shift` and `splice` fix from mutator Array functions mixin
source = source.replace(/(?:\s*\/\/.*)*\n( +)if *\(value.length *=== *0[\s\S]+?\n\1}/, '');
// remove `noCharByIndex` from `_.reduceRight`
source = source.replace(/noCharByIndex *&&[^:]+: *([^;]+)/g, '$1');
// remove `noArraySliceOnStrings` from `_.toArray`
source = source.replace(/noArraySliceOnStrings *\?[^:]+: *([^)]+)/g, '$1');
source = removeVar(source, 'extendIteratorOptions');
source = removeVar(source, 'hasDontEnumBug');
source = removeVar(source, 'iteratorTemplate');
source = removeVar(source, 'noArraySliceOnStrings');
source = removeVar(source, 'noCharByIndex');
source = removeIsArgumentsFallback(source);
source = removeKeysOptimization(source);
}
else {
// inline `iteratorTemplate` template
source = source.replace(/(( +)var iteratorTemplate *= *)[\s\S]+?\n\2.+?;\n/, (function() {
var snippet = getFunctionSource(lodash._iteratorTemplate);
// prepend data object references to property names to avoid having to
// use a with-statement
iteratorOptions.forEach(function(property) {
if (property == 'iteratee') {
// use a more fine-grained regexp for the `iteratee` property because
// it's a compiled variable as well as a data property
snippet = snippet.replace(/(__t *= *\( *)(iteratee)/, '$1obj.$2');
} else {
snippet = snippet.replace(RegExp('([^\\w.])\\b' + property + '\\b', 'g'), '$1obj.' + property);
}
});
// remove unnecessary code
snippet = snippet
.replace(/, *__t,[^;]+|function print[^}]+}/g, '')
.replace(/'(?:\\n|\s)+'/g, "''")
.replace(/__p *\+= *' *';/g, '')
.replace(/(__p *\+= *)' *' *\+/g, '$1')
.replace(/(\{) *;|; *(\})/g, '$1$2')
.replace(/\(\(__w?t *= *\( *([^)]+) *\)\) *== *null *\? *'' *: *__w?t\)/g, '$1');
// remove the with-statement
snippet = snippet.replace(/ *with *\(.+?\) *{/, '\n').replace(/}([^}]*}[^}]*$)/, '$1');
// minor cleanup
snippet = snippet
.replace(/obj *\|\| *\(obj *= *\{}\);/, '')
.replace(/var __p;\s*__p/, 'var __p');
// remove comments, including sourceURLs
snippet = snippet.replace(/\s*\/\/.*(?:\n|$)/g, '');
return '$1' + snippet + ';\n';
}()));
}
/*--------------------------------------------------------------------------*/
// remove pseudo private properties
source = source.replace(/(?:(?:\s*\/\/.*)*\s*lodash\._[^=]+=.+\n)+/g, '\n');
// cleanup code
source = source.replace(/^ *;\n/gm, '');
// begin the minification process
if (isCustom) {
fs.writeFileSync(path.join(__dirname, 'lodash.custom.js'), source);
if (filterType || isBackbone || isLegacy || isMobile) {
fs.writeFileSync(path.join(cwd, 'lodash.custom.js'), source);
minify(source, 'lodash.custom.min', function(result) {
fs.writeFileSync(path.join(__dirname, 'lodash.custom.min.js'), result);
fs.writeFileSync(path.join(cwd, 'lodash.custom.min.js'), result);
});
}
else {
minify(source, 'lodash.min', function(result) {
fs.writeFileSync(path.join(__dirname, 'lodash.min.js'), result);
fs.writeFileSync(path.join(cwd, 'lodash.min.js'), result);
});
}
}());

View File

@@ -35,8 +35,8 @@
/*--------------------------------------------------------------------------*/
/**
* The exposed `minify` function minifies a given `source` and invokes the
* `onComplete` callback when finished.
* 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.
@@ -58,7 +58,10 @@
function Minify(source, workingName, onComplete) {
// create the destination directory if it doesn't exist
if (!fs.existsSync(distPath)) {
fs.mkdirSync(distPath);
// avoid errors when called as a npm executable
try {
fs.mkdirSync(distPath);
} catch(e) { }
}
this.compiled = {};
@@ -291,17 +294,20 @@
name = this.workingName,
uglified = this.uglified;
// save the Closure Compiled version to disk
fs.writeFileSync(path.join(distPath, name + '.compiler.js'), compiled.source);
fs.writeFileSync(path.join(distPath, name + '.compiler.js.gz'), compiled.gzip);
// avoid errors when called as a npm executable
try {
// save the Closure Compiled version to disk
fs.writeFileSync(path.join(distPath, name + '.compiler.js'), compiled.source);
fs.writeFileSync(path.join(distPath, name + '.compiler.js.gz'), compiled.gzip);
// save the Uglified version to disk
fs.writeFileSync(path.join(distPath, name + '.uglify.js'), uglified.source);
fs.writeFileSync(path.join(distPath, name + '.uglify.js.gz'), uglified.gzip);
// save the Uglified version to disk
fs.writeFileSync(path.join(distPath, name + '.uglify.js'), uglified.source);
fs.writeFileSync(path.join(distPath, name + '.uglify.js.gz'), uglified.gzip);
// save the hybrid minified version to disk
fs.writeFileSync(path.join(distPath, name + '.hybrid.js'), hybrid.source);
fs.writeFileSync(path.join(distPath, name + '.hybrid.js.gz'), hybrid.gzip);
// save the hybrid minified version to disk
fs.writeFileSync(path.join(distPath, name + '.hybrid.js'), hybrid.source);
fs.writeFileSync(path.join(distPath, name + '.hybrid.js.gz'), hybrid.gzip);
} catch(e) { }
// select the smallest gzipped file and use its minified counterpart as the
// official minified release (ties go to Closure Compiler)
@@ -324,9 +330,9 @@
module.exports = minify;
}
else {
// read the JavaScript source file from the first argument if the script
// read the Lo-Dash source file from the first argument if the script
// was invoked directly (e.g. `node minify.js source.js`) and write to
// the same file
// `<filename>.min.js`
(function() {
var filePath = process.argv[2],
dirPath = path.dirname(filePath),
@@ -334,7 +340,7 @@
workingName = path.basename(filePath, '.js') + '.min';
minify(source, workingName, function(result) {
fs.writeFileSync(path.join(dirPath, workingName + '.js'), result);
fs.writeFileSync(path.join(dirPath, workingName + '.js'), result, 'utf8');
});
}());
}

View File

@@ -15,7 +15,7 @@
/*--------------------------------------------------------------------------*/
/**
* Post-process a given minified JavaScript `source`, preparing it for
* Post-process a given minified Lo-Dash `source`, preparing it for
* deployment.
*
* @param {String} source The source to process.
@@ -56,7 +56,7 @@
if (module != require.main) {
module.exports = postprocess;
} else {
// read the JavaScript source file from the first argument if the script
// read the Lo-Dash source file from the first argument if the script
// was invoked directly (e.g. `node post-compile.js source.js`) and write to
// the same file
(function() {

View File

@@ -9,29 +9,30 @@
var compiledVars = [
'accumulator',
'args',
'array',
'arrayClass',
'bind',
'callback',
'className',
'collection',
'concat',
'compareAscending',
'ctor',
'false',
'funcClass',
'hasOwnProperty',
'identity',
'index',
'indexOf',
'isArray',
'isEmpty',
'isFunc',
'iteratee',
'iteratorBind',
'length',
'methodName',
'nativeKeys',
'noaccum',
'object',
'objectTypes',
'noaccum',
'prop',
'propIndex',
'props',
'property',
'propertyIsEnumerable',
'result',
'skipProto',
'slice',
@@ -41,8 +42,6 @@
'target',
'thisArg',
'toString',
'true',
'undefined',
'value'
];
@@ -55,14 +54,14 @@
'bottom',
'exit',
'firstArg',
'hasExp',
'hasDontEnumBug',
'inLoop',
'init',
'iteratedObject',
'loopExp',
'isKeysFast',
'iteratee',
'object',
'objectBranch',
'noCharByIndex',
'shadowed',
'top',
'useHas'
@@ -71,14 +70,16 @@
/** Used to minify variables and string values to a single character */
var minNames = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
/** Used protect the specified properties from getting minified */
/** Used to protect the specified properties from getting minified */
var propWhitelist = [
'_',
'_chain',
'_wrapped',
'after',
'all',
'amd',
'any',
'attachEvent',
'bind',
'bindAll',
'chain',
@@ -109,6 +110,8 @@
'foldl',
'foldr',
'forEach',
'forIn',
'forOwn',
'functions',
'groupBy',
'has',
@@ -119,7 +122,6 @@
'initial',
'inject',
'interpolate',
'intersect',
'intersection',
'invoke',
'isArguments',
@@ -177,7 +179,6 @@
'throttle',
'times',
'toArray',
'toArray',
'union',
'uniq',
'unique',
@@ -188,13 +189,14 @@
'VERSION',
'without',
'wrap',
'zip'
'zip',
'zipObject'
];
/*--------------------------------------------------------------------------*/
/**
* Pre-process a given JavaScript `source`, preparing it for minification.
* Pre-process a given Lo-Dash `source`, preparing it for minification.
*
* @param {String} source The source to process.
* @returns {String} Returns the processed source.
@@ -203,48 +205,92 @@
// remove copyright to add later in post-compile.js
source = source.replace(/\/\*![\s\S]+?\*\//, '');
// correct JSDoc tags for Closure Compiler
// remove unrecognized JSDoc tags so Closure Compiler won't complain
source = source.replace(/@(?:alias|category)\b.*/g, '');
// 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']");
// remove brackets from `_.escape()` in `tokenizeEscape`
source = source.replace(/_\['escape']\("/, '_.escape("');
// remove brackets from `_.escape()` in `_.template`
source = source.replace(/__e *= *_\['escape']/, '__e=_.escape');
// remove brackets from `collection.indexOf` in `_.contains`
source = source.replace("collection['indexOf'](target)", 'collection.indexOf(target)');
// remove brackets from `result[length].value` in `_.sortBy`
source = source.replace("result[length]['value']", 'result[length].value');
// remove whitespace from string literals
source = source.replace(/'(?:(?=(\\?))\1.)*?'/g, function(string) {
// avoids removing the '\n' of the `escapes` object
return string.replace(/\[object |else if|function | in |return\s+[\w']|throw |typeof |use strict|var |'\\n'|\\\\n|\\n|\s+/g, function(match) {
// avoids removing the '\n' of the `stringEscapes` object
return string.replace(/\[object |else if|function | in |return\s+[\w']|throw |typeof |use strict|var |@ |'\\n'|\\\\n|\\n|\s+/g, function(match) {
return match == false || match == '\\n' ? '' : match;
});
});
// minify `_.sortBy` internal properties
// remove whitespace from `_.template` related regexpes
source = source.replace(/(?:reDelimiterCode\w+|reEmptyString\w+|reInsertVariable) *=.+/g, function(match) {
return match.replace(/ |\\n/g, '');
});
// remove newline from double-quoted strings in `_.template`
source = source
.replace('"\';\\n__with ("', '"\';__with("')
.replace('"\\n}__\\n__p += \'"', '"}____p+=\'"')
.replace('"__p = \'"', '"__p=\'"')
.replace('"\';\\n"', '"\';"')
.replace("') {\\n'", "'){'")
// remove `useSourceURL` variable
source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *try *\{(?:\s*\/\/.*\n)* *var useSourceURL[\s\S]+?catch[^}]+}\n/, '');
// remove debug sourceURL use in `_.template`
source = source.replace(/(?:\s*\/\/.*\n)* *if *\(useSourceURL[^}]+}/, '');
// minify internal properties used by `_.sortBy`
(function() {
var properties = ['criteria', 'value'],
snippet = (source.match(/( +)function sortBy\b[\s\S]+?\n\1}/) || 0)[0],
result = snippet;
snippets = source.match(/( +)(?:function compareAscending|var sortBy)\b[\s\S]+?\n\1}/g);
if (snippet) {
// minify property strings
if (!snippets) {
return;
}
snippets.forEach(function(snippet) {
var modified = snippet,
isSortBy = /var sortBy\b/.test(modified),
isInlined = !/\bcreateIterator\b/.test(modified);
// minify properties
properties.forEach(function(property, index) {
result = result.replace(RegExp("'" + property + "'", 'g'), "'" + minNames[index] + "'");
// add quotes around properties in the inlined `_.sortBy` of the mobile
// build so Closure Compiler won't mung them
if (isSortBy && isInlined) {
modified = modified
.replace(RegExp('\\.' + property + '\\b', 'g'), "['" + minNames[index] + "']")
.replace(RegExp('\\b' + property + ' *:', 'g'), "'" + minNames[index] + "':");
}
modified = modified.replace(RegExp('\\b' + property + '\\b', 'g'), minNames[index]);
});
// replace with modified snippet
source = source.replace(snippet, result);
}
source = source.replace(snippet, modified);
});
}());
// minify all compilable snippets
var snippets = source.match(
RegExp([
// match the `iteratorTemplate`
'var iteratorTemplate\\b[\\s\\S]+?\\);\\n',
'( +)var iteratorTemplate\\b[\\s\\S]+?\\n\\1}',
// match methods created by `createIterator` calls
'createIterator\\((?:{|[a-zA-Z]+)[\\s\\S]+?\\);\\n',
// match variables storing `createIterator` options
'( +)var [a-zA-Z]+IteratorOptions\\b[\\s\\S]+?\\n\\1}',
'( +)var [a-zA-Z]+IteratorOptions\\b[\\s\\S]+?\\n\\2}',
// match the the `createIterator` function
'( +)function createIterator\\b[\\s\\S]+?\\n\\2}'
'( +)function createIterator\\b[\\s\\S]+?\\n\\3}'
].join('|'), 'g')
);
@@ -256,38 +302,26 @@
snippets.forEach(function(snippet, index) {
var isCreateIterator = /function createIterator\b/.test(snippet),
isIteratorTemplate = /var iteratorTemplate\b/.test(snippet),
result = snippet;
modified = snippet;
// add brackets to whitelisted properties so Closure Compiler won't mung them
result = result.replace(RegExp('\\.(' + iteratorOptions.join('|') + ')\\b', 'g'), "['$1']");
modified = modified.replace(RegExp('\\.(' + iteratorOptions.join('|') + ')\\b', 'g'), "['$1']");
if (isCreateIterator) {
// add `true` and `false` arguments to be minified
result = result
.replace(/(Function\(\s*'[\s\S]+?)undefined/, '$1true,false,undefined')
.replace(/factory\([^)]+/, '$&,true,false');
// replace with modified snippet early and clip snippet so other arguments
// aren't minified
source = source.replace(snippet, result);
snippet = result = result.replace(/factory\([\s\S]+$/, '');
source = source.replace(snippet, modified);
snippet = modified = modified.replace(/factory\([\s\S]+$/, '');
}
// minify snippet variables / arguments
compiledVars.forEach(function(variable, index) {
// ensure properties in compiled strings aren't minified
result = result.replace(RegExp('([^.]\\b)' + variable + '\\b(?!\' *[\\]:])', 'g'), '$1' + minNames[index]);
modified = modified.replace(RegExp('([^.]\\b)' + variable + '\\b(?!\' *[\\]:])', 'g'), '$1' + minNames[index]);
// correct `typeof x == 'object'`
if (variable == 'object') {
result = result.replace(RegExp("(typeof [^']+')" + minNames[index] + "'", 'g'), "$1object'");
}
// correct external boolean literals
else if (variable == 'true' || variable == 'false') {
result = result
.replace(RegExp(': *' + minNames[index] + ',', 'g'), ':' + variable + ',')
.replace(RegExp('\\b' + minNames[index] + ';', 'g'), variable + ';');
modified = modified.replace(RegExp("(typeof [^']+')" + minNames[index] + "'", 'g'), "$1object'");
}
});
@@ -295,26 +329,26 @@
iteratorOptions.forEach(function(property, index) {
if (isIteratorTemplate) {
// minify property names as interpolated template variables
result = result.replace(RegExp('\\b' + property + '\\b', 'g'), minNames[index]);
modified = modified.replace(RegExp('\\b' + property + '\\b', 'g'), minNames[index]);
}
else {
if (property == 'array' || property == 'object') {
// minify "array" and "object" sub property names
result = result.replace(RegExp("'" + property + "'( *[\\]:])", 'g'), "'" + minNames[index] + "'$1");
modified = modified.replace(RegExp("'" + property + "'( *[\\]:])", 'g'), "'" + minNames[index] + "'$1");
}
else {
// minify property name strings
result = result.replace(RegExp("'" + property + "'", 'g'), "'" + minNames[index] + "'");
modified = modified.replace(RegExp("'" + property + "'", 'g'), "'" + minNames[index] + "'");
// minify property names in regexps and accessors
if (isCreateIterator) {
result = result.replace(RegExp('([\\.|/])' + property + '\\b' , 'g'), '$1' + minNames[index]);
modified = modified.replace(RegExp('([\\.|/])' + property + '\\b' , 'g'), '$1' + minNames[index]);
}
}
}
});
// replace with modified snippet
source = source.replace(snippet, result);
source = source.replace(snippet, modified);
});
return source;
@@ -327,7 +361,7 @@
module.exports = preprocess;
}
else {
// read the JavaScript source file from the first argument if the script
// read the Lo-Dash source file from the first argument if the script
// was invoked directly (e.g. `node pre-compile.js source.js`) and write to
// the same file
(function() {

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.2.0</sup>',
'title' => 'Lo-Dash <sup>v0.4.0</sup>',
'url' => 'https://github.com/bestiejs/lodash/blob/master/lodash.js'
));

2206
lodash.js

File diff suppressed because it is too large Load Diff

59
lodash.min.js vendored
View File

@@ -1,30 +1,35 @@
/*!
Lo-Dash 0.2.0 lodash.com/license
Lo-Dash 0.4.0 lodash.com/license
Underscore.js 1.3.3 github.com/documentcloud/underscore/blob/master/LICENSE
*/
;(function(u,m){"use strict";function R(a){return"[object Arguments]"==h.call(a)}function c(a){return new p(a)}function p(a){if(a&&a._wrapped)return a;this._wrapped=a}function j(){for(var a,b,d,k=-1,c=arguments.length,f={e:"",f:"",k:"",q:"",c:{m:"++o<t"},o:{}};++k<c;)for(b in a=arguments[k],a)d=(d=a[b])==o?"":d,/d|m|j/.test(b)?("string"==typeof d&&(d={b:d,n:d}),f.c[b]=d.b,f.o[b]=d.n):f[b]=d;a=f.a,b=f.c,d=f.o;var k=/^[^,]+/.exec(a)[0],c=d.m,g=/\S+$/.exec(c||k)[0];f.g=k,f.i=H,f.h="m.call("+g+",o)",f
.l=g,f.p=ba,f.r=f.r!==r,f.f||(f.f="if("+k+"==null)return z");if("u"==k||!b.j)f.c=o;return c||(d.m="o in "+g),Function("d,e,i,l,m,n,p,q,r,v,B,E,H,I,k,J",'"use strict";return function('+a+"){"+ta(f)+"}")(I,v,D,s,t,ca,w,J,da,S,l,K,h,n,r)}function ua(a,b){return x[b]}function va(a){return"\\"+wa[a]}function xa(a,b){var d=x.length;return x[d]="'+((__t=("+b+"))==null?'':_['escape'](__t))+'",T+d}function ya(a,b){var d=x.length;return x[d]="'+((__t=("+b+"))==null?'':__t)+'",T+d}function za(a,b){var d=x.length
;return x[d]="';"+b+";__p+='",T+d}function ea(a,b,d,c){if(!a)return d;var e=a.length,f=3>arguments.length;c&&(b=v(b,c));if(e===+e){for(e&&f&&(d=a[--e]);e--;)d=b(d,a[e],e,a);return d}var g=U(a);for((e=g.length)&&f&&(d=a[g[--e]]);e--;)f=g[e],d=b(d,a[f],f,a);return d}function V(a,b,d){return b==m||d?a[0]:l.call(a,0,b)}function fa(a,b){if(b)return D.apply(z,a);for(var d,c=-1,e=a.length,f=[];++c<e;)d=a[c],J(d)?E.apply(f,fa(d)):f.push(d);return f}function w(a,b,d){var c;if(!a)return-1;if(d)return d=ga(
a,b),a[d]===b?d:-1;d=0;for(c=a.length;d<c;d++)if(a[d]===b)return d;return-1}function ha(a){for(var b,d=-1,c=a.length,e=l.call(arguments,1),f=[];++d<c;)b=a[d],0>w(f,b)&&W(e,function(a){return-1<w(a,b)})&&f.push(b);return f}function ia(a,b,d){var c=-Infinity,e=-1,f=a.length,g=c;if(b)d&&(b=v(b,d));else{if(a[0]===+a[0]&&f<=L)try{return Math.max.apply(Math,a)}catch(i){}if(!a.length)return g}for(;++e<f;)d=b?b(a[e],e,a):a[e],d>c&&(c=d,g=a[e]);return g}function ja(a,b,d){return l.call(a,b==m||d?1:b)}function ga
(a,b,d){var c,e=0,f=a.length;for(d&&(b=d(b));e<f;)c=e+f>>1,(d?d(a[c]):a[c])<b?e=c+1:f=c;return e}function ka(a,b,d){var c,e=-1,f=a.length,g=[],i=[];for(3>f&&(b=n);++e<f;)if(c=d?d(a[e]):a[e],b?!e||i[i.length-1]!==c:0>w(i,c))i.push(c),g.push(a[e]);return g}function v(a,b){var d,c=h.call(a)==s;if(c){if(B)return a=B.call.apply(B,arguments),function(){return arguments.length?a.apply(m,arguments):a()}}else d=b,b=a;var e=l.call(arguments,2),f=e.length;return function(){var g;return g=arguments,c||(a=b[d
]),f&&(g.length&&(e.length=f,E.apply(e,g)),g=e),g=g.length?a.apply(b,g):a.call(b),e.length=f,g}}function M(a,b,d){d||(d=[]);if(a===b)return 0!==a||1/a==1/b;if(a==m||b==m)return a===b;a.s&&(a=a._wrapped),b.s&&(b=b._wrapped);if(a.isEqual&&h.call(a.isEqual)==s)return a.isEqual(b);if(b.isEqual&&h.call(b.isEqual)==s)return b.isEqual(a);var c=h.call(a);if(c!=h.call(b))return r;switch(c){case K:return a==""+b;case N:return a!=+a?b!=+b:0==a?1/a==1/b:a==+b;case la:case ma:return+a==+b;case na:return a.source==
b.source&&a.global==b.global&&a.multiline==b.multiline&&a.ignoreCase==b.ignoreCase}if("object"!=typeof a||"object"!=typeof b)return r;for(var e=d.length;e--;)if(d[e]==a)return n;var e=-1,f=n,g=0;d.push(a);if(c==I){if(g=a.length,f=g==b.length)for(;g--&&(f=M(a[g],b[g],d)););}else{if("constructor"in a!="constructor"in b||a.constructor!=b.constructor)return r;for(var i in a)if(t.call(a,i)&&(g++,!(f=t.call(b,i)&&M(a[i],b[i],d))))break;if(f){for(i in b)if(t.call(b,i)&&!(g--))break;f=!g}if(f&&H)for(;7>++
e&&(i=ba[e],!t.call(a,i)||!!(f=t.call(b,i)&&M(a[i],b[i],d))););}return d.pop(),f}function ca(a){return a}function oa(a){F(O(a),function(b){var d=c[b]=a[b];p.prototype[b]=function(){var a=[this._wrapped];return arguments.length&&E.apply(a,arguments),a=1==a.length?d.call(c,a[0]):d.apply(c,a),this.s&&(a=new p(a),a.s=n),a}})}var n=!0,o=null,r=!1,X="object"==typeof exports&&exports&&("object"==typeof global&&global&&global==global.global&&(u=global),exports),L=5e4;try{(function(){L=arguments.length}).
apply(o,Array(L))}catch(Ha){}var wa={"\\":"\\","'":"'","\n":"n","\r":"r"," ":"t","\u2028":"u2028","\u2029":"u2029"},H=!{valueOf:0}.propertyIsEnumerable("valueOf"),Aa=0,S={"boolean":r,"function":n,object:n,number:r,string:r,"undefined":r},Ba=u._,A=RegExp("^"+({}.valueOf+"").replace(/[.*+?^=!:${}()|[\]\/\\]/g,"\\$&").replace(/valueOf|for [^\]]+/g,".+?")+"$"),Ca=/__token__(\d+)/g,Da=/['\n\r\t\u2028\u2029\\]/g,ba="constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf"
.split(" "),T="__token__",x=[],I="[object Array]",la="[object Boolean]",ma="[object Date]",s="[object Function]",N="[object Number]",na="[object RegExp]",K="[object String]",z=Array.prototype,y=Object.prototype,D=z.concat,t=y.hasOwnProperty,E=z.push,l=z.slice,h=y.toString,B=A.test(B=l.bind)&&/\n|Opera/.test(B+h.call(u.opera))&&B,C=A.test(C=Array.isArray)&&C,Ea=u.isFinite,Y=A.test(Y=Object.keys)&&Y,Fa=u.clearTimeout,P=u.setTimeout,ta=Function("u","var __p;with(u){__p='var o,z';if(k){__p+='='+k};__p+=';'+f+';'+q+';';if(c){__p+='var t='+g+'.length;o=-1;';if(o){__p+='if(t===+t){'};__p+=''+c['d']+';while('+c['m']+'){'+c['j']+'}';if(o){__p+='}'}}if(o){if(c){__p+='else{'}if(!i){__p+='var A=typeof '+l+'==\\'function\\';'};__p+=''+o['d']+';for('+o['m']+'){';if(i){if(r){__p+='if('+h+'){'};__p+=''+o['j']+';';if(r){__p+='}'}}else{__p+='if(!(A&&o==\\'prototype\\')';if(r){__p+='&&'+h};__p+='){'+o['j']+'}'};__p+='}';if(i){__p+='var j='+l+'.constructor;';for(var k=0;k<7;k++){__p+='o=\\''+p[k]+'\\';if(';if(p[k]=='constructor'){__p+='!(j&&j.prototype==='+l+')&&'};__p+=''+h+'){'+o['j']+'}'}}if(c){__p+='}'}};__p+=''+e+';return z'}return __p"
),q={a:"h,f,G",k:"h",q:"if(!f){f=n}else if(G){f=e(f,G)}",j:"f(h[o],o,h)"},Z={k:"I",j:"if(!f(h[o],o,h))return!z"},A={a:"u",k:"u",q:"for(var C,D=1,t=arguments.length;D<t;D++){C=arguments[D];"+(H?"if(C){":""),m:"o in C",r:r,j:"u[o]=C[o]",e:(H?"}":"")+"}"},G={k:"[]",j:"f(h[o],o,h)&&z.push(h[o])"},y={k:"",f:"if(!h)return[]",d:{b:"z=Array(t)",n:"z=[]"},j:{b:"z[o]=f(h[o],o,h)",n:"z.push(f(h[o],o,h))"}},J=C||function(a){return h.call(a)==I},da=j({a:"K",k:"I",q:"var g=H.call(K);if(g==d||g==E)return!K.length"
,j:{n:"return k"}}),C=j({a:"h,F",k:"k",j:"if(h[o]===F)return I"}),W=j(q,Z),pa=j(q,G),qa=j(q,{j:"if(f(h[o],o,h))return h[o]"}),F=j(q,{q:"if(G)f=e(f,G)"}),Ga=j(q,{k:"{}",q:"var x,s=H.call(f)==l;if(s&&G)f=e(f,G)",j:"x=s?f(h[o],o,h):h[o][f];(z[x]||(z[x]=[])).push(h[o])"}),$=j(q,y),Q=j(y,{a:"h,y",j:{b:"z[o]=h[o][y]",n:"z.push(h[o][y])"}}),aa=j({a:"h,f,a,G",k:"a",q:"var w=arguments.length<3;if(G)f=e(f,G)",d:{b:"if(w)z=h[++o]"},j:{b:"z=f(z,h[o],o,h)",n:"z=w?(w=k,h[o]):f(z,h[o],o,h)"}}),G=j(q,G,{j:"!"+G.
j}),q=j(q,Z,{k:"k",j:Z.j.replace("!","")}),ra=j(y,{a:"h",j:{b:"z[o]=h[o]",n:"z.push(h[o])"}}),y=j(A,{j:"if(u[o]==J)"+A.j}),sa=j(A),O=j({a:"u",k:"[]",r:r,j:"if(H.call(u[o])==l)z.push(o)",e:"z.sort()"});R(arguments)||(R=function(a){return!!a&&!!t.call(a,"callee")});var U=Y||j({a:"u",f:"if(!v[typeof u]||u===null)throw TypeError()",k:"[]",j:"z.push(o)"});c.VERSION="0.2.0",c.templateSettings={escape:/<%-([\s\S]+?)%>/g,evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,variable:"object"},c.after=
function(a,b){return 1>a?b():function(){if(1>--a)return b.apply(this,arguments)}},c.bind=v,c.bindAll=function(a){var b=arguments,d=1;1==b.length&&(d=0,b=O(a));for(var c=b.length;d<c;d++)a[b[d]]=v(a[b[d]],a);return a},c.chain=function(a){return a=new p(a),a.s=n,a},c.clone=function(a){return S[typeof a]&&a!==o?J(a)?a.slice():sa({},a):a},c.compact=function(a){for(var b=-1,d=a.length,c=[];++b<d;)a[b]&&c.push(a[b]);return c},c.compose=function(){var a=arguments;return function(){for(var b=arguments,d=
a.length;d--;)b=[a[d].apply(this,b)];return b[0]}},c.contains=C,c.debounce=function(a,b,d){function c(){i=m,d||a.apply(g,e)}var e,f,g,i;return function(){var h=d&&!i;return e=arguments,g=this,Fa(i),i=P(c,b),h&&(f=a.apply(g,e)),f}},c.defaults=y,c.defer=function(a){var b=l.call(arguments,1);return P(function(){return a.apply(m,b)},1)},c.delay=function(a,b){var d=l.call(arguments,2);return P(function(){return a.apply(m,d)},b)},c.difference=function(a){for(var b=-1,d=a.length,c=[],e=D.apply(c,l.call(
arguments,1));++b<d;)0>w(e,a[b])&&c.push(a[b]);return c},c.escape=function(a){return(a+"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;").replace(/\//g,"&#x2F;")},c.every=W,c.extend=sa,c.filter=pa,c.find=qa,c.first=V,c.flatten=fa,c.forEach=F,c.functions=O,c.groupBy=Ga,c.has=function(a,b){return t.call(a,b)},c.identity=ca,c.indexOf=w,c.initial=function(a,b,d){return l.call(a,0,-(b==m||d?1:b))},c.intersection=ha,c.invoke=function(a,b){for(var d=l.call(arguments
,2),c=-1,e=a.length,f=h.call(b)==s,g=[];++c<e;)g[c]=(f?b:a[c][b]).apply(a[c],d);return g},c.isArguments=R,c.isArray=J,c.isBoolean=function(a){return a===n||a===r||h.call(a)==la},c.isDate=function(a){return h.call(a)==ma},c.isElement=function(a){return!!a&&1==a.nodeType},c.isEmpty=da,c.isEqual=M,c.isFinite=function(a){return Ea(a)&&h.call(a)==N},c.isFunction=function(a){return h.call(a)==s},c.isNaN=function(a){return h.call(a)==N&&a!=+a},c.isNull=function(a){return a===o},c.isNumber=function(a){return h
.call(a)==N},c.isObject=function(a){return S[typeof a]&&a!==o},c.isRegExp=function(a){return h.call(a)==na},c.isString=function(a){return h.call(a)==K},c.isUndefined=function(a){return a===m},c.keys=U,c.last=function(a,b,d){var c=a.length;return b==m||d?a[c-1]:l.call(a,-b||c)},c.lastIndexOf=function(a,b){if(!a)return-1;for(var d=a.length;d--;)if(a[d]===b)return d;return-1},c.map=$,c.max=ia,c.memoize=function(a,b){var d={};return function(){var c=b?b.apply(this,arguments):arguments[0];return t.call
(d,c)?d[c]:d[c]=a.apply(this,arguments)}},c.min=function(a,b,d){var c=Infinity,e=-1,f=a.length,g=c;if(b)d&&(b=v(b,d));else{if(a[0]===+a[0]&&f<=L)try{return Math.min.apply(Math,a)}catch(i){}if(!a.length)return g}for(;++e<f;)d=b?b(a[e],e,a):a[e],d<c&&(c=d,g=a[e]);return g},c.mixin=oa,c.noConflict=function(){return u._=Ba,this},c.once=function(a){var b,d=r;return function(){return d?b:(d=n,b=a.apply(this,arguments))}},c.partial=function(a){var b=l.call(arguments,1),d=b.length;return function(){var c
;return c=arguments,c.length&&(b.length=d,E.apply(b,c)),c=1==b.length?a.call(this,b[0]):a.apply(this,b),b.length=d,c}},c.pick=function(a){for(var b,d=0,c=D.apply(z,arguments),e=c.length,f={};++d<e;)b=c[d],b in a&&(f[b]=a[b]);return f},c.pluck=Q,c.range=function(a,b,d){d||(d=1),2>arguments.length&&(b=a||0,a=0);for(var c=-1,e=Math.max(Math.ceil((b-a)/d),0),f=Array(e);++c<e;)f[c]=a,a+=d;return f},c.reduce=aa,c.reduceRight=ea,c.reject=G,c.rest=ja,c.result=function(a,b){if(!a)return o;var d=a[b];return h
.call(d)==s?a[b]():d},c.shuffle=function(a){for(var b,d=-1,c=a.length,e=Array(c);++d<c;)b=Math.floor(Math.random()*(d+1)),e[d]=e[b],e[b]=a[d];return e},c.size=function(a){var b=h.call(a);return b==I||b==K?a.length:U(a).length},c.some=q,c.sortBy=function(a,b,d){if(h.call(b)!=s)var c=b,b=function(a){return a[c]};else d&&(b=v(b,d));return Q($(a,function(d,c){return{a:b(d,c,a),b:d}}).sort(function(a,b){var d=a.a,c=b.a;return d===m?1:c===m?-1:d<c?-1:d>c?1:0}),"b")},c.sortedIndex=ga,c.tap=function(a,b)
{return b(a),a},c.template=function(a,b,d){d||(d={});var k;k=c.templateSettings||{};var e=d.escape,f=d.evaluate,g=d.interpolate,d=d.variable;return e==o&&(e=k.escape),f==o&&(f=k.evaluate),g==o&&(g=k.interpolate),e&&(a=a.replace(e,xa)),g&&(a=a.replace(g,ya)),f&&(a=a.replace(f,za)),a="__p='"+a.replace(Da,va).replace(Ca,ua)+"';\n",x.length=0,d||(d=k.variable||"object",a="with("+d+"||{}){"+a+"}"),a="function("+d+"){var __p,__t,__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}"+a+"return __p}"
,k=Function("_","return "+a)(c),b?k(b):(k.source=a,k)},c.throttle=function(a,b){function d(){i=new Date,g=m,a.apply(f,c)}var c,e,f,g,i=0;return function(){var h=new Date,j=b-(h-i);return c=arguments,f=this,0>=j?(i=h,e=a.apply(f,c)):g||(g=P(d,j)),e}},c.times=function(a,b,d){d&&(b=v(b,d));for(d=0;d<a;d++)b(d)},c.toArray=function(a){if(!a)return[];if(h.call(a.toArray)==s)return a.toArray();var b=a.length;return b===+b?l.call(a):ra(a)},c.union=function(){for(var a=-1,b=[],d=D.apply(b,arguments),c=d.length
;++a<c;)0>w(b,d[a])&&b.push(d[a]);return b},c.uniq=ka,c.uniqueId=function(a){var b=Aa++;return a?a+b:b},c.values=ra,c.without=function(a){for(var b=l.call(arguments,1),d=-1,c=a.length,e=[];++d<c;)0>w(b,a[d])&&e.push(a[d]);return e},c.wrap=function(a,b){return function(){var c=[a];return arguments.length&&E.apply(c,arguments),b.apply(this,c)}},c.zip=function(){for(var a=-1,b=ia(Q(arguments,"length")),c=Array(b);++a<b;)c[a]=Q(arguments,a);return c},c.all=W,c.any=q,c.collect=$,c.detect=qa,c.each=F,c
.foldl=aa,c.foldr=ea,c.head=V,c.include=C,c.inject=aa,c.intersect=ha,c.methods=O,c.select=pa,c.tail=ja,c.take=V,c.unique=ka,p.prototype=c.prototype,oa(c),p.prototype.chain=function(){return this.s=n,this},p.prototype.value=function(){return this._wrapped},F("pop push reverse shift sort splice unshift".split(" "),function(a){var b=z[a];p.prototype[a]=function(){var a=this._wrapped;return arguments.length?b.apply(a,arguments):b.call(a),a.length===0&&delete a[0],this.s&&(a=new p(a),a.s=n),a}}),F(["concat"
,"join","slice"],function(a){var b=z[a];p.prototype[a]=function(){var a=this._wrapped,a=arguments.length?b.apply(a,arguments):b.call(a);return this.s&&(a=new p(a),a.s=n),a}}),X?"object"==typeof module&&module&&module.t==X?(module.t=c)._=c:X._=c:(u._=c,typeof define=="function"&&typeof define.amd=="object"&&define.amd&&define(function(){return c}))})(this);
;(function(e,t){"use strict";function s(e){return new o(e)}function o(e){if(e&&e._wrapped)return e;this._wrapped=e}function u(){for(var e,t,n,s=-1,o=arguments.length,u={e:"",f:"",j:"",q:"",c:{d:""},n:{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,m:n}),u.c[t]=n.b,u.n[t]=n.m):u[t]=n;e=u.a,t=/^[^,]+/.exec(e)[0],u.l=u.l||t,u.g=t,u.h=yt,u.k=St,u.p=Q,u.r=u.r!==i,"o"in u||(u.o=wt),u.f||(u.f="if(!"+t+")return A");if("f"!=t||!u.c.i)u.c=r;t="var A",u.
j&&(t+="="+u.j),t+=";"+u.f+";"+u.q+";var l,n="+u.l+";",u.c&&(t+="var p=n.length;l=-1;",u.n&&(t+="if(p===p>>>0){"),u.o&&(t+="if(I.call(n)==F){n=n.split('')}"),t+=u.c.d+";while(++l<p){"+u.c.i+"}",u.n&&(t+="}"));if(u.n){u.c&&(t+="else{"),u.h||(t+="var B=typeof n=='function'&&z.call(n,'prototype');"),u.k&&u.r?t+="var x=r(n),w=-1,p=x.length;"+u.n.d+";while(++w<p){l=x[w];if(!(B&&l=='prototype')){"+u.n.i+"}}":(t+=u.n.d+";for(l in n){",u.h?(u.r&&(t+="if(j.call(n,l)){"),t+=u.n.i+";",u.r&&(t+="}")):(t+="if(!(B&&l=='prototype')"
,u.r&&(t+="&&j.call(n,l)"),t+="){"+u.n.i+"}"),t+="}");if(u.h){t+="var h=n.constructor;";for(n=0;7>n;n++)t+="l='"+u.p[n]+"';if(","constructor"==u.p[n]&&(t+="!(h&&h.prototype===n)&&"),t+="j.call(n,l)){"+u.n.i+"}"}u.c&&(t+="}")}return t+=u.e+";return A",Function("c,g,i,j,k,o,u,r,z,C,F,I",'"use strict";return function('+e+"){"+t+"}")(ft,a,ht,et,k,h,Tt,at,nt,rt,vt,it)}function a(e,n){return e=e.a,n=n.a,e===t?1:n===t?-1:e<n?-1:e>n?1:0}function f(e,t){return Y[t]}function l(e){return"\\"+Nt[e]}function c
(e){return xt[e]}function h(e,t){return function(n,r,i){return e.call(t,n,r,i)}}function p(){}function d(e,t){if(F.test(t))return"<e%-"+t+"%>";var n=Y.length;return Y[n]="'+__e("+t+")+'",G+n}function v(e,t,n,r){return e=Y.length,t?Y[e]="';"+t+";__p+='":n?Y[e]="'+__we("+n+")+'":r&&(Y[e]="'+((__wt=("+r+"))==null?'':__wt)+'"),G+e}function m(e,t){if(F.test(t))return"<e%="+t+"%>";var n=Y.length;return Y[n]="'+((__t=("+t+"))==null?'':__t)+'",G+n}function g(e,t,n,r){if(!e)return n;var i=e.length,s=3>arguments
.length;r&&(t=h(t,r));if(i===i>>>0){var o=wt&&it.call(e)==vt?e.split(""):e;for(i&&s&&(n=o[--i]);i--;)n=t(n,o[i],i,e);return n}o=en(e);for((i=o.length)&&s&&(n=e[o[--i]]);i--;)s=o[i],n=t(n,e[s],s,e);return n}function y(e,t,n){if(e)return t==r||n?e[0]:rt.call(e,0,t)}function b(e,t){var n=[];if(!e)return n;for(var r,i=-1,s=e.length;++i<s;)r=e[i],Yt(r)?tt.apply(n,t?r:b(r)):n.push(r);return n}function w(e,t,n){if(!e)return-1;var r=-1,i=e.length;if(n){if("number"!=typeof n)return r=x(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 E(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=h(t,n));++s<o;)n=t(e[s],s,e),n>r&&(r=n,i=e[s]);return i}function S(e,t,n){return e?rt.call(e,t==r||n?1:t):[]}function x(e,t,n,r){if(!e)return 0;var i=0,s=e.length;if(n){r&&(n=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 T(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=h(n,r)):n=k;++o<u;)if(r=n(e[o],o,e),t?!o||a[a.length-1]!==r:0>w(a,r))a.push(r),s.push(e[o]);return s}function N(e,t){function n(){var o=arguments,u=t;return i||(e=t[r]),s.length&&(o=o.length?Z.apply(s,o):s),this instanceof n?(p.prototype=e.prototype,u=new p,(o=e.apply(u,o))&&Tt[typeof o]?o:u):e.apply(u,o)}var r,i=it.call(e)==ht;if(i){if(Et||st&&2<arguments.length)return st.call.apply(st,arguments)}else r=t,
t=e;var s=rt.call(arguments,2);return n}function C(e,t,s){s||(s=[]);if(e===t)return 0!==e||1/e==1/t;if(e==r||t==r)return e===t;e._chain&&(e=e._wrapped),t._chain&&(t=t._wrapped);if(e.isEqual&&it.call(e.isEqual)==ht)return e.isEqual(t);if(t.isEqual&&it.call(t.isEqual)==ht)return t.isEqual(e);var o=it.call(e);if(o!=it.call(t))return i;switch(o){case vt:return e==""+t;case pt:return e!=+e?t!=+t:0==e?1/e==1/t:e==+t;case lt:case ct:return+e==+t;case dt:return e.source==t.source&&e.global==t.global&&e.multiline==
t.multiline&&e.ignoreCase==t.ignoreCase}if("object"!=typeof e||"object"!=typeof t)return i;for(var u=s.length;u--;)if(s[u]==e)return n;var u=-1,a=n,f=0;s.push(e);if(o==ft){if(f=e.length,a=f==t.length)for(;f--&&(a=C(e[f],t[f],s)););}else{if("constructor"in e!="constructor"in t||e.constructor!=t.constructor)return i;for(var l in e)if(et.call(e,l)&&(f++,!(a=et.call(t,l)&&C(e[l],t[l],s))))break;if(a){for(l in t)if(et.call(t,l)&&!(f--))break;a=!f}if(a&&yt)for(;7>++u&&(l=Q[u],!et.call(e,l)||!!(a=et.call
(t,l)&&C(e[l],t[l],s))););}return s.pop(),a}function k(e){return e}function L(e){Ft(Gt(e),function(t){var r=s[t]=e[t];o.prototype[t]=function(){var e=[this._wrapped];return arguments.length&&tt.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,A,O,M,_,D="object"==typeof exports&&exports&&("object"==typeof global&&global&&global==global.global&&(e=global),exports),P=Array.prototype,H=Object.prototype,B=0,j=e._,F=/[-+=!~*%&^<>|{(\/]|\[\D|\b(?:delete|in|instanceof|new|typeof|void)\b/
,I=/^';/,q=/^'\+/,R=/(?:__p\+='|\+')$/,U=/\b__p\+='';/g,z=/\b(__p\+?=)''\+/g,W=/(__w?e\(.*?\)|\b__w?t\))\+'';/g,X=/(?:__e|__t=)\(\s*(?![\d\s"']|this\.)/g,V=RegExp("^"+(H.valueOf+"").replace(/[.*+?^=!:${}()|[\]\/\\]/g,"\\$&").replace(/valueOf|for [^\]]+/g,".+?")+"$"),$=/__token__(\d+)/g,J=/[&<"']/g,K=/['\n\r\t\u2028\u2029\\]/g,Q="constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf".split(" "),G="__token__",Y=[],Z=P.concat,et=H.hasOwnProperty,tt=P.push,nt=H
.propertyIsEnumerable,rt=P.slice,it=H.toString,st=V.test(st=rt.bind)&&st,ot=V.test(ot=Array.isArray)&&ot,ut=e.isFinite,at=V.test(at=Object.keys)&&at,ft="[object Array]",lt="[object Boolean]",ct="[object Date]",ht="[object Function]",pt="[object Number]",dt="[object RegExp]",vt="[object String]",mt=e.clearTimeout,gt=e.setTimeout,yt=!nt.call({valueOf:0},"valueOf"),bt="x"!=rt.call("x")[0],wt="xx"!="x"[0]+Object("x")[0],Et=st&&/\n|Opera/.test(st+it.call(e.opera)),St=at&&/^.+$|true/.test(at+!!e.attachEvent
),xt={"&":"&amp;","<":"&lt;",'"':"&quot;","'":"&#x27;"},Tt={"boolean":i,"function":n,object:n,number:i,string:i,"undefined":i},Nt={"\\":"\\","'":"'","\n":"n","\r":"r"," ":"t","\u2028":"u2028","\u2029":"u2029"};s.templateSettings={escape:/<%-([\s\S]+?)%>/g,evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,variable:"obj"};var Ct={a:"f,d,H",j:"f",q:"if(!d){d=k}else if(H)d=o(d,H)",i:"d(n[l],l,f)"},kt={j:"true",i:"if(!d(n[l],l,f))return!A"},Lt={a:"t",j:"t",q:"for(var D,E=1,p=arguments.length;E<p;E++){D=arguments[E];"+
(yt?"if(D){":""),l:"D",r:i,i:"A[l]=n[l]",e:(yt?"}":"")+"}"},At={j:"[]",i:"d(n[l],l,f)&&A.push(n[l])"},Ot={q:"if(H)d=o(d,H)"},Mt={i:{m:Ct.i}},_t={j:"",f:"if(!f)return[]",d:{b:"A=Array(p)",m:"A="+(St?"Array(p)":"[]")},i:{b:"A[l]=d(n[l],l,f)",m:"A"+(St?"[w]=":".push")+"(d(n[l],l,f))"}},Dt=u({a:"t",f:"if(!(t&&u[typeof t]))throw TypeError()",j:"[]",i:"A.push(l)"}),Pt=u({a:"f,G",j:"false",o:i,d:{b:"if(I.call(n)==F)return f.indexOf(G)>-1"},i:"if(n[l]===G)return true"}),Ht=u(Ct,kt),Bt=u(Ct,At),jt=u(Ct,Ot
,{j:"",i:"if(d(n[l],l,f))return n[l]"}),Ft=u(Ct,Ot),It=u(Ct,{j:"{}",q:"var v,m=typeof d=='function';if(m&&H)d=o(d,H)",i:"v=m?d(n[l],l,f):n[l][d];(j.call(A,v)?A[v]:A[v]=[]).push(n[l])"}),qt=u(_t,{a:"f,q",q:"var b=C.call(arguments,2),m=typeof q=='function'",i:{b:"A[l]=(m?q:n[l][q]).apply(n[l],b)",m:"A"+(St?"[w]=":".push")+"((m?q:n[l][q]).apply(n[l],b))"}}),Rt=u(Ct,_t),Ut=u(_t,{a:"f,y",i:{b:"A[l]=n[l][y]",m:"A"+(St?"[w]=":".push")+"(n[l][y])"}}),zt=u({a:"f,d,a,H",j:"a",q:"var s=arguments.length<3;if(H)d=o(d,H)"
,d:{b:"if(s)A=f[++l]"},i:{b:"A=d(A,n[l],l,f)",m:"A=s?(s=false,n[l]):d(A,n[l],l,f)"}}),Wt=u(Ct,At,{i:"!"+At.i}),Xt=u(Ct,kt,{j:"false",i:kt.i.replace("!","")}),Vt=u(Ct,_t,{q:"if(typeof d=='string'){var v=d;d=function(f){return f[v]}}else if(H)d=o(d,H)",i:{b:"A[l]={a:d(n[l],l,f),b:n[l]}",m:"A"+(St?"[w]=":".push")+"({a:d(n[l],l,f),b:n[l]})"},e:"A.sort(g);p=A.length;while(p--){A[p]=A[p].b}"}),$t=u(Lt,{i:"if(A[l]==null)"+Lt.i}),Jt=u(Lt),Kt=u(Ct,Ot,Mt,{r:i}),Qt=u(Ct,Ot,Mt),Gt=u({a:"t",j:"[]",r:i,i:"if(I.call(n[l])==i)A.push(l)"
,e:"A.sort()"});Ft({Arguments:"[object Arguments]",Date:ct,Function:ht,Number:pt,RegExp:dt,String:vt},function(e,t){s["is"+t]=function(t){return it.call(t)==e}}),s.isArguments(arguments)||(s.isArguments=function(e){return!!e&&!!et.call(e,"callee")});var Yt=ot||function(e){return it.call(e)==ft},Zt=u({a:"J",j:"true",q:"var e=I.call(J);if(e==c||e==F)return!J.length",i:{m:"return false"}}),en=at?function(e){return"function"==typeof e&&nt.call(e,"prototype")?Dt(e):at(e)}:Dt,tn=u({a:"t",j:"[]",i:"A.push(n[l])"
});s.VERSION="0.4.0",s.after=function(e,t){return 1>e?t():function(){if(1>--e)return t.apply(this,arguments)}},s.bind=N,s.bindAll=function(e){var t=arguments,n=1;1==t.length&&(n=0,t=Gt(e));for(var r=t.length;n<r;n++)e[t[n]]=N(e[t[n]],e);return e},s.chain=function(e){return e=new o(e),e._chain=n,e},s.clone=function(e){return e&&Tt[typeof e]?Yt(e)?e.slice():Jt({},e):e},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=Pt,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,mt(a),a=gt(i,t),r&&(o=e.apply(u,s)),o}},s.defaults=$t,s.defer=function(e){var n=rt.call(arguments,1);return gt(function(){return e.apply(t,n)},1)},s.delay=function(e,n){var r=rt.call(arguments,2);return gt(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=Z.apply(t,arguments);++n<r;)0>w(i,e[n],r)&&t.push(e[n]);return t},s.escape=function(e){return e==r?"":(e+"").replace(J,c)},s.every=Ht,s.extend=Jt,s.filter=Bt,s.find=jt,s.first=y,s.flatten=b,s.forEach=Ft,s.forIn=Kt,s.forOwn=Qt,s.functions=Gt,s.groupBy=It,s.has=function(e,t){return et.call(e,t)},s.identity=k,s.indexOf=w,s.initial=function(e,t,n){return e?rt.call(e,0,-(t==r||n?1:t)):[]},s.intersection=function(e){var t=[];if(!e)return t;for(var n,
r=-1,i=e.length,s=rt.call(arguments,1);++r<i;)n=e[r],0>w(t,n)&&Ht(s,function(e){return-1<w(e,n)})&&t.push(n);return t},s.invoke=qt,s.isArray=Yt,s.isBoolean=function(e){return e===n||e===i||it.call(e)==lt},s.isElement=function(e){return!!e&&1==e.nodeType},s.isEmpty=Zt,s.isEqual=C,s.isFinite=function(e){return ut(e)&&it.call(e)==pt},s.isNaN=function(e){return it.call(e)==pt&&e!=+e},s.isNull=function(e){return e===r},s.isObject=function(e){return e&&Tt[typeof e]},s.isUndefined=function(e){return e===
t},s.keys=en,s.last=function(e,t,n){if(e){var i=e.length;return t==r||n?e[i-1]:rt.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=Rt,s.max=E,s.memoize=function(e,t){var n={};return function(){var r=t?t.apply(this,arguments):arguments[0];return et.call(n,r)?n[r]:n[r]=e.apply(this,arguments)}},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=h(t,n));++s<o;)n=t(e[s],s,e),n<r&&(r=n,i=e[s]);return i},s.mixin=L,s.noConflict=function(){return e._=j,this},s.once=function(e){var t,r=i;return function(){return r?t:(r=n,t=e.apply(this,arguments))}},s.partial=function(e){var t=rt.call(arguments,1),n=t.length;return function(){var r;return r=arguments,r.length&&(t.length=n,tt.apply(t,r)),r=1==t.length?e.call(this,t[0]):e.apply(this,t),t.length=n,r}},s.pick=function(e){for(var t,n=0
,r=Z.apply(P,arguments),i=r.length,s={};++n<i;)t=r[n],t in e&&(s[t]=e[t]);return s},s.pluck=Ut,s.range=function(e,t,n){n||(n=1),t==r&&(t=e||0,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=zt,s.reduceRight=g,s.reject=Wt,s.rest=S,s.result=function(e,t){if(!e)return r;var n=e[t];return it.call(n)==ht?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=e.length;return t===t>>>0?e.length:en(e).length},s.some=Xt,s.sortBy=Vt,s.sortedIndex=x,s.tap=function(e,t){return t(e),e},s.template=function(e,t,n){n||(n={});var i,o,u,a,c;i=n.escape,o=n.evaluate,u=n.interpolate;var h=s.templateSettings,n=n.variable;i==r&&(i=h.escape),o==r&&(o=h.evaluate),u==r&&(u=h.interpolate),i&&(e=e.replace(i,d)),u&&(e=e.replace(u,m)),o!=A&&(A=o,_=RegExp((o?o.source:"($^)")+"|<e%-([\\s\\S]+?)%>|<e%=([\\s\\S]+?)%>","g")),u=Y.length,e=
e.replace(_,v),i=Y.length-1,o=u<=i,!n&&(n=h.variable||O||"obj",c=o)&&(Y[u]="';__with("+n+"){"+Y[u].replace(I,"").replace(q,"__p+="),Y[i]=Y[i].replace(R,"")+"}____p+='"),h="$&"+n+".",e="__p='"+e.replace(K,l).replace($,f)+"';",Y.length=0,c&&(u=e.indexOf("__with"),i=e.indexOf("}__",u+10)),n!=O&&(O=n,M=RegExp("([(\\s])"+n+"\\."+n+"\\b","g")),e=(c?e.slice(0,u):e).replace(X,h).replace(M,"$1__d")+(c?e.slice(u+2,i+1)+e.slice(i+3).replace(X,h).replace(M,"$1__d"):""),e=(o?e.replace(U,""):e).replace(z,"$1")
.replace(W,"$1;"),e="function("+n+"){"+n+"||("+n+"={});var __p,__t,__wt,__d="+n+"."+n+"||"+n+",__e=_.escape,__we=__e"+(o?",__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}":";")+e+"return __p}";try{a=Function("_","return "+e)(s)}catch(p){a=function(){throw p}}return t?a(t):(a.source=e,a)},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=gt(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&&it.call(e.toArray)==ht)return e.toArray();var t=e.length;return t===t>>>0?(bt?it.call(e)==vt:"string"==typeof e)?e.split(""):rt.call(e):tn(e)},s.union=function(){for(var e=-1,t=[],n=Z.apply(t,arguments),r=n.length;++e<r;)0>w(t,n[e])&&t.push(n[e]);return t},s.uniq=T,s.uniqueId=function(e){var t=B++;return e?e+t:t},s.values=tn,s.without=function(e){var t=[];
if(!e)return t;for(var n=-1,r=e.length;++n<r;)0>w(arguments,e[n],1)&&t.push(e[n]);return t},s.wrap=function(e,t){return function(){var n=[e];return arguments.length&&tt.apply(n,arguments),t.apply(this,n)}},s.zip=function(e){if(!e)return[];for(var t=-1,n=E(Ut(arguments,"length")),r=Array(n);++t<n;)r[t]=Ut(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=Ht,s.any=Xt,s.collect=Rt,s.detect=jt,s.each=Ft,s.foldl=zt
,s.foldr=g,s.head=y,s.include=Pt,s.inject=zt,s.methods=Gt,s.select=Bt,s.tail=S,s.take=y,s.unique=T,o.prototype=s.prototype,L(s),o.prototype.chain=function(){return this._chain=n,this},o.prototype.value=function(){return this._wrapped},Ft("pop push reverse shift sort splice unshift".split(" "),function(e){var t=P[e];o.prototype[e]=function(){var e=this._wrapped;return t.apply(e,arguments),e.length===0&&delete e[0],this._chain&&(e=new o(e),e._chain=n),e}}),Ft(["concat","join","slice"],function(e){var t=
P[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})):D?"object"==typeof module&&module&&module.s==D?(module.s=s)._=s:D._=s:e._=s})(this);

View File

@@ -1,6 +1,6 @@
{
"name": "lodash",
"version": "0.2.0",
"version": "0.4.0",
"description": "A drop-in replacement for Underscore.js that delivers performance improvements, bug fixes, and additional features.",
"homepage": "http://lodash.com",
"main": "lodash",
@@ -31,12 +31,19 @@
"type": "git",
"url": "https://github.com/bestiejs/lodash.git"
},
"bin": {
"lodash": "./build.js"
},
"directories": {
"doc": "./doc",
"test": "./test"
},
"engines": [
"node",
"rhino"
],
"directories": {
"doc": "./doc",
"test": "./test"
"scripts": {
"build": "node build",
"test": "node test/test"
}
}
}

View File

@@ -9,6 +9,10 @@
padding: 0;
height: 100%;
}
applet {
position: absolute;
left: -9999em;
}
</style>
</head>
<body>
@@ -16,7 +20,7 @@
<script>
var lodash = _.noConflict();
</script>
<script src="../vendor/underscore/underscore-min.js"></script>
<script src="../vendor/underscore/underscore.js"></script>
<script src="../vendor/benchmark.js/benchmark.js"></script>
<script src="../vendor/firebug-lite/src/firebug-lite-debug.js"></script>
<script>
@@ -30,13 +34,15 @@
}());
window.onload = function() {
var sibling = document.getElementsByTagName('script')[0],
var fbUI = document.getElementById('FirebugUI'),
fbDoc = (fbDoc = fbUI.contentWindow || fbUI.contentDocument).document || fbDoc,
sibling = document.getElementsByTagName('script')[0],
script = document.createElement('script');
document.getElementById('FirebugUI').style.height = '100%';
script.src = 'perf.js';
fbUI.style.height = fbDoc.body.style.height = fbDoc.documentElement.style.height = '100%';
script.src = 'perf.js?t=' + (+new Date);
sibling.parentNode.insertBefore(script, sibling);
};
</script>
</body>
</html>
</html>

View File

@@ -13,7 +13,7 @@
/** Load Lo-Dash */
var lodash =
window.lodash || (
lodash = load('../lodash.min.js') || window._,
lodash = load('../lodash.js') || window._,
lodash = lodash._ || lodash,
lodash.noConflict()
);
@@ -21,10 +21,15 @@
/** Load Underscore */
var _ =
window._ || (
_ = load('../vendor/underscore/underscore-min.js') || window._,
_ = load('../vendor/underscore/underscore.js') || window._,
_._ || _
);
/** Used to access the Firebug Lite panel */
var fbPanel = (fbPanel = window.document && document.getElementById('FirebugUI')) &&
(fbPanel = (fbPanel = fbPanel.contentWindow || fbPanel.contentDocument).document || fbPanel) &&
fbPanel.getElementById('fbPanel1');
/** Used to score Lo-Dash and Underscore performance */
var score = { 'lodash': 0, 'underscore': 0 };
@@ -41,43 +46,219 @@
/*--------------------------------------------------------------------------*/
/**
* Gets the Hz, i.e. operations per second, of `bench` adjusted for the
* margin of error.
*
* @private
* @param {Object} bench The benchmark object.
* @returns {Number} Returns the adjusted Hz.
*/
function getHz(bench) {
return 1 / (bench.stats.mean + bench.stats.moe);
}
/**
* Logs text to the console.
*
* @private
* @param {String} text The text to log.
*/
function log(text) {
console.log(text);
if (fbPanel) {
// scroll the Firebug Lite panel down
fbPanel.scrollTop = fbPanel.scrollHeight;
}
}
/*--------------------------------------------------------------------------*/
lodash.extend(Benchmark.options, {
'async': true,
'setup': function() {
var window = Function('return this || global')(),
_ = window._,
lodash = window.lodash,
numbers = [],
object = {};
lodash = window.lodash;
for (var index = 0; index < 20; index++) {
var length = 20,
numbers = [],
object = {},
fourNumbers = [5, 25, 10, 30],
nestedNumbers = [1, [2], [3, [[4]]]],
twoNumbers = [12, 21];
var ctor = function() { };
var func = function(greeting, punctuation) {
return greeting + ', ' + this.name + (punctuation || '.');
};
var lodashBoundNormal = lodash.bind(func, { 'name': 'moe' }),
lodashBoundCtor = lodash.bind(ctor, { 'name': 'moe' }),
lodashBoundPartial = lodash.bind(func, { 'name': 'moe' }, 'hi');
var _boundNormal = _.bind(func, { 'name': 'moe' }),
_boundCtor = _.bind(ctor, { 'name': 'moe' }),
_boundPartial = _.bind(func, { 'name': 'moe' }, 'hi');
var tplData = {
'header1': 'Header1',
'header2': 'Header2',
'header3': 'Header3',
'header4': 'Header4',
'header5': 'Header5',
'header6': 'Header6',
'list': ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']
};
var tplBase =
'<div>' +
"<h1 class='header1'><%= header1 %></h1>" +
"<h2 class='header2'><%= header2 %></h2>" +
"<h3 class='header3'><%= header3 %></h3>" +
"<h4 class='header4'><%= header4 %></h4>" +
"<h5 class='header5'><%= header5 %></h5>" +
"<h6 class='header6'><%= header6 %></h6>";
var tpl =
tplBase +
"<ul class='list'>" +
"<li class='item'><%= list[0] %></li>" +
"<li class='item'><%= list[1] %></li>" +
"<li class='item'><%= list[2] %></li>" +
"<li class='item'><%= list[3] %></li>" +
"<li class='item'><%= list[4] %></li>" +
"<li class='item'><%= list[5] %></li>" +
"<li class='item'><%= list[6] %></li>" +
"<li class='item'><%= list[7] %></li>" +
"<li class='item'><%= list[8] %></li>" +
"<li class='item'><%= list[9] %></li>" +
'</ul>' +
'</div>';
var tplWithEvaluate =
tplBase +
"<ul class='list'>" +
'<% for (var index = 0, length = list.length; index < length; index++) { %>' +
"<li class='item'><%= list[index] %></li>" +
'<% } %>' +
'</ul>' +
'</div>';
var tplBaseVerbose =
'<div>' +
"<h1 class='header1'><%= data.header1 %></h1>" +
"<h2 class='header2'><%= data.header2 %></h2>" +
"<h3 class='header3'><%= data.header3 %></h3>" +
"<h4 class='header4'><%= data.header4 %></h4>" +
"<h5 class='header5'><%= data.header5 %></h5>" +
"<h6 class='header6'><%= data.header6 %></h6>";
var tplVerbose =
tplBaseVerbose +
"<ul class='list'>" +
"<li class='item'><%= data.list[0] %></li>" +
"<li class='item'><%= data.list[1] %></li>" +
"<li class='item'><%= data.list[2] %></li>" +
"<li class='item'><%= data.list[3] %></li>" +
"<li class='item'><%= data.list[4] %></li>" +
"<li class='item'><%= data.list[5] %></li>" +
"<li class='item'><%= data.list[6] %></li>" +
"<li class='item'><%= data.list[7] %></li>" +
"<li class='item'><%= data.list[8] %></li>" +
"<li class='item'><%= data.list[9] %></li>" +
'</ul>' +
'</div>';
var tplVerboseWithEvaluate =
tplBaseVerbose +
"<ul class='list'>" +
'<% for (var index = 0, length = data.list.length; index < length; index++) { %>' +
"<li class='item'><%= data.list[index] %></li>" +
'<% } %>' +
'</ul>' +
'</div>';
var lodashTpl = lodash.template(tpl),
lodashTplWithEvaluate = lodash.template(tplWithEvaluate),
lodashTplVerbose = lodash.template(tplVerbose, null, { 'variable': 'data' }),
lodashTplVerboseWithEvaluate = lodash.template(tplVerboseWithEvaluate, null, { 'variable': 'data' });
var _tpl = _.template(tpl),
_tplWithEvaluate = _.template(tplWithEvaluate),
_tplVerbose = _.template(tplVerbose, null, { 'variable': 'data' }),
_tplVerboseWithEvaluate = _.template(tplVerboseWithEvaluate, null, { 'variable': 'data' });
var wordToNumber = {
'one': 1,
'two': 2,
'three': 3,
'four': 4,
'five': 5,
'six': 6,
'seven': 7,
'eight': 8,
'nine': 9,
'ten': 10,
'eleven': 11,
'twelve': 12,
'thirteen': 13,
'fourteen': 14,
'fifteen': 15,
'sixteen': 16,
'seventeen': 17,
'eighteen': 18,
'nineteen': 19,
'twenty': 20,
'twenty-one': 21,
'twenty-two': 22,
'twenty-three': 23,
'twenty-four': 24,
'twenty-five': 25
};
var words = _.keys(wordToNumber).slice(0, length);
for (var index = 0; index < length; index++) {
numbers[index] = index;
object['key' + index] = index;
}
var objects = lodash.map(numbers, function(n) {
return { 'num': n };
var objects = lodash.map(numbers, function(num) {
return { 'num': num };
});
}
});
lodash.extend(Benchmark.Suite.options, {
'onStart': function() {
console.log('\n' + this.name + ':');
log('\n' + this.name + ':');
},
'onCycle': function(event) {
console.log(event.target + '');
log(event.target + '');
},
'onComplete': function() {
var fastest = this.filter('fastest').pluck('name'),
lodashHz = 1 / (this[0].stats.mean + this[0].stats.moe),
underscoreHz = 1 / (this[1].stats.mean + this[1].stats.moe);
var formatNumber = Benchmark.formatNumber,
fastest = this.filter('fastest'),
fastestHz = getHz(fastest[0]),
slowest = this.filter('slowest'),
slowestHz = getHz(slowest[0]),
lodashHz = getHz(this[0]),
underscoreHz = getHz(this[1]);
if (fastest.length > 1) {
console.log('It\'s too close to call.');
lodashHz = underscoreHz = Math.min(lodashHz, underscoreHz);
} else {
console.log(fastest + ' is the fastest.');
log('It\'s too close to call.');
lodashHz = underscoreHz = slowestHz;
}
else {
var percent = ((fastestHz / slowestHz) - 1) * 100;
log(
fastest[0].name + ' is ' +
formatNumber(percent < 1 ? percent.toFixed(2) : Math.round(percent)) +
'% faster.'
);
}
// add score adjusted for margin of error
score.lodash += lodashHz;
@@ -91,11 +272,17 @@
suites[0].run();
}
else {
var fastestTotalHz = Math.max(score.lodash, score.underscore),
slowestTotalHz = Math.min(score.lodash, score.underscore),
totalPercent = formatNumber(Math.round(((fastestTotalHz / slowestTotalHz) - 1) * 100)),
totalX = fastestTotalHz / slowestTotalHz,
message = ' is ' + totalPercent + '% ' + (totalX == 1 ? '' : '(' + formatNumber(totalX.toFixed(2)) + 'x) ') + 'faster than ';
// report results
if (score.lodash >= score.underscore) {
console.log('\nLo-Dash is ' + (score.lodash / score.underscore).toFixed(2) + 'x faster than Underscore.');
log('\nLo-Dash' + message + 'Underscore.');
} else {
console.log('\nUnderscore is ' + (score.underscore / score.lodash).toFixed(2) + 'x faster than Lo-Dash.');
log('\nUnderscore' + message + 'Lo-Dash.');
}
}
}
@@ -104,35 +291,111 @@
/*--------------------------------------------------------------------------*/
suites.push(
Benchmark.Suite('each array')
Benchmark.Suite('`_.bind` (uses native `Function#bind` if available and inferred fast)')
.add('Lo-Dash', function() {
var timesTwo = [];
lodash.bind(func, { 'name': 'moe' }, 'hi');
})
.add('Underscore', function() {
_.bind(func, { 'name': 'moe' }, 'hi');
})
);
suites.push(
Benchmark.Suite('bound call')
.add('Lo-Dash', function() {
lodashBoundNormal();
})
.add('Underscore', function() {
_boundNormal();
})
);
suites.push(
Benchmark.Suite('bound call with arguments')
.add('Lo-Dash', function() {
lodashBoundNormal('hi', '!');
})
.add('Underscore', function() {
_boundNormal('hi', '!');
})
);
suites.push(
Benchmark.Suite('bound and partially applied call (uses native `Function#bind` if available)')
.add('Lo-Dash', function() {
lodashBoundPartial();
})
.add('Underscore', function() {
_boundPartial();
})
);
suites.push(
Benchmark.Suite('bound and partially applied call with arguments (uses native `Function#bind` if available)')
.add('Lo-Dash', function() {
lodashBoundPartial('!');
})
.add('Underscore', function() {
_boundPartial('!');
})
);
suites.push(
Benchmark.Suite('bound and called in a `new` expression, i.e. `new bound` (edge case)')
.add('Lo-Dash', function() {
new lodashBoundCtor();
})
.add('Underscore', function() {
new _boundCtor();
})
);
/*--------------------------------------------------------------------------*/
suites.push(
Benchmark.Suite('`_.each` iterating an array')
.add('Lo-Dash', function() {
var result = [];
lodash.each(numbers, function(num) {
timesTwo.push(num * 2);
result.push(num * 2);
});
})
.add('Underscore', function() {
var timesTwo = [];
var result = [];
_.each(numbers, function(num) {
timesTwo.push(num * 2);
result.push(num * 2);
});
})
);
/*--------------------------------------------------------------------------*/
suites.push(
Benchmark.Suite('`_.each` iterating an array with `thisArg` (slow path)')
.add('Lo-Dash', function() {
var result = [];
lodash.each(numbers, function(num, index) {
result.push(num + this['key' + index]);
}, object);
})
.add('Underscore', function() {
var result = [];
_.each(numbers, function(num, index) {
result.push(num + this['key' + index]);
}, object);
})
);
suites.push(
Benchmark.Suite('each object')
Benchmark.Suite('`_.each` iterating an object')
.add('Lo-Dash', function() {
var timesTwo = [];
var result = [];
lodash.each(object, function(num) {
timesTwo.push(num * 2);
result.push(num * 2);
});
})
.add('Underscore', function() {
var timesTwo = [];
var result = [];
_.each(object, function(num) {
timesTwo.push(num * 2);
result.push(num * 2);
});
})
);
@@ -140,7 +403,181 @@
/*--------------------------------------------------------------------------*/
suites.push(
Benchmark.Suite('keys')
Benchmark.Suite('`_.filter` iterating an array')
.add('Lo-Dash', function() {
lodash.filter(numbers, function(num) {
return num % 2;
});
})
.add('Underscore', function() {
_.filter(numbers, function(num) {
return num % 2;
});
})
);
suites.push(
Benchmark.Suite('`_.filter` iterating an array with `thisArg` (slow path)')
.add('Lo-Dash', function() {
lodash.filter(numbers, function(num, index) {
return this['key' + index] % 2;
}, object);
})
.add('Underscore', function() {
_.filter(numbers, function(num, index) {
return this['key' + index] % 2;
}, object);
})
);
suites.push(
Benchmark.Suite('`_.filter` iterating an object')
.add('Lo-Dash', function() {
lodash.filter(object, function(num) {
return num % 2;
});
})
.add('Underscore', function() {
_.filter(object, function(num) {
return num % 2;
});
})
);
/*--------------------------------------------------------------------------*/
suites.push(
Benchmark.Suite('`_.find` iterating an array')
.add('Lo-Dash', function() {
lodash.find(numbers, function(num) {
return num === 19;
});
})
.add('Underscore', function() {
_.find(numbers, function(num) {
return num === 19;
});
})
);
suites.push(
Benchmark.Suite('`_.find` iterating an object')
.add('Lo-Dash', function() {
lodash.find(numbers, function(value, key) {
return /\D9$/.test(key);
});
})
.add('Underscore', function() {
_.find(numbers, function(value, key) {
return /\D9$/.test(key);
});
})
);
/*--------------------------------------------------------------------------*/
suites.push(
Benchmark.Suite('`_.flatten`')
.add('Lo-Dash', function() {
lodash.flatten(nestedNumbers);
})
.add('Underscore', function() {
_.flatten(nestedNumbers);
})
);
suites.push(
Benchmark.Suite('`_.flatten` with `shallow`')
.add('Lo-Dash', function() {
lodash.flatten(nestedNumbers, true);
})
.add('Underscore', function() {
_.flatten(nestedNumbers, true);
})
);
/*--------------------------------------------------------------------------*/
suites.push(
Benchmark.Suite('`_.difference`')
.add('Lo-Dash', function() {
lodash.difference(numbers, fourNumbers, twoNumbers);
})
.add('Underscore', function() {
_.difference(numbers, fourNumbers, twoNumbers);
})
);
/*--------------------------------------------------------------------------*/
suites.push(
Benchmark.Suite('`_.groupBy` with `callback` iterating an array')
.add('Lo-Dash', function() {
lodash.groupBy(numbers, function(num) { return num >> 1; });
})
.add('Underscore', function() {
_.groupBy(numbers, function(num) { return num >> 1; });
})
);
suites.push(
Benchmark.Suite('`_.groupBy` with `property` name iterating an array')
.add('Lo-Dash', function() {
lodash.groupBy(words, 'length');
})
.add('Underscore', function() {
_.groupBy(words, 'length');
})
);
suites.push(
Benchmark.Suite('`_.groupBy` with `callback` iterating an object')
.add('Lo-Dash', function() {
lodash.groupBy(wordToNumber, function(num) { return num >> 1; });
})
.add('Underscore', function() {
_.groupBy(wordToNumber, function(num) { return num >> 1; });
})
);
/*--------------------------------------------------------------------------*/
suites.push(
Benchmark.Suite('`_.indexOf`')
.add('Lo-Dash', function() {
lodash.indexOf(numbers, 9);
})
.add('Underscore', function() {
_.indexOf(numbers, 9);
})
);
suites.push(
Benchmark.Suite('`_.indexOf` with `isSorted`')
.add('Lo-Dash', function() {
lodash.indexOf(numbers, 19, true);
})
.add('Underscore', function() {
_.indexOf(numbers, 19, true);
})
);
/*--------------------------------------------------------------------------*/
suites.push(
Benchmark.Suite('`_.intersection`')
.add('Lo-Dash', function() {
lodash.intersection(numbers, fourNumbers, twoNumbers);
})
.add('Underscore', function() {
_.intersection(numbers, fourNumbers, twoNumbers);
})
);
/*--------------------------------------------------------------------------*/
suites.push(
Benchmark.Suite('`_.keys` (uses native `Object.keys` if available)')
.add('Lo-Dash', function() {
lodash.keys(object);
})
@@ -152,15 +589,55 @@
/*--------------------------------------------------------------------------*/
suites.push(
Benchmark.Suite('map')
Benchmark.Suite('`_.lastIndexOf`')
.add('Lo-Dash', function() {
lodash.map(objects, function(obj) {
return obj.num;
lodash.lastIndexOf(numbers, 9);
})
.add('Underscore', function() {
_.lastIndexOf(numbers, 9);
})
);
/*--------------------------------------------------------------------------*/
suites.push(
Benchmark.Suite('`_.map` iterating an array')
.add('Lo-Dash', function() {
lodash.map(objects, function(value) {
return value.num;
});
})
.add('Underscore', function() {
_.map(objects, function(obj) {
return obj.num;
_.map(objects, function(value) {
return value.num;
});
})
);
suites.push(
Benchmark.Suite('`_.map` with `thisArg` iterating an array (slow path)')
.add('Lo-Dash', function() {
lodash.map(objects, function(value, index) {
return this['key' + index] + value.num;
}, object);
})
.add('Underscore', function() {
_.map(objects, function(value, index) {
return this['key' + index] + value.num;
}, object);
})
);
suites.push(
Benchmark.Suite('`_.map` iterating an object')
.add('Lo-Dash', function() {
lodash.map(object, function(value) {
return value;
});
})
.add('Underscore', function() {
_.map(object, function(value) {
return value;
});
})
);
@@ -168,7 +645,43 @@
/*--------------------------------------------------------------------------*/
suites.push(
Benchmark.Suite('pluck')
Benchmark.Suite('`_.max`')
.add('Lo-Dash', function() {
lodash.max(numbers);
})
.add('Underscore', function() {
_.max(numbers);
})
);
/*--------------------------------------------------------------------------*/
suites.push(
Benchmark.Suite('`_.min`')
.add('Lo-Dash', function() {
lodash.min(numbers);
})
.add('Underscore', function() {
_.min(numbers);
})
);
/*--------------------------------------------------------------------------*/
suites.push(
Benchmark.Suite('`_.pick`')
.add('Lo-Dash', function() {
lodash.pick(object, 'key6', 'key13');
})
.add('Underscore', function() {
_.pick(object, 'key6', 'key13');
})
);
/*--------------------------------------------------------------------------*/
suites.push(
Benchmark.Suite('`_.pluck`')
.add('Lo-Dash', function() {
lodash.pluck(objects, 'num');
})
@@ -180,21 +693,262 @@
/*--------------------------------------------------------------------------*/
suites.push(
Benchmark.Suite('values')
Benchmark.Suite('`_.shuffle`')
.add('Lo-Dash', function() {
lodash.values(objects);
lodash.shuffle(numbers);
})
.add('Underscore', function() {
_.values(objects);
_.shuffle(numbers);
})
);
/*--------------------------------------------------------------------------*/
suites.push(
Benchmark.Suite('`_.size` with an array')
.add('Lo-Dash', function() {
lodash.size(numbers);
})
.add('Underscore', function() {
_.size(numbers);
})
);
suites.push(
Benchmark.Suite('`_.size` with an object')
.add('Lo-Dash', function() {
lodash.size(object);
})
.add('Underscore', function() {
_.size(object);
})
);
/*--------------------------------------------------------------------------*/
suites.push(
Benchmark.Suite('`_.sortBy` with `callback`')
.add('Lo-Dash', function() {
lodash.sortBy(numbers, function(num) { return Math.sin(num); });
})
.add('Underscore', function() {
_.sortBy(numbers, function(num) { return Math.sin(num); });
})
);
suites.push(
Benchmark.Suite('`_.sortBy` with `callback` and `thisArg` (slow path)')
.add('Lo-Dash', function() {
lodash.sortBy(numbers, function(num) { return this.sin(num); }, Math);
})
.add('Underscore', function() {
_.sortBy(numbers, function(num) { return this.sin(num); }, Math);
})
);
suites.push(
Benchmark.Suite('`_.sortBy` with `property` name')
.add('Lo-Dash', function() {
lodash.sortBy(words, 'length');
})
.add('Underscore', function() {
_.sortBy(words, 'length');
})
);
/*--------------------------------------------------------------------------*/
suites.push(
Benchmark.Suite('`_.sortedIndex`')
.add('Lo-Dash', function() {
lodash.sortedIndex(numbers, 25);
})
.add('Underscore', function() {
_.sortedIndex(numbers, 25);
})
);
suites.push(
Benchmark.Suite('`_.sortedIndex` with `callback`')
.add('Lo-Dash', function() {
lodash.sortedIndex(words, 'twenty-five', function(value) {
return wordToNumber[value];
});
})
.add('Underscore', function() {
_.sortedIndex(words, 'twenty-five', function(value) {
return wordToNumber[value];
});
})
);
/*--------------------------------------------------------------------------*/
suites.push(
Benchmark.Suite('`_.template` without "evaluate" delimiters (slow path)')
.add('Lo-Dash', function() {
lodash.template(tpl, tplData);
})
.add('Underscore', function() {
_.template(tpl, tplData);
})
);
suites.push(
Benchmark.Suite('`_.template` with "evaluate" delimiters (slow path)')
.add('Lo-Dash', function() {
lodash.template(tplWithEvaluate, tplData);
})
.add('Underscore', function() {
_.template(tplWithEvaluate, tplData);
})
);
suites.push(
Benchmark.Suite('compiled template without "evaluate" delimiters')
.add('Lo-Dash', function() {
lodashTpl(tplData);
})
.add('Underscore', function() {
_tpl(tplData);
})
);
suites.push(
Benchmark.Suite('compiled template with "evaluate" delimiters')
.add('Lo-Dash', function() {
lodashTplWithEvaluate(tplData);
})
.add('Underscore', function() {
_tplWithEvaluate(tplData);
})
);
suites.push(
Benchmark.Suite('compiled template without a with-statement or "evaluate" delimiters')
.add('Lo-Dash', function() {
lodashTplVerbose(tplData);
})
.add('Underscore', function() {
_tplVerbose(tplData);
})
);
suites.push(
Benchmark.Suite('compiled template without a with-statement using "evaluate" delimiters')
.add('Lo-Dash', function() {
lodashTplVerboseWithEvaluate(tplData);
})
.add('Underscore', function() {
_tplVerboseWithEvaluate(tplData);
})
);
/*--------------------------------------------------------------------------*/
suites.push(
Benchmark.Suite('`_.times`')
.add('Lo-Dash', function() {
var result = [];
lodash.times(length, function(n) { result.push(n); });
})
.add('Underscore', function() {
var result = [];
_.times(length, function(n) { result.push(n); });
})
);
suites.push(
Benchmark.Suite('`_.times` with `thisArg`')
.add('Lo-Dash', function() {
var result = [];
lodash.times(length, function(n) { result.push(this.sin(n)); }, Math);
})
.add('Underscore', function() {
var result = [];
_.times(length, function(n) { result.push(this.sin(n)); }, Math);
})
);
/*--------------------------------------------------------------------------*/
suites.push(
Benchmark.Suite('`_.toArray` with an array')
.add('Lo-Dash', function() {
lodash.toArray(numbers);
})
.add('Underscore', function() {
_.toArray(numbers);
})
);
suites.push(
Benchmark.Suite('`_.toArray` with an object')
.add('Lo-Dash', function() {
lodash.toArray(object);
})
.add('Underscore', function() {
_.toArray(object);
})
);
/*--------------------------------------------------------------------------*/
suites.push(
Benchmark.Suite('`_.union`')
.add('Lo-Dash', function() {
lodash.union(numbers, fourNumbers, twoNumbers);
})
.add('Underscore', function() {
_.union(numbers, fourNumbers, twoNumbers);
})
);
/*--------------------------------------------------------------------------*/
suites.push(
Benchmark.Suite('`_.uniq`')
.add('Lo-Dash', function() {
lodash.uniq(numbers.concat(fourNumbers, twoNumbers));
})
.add('Underscore', function() {
_.uniq(numbers.concat(fourNumbers, twoNumbers));
})
);
suites.push(
Benchmark.Suite('`_.uniq` with `callback`')
.add('Lo-Dash', function() {
lodash.uniq(numbers.concat(fourNumbers, twoNumbers), function(num) {
return num % 2;
});
})
.add('Underscore', function() {
_.uniq(numbers.concat(fourNumbers, twoNumbers), function(num) {
return num % 2;
});
})
);
/*--------------------------------------------------------------------------*/
suites.push(
Benchmark.Suite('`_.values`')
.add('Lo-Dash', function() {
lodash.values(object);
})
.add('Underscore', function() {
_.values(object);
})
);
/*--------------------------------------------------------------------------*/
if (Benchmark.platform + '') {
console.log(Benchmark.platform + '');
log(Benchmark.platform + '');
}
// start suites
log('\nSit back and relax, this may take a while.');
suites[0].run();
}(typeof global == 'object' && global || this));

View File

@@ -25,7 +25,10 @@
<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="../lodash.js"></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/noconflict.js"></script>
<script src="../vendor/backbone/test/events.js"></script>
@@ -34,6 +37,5 @@
<script src="../vendor/backbone/test/router.js"></script>
<script src="../vendor/backbone/test/view.js"></script>
<script src="../vendor/backbone/test/sync.js"></script>
<script src="../vendor/backbone/test/setdomlibrary.js"></script>
</body>
</html>
</html>

View File

@@ -8,46 +8,57 @@
<body>
<div id="qunit"></div>
<script src="../vendor/qunit/qunit/qunit.js"></script>
<script src="test-ui.js"></script>
<script>
var _2,
_3 = Object.keys;
// set a bad shim
Object._keys = Object.keys;
Object.keys = function() { return []; };
// load Lo-Dash and expose it to the bad `Object.keys` shim
document.write('<script src="../' + QUnit.config.lodashFilename + '.js"><\/script>');
</script>
<script src="../lodash.js"></script>
<script>
var lodashBadKeys = _,
_ = 1;
// store Lo-Dash to test for bad shim detection
var lodashBadShim = _;
Object.keys = _3;
_3 = void 0;
// restore nativeKeys
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>');
// load test.js if not using require.js
document.write(QUnit.urlParams.norequire
? '<script src="test.js"><\/script>'
: '<script src="../vendor/requirejs/require.js"><\/script>'
);
</script>
<script src="../lodash.js"></script>
<script src="../vendor/requirejs/require.js"></script>
<script>
if (/[?&]norequire=true(?:&|$)/.test(location.search)) {
require = define = null;
document.write('<script src="test.js"><\/script>');
}
else {
require({
'baseUrl': '../vendor/requirejs/',
'urlArgs': 't=' + (+new Date),
'paths': {
'lodash': '../../lodash',
'underscore': './../../lodash'
}
},
['lodash', 'underscore'], function(lodash, lodashAsUnderscore) {
_2 = lodash.noConflict();
_2.moduleName = 'lodash';
// load Lo-Dash as a module
var lodashModule,
underscoreModule;
_3 = lodashAsUnderscore.noConflict();
_3.moduleName = 'underscore';
window.require && require({
'baseUrl': '../vendor/requirejs/',
'urlArgs': 't=' + (+new Date),
'paths': {
'lodash': '../../' + QUnit.config.lodashFilename,
'underscore': './../../' + QUnit.config.lodashFilename
}
},
['lodash', 'underscore'], function(lodash, underscore) {
lodashModule = lodash.noConflict();
lodashModule.moduleName = 'lodash';
require(['test.js']);
});
}
underscoreModule = underscore.noConflict();
underscoreModule.moduleName = 'underscore';
require(['test.js']);
});
</script>
</body>
</html>
</html>

View File

@@ -6,4 +6,4 @@ for cmd in rhino ringo narwhal node; do
done
echo ""
echo "Testing in a browser..."
open index.html
open index.html

90
test/test-ui.js Normal file
View File

@@ -0,0 +1,90 @@
;(function(window) {
'use strict';
/** `QUnit.addEvent` shortcut */
var addEvent = QUnit.addEvent;
/** The Lo-Dash build to load */
var build = (/build=([^&]+)/.exec(location.search) || [])[1];
/** A flag to determine if RequireJS should be loaded */
var norequire = /[?&]norequire=true(?:&|$)/.test(location.search);
/*--------------------------------------------------------------------------*/
// assign `QUnit.config` properties
QUnit.config.lodashFilename = (function() {
switch (build) {
case 'prod': return 'lodash.min';
case 'custom': return 'lodash.custom.min';
case 'custom-debug': return 'lodash.custom';
}
return 'lodash';
}());
// assign `QUnit.urlParams` properties
QUnit.extend(QUnit.urlParams, {
'build': build,
'norequire': norequire
});
// initialize the build dropdown
addEvent(window, 'load', function() {
function eventHandler(event) {
var search = location.search.replace(/^\?|&?(?:build|norequire)=[^&]*&?/g, '');
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
location.href =
location.href.split('?')[0] + '?' +
(search ? search + '&' : '') + 'build=' +
dropdown[dropdown.selectedIndex].value +
(checkbox.checked ? '&norequire=true' : '');
}
function init() {
var header = document.getElementById('qunit-header');
if (header) {
header.appendChild(label1);
header.appendChild(label2);
dropdown.selectedIndex = (function() {
switch (build) {
case 'prod': return 1;
case 'custom': return 2;
case 'custom-debug': return 3;
}
return 0;
}());
checkbox.checked = norequire;
addEvent(checkbox, 'click', eventHandler);
addEvent(dropdown, 'change', eventHandler);
}
else {
setTimeout(init, 15);
}
}
var label1 = document.createElement('label');
label1.innerHTML =
'<input name="norequire" type="checkbox">norequire</label>';
var label2 = document.createElement('label');
label2.innerHTML =
'<select name="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';
var checkbox = label1.firstChild,
dropdown = label2.firstChild;
init();
});
}(this));

View File

@@ -1,4 +1,5 @@
(function(window, undefined) {
;(function(window, undefined) {
'use strict';
/** Use a single load function */
var load = typeof require == 'function' ? require : window.load;
@@ -23,9 +24,6 @@
/** Shortcut used to convert array-like objects to arrays */
var slice = [].slice;
/** Used to resolve a value's internal [[Class]] */
var toString = {}.toString;
/** Used to check problem JScript properties (a.k.a. the [[DontEnum]] bug) */
var shadowed = {
'constructor': 1,
@@ -37,6 +35,17 @@
'valueOf': 7
};
/** Used to check problem JScript properties too */
var shadowedKeys = [
'constructor',
'hasOwnProperty',
'isPrototypeOf',
'propertyIsEnumerable',
'toLocaleString',
'toString',
'valueOf'
];
/*--------------------------------------------------------------------------*/
/**
@@ -60,7 +69,7 @@
(function() {
test('supports loading lodash.js as the "lodash" module', function() {
if (window.document && window.require) {
equal((_2 || {}).moduleName, 'lodash');
equal((lodashModule || {}).moduleName, 'lodash');
} else {
skipTest(1)
}
@@ -68,15 +77,15 @@
test('supports loading lodash.js as the "underscore" module', function() {
if (window.document && window.require) {
equal((_3 || {}).moduleName, 'underscore');
equal((underscoreModule || {}).moduleName, 'underscore');
} else {
skipTest(1)
}
});
test('avoids overwritten native methods', function() {
if (window.lodashBadKeys) {
notDeepEqual(lodashBadKeys.keys({ 'a': 1 }), []);
if (window.document) {
notDeepEqual(lodashBadShim.keys({ 'a': 1 }), []);
} else {
skipTest(1);
}
@@ -92,7 +101,7 @@
ok(_() instanceof _);
});
test('should pass through LoDash instances', function() {
test('should return passed LoDash instances', function() {
var wrapped = _([]);
equal(_(wrapped), wrapped);
});
@@ -123,6 +132,23 @@
/*--------------------------------------------------------------------------*/
QUnit.module('lodash.contains');
(function() {
_.each([
{ 'kind': 'literal', 'value': 'abc' },
{ 'kind': 'object', 'value': Object('abc') }
],
function(data) {
test('should work with a string ' + data.kind + ' for `collection`', function() {
equal(_.contains(data.value, 'bc'), true);
equal(_.contains(data.value, 'd'), false);
});
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('lodash.debounce');
(function() {
@@ -142,6 +168,15 @@
test('should not escape the ">" character', function() {
equal(_.escape('>'), '>');
});
test('should not escape the "/" character', function() {
equal(_.escape('/'), '/');
});
test('should return empty string when passed `null` or `undefined`', function() {
equal(_.escape(null), '');
equal(_.escape(undefined), '');
});
}());
/*--------------------------------------------------------------------------*/
@@ -174,26 +209,140 @@
/*--------------------------------------------------------------------------*/
QUnit.module('lodash.find');
(function() {
var array = [1, 2, 3];
test('should return found `value`', function() {
equal(_.find(array, function(n) { return n > 2; }), 3);
});
test('should return `undefined` if `value` is not found', function() {
equal(_.find(array, function(n) { return n == 4; }), undefined);
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('lodash.flatten');
(function() {
test('should treat sparse arrays as dense', function() {
var array = [[1, 2, 3], Array(3)],
expected = [1, 2, 3],
actual1 = _.flatten(array),
actual2 = _.flatten(array, true);
expected.push(undefined, undefined, undefined);
deepEqual(actual1, expected);
ok('4' in actual1);
deepEqual(actual2, expected);
ok('4' in actual2);
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('lodash.forEach');
(function() {
test('returns the collection', function() {
var collection = [1, 2, 3, 4];
var collection = [1, 2, 3];
equal(_.forEach(collection, Boolean), collection);
});
test('fixes the JScript [[DontEnum]] bug (test in IE < 9)', function() {
var object = {};
_.forEach(shadowed, function(value, key) {
object[key] = value;
});
test('should treat array-like object with invalid `length` as a regular object', function() {
var keys = [],
object = { 'length': -1 };
deepEqual(object, shadowed);
_.forEach(object, function(value, key) { keys.push(key); });
deepEqual(keys, ['length']);
});
_.each([
{ 'kind': 'literal', 'value': 'abc' },
{ 'kind': 'object', 'value': Object('abc') }
],
function(data) {
test('should work with a string ' + data.kind + ' for `collection` (test in IE < 9)', function() {
var args,
collection = data.value,
values = [];
_.forEach(collection, function(value) {
args || (args = slice.call(arguments));
values.push(value);
});
deepEqual(args, ['a', 0, collection]);
deepEqual(values, ['a', 'b', 'c']);
});
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('lodash.forIn');
(function() {
test('iterates over inherited properties', function() {
function Dog(name) { this.name = name; }
Dog.prototype.bark = function() { /* Woof, woof! */ };
var keys = [];
_.forIn(new Dog('Dagny'), function(value, key) { keys.push(key); });
deepEqual(keys.sort(), ['bark', 'name']);
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('lodash.forOwn');
(function() {
test('iterates over the `length` property', function() {
var keys = [],
object = { '0': 'zero', '1': 'one', 'length': 2 };
_.forOwn(object, function(value, key) { keys.push(key); });
deepEqual(keys.sort(), ['0', '1', 'length']);
});
}());
/*--------------------------------------------------------------------------*/
_.each(['forEach', 'forIn', 'forOwn'], function(methodName) {
var func = _[methodName];
QUnit.module('lodash.' + methodName + ' iteration bugs');
test('fixes the JScript [[DontEnum]] bug (test in IE < 9)', function() {
var keys = [];
func(shadowed, function(value, key) { keys.push(key); });
deepEqual(keys.sort(), shadowedKeys);
});
test('skips the prototype property of functions (test in Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1)', function() {
function Foo() {}
Foo.prototype.a = 1;
var keys = [];
function callback(value, key) { keys.push(key); }
func(Foo, callback);
deepEqual(keys, []);
keys.length = 0;
Foo.prototype = { 'a': 1 };
func(Foo, callback);
deepEqual(keys, []);
});
});
/*--------------------------------------------------------------------------*/
QUnit.module('lodash.groupBy');
(function() {
@@ -204,6 +353,59 @@
deepEqual(actual, { '1': [1.3], '2': [2.1, 2.4] });
});
test('should only add elements to own, not inherited, properties', function() {
var actual = _.groupBy([1.3, 2.1, 2.4], function(num) {
return Math.floor(num) > 1 ? 'hasOwnProperty' : 'constructor';
});
deepEqual(actual.constructor, [1.3]);
deepEqual(actual.hasOwnProperty, [2.1, 2.4]);
});
test('should work with an object for `collection`', function() {
var actual = _.groupBy({ 'a': 1.3, 'b': 2.1, 'c': 2.4 }, function(num) {
return Math.floor(num);
});
deepEqual(actual, { '1': [1.3], '2': [2.1, 2.4] });
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('lodash.indexOf');
(function() {
var array = [1, 2, 3, 1, 2, 3];
test('should work with a positive `fromIndex`', function() {
equal(_.indexOf(array, 1, 2), 3);
});
test('should work with `fromIndex` >= `array.length`', function() {
equal(_.indexOf(array, 1, 6), -1);
equal(_.indexOf(array, undefined, 6), -1);
equal(_.indexOf(array, 1, 8), -1);
equal(_.indexOf(array, undefined, 8), -1);
});
test('should work with a negative `fromIndex`', function() {
equal(_.indexOf(array, 2, -3), 4);
});
test('should work with a negative `fromIndex` <= `-array.length`', function() {
equal(_.indexOf(array, 1, -6), 0);
equal(_.indexOf(array, 2, -8), 1);
});
test('should ignore non-number `fromIndex` values', function() {
equal(_.indexOf([1, 2, 3], 1, '1'), 0);
});
test('should work with `isSorted`', function() {
equal(_.indexOf([1, 2, 3], 1, true), 0);
});
}());
/*--------------------------------------------------------------------------*/
@@ -212,19 +414,39 @@
(function() {
test('returns an empty collection for `n` of `0`', function() {
var array = [1, 2, 3, 4];
var array = [1, 2, 3];
deepEqual(_.initial(array, 0), []);
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('lodash.invoke');
(function() {
test('should work with an object for `collection`', function() {
var object = { 'a': 1, 'b': 2, 'c': 3 };
deepEqual(_.invoke(object, 'toFixed', 1), ['1.0', '2.0', '3.0']);
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('lodash.isEmpty');
(function() {
test('fixes the JScript [[DontEnum]] bug (test in IE < 9)', function() {
equal(_.isEmpty(shadowed), false);
});
test('skips the prototype property of functions (test in Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1)', function() {
function Foo() {}
Foo.prototype.a = 1;
equal(_.isEmpty(Foo), true);
Foo.prototype = { 'a': 1 };
equal(_.isEmpty(Foo), true);
});
}());
/*--------------------------------------------------------------------------*/
@@ -257,30 +479,54 @@
Foo.prototype.a = 1;
deepEqual(_.keys(Foo.prototype), ['a']);
deepEqual(_.keys(shadowed).sort(),
'constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf'.split(' '));
deepEqual(_.keys(shadowed).sort(), shadowedKeys);
});
test('skips the prototype property of functions (test in Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1)', function() {
function Foo() {}
Foo.prototype.c = 3;
Foo.a = 1;
Foo.b = 2;
var expected = ['a', 'b'];
deepEqual(_.keys(Foo), expected);
Foo.prototype = { 'c': 3 };
deepEqual(_.keys(Foo), expected);
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('lodash.lastIndexOf');
(function() {
var i = -1,
largeArray = [];
var array = [1, 2, 3, 1, 2, 3];
while (++i <= 1e6) {
largeArray[i] = i;
}
_.each(['max', 'min'], function(methodName) {
QUnit.module('lodash.' + methodName);
test('should work with a positive `fromIndex`', function() {
equal(_.lastIndexOf(array, 1, 2), 0);
});
test('does not error when computing the ' + methodName + ' value of massive arrays', function() {
try {
var actual = _[methodName](largeArray);
} catch(e) { }
test('should work with `fromIndex` >= `array.length`', function() {
equal(_.lastIndexOf(array, undefined, 6), -1);
equal(_.lastIndexOf(array, 1, 6), 3);
equal(_.lastIndexOf(array, undefined, 8), -1);
equal(_.lastIndexOf(array, 1, 8), 3);
});
equal(actual, methodName == 'max' ? 1e6 : 0);
});
test('should work with a negative `fromIndex`', function() {
equal(_.lastIndexOf(array, 2, -3), 1);
});
test('should work with a negative `fromIndex` <= `-array.length`', function() {
equal(_.lastIndexOf(array, 1, -6), 0);
equal(_.lastIndexOf(array, 2, -8), -1);
});
test('should ignore non-number `fromIndex` values', function() {
equal(_.lastIndexOf([1, 2, 3], 3, '1'), 2);
equal(_.lastIndexOf([1, 2, 3], 3, true), 2);
});
}());
@@ -328,19 +574,69 @@
/*--------------------------------------------------------------------------*/
QUnit.module('lodash.pluck');
(function() {
test('should work with an object for `collection`', function() {
var object = { 'a': [1], 'b': [1, 2], 'c': [1, 2, 3] };
deepEqual(_.pluck(object, 'length'), [1, 2, 3]);
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('lodash.reduceRight');
(function() {
test('should pass the correct `callback` arguments when iterating an object', function() {
var args,
object = { 'a': 'A', 'b': 'B', 'c': 'C' },
keys = _.keys(object);
object = { 'a': 'A', 'b': 'B' },
lastKey = _.keys(object).pop();
var expected = lastKey == 'a'
? ['A', 'B', 'b', object]
: ['B', 'A', 'a', object];
_.reduceRight(object, function() {
args || (args = slice.call(arguments));
});
deepEqual(args, ['C', 'B', 'b', object]);
deepEqual(args, expected);
});
test('should treat array-like object with invalid `length` as a regular object', function() {
var args,
object = { 'a': 'A', 'length': -1 },
lastKey = _.keys(object).pop();
var expected = lastKey == 'a'
? ['A', '-1', 'length', object]
: [-1, 'A', 'a', object];
_.reduceRight(object, function() {
args || (args = slice.call(arguments));
});
deepEqual(args, expected);
});
_.each([
{ 'kind': 'literal', 'value': 'abc' },
{ 'kind': 'object', 'value': Object('abc') }
],
function(data) {
test('should work with a string ' + data.kind + ' for `collection` (test in IE < 9)', function() {
var args,
collection = data.value;
var actual = _.reduceRight(collection, function(accumulator, value) {
args || (args = slice.call(arguments));
return accumulator + value;
});
deepEqual(args, ['c', 'b', 1, collection]);
equal(actual, 'cba');
});
});
}());
@@ -349,14 +645,29 @@
QUnit.module('lodash.size');
(function() {
var args = arguments;
test('should detect the size of a string value', function() {
equal(_.size('abc'), 3);
});
test('should allow a falsey `object` argument', function() {
var func = _.size;
try {
var actual = [func(), func(undefined), func(null), func(false), func(0)];
} catch(e) { }
deepEqual(actual, [0, 0, 0, 0, 0]);
});
test('should work with `arguments` objects (test in IE < 9)', function() {
equal(_.size(args), 3);
});
test('fixes the JScript [[DontEnum]] bug (test in IE < 9)', function() {
equal(_.size(shadowed), 7);
});
}());
}(1, 2, 3));
/*--------------------------------------------------------------------------*/
@@ -364,11 +675,44 @@
(function() {
test('supports the `thisArg` argument', function() {
var actual = _.sortBy([1, 2, 3, 4], function(num) {
var actual = _.sortBy([1, 2, 3], function(num) {
return this.sin(num);
}, Math);
deepEqual(actual, [4, 3, 1, 2]);
deepEqual(actual, [3, 1, 2]);
});
test('should work with an object for `collection`', function() {
var actual = _.sortBy({ 'a': 1, 'b': 2, 'c': 3 }, function(num) {
return Math.sin(num);
});
deepEqual(actual, [3, 1, 2]);
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('lodash.sortedIndex');
(function() {
test('supports the `thisArg` argument', function() {
var actual = _.sortedIndex([1, 2, 3], 4, function(num) {
return this.sin(num);
}, Math);
equal(actual, 0);
});
test('supports arrays with lengths larger than `Math.pow(2, 31) - 1`', function() {
var length = Math.pow(2, 32) - 1,
index = length - 1,
array = Array(length),
steps = 0;
array[index] = index;
_.sortedIndex(array, index, function() { steps++; });
equal(steps, 33);
});
}());
@@ -378,7 +722,7 @@
(function() {
test('supports recursive calls', function() {
var compiled = _.template('<%= a %><% a = _.template(c, object) %><%= a %>'),
var compiled = _.template('<%= a %><% a = _.template(c, obj) %><%= a %>'),
data = { 'a': 'A', 'b': 'B', 'c': '<%= b %>' };
equal(compiled(data), 'AB');
@@ -389,6 +733,47 @@
_.template('', null, options);
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 work with complex "interpolate" delimiters', function() {
_.each({
'<%= a + b %>': '3',
'<%= b - a %>': '1',
'<%= a = b %>': '2',
'<%= !a %>': 'false',
'<%= ~a %>': '-2',
'<%= a * b %>': '2',
'<%= a / b %>': '0.5',
'<%= a % b %>': '1',
'<%= a >> b %>': '0',
'<%= a << b %>': '4',
'<%= a & b %>': '0',
'<%= a ^ b %>': '3',
'<%= a | b %>': '3',
'<%= {}.toString.call(0) %>': '[object Number]',
'<%= a.toFixed(2) %>': '1.00',
'<%= obj["a"] %>': '1',
'<%= delete a %>': 'true',
'<%= "a" in obj %>': 'true',
'<%= obj instanceof Object %>': 'true',
'<%= new Boolean %>': 'false',
'<%= typeof a %>': 'number',
'<%= void a %>': ''
}, function(value, key) {
var compiled = _.template(key),
data = { 'a': 1, 'b': 2 };
equal(compiled(data), value);
});
});
}());
/*--------------------------------------------------------------------------*/
@@ -412,7 +797,24 @@
while ((new Date - start) < limit) {
throttled();
}
equal(counter, 3);
ok(counter > 1);
});
asyncTest('supports recursive calls', function() {
var counter = 0;
var throttled = _.throttle(function() {
counter++;
if (counter < 4) {
throttled();
}
}, 100);
setTimeout(function() {
ok(counter > 1);
QUnit.start();
}, 220);
throttled();
});
}());
@@ -429,15 +831,49 @@
deepEqual(_.toArray(array), [3, 2, 1]);
});
test('should treat array-like-objects like arrays', function() {
test('should treat array-like objects like arrays', function() {
var object = { '0': 'a', '1': 'b', '2': 'c', 'length': 3 };
deepEqual(_.toArray(object), ['a', 'b', 'c']);
deepEqual(_.toArray(args), [1, 2, 3]);
});
test('should treat array-like object with invalid `length` as a regular object', function() {
var object = { 'length': -1 };
deepEqual(_.toArray(object), [-1]);
});
test('should work with a string for `collection` (test in IE < 9)', function() {
deepEqual(_.toArray('abc'), ['a', 'b', 'c']);
deepEqual(_.toArray(Object('abc')), ['a', 'b', 'c']);
});
}(1, 2, 3));
/*--------------------------------------------------------------------------*/
QUnit.module('lodash.uniq');
(function() {
test('supports the `thisArg` argument', function() {
var actual = _.uniq([1, 2, 1.5, 3, 2.5], function(num) {
return this.floor(num);
}, Math);
deepEqual(actual, [1, 2, 3]);
});
}());
/*--------------------------------------------------------------------------*/
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() {
@@ -466,7 +902,102 @@
/*--------------------------------------------------------------------------*/
// explicitly call `QUnit.start()` for Narwhal, Rhino, and RingoJS
QUnit.start();
QUnit.module('lodash "Arrays" methods');
(function() {
test('should allow a falsey `array` argument', function() {
_.each([
'compact',
'difference',
'first',
'flatten',
'groupBy',
'indexOf',
'initial',
'intersection',
'last',
'lastIndexOf',
'max',
'min',
'range',
'rest',
'shuffle',
'sortBy',
'sortedIndex',
'union',
'uniq',
'without',
'zip',
'zipObject'
], function(methodName) {
var func = _[methodName],
pass = true;
try {
func();
func(undefined);
func(null);
func(false);
func(0);
} catch(e) {
pass = false;
}
ok(pass, methodName + ' allows a falsey `array` argument');
});
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('lodash "Collections" methods');
(function() {
test('should allow a falsey `collection` argument', function() {
_.each([
'contains',
'every',
'filter',
'find',
'forEach',
'invoke',
'map',
'pluck',
'reduce',
'reduceRight',
'reject',
'some',
'toArray'
], function(methodName) {
var func = _[methodName],
identity = _.identity,
pass = true;
try {
if (/^(?:contains|toArray)$/.test(methodName)) {
func();
func(undefined);
func(null);
func(false);
func(0);
}
else {
func(undefined, identity);
func(null, identity);
func(false, identity);
func(0, identity);
}
} catch(e) {
pass = false;
}
ok(pass, methodName + ' allows a falsey `collection` argument');
});
});
}());
/*--------------------------------------------------------------------------*/
// explicitly call `QUnit.start()` for Narwhal, Rhino, and RingoJS
if (!window.document) {
QUnit.start();
}
}(typeof global == 'object' && global || this));

View File

@@ -22,7 +22,10 @@
<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="../lodash.js"></script>
<script src="test-ui.js"></script>
<script>
document.write('<script src="../' + QUnit.config.lodashFilename + '.js"><\/script>');
</script>
<script src="../vendor/underscore/test/collections.js"></script>
<script src="../vendor/underscore/test/arrays.js"></script>
<script src="../vendor/underscore/test/functions.js"></script>
@@ -36,4 +39,4 @@
<li><%= data %></li>
</script>
</body>
</html>
</html>

1
vendor/backbone vendored

Submodule vendor/backbone deleted from e34e14c466

22
vendor/backbone/LICENSE vendored Normal file
View File

@@ -0,0 +1,22 @@
Copyright (c) 2010-2012 Jeremy Ashkenas, DocumentCloud
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

26
vendor/backbone/README.md vendored Normal file
View File

@@ -0,0 +1,26 @@
____ __ __
/\ _`\ /\ \ /\ \ __
\ \ \ \ \ __ ___\ \ \/'\\ \ \____ ___ ___ __ /\_\ ____
\ \ _ <' /'__`\ /'___\ \ , < \ \ '__`\ / __`\ /' _ `\ /'__`\ \/\ \ /',__\
\ \ \ \ \/\ \ \.\_/\ \__/\ \ \\`\\ \ \ \ \/\ \ \ \/\ \/\ \/\ __/ __ \ \ \/\__, `\
\ \____/\ \__/.\_\ \____\\ \_\ \_\ \_,__/\ \____/\ \_\ \_\ \____\/\_\_\ \ \/\____/
\/___/ \/__/\/_/\/____/ \/_/\/_/\/___/ \/___/ \/_/\/_/\/____/\/_/\ \_\ \/___/
\ \____/
\/___/
(_'_______________________________________________________________________________'_)
(_.———————————————————————————————————————————————————————————————————————————————._)
Backbone supplies structure to JavaScript-heavy applications by providing models key-value binding and custom events, collections with a rich API of enumerable functions, views with declarative event handling, and connects it all to your existing application over a RESTful JSON interface.
For Docs, License, Tests, pre-packed downloads, and everything else, really, see:
http://backbonejs.org
To suggest a feature, report a bug, or general discussion:
http://github.com/documentcloud/backbone/issues/
All contributors are listed here:
http://github.com/documentcloud/backbone/contributors
Special thanks to Robert Kieffer for the original philosophy behind Backbone.
http://github.com/broofa

1461
vendor/backbone/backbone.js vendored Normal file

File diff suppressed because it is too large Load Diff

669
vendor/backbone/test/collection.js vendored Normal file
View File

@@ -0,0 +1,669 @@
$(document).ready(function() {
var lastRequest = null;
var sync = Backbone.sync;
var a, b, c, d, e, col, otherCol;
module("Backbone.Collection", {
setup: function() {
a = new Backbone.Model({id: 3, label: 'a'});
b = new Backbone.Model({id: 2, label: 'b'});
c = new Backbone.Model({id: 1, label: 'c'});
d = new Backbone.Model({id: 0, label: 'd'});
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() {
equal(col.first(), a, "a should be first");
equal(col.last(), d, "d should be last");
col.comparator = function(a, b) {
return a.id > b.id ? -1 : 1;
};
col.sort();
equal(col.first(), a, "a should be first");
equal(col.last(), d, "d should be last");
col.comparator = function(model) { return model.id; };
col.sort();
equal(col.first(), d, "d should be first");
equal(col.last(), a, "a should be last");
equal(col.length, 4);
});
test("Collection: 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() {
var col = new Backbone.Collection();
var MongoModel = Backbone.Model.extend({
idAttribute: '_id'
});
var model = new MongoModel({_id: 100});
col.push(model);
equal(col.get(100), model);
model.set({_id: 101});
equal(col.get(101), model);
});
test("Collection: update index when id changes", 3, function() {
var col = new Backbone.Collection();
col.add([
{id : 0, name : 'one'},
{id : 1, name : 'two'}
]);
var one = col.get(0);
equal(one.get('name'), 'one');
one.set({id : 101});
equal(col.get(0), null);
equal(col.get(101).get('name'), 'one');
});
test("Collection: at", 1, function() {
equal(col.at(2), c);
});
test("Collection: pluck", 1, function() {
equal(col.pluck('label').join(' '), 'a b c d');
});
test("Collection: add", 11, function() {
var added, opts, secondAdded;
added = opts = secondAdded = null;
e = new Backbone.Model({id: 10, label : 'e'});
otherCol.add(e);
otherCol.on('add', function() {
secondAdded = true;
});
col.on('add', function(model, collection, options){
added = model.get('label');
equal(options.index, 4);
opts = options;
});
col.add(e, {amazing: true});
equal(added, 'e');
equal(col.length, 5);
equal(col.last(), e);
equal(otherCol.length, 1);
equal(secondAdded, null);
ok(opts.amazing);
var f = new Backbone.Model({id: 20, label : 'f'});
var g = new Backbone.Model({id: 21, label : 'g'});
var h = new Backbone.Model({id: 22, label : 'h'});
var atCol = new Backbone.Collection([f, g, h]);
equal(atCol.length, 3);
atCol.add(e, {at: 1});
equal(atCol.length, 4);
equal(atCol.at(1), e);
equal(atCol.last(), h);
});
test("Collection: 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++) {
equal(col.at(i).get('at'), i);
}
});
test("Collection: 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;
}
});
var col = new Col([{id: 2}, {id: 3}]);
col.add(new Backbone.Model({id: 1}), {at: 1});
equal(col.pluck('id').join(' '), '3 1 2');
});
test("Collection: 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() {
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() {
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'});
equal(col.first().get('name'), 'Moe');
col.add({id: 1, name: 'Moses'}, {merge: true});
equal(col.first().get('name'), 'Moses');
col.add({id: 1, name: 'Tim'}, {merge: true, silent: true});
equal(col.first().get('name'), 'Tim');
});
test("Collection: 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) {
counter++;
equal(e, model);
if (counter > 1) {
equal(collection, colF);
} else {
equal(collection, colE);
}
});
var colE = new Backbone.Collection([]);
colE.on('add', function(model, collection) {
equal(e, model);
equal(colE, collection);
});
var colF = new Backbone.Collection([]);
colF.on('add', function(model, collection) {
equal(e, model);
equal(colF, collection);
});
colE.add(e);
equal(e.collection, colE);
colF.add(e);
equal(e.collection, colE);
});
test("Collection: add model with parse", 1, function() {
var Model = Backbone.Model.extend({
parse: function(obj) {
obj.value += 1;
return obj;
}
});
var Col = Backbone.Collection.extend({model: Model});
var col = new Col;
col.add({value: 1}, {parse: true});
equal(col.at(0).get('value'), 2);
});
test("Collection: 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;
};
var tom = new Backbone.Model({name: 'Tom'});
var rob = new Backbone.Model({name: 'Rob'});
var tim = new Backbone.Model({name: 'Tim'});
col.add(tom);
col.add(rob);
col.add(tim);
equal(col.indexOf(rob), 0);
equal(col.indexOf(tim), 1);
equal(col.indexOf(tom), 2);
});
test("Collection: comparator that depends on `this`", 1, function() {
var col = new Backbone.Collection;
col.negative = function(num) {
return -num;
};
col.comparator = function(a) {
return this.negative(a.id);
};
col.add([{id: 1}, {id: 2}, {id: 3}]);
equal(col.pluck('id').join(' '), '3 2 1');
});
test("Collection: remove", 5, function() {
var removed = null;
var otherRemoved = null;
col.on('remove', function(model, col, options) {
removed = model.get('label');
equal(options.index, 3);
});
otherCol.on('remove', function(model, col, options) {
otherRemoved = true;
});
col.remove(d);
equal(removed, 'd');
equal(col.length, 3);
equal(col.first(), a);
equal(otherRemoved, null);
});
test("Collection: 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() {
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() {
var counter = 0;
var dj = new Backbone.Model();
var emcees = new Backbone.Collection([dj]);
emcees.on('change', function(){ counter++; });
dj.set({name : 'Kool'});
equal(counter, 1);
emcees.reset([]);
equal(dj.collection, undefined);
dj.set({name : 'Shadow'});
equal(counter, 1);
});
test("Collection: remove in multiple collections", 7, function() {
var modelData = {
id : 5,
title : 'Othello'
};
var passed = false;
var e = new Backbone.Model(modelData);
var f = new Backbone.Model(modelData);
f.on('remove', function() {
passed = true;
});
var colE = new Backbone.Collection([e]);
var colF = new Backbone.Collection([f]);
ok(e != f);
ok(colE.length == 1);
ok(colF.length == 1);
colE.remove(e);
equal(passed, false);
ok(colE.length == 0);
colF.remove(e);
ok(colF.length == 0);
equal(passed, true);
});
test("Collection: 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) {
counter++;
equal(e, model);
if (counter > 1) {
equal(collection, colE);
} else {
equal(collection, colF);
}
});
var colE = new Backbone.Collection([e]);
colE.on('remove', function(model, collection) {
equal(e, model);
equal(colE, collection);
});
var colF = new Backbone.Collection([e]);
colF.on('remove', function(model, collection) {
equal(e, model);
equal(colF, collection);
});
equal(colE, e.collection);
colF.remove(e);
ok(colF.length == 0);
ok(colE.length == 1);
equal(counter, 1);
equal(colE, e.collection);
colE.remove(e);
equal(null, e.collection);
ok(colE.length == 0);
equal(counter, 2);
});
test("Collection: 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]);
var colF = new Backbone.Collection([e]);
e.destroy();
ok(colE.length == 0);
ok(colF.length == 0);
equal(undefined, e.collection);
});
test("Colllection: non-persisted model destroy removes from all collections", 3, function() {
var e = new Backbone.Model({title: 'Othello'});
e.sync = function(method, model, options) { throw "should not be called"; };
var colE = new Backbone.Collection([e]);
var colF = new Backbone.Collection([e]);
e.destroy();
ok(colE.length == 0);
ok(colF.length == 0);
equal(undefined, e.collection);
});
test("Collection: fetch", 4, function() {
col.fetch();
equal(lastRequest.method, 'read');
equal(lastRequest.model, col);
equal(lastRequest.options.parse, true);
col.fetch({parse: false});
equal(lastRequest.options.parse, false);
});
test("Collection: create", 4, function() {
var model = col.create({label: 'f'}, {wait: true});
equal(lastRequest.method, 'create');
equal(lastRequest.model, model);
equal(model.get('label'), 'f');
equal(model.collection, col);
});
test("Collection: create enforces validation", 1, function() {
var ValidatingModel = Backbone.Model.extend({
validate: function(attrs) {
return "fail";
}
});
var ValidatingCollection = Backbone.Collection.extend({
model: ValidatingModel
});
var col = new ValidatingCollection();
equal(col.create({"foo":"bar"}), false);
});
test("Collection: a failing create runs the error callback", 1, function() {
var ValidatingModel = Backbone.Model.extend({
validate: function(attrs) {
return "fail";
}
});
var ValidatingCollection = Backbone.Collection.extend({
model: ValidatingModel
});
var flag = false;
var callback = function(model, error) { flag = true; };
var col = new ValidatingCollection();
col.create({"foo":"bar"}, { error: callback });
equal(flag, true);
});
test("collection: initialize", 1, function() {
var Collection = Backbone.Collection.extend({
initialize: function() {
this.one = 1;
}
});
var coll = new Collection;
equal(coll.one, 1);
});
test("Collection: 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() {
var coll = new Backbone.Collection([
{a: 1},
{a: 1},
{a: 1, b: 2},
{a: 2, b: 2},
{a: 3}
]);
equal(coll.where({a: 1}).length, 3);
equal(coll.where({a: 2}).length, 1);
equal(coll.where({a: 3}).length, 1);
equal(coll.where({b: 1}).length, 0);
equal(coll.where({b: 2}).length, 2);
equal(coll.where({a: 1, b: 2}).length, 1);
});
test("Collection: 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);
equal(col.indexOf(b), 1);
equal(col.size(), 4);
equal(col.rest().length, 3);
ok(!_.include(col.rest()), a);
ok(!_.include(col.rest()), d);
ok(!col.isEmpty());
ok(!_.include(col.without(d)), d);
equal(col.max(function(model){ return model.id; }).id, 3);
equal(col.min(function(model){ return model.id; }).id, 0);
deepEqual(col.chain()
.filter(function(o){ return o.id % 2 === 0; })
.map(function(o){ return o.id * 2; })
.value(),
[4, 0]);
});
test("Collection: reset", 10, function() {
var resetCount = 0;
var models = col.models;
col.on('reset', function() { resetCount += 1; });
col.reset([]);
equal(resetCount, 1);
equal(col.length, 0);
equal(col.last(), null);
col.reset(models);
equal(resetCount, 2);
equal(col.length, 4);
equal(col.last(), d);
col.reset(_.map(models, function(m){ return m.attributes; }));
equal(resetCount, 3);
equal(col.length, 4);
ok(col.last() !== d);
ok(_.isEqual(col.last().attributes, d.attributes));
});
test("Collection: reset passes caller options", 3, function() {
var Model = Backbone.Model.extend({
initialize: function(attrs, options) {
this.model_parameter = options.model_parameter;
}
});
var col = new (Backbone.Collection.extend({ model: Model }))();
col.reset([{ astring: "green", anumber: 1 }, { astring: "blue", anumber: 2 }], { model_parameter: 'model parameter' });
equal(col.length, 2);
col.each(function(model) {
equal(model.model_parameter, 'model parameter');
});
});
test("Collection: 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(){
var attrs = {};
var models = [attrs];
new Backbone.Collection().add(models);
equal(models.length, 1);
ok(attrs === models[0]);
});
test("#714: access `model.collection` in a brand new model.", 2, function() {
var col = new Backbone.Collection;
var Model = Backbone.Model.extend({
set: function(attrs) {
equal(attrs.prop, 'value');
equal(this.collection, col);
return this;
}
});
col.model = Model;
col.create({prop: 'value'});
});
test("#574, remove its own reference to the .models array.", 2, function() {
var col = new Backbone.Collection([
{id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}, {id: 6}
]);
equal(col.length, 6);
col.remove(col.models);
equal(col.length, 0);
});
test("#861, adding models to a collection which do not pass validation", 1, function() {
raises(function() {
var Model = Backbone.Model.extend({
validate: function(attrs) {
if (attrs.id == 3) return "id can't be 3";
}
});
var Collection = Backbone.Collection.extend({
model: Model
});
var col = new Collection;
col.add([{id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}, {id: 6}]);
}, function(e) {
return e.message === "Can't add an invalid model to a collection";
});
});
test("Collection: index with comparator", 4, function() {
var counter = 0;
var col = new Backbone.Collection([{id: 2}, {id: 4}], {
comparator: function(model){ return model.id; }
}).on('add', function(model, colleciton, options){
if (model.id == 1) {
equal(options.index, 0);
equal(counter++, 0);
}
if (model.id == 3) {
equal(options.index, 2);
equal(counter++, 1);
}
});
col.add([{id: 3}, {id: 1}]);
});
test("Collection: throwing during add leaves consistent state", 4, function() {
var col = new Backbone.Collection();
col.on('test', function() { ok(false); });
col.model = Backbone.Model.extend({
validate: function(attrs){ if (!attrs.valid) return 'invalid'; }
});
var model = new col.model({id: 1, valid: true});
raises(function() { col.add([model, {id: 2}]); });
model.trigger('test');
ok(!col.getByCid(model.cid));
ok(!col.get(1));
equal(col.length, 0);
});
test("Collection: multiple copies of the same model", 3, function() {
var col = new Backbone.Collection();
var model = new Backbone.Model();
col.add([model, model]);
equal(col.length, 1);
col.add([{id: 1}, {id: 1}]);
equal(col.length, 2);
equal(col.last().id, 1);
});
test("#964 - collection.get return inconsistent", 2, function() {
var c = new Backbone.Collection();
ok(c.get(null) === undefined);
ok(c.get() === undefined);
});
test("#1112 - passing options.model sets collection.model", 2, function() {
var Model = Backbone.Model.extend({});
var c = new Backbone.Collection([{id: 1}], {model: Model});
ok(c.model === Model);
ok(c.at(0) instanceof Model);
});
test("null and undefined are invalid ids.", 2, function() {
var model = new Backbone.Model({id: 1});
var collection = new Backbone.Collection([model]);
model.set({id: null});
ok(!collection.get('null'));
model.set({id: 1});
model.set({id: undefined});
ok(!collection.get('undefined'));
});
test("Collection: falsy comparator", 4, function(){
var Col = Backbone.Collection.extend({
comparator: function(model){ return model.id; }
});
var col = new Col();
var colFalse = new Col(null, {comparator: false});
var colNull = new Col(null, {comparator: null});
var colUndefined = new Col(null, {comparator: undefined});
ok(col.comparator);
ok(!colFalse.comparator);
ok(!colNull.comparator);
ok(colUndefined.comparator);
});
test("#1355 - `options` is passed to success callbacks", 2, function(){
var m = new Backbone.Model({x:1});
var col = new Backbone.Collection();
var opts = {
success: function(collection, resp, options){
ok(options);
}
};
col.sync = m.sync = function( method, collection, options ){
options.success();
};
col.fetch(opts);
col.create(m, opts);
});
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(); };
collection.on('sync', function() { ok(true); });
collection.fetch();
collection.create({id: 1});
});
test("#1447 - create with wait adds model.", function() {
var collection = new Backbone.Collection;
var model = new Backbone.Model;
model.sync = function(method, model, options){ options.success(); };
collection.on('add', function(){ ok(true); });
collection.create(model, {wait: true});
});
test("#1448 - add sorts collection after merge.", function() {
var collection = new Backbone.Collection([
{id: 1, x: 1},
{id: 2, x: 2}
]);
collection.comparator = function(model){ return model.get('x'); };
collection.add({id: 1, x: 3}, {merge: true});
deepEqual(collection.pluck('id'), [2, 1]);
});
});

195
vendor/backbone/test/events.js vendored Normal file
View File

@@ -0,0 +1,195 @@
$(document).ready(function() {
module("Backbone.Events");
test("Events: on and trigger", 2, function() {
var obj = { counter: 0 };
_.extend(obj,Backbone.Events);
obj.on('event', function() { obj.counter += 1; });
obj.trigger('event');
equal(obj.counter,1,'counter should be incremented.');
obj.trigger('event');
obj.trigger('event');
obj.trigger('event');
obj.trigger('event');
equal(obj.counter, 5, 'counter should be incremented five times.');
});
test("Events: binding and triggering multiple events", 4, function() {
var obj = { counter: 0 };
_.extend(obj,Backbone.Events);
obj.on('a b c', function() { obj.counter += 1; });
obj.trigger('a');
equal(obj.counter, 1);
obj.trigger('a b');
equal(obj.counter, 3);
obj.trigger('c');
equal(obj.counter, 4);
obj.off('a c');
obj.trigger('a b c');
equal(obj.counter, 5);
});
test("Events: trigger all for each event", 3, function() {
var a, b, obj = { counter: 0 };
_.extend(obj, Backbone.Events);
obj.on('all', function(event) {
obj.counter++;
if (event == 'a') a = true;
if (event == 'b') b = true;
})
.trigger('a b');
ok(a);
ok(b);
equal(obj.counter, 2);
});
test("Events: on, then unbind all functions", 1, function() {
var obj = { counter: 0 };
_.extend(obj,Backbone.Events);
var callback = function() { obj.counter += 1; };
obj.on('event', callback);
obj.trigger('event');
obj.off('event');
obj.trigger('event');
equal(obj.counter, 1, 'counter should have only been incremented once.');
});
test("Events: bind two callbacks, unbind only one", 2, function() {
var obj = { counterA: 0, counterB: 0 };
_.extend(obj,Backbone.Events);
var callback = function() { obj.counterA += 1; };
obj.on('event', callback);
obj.on('event', function() { obj.counterB += 1; });
obj.trigger('event');
obj.off('event', callback);
obj.trigger('event');
equal(obj.counterA, 1, 'counterA should have only been incremented once.');
equal(obj.counterB, 2, 'counterB should have been incremented twice.');
});
test("Events: unbind a callback in the midst of it firing", 1, function() {
var obj = {counter: 0};
_.extend(obj, Backbone.Events);
var callback = function() {
obj.counter += 1;
obj.off('event', callback);
};
obj.on('event', callback);
obj.trigger('event');
obj.trigger('event');
obj.trigger('event');
equal(obj.counter, 1, 'the callback should have been unbound.');
});
test("Events: 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); };
var incrB = function(){ obj.counterB += 1; obj.off('event', incrB); };
obj.on('event', incrA);
obj.on('event', incrB);
obj.trigger('event');
obj.trigger('event');
obj.trigger('event');
equal(obj.counterA, 1, 'counterA should have only been incremented once.');
equal(obj.counterB, 1, 'counterB should have only been incremented once.');
});
test("Events: bind a callback with a supplied context", 1, function () {
var TestClass = function () {
return this;
};
TestClass.prototype.assertTrue = function () {
ok(true, '`this` was bound to the callback');
};
var obj = _.extend({},Backbone.Events);
obj.on('event', function () { this.assertTrue(); }, (new TestClass));
obj.trigger('event');
});
test("Events: 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'); };
var incr2 = function(){ obj.counter += 1; };
obj.on('event', incr1);
obj.on('event', incr2);
obj.trigger('event');
equal(obj.counter, 3, 'counter should have been incremented three times');
});
test("Events: 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); })
.trigger('event');
equal(counter, 0, 'bind does not alter callback list');
obj.off()
.on('event', function(){ obj.off('event', incr).off('all', incr); })
.on('event', incr)
.on('all', incr)
.trigger('event');
equal(counter, 2, 'unbind does not alter callback list');
});
test("#1282 - 'all' callback list is retrieved after each event.", 1, function() {
var counter = 0;
var obj = _.extend({}, Backbone.Events);
var incr = function(){ counter++; };
obj.on('x', function() {
obj.on('y', incr).on('all', incr);
})
.trigger('x y');
strictEqual(counter, 2);
});
test("if no callback is provided, `on` is a noop", 0, function() {
_.extend({}, Backbone.Events).on('test').trigger('test');
});
test("remove all events for a specific context", 4, function() {
var obj = _.extend({}, Backbone.Events);
obj.on('x y all', function() { ok(true); });
obj.on('x y all', function() { ok(false); }, obj);
obj.off(null, null, obj);
obj.trigger('x y');
});
test("remove all events for a specific callback", 4, function() {
var obj = _.extend({}, Backbone.Events);
var success = function() { ok(true); };
var fail = function() { ok(false); };
obj.on('x y all', success);
obj.on('x y all', fail);
obj.off(null, fail);
obj.trigger('x y');
});
test("off is chainable", 3, function() {
var obj = _.extend({}, Backbone.Events);
// With no events
ok(obj.off() === obj);
// When removing all events
obj.on('event', function(){}, obj);
ok(obj.off() === obj);
// When removing some events
obj.on('event', function(){}, obj);
ok(obj.off('event') === obj);
});
test("#1310 - off does not skip consecutive events", 0, function() {
var obj = _.extend({}, Backbone.Events);
obj.on('event', function() { ok(false); }, obj);
obj.on('event', function() { ok(false); }, obj);
obj.off(null, null, obj);
obj.trigger('event');
});
});

855
vendor/backbone/test/model.js vendored Normal file
View File

@@ -0,0 +1,855 @@
$(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", {
setup: function() {
doc = new proxy({
id : '1-the-tempest',
title : "The Tempest",
author : "Bill Shakespeare",
length : 123
});
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() {
var Model = Backbone.Model.extend({
initialize: function() {
this.one = 1;
equal(this.collection, collection);
}
});
var model = new Model({}, {collection: collection});
equal(model.one, 1);
equal(model.collection, collection);
});
test("Model: initialize with attributes and options", 1, function() {
var Model = Backbone.Model.extend({
initialize: function(attributes, options) {
this.one = options.one;
}
});
var model = new Model({}, {one: 1});
equal(model.one, 1);
});
test("Model: initialize with parsed attributes", 1, function() {
var Model = Backbone.Model.extend({
parse: function(obj) {
obj.value += 1;
return obj;
}
});
var model = new Model({value: 1}, {parse: true});
equal(model.get('value'), 2);
});
test("Model: url", 3, function() {
doc.urlRoot = null;
equal(doc.url(), '/collection/1-the-tempest');
doc.collection.url = '/collection/';
equal(doc.url(), '/collection/1-the-tempest');
doc.collection = null;
raises(function() { doc.url(); });
doc.collection = collection;
});
test("Model: url when using urlRoot, and uri encoding", 2, function() {
var Model = Backbone.Model.extend({
urlRoot: '/collection'
});
var model = new Model();
equal(model.url(), '/collection');
model.set({id: '+1+'});
equal(model.url(), '/collection/%2B1%2B');
});
test("Model: 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';
}
});
var model = new Model({parent_id: 1});
equal(model.url(), '/nested/1/collection');
model.set({id: 2});
equal(model.url(), '/nested/1/collection/2');
});
test("Model: clone", 8, function() {
var a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3});
var b = a.clone();
equal(a.get('foo'), 1);
equal(a.get('bar'), 2);
equal(a.get('baz'), 3);
equal(b.get('foo'), a.get('foo'), "Foo should be the same on the clone.");
equal(b.get('bar'), a.get('bar'), "Bar should be the same on the clone.");
equal(b.get('baz'), a.get('baz'), "Baz should be the same on the clone.");
a.set({foo : 100});
equal(a.get('foo'), 100);
equal(b.get('foo'), 1, "Changing a parent attribute does not change the clone.");
});
test("Model: 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 });
ok(!a.isNew(), "any defined ID is legal, negative or positive");
a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3, 'id': 0 });
ok(!a.isNew(), "any defined ID is legal, including zero");
ok( new Backbone.Model({ }).isNew(), "is true when there is no id");
ok(!new Backbone.Model({ 'id': 2 }).isNew(), "is false for a positive integer");
ok(!new Backbone.Model({ 'id': -5 }).isNew(), "is false for a negative integer");
});
test("Model: get", 2, function() {
equal(doc.get('title'), 'The Tempest');
equal(doc.get('author'), 'Bill Shakespeare');
});
test("Model: escape", 5, function() {
equal(doc.escape('title'), 'The Tempest');
doc.set({audience: 'Bill & Bob'});
equal(doc.escape('audience'), 'Bill &amp; Bob');
doc.set({audience: 'Tim > Joan'});
equal(doc.escape('audience'), 'Tim &gt; Joan');
doc.set({audience: 10101});
equal(doc.escape('audience'), '10101');
doc.unset('audience');
equal(doc.escape('audience'), '');
});
test("Model: has", 10, function() {
var model = new Backbone.Model();
strictEqual(model.has('name'), false);
model.set({
'0': 0,
'1': 1,
'true': true,
'false': false,
'empty': '',
'name': 'name',
'null': null,
'undefined': undefined
});
strictEqual(model.has('0'), true);
strictEqual(model.has('1'), true);
strictEqual(model.has('true'), true);
strictEqual(model.has('false'), true);
strictEqual(model.has('empty'), true);
strictEqual(model.has('name'), true);
model.unset('name');
strictEqual(model.has('name'), false);
strictEqual(model.has('null'), false);
strictEqual(model.has('undefined'), false);
});
test("Model: 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; });
a.set({'foo': 2});
ok(a.get('foo') == 2, "Foo should have changed.");
ok(changeCount == 1, "Change count should have incremented.");
a.set({'foo': 2}); // set with value that is not new shouldn't fire change event
ok(a.get('foo') == 2, "Foo should NOT have changed, still 2");
ok(changeCount == 1, "Change count should NOT have incremented.");
a.validate = function(attrs) {
equal(attrs.foo, void 0, "don't ignore values when unsetting");
};
a.unset('foo');
equal(a.get('foo'), void 0, "Foo should have changed");
delete a.validate;
ok(changeCount == 2, "Change count should have incremented for unset.");
a.unset('id');
equal(a.id, undefined, "Unsetting the id should remove the id property.");
});
test("Model: multiple unsets", 1, function() {
var i = 0;
var counter = function(){ i++; };
var model = new Backbone.Model({a: 1});
model.on("change:a", counter);
model.set({a: 2});
model.unset('a');
model.unset('a');
equal(i, 2, 'Unset does not fire an event for missing attributes.');
});
test("Model: unset and changedAttributes", 2, function() {
var model = new Backbone.Model({a: 1});
model.unset('a', {silent: true});
var changedAttributes = model.changedAttributes();
ok('a' in changedAttributes, 'changedAttributes should contain unset properties');
changedAttributes = model.changedAttributes();
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() {
var MongoModel = Backbone.Model.extend({idAttribute : '_id'});
var model = new MongoModel({id: 'eye-dee', _id: 25, title: 'Model'});
equal(model.get('id'), 'eye-dee');
equal(model.id, 25);
equal(model.isNew(), false);
model.unset('_id');
equal(model.id, undefined);
equal(model.isNew(), true);
});
test("Model: 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() {
var changed;
var model = new Backbone.Model({id: 1, name : "Model"});
model.on("change:name", function(){ changed = true; });
model.on("change", function() {
var changedAttrs = model.changedAttributes();
ok('name' in changedAttrs);
});
model.clear();
equal(changed, true);
equal(model.get('name'), undefined);
});
test("Model: defaults", 4, function() {
var Defaulted = Backbone.Model.extend({
defaults: {
"one": 1,
"two": 2
}
});
var model = new Defaulted({two: null});
equal(model.get('one'), 1);
equal(model.get('two'), null);
Defaulted = Backbone.Model.extend({
defaults: function() {
return {
"one": 3,
"two": 4
};
}
});
var model = new Defaulted({two: null});
equal(model.get('one'), 3);
equal(model.get('two'), null);
});
test("Model: change, hasChanged, changedAttributes, previous, previousAttributes", 12, function() {
var model = new Backbone.Model({name : "Tim", age : 10});
equal(model.changedAttributes(), false);
model.on('change', function() {
ok(model.hasChanged('name'), 'name changed');
ok(!model.hasChanged('age'), 'age did not');
ok(_.isEqual(model.changedAttributes(), {name : 'Rob'}), 'changedAttributes returns the changed attrs');
equal(model.previous('name'), 'Tim');
ok(_.isEqual(model.previousAttributes(), {name : "Tim", age : 10}), 'previousAttributes is correct');
});
equal(model.hasChanged(), false);
equal(model.hasChanged(undefined), false);
model.set({name : 'Rob'}, {silent : true});
equal(model.hasChanged(), true);
equal(model.hasChanged(undefined), true);
equal(model.hasChanged('name'), true);
model.change();
equal(model.get('name'), 'Rob');
});
test("Model: 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() {
var value;
var model = new Backbone.Model({name: 'Rob'});
model.on('change', function(model, options) {
value = options.prefix + model.get('name');
});
model.set({name: 'Bob'}, {silent: true});
model.change({prefix: 'Mr. '});
equal(value, 'Mr. Bob');
model.set({name: 'Sue'}, {prefix: 'Ms. '});
equal(value, 'Ms. Sue');
});
test("Model: change after initialize", 1, function () {
var changed = 0;
var attrs = {id: 1, label: 'c'};
var obj = new Backbone.Model(attrs);
obj.on('change', function() { changed += 1; });
obj.set(attrs);
equal(changed, 0);
});
test("Model: save within change event", 1, function () {
var model = new Backbone.Model({firstName : "Taylor", lastName: "Swift"});
model.on('change', function () {
model.save();
ok(_.isEqual(lastRequest.model, model));
});
model.set({lastName: 'Hicks'});
});
test("Model: validate after save", 1, function() {
var lastError, model = new Backbone.Model();
model.validate = function(attrs) {
if (attrs.admin) return "Can't change admin status.";
};
model.sync = function(method, model, options) {
options.success.call(this, {admin: true});
};
model.save(null, {error: function(model, error) {
lastError = error;
}});
equal(lastError, "Can't change admin status.");
});
test("Model: isValid", 5, function() {
var model = new Backbone.Model({valid: true});
model.validate = function(attrs) {
if (!attrs.valid) return "invalid";
};
equal(model.isValid(), true);
equal(model.set({valid: false}), false);
equal(model.isValid(), true);
ok(model.set('valid', false, {silent: true}));
equal(model.isValid(), false);
});
test("Model: save", 2, function() {
doc.save({title : "Henry V"});
equal(lastRequest.method, 'update');
ok(_.isEqual(lastRequest.model, doc));
});
test("Model: save in positional style", 1, function() {
var model = new Backbone.Model();
model.sync = function(method, model, options) {
options.success();
};
model.save('title', 'Twelfth Night');
equal(model.get('title'), 'Twelfth Night');
});
test("Model: fetch", 2, function() {
doc.fetch();
equal(lastRequest.method, 'read');
ok(_.isEqual(lastRequest.model, doc));
});
test("Model: destroy", 3, function() {
doc.destroy();
equal(lastRequest.method, 'delete');
ok(_.isEqual(lastRequest.model, doc));
var newModel = new Backbone.Model;
equal(newModel.destroy(), false);
});
test("Model: 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() {
var lastError;
var model = new Backbone.Model();
model.validate = function(attrs) {
if (attrs.admin != this.get('admin')) return "Can't change admin status.";
};
model.on('error', function(model, error) {
lastError = error;
});
var result = model.set({a: 100});
equal(result, model);
equal(model.get('a'), 100);
equal(lastError, undefined);
result = model.set({admin: true}, {silent: true});
equal(model.get('admin'), true);
result = model.set({a: 200, admin: false});
equal(lastError, "Can't change admin status.");
equal(result, false);
equal(model.get('a'), 100);
});
test("Model: validate on unset and clear", 6, function() {
var error;
var model = new Backbone.Model({name: "One"});
model.validate = function(attrs) {
if (!attrs.name) {
error = true;
return "No thanks.";
}
};
model.set({name: "Two"});
equal(model.get('name'), 'Two');
equal(error, undefined);
model.unset('name');
equal(error, true);
equal(model.get('name'), 'Two');
model.clear();
equal(model.get('name'), 'Two');
delete model.validate;
model.clear();
equal(model.get('name'), undefined);
});
test("Model: validate with error callback", 8, function() {
var lastError, boundError;
var model = new Backbone.Model();
model.validate = function(attrs) {
if (attrs.admin) return "Can't change admin status.";
};
var callback = function(model, error) {
lastError = error;
};
model.on('error', function(model, error) {
boundError = true;
});
var result = model.set({a: 100}, {error: callback});
equal(result, model);
equal(model.get('a'), 100);
equal(lastError, undefined);
equal(boundError, undefined);
result = model.set({a: 200, admin: true}, {error: callback});
equal(result, false);
equal(model.get('a'), 100);
equal(lastError, "Can't change admin status.");
equal(boundError, undefined);
});
test("Model: defaults always extend attrs (#459)", 2, function() {
var Defaulted = Backbone.Model.extend({
defaults: {one: 1},
initialize : function(attrs, opts) {
equal(this.attributes.one, 1);
}
});
var providedattrs = new Defaulted({});
var emptyattrs = new Defaulted();
});
test("Model: Inherit class properties", 6, function() {
var Parent = Backbone.Model.extend({
instancePropSame: function() {},
instancePropDiff: function() {}
}, {
classProp: function() {}
});
var Child = Parent.extend({
instancePropDiff: function() {}
});
var adult = new Parent;
var kid = new Child;
equal(Child.classProp, Parent.classProp);
notEqual(Child.classProp, undefined);
equal(kid.instancePropSame, adult.instancePropSame);
notEqual(kid.instancePropSame, undefined);
notEqual(Child.prototype.instancePropDiff, Parent.prototype.instancePropDiff);
notEqual(Child.prototype.instancePropDiff, undefined);
});
test("Model: Nested change events don't clobber previous attributes", 4, function() {
new Backbone.Model()
.on('change:state', function(model, newState) {
equal(model.previous('state'), undefined);
equal(newState, 'hello');
// Fire a nested change event.
model.set({other: 'whatever'});
})
.on('change:state', function(model, newState) {
equal(model.previous('state'), undefined);
equal(newState, 'hello');
})
.set({state: 'hello'});
});
test("hasChanged/set should use same comparison", 2, function() {
var changed = 0, model = new Backbone.Model({a: null});
model.on('change', function() {
ok(this.hasChanged('a'));
})
.on('change:a', function() {
changed++;
})
.set({a: undefined});
equal(changed, 1);
});
test("#582, #425, change:attribute callbacks should fire after all changes have occurred", 9, function() {
var model = new Backbone.Model;
var assertion = function() {
equal(model.get('a'), 'a');
equal(model.get('b'), 'b');
equal(model.get('c'), 'c');
};
model.on('change:a', assertion);
model.on('change:b', assertion);
model.on('change:c', assertion);
model.set({a: 'a', b: 'b', c: 'c'});
});
test("#871, set with attributes property", 1, function() {
var model = new Backbone.Model();
model.set({attributes: true});
ok(model.has('attributes'));
});
test("set value regardless of equality/change", 1, function() {
var model = new Backbone.Model({x: []});
var a = [];
model.set({x: a});
ok(model.get('x') === a);
});
test("unset fires change for undefined attributes", 1, function() {
var model = new Backbone.Model({x: undefined});
model.on('change:x', function(){ ok(true); });
model.unset('x');
});
test("set: undefined values", 1, function() {
var model = new Backbone.Model({x: undefined});
ok('x' in model.attributes);
});
test("change fires change:attr", 1, function() {
var model = new Backbone.Model({x: 1});
model.set({x: 2}, {silent: true});
model.on('change:x', function(){ ok(true); });
model.change();
});
test("hasChanged is false after original values are set", 2, function() {
var model = new Backbone.Model({x: 1});
model.on('change:x', function(){ ok(false); });
model.set({x: 2}, {silent: true});
ok(model.hasChanged());
model.set({x: 1}, {silent: true});
ok(!model.hasChanged());
});
test("save with `wait` succeeds without `validate`", 1, function() {
var model = new Backbone.Model();
model.save({x: 1}, {wait: true});
ok(lastRequest.model === model);
});
test("`hasChanged` for falsey keys", 2, function() {
var model = new Backbone.Model();
model.set({x: true}, {silent: true});
ok(!model.hasChanged(0));
ok(!model.hasChanged(''));
});
test("`previous` for falsey keys", 2, function() {
var model = new Backbone.Model({0: true, '': true});
model.set({0: false, '': false}, {silent: true});
equal(model.previous(0), true);
equal(model.previous(''), true);
});
test("`save` with `wait` sends correct attributes", 5, function() {
var changed = 0;
var model = new Backbone.Model({x: 1, y: 2});
model.on('change:x', function() { changed++; });
model.save({x: 3}, {wait: true});
deepEqual(JSON.parse(ajaxParams.data), {x: 3, y: 2});
equal(model.get('x'), 1);
equal(changed, 0);
lastRequest.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.save({x: 1}, {wait: true});
equal(model.get('x'), void 0);
});
test("#1030 - `save` with `wait` results in correct attributes if success is called during sync", 2, function() {
var model = new Backbone.Model({x: 1, y: 2});
model.sync = function(method, model, options) {
options.success();
};
model.on("change:x", function() { ok(true); });
model.save({x: 3}, {wait: true});
equal(model.get('x'), 3);
});
test("save with wait validates attributes", 1, function() {
var model = new Backbone.Model();
model.validate = function() { ok(true); };
model.save({x: 1}, {wait: true});
});
test("nested `set` during `'change:attr'`", 2, function() {
var events = [];
var model = new Backbone.Model();
model.on('all', function(event) { events.push(event); });
model.on('change', function() {
model.set({z: true}, {silent:true});
});
model.on('change:x', function() {
model.set({y: true});
});
model.set({x: true});
deepEqual(events, ['change:y', 'change:x', 'change']);
events = [];
model.change();
deepEqual(events, ['change:z', 'change']);
});
test("nested `change` only fires once", 1, function() {
var model = new Backbone.Model();
model.on('change', function() {
ok(true);
model.change();
});
model.set({x: true});
});
test("no `'change'` event if no changes", 0, function() {
var model = new Backbone.Model();
model.on('change', function() { ok(false); });
model.change();
});
test("nested `set` during `'change'`", 6, function() {
var count = 0;
var model = new Backbone.Model();
model.on('change', function() {
switch(count++) {
case 0:
deepEqual(this.changedAttributes(), {x: true});
equal(model.previous('x'), undefined);
model.set({y: true});
break;
case 1:
deepEqual(this.changedAttributes(), {y: true});
equal(model.previous('x'), true);
model.set({z: true});
break;
case 2:
deepEqual(this.changedAttributes(), {z: true});
equal(model.previous('y'), true);
break;
default:
ok(false);
}
});
model.set({x: true});
});
test("nested `'change'` with silent", 3, function() {
var count = 0;
var model = new Backbone.Model();
model.on('change:y', function() { ok(true); });
model.on('change', function() {
switch(count++) {
case 0:
deepEqual(this.changedAttributes(), {x: true});
model.set({y: true}, {silent: true});
break;
case 1:
deepEqual(this.changedAttributes(), {y: true, z: true});
break;
default:
ok(false);
}
});
model.set({x: true});
model.set({z: true});
});
test("nested `'change:attr'` with silent", 1, function() {
var model = new Backbone.Model();
model.on('change:y', function(){ ok(true); });
model.on('change', function() {
model.set({y: true}, {silent: true});
model.set({z: true});
});
model.set({x: true});
});
test("multiple nested changes with silent", 1, function() {
var model = new Backbone.Model();
model.on('change:x', function() {
model.set({y: 1}, {silent: true});
model.set({y: 2});
});
model.on('change:y', function(model, val) {
equal(val, 2);
});
model.set({x: true});
model.change();
});
test("multiple nested changes with silent", 2, function() {
var changes = [];
var model = new Backbone.Model();
model.on('change:b', function(model, val) { changes.push(val); });
model.on('change', function() {
model.set({b: 1});
model.set({b: 2}, {silent: true});
});
model.set({b: 0});
deepEqual(changes, [0, 1, 1]);
model.change();
deepEqual(changes, [0, 1, 1, 2, 1]);
});
test("nested set multiple times", 1, function() {
var model = new Backbone.Model();
model.on('change:b', function() {
ok(true);
});
model.on('change:a', function() {
model.set({b: true});
model.set({b: true});
});
model.set({a: true});
});
test("Backbone.wrapError triggers `'error'`", 12, function() {
var resp = {};
var options = {};
var model = new Backbone.Model();
model.on('error', error);
var callback = Backbone.wrapError(null, model, options);
callback(model, resp);
callback(resp);
callback = Backbone.wrapError(error, model, options);
callback(model, resp);
callback(resp);
function error(_model, _resp, _options) {
ok(model === _model);
ok(resp === _resp);
ok(options === _options);
}
});
test("#1179 - isValid returns true in the absence of validate.", 1, function() {
var model = new Backbone.Model();
model.validate = null;
ok(model.isValid());
});
test("#1122 - clear does not alter options.", 1, function() {
var model = new Backbone.Model();
var options = {};
model.clear(options);
ok(!options.unset);
});
test("#1122 - unset does not alter options.", 1, function() {
var model = new Backbone.Model();
var options = {};
model.unset('x', options);
ok(!options.unset);
});
test("#1355 - `options` is passed to success callbacks", 3, function() {
var model = new Backbone.Model();
var opts = {
success: function( model, resp, options ) {
ok(options);
}
};
model.sync = function(method, model, options) {
options.success();
};
model.save({id: 1}, opts);
model.fetch(opts);
model.destroy(opts);
});
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.fetch();
model.save();
model.destroy();
});
test("#1365 - Destroy: New models execute success callback.", 2, function() {
new Backbone.Model()
.on('sync', function() { ok(false); })
.on('destroy', function(){ ok(true); })
.destroy({ success: function(){ ok(true); }});
});
test("#1433 - Save: An invalid model cannot be persisted.", 1, function() {
var model = new Backbone.Model;
model.validate = function(){ return 'invalid'; };
model.sync = function(){ ok(false); };
strictEqual(model.save(), false);
});
});

12
vendor/backbone/test/noconflict.js vendored Normal file
View File

@@ -0,0 +1,12 @@
$(document).ready(function() {
module("Backbone.noConflict");
test('Backbone.noConflict', 2, function() {
var noconflictBackbone = Backbone.noConflict();
equal(window.Backbone, undefined, 'Returned window.Backbone');
window.Backbone = noconflictBackbone;
equal(window.Backbone, noconflictBackbone, 'Backbone is still pointing to the original Backbone');
});
});

321
vendor/backbone/test/router.js vendored Normal file
View File

@@ -0,0 +1,321 @@
$(document).ready(function() {
var router = null;
var location = null;
var lastRoute = null;
var lastArgs = [];
function onRoute(router, route, args) {
lastRoute = route;
lastArgs = args;
}
var Location = function(href) {
this.replace(href);
};
_.extend(Location.prototype, {
replace: function(href) {
_.extend(this, _.pick($('<a></a>', {href: href})[0],
'href',
'hash',
'search',
'fragment',
'pathname'
));
// In IE, anchor.pathname does not contain a leading slash though
// window.location.pathname does.
if (!/^\//.test(this.pathname)) this.pathname = '/' + this.pathname;
},
toString: function() {
return this.href;
}
});
module("Backbone.Router", {
setup: function() {
location = new Location('http://example.com');
Backbone.history = new Backbone.History({location: location});
router = new Router({testing: 101});
Backbone.history.interval = 9;
Backbone.history.start({pushState: false});
lastRoute = null;
lastArgs = [];
Backbone.history.on('route', onRoute);
},
teardown: function() {
Backbone.history.stop();
Backbone.history.off('route', onRoute);
}
});
var Router = Backbone.Router.extend({
count: 0,
routes: {
"noCallback": "noCallback",
"counter": "counter",
"search/:query": "search",
"search/:query/p:page": "search",
"contacts": "contacts",
"contacts/new": "newContact",
"contacts/:id": "loadContact",
"splat/*args/end": "splat",
"*first/complex-:part/*rest": "complex",
":entity?*args": "query",
"*anything": "anything"
},
initialize : function(options) {
this.testing = options.testing;
this.route('implicit', 'implicit');
},
counter: function() {
this.count++;
},
implicit: function() {
this.count++;
},
search : function(query, page) {
this.query = query;
this.page = page;
},
contacts: function(){
this.contact = 'index';
},
newContact: function(){
this.contact = 'new';
},
loadContact: function(){
this.contact = 'load';
},
splat : function(args) {
this.args = args;
},
complex : function(first, part, rest) {
this.first = first;
this.part = part;
this.rest = rest;
},
query : function(entity, args) {
this.entity = entity;
this.queryArgs = args;
},
anything : function(whatever) {
this.anything = whatever;
}
});
test("Router: initialize", 1, function() {
equal(router.testing, 101);
});
test("Router: routes (simple)", 4, function() {
location.replace('http://example.com#search/news');
Backbone.history.checkUrl();
equal(router.query, 'news');
equal(router.page, undefined);
equal(lastRoute, 'search');
equal(lastArgs[0], 'news');
});
test("Router: 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() {
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() {
Backbone.history.navigate('search/manhattan/p20', true);
equal(router.query, 'manhattan');
equal(router.page, '20');
});
test("Router: 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);
equal(router.contact, 'index');
Backbone.history.navigate('contacts/new', options);
equal(router.contact, 'new');
Backbone.history.navigate('contacts/foo', options);
equal(router.contact, 'load');
});
});
test("loadUrl is not called for identical routes.", 0, function() {
Backbone.history.loadUrl = function(){ ok(false); };
location.replace('http://example.com#route');
Backbone.history.navigate('route');
Backbone.history.navigate('/route');
Backbone.history.navigate('/route');
});
test("Router: 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() {
location.replace('http://example.com#start_here');
Backbone.history.checkUrl();
location.replace = function(href) {
strictEqual(href, new Location('http://example.com#end_here').href);
};
Backbone.history.navigate('end_here', {replace: true});
});
test("Router: 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() {
location.replace('http://example.com#one/two/three/complex-part/four/five/six/seven');
Backbone.history.checkUrl();
equal(router.first, 'one/two/three');
equal(router.part, 'part');
equal(router.rest, 'four/five/six/seven');
});
test("Router: routes (query)", 5, function() {
location.replace('http://example.com#mandel?a=b&c=d');
Backbone.history.checkUrl();
equal(router.entity, 'mandel');
equal(router.queryArgs, 'a=b&c=d');
equal(lastRoute, 'query');
equal(lastArgs[0], 'mandel');
equal(lastArgs[1], 'a=b&c=d');
});
test("Router: 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() {
router.on("route:noCallback", function(){ ok(true); });
location.replace('http://example.com#noCallback');
Backbone.history.checkUrl();
});
test("#933, #908 - leading slash", 2, function() {
location.replace('http://example.com/root/foo');
Backbone.history.stop();
Backbone.history = new Backbone.History({location: location});
Backbone.history.start({root: '/root', hashChange: false, silent: true});
strictEqual(Backbone.history.getFragment(), 'foo');
Backbone.history.stop();
Backbone.history = new Backbone.History({location: location});
Backbone.history.start({root: '/root/', hashChange: false, silent: true});
strictEqual(Backbone.history.getFragment(), 'foo');
});
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();
// If this is not an old IE navigate will not be called.
if (!history.iframe) ok(true);
});
test("Router: 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');
equal(router.part, 'has#hash');
equal(router.rest, 'has space');
});
test("Router: 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');
Backbone.history.checkUrl();
equal(router.query, 'fat');
equal(router.page, undefined);
equal(lastRoute, 'search');
});
test("#1185 - Use pathname when hashChange is not wanted.", 1, function() {
Backbone.history.stop();
location.replace('http://example.com/path/name#hash');
Backbone.history = new Backbone.History({location: location});
Backbone.history.start({hashChange: false});
var fragment = Backbone.history.getFragment();
strictEqual(fragment, location.pathname.replace(/^\//, ''));
});
test("#1206 - Strip leading slash before location.assign.", 1, function() {
Backbone.history.stop();
location.replace('http://example.com/root/');
Backbone.history = new Backbone.History({location: location});
Backbone.history.start({hashChange: false, root: '/root/'});
location.assign = function(pathname) {
strictEqual(pathname, '/root/fragment');
};
Backbone.history.navigate('/fragment');
});
test("#1387 - Root fragment without trailing slash.", 1, function() {
Backbone.history.stop();
location.replace('http://example.com/root');
Backbone.history = new Backbone.History({location: location});
Backbone.history.start({hashChange: false, root: '/root/', silent: true});
strictEqual(Backbone.history.getFragment(), '');
});
test("#1366 - History does not prepend root to fragment.", 2, function() {
Backbone.history.stop();
location.replace('http://example.com/root/');
Backbone.history = new Backbone.History({
location: location,
history: {
pushState: function(state, title, url) {
strictEqual(url, '/root/x');
}
}
});
Backbone.history.start({
root: '/root/',
pushState: true,
hashChange: false
});
Backbone.history.navigate('x');
strictEqual(Backbone.history.fragment, 'x');
});
});

160
vendor/backbone/test/sync.js vendored Normal file
View File

@@ -0,0 +1,160 @@
$(document).ready(function() {
var ajax = Backbone.ajax;
var lastRequest = null;
var Library = Backbone.Collection.extend({
url : function() { return '/library'; }
});
var library;
var attrs = {
title : "The Tempest",
author : "Bill Shakespeare",
length : 123
};
module("Backbone.sync", {
setup : function() {
library = new Library();
Backbone.ajax = function(obj) {
lastRequest = obj;
};
library.create(attrs, {wait: false});
},
teardown: function() {
Backbone.ajax = ajax;
}
});
test("sync: read", 4, function() {
library.fetch();
equal(lastRequest.url, '/library');
equal(lastRequest.type, 'GET');
equal(lastRequest.dataType, 'json');
ok(_.isEmpty(lastRequest.data));
});
test("sync: 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);
});
test("sync: create", 6, function() {
equal(lastRequest.url, '/library');
equal(lastRequest.type, 'POST');
equal(lastRequest.dataType, 'json');
var data = JSON.parse(lastRequest.data);
equal(data.title, 'The Tempest');
equal(data.author, 'Bill Shakespeare');
equal(data.length, 123);
});
test("sync: 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(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() {
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(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() {
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(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() {
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(data.id, '2-the-tempest');
equal(data.author, 'Tim Shakespeare');
equal(data.length, 123);
Backbone.emulateJSON = false;
});
test("sync: 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));
});
test("sync: 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);
});
test("sync: 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"}');
Backbone.emulateHTTP = Backbone.emulateJSON = false;
});
test("sync: urlError", 2, function() {
var model = new Backbone.Model();
raises(function() {
model.fetch();
});
model.fetch({url: '/one/two'});
equal(lastRequest.url, '/one/two');
});
test("#1052 - `options` is optional.", 0, function() {
var model = new Backbone.Model();
model.url = '/test';
Backbone.sync('create', model);
});
test("Backbone.ajax", 1, function() {
Backbone.ajax = function(settings){
strictEqual(settings.url, '/test');
};
var model = new Backbone.Model();
model.url = '/test';
Backbone.sync('create', model);
});
});

File diff suppressed because it is too large Load Diff

649
vendor/backbone/test/vendor/jslitmus.js vendored Normal file
View File

@@ -0,0 +1,649 @@
// JSLitmus.js
//
// Copyright (c) 2010, Robert Kieffer, http://broofa.com
// Available under MIT license (http://en.wikipedia.org/wiki/MIT_License)
(function() {
// Private methods and state
// Get platform info but don't go crazy trying to recognize everything
// that's out there. This is just for the major platforms and OSes.
var platform = 'unknown platform', ua = navigator.userAgent;
// Detect OS
var oses = ['Windows','iPhone OS','(Intel |PPC )?Mac OS X','Linux'].join('|');
var pOS = new RegExp('((' + oses + ') [^ \);]*)').test(ua) ? RegExp.$1 : null;
if (!pOS) pOS = new RegExp('((' + oses + ')[^ \);]*)').test(ua) ? RegExp.$1 : null;
// Detect browser
var pName = /(Chrome|MSIE|Safari|Opera|Firefox)/.test(ua) ? RegExp.$1 : null;
// Detect version
var vre = new RegExp('(Version|' + pName + ')[ \/]([^ ;]*)');
var pVersion = (pName && vre.test(ua)) ? RegExp.$2 : null;
var platform = (pOS && pName && pVersion) ? pName + ' ' + pVersion + ' on ' + pOS : 'unknown platform';
/**
* A smattering of methods that are needed to implement the JSLitmus testbed.
*/
var jsl = {
/**
* Enhanced version of escape()
*/
escape: function(s) {
s = s.replace(/,/g, '\\,');
s = escape(s);
s = s.replace(/\+/g, '%2b');
s = s.replace(/ /g, '+');
return s;
},
/**
* Get an element by ID.
*/
$: function(id) {
return document.getElementById(id);
},
/**
* Null function
*/
F: function() {},
/**
* Set the status shown in the UI
*/
status: function(msg) {
var el = jsl.$('jsl_status');
if (el) el.innerHTML = msg || '';
},
/**
* Convert a number to an abbreviated string like, "15K" or "10M"
*/
toLabel: function(n) {
if (n == Infinity) {
return 'Infinity';
} else if (n > 1e9) {
n = Math.round(n/1e8);
return n/10 + 'B';
} else if (n > 1e6) {
n = Math.round(n/1e5);
return n/10 + 'M';
} else if (n > 1e3) {
n = Math.round(n/1e2);
return n/10 + 'K';
}
return n;
},
/**
* Copy properties from src to dst
*/
extend: function(dst, src) {
for (var k in src) dst[k] = src[k]; return dst;
},
/**
* Like Array.join(), but for the key-value pairs in an object
*/
join: function(o, delimit1, delimit2) {
if (o.join) return o.join(delimit1); // If it's an array
var pairs = [];
for (var k in o) pairs.push(k + delimit1 + o[k]);
return pairs.join(delimit2);
},
/**
* Array#indexOf isn't supported in IE, so we use this as a cross-browser solution
*/
indexOf: function(arr, o) {
if (arr.indexOf) return arr.indexOf(o);
for (var i = 0; i < this.length; i++) if (arr[i] === o) return i;
return -1;
}
};
/**
* Test manages a single test (created with
* JSLitmus.test())
*
* @private
*/
var Test = function (name, f) {
if (!f) throw new Error('Undefined test function');
if (!/function[^\(]*\(([^,\)]*)/.test(f.toString())) {
throw new Error('"' + name + '" test: Test is not a valid Function object');
}
this.loopArg = RegExp.$1;
this.name = name;
this.f = f;
};
jsl.extend(Test, /** @lends Test */ {
/** Calibration tests for establishing iteration loop overhead */
CALIBRATIONS: [
new Test('calibrating loop', function(count) {while (count--);}),
new Test('calibrating function', jsl.F)
],
/**
* Run calibration tests. Returns true if calibrations are not yet
* complete (in which case calling code should run the tests yet again).
* onCalibrated - Callback to invoke when calibrations have finished
*/
calibrate: function(onCalibrated) {
for (var i = 0; i < Test.CALIBRATIONS.length; i++) {
var cal = Test.CALIBRATIONS[i];
if (cal.running) return true;
if (!cal.count) {
cal.isCalibration = true;
cal.onStop = onCalibrated;
//cal.MIN_TIME = .1; // Do calibrations quickly
cal.run(2e4);
return true;
}
}
return false;
}
});
jsl.extend(Test.prototype, {/** @lends Test.prototype */
/** Initial number of iterations */
INIT_COUNT: 10,
/** Max iterations allowed (i.e. used to detect bad looping functions) */
MAX_COUNT: 1e9,
/** Minimum time a test should take to get valid results (secs) */
MIN_TIME: .5,
/** Callback invoked when test state changes */
onChange: jsl.F,
/** Callback invoked when test is finished */
onStop: jsl.F,
/**
* Reset test state
*/
reset: function() {
delete this.count;
delete this.time;
delete this.running;
delete this.error;
},
/**
* Run the test (in a timeout). We use a timeout to make sure the browser
* has a chance to finish rendering any UI changes we've made, like
* updating the status message.
*/
run: function(count) {
count = count || this.INIT_COUNT;
jsl.status(this.name + ' x ' + count);
this.running = true;
var me = this;
setTimeout(function() {me._run(count);}, 200);
},
/**
* The nuts and bolts code that actually runs a test
*/
_run: function(count) {
var me = this;
// Make sure calibration tests have run
if (!me.isCalibration && Test.calibrate(function() {me.run(count);})) return;
this.error = null;
try {
var start, f = this.f, now, i = count;
// Start the timer
start = new Date();
// Now for the money shot. If this is a looping function ...
if (this.loopArg) {
// ... let it do the iteration itself
f(count);
} else {
// ... otherwise do the iteration for it
while (i--) f();
}
// Get time test took (in secs)
this.time = Math.max(1,new Date() - start)/1000;
// Store iteration count and per-operation time taken
this.count = count;
this.period = this.time/count;
// Do we need to do another run?
this.running = this.time <= this.MIN_TIME;
// ... if so, compute how many times we should iterate
if (this.running) {
// Bump the count to the nearest power of 2
var x = this.MIN_TIME/this.time;
var pow = Math.pow(2, Math.max(1, Math.ceil(Math.log(x)/Math.log(2))));
count *= pow;
if (count > this.MAX_COUNT) {
throw new Error('Max count exceeded. If this test uses a looping function, make sure the iteration loop is working properly.');
}
}
} catch (e) {
// Exceptions are caught and displayed in the test UI
this.reset();
this.error = e;
}
// Figure out what to do next
if (this.running) {
me.run(count);
} else {
jsl.status('');
me.onStop(me);
}
// Finish up
this.onChange(this);
},
/**
* Get the number of operations per second for this test.
*
* @param normalize if true, iteration loop overhead taken into account
*/
getHz: function(/**Boolean*/ normalize) {
var p = this.period;
// Adjust period based on the calibration test time
if (normalize && !this.isCalibration) {
var cal = Test.CALIBRATIONS[this.loopArg ? 0 : 1];
// If the period is within 20% of the calibration time, then zero the
// it out
p = p < cal.period*1.2 ? 0 : p - cal.period;
}
return Math.round(1/p);
},
/**
* Get a friendly string describing the test
*/
toString: function() {
return this.name + ' - ' + this.time/this.count + ' secs';
}
});
// CSS we need for the UI
var STYLESHEET = '<style> \
#jslitmus {font-family:sans-serif; font-size: 12px;} \
#jslitmus a {text-decoration: none;} \
#jslitmus a:hover {text-decoration: underline;} \
#jsl_status { \
margin-top: 10px; \
font-size: 10px; \
color: #888; \
} \
A IMG {border:none} \
#test_results { \
margin-top: 10px; \
font-size: 12px; \
font-family: sans-serif; \
border-collapse: collapse; \
border-spacing: 0px; \
} \
#test_results th, #test_results td { \
border: solid 1px #ccc; \
vertical-align: top; \
padding: 3px; \
} \
#test_results th { \
vertical-align: bottom; \
background-color: #ccc; \
padding: 1px; \
font-size: 10px; \
} \
#test_results #test_platform { \
color: #444; \
text-align:center; \
} \
#test_results .test_row { \
color: #006; \
cursor: pointer; \
} \
#test_results .test_nonlooping { \
border-left-style: dotted; \
border-left-width: 2px; \
} \
#test_results .test_looping { \
border-left-style: solid; \
border-left-width: 2px; \
} \
#test_results .test_name {white-space: nowrap;} \
#test_results .test_pending { \
} \
#test_results .test_running { \
font-style: italic; \
} \
#test_results .test_done {} \
#test_results .test_done { \
text-align: right; \
font-family: monospace; \
} \
#test_results .test_error {color: #600;} \
#test_results .test_error .error_head {font-weight:bold;} \
#test_results .test_error .error_body {font-size:85%;} \
#test_results .test_row:hover td { \
background-color: #ffc; \
text-decoration: underline; \
} \
#chart { \
margin: 10px 0px; \
width: 250px; \
} \
#chart img { \
border: solid 1px #ccc; \
margin-bottom: 5px; \
} \
#chart #tiny_url { \
height: 40px; \
width: 250px; \
} \
#jslitmus_credit { \
font-size: 10px; \
color: #888; \
margin-top: 8px; \
} \
</style>';
// HTML markup for the UI
var MARKUP = '<div id="jslitmus"> \
<button onclick="JSLitmus.runAll(event)">Run Tests</button> \
<button id="stop_button" disabled="disabled" onclick="JSLitmus.stop()">Stop Tests</button> \
<br \> \
<br \> \
<input type="checkbox" style="vertical-align: middle" id="test_normalize" checked="checked" onchange="JSLitmus.renderAll()""> Normalize results \
<table id="test_results"> \
<colgroup> \
<col /> \
<col width="100" /> \
</colgroup> \
<tr><th id="test_platform" colspan="2">' + platform + '</th></tr> \
<tr><th>Test</th><th>Ops/sec</th></tr> \
<tr id="test_row_template" class="test_row" style="display:none"> \
<td class="test_name"></td> \
<td class="test_result">Ready</td> \
</tr> \
</table> \
<div id="jsl_status"></div> \
<div id="chart" style="display:none"> \
<a id="chart_link" target="_blank"><img id="chart_image"></a> \
TinyURL (for chart): \
<iframe id="tiny_url" frameBorder="0" scrolling="no" src=""></iframe> \
</div> \
<a id="jslitmus_credit" title="JSLitmus home page" href="http://code.google.com/p/jslitmus" target="_blank">Powered by JSLitmus</a> \
</div>';
/**
* The public API for creating and running tests
*/
window.JSLitmus = {
/** The list of all tests that have been registered with JSLitmus.test */
_tests: [],
/** The queue of tests that need to be run */
_queue: [],
/**
* The parsed query parameters the current page URL. This is provided as a
* convenience for test functions - it's not used by JSLitmus proper
*/
params: {},
/**
* Initialize
*/
_init: function() {
// Parse query params into JSLitmus.params[] hash
var match = (location + '').match(/([^?#]*)(#.*)?$/);
if (match) {
var pairs = match[1].split('&');
for (var i = 0; i < pairs.length; i++) {
var pair = pairs[i].split('=');
if (pair.length > 1) {
var key = pair.shift();
var value = pair.length > 1 ? pair.join('=') : pair[0];
this.params[key] = value;
}
}
}
// Write out the stylesheet. We have to do this here because IE
// doesn't honor sheets written after the document has loaded.
document.write(STYLESHEET);
// Setup the rest of the UI once the document is loaded
if (window.addEventListener) {
window.addEventListener('load', this._setup, false);
} else if (document.addEventListener) {
document.addEventListener('load', this._setup, false);
} else if (window.attachEvent) {
window.attachEvent('onload', this._setup);
}
return this;
},
/**
* Set up the UI
*/
_setup: function() {
var el = jsl.$('jslitmus_container');
if (!el) document.body.appendChild(el = document.createElement('div'));
el.innerHTML = MARKUP;
// Render the UI for all our tests
for (var i=0; i < JSLitmus._tests.length; i++)
JSLitmus.renderTest(JSLitmus._tests[i]);
},
/**
* (Re)render all the test results
*/
renderAll: function() {
for (var i = 0; i < JSLitmus._tests.length; i++)
JSLitmus.renderTest(JSLitmus._tests[i]);
JSLitmus.renderChart();
},
/**
* (Re)render the chart graphics
*/
renderChart: function() {
var url = JSLitmus.chartUrl();
jsl.$('chart_link').href = url;
jsl.$('chart_image').src = url;
jsl.$('chart').style.display = '';
// Update the tiny URL
jsl.$('tiny_url').src = 'http://tinyurl.com/api-create.php?url='+escape(url);
},
/**
* (Re)render the results for a specific test
*/
renderTest: function(test) {
// Make a new row if needed
if (!test._row) {
var trow = jsl.$('test_row_template');
if (!trow) return;
test._row = trow.cloneNode(true);
test._row.style.display = '';
test._row.id = '';
test._row.onclick = function() {JSLitmus._queueTest(test);};
test._row.title = 'Run ' + test.name + ' test';
trow.parentNode.appendChild(test._row);
test._row.cells[0].innerHTML = test.name;
}
var cell = test._row.cells[1];
var cns = [test.loopArg ? 'test_looping' : 'test_nonlooping'];
if (test.error) {
cns.push('test_error');
cell.innerHTML =
'<div class="error_head">' + test.error + '</div>' +
'<ul class="error_body"><li>' +
jsl.join(test.error, ': ', '</li><li>') +
'</li></ul>';
} else {
if (test.running) {
cns.push('test_running');
cell.innerHTML = 'running';
} else if (jsl.indexOf(JSLitmus._queue, test) >= 0) {
cns.push('test_pending');
cell.innerHTML = 'pending';
} else if (test.count) {
cns.push('test_done');
var hz = test.getHz(jsl.$('test_normalize').checked);
cell.innerHTML = hz != Infinity ? hz : '&infin;';
cell.title = 'Looped ' + test.count + ' times in ' + test.time + ' seconds';
} else {
cell.innerHTML = 'ready';
}
}
cell.className = cns.join(' ');
},
/**
* Create a new test
*/
test: function(name, f) {
// Create the Test object
var test = new Test(name, f);
JSLitmus._tests.push(test);
// Re-render if the test state changes
test.onChange = JSLitmus.renderTest;
// Run the next test if this one finished
test.onStop = function(test) {
if (JSLitmus.onTestFinish) JSLitmus.onTestFinish(test);
JSLitmus.currentTest = null;
JSLitmus._nextTest();
};
// Render the new test
this.renderTest(test);
},
/**
* Add all tests to the run queue
*/
runAll: function(e) {
e = e || window.event;
var reverse = e && e.shiftKey, len = JSLitmus._tests.length;
for (var i = 0; i < len; i++) {
JSLitmus._queueTest(JSLitmus._tests[!reverse ? i : (len - i - 1)]);
}
},
/**
* Remove all tests from the run queue. The current test has to finish on
* it's own though
*/
stop: function() {
while (JSLitmus._queue.length) {
var test = JSLitmus._queue.shift();
JSLitmus.renderTest(test);
}
},
/**
* Run the next test in the run queue
*/
_nextTest: function() {
if (!JSLitmus.currentTest) {
var test = JSLitmus._queue.shift();
if (test) {
jsl.$('stop_button').disabled = false;
JSLitmus.currentTest = test;
test.run();
JSLitmus.renderTest(test);
if (JSLitmus.onTestStart) JSLitmus.onTestStart(test);
} else {
jsl.$('stop_button').disabled = true;
JSLitmus.renderChart();
}
}
},
/**
* Add a test to the run queue
*/
_queueTest: function(test) {
if (jsl.indexOf(JSLitmus._queue, test) >= 0) return;
JSLitmus._queue.push(test);
JSLitmus.renderTest(test);
JSLitmus._nextTest();
},
/**
* Generate a Google Chart URL that shows the data for all tests
*/
chartUrl: function() {
var n = JSLitmus._tests.length, markers = [], data = [];
var d, min = 0, max = -1e10;
var normalize = jsl.$('test_normalize').checked;
// Gather test data
for (var i=0; i < JSLitmus._tests.length; i++) {
var test = JSLitmus._tests[i];
if (test.count) {
var hz = test.getHz(normalize);
var v = hz != Infinity ? hz : 0;
data.push(v);
markers.push('t' + jsl.escape(test.name + '(' + jsl.toLabel(hz)+ ')') + ',000000,0,' +
markers.length + ',10');
max = Math.max(v, max);
}
}
if (markers.length <= 0) return null;
// Build chart title
var title = document.getElementsByTagName('title');
title = (title && title.length) ? title[0].innerHTML : null;
var chart_title = [];
if (title) chart_title.push(title);
chart_title.push('Ops/sec (' + platform + ')');
// Build labels
var labels = [jsl.toLabel(min), jsl.toLabel(max)];
var w = 250, bw = 15;
var bs = 5;
var h = markers.length*(bw + bs) + 30 + chart_title.length*20;
var params = {
chtt: escape(chart_title.join('|')),
chts: '000000,10',
cht: 'bhg', // chart type
chd: 't:' + data.join(','), // data set
chds: min + ',' + max, // max/min of data
chxt: 'x', // label axes
chxl: '0:|' + labels.join('|'), // labels
chsp: '0,1',
chm: markers.join('|'), // test names
chbh: [bw, 0, bs].join(','), // bar widths
// chf: 'bg,lg,0,eeeeee,0,eeeeee,.5,ffffff,1', // gradient
chs: w + 'x' + h
};
return 'http://chart.apis.google.com/chart?' + jsl.join(params, '=', '&');
}
};
JSLitmus._init();
})();

481
vendor/backbone/test/vendor/json2.js vendored Normal file
View File

@@ -0,0 +1,481 @@
/*
http://www.JSON.org/json2.js
2009-09-29
Public Domain.
NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
See http://www.JSON.org/js.html
This code should be minified before deployment.
See http://javascript.crockford.com/jsmin.html
USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
NOT CONTROL.
This file creates a global JSON object containing two methods: stringify
and parse.
JSON.stringify(value, replacer, space)
value any JavaScript value, usually an object or array.
replacer an optional parameter that determines how object
values are stringified for objects. It can be a
function or an array of strings.
space an optional parameter that specifies the indentation
of nested structures. If it is omitted, the text will
be packed without extra whitespace. If it is a number,
it will specify the number of spaces to indent at each
level. If it is a string (such as '\t' or '&nbsp;'),
it contains the characters used to indent at each level.
This method produces a JSON text from a JavaScript value.
When an object value is found, if the object contains a toJSON
method, its toJSON method will be called and the result will be
stringified. A toJSON method does not serialize: it returns the
value represented by the name/value pair that should be serialized,
or undefined if nothing should be serialized. The toJSON method
will be passed the key associated with the value, and this will be
bound to the value
For example, this would serialize Dates as ISO strings.
Date.prototype.toJSON = function (key) {
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
return this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z';
};
You can provide an optional replacer method. It will be passed the
key and value of each member, with this bound to the containing
object. The value that is returned from your method will be
serialized. If your method returns undefined, then the member will
be excluded from the serialization.
If the replacer parameter is an array of strings, then it will be
used to select the members to be serialized. It filters the results
such that only members with keys listed in the replacer array are
stringified.
Values that do not have JSON representations, such as undefined or
functions, will not be serialized. Such values in objects will be
dropped; in arrays they will be replaced with null. You can use
a replacer function to replace those with JSON values.
JSON.stringify(undefined) returns undefined.
The optional space parameter produces a stringification of the
value that is filled with line breaks and indentation to make it
easier to read.
If the space parameter is a non-empty string, then that string will
be used for indentation. If the space parameter is a number, then
the indentation will be that many spaces.
Example:
text = JSON.stringify(['e', {pluribus: 'unum'}]);
// text is '["e",{"pluribus":"unum"}]'
text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
// text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
text = JSON.stringify([new Date()], function (key, value) {
return this[key] instanceof Date ?
'Date(' + this[key] + ')' : value;
});
// text is '["Date(---current time---)"]'
JSON.parse(text, reviver)
This method parses a JSON text to produce an object or array.
It can throw a SyntaxError exception.
The optional reviver parameter is a function that can filter and
transform the results. It receives each of the keys and values,
and its return value is used instead of the original value.
If it returns what it received, then the structure is not modified.
If it returns undefined then the member is deleted.
Example:
// Parse the text. Values that look like ISO date strings will
// be converted to Date objects.
myData = JSON.parse(text, function (key, value) {
var a;
if (typeof value === 'string') {
a =
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
if (a) {
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+a[5], +a[6]));
}
}
return value;
});
myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
var d;
if (typeof value === 'string' &&
value.slice(0, 5) === 'Date(' &&
value.slice(-1) === ')') {
d = new Date(value.slice(5, -1));
if (d) {
return d;
}
}
return value;
});
This is a reference implementation. You are free to copy, modify, or
redistribute.
*/
/*jslint evil: true, strict: false */
/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
lastIndex, length, parse, prototype, push, replace, slice, stringify,
test, toJSON, toString, valueOf
*/
// Create a JSON object only if one does not already exist. We create the
// methods in a closure to avoid creating global variables.
if (!this.JSON) {
this.JSON = {};
}
(function () {
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
if (typeof Date.prototype.toJSON !== 'function') {
Date.prototype.toJSON = function (key) {
return isFinite(this.valueOf()) ?
this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z' : null;
};
String.prototype.toJSON =
Number.prototype.toJSON =
Boolean.prototype.toJSON = function (key) {
return this.valueOf();
};
}
var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
gap,
indent,
meta = { // table of character substitutions
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"' : '\\"',
'\\': '\\\\'
},
rep;
function quote(string) {
// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.
escapable.lastIndex = 0;
return escapable.test(string) ?
'"' + string.replace(escapable, function (a) {
var c = meta[a];
return typeof c === 'string' ? c :
'\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
}) + '"' :
'"' + string + '"';
}
function str(key, holder) {
// Produce a string from holder[key].
var i, // The loop counter.
k, // The member key.
v, // The member value.
length,
mind = gap,
partial,
value = holder[key];
// If the value has a toJSON method, call it to obtain a replacement value.
if (value && typeof value === 'object' &&
typeof value.toJSON === 'function') {
value = value.toJSON(key);
}
// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.
if (typeof rep === 'function') {
value = rep.call(holder, key, value);
}
// What happens next depends on the value's type.
switch (typeof value) {
case 'string':
return quote(value);
case 'number':
// JSON numbers must be finite. Encode non-finite numbers as null.
return isFinite(value) ? String(value) : 'null';
case 'boolean':
case 'null':
// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.
return String(value);
// If the type is 'object', we might be dealing with an object or an array or
// null.
case 'object':
// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.
if (!value) {
return 'null';
}
// Make an array to hold the partial results of stringifying this object value.
gap += indent;
partial = [];
// Is the value an array?
if (Object.prototype.toString.apply(value) === '[object Array]') {
// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.
length = value.length;
for (i = 0; i < length; i += 1) {
partial[i] = str(i, value) || 'null';
}
// Join all of the elements together, separated with commas, and wrap them in
// brackets.
v = partial.length === 0 ? '[]' :
gap ? '[\n' + gap +
partial.join(',\n' + gap) + '\n' +
mind + ']' :
'[' + partial.join(',') + ']';
gap = mind;
return v;
}
// If the replacer is an array, use it to select the members to be stringified.
if (rep && typeof rep === 'object') {
length = rep.length;
for (i = 0; i < length; i += 1) {
k = rep[i];
if (typeof k === 'string') {
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
} else {
// Otherwise, iterate through all of the keys in the object.
for (k in value) {
if (Object.hasOwnProperty.call(value, k)) {
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
}
// Join all of the member texts together, separated with commas,
// and wrap them in braces.
v = partial.length === 0 ? '{}' :
gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
mind + '}' : '{' + partial.join(',') + '}';
gap = mind;
return v;
}
}
// If the JSON object does not yet have a stringify method, give it one.
if (typeof JSON.stringify !== 'function') {
JSON.stringify = function (value, replacer, space) {
// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.
var i;
gap = '';
indent = '';
// If the space parameter is a number, make an indent string containing that
// many spaces.
if (typeof space === 'number') {
for (i = 0; i < space; i += 1) {
indent += ' ';
}
// If the space parameter is a string, it will be used as the indent string.
} else if (typeof space === 'string') {
indent = space;
}
// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.
rep = replacer;
if (replacer && typeof replacer !== 'function' &&
(typeof replacer !== 'object' ||
typeof replacer.length !== 'number')) {
throw new Error('JSON.stringify');
}
// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.
return str('', {'': value});
};
}
// If the JSON object does not yet have a parse method, give it one.
if (typeof JSON.parse !== 'function') {
JSON.parse = function (text, reviver) {
// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.
var j;
function walk(holder, key) {
// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.
var k, v, value = holder[key];
if (value && typeof value === 'object') {
for (k in value) {
if (Object.hasOwnProperty.call(value, k)) {
v = walk(value, k);
if (v !== undefined) {
value[k] = v;
} else {
delete value[k];
}
}
}
}
return reviver.call(holder, key, value);
}
// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.
cx.lastIndex = 0;
if (cx.test(text)) {
text = text.replace(cx, function (a) {
return '\\u' +
('0000' + a.charCodeAt(0).toString(16)).slice(-4);
});
}
// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.
// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
if (/^[\],:{}\s]*$/.
test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.
j = eval('(' + text + ')');
// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.
return typeof reviver === 'function' ?
walk({'': j}, '') : j;
}
// If the text is not JSON parseable, then a SyntaxError is thrown.
throw new SyntaxError('JSON.parse');
};
}
}());

236
vendor/backbone/test/vendor/qunit.css vendored Executable file
View File

@@ -0,0 +1,236 @@
/**
* QUnit v1.8.0 - A JavaScript Unit Testing Framework
*
* http://docs.jquery.com/QUnit
*
* Copyright (c) 2012 John Resig, Jörn Zaefferer
* Dual licensed under the MIT (MIT-LICENSE.txt)
* or GPL (GPL-LICENSE.txt) licenses.
*/
/** Font Family and Sizes */
#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
}
#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
#qunit-tests { font-size: smaller; }
/** Resets */
#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult {
margin: 0;
padding: 0;
}
/** Header */
#qunit-header {
padding: 0.5em 0 0.5em 1em;
color: #8699a4;
background-color: #0d3349;
font-size: 1.5em;
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;
}
#qunit-header a {
text-decoration: none;
color: #c2ccd1;
}
#qunit-header a:hover,
#qunit-header a:focus {
color: #fff;
}
#qunit-header label {
display: inline-block;
padding-left: 0.5em;
}
#qunit-banner {
height: 5px;
}
#qunit-testrunner-toolbar {
padding: 0.5em 0 0.5em 2em;
color: #5E740B;
background-color: #eee;
}
#qunit-userAgent {
padding: 0.5em 0 0.5em 2.5em;
background-color: #2b81af;
color: #fff;
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
}
/** Tests: Pass/Fail */
#qunit-tests {
list-style-position: inside;
}
#qunit-tests li {
padding: 0.4em 0.5em 0.4em 2.5em;
border-bottom: 1px solid #fff;
list-style-position: inside;
}
#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
display: none;
}
#qunit-tests li strong {
cursor: pointer;
}
#qunit-tests li a {
padding: 0.5em;
color: #c2ccd1;
text-decoration: none;
}
#qunit-tests li a:hover,
#qunit-tests li a:focus {
color: #000;
}
#qunit-tests ol {
margin-top: 0.5em;
padding: 0.5em;
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;
}
#qunit-tests table {
border-collapse: collapse;
margin-top: .2em;
}
#qunit-tests th {
text-align: right;
vertical-align: top;
padding: 0 .5em 0 0;
}
#qunit-tests td {
vertical-align: top;
}
#qunit-tests pre {
margin: 0;
white-space: pre-wrap;
word-wrap: break-word;
}
#qunit-tests del {
background-color: #e0f2be;
color: #374e0c;
text-decoration: none;
}
#qunit-tests ins {
background-color: #ffcaca;
color: #500;
text-decoration: none;
}
/*** Test Counts */
#qunit-tests b.counts { color: black; }
#qunit-tests b.passed { color: #5E740B; }
#qunit-tests b.failed { color: #710909; }
#qunit-tests li li {
margin: 0.5em;
padding: 0.4em 0.5em 0.4em 0.5em;
background-color: #fff;
border-bottom: none;
list-style-position: inside;
}
/*** Passing Styles */
#qunit-tests li li.pass {
color: #5E740B;
background-color: #fff;
border-left: 26px solid #C6E746;
}
#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
#qunit-tests .pass .test-name { color: #366097; }
#qunit-tests .pass .test-actual,
#qunit-tests .pass .test-expected { color: #999999; }
#qunit-banner.qunit-pass { background-color: #C6E746; }
/*** Failing Styles */
#qunit-tests li li.fail {
color: #710909;
background-color: #fff;
border-left: 26px 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;
}
#qunit-tests .fail { color: #000000; background-color: #EE5757; }
#qunit-tests .fail .test-name,
#qunit-tests .fail .module-name { color: #000000; }
#qunit-tests .fail .test-actual { color: #EE5757; }
#qunit-tests .fail .test-expected { color: green; }
#qunit-banner.qunit-fail { background-color: #EE5757; }
/** Result */
#qunit-testresult {
padding: 0.5em 0.5em 0.5em 2.5em;
color: #2b81af;
background-color: #D2E0E6;
border-bottom: 1px solid white;
}
#qunit-testresult .module-name {
font-weight: bold;
}
/** Fixture */
#qunit-fixture {
position: absolute;
top: -10000px;
left: -10000px;
width: 1000px;
height: 1000px;
}

1863
vendor/backbone/test/vendor/qunit.js vendored Executable file

File diff suppressed because it is too large Load Diff

230
vendor/backbone/test/view.js vendored Normal file
View File

@@ -0,0 +1,230 @@
$(document).ready(function() {
var view;
module("Backbone.View", {
setup: function() {
view = new Backbone.View({
id : 'test-view',
className : 'test-view'
});
}
});
test("View: 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("View: 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() {
var div = view.make('div', {id: 'test-div'}, 0);
equal($(div).text(), '0');
var div = view.make('div', {id: 'test-div'}, '');
equal($(div).text(), '');
});
test("View: initialize", 1, function() {
var View = Backbone.View.extend({
initialize: function() {
this.one = 1;
}
});
var view = new View;
equal(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"};
view.delegateEvents(events);
$('#qunit-banner').trigger('click');
equal(counter, 1);
equal(counter2, 1);
$('#qunit-banner').trigger('click');
equal(counter, 2);
equal(counter2, 2);
view.delegateEvents(events);
$('#qunit-banner').trigger('click');
equal(counter, 3);
equal(counter2, 3);
});
test("View: delegateEvents allows functions for callbacks", 3, function() {
view.counter = 0;
view.setElement("#qunit-banner");
var events = {"click": function() { this.counter++; }};
view.delegateEvents(events);
$('#qunit-banner').trigger('click');
equal(view.counter, 1);
$('#qunit-banner').trigger('click');
equal(view.counter, 2);
view.delegateEvents(events);
$('#qunit-banner').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"};
view.delegateEvents(events);
$('#qunit-userAgent').trigger('click');
equal(counter, 1);
equal(counter2, 1);
view.undelegateEvents();
$('#qunit-userAgent').trigger('click');
equal(counter, 1);
equal(counter2, 2);
view.delegateEvents(events);
$('#qunit-userAgent').trigger('click');
equal(counter, 2);
equal(counter2, 3);
});
test("View: _ensureElement with DOM node el", 1, function() {
var ViewClass = Backbone.View.extend({
el: document.body
});
var view = new ViewClass;
equal(view.el, document.body);
});
test("View: _ensureElement with string el", 3, function() {
var ViewClass = Backbone.View.extend({
el: "body"
});
var view = new ViewClass;
strictEqual(view.el, document.body);
ViewClass = Backbone.View.extend({
el: "#testElement > h1"
});
view = new ViewClass;
strictEqual(view.el, $("#testElement > h1").get(0));
ViewClass = Backbone.View.extend({
el: "#nonexistent"
});
view = new ViewClass;
ok(!view.el);
});
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("View: with attributes as a function", 1, function() {
var viewClass = Backbone.View.extend({
attributes: function() {
return {'class': 'dynamic'};
}
});
equal((new viewClass).el.className, 'dynamic');
});
test("View: multiple views per element", 3, function() {
var count = 0, ViewClass = Backbone.View.extend({
el: $("body"),
events: {
"click": "click"
},
click: function() {
count++;
}
});
var view1 = new ViewClass;
$("body").trigger("click");
equal(1, count);
var view2 = new ViewClass;
$("body").trigger("click");
equal(3, count);
view1.delegateEvents();
$("body").trigger("click");
equal(5, count);
});
test("View: custom events, with namespaces", 2, function() {
var count = 0;
var ViewClass = Backbone.View.extend({
el: $('body'),
events: function() {
return {"fake$event.namespaced": "run"};
},
run: function() {
count++;
}
});
var view = new ViewClass;
$('body').trigger('fake$event').trigger('fake$event');
equal(count, 2);
$('body').unbind('.namespaced');
$('body').trigger('fake$event');
equal(count, 2);
});
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 View = Backbone.View.extend({
events: {click: function(e) { ok(view.el === e.target); }}
});
var view = new View({el: a});
view.setElement(b);
a.trigger('click');
b.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);
});
test("#1228 - tagName can be provided as a function", 1, function() {
var View = Backbone.View.extend({tagName: function(){ return 'p'; }});
ok(new View().$el.is('p'));
});
});

1
vendor/benchmark.js vendored

Submodule vendor/benchmark.js deleted from 5ff44811ef

22
vendor/benchmark.js/LICENSE.txt vendored Normal file
View File

@@ -0,0 +1,22 @@
Copyright 2010-2012 Mathias Bynens <http://mathiasbynens.be/>
Based on JSLitmus.js, copyright Robert Kieffer <http://broofa.com/>
Modified by John-David Dalton <http://allyoucanleet.com/>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

132
vendor/benchmark.js/README.md vendored Normal file
View File

@@ -0,0 +1,132 @@
# Benchmark.js <sup>v1.0.0-pre</sup>
A [robust](http://calendar.perfplanet.com/2010/bulletproof-javascript-benchmarks/ "Bulletproof JavaScript benchmarks") benchmarking library that works on nearly all JavaScript platforms<sup><a name="fnref1" href="#fn1">1</a></sup>, supports high-resolution timers, and returns statistically significant results. As seen on [jsPerf](http://jsperf.com/).
## BestieJS
Benchmark.js is part of the BestieJS *"Best in Class"* module collection. This means we promote solid browser/environment support, ES5 precedents, unit testing, and plenty of documentation.
## Documentation
The documentation for Benchmark.js can be viewed here: <http://benchmarkjs.com/docs>
For a list of upcoming features, check out our [roadmap](https://github.com/bestiejs/benchmark.js/wiki/Roadmap).
## Installation and usage
In a browser or Adobe AIR:
~~~ html
<script src="benchmark.js"></script>
~~~
Optionally, expose Javas nanosecond timer by adding the `nano` applet to the `<body>`:
~~~ html
<applet code="nano" archive="nano.jar"></applet>
~~~
Or enable Chromes microsecond timer by using the [command line switch](http://peter.sh/experiments/chromium-command-line-switches/#enable-benchmarking):
--enable-benchmarking
Via [npm](http://npmjs.org/):
~~~ bash
npm install benchmark
~~~
In [Node.js](http://nodejs.org/) and [RingoJS v0.8.0+](http://ringojs.org/):
~~~ js
var Benchmark = require('benchmark');
~~~
Optionally, use the [microtime module](https://github.com/wadey/node-microtime) by Wade Simmons:
~~~ bash
npm install microtime
~~~
In [Narwhal](http://narwhaljs.org/) and [RingoJS v0.7.0-](http://ringojs.org/):
~~~ js
var Benchmark = require('benchmark').Benchmark;
~~~
In [Rhino](http://www.mozilla.org/rhino/):
~~~ js
load('benchmark.js');
~~~
In an AMD loader like [RequireJS](http://requirejs.org/):
~~~ js
require({
'paths': {
'benchmark': 'path/to/benchmark'
}
},
['benchmark'], function(Benchmark) {
console.log(Benchmark.version);
});
// or with platform.js
// https://github.com/bestiejs/platform.js
require({
'paths': {
'benchmark': 'path/to/benchmark',
'platform': 'path/to/platform'
}
},
['benchmark', 'platform'], function(Benchmark, platform) {
Benchmark.platform = platform;
console.log(Benchmark.platform.name);
});
~~~
Usage example:
~~~ js
var suite = new Benchmark.Suite;
// add tests
suite.add('RegExp#test', function() {
/o/.test('Hello World!');
})
.add('String#indexOf', function() {
'Hello World!'.indexOf('o') > -1;
})
// add listeners
.on('cycle', function(event) {
console.log(String(event.target));
})
.on('complete', function() {
console.log('Fastest is ' + this.filter('fastest').pluck('name'));
})
// run async
.run({ 'async': true });
// logs:
// > RegExp#test x 4,161,532 +-0.99% (59 cycles)
// > String#indexOf x 6,139,623 +-1.00% (131 cycles)
// > Fastest is String#indexOf
~~~
## Footnotes
1. Benchmark.js has been tested in at least Adobe AIR 2.6, Chrome 5-15, Firefox 1.5-8, IE 6-10, Opera 9.25-11.52, Safari 2-5.1.1, Node.js 0.4.8-0.6.1, Narwhal 0.3.2, RingoJS 0.7-0.8, and Rhino 1.7RC3.
<a name="fn1" title="Jump back to footnote 1 in the text." href="#fnref1">&#8617;</a>
## Authors
* [Mathias Bynens](http://mathiasbynens.be/)
[![twitter/mathias](http://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=70)](https://twitter.com/mathias "Follow @mathias on Twitter")
* [John-David Dalton](http://allyoucanleet.com/)
[![twitter/jdalton](http://gravatar.com/avatar/299a3d891ff1920b69c364d061007043?s=70)](https://twitter.com/jdalton "Follow @jdalton on Twitter")
## Contributors
* [Kit Cambridge](http://kitcambridge.github.com/)
[![twitter/kitcambridge](http://gravatar.com/avatar/6662a1d02f351b5ef2f8b4d815804661?s=70)](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter")

3875
vendor/benchmark.js/benchmark.js vendored Normal file

File diff suppressed because it is too large Load Diff

BIN
vendor/benchmark.js/nano.jar vendored Normal file

Binary file not shown.

1
vendor/docdown vendored

Submodule vendor/docdown deleted from 5448a5837d

20
vendor/docdown/LICENSE.txt vendored Normal file
View File

@@ -0,0 +1,20 @@
Copyright 2011-2012 John-David Dalton <http://allyoucanleet.com/>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

44
vendor/docdown/README.md vendored Normal file
View File

@@ -0,0 +1,44 @@
# Docdown <sup>v1.0.0-pre</sup>
A simple JSDoc to Markdown documentation generator.
## Documentation
The documentation for Docdown can be viewed here: [/doc/README.md](https://github.com/jdalton/docdown/blob/master/doc/README.md#readme)
For a list of upcoming features, check out our [roadmap](https://github.com/jdalton/docdown/wiki/Roadmap).
## Installation and usage
Usage example:
~~~ php
require("docdown.php");
// generate Markdown
$markdown = docdown(array(
"path" => $filepath,
"url" => "https://github.com/username/project/blob/master/my.js"
));
~~~
## Cloning this repo
To clone this repository just use:
~~~ bash
git clone https://github.com/docdown/docdown.git
cd docdown
~~~
Feel free to fork and send pull requests if you see improvements!
## Author
* [John-David Dalton](http://allyoucanleet.com/)
[![twitter/jdalton](http://gravatar.com/avatar/299a3d891ff1920b69c364d061007043?s=70)](https://twitter.com/jdalton "Follow @jdalton on Twitter")
## Contributors
* [Mathias Bynens](http://mathiasbynens.be/)
[![twitter/mathias](http://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=70)](https://twitter.com/mathias "Follow @mathias on Twitter")

38
vendor/docdown/docdown.php vendored Normal file
View File

@@ -0,0 +1,38 @@
<?php
/*!
* Docdown v1.0.0-pre
* Copyright 2011-2012 John-David Dalton <http://allyoucanleet.com/>
* Available under MIT license <http://mths.be/mit>
*/
require(dirname(__FILE__) . '/src/DocDown/Generator.php');
/**
* Generates Markdown from JSDoc entries in a given file.
*
* @param {Array} [$options=array()] The options array.
* @returns {String} The generated Markdown.
* @example
*
* // specify a file path
* $markdown = docdown(array(
* // path to js file
* 'path' => $filepath,
* // url used to reference line numbers in code
* 'url' => 'https://github.com/username/project/blob/master/my.js'
* ));
*
* // or pass raw js
* $markdown = docdown(array(
* // raw JavaScript source
* 'source' => $rawJS,
* // documentation title
* 'title' => 'My API Documentation',
* // url used to reference line numbers in code
* 'url' => 'https://github.com/username/project/blob/master/my.js'
* ));
*/
function docdown( $options = array() ) {
$gen = new Generator($options);
return $gen->generate();
}
?>

304
vendor/docdown/src/DocDown/Entry.php vendored Normal file
View File

@@ -0,0 +1,304 @@
<?php
/**
* A class to simplify parsing a single JSDoc entry.
*/
class Entry {
/**
* The documentation entry.
*
* @memberOf Entry
* @type String
*/
public $entry = '';
/**
* The language highlighter used for code examples.
*
* @memberOf Entry
* @type String
*/
public $lang = '';
/**
* The source code.
*
* @memberOf Entry
* @type String
*/
public $source = '';
/*--------------------------------------------------------------------------*/
/**
* The Entry constructor.
*
* @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.
*/
public function __construct( $entry, $source, $lang = 'js' ) {
$this->entry = $entry;
$this->lang = $lang;
$this->source = str_replace(PHP_EOL, "\n", $source);
}
/*--------------------------------------------------------------------------*/
/**
* Extracts the documentation entries from source code.
*
* @static
* @memberOf Entry
* @param {String} $source The source code.
* @returns {Array} The array of entries.
*/
public static function getEntries( $source ) {
preg_match_all('#/\*\*(?![-!])[\s\S]*?\*/\s*[^\n]+#', $source, $result);
return array_pop($result);
}
/*--------------------------------------------------------------------------*/
/**
* Checks if the entry is a function reference.
*
* @private
* @memberOf 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)
);
}
/*--------------------------------------------------------------------------*/
/**
* Extracts the function call from the entry.
*
* @memberOf Entry
* @returns {String} The function call.
*/
public function getCall() {
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);
if (count($name)) {
$name = trim($name[1]);
} else {
$name = $result;
}
// compile function call syntax
if ($this->isFunction()) {
// compose parts
$result = array($result);
$params = $this->getParams();
foreach ($params as $param) {
$result[] = $param[1];
}
// format
$result = $name .'('. implode(array_slice($result, 1), ', ') .')';
$result = str_replace(', [', ' [, ', str_replace('], [', ', ', $result));
}
return $result ? $result : $name;
}
/**
* Extracts the entry description.
*
* @memberOf Entry
* @returns {String} The entry description.
*/
public function getDesc() {
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;
}
return $result;
}
/**
* Extracts the entry `example` data.
*
* @memberOf Entry
* @returns {String} The entry `example` data.
*/
public function getExample() {
preg_match('#\*\s*@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~~~";
}
return $result;
}
/**
* Resolves the line number of the entry.
*
* @memberOf Entry
* @returns {Number} The 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;
}
/**
* Extracts the entry `member` data.
*
* @memberOf Entry
* @param {Number} $index The index of the array value to return.
* @returns {Array|String} The entry `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);
}
return $index !== null ? @$result[$index] : $result;
}
/**
* Extracts the entry `name` data.
*
* @memberOf Entry
* @returns {String} The entry `name` data.
*/
public function getName() {
preg_match('#\*\s*@name\s+([^\n]+)#', $this->entry, $result);
if (count($result)) {
$result = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1]));
} else {
$result = array_shift(explode('(', $this->getCall()));
}
return $result;
}
/**
* Extracts the entry `param` data.
*
* @memberOf Entry
* @param {Number} $index The index of the array value to return.
* @returns {Array} The entry `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();
}
$result[0][$key][] = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $value));
}
}
$result = $result[0];
}
return $index !== null ? @$result[$index] : $result;
}
/**
* Extracts the entry `returns` data.
*
* @memberOf Entry
* @returns {String} The entry `returns` data.
*/
public function getReturns() {
preg_match('#\*\s*@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]);
}
return $result;
}
/**
* Extracts the entry `type` data.
*
* @memberOf Entry
* @returns {String} The entry `type` data.
*/
public function getType() {
preg_match('#\*\s*@type\s+([^\n]+)#', $this->entry, $result);
if (count($result)) {
$result = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1]));
} else {
$result = $this->isFunction() ? 'Function' : 'Unknown';
}
return $result;
}
/**
* Checks if an 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);
}
/**
* Checks if an entry *is* assigned to a prototype.
*
* @memberOf Entry
* @returns {Boolean} Returns true if assigned to a prototype, else false.
*/
public function isPlugin() {
return !$this->isCtor() && !$this->isPrivate() && !$this->isStatic();
}
/**
* Checks if an entry is private.
*
* @memberOf Entry
* @returns {Boolean} Returns true if private, else false.
*/
public function isPrivate() {
return !!preg_match('/\*\s*@private\b/', $this->entry) || strrpos($this->entry, '@') === false;
}
/**
* Checks if an entry is *not* assigned to a prototype.
*
* @memberOf Entry
* @returns {Boolean} Returns true if not assigned to a prototype, else false.
*/
public function isStatic() {
$public = !$this->isPrivate();
$result = $public && !!preg_match('/\*\s*@static\b/', $this->entry);
// set in cases where it isn't explicitly stated
if ($public && !$result) {
if ($parent = array_pop(preg_split('/[#.]/', $this->getMembers(0)))) {
foreach (Entry::getEntries($this->source) as $entry) {
$entry = new Entry($entry, $this->source);
if ($entry->getName() == $parent) {
$result = !$entry->isCtor();
break;
}
}
} else {
$result = true;
}
}
return $result;
}
}
?>

410
vendor/docdown/src/DocDown/Generator.php vendored Normal file
View File

@@ -0,0 +1,410 @@
<?php
require(dirname(__FILE__) . "/Entry.php");
/**
* Generates Markdown from JSDoc entries.
*/
class Generator {
/**
* An array of JSDoc entries.
*
* @memberOf Generator
* @type Array
*/
public $entries = array();
/**
* An options array used to configure the generator.
*
* @memberOf Generator
* @type Array
*/
public $options = array();
/**
* The entire file's source code.
*
* @memberOf Generator
* @type String
*/
public $source = '';
/*--------------------------------------------------------------------------*/
/**
* The Generator constructor.
*
* @constructor
* @param {String} $source The source code to parse.
* @param {Array} $options The options array.
*/
public function __construct( $source, $options = array() ) {
// juggle arguments
if (is_array($source)) {
$options = $source;
} else {
$options['source'] = $source;
}
if (isset($options['source']) && realpath($options['source'])) {
$options['path'] = $options['source'];
}
if (isset($options['path'])) {
preg_match('/(?<=\.)[a-z]+$/', $options['path'], $ext);
$options['source'] = file_get_contents($options['path']);
$ext = array_pop($ext);
if (!isset($options['lang']) && $ext) {
$options['lang'] = $ext;
}
if (!isset($options['title'])) {
$options['title'] = ucfirst(basename($options['path'])) . ' API documentation';
}
}
if (!isset($options['lang'])) {
$options['lang'] = 'js';
}
$this->options = $options;
$this->source = str_replace(PHP_EOL, "\n", $options['source']);
$this->entries = Entry::getEntries($this->source);
foreach ($this->entries as $index => $value) {
$this->entries[$index] = new Entry($value, $this->source, $options['lang']);
}
}
/*--------------------------------------------------------------------------*/
/**
* Performs common string formatting operations.
*
* @private
* @static
* @memberOf Generator
* @param {String} $string The string to format.
* @returns {String} The formatted string.
*/
private static function format($string) {
$counter = 0;
// tokenize inline code snippets
preg_match_all('/`[^`]+`/', $string, $tokenized);
$tokenized = $tokenized[0];
foreach ($tokenized as $snippet) {
$string = str_replace($snippet, '__token' . ($counter++) .'__', $string);
}
// italicize parentheses
$string = preg_replace('/(^|\s)(\([^)]+\))/', '$1*$2*', $string);
// mark numbers as inline code
$string = preg_replace('/ (-?\d+(?:.\d+)?)(?!\.[^\n])/', ' `$1`', $string);
// detokenize inline code snippets
$counter = 0;
foreach ($tokenized as $snippet) {
$string = str_replace('__token' . ($counter++) . '__', $snippet, $string);
}
return trim($string);
}
/**
* Modify a string by replacing named tokens with matching assoc. array values.
*
* @private
* @static
* @memberOf Generator
* @param {String} $string The string to modify.
* @param {Array|Object} $object The template object.
* @returns {String} The modified string.
*/
private static function interpolate($string, $object) {
preg_match_all('/#\{([^}]+)\}/', $string, $tokens);
$tokens = array_unique(array_pop($tokens));
foreach ($tokens as $token) {
$pattern = '/#\{' . $token . '\}/';
$replacement = '';
if (is_object($object)) {
preg_match('/\(([^)]+?)\)$/', $token, $args);
$args = preg_split('/,\s*/', array_pop($args));
$method = 'get' . ucfirst(str_replace('/\([^)]+?\)$/', '', $token));
if (method_exists($object, $method)) {
$replacement = (string) call_user_func_array(array($object, $method), $args);
} else if (isset($object->{$token})) {
$replacement = (string) $object->{$token};
}
} else if (isset($object[$token])) {
$replacement = (string) $object[$token];
}
$string = preg_replace($pattern, trim($replacement), $string);
}
return Generator::format($string);
}
/*--------------------------------------------------------------------------*/
/**
* Resolves the entry's hash used to navigate the documentation.
*
* @private
* @memberOf Generator
* @param {Number|Object} $entry The entry object.
* @param {String} $member The name of the member.
* @returns {String} The url hash.
*/
private function getHash( $entry, $member = '' ) {
$entry = is_numeric($entry) ? $this->entries[$entry] : $entry;
$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);
return strtolower($result);
}
/**
* Resolves the entry's url for the specific line number.
*
* @private
* @memberOf Generator
* @param {Number|Object} $entry The entry object.
* @returns {String} The url.
*/
private function getLineUrl( $entry ) {
$entry = is_numeric($entry) ? $this->entries($entry) : $entry;
return $this->options['url'] . '#L' . $entry->getLineNumber();
}
/**
* Extracts the character used to separate the entry's name from its member.
*
* @private
* @memberOf Generator
* @param {Number|Object} $entry The entry object.
* @returns {String} The separator.
*/
private function getSeparator( $entry ) {
$entry = is_numeric($entry) ? $this->entries($entry) : $entry;
return $entry->isPlugin() ? '.prototype.' : '.';
}
/*--------------------------------------------------------------------------*/
/**
* Generates Markdown from JSDoc entries.
*
* @memberOf Generator
* @returns {String} The rendered Markdown.
*/
public function generate() {
$api = array();
$compiling = false;
$openTag = "\n<!-- div -->\n";
$closeTag = "\n<!-- /div -->\n";
$result = array('# ' . $this->options['title']);
// initialize $api array
foreach ($this->entries as $entry) {
if (!$entry->isPrivate()) {
$name = $entry->getName();
$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();
}
// 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;
} else if (!$entry->isCtor()) {
$api[$member]->plugin[] = $entry;
}
}
}
}
/*------------------------------------------------------------------------*/
// custom sort for root level entries
// TODO: see how well it handles deeper namespace traversal
function sortCompare($a, $b) {
$score = array( 'a' => 0, 'b' => 0);
foreach (array( 'a' => $a, 'b' => $b) as $key => $value) {
// capitalized keys that represent constructor properties are last
if (preg_match('/[#.][A-Z]/', $value)) {
$score[$key] = 0;
}
// lowercase keys with prototype properties are next to last
else if (preg_match('/#[a-z]/', $value)) {
$score[$key] = 1;
}
// lowercase keys with static properties next to first
else if (preg_match('/\.[a-z]/', $value)) {
$score[$key] = 2;
}
// lowercase keys with no properties are first
else if (preg_match('/^[^#.]+$/', $value)) {
$score[$key] = 3;
}
}
$score = $score['b'] - $score['a'];
return $score ? $score : strcasecmp($a, $b);
}
uksort($api, 'sortCompare');
// sort static and plugin sub-entries
foreach ($api as $entry) {
foreach (array('static', 'plugin') as $kind) {
$sortBy = array( 'a' => array(), 'b' => array(), 'c' => array() );
foreach ($entry->{$kind} as $subentry) {
$name = $subentry->getName();
// functions w/o ALL-CAPs names are last
$sortBy['a'][] = $subentry->getType() == 'Function' && !preg_match('/^[A-Z_]+$/', $name);
// ALL-CAPs properties first
$sortBy['b'][] = preg_match('/^[A-Z_]+$/', $name);
// lowercase alphanumeric sort
$sortBy['c'][] = strtolower($name);
}
array_multisort($sortBy['a'], SORT_ASC, $sortBy['b'], SORT_DESC, $sortBy['c'], SORT_ASC, $entry->{$kind});
}
}
/*------------------------------------------------------------------------*/
// compile TOC
$result[] = $openTag;
foreach ($api as $key => $entry) {
$entry->hash = $this->getHash($entry);
$entry->href = $this->getLineUrl($entry);
$member = $entry->getMembers(0);
$member = ($member ? $member . ($entry->isPlugin() ? '.prototype.' : '.') : '') . $entry->getName();
$entry->member = preg_replace('/' . $entry->getName() . '$/', '', $member);
$compiling = $compiling ? ($result[] = $closeTag) : true;
// add root entry
array_push(
$result,
$openTag, '## ' . (count($result) == 2 ? '<a id="toc"></a>' : '') . '`' . $member . '`',
Generator::interpolate('* [`' . $member . '`](##{hash})', $entry)
);
// add static and plugin sub-entries
foreach (array('static', 'plugin') as $kind) {
if ($kind == 'plugin' && count($entry->plugin)) {
array_push(
$result,
$closeTag,
$openTag,
'## `' . $member . ($entry->isCtor() ? '.prototype`' : '`')
);
}
foreach ($entry->{$kind} as $subentry) {
$subentry->hash = $this->getHash($subentry);
$subentry->href = $this->getLineUrl($subentry);
$subentry->member = $member;
$subentry->separator = $this->getSeparator($subentry);
$result[] = Generator::interpolate('* [`#{member}#{separator}#{name}`](##{hash})', $subentry);
}
}
}
array_push($result, $closeTag, $closeTag);
/*------------------------------------------------------------------------*/
// compile content
$compiling = false;
$result[] = $openTag;
foreach ($api as $entry) {
// add root entry
$member = $entry->member . $entry->getName();
$compiling = $compiling ? ($result[] = $closeTag) : true;
array_push($result, $openTag, '## `' . $member . '`');
foreach (array($entry, 'static', 'plugin') as $kind) {
$subentries = is_string($kind) ? $entry->{$kind} : array($kind);
// title
if ($kind != 'static' && $entry->getType() != 'Object' &&
count($subentries) && $subentries[0] != $kind) {
if ($kind == 'plugin') {
$result[] = $closeTag;
}
array_push(
$result,
$openTag,
'## `' . $member . ($kind == 'plugin' ? '.prototype`' : '`')
);
}
// body
foreach ($subentries as $subentry) {
// description
array_push(
$result,
$openTag,
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)
);
// @param
if (count($params = $subentry->getParams())) {
array_push($result, '', '#### Arguments');
foreach ($params as $index => $param) {
$result[] = Generator::interpolate('#{num}. `#{name}` (#{type}): #{desc}', array(
'desc' => $param[2],
'name' => $param[1],
'num' => $index + 1,
'type' => $param[0]
));
}
}
// @returns
if (count($returns = $subentry->getReturns())) {
array_push(
$result, '',
'#### Returns',
Generator::interpolate('(#{type}): #{desc}', array('desc' => $returns[1], 'type' => $returns[0]))
);
}
// @example
if ($example = $subentry->getExample()) {
array_push($result, '', '#### Example', $example);
}
array_push($result, "\n* * *", $closeTag);
}
}
}
// close tags add TOC link reference
array_push($result, $closeTag, $closeTag, '', ' [1]: #toc "Jump back to the TOC."');
// cleanup whitespace
return trim(preg_replace('/ +\n/', "\n", join($result, "\n")));
}
}
?>

1
vendor/qunit vendored

Submodule vendor/qunit deleted from 3f5e8b2123

1
vendor/qunit-clib vendored

Submodule vendor/qunit-clib deleted from 2ed9f21633

20
vendor/qunit-clib/LICENSE.txt vendored Normal file
View File

@@ -0,0 +1,20 @@
Copyright 2011-2012 John-David Dalton <http://allyoucanleet.com/>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

65
vendor/qunit-clib/README.md vendored Normal file
View File

@@ -0,0 +1,65 @@
# QUnit CLIB <sup>v1.0.0-pre</sup>
## command-line interface boilerplate
QUnit CLIB helps extend QUnit's CLI support to many common CLI environments<sup><a name="fnref1" href="#fn1">1</a></sup>.
## Screenshot
![QUnit CLIB brings QUnit to your favorite shell.](http://i.imgur.com/jpu9l.png)
## Usage
~~~ js
(function(window) {
// use a single load function
var load = typeof require == 'function' ? require : window.load;
// load QUnit and CLIB if needed
var QUnit =
window.QUnit || (
window.setTimeout || (window.addEventListener = window.setTimeout = / /),
window.QUnit = load('path/to/qunit.js') || window.QUnit,
load('path/to/qunit-clib.js'),
(window.addEventListener || 0).test && delete window.addEventListener,
window.QUnit
);
// explicitly call `QUnit.module()` instead of `module()`
// in case we are in a CLI environment
QUnit.module('A Test Module');
test('A Test', function() {
// ...
});
// must call `QUnit.start()` if using QUnit < 1.3.0 with Node.js or any
// version of QUnit with Narwhal, Rhino, or RingoJS
if (!window.document) {
QUnit.start();
}
}(typeof global == 'object' && global || this));
~~~
## Cloning this repo
To clone this repository just use:
~~~ bash
git clone https://github.com/jdalton/qunit-clib.git
cd qunit-clib
~~~
Feel free to fork and send pull requests if you see improvements!
## Footnotes
1. QUnit CLIB has been tested in at least Node.js v0.4.8-0.6.1, Narwhal v0.3.2, RingoJS v0.7.0-0.8.0, and Rhino v1.7RC3.
<a name="fn1" title="Jump back to footnote 1 in the text." href="#fnref1">&#8617;</a>
2. QUnit v1.3.0 does not work with Narwhal or Ringo < v0.8.0
## Author
* [John-David Dalton](http://allyoucanleet.com/)
[![twitter/jdalton](http://gravatar.com/avatar/299a3d891ff1920b69c364d061007043?s=70)](https://twitter.com/jdalton "Follow @jdalton on Twitter")

311
vendor/qunit-clib/qunit-clib.js vendored Normal file
View File

@@ -0,0 +1,311 @@
/*!
* QUnit CLI Boilerplate v1.0.0-pre
* Copyright 2011-2012 John-David Dalton <http://allyoucanleet.com/>
* Based on a gist by Jörn Zaefferer <https://gist.github.com/722381>
* Available under MIT license <http://mths.be/mit>
*/
;(function(global) {
/** Add `console.log()` support for Narwhal, Rhino, and RingoJS */
global.console || (global.console = { 'log': global.print });
/** Reduce global.QUnit.QUnit -> global.QUnit */
global.QUnit && (QUnit = QUnit.QUnit || QUnit);
/*--------------------------------------------------------------------------*/
/** Used as a horizontal rule in console output */
var hr = '----------------------------------------';
/** Shortcut used to convert array-like objects to arrays */
var slice = [].slice;
/** Used to resolve a value's internal [[Class]] */
var toString = {}.toString;
/** Used by timer methods */
var timer,
counter = 0,
ids = {};
/*--------------------------------------------------------------------------*/
/**
* An iteration utility for arrays.
*
* @private
* @param {Array} array The array to iterate over.
* @param {Function} callback The function called per iteration.
*/
function each(array, callback) {
var index = -1,
length = array.length;
while (++index < length) {
callback(array[index], index, array);
}
}
/**
* Checks if the specified `value` is a function.
*
* @private
* @param {Mixed} value The value to check.
* @returns {Boolean} Returns `true` if `value` is a function, else `false`.
*/
function isFunction(value) {
return toString.call(value) == '[object Function]';
}
/*--------------------------------------------------------------------------*/
/**
* Timeout fallbacks based on the work of Andrea Giammarchi and Weston C.
* https://github.com/WebReflection/wru/blob/master/src/rhinoTimers.js
* http://stackoverflow.com/questions/2261705/how-to-run-a-javascript-function-asynchronously-without-using-settimeout
*/
/**
* Clears the delay set by `setInterval` or `setTimeout`.
*
* @memberOf global
* @param {Number} id The ID of the timeout to be cleared.
*/
function clearTimer(id) {
if (ids[id]) {
ids[id].cancel();
timer.purge();
delete ids[id];
}
}
/**
* Schedules timer-based callbacks.
*
* @private
* @param {Function} fn The function to call.
* @oaram {Number} delay The number of milliseconds to delay the `fn` call.
* @param [arg1, arg2, ...] Arguments to invoke `fn` with.
* @param {Boolean} repeated A flag to specify whether `fn` is called repeatedly.
* @returns {Number} The the ID of the timeout.
*/
function schedule(fn, delay, args, repeated) {
var task = ids[++counter] = new JavaAdapter(java.util.TimerTask, {
'run': function() {
fn.apply(global, args);
}
});
// support non-functions
if (!isFunction(fn)) {
fn = (function(code) {
code = String(code);
return function() { eval(code); };
}(fn));
}
// used by setInterval
if (repeated) {
timer.schedule(task, delay, delay);
}
// used by setTimeout
else {
timer.schedule(task, delay);
}
return counter;
}
/**
* Executes a code snippet or function repeatedly, with a delay between each call.
*
* @memberOf global
* @param {Function|String} fn The function to call or string to evaluate.
* @oaram {Number} delay The number of milliseconds to delay each `fn` call.
* @param [arg1, arg2, ...] Arguments to invoke `fn` with.
* @returns {Number} The the ID of the timeout.
*/
function setInterval(fn, delay) {
return schedule(fn, delay, slice.call(arguments, 2), true);
}
/**
* Executes a code snippet or a function after specified delay.
*
* @memberOf global
* @param {Function|String} fn The function to call or string to evaluate.
* @oaram {Number} delay The number of milliseconds to delay the `fn` call.
* @param [arg1, arg2, ...] Arguments to invoke `fn` with.
* @returns {Number} The the ID of the timeout.
*/
function setTimeout(fn, delay) {
return schedule(fn, delay, slice.call(arguments, 2));
}
/*--------------------------------------------------------------------------*/
/**
* A logging callback triggered when all testing is completed.
*
* @memberOf QUnit
* @param {Object} details An object with properties `failed`, `passed`,
* `runtime`, and `total`.
*/
function done(details) {
// stop `asyncTest()` from erroneously calling `done()` twice in environments w/o timeouts
if (!QUnit.doneCalled) {
console.log(hr);
console.log(' PASS: ' + details.passed + ' FAIL: ' + details.failed + ' TOTAL: ' + details.total);
console.log(' Finished in ' + details.runtime + ' milliseconds.');
console.log(hr);
// exit out of Rhino
try {
quit();
} catch(e) { }
// exit out of Node.js
try {
process.exit();
} catch(e) { }
// prevent multiple calls to `done()`
QUnit.doneCalled = true;
}
}
/**
* A logging callback triggered after every assertion.
*
* @memberOf QUnit
* @param {Object} details An object with properties `actual`, `expected`,
* `message`, and `result`.
*/
function log(details) {
var expected = details.expected,
result = details.result,
type = typeof expected != 'undefined' ? 'EQ' : 'OK',
assertion = [
result ? 'PASS' : 'FAIL',
type,
details.message || 'ok'
];
if (!result && type == 'EQ') {
assertion.push('Expected: ' + expected + ', Actual: ' + details.actual);
}
QUnit.config.testStats.assertions.push(assertion.join(' | '));
}
/**
* A logging callback triggered at the start of every test module.
*
* @memberOf QUnit
* @param {Object} details An object with property `name`.
*/
function moduleStart(details) {
console.log(hr);
console.log(details.name);
console.log(hr);
}
/**
* Converts an object into a string representation.
*
* @memberOf QUnit
* @type Function
* @param {Object} object The object to stringify.
* @returns {String} The result string.
*/
var parseObject = (function() {
var _parseObject = QUnit.jsDump.parsers.object;
return function(object) {
// fork to support Rhino's error objects
if (typeof object.rhinoException == 'object') {
return object.name +
' { message: "' + object.message +
'", fileName: "' + object.fileName +
'", lineNumber: ' + object.lineNumber + ' }';
}
return _parseObject(object);
};
}());
/**
* A logging callback triggered after a test is completed.
*
* @memberOf QUnit
* @param {Object} details An object with properties `failed`, `name`,
* `passed`, and `total`.
*/
function testDone(details) {
var assertions = QUnit.config.testStats.assertions,
name = details.name;
if (details.failed > 0) {
console.log(' FAIL - '+ name);
each(assertions, function(value) {
console.log(' ' + value);
});
}
else {
console.log(' PASS - ' + name);
}
assertions.length = 0;
}
/*--------------------------------------------------------------------------*/
/**
* An object used to hold information about the current running test.
*
* @memberOf QUnit.config
* @type Object
*/
QUnit.config.testStats = {
/**
* An array of test summaries (pipe separated).
*
* @memberOf QUnit.config.testStats
* @type Array
*/
'assertions': []
};
// add shortcuts to the global
// exclude `module` because some environments have it as a built-in object
each(['asyncTest', 'deepEqual', 'equal', 'equals', 'expect', 'notDeepEqual',
'notEqual', 'notStrictEqual', 'ok', 'raises', 'same', 'start', 'stop',
'strictEqual', 'test'], function(name) {
global[name] = QUnit[name];
});
// expose timer methods to global
try {
timer = new java.util.Timer;
if (!isFunction(global.clearInterval)) {
global.clearInterval = clearTimer;
}
if (!isFunction(global.clearTimeout)) {
global.clearTimeout = clearTimer;
}
if (!isFunction(global.setInterval)) {
global.setInterval = setInterval;
}
if (!isFunction(global.setTimeout)) {
global.setTimeout = setTimeout;
}
} catch(e) { }
// add callbacks
QUnit.done(done);
QUnit.log(log);
QUnit.moduleStart(moduleStart);
QUnit.testDone(testDone);
// wrap `parseObject`
QUnit.jsDump.parsers.object = parseObject;
// must call `QUnit.start()` in the test file if using QUnit < 1.3.0 with Node.js
// or any version of QUnit with Narwhal, Rhino, or RingoJS
QUnit.init();
}(typeof global == 'object' && global || this));

48
vendor/qunit/README.md vendored Normal file
View File

@@ -0,0 +1,48 @@
[QUnit](http://docs.jquery.com/QUnit) - A JavaScript Unit Testing framework.
================================
QUnit is a powerful, easy-to-use, JavaScript test suite. It's used by the jQuery
project to test its code and plugins but is capable of testing any generic
JavaScript code (and even capable of testing JavaScript code on the server-side).
QUnit is especially useful for regression testing: Whenever a bug is reported,
write a test that asserts the existence of that particular bug. Then fix it and
commit both. Every time you work on the code again, run the tests. If the bug
comes up again - a regression - you'll spot it immediately and know how to fix
it, because you know what code you just changed.
Having good unit test coverage makes safe refactoring easy and cheap. You can
run the tests after each small refactoring step and always know what change
broke something.
QUnit is similar to other unit testing frameworks like JUnit, but makes use of
the features JavaScript provides and helps with testing code in the browser, e.g.
with its stop/start facilities for testing asynchronous code.
If you are interested in helping developing QUnit, you are in the right place.
For related discussions, visit the
[QUnit and Testing forum](http://forum.jquery.com/qunit-and-testing).
Planning for a qunitjs.com site and other testing tools related work now happens
on the [jQuery Testing Team planning wiki](http://jquerytesting.pbworks.com/w/page/41556026/FrontPage).
Development
-----------
To submit patches, fork the repository, create a branch for the change. Then implement
the change, run `grunt` to lint and test it, then commit, push and create a pull request.
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`.
Releases
--------
Install git-extras and run `git changelog` to update History.md.
Update qunit/qunit.js|css to the release version, commit and tag, update them
again to the next version, commit and push commits and tags.
Put the 'v' in front of the tag (unlike the 1.1.0 release). Clean up the changelog,
removing merge commits or whitespace cleanups.

236
vendor/qunit/qunit/qunit.css vendored Normal file
View File

@@ -0,0 +1,236 @@
/**
* QUnit v1.8.0 - A JavaScript Unit Testing Framework
*
* http://docs.jquery.com/QUnit
*
* Copyright (c) 2012 John Resig, Jörn Zaefferer
* Dual licensed under the MIT (MIT-LICENSE.txt)
* or GPL (GPL-LICENSE.txt) licenses.
*/
/** Font Family and Sizes */
#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
}
#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
#qunit-tests { font-size: smaller; }
/** Resets */
#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult {
margin: 0;
padding: 0;
}
/** Header */
#qunit-header {
padding: 0.5em 0 0.5em 1em;
color: #8699a4;
background-color: #0d3349;
font-size: 1.5em;
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;
}
#qunit-header a {
text-decoration: none;
color: #c2ccd1;
}
#qunit-header a:hover,
#qunit-header a:focus {
color: #fff;
}
#qunit-header label {
display: inline-block;
padding-left: 0.5em;
}
#qunit-banner {
height: 5px;
}
#qunit-testrunner-toolbar {
padding: 0.5em 0 0.5em 2em;
color: #5E740B;
background-color: #eee;
}
#qunit-userAgent {
padding: 0.5em 0 0.5em 2.5em;
background-color: #2b81af;
color: #fff;
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
}
/** Tests: Pass/Fail */
#qunit-tests {
list-style-position: inside;
}
#qunit-tests li {
padding: 0.4em 0.5em 0.4em 2.5em;
border-bottom: 1px solid #fff;
list-style-position: inside;
}
#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
display: none;
}
#qunit-tests li strong {
cursor: pointer;
}
#qunit-tests li a {
padding: 0.5em;
color: #c2ccd1;
text-decoration: none;
}
#qunit-tests li a:hover,
#qunit-tests li a:focus {
color: #000;
}
#qunit-tests ol {
margin-top: 0.5em;
padding: 0.5em;
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;
}
#qunit-tests table {
border-collapse: collapse;
margin-top: .2em;
}
#qunit-tests th {
text-align: right;
vertical-align: top;
padding: 0 .5em 0 0;
}
#qunit-tests td {
vertical-align: top;
}
#qunit-tests pre {
margin: 0;
white-space: pre-wrap;
word-wrap: break-word;
}
#qunit-tests del {
background-color: #e0f2be;
color: #374e0c;
text-decoration: none;
}
#qunit-tests ins {
background-color: #ffcaca;
color: #500;
text-decoration: none;
}
/*** Test Counts */
#qunit-tests b.counts { color: black; }
#qunit-tests b.passed { color: #5E740B; }
#qunit-tests b.failed { color: #710909; }
#qunit-tests li li {
margin: 0.5em;
padding: 0.4em 0.5em 0.4em 0.5em;
background-color: #fff;
border-bottom: none;
list-style-position: inside;
}
/*** Passing Styles */
#qunit-tests li li.pass {
color: #5E740B;
background-color: #fff;
border-left: 26px solid #C6E746;
}
#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
#qunit-tests .pass .test-name { color: #366097; }
#qunit-tests .pass .test-actual,
#qunit-tests .pass .test-expected { color: #999999; }
#qunit-banner.qunit-pass { background-color: #C6E746; }
/*** Failing Styles */
#qunit-tests li li.fail {
color: #710909;
background-color: #fff;
border-left: 26px 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;
}
#qunit-tests .fail { color: #000000; background-color: #EE5757; }
#qunit-tests .fail .test-name,
#qunit-tests .fail .module-name { color: #000000; }
#qunit-tests .fail .test-actual { color: #EE5757; }
#qunit-tests .fail .test-expected { color: green; }
#qunit-banner.qunit-fail { background-color: #EE5757; }
/** Result */
#qunit-testresult {
padding: 0.5em 0.5em 0.5em 2.5em;
color: #2b81af;
background-color: #D2E0E6;
border-bottom: 1px solid white;
}
#qunit-testresult .module-name {
font-weight: bold;
}
/** Fixture */
#qunit-fixture {
position: absolute;
top: -10000px;
left: -10000px;
width: 1000px;
height: 1000px;
}

1863
vendor/qunit/qunit/qunit.js vendored Normal file

File diff suppressed because it is too large Load Diff

1
vendor/requirejs vendored

Submodule vendor/requirejs deleted from 07de481552

58
vendor/requirejs/LICENSE vendored Normal file
View File

@@ -0,0 +1,58 @@
RequireJS is released under two licenses: new BSD, and MIT. You may pick the
license that best suits your development needs. The text of both licenses are
provided below.
The "New" BSD License:
----------------------
Copyright (c) 2010-2011, The Dojo Foundation
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the Dojo Foundation nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
MIT License
-----------
Copyright (c) 2010-2011, The Dojo Foundation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

51
vendor/requirejs/README.md vendored Normal file
View File

@@ -0,0 +1,51 @@
# RequireJS
RequireJS loads plain JavaScript files as well as more defined modules. It is
optimized for in-browser use, including in
[a Web Worker](http://requirejs.org/docs/api.html#webworker), but it can be used
in other JavaScript environments, like Rhino and
[Node](http://requirejs.org/docs/node.html). It implements the
[Asynchronous Module](https://github.com/amdjs/amdjs-api/wiki/AMD)
API.
RequireJS uses plain script tags to load modules/files, so it should allow for
easy debugging. It can be used
[simply to load existing JavaScript files](http://requirejs.org/docs/api.html#jsfiles),
so you can add it to your existing project without having to re-write your
JavaScript files.
RequireJS includes [an optimization tool](http://requirejs.org/docs/optimization.html)
you can run as part of your packaging steps for deploying your code. The
optimization tool can combine and minify your JavaScript files to allow for
better performance.
If the JavaScript file defines a JavaScript module via
[define()](http://requirejs.org/docs/api.html#define), then there are other benefits
RequireJS can offer: [improvements over traditional CommonJS modules](http://requirejs.org/docs/commonjs.html)
and [loading multiple versions](http://requirejs.org/docs/api.html#multiversion)
of a module in a page. RequireJS also has a plugin system that supports features like
[i18n string bundles](http://requirejs.org/docs/api.html#i18n), and
[text file dependencies](http://requirejs.org/docs/api.html#text).
RequireJS does not have any dependencies on a JavaScript framework.
It is dual-licensed -- new BSD or MIT.
The standard require.js file is around 5.5KB when minified via Closure Compiler
and gzipped.
RequireJS works in IE 6+, Firefox 2+, Safari 3.2+, Chrome 3+, and Opera 10+.
[Latest Release](http://requirejs.org/docs/download.html)
## Directories
* **dist**: Scripts and assets to generate the requirejs.org docs, and for
generating a require.js release.
* **docs**: The raw HTML files for the requirejs.org docs. Only includes the
body of each page. Files in **dist** are used to generate a complete HTML page.
* **tests**: Tests for require.js.
* **testBaseUrl.js**: A file used in the tests inside **tests**. Purposely
placed outside the tests directory for testing paths that go outside a baseUrl.
* **updatesubs.sh**: Updates projects that depend on require.js Assumes the
projects are siblings to this directory and have specific names. Useful to
copy require.js to dependent projects easily while in development.

2030
vendor/requirejs/require.js vendored Normal file

File diff suppressed because it is too large Load Diff

1
vendor/uglifyjs vendored

Submodule vendor/uglifyjs deleted from ef4d776aed

1
vendor/underscore vendored

Submodule vendor/underscore deleted from a4414cede6

22
vendor/underscore/LICENSE vendored Normal file
View File

@@ -0,0 +1,22 @@
Copyright (c) 2009-2012 Jeremy Ashkenas, DocumentCloud
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

19
vendor/underscore/README.md vendored Normal file
View File

@@ -0,0 +1,19 @@
__
/\ \ __
__ __ ___ \_\ \ __ _ __ ____ ___ ___ _ __ __ /\_\ ____
/\ \/\ \ /' _ `\ /'_ \ /'__`\/\ __\/ ,__\ / ___\ / __`\/\ __\/'__`\ \/\ \ /',__\
\ \ \_\ \/\ \/\ \/\ \ \ \/\ __/\ \ \//\__, `\/\ \__//\ \ \ \ \ \//\ __/ __ \ \ \/\__, `\
\ \____/\ \_\ \_\ \___,_\ \____\\ \_\\/\____/\ \____\ \____/\ \_\\ \____\/\_\ _\ \ \/\____/
\/___/ \/_/\/_/\/__,_ /\/____/ \/_/ \/___/ \/____/\/___/ \/_/ \/____/\/_//\ \_\ \/___/
\ \____/
\/___/
Underscore.js is a utility-belt library for JavaScript that provides
support for the usual functional suspects (each, map, reduce, filter...)
without extending any core JavaScript objects.
For Docs, License, Tests, and pre-packed downloads, see:
http://underscorejs.org
Many thanks to our contributors:
https://github.com/documentcloud/underscore/contributors

174
vendor/underscore/test/arrays.js vendored Normal file
View File

@@ -0,0 +1,174 @@
$(document).ready(function() {
module("Arrays");
test("arrays: 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');
equal(_.first([1,2,3], 2).join(', '), '1, 2', 'can pass an index to first');
equal(_.first([1,2,3], 5).join(', '), '1, 2, 3', 'can pass an index to first');
var result = (function(){ return _.first(arguments); })(4, 3, 2, 1);
equal(result, 4, 'works on an arguments object.');
result = _.map([[1,2,3],[1,2,3]], _.first);
equal(result.join(','), '1,1', 'works well with _.map');
result = (function() { return _.take([1,2,3], 2); })();
equal(result.join(','), '1,2', 'aliased as take');
});
test("arrays: 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)');
equal(_.rest(numbers, 2).join(', '), '3, 4', 'rest can take an index');
var result = (function(){ return _(arguments).tail(); })(1, 2, 3, 4);
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');
});
test("arrays: 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);
equal(result.join(", "), "1, 2, 3", 'initial works on arguments object');
result = _.map([[1,2,3],[1,2,3]], _.initial);
equal(_.flatten(result).join(','), '1,2,1,2', 'initial works with _.map');
});
test("arrays: 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');
equal(_.last([1,2,3], 5).join(', '), '1, 2, 3', 'can pass an index to last');
var result = (function(){ return _(arguments).last(); })(1, 2, 3, 4);
equal(result, 4, 'works on an arguments object');
result = _.map([[1,2,3],[1,2,3]], _.last);
equal(result.join(','), '3,3', 'works well with _.map');
});
test("arrays: 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() {
if (window.JSON) {
var list = [1, [2], [3, [[[4]]]]];
equal(JSON.stringify(_.flatten(list)), '[1,2,3,4]', 'can flatten nested arrays');
equal(JSON.stringify(_.flatten(list, true)), '[1,2,3,[[[4]]]]', 'can shallowly flatten nested arrays');
var result = (function(){ return _.flatten(arguments); })(1, [2], [3, [[[4]]]]);
equal(JSON.stringify(result), '[1,2,3,4]', 'works on an arguments object');
}
});
test("arrays: 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);
equal(result.join(', '), '2, 3, 4', 'works on an arguments object');
var list = [{one : 1}, {two : 2}];
ok(_.without(list, {one : 1}).length == 2, 'uses real object identity for comparisons.');
ok(_.without(list, list[0]).length == 1, 'ditto.');
});
test("arrays: 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');
var list = [1, 1, 1, 2, 2, 3];
equal(_.uniq(list, true).join(', '), '1, 2, 3', 'can find the unique values of a sorted array faster');
var list = [{name:'moe'}, {name:'curly'}, {name:'larry'}, {name:'curly'}];
var iterator = function(value) { return value.name; };
equal(_.map(_.uniq(list, false, iterator), iterator).join(', '), 'moe, curly, larry', 'can find the unique values of an array using a custom iterator');
var iterator = function(value) { return value +1; };
var list = [1, 2, 2, 3, 4, 4];
equal(_.uniq(list, true, iterator).join(', '), '1, 2, 3, 4', 'iterator works with sorted array');
var result = (function(){ return _.uniq(arguments); })(1, 2, 1, 3, 1, 4);
equal(result.join(', '), '1, 2, 3, 4', 'works on an arguments object');
});
test("arrays: 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');
var result = (function(){ return _.intersection(arguments, leaders); })('moe', 'curly', 'larry');
equal(result.join(''), 'moe', 'works on an arguments object');
});
test("arrays: 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');
var result = _.union([1, 2, 3], [2, 30, 1], [1, 40, [1]]);
equal(result.join(' '), '1 2 3 30 40 1', 'takes the union of a list of nested arrays');
});
test("arrays: difference", function() {
var result = _.difference([1, 2, 3], [2, 30, 40]);
equal(result.join(' '), '1 3', 'takes the difference of two arrays');
var result = _.difference([1, 2, 3, 4], [2, 30, 40], [1, 11, 111]);
equal(result.join(' '), '3 4', 'takes the difference of three arrays');
});
test('arrays: 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]);
var shouldBe = {moe: 30, larry: 40, curly: 50};
ok(_.isEqual(result, shouldBe), 'two arrays zipped together into an object');
});
test("arrays: indexOf", function() {
var numbers = [1, 2, 3];
numbers.indexOf = null;
equal(_.indexOf(numbers, 2), 1, 'can compute indexOf, even without the native function');
var result = (function(){ return _.indexOf(arguments, 2); })(1, 2, 3);
equal(result, 1, 'works on an arguments object');
equal(_.indexOf(null, 2), -1, 'handles nulls properly');
var numbers = [10, 20, 30, 40, 50], num = 35;
var index = _.indexOf(numbers, num, true);
equal(index, -1, '35 is not in the list');
numbers = [10, 20, 30, 40, 50]; num = 40;
index = _.indexOf(numbers, num, true);
equal(index, 3, '40 is in the list');
numbers = [1, 40, 40, 40, 40, 40, 40, 40, 50, 60, 70]; num = 40;
index = _.indexOf(numbers, num, true);
equal(index, 1, '40 is in the list');
});
test("arrays: 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');
equal(_.lastIndexOf(numbers, 0), 8, 'lastIndexOf the other element');
var result = (function(){ return _.lastIndexOf(arguments, 1); })(1, 0, 1, 0, 0, 1, 0, 0, 0);
equal(result, 5, 'works on an arguments object');
equal(_.indexOf(null, 2), -1, 'handles nulls properly');
});
test("arrays: 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');
equal(_.range(8, 5).join(''), '', 'range with two arguments a &amp; b, b&lt;a generates an empty array');
equal(_.range(3, 10, 3).join(' '), '3 6 9', 'range with three arguments a &amp; b &amp; c, c &lt; b-a, a &lt; b generates an array of elements a,a+c,a+2c,...,b - (multiplier of a) &lt; c');
equal(_.range(3, 10, 15).join(''), '3', 'range with three arguments a &amp; b &amp; c, c &gt; b-a, a &lt; b generates an array with a single element, equal to a');
equal(_.range(12, 7, -2).join(' '), '12 10 8', 'range with three arguments a &amp; b &amp; c, a &gt; b, c &lt; 0 generates an array of elements a,a-c,a-2c and ends with the number not less than b');
equal(_.range(0, -10, -1).join(' '), '0 -1 -2 -3 -4 -5 -6 -7 -8 -9', 'final example in the Python docs');
});
});

59
vendor/underscore/test/chaining.js vendored Normal file
View File

@@ -0,0 +1,59 @@
$(document).ready(function() {
module("Chaining");
test("chaining: map/flatten/reduce", function() {
var lyrics = [
"I'm a lumberjack and I'm okay",
"I sleep all night and I work all day",
"He's a lumberjack and he's okay",
"He sleeps all night and he works all day"
];
var counts = _(lyrics).chain()
.map(function(line) { return line.split(''); })
.flatten()
.reduce(function(hash, l) {
hash[l] = hash[l] || 0;
hash[l]++;
return hash;
}, {}).value();
ok(counts['a'] == 16 && counts['e'] == 10, 'counted all the letters in the song');
});
test("chaining: 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;
}).reject(function(n) {
return n % 4 == 0;
}).sortBy(function(n) {
return -n;
}).value();
equal(numbers.join(', '), "10, 6, 2", "filtered and reversed the numbers");
});
test("chaining: 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;
}).reject(function(n) {
return n % 4 == 0;
}).sortBy(function(n) {
return -n;
}).value();
equal(numbers.join(', '), "10, 6, 2", "filtered and reversed the numbers");
});
test("chaining: reverse/concat/unshift/pop/map", function() {
var numbers = [1,2,3,4,5];
numbers = _(numbers).chain()
.reverse()
.concat([5, 5, 5])
.unshift(17)
.pop()
.map(function(n){ return n * 2; })
.value();
equal(numbers.join(', '), "34, 10, 8, 6, 4, 2, 10, 10", 'can chain together array functions.');
});
});

296
vendor/underscore/test/collections.js vendored Normal file
View File

@@ -0,0 +1,296 @@
$(document).ready(function() {
module("Collections");
test("collections: each", function() {
_.each([1, 2, 3], function(num, i) {
equal(num, i + 1, 'each iterators provide value and iteration count');
});
var answers = [];
_.each([1, 2, 3], function(num){ answers.push(num * this.multiplier);}, {multiplier : 5});
equal(answers.join(', '), '5, 10, 15', 'context object property accessed');
answers = [];
_.forEach([1, 2, 3], function(num){ answers.push(num); });
equal(answers.join(', '), '1, 2, 3', 'aliased as "forEach"');
answers = [];
var obj = {one : 1, two : 2, three : 3};
obj.constructor.prototype.four = 4;
_.each(obj, function(value, key){ answers.push(key); });
equal(answers.join(", "), 'one, two, three', 'iterating over objects works, and ignores the object prototype.');
delete obj.constructor.prototype.four;
answer = null;
_.each([1, 2, 3], function(num, index, arr){ if (_.include(arr, num)) answer = true; });
ok(answer, 'can reference the original collection from inside the iterator');
answers = 0;
_.each(null, function(){ ++answers; });
equal(answers, 0, 'handles a null properly');
});
test('collections: map', function() {
var doubled = _.map([1, 2, 3], function(num){ return num * 2; });
equal(doubled.join(', '), '2, 4, 6', 'doubled numbers');
doubled = _.collect([1, 2, 3], function(num){ return num * 2; });
equal(doubled.join(', '), '2, 4, 6', 'aliased as "collect"');
var tripled = _.map([1, 2, 3], function(num){ return num * this.multiplier; }, {multiplier : 3});
equal(tripled.join(', '), '3, 6, 9', 'tripled numbers with context');
var doubled = _([1, 2, 3]).map(function(num){ return num * 2; });
equal(doubled.join(', '), '2, 4, 6', 'OO-style doubled numbers');
var ids = _.map($('#map-test').children(), function(n){ return n.id; });
deepEqual(ids, ['id1', 'id2'], 'Can use collection methods on NodeLists.');
var ids = _.map(document.images, function(n){ return n.id; });
ok(ids[0] == 'chart_image', 'can use collection methods on HTMLCollections');
var ifnull = _.map(null, function(){});
ok(_.isArray(ifnull) && ifnull.length === 0, 'handles a null properly');
});
test('collections: reduce', function() {
var sum = _.reduce([1, 2, 3], function(sum, num){ return sum + num; }, 0);
equal(sum, 6, 'can sum up an array');
var context = {multiplier : 3};
sum = _.reduce([1, 2, 3], function(sum, num){ return sum + num * this.multiplier; }, 0, context);
equal(sum, 18, 'can reduce with a context object');
sum = _.inject([1, 2, 3], function(sum, num){ return sum + num; }, 0);
equal(sum, 6, 'aliased as "inject"');
sum = _([1, 2, 3]).reduce(function(sum, num){ return sum + num; }, 0);
equal(sum, 6, 'OO-style reduce');
var sum = _.reduce([1, 2, 3], function(sum, num){ return sum + num; });
equal(sum, 6, 'default initial value');
var ifnull;
try {
_.reduce(null, function(){});
} catch (ex) {
ifnull = ex;
}
ok(ifnull instanceof TypeError, 'handles a null (without inital value) properly');
ok(_.reduce(null, function(){}, 138) === 138, 'handles a null (with initial value) properly');
equal(_.reduce([], function(){}, undefined), undefined, 'undefined can be passed as a special case');
raises(function() { _.reduce([], function(){}); }, TypeError, 'throws an error for empty arrays with no initial value');
});
test('collections: reduceRight', function() {
var list = _.reduceRight(["foo", "bar", "baz"], function(memo, str){ return memo + str; }, '');
equal(list, 'bazbarfoo', 'can perform right folds');
var list = _.foldr(["foo", "bar", "baz"], function(memo, str){ return memo + str; }, '');
equal(list, 'bazbarfoo', 'aliased as "foldr"');
var list = _.foldr(["foo", "bar", "baz"], function(memo, str){ return memo + str; });
equal(list, 'bazbarfoo', 'default initial value');
var ifnull;
try {
_.reduceRight(null, function(){});
} catch (ex) {
ifnull = ex;
}
ok(ifnull instanceof TypeError, 'handles a null (without inital value) properly');
ok(_.reduceRight(null, function(){}, 138) === 138, 'handles a null (with initial value) properly');
equal(_.reduceRight([], function(){}, undefined), undefined, 'undefined can be passed as a special case');
raises(function() { _.reduceRight([], function(){}); }, TypeError, 'throws an error for empty arrays with no initial value');
});
test('collections: 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() {
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() {
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');
evens = _.filter([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
equal(evens.join(', '), '2, 4, 6', 'aliased as "filter"');
});
test('collections: 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() {
ok(_.all([], _.identity), 'the empty set');
ok(_.all([true, true, true], _.identity), 'all true values');
ok(!_.all([true, false, true], _.identity), 'one false value');
ok(_.all([0, 10, 28], function(num){ return num % 2 == 0; }), 'even numbers');
ok(!_.all([0, 11, 28], function(num){ return num % 2 == 0; }), 'an odd number');
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"');
});
test('collections: any', function() {
var nativeSome = Array.prototype.some;
Array.prototype.some = null;
ok(!_.any([]), 'the empty set');
ok(!_.any([false, false, false]), 'all false values');
ok(_.any([false, false, true]), 'one true value');
ok(_.any([null, 0, 'yes', false]), 'a string');
ok(!_.any([null, 0, '', false]), 'falsy values');
ok(!_.any([1, 11, 29], function(num){ return num % 2 == 0; }), 'all odd numbers');
ok(_.any([1, 10, 29], function(num){ return num % 2 == 0; }), 'an even number');
ok(_.any([1], _.identity) === true, 'cast to boolean - true');
ok(_.any([0], _.identity) === false, 'cast to boolean - false');
ok(_.some([false, false, true]), 'aliased as "some"');
Array.prototype.some = nativeSome;
});
test('collections: 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() {
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() {
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');
equal(result[1].join(', '), '1, 2, 3', 'second array sorted');
});
// Relevant when using ClojureScript
test('collections: invoke when strings have a call method', function() {
String.prototype.call = function() {
return 42;
};
var list = [[5, 1, 7], [3, 2, 1]];
var s = "foo";
equal(s.call(), 42, "call function exists");
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');
delete String.prototype.call;
equal(s.call, undefined, "call function removed");
});
test('collections: 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() {
equal(3, _.max([1, 2, 3]), 'can perform a regular Math.max');
var neg = _.max([1, 2, 3], function(num){ return -num; });
equal(neg, 1, 'can perform a computation-based max');
equal(-Infinity, _.max({}), 'Maximum value of an empty object');
equal(-Infinity, _.max([]), 'Maximum value of an empty array');
equal(299999, _.max(_.range(1,300000)), "Maximum value of a too-big array");
});
test('collections: min', function() {
equal(1, _.min([1, 2, 3]), 'can perform a regular Math.min');
var neg = _.min([1, 2, 3], function(num){ return -num; });
equal(neg, 3, 'can perform a computation-based min');
equal(Infinity, _.min({}), 'Minimum value of an empty object');
equal(Infinity, _.min([]), 'Minimum value of an empty array');
var now = new Date(9999999999);
var then = new Date(0);
equal(_.min([now, then]), then);
equal(1, _.min(_.range(1,300000)), "Minimum value of a too-big array");
});
test('collections: 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');
var list = [undefined, 4, 1, undefined, 3, 2];
equal(_.sortBy(list, _.identity).join(','), '1,2,3,4,,', 'sortBy with undefined values');
var list = ["one", "two", "three", "four", "five"];
var sorted = _.sortBy(list, 'length');
equal(sorted.join(' '), 'one two four five three', 'sorted by length');
});
test('collections: 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');
var list = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"];
var grouped = _.groupBy(list, 'length');
equal(grouped['3'].join(' '), 'one two six ten');
equal(grouped['4'].join(' '), 'four five nine');
equal(grouped['5'].join(' '), 'three seven eight');
});
test('collections: 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');
var indexFor30 = _.sortedIndex(numbers, 30);
equal(indexFor30, 2, '30 should be inserted at index 2');
});
test('collections: 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() {
ok(!_.isArray(arguments), 'arguments object is not an array');
ok(_.isArray(_.toArray(arguments)), 'arguments object converted into array');
var a = [1,2,3];
ok(_.toArray(a) !== a, 'array is cloned');
equal(_.toArray(a).join(', '), '1, 2, 3', 'cloned array contains same elements');
var numbers = _.toArray({one : 1, two : 2, three : 3});
equal(numbers.join(', '), '1, 2, 3', 'object flattened into array');
var objectWithToArrayFunction = {toArray: function() {
return [1, 2, 3];
}};
equal(_.toArray(objectWithToArrayFunction).join(', '), '1, 2, 3', 'toArray method used if present');
var objectWithToArrayValue = {toArray: 1};
equal(_.toArray(objectWithToArrayValue).join(', '), '1', 'toArray property ignored if not a function');
});
test('collections: 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');
});
});

226
vendor/underscore/test/functions.js vendored Normal file
View File

@@ -0,0 +1,226 @@
$(document).ready(function() {
module("Functions");
test("functions: bind", function() {
var context = {name : 'moe'};
var func = function(arg) { return "name: " + (this.name || arg); };
var bound = _.bind(func, context);
equal(bound(), 'name: moe', 'can bind a function to a context');
bound = _(func).bind(context);
equal(bound(), 'name: moe', 'can do OO-style binding');
bound = _.bind(func, null, 'curly');
equal(bound(), 'name: curly', 'can bind without specifying a context');
func = function(salutation, name) { return salutation + ': ' + name; };
func = _.bind(func, this, 'hello');
equal(func('moe'), 'hello: moe', 'the function was partially applied in advance');
var func = _.bind(func, this, 'curly');
equal(func(), 'hello: curly', 'the function was completely applied in advance');
var func = function(salutation, firstname, lastname) { return salutation + ': ' + firstname + ' ' + lastname; };
func = _.bind(func, this, 'hello', 'moe', 'curly');
equal(func(), 'hello: moe curly', 'the function was partially applied in advance and can accept multiple arguments');
func = function(context, message) { equal(this, context, message); };
_.bind(func, 0, 0, 'can bind a function to `0`')();
_.bind(func, '', '', 'can bind a function to an empty string')();
_.bind(func, false, false, 'can bind a function to `false`')();
// These tests are only meaningful when using a browser without a native bind function
// To test this with a modern browser, set underscore's nativeBind to undefined
var F = function () { return this; };
var Boundf = _.bind(F, {hello: "moe curly"});
equal(new Boundf().hello, undefined, "function should not be bound to the context, to comply with ECMAScript 5");
equal(Boundf().hello, "moe curly", "When called without the new operator, it's OK to be bound to the context");
});
test("functions: bindAll", function() {
var curly = {name : 'curly'}, moe = {
name : 'moe',
getName : function() { return 'name: ' + this.name; },
sayHi : function() { return 'hi: ' + this.name; }
};
curly.getName = moe.getName;
_.bindAll(moe, 'getName', 'sayHi');
curly.sayHi = moe.sayHi;
equal(curly.getName(), 'name: curly', 'unbound function is bound to current object');
equal(curly.sayHi(), 'hi: moe', 'bound function is still bound to original object');
curly = {name : 'curly'};
moe = {
name : 'moe',
getName : function() { return 'name: ' + this.name; },
sayHi : function() { return 'hi: ' + this.name; }
};
_.bindAll(moe);
curly.sayHi = moe.sayHi;
equal(curly.sayHi(), 'hi: moe', 'calling bindAll with no arguments binds all functions to the object');
});
test("functions: memoize", function() {
var fib = function(n) {
return n < 2 ? n : fib(n - 1) + fib(n - 2);
};
var fastFib = _.memoize(fib);
equal(fib(10), 55, 'a memoized version of fibonacci produces identical results');
equal(fastFib(10), 55, 'a memoized version of fibonacci produces identical results');
var o = function(str) {
return str;
};
var fastO = _.memoize(o);
equal(o('toString'), 'toString', 'checks hasOwnProperty');
equal(fastO('toString'), 'toString', 'checks hasOwnProperty');
});
asyncTest("functions: 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() {
var deferred = false;
_.defer(function(bool){ deferred = bool; }, true);
_.delay(function(){ ok(deferred, "deferred the function"); start(); }, 50);
});
asyncTest("functions: throttle", 2, function() {
var counter = 0;
var incr = function(){ counter++; };
var throttledIncr = _.throttle(incr, 100);
throttledIncr(); throttledIncr(); throttledIncr();
setTimeout(throttledIncr, 70);
setTimeout(throttledIncr, 120);
setTimeout(throttledIncr, 140);
setTimeout(throttledIncr, 190);
setTimeout(throttledIncr, 220);
setTimeout(throttledIncr, 240);
_.delay(function(){ equal(counter, 1, "incr was called immediately"); }, 30);
_.delay(function(){ equal(counter, 4, "incr was throttled"); start(); }, 400);
});
asyncTest("functions: throttle arguments", 2, function() {
var value = 0;
var update = function(val){ value = val; };
var throttledUpdate = _.throttle(update, 100);
throttledUpdate(1); throttledUpdate(2); throttledUpdate(3);
setTimeout(function(){ throttledUpdate(4); }, 120);
setTimeout(function(){ throttledUpdate(5); }, 140);
setTimeout(function(){ throttledUpdate(6); }, 250);
_.delay(function(){ equal(value, 1, "updated to latest value"); }, 40);
_.delay(function(){ equal(value, 6, "updated to latest value"); start(); }, 400);
});
asyncTest("functions: throttle once", 2, function() {
var counter = 0;
var incr = function(){ return ++counter; };
var throttledIncr = _.throttle(incr, 100);
var result = throttledIncr();
_.delay(function(){
equal(result, 1, "throttled functions return their value");
equal(counter, 1, "incr was called once"); start();
}, 220);
});
asyncTest("functions: throttle twice", 1, function() {
var counter = 0;
var incr = function(){ counter++; };
var throttledIncr = _.throttle(incr, 100);
throttledIncr(); throttledIncr();
_.delay(function(){ equal(counter, 2, "incr was called twice"); start(); }, 220);
});
asyncTest("functions: debounce", 1, function() {
var counter = 0;
var incr = function(){ counter++; };
var debouncedIncr = _.debounce(incr, 50);
debouncedIncr(); debouncedIncr(); debouncedIncr();
setTimeout(debouncedIncr, 30);
setTimeout(debouncedIncr, 60);
setTimeout(debouncedIncr, 90);
setTimeout(debouncedIncr, 120);
setTimeout(debouncedIncr, 150);
_.delay(function(){ equal(counter, 1, "incr was debounced"); start(); }, 220);
});
asyncTest("functions: debounce asap", 2, function() {
var counter = 0;
var incr = function(){ counter++; };
var debouncedIncr = _.debounce(incr, 50, true);
debouncedIncr(); debouncedIncr(); debouncedIncr();
equal(counter, 1, 'incr was called immediately');
setTimeout(debouncedIncr, 30);
setTimeout(debouncedIncr, 60);
setTimeout(debouncedIncr, 90);
setTimeout(debouncedIncr, 120);
setTimeout(debouncedIncr, 150);
_.delay(function(){ equal(counter, 1, "incr was debounced"); start(); }, 220);
});
asyncTest("functions: debounce asap recursively", 2, function() {
var counter = 0;
var debouncedIncr = _.debounce(function(){
counter++;
if (counter < 5) debouncedIncr();
}, 50, true);
debouncedIncr();
equal(counter, 1, 'incr was called immediately');
_.delay(function(){ equal(counter, 1, "incr was debounced"); start(); }, 70);
});
test("functions: once", function() {
var num = 0;
var increment = _.once(function(){ num++; });
increment();
increment();
equal(num, 1);
});
test("functions: 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');
var inner = function(){ return "Hello "; };
var obj = {name : "Moe"};
obj.hi = _.wrap(inner, function(fn){ return fn() + this.name; });
equal(obj.hi(), "Hello Moe");
var noop = function(){};
var wrapped = _.wrap(noop, function(fn){ return Array.prototype.slice.call(arguments, 0); });
var ret = wrapped(['whats', 'your'], 'vector', 'victor');
deepEqual(ret, [noop, ['whats', 'your'], 'vector', 'victor']);
});
test("functions: compose", function() {
var greet = function(name){ return "hi: " + name; };
var exclaim = function(sentence){ return sentence + '!'; };
var composed = _.compose(exclaim, greet);
equal(composed('moe'), 'hi: moe!', 'can compose a function that takes another');
composed = _.compose(greet, exclaim);
equal(composed('moe'), 'hi: moe!', 'in this case, the functions are also commutative');
});
test("functions: after", function() {
var testAfter = function(afterAmount, timesCalled) {
var afterCalled = 0;
var after = _.after(afterAmount, function() {
afterCalled++;
});
while (timesCalled--) after();
return afterCalled;
};
equal(testAfter(5, 5), 1, "after(N) should fire after being called N times");
equal(testAfter(5, 4), 0, "after(N) should not fire unless called N times");
equal(testAfter(0, 0), 1, "after(0) should fire immediately");
});
});

558
vendor/underscore/test/objects.js vendored Normal file
View File

@@ -0,0 +1,558 @@
$(document).ready(function() {
module("Objects");
test("objects: 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;
equal(_.keys(a).join(', '), '1', 'is not fooled by sparse arrays; see issue #95');
raises(function() { _.keys(null); }, TypeError, 'throws an error for `null` values');
raises(function() { _.keys(void 0); }, TypeError, 'throws an error for `undefined` values');
raises(function() { _.keys(1); }, TypeError, 'throws an error for number primitives');
raises(function() { _.keys('a'); }, TypeError, 'throws an error for string primitives');
raises(function() { _.keys(true); }, TypeError, 'throws an error for boolean primitives');
});
test("objects: values", function() {
equal(_.values({one : 1, two : 2}).join(', '), '1, 2', 'can extract the values from an object');
});
test("objects: 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');
var Animal = function(){};
Animal.prototype.run = function(){};
equal(_.functions(new Animal).join(''), 'run', 'also looks up functions on the prototype');
});
test("objects: 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');
equal(_.extend({x:'x'}, {a:'b'}).x, 'x', 'properties not in source dont get overriden');
result = _.extend({x:'x'}, {a:'a'}, {b:'b'});
ok(_.isEqual(result, {x:'x', a:'a', b:'b'}), 'can extend from multiple source objects');
result = _.extend({x:'x'}, {a:'a', x:2}, {a:'b'});
ok(_.isEqual(result, {x:2, a:'b'}), 'extending from multiple source objects last property trumps');
result = _.extend({}, {a: void 0, b: null});
equal(_.keys(result).join(''), 'ab', 'extend does not copy undefined values');
});
test("objects: 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');
result = _.pick({a:1, b:2, c:3}, ['b', 'c']);
ok(_.isEqual(result, {b:2, c:3}), 'can restrict properties to those named in an array');
result = _.pick({a:1, b:2, c:3}, ['a'], 'b');
ok(_.isEqual(result, {a:1, b:2}), 'can restrict properties to those named in mixed args');
});
test("objects: defaults", function() {
var result;
var options = {zero: 0, one: 1, empty: "", nan: NaN, string: "string"};
_.defaults(options, {zero: 1, one: 10, twenty: 20});
equal(options.zero, 0, 'value exists');
equal(options.one, 1, 'value exists');
equal(options.twenty, 20, 'default applied');
_.defaults(options, {empty: "full"}, {nan: "nan"}, {word: "word"}, {word: "dog"});
equal(options.empty, "", 'value exists');
ok(_.isNaN(options.nan), "NaN isn't overridden");
equal(options.word, "word", 'new value is added, first one wins');
});
test("objects: 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');
clone.name = 'curly';
ok(clone.name == 'curly' && moe.name == 'moe', 'clones can change shallow attributes without affecting the original');
clone.lucky.push(101);
equal(_.last(moe.lucky), 101, 'changes to deep attributes are shared with the original');
equal(_.clone(undefined), void 0, 'non objects should not be changed by clone');
equal(_.clone(1), 1, 'non objects should not be changed by clone');
equal(_.clone(null), null, 'non objects should not be changed by clone');
});
test("objects: isEqual", function() {
function First() {
this.value = 1;
}
First.prototype.value = 1;
function Second() {
this.value = 1;
}
Second.prototype.value = 2;
// Basic equality and identity comparisons.
ok(_.isEqual(null, null), "`null` is equal to `null`");
ok(_.isEqual(), "`undefined` is equal to `undefined`");
ok(!_.isEqual(0, -0), "`0` is not equal to `-0`");
ok(!_.isEqual(-0, 0), "Commutative equality is implemented for `0` and `-0`");
ok(!_.isEqual(null, undefined), "`null` is not equal to `undefined`");
ok(!_.isEqual(undefined, null), "Commutative equality is implemented for `null` and `undefined`");
// String object and primitive comparisons.
ok(_.isEqual("Curly", "Curly"), "Identical string primitives are equal");
ok(_.isEqual(new String("Curly"), new String("Curly")), "String objects with identical primitive values are equal");
ok(_.isEqual(new String("Curly"), "Curly"), "String primitives and their corresponding object wrappers are equal");
ok(_.isEqual("Curly", new String("Curly")), "Commutative equality is implemented for string objects and primitives");
ok(!_.isEqual("Curly", "Larry"), "String primitives with different values are not equal");
ok(!_.isEqual(new String("Curly"), new String("Larry")), "String objects with different primitive values are not equal");
ok(!_.isEqual(new String("Curly"), {toString: function(){ return "Curly"; }}), "String objects and objects with a custom `toString` method are not equal");
// Number object and primitive comparisons.
ok(_.isEqual(75, 75), "Identical number primitives are equal");
ok(_.isEqual(new Number(75), new Number(75)), "Number objects with identical primitive values are equal");
ok(_.isEqual(75, new Number(75)), "Number primitives and their corresponding object wrappers are equal");
ok(_.isEqual(new Number(75), 75), "Commutative equality is implemented for number objects and primitives");
ok(!_.isEqual(new Number(0), -0), "`new Number(0)` and `-0` are not equal");
ok(!_.isEqual(0, new Number(-0)), "Commutative equality is implemented for `new Number(0)` and `-0`");
ok(!_.isEqual(new Number(75), new Number(63)), "Number objects with different primitive values are not equal");
ok(!_.isEqual(new Number(63), {valueOf: function(){ return 63; }}), "Number objects and objects with a `valueOf` method are not equal");
// Comparisons involving `NaN`.
ok(_.isEqual(NaN, NaN), "`NaN` is equal to `NaN`");
ok(!_.isEqual(61, NaN), "A number primitive is not equal to `NaN`");
ok(!_.isEqual(new Number(79), NaN), "A number object is not equal to `NaN`");
ok(!_.isEqual(Infinity, NaN), "`Infinity` is not equal to `NaN`");
// Boolean object and primitive comparisons.
ok(_.isEqual(true, true), "Identical boolean primitives are equal");
ok(_.isEqual(new Boolean, new Boolean), "Boolean objects with identical primitive values are equal");
ok(_.isEqual(true, new Boolean(true)), "Boolean primitives and their corresponding object wrappers are equal");
ok(_.isEqual(new Boolean(true), true), "Commutative equality is implemented for booleans");
ok(!_.isEqual(new Boolean(true), new Boolean), "Boolean objects with different primitive values are not equal");
// Common type coercions.
ok(!_.isEqual(true, new Boolean(false)), "Boolean objects are not equal to the boolean primitive `true`");
ok(!_.isEqual("75", 75), "String and number primitives with like values are not equal");
ok(!_.isEqual(new Number(63), new String(63)), "String and number objects with like values are not equal");
ok(!_.isEqual(75, "75"), "Commutative equality is implemented for like string and number values");
ok(!_.isEqual(0, ""), "Number and string primitives with like values are not equal");
ok(!_.isEqual(1, true), "Number and boolean primitives with like values are not equal");
ok(!_.isEqual(new Boolean(false), new Number(0)), "Boolean and number objects with like values are not equal");
ok(!_.isEqual(false, new String("")), "Boolean primitives and string objects with like values are not equal");
ok(!_.isEqual(12564504e5, new Date(2009, 9, 25)), "Dates and their corresponding numeric primitive values are not equal");
// Dates.
ok(_.isEqual(new Date(2009, 9, 25), new Date(2009, 9, 25)), "Date objects referencing identical times are equal");
ok(!_.isEqual(new Date(2009, 9, 25), new Date(2009, 11, 13)), "Date objects referencing different times are not equal");
ok(!_.isEqual(new Date(2009, 11, 13), {
getTime: function(){
return 12606876e5;
}
}), "Date objects and objects with a `getTime` method are not equal");
ok(!_.isEqual(new Date("Curly"), new Date("Curly")), "Invalid dates are not equal");
// Functions.
ok(!_.isEqual(First, Second), "Different functions with identical bodies and source code representations are not equal");
// RegExps.
ok(_.isEqual(/(?:)/gim, /(?:)/gim), "RegExps with equivalent patterns and flags are equal");
ok(!_.isEqual(/(?:)/g, /(?:)/gi), "RegExps with equivalent patterns and different flags are not equal");
ok(!_.isEqual(/Moe/gim, /Curly/gim), "RegExps with different patterns and equivalent flags are not equal");
ok(!_.isEqual(/(?:)/gi, /(?:)/g), "Commutative equality is implemented for RegExps");
ok(!_.isEqual(/Curly/g, {source: "Larry", global: true, ignoreCase: false, multiline: false}), "RegExps and RegExp-like objects are not equal");
// Empty arrays, array-like objects, and object literals.
ok(_.isEqual({}, {}), "Empty object literals are equal");
ok(_.isEqual([], []), "Empty array literals are equal");
ok(_.isEqual([{}], [{}]), "Empty nested arrays and objects are equal");
ok(!_.isEqual({length: 0}, []), "Array-like objects and arrays are not equal.");
ok(!_.isEqual([], {length: 0}), "Commutative equality is implemented for array-like objects");
ok(!_.isEqual({}, []), "Object literals and array literals are not equal");
ok(!_.isEqual([], {}), "Commutative equality is implemented for objects and arrays");
// Arrays with primitive and object values.
ok(_.isEqual([1, "Larry", true], [1, "Larry", true]), "Arrays containing identical primitives are equal");
ok(_.isEqual([(/Moe/g), new Date(2009, 9, 25)], [(/Moe/g), new Date(2009, 9, 25)]), "Arrays containing equivalent elements are equal");
// Multi-dimensional arrays.
var a = [new Number(47), false, "Larry", /Moe/, new Date(2009, 11, 13), ['running', 'biking', new String('programming')], {a: 47}];
var b = [new Number(47), false, "Larry", /Moe/, new Date(2009, 11, 13), ['running', 'biking', new String('programming')], {a: 47}];
ok(_.isEqual(a, b), "Arrays containing nested arrays and objects are recursively compared");
// Overwrite the methods defined in ES 5.1 section 15.4.4.
a.forEach = a.map = a.filter = a.every = a.indexOf = a.lastIndexOf = a.some = a.reduce = a.reduceRight = null;
b.join = b.pop = b.reverse = b.shift = b.slice = b.splice = b.concat = b.sort = b.unshift = null;
// Array elements and properties.
ok(_.isEqual(a, b), "Arrays containing equivalent elements and different non-numeric properties are equal");
a.push("White Rocks");
ok(!_.isEqual(a, b), "Arrays of different lengths are not equal");
a.push("East Boulder");
b.push("Gunbarrel Ranch", "Teller Farm");
ok(!_.isEqual(a, b), "Arrays of identical lengths containing different elements are not equal");
// Sparse arrays.
ok(_.isEqual(Array(3), Array(3)), "Sparse arrays of identical lengths are equal");
ok(!_.isEqual(Array(3), Array(6)), "Sparse arrays of different lengths are not equal when both are empty");
// According to the Microsoft deviations spec, section 2.1.26, JScript 5.x treats `undefined`
// elements in arrays as elisions. Thus, sparse arrays and dense arrays containing `undefined`
// values are equivalent.
if (0 in [undefined]) {
ok(!_.isEqual(Array(3), [undefined, undefined, undefined]), "Sparse and dense arrays are not equal");
ok(!_.isEqual([undefined, undefined, undefined], Array(3)), "Commutative equality is implemented for sparse and dense arrays");
}
// Simple objects.
ok(_.isEqual({a: "Curly", b: 1, c: true}, {a: "Curly", b: 1, c: true}), "Objects containing identical primitives are equal");
ok(_.isEqual({a: /Curly/g, b: new Date(2009, 11, 13)}, {a: /Curly/g, b: new Date(2009, 11, 13)}), "Objects containing equivalent members are equal");
ok(!_.isEqual({a: 63, b: 75}, {a: 61, b: 55}), "Objects of identical sizes with different values are not equal");
ok(!_.isEqual({a: 63, b: 75}, {a: 61, c: 55}), "Objects of identical sizes with different property names are not equal");
ok(!_.isEqual({a: 1, b: 2}, {a: 1}), "Objects of different sizes are not equal");
ok(!_.isEqual({a: 1}, {a: 1, b: 2}), "Commutative equality is implemented for objects");
ok(!_.isEqual({x: 1, y: undefined}, {x: 1, z: 2}), "Objects with identical keys and different values are not equivalent");
// `A` contains nested objects and arrays.
a = {
name: new String("Moe Howard"),
age: new Number(77),
stooge: true,
hobbies: ["acting"],
film: {
name: "Sing a Song of Six Pants",
release: new Date(1947, 9, 30),
stars: [new String("Larry Fine"), "Shemp Howard"],
minutes: new Number(16),
seconds: 54
}
};
// `B` contains equivalent nested objects and arrays.
b = {
name: new String("Moe Howard"),
age: new Number(77),
stooge: true,
hobbies: ["acting"],
film: {
name: "Sing a Song of Six Pants",
release: new Date(1947, 9, 30),
stars: [new String("Larry Fine"), "Shemp Howard"],
minutes: new Number(16),
seconds: 54
}
};
ok(_.isEqual(a, b), "Objects with nested equivalent members are recursively compared");
// Instances.
ok(_.isEqual(new First, new First), "Object instances are equal");
ok(!_.isEqual(new First, new Second), "Objects with different constructors and identical own properties are not equal");
ok(!_.isEqual({value: 1}, new First), "Object instances and objects sharing equivalent properties are not equal");
ok(!_.isEqual({value: 2}, new Second), "The prototype chain of objects should not be examined");
// Circular Arrays.
(a = []).push(a);
(b = []).push(b);
ok(_.isEqual(a, b), "Arrays containing circular references are equal");
a.push(new String("Larry"));
b.push(new String("Larry"));
ok(_.isEqual(a, b), "Arrays containing circular references and equivalent properties are equal");
a.push("Shemp");
b.push("Curly");
ok(!_.isEqual(a, b), "Arrays containing circular references and different properties are not equal");
// Circular Objects.
a = {abc: null};
b = {abc: null};
a.abc = a;
b.abc = b;
ok(_.isEqual(a, b), "Objects containing circular references are equal");
a.def = 75;
b.def = 75;
ok(_.isEqual(a, b), "Objects containing circular references and equivalent properties are equal");
a.def = new Number(75);
b.def = new Number(63);
ok(!_.isEqual(a, b), "Objects containing circular references and different properties are not equal");
// Cyclic Structures.
a = [{abc: null}];
b = [{abc: null}];
(a[0].abc = a).push(a);
(b[0].abc = b).push(b);
ok(_.isEqual(a, b), "Cyclic structures are equal");
a[0].def = "Larry";
b[0].def = "Larry";
ok(_.isEqual(a, b), "Cyclic structures containing equivalent properties are equal");
a[0].def = new String("Larry");
b[0].def = new String("Curly");
ok(!_.isEqual(a, b), "Cyclic structures containing different properties are not equal");
// Complex Circular References.
a = {foo: {b: {foo: {c: {foo: null}}}}};
b = {foo: {b: {foo: {c: {foo: null}}}}};
a.foo.b.foo.c.foo = a;
b.foo.b.foo.c.foo = b;
ok(_.isEqual(a, b), "Cyclic structures with nested and identically-named properties are equal");
// Chaining.
ok(!_.isEqual(_({x: 1, y: undefined}).chain(), _({x: 1, z: 2}).chain()), 'Chained objects containing different values are not equal');
equal(_({x: 1, y: 2}).chain().isEqual(_({x: 1, y: 2}).chain()).value(), true, '`isEqual` can be chained');
// Custom `isEqual` methods.
var isEqualObj = {isEqual: function (o) { return o.isEqual == this.isEqual; }, unique: {}};
var isEqualObjClone = {isEqual: isEqualObj.isEqual, unique: {}};
ok(_.isEqual(isEqualObj, isEqualObjClone), 'Both objects implement identical `isEqual` methods');
ok(_.isEqual(isEqualObjClone, isEqualObj), 'Commutative equality is implemented for objects with custom `isEqual` methods');
ok(!_.isEqual(isEqualObj, {}), 'Objects that do not implement equivalent `isEqual` methods are not equal');
ok(!_.isEqual({}, isEqualObj), 'Commutative equality is implemented for objects with different `isEqual` methods');
// Custom `isEqual` methods - comparing different types
LocalizedString = (function() {
function LocalizedString(id) { this.id = id; this.string = (this.id===10)? 'Bonjour': ''; }
LocalizedString.prototype.isEqual = function(that) {
if (_.isString(that)) return this.string == that;
else if (that instanceof LocalizedString) return this.id == that.id;
return false;
};
return LocalizedString;
})();
var localized_string1 = new LocalizedString(10), localized_string2 = new LocalizedString(10), localized_string3 = new LocalizedString(11);
ok(_.isEqual(localized_string1, localized_string2), 'comparing same typed instances with same ids');
ok(!_.isEqual(localized_string1, localized_string3), 'comparing same typed instances with different ids');
ok(_.isEqual(localized_string1, 'Bonjour'), 'comparing different typed instances with same values');
ok(_.isEqual('Bonjour', localized_string1), 'comparing different typed instances with same values');
ok(!_.isEqual('Bonjour', localized_string3), 'comparing two localized strings with different ids');
ok(!_.isEqual(localized_string1, 'Au revoir'), 'comparing different typed instances with different values');
ok(!_.isEqual('Au revoir', localized_string1), 'comparing different typed instances with different values');
// Custom `isEqual` methods - comparing with serialized data
Date.prototype.toJSON = function() {
return {
_type:'Date',
year:this.getUTCFullYear(),
month:this.getUTCMonth(),
day:this.getUTCDate(),
hours:this.getUTCHours(),
minutes:this.getUTCMinutes(),
seconds:this.getUTCSeconds()
};
};
Date.prototype.isEqual = function(that) {
var this_date_components = this.toJSON();
var that_date_components = (that instanceof Date) ? that.toJSON() : that;
delete this_date_components['_type']; delete that_date_components['_type'];
return _.isEqual(this_date_components, that_date_components);
};
var date = new Date();
var date_json = {
_type:'Date',
year:date.getUTCFullYear(),
month:date.getUTCMonth(),
day:date.getUTCDate(),
hours:date.getUTCHours(),
minutes:date.getUTCMinutes(),
seconds:date.getUTCSeconds()
};
ok(_.isEqual(date_json, date), 'serialized date matches date');
ok(_.isEqual(date, date_json), 'date matches serialized date');
});
test("objects: isEmpty", function() {
ok(!_([1]).isEmpty(), '[1] is not empty');
ok(_.isEmpty([]), '[] is empty');
ok(!_.isEmpty({one : 1}), '{one : 1} is not empty');
ok(_.isEmpty({}), '{} is empty');
ok(_.isEmpty(new RegExp('')), 'objects with prototype properties are empty');
ok(_.isEmpty(null), 'null is empty');
ok(_.isEmpty(), 'undefined is empty');
ok(_.isEmpty(''), 'the empty string is empty');
ok(!_.isEmpty('moe'), 'but other strings are not');
var obj = {one : 1};
delete obj.one;
ok(_.isEmpty(obj), 'deleting all the keys from an object empties it');
});
// Setup remote variables for iFrame tests.
var iframe = document.createElement('iframe');
jQuery(iframe).appendTo(document.body);
var iDoc = iframe.contentDocument || iframe.contentWindow.document;
iDoc.write(
"<script>\
parent.iElement = document.createElement('div');\
parent.iArguments = (function(){ return arguments; })(1, 2, 3);\
parent.iArray = [1, 2, 3];\
parent.iString = new String('hello');\
parent.iNumber = new Number(100);\
parent.iFunction = (function(){});\
parent.iDate = new Date();\
parent.iRegExp = /hi/;\
parent.iNaN = NaN;\
parent.iNull = null;\
parent.iBoolean = new Boolean(false);\
parent.iUndefined = undefined;\
</script>"
);
iDoc.close();
test("objects: 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() {
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');
ok(_.isArguments(args), 'but the arguments object is an arguments object');
ok(!_.isArguments(_.toArray(args)), 'but not when it\'s converted into an array');
ok(!_.isArguments([1,2,3]), 'and not vanilla arrays.');
ok(_.isArguments(iArguments), 'even from another frame');
});
test("objects: isObject", function() {
ok(_.isObject(arguments), 'the arguments object is object');
ok(_.isObject([1, 2, 3]), 'and arrays');
ok(_.isObject($('html')[0]), 'and DOM element');
ok(_.isObject(iElement), 'even from another frame');
ok(_.isObject(function () {}), 'and functions');
ok(_.isObject(iFunction), 'even from another frame');
ok(!_.isObject(null), 'but not null');
ok(!_.isObject(undefined), 'and not undefined');
ok(!_.isObject('string'), 'and not string');
ok(!_.isObject(12), 'and not number');
ok(!_.isObject(true), 'and not boolean');
ok(_.isObject(new String('string')), 'but new String()');
});
test("objects: 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() {
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() {
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');
ok(_.isNumber(3 * 4 - 7 / 10), 'but numbers are');
ok(_.isNumber(NaN), 'NaN *is* a number');
ok(_.isNumber(Infinity), 'Infinity is a number');
ok(_.isNumber(iNumber), 'even from another frame');
ok(!_.isNumber('1'), 'numeric strings are not numbers');
});
test("objects: 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');
ok(!_.isBoolean("true"), 'the string "true" is not a boolean');
ok(!_.isBoolean(arguments), 'the arguments object is not a boolean');
ok(!_.isBoolean(undefined), 'undefined is not a boolean');
ok(!_.isBoolean(NaN), 'NaN is not a boolean');
ok(!_.isBoolean(null), 'null is not a boolean');
ok(_.isBoolean(true), 'but true is');
ok(_.isBoolean(false), 'and so is false');
ok(_.isBoolean(iBoolean), 'even from another frame');
});
test("objects: 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() {
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() {
ok(!_.isRegExp(_.identity), 'functions are not RegExps');
ok(_.isRegExp(/identity/), 'but RegExps are');
ok(_.isRegExp(iRegExp), 'even from another frame');
});
test("objects: isFinite", function() {
ok(!_.isFinite(undefined), 'undefined is not Finite');
ok(!_.isFinite(null), 'null is not Finite');
ok(!_.isFinite(NaN), 'NaN is not Finite');
ok(!_.isFinite(Infinity), 'Infinity is not Finite');
ok(!_.isFinite(-Infinity), '-Infinity is not Finite');
ok(!_.isFinite('12'), 'Strings are not numbers');
var obj = new Number(5);
ok(_.isFinite(obj), 'Number instances can be finite');
ok(_.isFinite(0), '0 is Finite');
ok(_.isFinite(123), 'Ints are Finite');
ok(_.isFinite(-12.44), 'Floats are Finite');
});
test("objects: isNaN", function() {
ok(!_.isNaN(undefined), 'undefined is not NaN');
ok(!_.isNaN(null), 'null is not NaN');
ok(!_.isNaN(0), '0 is not NaN');
ok(_.isNaN(NaN), 'but NaN is');
ok(_.isNaN(iNaN), 'even from another frame');
});
test("objects: 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() {
ok(!_.isUndefined(1), 'numbers are defined');
ok(!_.isUndefined(null), 'null is defined');
ok(!_.isUndefined(false), 'false is defined');
ok(!_.isUndefined(NaN), 'NaN is defined');
ok(_.isUndefined(), 'nothing is undefined');
ok(_.isUndefined(undefined), 'undefined is undefined');
ok(_.isUndefined(iUndefined), 'even from another frame');
});
if (window.ActiveXObject) {
test("objects: IE host objects", function() {
var xml = new ActiveXObject("Msxml2.DOMDocument.3.0");
ok(!_.isNumber(xml));
ok(!_.isBoolean(xml));
ok(!_.isNaN(xml));
ok(!_.isFunction(xml));
ok(!_.isNull(xml));
ok(!_.isUndefined(xml));
});
}
test("objects: tap", function() {
var intercepted = null;
var interceptor = function(obj) { intercepted = obj; };
var returned = _.tap(1, interceptor);
equal(intercepted, 1, "passes tapped object to interceptor");
equal(returned, 1, "returns tapped object");
returned = _([1,2,3]).chain().
map(function(n){ return n * 2; }).
max().
tap(interceptor).
value();
ok(returned == 6 && intercepted == 6, 'can use tapped objects in a chain');
});
});

215
vendor/underscore/test/utility.js vendored Normal file
View File

@@ -0,0 +1,215 @@
$(document).ready(function() {
var templateSettings;
module("Utility", {
setup: function() {
templateSettings = _.clone(_.templateSettings);
},
teardown: function() {
_.templateSettings = templateSettings;
}
});
test("utility: identity", function() {
var moe = {name : 'moe'};
equal(_.identity(moe), moe, 'moe is the same as his identity');
});
test("utility: 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() {
var vals = [];
_.times(3, function (i) { vals.push(i); });
ok(_.isEqual(vals, [0,1,2]), "is 0 indexed");
//
vals = [];
_(3).times(function (i) { vals.push(i); });
ok(_.isEqual(vals, [0,1,2]), "works as a wrapper");
});
test("utility: mixin", function() {
_.mixin({
myReverse: function(string) {
return string.split('').reverse().join('');
}
});
equal(_.myReverse('panacea'), 'aecanap', 'mixed in a function to _');
equal(_('champ').myReverse(), 'pmahc', 'mixed in a function to the OOP wrapper');
});
test("utility: _.escape", function() {
equal(_.escape("Curly & Moe"), "Curly &amp; Moe");
equal(_.escape("Curly &amp; Moe"), "Curly &amp;amp; Moe");
});
test("utility: 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');
var sansSemicolonTemplate = _.template("A <% this %> B");
equal(sansSemicolonTemplate(), "A B");
var backslashTemplate = _.template("<%= thing %> is \\ridanculous");
equal(backslashTemplate({thing: 'This'}), "This is \\ridanculous");
var escapeTemplate = _.template('<%= a ? "checked=\\"checked\\"" : "" %>');
equal(escapeTemplate({a: true}), 'checked="checked"', 'can handle slash escapes in interpolations.');
var fancyTemplate = _.template("<ul><% \
for (key in people) { \
%><li><%= people[key] %></li><% } %></ul>");
result = fancyTemplate({people : {moe : "Moe", larry : "Larry", curly : "Curly"}});
equal(result, "<ul><li>Moe</li><li>Larry</li><li>Curly</li></ul>", 'can run arbitrary javascript in templates');
var escapedCharsInJavascriptTemplate = _.template("<ul><% _.each(numbers.split('\\n'), function(item) { %><li><%= item %></li><% }) %></ul>");
result = escapedCharsInJavascriptTemplate({numbers: "one\ntwo\nthree\nfour"});
equal(result, "<ul><li>one</li><li>two</li><li>three</li><li>four</li></ul>", 'Can use escaped characters (e.g. \\n) in Javascript');
var namespaceCollisionTemplate = _.template("<%= pageCount %> <%= thumbnails[pageCount] %> <% _.each(thumbnails, function(p) { %><div class=\"thumbnail\" rel=\"<%= p %>\"></div><% }); %>");
result = namespaceCollisionTemplate({
pageCount: 3,
thumbnails: {
1: "p1-thumbnail.gif",
2: "p2-thumbnail.gif",
3: "p3-thumbnail.gif"
}
});
equal(result, "3 p3-thumbnail.gif <div class=\"thumbnail\" rel=\"p1-thumbnail.gif\"></div><div class=\"thumbnail\" rel=\"p2-thumbnail.gif\"></div><div class=\"thumbnail\" rel=\"p3-thumbnail.gif\"></div>");
var noInterpolateTemplate = _.template("<div><p>Just some text. Hey, I know this is silly but it aids consistency.</p></div>");
result = noInterpolateTemplate();
equal(result, "<div><p>Just some text. Hey, I know this is silly but it aids consistency.</p></div>");
var quoteTemplate = _.template("It's its, not it's");
equal(quoteTemplate({}), "It's its, not it's");
var quoteInStatementAndBody = _.template("<%\
if(foo == 'bar'){ \
%>Statement quotes and 'quotes'.<% } %>");
equal(quoteInStatementAndBody({foo: "bar"}), "Statement quotes and 'quotes'.");
var withNewlinesAndTabs = _.template('This\n\t\tis: <%= x %>.\n\tok.\nend.');
equal(withNewlinesAndTabs({x: 'that'}), 'This\n\t\tis: that.\n\tok.\nend.');
var template = _.template("<i><%- value %></i>");
var result = template({value: "<script>"});
equal(result, '<i>&lt;script&gt;</i>');
var stooge = {
name: "Moe",
template: _.template("I'm <%= this.name %>")
};
equal(stooge.template(), "I'm Moe");
if (!$.browser.msie) {
var fromHTML = _.template($('#template').html());
equal(fromHTML({data : 12345}).replace(/\s/g, ''), '<li>24690</li>');
}
_.templateSettings = {
evaluate : /\{\{([\s\S]+?)\}\}/g,
interpolate : /\{\{=([\s\S]+?)\}\}/g
};
var custom = _.template("<ul>{{ for (key in people) { }}<li>{{= people[key] }}</li>{{ } }}</ul>");
result = custom({people : {moe : "Moe", larry : "Larry", curly : "Curly"}});
equal(result, "<ul><li>Moe</li><li>Larry</li><li>Curly</li></ul>", 'can run arbitrary javascript in templates');
var customQuote = _.template("It's its, not it's");
equal(customQuote({}), "It's its, not it's");
var quoteInStatementAndBody = _.template("{{ if(foo == 'bar'){ }}Statement quotes and 'quotes'.{{ } }}");
equal(quoteInStatementAndBody({foo: "bar"}), "Statement quotes and 'quotes'.");
_.templateSettings = {
evaluate : /<\?([\s\S]+?)\?>/g,
interpolate : /<\?=([\s\S]+?)\?>/g
};
var customWithSpecialChars = _.template("<ul><? for (key in people) { ?><li><?= people[key] ?></li><? } ?></ul>");
result = customWithSpecialChars({people : {moe : "Moe", larry : "Larry", curly : "Curly"}});
equal(result, "<ul><li>Moe</li><li>Larry</li><li>Curly</li></ul>", 'can run arbitrary javascript in templates');
var customWithSpecialCharsQuote = _.template("It's its, not it's");
equal(customWithSpecialCharsQuote({}), "It's its, not it's");
var quoteInStatementAndBody = _.template("<? if(foo == 'bar'){ ?>Statement quotes and 'quotes'.<? } ?>");
equal(quoteInStatementAndBody({foo: "bar"}), "Statement quotes and 'quotes'.");
_.templateSettings = {
interpolate : /\{\{(.+?)\}\}/g
};
var mustache = _.template("Hello {{planet}}!");
equal(mustache({planet : "World"}), "Hello World!", "can mimic mustache.js");
var templateWithNull = _.template("a null undefined {{planet}}");
equal(templateWithNull({planet : "world"}), "a null undefined world", "can handle missing escape and evaluate settings");
});
test('_.template handles \\u2028 & \\u2029', function() {
var tmpl = _.template('<p>\u2028<%= "\\u2028\\u2029" %>\u2029</p>');
strictEqual(tmpl(), '<p>\u2028\u2028\u2029\u2029</p>');
});
test('result calls functions and returns primitives', function() {
var obj = {w: '', x: 'x', y: function(){ return this.x; }};
strictEqual(_.result(obj, 'w'), '');
strictEqual(_.result(obj, 'x'), 'x');
strictEqual(_.result(obj, 'y'), 'x');
strictEqual(_.result(obj, 'z'), undefined);
strictEqual(_.result(null, 'x'), null);
});
test('_.templateSettings.variable', function() {
var s = '<%=data.x%>';
var data = {x: 'x'};
strictEqual(_.template(s, data, {variable: 'data'}), 'x');
_.templateSettings.variable = 'data';
strictEqual(_.template(s)(data), 'x');
});
test('#547 - _.templateSettings is unchanged by custom settings.', function() {
ok(!_.templateSettings.variable);
_.template('', {}, {variable: 'x'});
ok(!_.templateSettings.variable);
});
test('#556 - undefined template variables.', function() {
var template = _.template('<%=x%>');
strictEqual(template({x: null}), '');
strictEqual(template({x: undefined}), '');
var templateEscaped = _.template('<%-x%>');
strictEqual(templateEscaped({x: null}), '');
strictEqual(templateEscaped({x: undefined}), '');
var templateWithProperty = _.template('<%=x.foo%>');
strictEqual(templateWithProperty({x: {} }), '');
strictEqual(templateWithProperty({x: {} }), '');
var templateWithPropertyEscaped = _.template('<%-x.foo%>');
strictEqual(templateWithPropertyEscaped({x: {} }), '');
strictEqual(templateWithPropertyEscaped({x: {} }), '');
});
test('interpolate evaluates code only once.', 2, function() {
var count = 0;
var template = _.template('<%= f() %>');
template({f: function(){ ok(!(count++)); }});
var countEscaped = 0;
var templateEscaped = _.template('<%- f() %>');
templateEscaped({f: function(){ ok(!(countEscaped++)); }});
});
});

9404
vendor/underscore/test/vendor/jquery.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,670 @@
// JSLitmus.js
//
// History:
// 2008-10-27: Initial release
// 2008-11-09: Account for iteration loop overhead
// 2008-11-13: Added OS detection
// 2009-02-25: Create tinyURL automatically, shift-click runs tests in reverse
//
// Copyright (c) 2008-2009, Robert Kieffer
// All Rights Reserved
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the
// Software), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
(function() {
// Private methods and state
// Get platform info but don't go crazy trying to recognize everything
// that's out there. This is just for the major platforms and OSes.
var platform = 'unknown platform', ua = navigator.userAgent;
// Detect OS
var oses = ['Windows','iPhone OS','(Intel |PPC )?Mac OS X','Linux'].join('|');
var pOS = new RegExp('((' + oses + ') [^ \);]*)').test(ua) ? RegExp.$1 : null;
if (!pOS) pOS = new RegExp('((' + oses + ')[^ \);]*)').test(ua) ? RegExp.$1 : null;
// Detect browser
var pName = /(Chrome|MSIE|Safari|Opera|Firefox)/.test(ua) ? RegExp.$1 : null;
// Detect version
var vre = new RegExp('(Version|' + pName + ')[ \/]([^ ;]*)');
var pVersion = (pName && vre.test(ua)) ? RegExp.$2 : null;
var platform = (pOS && pName && pVersion) ? pName + ' ' + pVersion + ' on ' + pOS : 'unknown platform';
/**
* A smattering of methods that are needed to implement the JSLitmus testbed.
*/
var jsl = {
/**
* Enhanced version of escape()
*/
escape: function(s) {
s = s.replace(/,/g, '\\,');
s = escape(s);
s = s.replace(/\+/g, '%2b');
s = s.replace(/ /g, '+');
return s;
},
/**
* Get an element by ID.
*/
$: function(id) {
return document.getElementById(id);
},
/**
* Null function
*/
F: function() {},
/**
* Set the status shown in the UI
*/
status: function(msg) {
var el = jsl.$('jsl_status');
if (el) el.innerHTML = msg || '';
},
/**
* Convert a number to an abbreviated string like, "15K" or "10M"
*/
toLabel: function(n) {
if (n == Infinity) {
return 'Infinity';
} else if (n > 1e9) {
n = Math.round(n/1e8);
return n/10 + 'B';
} else if (n > 1e6) {
n = Math.round(n/1e5);
return n/10 + 'M';
} else if (n > 1e3) {
n = Math.round(n/1e2);
return n/10 + 'K';
}
return n;
},
/**
* Copy properties from src to dst
*/
extend: function(dst, src) {
for (var k in src) dst[k] = src[k]; return dst;
},
/**
* Like Array.join(), but for the key-value pairs in an object
*/
join: function(o, delimit1, delimit2) {
if (o.join) return o.join(delimit1); // If it's an array
var pairs = [];
for (var k in o) pairs.push(k + delimit1 + o[k]);
return pairs.join(delimit2);
},
/**
* Array#indexOf isn't supported in IE, so we use this as a cross-browser solution
*/
indexOf: function(arr, o) {
if (arr.indexOf) return arr.indexOf(o);
for (var i = 0; i < this.length; i++) if (arr[i] === o) return i;
return -1;
}
};
/**
* Test manages a single test (created with
* JSLitmus.test())
*
* @private
*/
var Test = function (name, f) {
if (!f) throw new Error('Undefined test function');
if (!(/function[^\(]*\(([^,\)]*)/).test(f.toString())) {
throw new Error('"' + name + '" test: Test is not a valid Function object');
}
this.loopArg = RegExp.$1;
this.name = name;
this.f = f;
};
jsl.extend(Test, /** @lends Test */ {
/** Calibration tests for establishing iteration loop overhead */
CALIBRATIONS: [
new Test('calibrating loop', function(count) {while (count--);}),
new Test('calibrating function', jsl.F)
],
/**
* Run calibration tests. Returns true if calibrations are not yet
* complete (in which case calling code should run the tests yet again).
* onCalibrated - Callback to invoke when calibrations have finished
*/
calibrate: function(onCalibrated) {
for (var i = 0; i < Test.CALIBRATIONS.length; i++) {
var cal = Test.CALIBRATIONS[i];
if (cal.running) return true;
if (!cal.count) {
cal.isCalibration = true;
cal.onStop = onCalibrated;
//cal.MIN_TIME = .1; // Do calibrations quickly
cal.run(2e4);
return true;
}
}
return false;
}
});
jsl.extend(Test.prototype, {/** @lends Test.prototype */
/** Initial number of iterations */
INIT_COUNT: 10,
/** Max iterations allowed (i.e. used to detect bad looping functions) */
MAX_COUNT: 1e9,
/** Minimum time a test should take to get valid results (secs) */
MIN_TIME: .5,
/** Callback invoked when test state changes */
onChange: jsl.F,
/** Callback invoked when test is finished */
onStop: jsl.F,
/**
* Reset test state
*/
reset: function() {
delete this.count;
delete this.time;
delete this.running;
delete this.error;
},
/**
* Run the test (in a timeout). We use a timeout to make sure the browser
* has a chance to finish rendering any UI changes we've made, like
* updating the status message.
*/
run: function(count) {
count = count || this.INIT_COUNT;
jsl.status(this.name + ' x ' + count);
this.running = true;
var me = this;
setTimeout(function() {me._run(count);}, 200);
},
/**
* The nuts and bolts code that actually runs a test
*/
_run: function(count) {
var me = this;
// Make sure calibration tests have run
if (!me.isCalibration && Test.calibrate(function() {me.run(count);})) return;
this.error = null;
try {
var start, f = this.f, now, i = count;
// Start the timer
start = new Date();
// Now for the money shot. If this is a looping function ...
if (this.loopArg) {
// ... let it do the iteration itself
f(count);
} else {
// ... otherwise do the iteration for it
while (i--) f();
}
// Get time test took (in secs)
this.time = Math.max(1,new Date() - start)/1000;
// Store iteration count and per-operation time taken
this.count = count;
this.period = this.time/count;
// Do we need to do another run?
this.running = this.time <= this.MIN_TIME;
// ... if so, compute how many times we should iterate
if (this.running) {
// Bump the count to the nearest power of 2
var x = this.MIN_TIME/this.time;
var pow = Math.pow(2, Math.max(1, Math.ceil(Math.log(x)/Math.log(2))));
count *= pow;
if (count > this.MAX_COUNT) {
throw new Error('Max count exceeded. If this test uses a looping function, make sure the iteration loop is working properly.');
}
}
} catch (e) {
// Exceptions are caught and displayed in the test UI
this.reset();
this.error = e;
}
// Figure out what to do next
if (this.running) {
me.run(count);
} else {
jsl.status('');
me.onStop(me);
}
// Finish up
this.onChange(this);
},
/**
* Get the number of operations per second for this test.
*
* @param normalize if true, iteration loop overhead taken into account
*/
getHz: function(/**Boolean*/ normalize) {
var p = this.period;
// Adjust period based on the calibration test time
if (normalize && !this.isCalibration) {
var cal = Test.CALIBRATIONS[this.loopArg ? 0 : 1];
// If the period is within 20% of the calibration time, then zero the
// it out
p = p < cal.period*1.2 ? 0 : p - cal.period;
}
return Math.round(1/p);
},
/**
* Get a friendly string describing the test
*/
toString: function() {
return this.name + ' - ' + this.time/this.count + ' secs';
}
});
// CSS we need for the UI
var STYLESHEET = '<style> \
#jslitmus {font-family:sans-serif; font-size: 12px;} \
#jslitmus a {text-decoration: none;} \
#jslitmus a:hover {text-decoration: underline;} \
#jsl_status { \
margin-top: 10px; \
font-size: 10px; \
color: #888; \
} \
A IMG {border:none} \
#test_results { \
margin-top: 10px; \
font-size: 12px; \
font-family: sans-serif; \
border-collapse: collapse; \
border-spacing: 0px; \
} \
#test_results th, #test_results td { \
border: solid 1px #ccc; \
vertical-align: top; \
padding: 3px; \
} \
#test_results th { \
vertical-align: bottom; \
background-color: #ccc; \
padding: 1px; \
font-size: 10px; \
} \
#test_results #test_platform { \
color: #444; \
text-align:center; \
} \
#test_results .test_row { \
color: #006; \
cursor: pointer; \
} \
#test_results .test_nonlooping { \
border-left-style: dotted; \
border-left-width: 2px; \
} \
#test_results .test_looping { \
border-left-style: solid; \
border-left-width: 2px; \
} \
#test_results .test_name {white-space: nowrap;} \
#test_results .test_pending { \
} \
#test_results .test_running { \
font-style: italic; \
} \
#test_results .test_done {} \
#test_results .test_done { \
text-align: right; \
font-family: monospace; \
} \
#test_results .test_error {color: #600;} \
#test_results .test_error .error_head {font-weight:bold;} \
#test_results .test_error .error_body {font-size:85%;} \
#test_results .test_row:hover td { \
background-color: #ffc; \
text-decoration: underline; \
} \
#chart { \
margin: 10px 0px; \
width: 250px; \
} \
#chart img { \
border: solid 1px #ccc; \
margin-bottom: 5px; \
} \
#chart #tiny_url { \
height: 40px; \
width: 250px; \
} \
#jslitmus_credit { \
font-size: 10px; \
color: #888; \
margin-top: 8px; \
} \
</style>';
// HTML markup for the UI
var MARKUP = '<div id="jslitmus"> \
<button onclick="JSLitmus.runAll(event)">Run Tests</button> \
<button id="stop_button" disabled="disabled" onclick="JSLitmus.stop()">Stop Tests</button> \
<br \> \
<br \> \
<input type="checkbox" style="vertical-align: middle" id="test_normalize" checked="checked" onchange="JSLitmus.renderAll()""> Normalize results \
<table id="test_results"> \
<colgroup> \
<col /> \
<col width="100" /> \
</colgroup> \
<tr><th id="test_platform" colspan="2">' + platform + '</th></tr> \
<tr><th>Test</th><th>Ops/sec</th></tr> \
<tr id="test_row_template" class="test_row" style="display:none"> \
<td class="test_name"></td> \
<td class="test_result">Ready</td> \
</tr> \
</table> \
<div id="jsl_status"></div> \
<div id="chart" style="display:none"> \
<a id="chart_link" target="_blank"><img id="chart_image"></a> \
TinyURL (for chart): \
<iframe id="tiny_url" frameBorder="0" scrolling="no" src=""></iframe> \
</div> \
<a id="jslitmus_credit" title="JSLitmus home page" href="http://code.google.com/p/jslitmus" target="_blank">Powered by JSLitmus</a> \
</div>';
/**
* The public API for creating and running tests
*/
window.JSLitmus = {
/** The list of all tests that have been registered with JSLitmus.test */
_tests: [],
/** The queue of tests that need to be run */
_queue: [],
/**
* The parsed query parameters the current page URL. This is provided as a
* convenience for test functions - it's not used by JSLitmus proper
*/
params: {},
/**
* Initialize
*/
_init: function() {
// Parse query params into JSLitmus.params[] hash
var match = (location + '').match(/([^?#]*)(#.*)?$/);
if (match) {
var pairs = match[1].split('&');
for (var i = 0; i < pairs.length; i++) {
var pair = pairs[i].split('=');
if (pair.length > 1) {
var key = pair.shift();
var value = pair.length > 1 ? pair.join('=') : pair[0];
this.params[key] = value;
}
}
}
// Write out the stylesheet. We have to do this here because IE
// doesn't honor sheets written after the document has loaded.
document.write(STYLESHEET);
// Setup the rest of the UI once the document is loaded
if (window.addEventListener) {
window.addEventListener('load', this._setup, false);
} else if (document.addEventListener) {
document.addEventListener('load', this._setup, false);
} else if (window.attachEvent) {
window.attachEvent('onload', this._setup);
}
return this;
},
/**
* Set up the UI
*/
_setup: function() {
var el = jsl.$('jslitmus_container');
if (!el) document.body.appendChild(el = document.createElement('div'));
el.innerHTML = MARKUP;
// Render the UI for all our tests
for (var i=0; i < JSLitmus._tests.length; i++)
JSLitmus.renderTest(JSLitmus._tests[i]);
},
/**
* (Re)render all the test results
*/
renderAll: function() {
for (var i = 0; i < JSLitmus._tests.length; i++)
JSLitmus.renderTest(JSLitmus._tests[i]);
JSLitmus.renderChart();
},
/**
* (Re)render the chart graphics
*/
renderChart: function() {
var url = JSLitmus.chartUrl();
jsl.$('chart_link').href = url;
jsl.$('chart_image').src = url;
jsl.$('chart').style.display = '';
// Update the tiny URL
jsl.$('tiny_url').src = 'http://tinyurl.com/api-create.php?url='+escape(url);
},
/**
* (Re)render the results for a specific test
*/
renderTest: function(test) {
// Make a new row if needed
if (!test._row) {
var trow = jsl.$('test_row_template');
if (!trow) return;
test._row = trow.cloneNode(true);
test._row.style.display = '';
test._row.id = '';
test._row.onclick = function() {JSLitmus._queueTest(test);};
test._row.title = 'Run ' + test.name + ' test';
trow.parentNode.appendChild(test._row);
test._row.cells[0].innerHTML = test.name;
}
var cell = test._row.cells[1];
var cns = [test.loopArg ? 'test_looping' : 'test_nonlooping'];
if (test.error) {
cns.push('test_error');
cell.innerHTML =
'<div class="error_head">' + test.error + '</div>' +
'<ul class="error_body"><li>' +
jsl.join(test.error, ': ', '</li><li>') +
'</li></ul>';
} else {
if (test.running) {
cns.push('test_running');
cell.innerHTML = 'running';
} else if (jsl.indexOf(JSLitmus._queue, test) >= 0) {
cns.push('test_pending');
cell.innerHTML = 'pending';
} else if (test.count) {
cns.push('test_done');
var hz = test.getHz(jsl.$('test_normalize').checked);
cell.innerHTML = hz != Infinity ? hz : '&infin;';
} else {
cell.innerHTML = 'ready';
}
}
cell.className = cns.join(' ');
},
/**
* Create a new test
*/
test: function(name, f) {
// Create the Test object
var test = new Test(name, f);
JSLitmus._tests.push(test);
// Re-render if the test state changes
test.onChange = JSLitmus.renderTest;
// Run the next test if this one finished
test.onStop = function(test) {
if (JSLitmus.onTestFinish) JSLitmus.onTestFinish(test);
JSLitmus.currentTest = null;
JSLitmus._nextTest();
};
// Render the new test
this.renderTest(test);
},
/**
* Add all tests to the run queue
*/
runAll: function(e) {
e = e || window.event;
var reverse = e && e.shiftKey, len = JSLitmus._tests.length;
for (var i = 0; i < len; i++) {
JSLitmus._queueTest(JSLitmus._tests[!reverse ? i : (len - i - 1)]);
}
},
/**
* Remove all tests from the run queue. The current test has to finish on
* it's own though
*/
stop: function() {
while (JSLitmus._queue.length) {
var test = JSLitmus._queue.shift();
JSLitmus.renderTest(test);
}
},
/**
* Run the next test in the run queue
*/
_nextTest: function() {
if (!JSLitmus.currentTest) {
var test = JSLitmus._queue.shift();
if (test) {
jsl.$('stop_button').disabled = false;
JSLitmus.currentTest = test;
test.run();
JSLitmus.renderTest(test);
if (JSLitmus.onTestStart) JSLitmus.onTestStart(test);
} else {
jsl.$('stop_button').disabled = true;
JSLitmus.renderChart();
}
}
},
/**
* Add a test to the run queue
*/
_queueTest: function(test) {
if (jsl.indexOf(JSLitmus._queue, test) >= 0) return;
JSLitmus._queue.push(test);
JSLitmus.renderTest(test);
JSLitmus._nextTest();
},
/**
* Generate a Google Chart URL that shows the data for all tests
*/
chartUrl: function() {
var n = JSLitmus._tests.length, markers = [], data = [];
var d, min = 0, max = -1e10;
var normalize = jsl.$('test_normalize').checked;
// Gather test data
for (var i=0; i < JSLitmus._tests.length; i++) {
var test = JSLitmus._tests[i];
if (test.count) {
var hz = test.getHz(normalize);
var v = hz != Infinity ? hz : 0;
data.push(v);
markers.push('t' + jsl.escape(test.name + '(' + jsl.toLabel(hz)+ ')') + ',000000,0,' +
markers.length + ',10');
max = Math.max(v, max);
}
}
if (markers.length <= 0) return null;
// Build chart title
var title = document.getElementsByTagName('title');
title = (title && title.length) ? title[0].innerHTML : null;
var chart_title = [];
if (title) chart_title.push(title);
chart_title.push('Ops/sec (' + platform + ')');
// Build labels
var labels = [jsl.toLabel(min), jsl.toLabel(max)];
var w = 250, bw = 15;
var bs = 5;
var h = markers.length*(bw + bs) + 30 + chart_title.length*20;
var params = {
chtt: escape(chart_title.join('|')),
chts: '000000,10',
cht: 'bhg', // chart type
chd: 't:' + data.join(','), // data set
chds: min + ',' + max, // max/min of data
chxt: 'x', // label axes
chxl: '0:|' + labels.join('|'), // labels
chsp: '0,1',
chm: markers.join('|'), // test names
chbh: [bw, 0, bs].join(','), // bar widths
// chf: 'bg,lg,0,eeeeee,0,eeeeee,.5,ffffff,1', // gradient
chs: w + 'x' + h
};
return 'http://chart.apis.google.com/chart?' + jsl.join(params, '=', '&');
}
};
JSLitmus._init();
})();

236
vendor/underscore/test/vendor/qunit.css vendored Normal file
View File

@@ -0,0 +1,236 @@
/**
* QUnit v1.8.0 - A JavaScript Unit Testing Framework
*
* http://docs.jquery.com/QUnit
*
* Copyright (c) 2012 John Resig, Jörn Zaefferer
* Dual licensed under the MIT (MIT-LICENSE.txt)
* or GPL (GPL-LICENSE.txt) licenses.
*/
/** Font Family and Sizes */
#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
}
#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
#qunit-tests { font-size: smaller; }
/** Resets */
#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult {
margin: 0;
padding: 0;
}
/** Header */
#qunit-header {
padding: 0.5em 0 0.5em 1em;
color: #8699a4;
background-color: #0d3349;
font-size: 1.5em;
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;
}
#qunit-header a {
text-decoration: none;
color: #c2ccd1;
}
#qunit-header a:hover,
#qunit-header a:focus {
color: #fff;
}
#qunit-header label {
display: inline-block;
padding-left: 0.5em;
}
#qunit-banner {
height: 5px;
}
#qunit-testrunner-toolbar {
padding: 0.5em 0 0.5em 2em;
color: #5E740B;
background-color: #eee;
}
#qunit-userAgent {
padding: 0.5em 0 0.5em 2.5em;
background-color: #2b81af;
color: #fff;
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
}
/** Tests: Pass/Fail */
#qunit-tests {
list-style-position: inside;
}
#qunit-tests li {
padding: 0.4em 0.5em 0.4em 2.5em;
border-bottom: 1px solid #fff;
list-style-position: inside;
}
#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
display: none;
}
#qunit-tests li strong {
cursor: pointer;
}
#qunit-tests li a {
padding: 0.5em;
color: #c2ccd1;
text-decoration: none;
}
#qunit-tests li a:hover,
#qunit-tests li a:focus {
color: #000;
}
#qunit-tests ol {
margin-top: 0.5em;
padding: 0.5em;
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;
}
#qunit-tests table {
border-collapse: collapse;
margin-top: .2em;
}
#qunit-tests th {
text-align: right;
vertical-align: top;
padding: 0 .5em 0 0;
}
#qunit-tests td {
vertical-align: top;
}
#qunit-tests pre {
margin: 0;
white-space: pre-wrap;
word-wrap: break-word;
}
#qunit-tests del {
background-color: #e0f2be;
color: #374e0c;
text-decoration: none;
}
#qunit-tests ins {
background-color: #ffcaca;
color: #500;
text-decoration: none;
}
/*** Test Counts */
#qunit-tests b.counts { color: black; }
#qunit-tests b.passed { color: #5E740B; }
#qunit-tests b.failed { color: #710909; }
#qunit-tests li li {
margin: 0.5em;
padding: 0.4em 0.5em 0.4em 0.5em;
background-color: #fff;
border-bottom: none;
list-style-position: inside;
}
/*** Passing Styles */
#qunit-tests li li.pass {
color: #5E740B;
background-color: #fff;
border-left: 26px solid #C6E746;
}
#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
#qunit-tests .pass .test-name { color: #366097; }
#qunit-tests .pass .test-actual,
#qunit-tests .pass .test-expected { color: #999999; }
#qunit-banner.qunit-pass { background-color: #C6E746; }
/*** Failing Styles */
#qunit-tests li li.fail {
color: #710909;
background-color: #fff;
border-left: 26px 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;
}
#qunit-tests .fail { color: #000000; background-color: #EE5757; }
#qunit-tests .fail .test-name,
#qunit-tests .fail .module-name { color: #000000; }
#qunit-tests .fail .test-actual { color: #EE5757; }
#qunit-tests .fail .test-expected { color: green; }
#qunit-banner.qunit-fail { background-color: #EE5757; }
/** Result */
#qunit-testresult {
padding: 0.5em 0.5em 0.5em 2.5em;
color: #2b81af;
background-color: #D2E0E6;
border-bottom: 1px solid white;
}
#qunit-testresult .module-name {
font-weight: bold;
}
/** Fixture */
#qunit-fixture {
position: absolute;
top: -10000px;
left: -10000px;
width: 1000px;
height: 1000px;
}

1863
vendor/underscore/test/vendor/qunit.js vendored Normal file

File diff suppressed because it is too large Load Diff

1082
vendor/underscore/underscore.js vendored Normal file

File diff suppressed because it is too large Load Diff