Compare commits

..

290 Commits
0.4.2 ... 0.8.0

Author SHA1 Message Date
John-David Dalton
5167bbf59e Fix _.lateBind doc typo.
Former-commit-id: 3284bee699837ab380a3e8fd2853f8bdcf0684b6
2012-10-01 23:33:35 -07:00
John-David Dalton
8bcdfa2793 Add identitydependencies to build dependency map.
Former-commit-id: e466c8547888755b9e3d645d555298b21b5a6849
2012-10-01 08:31:23 -07:00
John-David Dalton
2f9cb6a91e Add more to .jamignore.
Former-commit-id: aaa51092bb28995478e4cf8cf1b5ff249880a99c
2012-09-30 23:29:43 -07:00
John-David Dalton
662be14535 Tweak whitespace in README.md and update QUnit-CLIB.
Former-commit-id: b8fed819580bf7db926b8a4cfb794aa7666c5f58
2012-09-30 23:06:40 -07:00
John-David Dalton
d6d065cd61 Cleanup settings=.. build option usage.
Former-commit-id: 33506d9cfc2101cba8d160169c5d27861f8c7064
2012-09-30 22:52:34 -07:00
John-David Dalton
ac7f045708 Correct bullets in README.md.
Former-commit-id: fb75befff853071b7ad5dde3f3a575c707930cc4
2012-09-30 22:47:29 -07:00
John-David Dalton
ffe02ad7bf Reduce the size of lodash.underscore.min.js.
Former-commit-id: fd7d512e104c6325a38a7d0e09015235ca69b1da
2012-09-30 22:36:57 -07:00
John-David Dalton
db1a87d10c Bump to v0.8.0.
Former-commit-id: b5bed986eed052cab3f927a928d92d58044a4798
2012-09-30 21:49:12 -07:00
John-David Dalton
4bae41ffd8 Use a better strict mode detect for tests.js so older versions of Node.js pass.
Former-commit-id: fd9f6ea35c71a8183ce0dcf4a6ec6e6afe13c39e
2012-09-30 21:45:50 -07:00
John-David Dalton
be36fb979f Make .jamignore ignore test-build.js and test/template/.
Former-commit-id: cc6e5e744e81cfb92ae46a4991a39b5c925a0727
2012-09-30 21:39:00 -07:00
John-David Dalton
0f66d763e0 No longer create/use the dist/ folder during the build process.
Former-commit-id: d8d298d5ce21032542d21c4d4fbc7e0112f6ad65
2012-09-30 15:05:17 -07:00
John-David Dalton
06f4743f51 Add lodash.underscore.min.js.
Former-commit-id: 15592a33a6f6979a1d60632a6ade3c341f13d0e7
2012-09-30 02:49:48 -07:00
John-David Dalton
9cc11b8774 Cleanup build files.
Former-commit-id: d19939a34688a8a63979f84eb1a5c5f9c926897b
2012-09-30 01:55:26 -07:00
John-David Dalton
de821e55ae Add template and settings build options unit tests and tweak _.template docs.
Former-commit-id: c814799c82e5a1dde60e5eda4dda5cb192d437f9
2012-09-30 01:03:43 -07:00
John-David Dalton
463b5c6e49 Tweak build option internals and add test template files.
Former-commit-id: ed5ec6ed7886a066c8c727de19dc0fe6548a276d
2012-09-30 00:57:09 -07:00
John-David Dalton
65ab5fdb34 Ensured failed tests trigger travis fails.
Former-commit-id: d513b5b1bae77ab1f4aa9e98ec3622e143048def
2012-09-29 19:54:39 -07:00
John-David Dalton
d2f7a035b3 Add test-build.js to npm test.
Former-commit-id: c915ba8401c1c1b11aa69d155cebe2a0a81eb2d1
2012-09-29 17:20:51 -07:00
John-David Dalton
bc0b924283 Reorganize tests.
Former-commit-id: 5293cdc1206af20824e8aec86b892afd4badf639
2012-09-29 16:42:13 -07:00
John-David Dalton
56ad6af5b2 Add travis integration.
Former-commit-id: 293478e5175ff94dab92bc340034d8d83e3e4773
2012-09-29 16:05:17 -07:00
John-David Dalton
c50bb3a5a9 Remove component.json and add index.js.
Former-commit-id: 16718a8ee6c4b6c834fe96feb58404311b82e3a0
2012-09-29 15:31:47 -07:00
John-David Dalton
d993a62263 Use invert to assign htmlUnescapes.
Former-commit-id: 20f9f29d622643e2f59bbc4a57fd2456c09ef49e
2012-09-29 14:36:23 -07:00
John-David Dalton
82bc52b909 Reduce underscore build size.
Former-commit-id: 207d4ab49063483245dc951d4646413d6d4a1903
2012-09-29 12:21:34 -07:00
John-David Dalton
f31598f916 Update README.md and rebuild minified file.
Former-commit-id: 0b37eebda0d1a82d0bb62cf2ba6e5b190e176547
2012-09-29 03:48:15 -07:00
John-David Dalton
6a9efd8ac6 Ensure build tests pass.
Former-commit-id: 9b91f0d884fe96dce1df34a6c0b659619276b83e
2012-09-29 03:41:00 -07:00
John-David Dalton
a9dddb6066 Update vendors.
Former-commit-id: 31e8de8842ed9ea020f54ca06cdb87b1478e3b08
2012-09-29 03:14:03 -07:00
John-David Dalton
40cf5c99ef Update reduce repeated code.
Former-commit-id: 3412cde47a136dab5c241c67d1c29f2e676c38d1
2012-09-29 03:13:45 -07:00
John-David Dalton
42f58cbbb3 Fix perf.js.
Former-commit-id: 4eef40ddbcb851aca3a87813a17dc329f9ecb071
2012-09-29 00:17:27 -07:00
John-David Dalton
fd9c780015 Reduce file size further.
Former-commit-id: 009540db12d86f0fb79ccd493b61c4fa2cdd9b1f
2012-09-28 07:58:16 -07:00
John-David Dalton
383b92b207 Update build.js, test-build.js and rebuild docs.
Former-commit-id: 385c6b4cb127ad8089622416758021556e413a0a
2012-09-28 02:14:59 -07:00
John-David Dalton
7036ed5e2f Remove falsey checks and reduce file size.
Former-commit-id: 5263a0beaffe2a987eb65fd3631ea4aff8d9f000
2012-09-28 00:46:57 -07:00
John-David Dalton
30666aa111 Update vendor.
Former-commit-id: 9f433cc31e3c70dddba332346c7d053539f54ab5
2012-09-27 20:53:49 -07:00
John-David Dalton
9614d68baa Cleanup test-build.js.
Former-commit-id: d6588b8cc7cebe0ce44392269cdda1ebd851f1ae
2012-09-26 23:08:19 -07:00
John-David Dalton
c25fb4c743 Merge branch 'master' of github.com:bestiejs/lodash
Former-commit-id: a8dbad27cb89e403dbdafcc7cc69a397ae1e5bbd
2012-09-26 22:31:18 -07:00
John-David Dalton
09d5222b1f Allow _.sortedIndex to accept a string value.
Former-commit-id: 7ac17a6bb620ad16ecce17718a8110d422d49118
2012-09-26 22:30:15 -07:00
John-David Dalton
426ca78bf7 Update vendors.
Former-commit-id: 48e14b4b41c9b26382b09294127e552a794e49be
2012-09-26 20:42:52 -07:00
Kit Cambridge
a77a0945fe Add tests for the -d and -m options.
Former-commit-id: def5bcf323aaed96c037fea3e15c4a5c8c72a977
2012-09-26 09:26:40 -06:00
Kit Cambridge
5311a0f903 Allow the -d option to be used independently.
Former-commit-id: cb838b158edb8360d6d7c98ee18f2a7fbb4e9bb4
2012-09-26 08:59:33 -06:00
John-David Dalton
d421656be8 Cleanup build exports options.
Former-commit-id: 75a5f57c0c9f71067cf6d55006f59fa0296a82e2
2012-09-25 23:06:30 -07:00
John-David Dalton
04459eaa50 Remove internal skipArgsCheck from _.isPlainObject and add unit tests.
Former-commit-id: 213c1e95f61368eb8912850248a97f44664384d8
2012-09-25 09:05:09 -07:00
John-David Dalton
3beda8eea0 Add _ references to precompiled templates.
Former-commit-id: 4a6f38ec03790d647de4923262bba8d73378ce14
2012-09-25 02:10:35 -07:00
Mathias Bynens
5e894be06b Fix typo
Former-commit-id: b9fa32da8453a1a928b16bc712a9f2ec53722341
2012-09-24 15:22:55 +03:00
John-David Dalton
bc8f93b596 Remove internal params from built docs.
Former-commit-id: 8b0be9d888ef88dae74554101463f4fa8d268dbc
2012-09-24 01:15:46 -07:00
John-David Dalton
2fb93bac99 Use a different private indicator for _.merge now that isPlainObject is exposed.
Former-commit-id: 5c75de935eb3e5e9e035bd6520398c7fbd811ea6
2012-09-24 01:06:58 -07:00
John-David Dalton
d0c94c1aef Cleanup template build option.
Former-commit-id: 38b94dad822dd9030a6a71f66e65ff7aec0726cc
2012-09-24 00:03:41 -07:00
John-David Dalton
77242bfb95 Expose _.isPlainObject.
Former-commit-id: 884bc87df7773ef3cb5e52725d5a37f07812385a
2012-09-23 21:30:14 -07:00
John-David Dalton
5d2d86bffc Initial lodash template=… build support.
Former-commit-id: 9d13021463380556a997cb53f5ae89eb22a7b98b
2012-09-23 21:29:20 -07:00
John-David Dalton
8492c13e72 Remove DRY modifications for smaller gzip size.
Former-commit-id: 69391d792d76c6592e7d48aec44165a8db388f81
2012-09-22 20:50:59 -07:00
John-David Dalton
e4eb5dadda Modify "underscore" build methods in prep for Underscore.next.
Former-commit-id: 22503a046256915aa6667e32f6efb992c6ddbce9
2012-09-22 20:03:43 -07:00
John-David Dalton
8532dc4b75 Refactor reduceRight and modify a _.difference benchmark.
Former-commit-id: b70272ac5316fe1bee52b9611a1a5ea4d761dd3c
2012-09-22 16:11:23 -07:00
John-David Dalton
1ca26ce676 Ensure methods accepting a thisArg argument allow null values.
Former-commit-id: 368b943687291f0d7ed02304284ac076ef86e02b
2012-09-22 15:39:37 -07:00
John-David Dalton
d8e3e823a7 Removed Underscore's fixed issues from README.md and rebuild docs.
Former-commit-id: 08db54926a791ab32b7e003143ae0f70a251068e
2012-09-20 21:13:36 -07:00
John-David Dalton
473dd7660b Reduce _.reduceRight and update vendors.
Former-commit-id: f7250ccb4b8f15052c1f1420947c2ac68963a92c
2012-09-20 21:06:32 -07:00
John-David Dalton
5afeed56ef Cleanup _.difference and _.union.
Former-commit-id: 1fcaab3989caaaacd9fe73de071d8f360f52b715
2012-09-19 22:15:15 -07:00
John-David Dalton
b91b04f652 Update vendors and add _.reduce, _.reduceRight, and _.where benchmarks.
Former-commit-id: c1b4bc7f8aaf08c429ae918f5d528401f1a66255
2012-09-19 21:40:39 -07:00
John-David Dalton
25de44a23d Cleanup _.template.
Former-commit-id: dc3fa2d02a9a4a2d4034136d2ce7f03d0b67224a
2012-09-19 01:06:45 -07:00
John-David Dalton
00cfb95971 Update vendors.
Former-commit-id: fddaef2be532e30f197f8bdff70dc6ec9bfa3cfc
2012-09-18 23:42:52 -07:00
John-David Dalton
f4ba0e1191 Simplify _.template and remove superfluous caching.
Former-commit-id: f9f18a63a77376471e69c95d5046dfe0146b9887
2012-09-18 23:42:26 -07:00
John-David Dalton
6499643769 Make _.random return 0 or 1 when no arguments are passed.
Former-commit-id: 7d20f5240d534f0091757f613c664b08e0d45759
2012-09-18 20:29:36 -07:00
John-David Dalton
f85287a444 Add "category" options unit tests to test-build.js.
Former-commit-id: 0499317babeb422e88700edd0f1e46c1fa6196fd
2012-09-18 01:22:10 -07:00
John-David Dalton
483bc9ad87 Add unit test to confirm correctly parsing delimiters.
Former-commit-id: 71aa7a240d2becb8082e36f9aa4874eb0aed09d3
2012-09-17 23:49:03 -07:00
John-David Dalton
93b89a93c1 Make _.times return an array of the results of each callback execution. [Closes #73]
Former-commit-id: 808af6b9eb07bf1fada8221ca659558d82e6eb57
2012-09-17 21:48:26 -07:00
John-David Dalton
10064c046c Allow build "plus", "minus", and "include" to accept case-sensitive category names.
Former-commit-id: f74d4cda73195854b3471ddce0afaab099dcfe77
2012-09-16 18:11:53 -07:00
John-David Dalton
a5aecb9d0e Add minus and plus commands to build.js.
Former-commit-id: fcdd2e829de5a6a29ebc342e694b7986d9a5eade
2012-09-16 13:22:26 -07:00
John-David Dalton
de1e889f1d Make lodash underscore build smaller and rebuild docs.
Former-commit-id: 19405bc4e490588d7a55379974d2d7d81d1cf5f7
2012-09-15 19:24:26 -07:00
John-David Dalton
16f89b40c3 Remove issues resolved by Underscore from README.md.
Former-commit-id: 27510827a1e60dd4cf81f5306991e3f63e8cedb5
2012-09-15 08:47:22 -07:00
John-David Dalton
0e387d2cda Remove custom toArray checks in _.toArray and simplify array-like object checks in "Collections" methods.
Former-commit-id: 6b7678de16907f44c1b079a22a6a2e091a638d4b
2012-09-15 08:46:49 -07:00
John-David Dalton
837c15e4f9 Update vendors.
Former-commit-id: 1e93a793891e5f4e2c0d3927f3f38b25bd182263
2012-09-15 08:21:44 -07:00
John-David Dalton
ae7e353169 DRY more code in the "underscore" build.
Former-commit-id: c1dbc3daccb1efba371c38f20b9a4ff96a7171a4
2012-09-14 01:09:02 -07:00
John-David Dalton
6c1bbd2344 Tweak iteratorTemplate formatting and remove more code from "underscore" builds.
Former-commit-id: cbaa8e41a9a60b5828d2c0da7188d483702c55e1
2012-09-13 23:51:52 -07:00
John-David Dalton
27247e8e56 Remove freeExports var when not used via exports build option.
Former-commit-id: 66950af3a18b35412fbe1092e19c5d7ef0c9a029
2012-09-13 19:28:56 -07:00
John-David Dalton
08249d78ea Add RequireJS "shim" test and a build test to ensure the AMD snippet is maintained for r.js.
Former-commit-id: 708c07877cfca0022d6d56c16c36d8bae79e4796
2012-09-13 19:11:48 -07:00
John-David Dalton
827091e522 Remove "lazy" bind from README.md until next version bump.
Former-commit-id: f132182f32670aa7df7ac418f58db0a1fb0f8547
2012-09-13 02:10:23 -07:00
John-David Dalton
13d901bf8d Make the IIFE Closure Compiler regexp in post process more restrictive to work better with the "iife" build option.
Former-commit-id: 999602451e50850eb82680b9c377d97605be8af4
2012-09-13 01:40:28 -07:00
John-David Dalton
e69bdabc99 Rename makeBound to createBound for naming consistency.
Former-commit-id: ec6845badba06231af0b1341b1f1efedb8adbc88
2012-09-13 00:46:47 -07:00
John-David Dalton
e8d19265e4 Cleanup component.json.
Former-commit-id: 0761bb592e5c643223848e2cd7fca0efc5b9c725
2012-09-13 00:10:16 -07:00
John-David Dalton
49e3a49dc5 Update docs, minified, build, and vendors.
Former-commit-id: 8b1425b8c4a5238a42185dfa974bb3b2468eebea
2012-09-13 00:08:52 -07:00
John-David Dalton
82a7c01898 Simplify "underscore" build of _.clone.
Former-commit-id: 32975bb5966f1ded4f007eb76dcf2d4677478e7d
2012-09-13 00:04:53 -07:00
John-David Dalton
c0d7dbf639 Reduce code around _.bind and _.partial, and add _.lateBind.
Former-commit-id: 4c962d066ecfa54882cee2216a7310ab34b3b5a3
2012-09-13 00:04:00 -07:00
John-David Dalton
569caa0bf2 Remove custom isEqual checks from _.isEqual and custom clone checks from _.clone and simply _.clone, _.isEqual, and _.merge.
Former-commit-id: 45e90ab1494e46e281265f660c87e27f59581308
2012-09-12 22:00:23 -07:00
John-David Dalton
f88ea1ee7d Re-order test-build unit tests and make the iife test a bit more thorough.
Former-commit-id: f798639cfa58241b052d16b9ca6fbf4537482349
2012-09-12 08:40:01 -07:00
John-David Dalton
d5a8fa0b97 Update underscore unit tests.
Former-commit-id: ae00a864c7cb3bff9970289917df681ad5e295d9
2012-09-12 08:39:18 -07:00
John-David Dalton
3f8f96edea Add "mobile" build unit test.
Former-commit-id: f067b42618abe6a9f747fea000522de6a9117b3c
2012-09-11 22:30:23 -07:00
John-David Dalton
04fb4aff28 Update underscore.
Former-commit-id: 7041883ef258e3dc80d3c3751a5e4beecf0b4767
2012-09-11 20:15:13 -07:00
John-David Dalton
83c6fb089e Ensure mobile build has properties correctly minified.
Former-commit-id: 61da738afbcefc7ecd277190147041d884002af1
2012-09-11 09:15:32 -07:00
John-David Dalton
9d8d17b964 Update cdn link in README.md.
Former-commit-id: 5da17daf1a6d1d5c9f6e9ba7875f45bd2d763cda
2012-09-11 07:41:47 -07:00
John-David Dalton
39d4842ff5 Bump to v0.7.0.
Former-commit-id: 7c51a310c7c62bfe0ba9a2cdea4e074e633cee70
2012-09-11 02:53:12 -07:00
John-David Dalton
cad8473986 Re-add overwritten _.isEqual unit test.
Former-commit-id: 52d4e3bc02a6fd0ac30177c9da82dec60ee6eb81
2012-09-10 23:41:20 -07:00
John-David Dalton
5f085ccb52 Fix typo in _.isEqual.
Former-commit-id: 9d5065953c51d12f1308dd8c0c142b6505efe765
2012-09-10 23:04:42 -07:00
John-David Dalton
b406246689 Add "iife=.." command to build.js.
Former-commit-id: 85d8c7ea550403663a878f2713ce93ae8c2dbc6a
2012-09-10 22:52:28 -07:00
John-David Dalton
cbe46afdff Minor lodash.js cleanup.
Former-commit-id: 544f2a2690b48b52870b3ab62509221a82ed4173
2012-09-10 21:04:04 -07:00
John-David Dalton
a59d6dc3c7 Add minify.js Underscore unit test.
Former-commit-id: 1db7b19709ef953dd1996a082e73a2ba542f29f7
2012-09-10 20:35:27 -07:00
John-David Dalton
2afb2dd5fd Reduce temporary objects created in _.merge, _.clone, and _.isEqual.
Former-commit-id: e6696642505f39eefdf59075ff8a993ab033465a
2012-09-10 20:12:42 -07:00
John-David Dalton
4fc3c969d3 Add component.json for Bower.
Former-commit-id: d681c2300b4e79fb168793aed34b82c3021647d1
2012-09-10 00:48:12 -07:00
John-David Dalton
1796ce324b Cleanup build.js
Former-commit-id: 6a4502883d7431f5dcedbf7f7d3bfb871ce1c0f4
2012-09-09 23:30:38 -07:00
John-David Dalton
5e04c7f827 Add "output" and "stdout" build option unit tests.
Former-commit-id: 2adcdbff4cd1ef6319e33c69fd5ed3b07b205cfe
2012-09-09 20:33:08 -07:00
John-David Dalton
20fcede440 Add "-o" and "--output" build options.
Former-commit-id: 154c0a6b749ff2439c024602fb9ec781e293f511
2012-09-09 17:26:30 -07:00
John-David Dalton
012c1833f2 Added "-c" and "--stdout" options to build.js.
Former-commit-id: 388c529ca1836ee7cd65517d2e9f8533e480b8cd
2012-09-09 16:33:57 -07:00
John-David Dalton
32e8e03256 Add build "exports" unit tests.
Former-commit-id: afe0fe59933d272bfa597be835011b3c81b28dda
2012-09-09 16:00:11 -07:00
John-David Dalton
dca653cb92 Update minified build and docs.
Former-commit-id: a7719898e67aab04d37e775f4794b0be7a4a2e24
2012-09-09 14:13:18 -07:00
John-David Dalton
0805eca979 Remove Object.keys optimization from the "legacy" build iteratorTemplate.
Former-commit-id: 5a63b627c4982ca43a46a830722513cab2c7b633
2012-09-09 14:11:46 -07:00
John-David Dalton
17935a78ff Make previous _.isEqual fix pass Underscore unit tests.
Former-commit-id: 2b3563bb628b307ad2e4a2ef00ed5afec2f59506
2012-09-09 14:11:07 -07:00
John-David Dalton
e16918ee32 Add "exports" option to build.js.
Former-commit-id: cc1572dbe9d1367f806a44597cbcec8508f51ad6
2012-09-09 13:12:44 -07:00
John-David Dalton
78471b4595 Cleanup test files.
Former-commit-id: 3b138bc74c2f4c2c6d374893c0f90a8422a7248b
2012-09-09 11:55:24 -07:00
John-David Dalton
c30bcdd515 Ensure _.isEqual matches values with circular references correctly.
Former-commit-id: 07968aeb430f56c32aab22dfda919706da840680
2012-09-09 11:54:32 -07:00
John-David Dalton
ac78c5f4e5 Make minify.js support passing minify an array of command-line arguments.
Former-commit-id: fd67d3d6dd8b19c88c74529a33fd50b2fbd0db01
2012-09-08 23:43:36 -07:00
John-David Dalton
57a990ce25 Add "underscore" build test.
Former-commit-id: 8050e285fae94c96e7db1c8847ace45ae5cade33
2012-09-08 19:30:58 -07:00
John-David Dalton
24825b42a2 Cleanup and add test-build to run-test.sh.
Former-commit-id: 29d198ca03dbd23b864a96ea48348fb22728056a
2012-09-08 14:20:46 -07:00
John-David Dalton
4f7323f7fc Add test/test-build.js.
Former-commit-id: b0c28b814dec71095a927469cbbda766fd9fc701
2012-09-08 14:03:21 -07:00
John-David Dalton
a228be85e2 Cleanup compareAscending.
Former-commit-id: c11be9f8211242a8d25a2cd06e20efefa685c3ee
2012-09-07 23:56:28 -07:00
John-David Dalton
fa565bdbdf Hold off on the version bump until test-build.js is finished.
Former-commit-id: a627062b1133cdb5a06a3fd960bbeaddfd0f9a54
2012-09-07 22:00:27 -07:00
John-David Dalton
2dc53223e5 Ensure _.template works with "interpolate" delimiters containing ternary operators. [closes #68]
Former-commit-id: 287df2ef5802ea6db743da5f211e480d6b0f85c9
2012-09-07 21:17:00 -07:00
John-David Dalton
958ac72805 Ensure the internal stack argument of _.merge doesn't pave the 4th argument passed to it. [closes #69]
Former-commit-id: b33e1cb7795294b9481e2c9c6888d0f37419208d
2012-09-07 21:09:21 -07:00
John-David Dalton
f7297b84e7 Tweak text of build.js help message.
Former-commit-id: 755872c9ae5670cc3a33aa158be4478eafacc574
2012-09-06 23:27:00 -07:00
John-David Dalton
9a7d9e7bb8 Ensure _.sortBy is stable for undefined values.
Former-commit-id: bf250150d27de050ea7a6fa376aacdc8d1ba7716
2012-09-06 22:35:20 -07:00
John-David Dalton
fa9df75cf7 Rework build.js to work as a module and add a "silent" mode to minify.js.
Former-commit-id: cf62532b957d37da77a2d64aa64d2d388e6382ae
2012-09-06 22:19:43 -07:00
John-David Dalton
e3ec76418b Update Underscore/Backbone vendors.
Former-commit-id: beb38126acaebf1045c2676aeda037e35f0b99c8
2012-09-06 20:56:02 -07:00
John-David Dalton
102d6d8c84 Capture the result of the last func call in _.throttle and _.debounce.
Former-commit-id: 2e783fad2e86824bf098bdb24ca6911317576f32
2012-09-06 20:49:06 -07:00
John-David Dalton
a742b5f3e2 Rewrite build.js to be used as a module.
Former-commit-id: bf6425925e511a327b5297f9b17620a97ff53b67
2012-09-06 20:36:24 -07:00
John-David Dalton
a2a3bb291f Let build.js handle the "use strict" directive and not minify.js.
Former-commit-id: 741eb692b158e22aa688d6dac1b63fc2787cc426
2012-09-06 00:41:18 -07:00
John-David Dalton
b7c0ac7d67 Tweak test/test-ui.js for QUnit v1.10.0.
Former-commit-id: 6481cce305fb4d69bba22ba2186a30ee13bb2282
2012-09-06 00:40:26 -07:00
John-David Dalton
13b1fc6b44 Remove an unneeded _.object unit test.
Former-commit-id: 2334bda13fbd9bd683b3f650ff47f1a676139319
2012-09-06 00:38:22 -07:00
John-David Dalton
3939fcf6e7 Allow unit tests to run when testing custom builds without noConflict.
Former-commit-id: 2aee7eb872144583df1f22743f5d3f7102d14eae
2012-09-05 07:12:35 -07:00
John-David Dalton
13abbb81af Bump to v0.7.0.
Former-commit-id: 4ab5bfe3bba14182ffe24c05792b3b8f194afa0c
2012-09-05 01:12:57 -07:00
John-David Dalton
019f0153c8 Update alias style in docs.
Former-commit-id: d7b1eb9999f535c365ce3ea6251a359c7d901769
2012-09-04 23:47:27 -07:00
John-David Dalton
8abc2925e0 Fix invalid doc entry in doc/README.md.
Former-commit-id: 64721e5c3417a25b2f34f1f380b0cecc6561fa35
2012-09-04 22:29:06 -07:00
John-David Dalton
996c9a032a Update docs to include method aliases.
Former-commit-id: b93b13a42381ba28b84a3e279d5157673b20fdce
2012-09-04 21:40:29 -07:00
John-David Dalton
22d3794d22 Update vendors.
Former-commit-id: ad3284b1e77cfb0b17af99e0ddaf00618e4485b7
2012-09-04 21:37:01 -07:00
John-David Dalton
ba948a38e9 Ensure to escape exports property for Closure Compiler.
Former-commit-id: 9b6bd1201e74d9e85fbc340bcabce40039239a59
2012-09-04 15:25:47 -07:00
John-David Dalton
e8a522c4df Update README to reflect Underscore patches.
Former-commit-id: b0222d92b90e190c7c322e409ec877ca473f3594
2012-09-04 11:27:23 -07:00
John-David Dalton
c60f3da32e Reduce size of "mobile" and "underscore" builds.
Former-commit-id: 062dc03e3d3dd7a8e1ceb6a8b4ea155394a9b899
2012-09-03 23:20:18 -07:00
John-David Dalton
0c92d3cbb2 Fix how post-compile.js unescapes properties to avoid extra work in pre-compile.js.
Former-commit-id: f604f706af358288877763681243f4816d5cbe9e
2012-09-03 21:40:23 -07:00
John-David Dalton
e4fc8dd6fe Remove older Opera fixes from the "underscore" build.
Former-commit-id: a012ed6957b4d964b5f2dc1a636d7f5f19fbf307
2012-09-03 16:30:55 -07:00
John-David Dalton
1a849e2de0 Update docs and minified build.
Former-commit-id: 62f3293c13d5fa08f857ca455506b4762aa65416
2012-09-03 16:07:46 -07:00
John-David Dalton
ffdd79f86b Adjust how _.template handles compiled syntax errors for compatibility with Underscore.
Former-commit-id: ba84c5b468938a1be1a1fd0afd31cb83f563e1ca
2012-09-03 16:05:47 -07:00
John-David Dalton
e87e46b1b6 Remove deep clone from "underscore" build and fix how invalidArgs are detected in build.js.
Former-commit-id: 5038d1541fa7d0c062e5a48004a60fb9140778d7
2012-09-03 15:42:02 -07:00
John-David Dalton
3ca81a4ff7 Fix Underscore detection in post-compile.js.
Former-commit-id: ad3c5cd28bc9ac0f6c9e5801c3849acd4305c528
2012-09-03 15:40:47 -07:00
John-David Dalton
5477d3c292 Cleanup iteratorTemplate and isPlainObject.
Former-commit-id: a96b8716cfd0efbc46daf2307fae8f1ee5969862
2012-09-03 15:36:18 -07:00
John-David Dalton
08300183b3 Cleanup build.js help message.
Former-commit-id: 36edcf06e78580835f33872f0c72a233604a4adc
2012-09-03 12:58:46 -07:00
John-David Dalton
095c77f22c Inform users of invalid arguments passed to build.js.
Former-commit-id: 1b15dd2242387c7037678a3348931f5430612a8b
2012-09-03 10:50:25 -07:00
John-David Dalton
09926e63a3 Add 4th screencast link to README.md.
Former-commit-id: 07d890b65e7e4ed600173634f04a9783d18bc701
2012-09-02 16:15:56 -07:00
John-David Dalton
87d70f29a1 Make pre-compile.js and post-compile.js support underscore.js.
Former-commit-id: 76d040f630faf03bd5a8eb168259814f5662ba50
2012-09-02 16:11:29 -07:00
John-David Dalton
3a7661b111 Make _chain and _wrapped double underscored to further avoid conflicts.
Former-commit-id: 27f545d99cc383be05509ac7382e42fc727e0215
2012-09-02 12:57:53 -07:00
John-David Dalton
ec976953cd Simplify wrapper inference in _.isEqual.
Former-commit-id: b4fda683ebee4c3f7dddd0cb87201306c08fa7d5
2012-09-02 12:04:35 -07:00
John-David Dalton
1837562b50 Cleanup README.md, update Backbone, rebuild docs and minified build.
Former-commit-id: 8f54e0a4b76cf1a42c11ca37f3e8672a75178d30
2012-09-02 02:44:26 -07:00
John-David Dalton
f3bec4fc37 Ensure optimized isPlainObject works with objects from other documents.
Former-commit-id: 2f782b3dfc19e7ea3274132c31cd408ee2387021
2012-09-02 02:35:02 -07:00
John-David Dalton
c2117ef4fd Add _.result to "backbone" build.
Former-commit-id: c04ddcdbfb229440b19b268d51887bf31ae11296
2012-09-01 14:58:35 -07:00
John-David Dalton
910804ecd1 Avoid arguments object in _.random.
Former-commit-id: 24e54869ae03c0251f419c922f59f53f01b8fa35
2012-08-31 18:03:10 -07:00
John-David Dalton
ce5ae1dfdd Simplify _.size.
Former-commit-id: a7d3338cbd5784ec6b9b6a25e18acd9507f4b21c
2012-08-31 17:58:34 -07:00
John-David Dalton
2c31411ffb Optimize _.pairs.
Former-commit-id: 1de87609a8635fb8d48bc558fbdabc545da53b4b
2012-08-31 15:45:27 -07:00
John-David Dalton
84d69fa2a1 Update resolved issue count in README.md.
Former-commit-id: 41a22ec12d02a1cff5b172022c8260b80c63fe9b
2012-08-31 13:57:23 -07:00
John-David Dalton
2aea296d19 Update minified build and docs.
Former-commit-id: aed499a9ffee5fd7c1f29dad13a8c4e756b431d8
2012-08-31 13:53:04 -07:00
John-David Dalton
2b0bffc362 Ensure template delimiters are tokenized correctly. [closes #64]
Former-commit-id: 814f3f8a840a70a9b455e5f91da0e21174f08787
2012-08-31 13:50:10 -07:00
John-David Dalton
83356142c1 Update README.md with API changes.
Former-commit-id: 4571a2331d433af28ae33813780f8abc9477437e
2012-08-31 13:36:16 -07:00
John-David Dalton
24dcc6947c Update build.js and pre-compile.js for _.invert, _.pairs, and _.random.
Former-commit-id: 0dc281f6e1a07f0a4121f71c37e15a7ca0e18960
2012-08-31 13:35:40 -07:00
John-David Dalton
a7e3136a0b Add _.random.
Former-commit-id: cf720b9187b0b54b43773a9f5f02fb475d786bfa
2012-08-31 13:33:44 -07:00
John-David Dalton
71639cfea7 Cleanup _.times documentation.
Former-commit-id: 59ce4b689bb280650d28944664abd6a38f1c43a0
2012-08-31 13:05:12 -07:00
John-David Dalton
8c2d39fb82 Add _.invert and _.pairs.
Former-commit-id: b265ed3f148e5e951b8d061107bb376e0b2e651e
2012-08-31 13:02:55 -07:00
John-David Dalton
e060d29337 Rename _.drop and _.zipObject unit tests.
Former-commit-id: d68abda2400244b7801eac9ad90de88b2f99a1f4
2012-08-31 12:59:18 -07:00
John-David Dalton
79e9156d2f Make _.drop an alias of _.rest and rename _.zipObject to _.object.
Former-commit-id: 08cb9ec2d5009b9a9f959b2341f8b78f6bbd37a0
2012-08-31 12:57:52 -07:00
John-David Dalton
9100db55b0 Update vendors.
Former-commit-id: 88e9746e94e8ec899227b1a925bea4ab4d373fb0
2012-08-31 12:47:55 -07:00
John-David Dalton
141c10f6fe Ensure noArgsClass references aren't removed for "underscore" builds.
Former-commit-id: 697e2d29d5fef32b0b10d775eee7a12d193ff1a9
2012-08-30 00:58:54 -07:00
John-David Dalton
4613ab9dc3 Update cdn links and tested browsers in README.md.
Former-commit-id: db952c5d72fa9e4d6ce3280574b9e4e26da8c928
2012-08-29 18:03:03 -07:00
John-David Dalton
061ad41b51 Cleanup link hashes in generated docs and update vendors.
Former-commit-id: 278366a7dacea91d965835b24ae7ec046f455110
2012-08-29 08:28:37 -07:00
John-David Dalton
3a0007cd18 Remove pseudo private properties from debug builds.
Former-commit-id: 839c2e47e19ba163f0c69d3c115ec9ee262d5d6b
2012-08-29 01:17:28 -07:00
John-David Dalton
0044917943 Ensure Closure Compiler removes isPlainObject when it's no longer needed in build.js.
Former-commit-id: 748de0f28f4e0733ef83a77fe2b1f6e40e4f8ce2
2012-08-29 00:34:58 -07:00
John-David Dalton
0337c04278 Bump to v0.6.1.
Former-commit-id: 68b59ea3e76d06d60acc81e30770b5899b8ea761
2012-08-29 00:14:40 -07:00
John-David Dalton
4585acf70b Optimize isPlainObject.
Former-commit-id: 37754e27d03929927d8b8653bd7e44ad3ce2b23c
2012-08-29 00:05:02 -07:00
John-David Dalton
2ad9bbae25 Avoid issues in IE with other code containing comments like //@todo xyz.
Former-commit-id: 92ec6258539bf48144e9af05d0e326651a5fedda
2012-08-28 23:58:07 -07:00
John-David Dalton
cf075df7d1 Update cdnjs link in README.md.
Former-commit-id: 50a52807a505d0d559f9977e3b5c9532fa3f131e
2012-08-28 08:58:47 -07:00
John-David Dalton
11cd9905c4 Bump to v0.6.0.
Former-commit-id: 7339c27ba63208f23d94ae4a29dec53a43220bad
2012-08-28 01:47:22 -07:00
John-David Dalton
cf462542e9 Cleanup iteratorTemplate and remove more code from the "underscore" build.
Former-commit-id: 3f4c283f0aa205dbcce1940f7222b594284263c4
2012-08-28 00:30:52 -07:00
John-David Dalton
b63f25a1ae Simplify _.unescape to match the behavior of _.escape.
Former-commit-id: ec7f4cf9a6f44b1ba99f467c47d7e04d5596d76e
2012-08-27 07:54:03 -07:00
John-David Dalton
7de69a21c5 Cleanup unescape comments.
Former-commit-id: 4c0a66d28ed8b04748e4d97755983ec328e53ca4
2012-08-27 02:54:55 -07:00
John-David Dalton
21783e4ea0 Add unescape to the dependency map and exclude unescape from the "underscore" build.
Former-commit-id: 20656e5226207e55713a286c7d7069b73170272f
2012-08-27 02:33:38 -07:00
John-David Dalton
53e5a756d7 Update minified build, docdown, and rebuild docs.
Former-commit-id: f198b30ba8963d8b69838ba31d2e3d8085537629
2012-08-27 02:09:04 -07:00
John-David Dalton
8c911a2fd0 Add _.unescape method. [closes #63]
Former-commit-id: 10eada385fd0e1157271a2da6fb32de047d6d88a
2012-08-27 02:05:37 -07:00
John-David Dalton
ce440e9f43 Add hasObjectSpliceBug to avoid unnecessary use of the delete operator from the mutator Array function mixin.
Former-commit-id: 8e92914f451454323a1ff6ef8ec1886b41e54ced
2012-08-26 12:54:03 -07:00
John-David Dalton
6465f8d8e6 Cleanup docs for _.drop, _.pick, _.countBy, _.groupBy, and _.sortBy.
Former-commit-id: 0dc89937d067c996fd28b585f42c1e01e928441b
2012-08-26 11:41:17 -07:00
John-David Dalton
4bdf28059a Cleanup unit tests, update minified build, and docs.
Former-commit-id: 5bca51894bf2fb01732a1ecc5873711d8abf3f9c
2012-08-26 03:09:19 -07:00
John-David Dalton
21010c6540 Add callback and thisArg arguments to _.drop and _.pick. [closes #62]
Former-commit-id: 990655e9e849348c287b3d994d2e2dc741f78fbf
2012-08-26 03:06:43 -07:00
John-David Dalton
a5a6cabac6 Cleanup createIterator and _.contains.
Former-commit-id: 39d4c33e5af0d9338a1725b5c07332a14f453374
2012-08-26 01:07:36 -07:00
John-David Dalton
f460c77f2c Ensure _.reduce works with string Objects in IE < 9.
Former-commit-id: 0ee3496e52f4c393900f37f03e451b8e4abba206
2012-08-26 01:04:09 -07:00
John-David Dalton
1c69d9213e Update minified build and rebuild docs.
Former-commit-id: e9da1d37b6741caf19c47e8dd81b89df60e8410f
2012-08-25 21:50:52 -07:00
John-David Dalton
c02c2d3b2c Optimize _.intersection, move largeSize default to largeArraySize, and cleanup _.where.
Former-commit-id: 9eaea7922623f1bd69f2b18578468a6fc9ba13fc
2012-08-25 21:50:08 -07:00
John-David Dalton
7adf5e763b Cleanup benchmarks in perf.js.
Former-commit-id: f845855383e01c63d513cea53cec86abb4f4fb65
2012-08-25 20:13:24 -07:00
John-David Dalton
0e4afefc7f Add more _.omit benchmarks and update Underscore.
Former-commit-id: b8de29706b381ebc000a7cbaa19aa0a2a628d6a8
2012-08-25 01:04:33 -07:00
John-David Dalton
c7f290f42e Cleanup unit tests and update ticket reference in README.md.
Former-commit-id: c2433e841c20eb17334d375deabcf0605ed49a3b
2012-08-25 01:00:19 -07:00
John-David Dalton
bb553b8a6a Update whitespace removal for delete operators in pre-compile.js.
Former-commit-id: 8dddeed54f8f13268f777d829ba52363706b9f4a
2012-08-25 00:56:00 -07:00
John-David Dalton
21eda2a1a3 Add _.omit as an alias of _.drop.
Former-commit-id: 630b0897bb49161fbc3b51a38c816b4bce548fba
2012-08-24 09:46:44 -07:00
John-David Dalton
48c13c990f Update vendors.
Former-commit-id: f2a09b6501a34b2ae2d8e996b13c9da4fb048535
2012-08-24 08:26:34 -07:00
John-David Dalton
a2665529f6 Optimize method compilation with/without strict mode.
Former-commit-id: 05e4d9282116987b53a817b406c82c140a25c761
2012-08-23 01:49:44 -07:00
John-David Dalton
8d35d78eff Ensure debug builds work against more build options.
Former-commit-id: 121b3605026d8f936e2ca0e3a03142cb08e75e66
2012-08-22 09:13:50 -07:00
John-David Dalton
a82a364c22 Update dependencies in build.js.
Former-commit-id: 020a52bdd604b55b078637aeb59e2e53483c950d
2012-08-21 21:21:24 -07:00
John-David Dalton
cc150ff9d2 Cleanup test.js and README.md.
Former-commit-id: af3372f58ecf8d979d22c88193589857c06ff7c1
2012-08-21 02:31:40 -07:00
John-David Dalton
feeb38293d Update cdnjs link to v0.5.2 in README.md.
Former-commit-id: e519a03ad9016f8aebd2c703f29bb350bcd442f1
2012-08-21 02:13:53 -07:00
John-David Dalton
5b9271ccfe Add isType benchmarks.
Former-commit-id: 875af51200d60eb6105eb36ab090e9f307a15340
2012-08-21 01:59:56 -07:00
John-David Dalton
e60bbc2fb7 Bump to v0.5.2.
Former-commit-id: 43998ef8cf3cbdb9205d702d950f9f59febc420b
2012-08-21 01:32:41 -07:00
John-David Dalton
387cc184e6 Keep test count consistent for Node.js.
Former-commit-id: ca0452ff4511c946946a2208b44471b1e2c6fc43
2012-08-21 01:31:54 -07:00
John-David Dalton
a830ddcb43 Cleanup perf/index.html.
Former-commit-id: 7592ee8ec0c6b2bb85d93d7dc599d814ef5a1eef
2012-08-20 22:46:35 -07:00
John-David Dalton
35fea30191 Cleanup string tests and add string test to _.toArray.
Former-commit-id: 3565f7233f1f7662604e3b8e1f07eead62349454
2012-08-20 22:45:57 -07:00
John-David Dalton
24b672b968 Update vendors.
Former-commit-id: 4ccda4fafe86514cdb45d191cb743baca9b1baa4
2012-08-20 22:43:43 -07:00
John-David Dalton
de6a3c5ab1 Switch order of performance.now check in perf/index.html and convert benchmarks to strings in perf.js for better compatibility with older browsers.
Former-commit-id: d766316c9c162e31dd0471d80b50ecfd1da2c9a8
2012-08-19 23:45:16 -07:00
John-David Dalton
260ff6de3e Avoid using the nano applet in perf/index.html if it isn't needed.
Former-commit-id: 342983337140dd5608848b7f09d24038ea61d1a1
2012-08-19 15:35:38 -07:00
John-David Dalton
7cb37411c9 Ensure _.template won't error when passed falsey values, add _.template documentation, and DRY out tokenizeEvaluate.
Former-commit-id: b575de8bf968c2fc2655eadff0a09bcbff1e1753
2012-08-19 13:34:58 -07:00
John-David Dalton
e4e41e5ef8 Cleanup _.template docs and generate less unused code in compiled templates.
Former-commit-id: e6703414b83e9286d9ce5e14214375bbbaf9285f
2012-08-19 02:13:43 -07:00
John-David Dalton
985f4aafca Update minified build and rebuild docs.
Former-commit-id: 0c2c5769a94c0591359fcc7d0ae55ccfe975c38b
2012-08-18 21:44:35 -07:00
John-David Dalton
0c25dd44b3 Ensure _.isElement uses strict equality in its duck type check.
Former-commit-id: 6348026ebdc219ef1df9926aca1d8df66e472de9
2012-08-18 21:41:29 -07:00
John-David Dalton
285f0bc6dd Ensure isXYZ methods return boolean values and almost all methods allow falsey arguments.
Former-commit-id: a842eaf2fd262bed03df4a71b560b91801b7a75f
2012-08-18 20:52:31 -07:00
John-David Dalton
07a370fd24 Update cdnjs link to v0.5.1 in README.md.
Former-commit-id: 3c00694a949b4f30be48a898f1f2a9046b2573d1
2012-08-18 10:02:38 -07:00
John-David Dalton
95f07ea38a Add stable-sort note to _.sortBy documentation.
Former-commit-id: 17b9818baa6ff72ce5762ec8b3cc01bbac8725bb
2012-08-18 03:40:42 -07:00
John-David Dalton
2890b92b21 Tweak to _.bind unit test for Narwhal.
Former-commit-id: 5df60a9d1e46b09253151ab39281c5af0c48544f
2012-08-18 03:37:43 -07:00
John-David Dalton
8b80c5d0b5 Bump v0.5.1.
Former-commit-id: 09922fa2e936e0b112100690a04d6437131904a8
2012-08-18 02:20:30 -07:00
John-David Dalton
04425786a1 Ensure _.bind correctly appends array arguments to partially applied arguments.
Former-commit-id: 4fdb100f83ff9a0eafcba3f5bf91872748205595
2012-08-18 02:11:40 -07:00
John-David Dalton
15b14e12e2 Change to triple-backtick code fences.
Former-commit-id: 81d38ed0155734a72d98b2e16425635da907aa01
2012-08-18 00:04:17 -07:00
Mathias Bynens
b328972c4d README: Switch to the triple backtick syntax for highlighted code blocks
Former-commit-id: c4fba0c9cf7e8b0fdfc846260b148ecd327c0a1f
2012-08-17 19:31:03 +02:00
John-David Dalton
da3156047f Update minified build and rebuild documentation.
Former-commit-id: ab353b4c73c47b51144f5a917aeb53682b40552e
2012-08-17 08:17:14 -07:00
John-David Dalton
f9f08ba54f Optimize isPlainObject use in _.clone.
Former-commit-id: 7bb48bc5f9276c730f947b6e75b6fba4588f17c1
2012-08-17 08:16:31 -07:00
John-David Dalton
b43684262f Cleanup _.template documentation.
Former-commit-id: 7b45705bd8a4661dbf824912798ccba15e5ed989
2012-08-17 04:05:50 -07:00
John-David Dalton
2120f78bd7 Fix typos in README.md.
Former-commit-id: fb31a291a3afe799d2e9809f17420eda10888250
2012-08-17 02:54:19 -07:00
John-David Dalton
2d896f22d6 Bump to v0.5.0.
Former-commit-id: f30f4b99104c94fb52df4c90dcda24b8ac51432c
2012-08-17 02:28:42 -07:00
John-David Dalton
9fa0aebfe9 Add check for V8 bug #2291.
Former-commit-id: 245f156a2ce1ee92c8f8f8f34b9891ba4eff0d23
2012-08-17 02:12:17 -07:00
John-David Dalton
2ddc3af5ff Fix jQuery/MooTools DOM collection unit tests in IE6.
Former-commit-id: b9c3dbcffb28b0088018614e21fbe313b0d53713
2012-08-17 00:25:52 -07:00
John-David Dalton
fd80e096ea Remove noArgsClass check from isPlainObject.
Former-commit-id: bab7a68e8eaa21679b33b2ab70a9f8e47758ebc1
2012-08-17 00:10:03 -07:00
John-David Dalton
fab2d69fce Ensure _.sortBy performs a stable sort. [closes #59]
Former-commit-id: 09c5ff85ef0f1d054579ec4260a7f76d9c0da281
2012-08-16 23:24:59 -07:00
John-David Dalton
83d08e3aba Avoid false positives for arguments objects in IE < 9 when using isPlainObject.
Former-commit-id: 19e5a6efed32f26d0c908771eec40e7fb2ecf265
2012-08-16 22:22:08 -07:00
John-David Dalton
bfea6bcacf Maintain up-to-date minified underscore-min.js and use in perf tests.
Former-commit-id: 6317df872bd6136d81763d27a62a444f6e392537
2012-08-16 21:21:48 -07:00
John-David Dalton
feff34b021 Update vendors.
Former-commit-id: 44c2fe9fbfbf50aa64663d46b45d6fd2c778f2b7
2012-08-16 21:20:54 -07:00
John-David Dalton
79fbade92a Minor unit test cleanup.
Former-commit-id: 3243247d9da53f3a12cd19bec822bb43eb7f315e
2012-08-16 21:20:05 -07:00
John-David Dalton
bf508e453e Remove unused funcClass variable from compiled functions.
Former-commit-id: f47b8e412b5151e69a3ab78f03243c461b5ab072
2012-08-16 21:19:47 -07:00
John-David Dalton
47b51c22fa Ensure build tweaks are applied when new lines break up specific patterns.
Former-commit-id: 085a837521750457db9f20f3701984cba24a4f4f
2012-08-14 23:15:04 -07:00
John-David Dalton
ebd16105f2 Detect and remove additional unnecessary brackets from template strings during the build process.
Former-commit-id: 0b9786e7d41497fd1627e31401a5e7e0926eea9c
2012-08-14 00:12:27 -07:00
John-David Dalton
01fb1a5775 Optimize _.isFunction.
Former-commit-id: 0aaaa6d166c7eea94237388d61a11c6d183cbe1f
2012-08-13 23:11:01 -07:00
John-David Dalton
b8c2a05db9 Add strict comparison note to _.intersection and _.union documentation.
Former-commit-id: 47792d96e77fdeb6f00db1e4d30b41a6f1a59dbb
2012-08-13 07:37:26 -07:00
John-David Dalton
4087dc5fe4 Reword unit jQuery/MooTools DOM query collection unit test.
Former-commit-id: c80673f799ce6f1449d043eabf76fd6970a70b77
2012-08-13 00:45:38 -07:00
John-David Dalton
408029e6e0 Don't expose _.forIn or _.forOwn if underscore build modifier is present and include doesn't contain forIn or forOwn.
Former-commit-id: 6ebd0bc61d3ae6e2d0506fe72dd22ed59c601c70
2012-08-13 00:44:37 -07:00
John-David Dalton
23ff403529 Update vendors.
Former-commit-id: b239f365c9d8e012bf20525167e6d1a412ce828e
2012-08-12 22:33:27 -07:00
John-David Dalton
be4f81f584 Remove more unnecessary brackets from template strings during the build process.
Former-commit-id: c8e3dfe7d34f9c61fff75512fb4587a987bdf49f
2012-08-12 21:41:41 -07:00
John-David Dalton
361c91e610 Add support in _.isEmpty and _.size for jQuery/MooTools DOM query collections.
Former-commit-id: dc834256d09d7a3f2797ba65690961aad00717bf
2012-08-12 21:40:46 -07:00
John-David Dalton
2aa2ea9675 Fix edge issue with Opera 10.53-10.60.
Former-commit-id: 60014a8e3855bcbf0e4c6f47461123cacafcf668
2012-08-11 21:47:04 -07:00
John-David Dalton
e084225edf Cleanup .npmignore and ensure perf.js will work in the package.
Former-commit-id: e03213fba34c38af2be9003f5be4837d293198e1
2012-08-11 21:36:14 -07:00
John-David Dalton
9ce342205b Add Jam package support.
Former-commit-id: 399caeea73fc79dd340baa7e34b46179ff2beb86
2012-08-11 21:35:06 -07:00
John-David Dalton
0edf50e00e Update edge links in README.md to v0.5.0-rc.1 tagged links.
Former-commit-id: 1f9fea70c6608d980cc8c8fd7d0a39764cf14fa2
2012-08-09 00:24:53 -07:00
John-David Dalton
1ea19daad9 Clear the func variable in _.once so the function may be garbage collected.
Former-commit-id: 521a9d997cb206a5c468b30e5dfa8e096e1a13d5
2012-08-09 00:16:35 -07:00
John-David Dalton
32b302314e Add doc links to the change log entry in README.md.
Former-commit-id: 3c6c79de33331f14af2d5d2c64d1286c67bfb774
2012-08-07 08:20:00 -07:00
John-David Dalton
ee197e02a2 Updated tested environments and resolved issues in README.md.
Former-commit-id: 7d8cc17bad215d87c8c11210d58e1927c5766145
2012-08-07 08:04:46 -07:00
John-David Dalton
7cd6ffec2a Bump to v0.5.0-rc.1.
Former-commit-id: f5a29fd34de5aced3287413f40a0a36226465c84
2012-08-07 00:47:15 -07:00
John-David Dalton
0077580838 Fix failing unit tests in various browsers.
Former-commit-id: 9b5c6e82e0dfed79c505f41b66030ede81ca9a1c
2012-08-07 00:17:04 -07:00
John-David Dalton
4d85a79fd1 Add note about Chrome extensions to _.template.
Former-commit-id: 97b5220ebc3dbbd30b0e52f6c804b449b8d451ca
2012-08-06 08:03:54 -07:00
John-David Dalton
a35139bb61 Fix typo in test-ui.js.
Former-commit-id: 95057e80cfac9d7d776170d66f2c2bfdd364944f
2012-08-06 02:02:23 -07:00
John-David Dalton
f72b833724 Ensure _.keys works with arguments objets cross-browser.
Former-commit-id: 1238c9efcfc13a420804c26c5edee1e4aa5a4238
2012-08-06 01:51:47 -07:00
John-David Dalton
0e16bac6e2 Rebuild documentation.
Former-commit-id: d24c2fb65a54b018da9ad08aa3a67363a31c1122
2012-08-05 23:38:23 -07:00
John-David Dalton
e17564b362 Add unit tests for exiting early from _.forEach, _.forIn, _.forOwn.
Former-commit-id: 18d55054989d3b55afce1f4d604cd274eb9289c8
2012-08-05 23:09:05 -07:00
John-David Dalton
90b66eddf5 Add _.keys unit test for arguments objects.
Former-commit-id: dd90a9d3dc973a6ea8dce0c10ad511cce82097dd
2012-08-05 22:37:43 -07:00
John-David Dalton
2862a5849f Document how _.each, _.forIn, and _.forOwn may terminate iteration early by explicitly returning false in the callback.
Former-commit-id: e218eae83de880deecf1e45d32ca019efd25b548
2012-08-05 00:29:18 -07:00
John-David Dalton
7e839231ed Add iteratesOwnLast bug check.
Former-commit-id: fcc42a42f08e60acc1252ec06b045cfee17bd020
2012-08-05 00:09:00 -07:00
John-David Dalton
fa56a4bb73 Cleanup build.js and update dependencies for _.clone, _.isEmpty, _.isEqual, _.merge, and _.size.
Former-commit-id: df19990609a3dd8432694798149a5eb5bda142c4
2012-08-05 00:02:33 -07:00
John-David Dalton
dff950748c Fix the build to work with _.merge.
Former-commit-id: cb1d9897b97b357197bb6933c65f4afbecea1aea
2012-08-04 17:13:09 -07:00
John-David Dalton
b7374e3f8e Ensure merge works with sources containing circular references.
Former-commit-id: cb3301868299c4e4d32a3d3608199e4269d951ec
2012-08-04 17:12:25 -07:00
John-David Dalton
4244b92b08 Update minified build and rebuild docs.
Former-commit-id: f80bacba89ab171aae61d5ce7269e8507117bffa
2012-08-02 00:33:39 -07:00
John-David Dalton
4f688028ad Update vendors.
Former-commit-id: d1dd2ecf337cf8cda71e0da2af0647813b8de24a
2012-08-02 00:32:15 -07:00
John-David Dalton
5a9a18501d Add _.merge.
Former-commit-id: e393655b1fa41c8eb6ae1b925f456aa05231078a
2012-08-02 00:28:19 -07:00
John-David Dalton
896b8f7cf1 Remove noNodeClass from "mobile" and "csp" builds.
Former-commit-id: 0773f7b1c1ea637d0d519a3015d1534863a9be11
2012-07-29 23:54:52 -07:00
John-David Dalton
95bd1b014f Rework the noNodeClass inference to detect the JScript quirk too.
Former-commit-id: 3a6b73509490d7e87d2322fe1c09890f5f6b57ea
2012-07-29 23:48:07 -07:00
John-David Dalton
dbdf8642bb Rebuild documentation and update the minified build.
Former-commit-id: c4d4281f16250f50ea4d5337a53acceedd2ce660
2012-07-29 14:18:05 -07:00
John-David Dalton
c3cd9007d2 Add unit tests for _.where.
Former-commit-id: 5247bba20d7aa772867e29def99221faea376db1
2012-07-29 14:17:34 -07:00
John-David Dalton
8079fb5bc5 Reorganize and cleanup code for _.where.
Former-commit-id: 769dfafc1701c5ebc2d351abeb729ca6fcfa12e9
2012-07-29 13:49:23 -07:00
John-David Dalton
80d0b5d4ed Add _.where. [closes #22].
Former-commit-id: dec7a9d0df4158a395ec84fb9e774ed20205d421
2012-07-29 13:48:21 -07:00
John-David Dalton
86bd847bf9 Ensure _.isEqual works correctly for objects from another document and add _.clone benchmark.
Former-commit-id: b1ef745ec6c24e8ea0c8fae304ead80c60dfd5aa
2012-07-29 00:58:23 -07:00
John-David Dalton
943004844a Ensure the callback of _.filter can't modify the resulting value.
Former-commit-id: 6652dc5926390e8418b524c7ffd2347c2fa65c82
2012-07-26 23:24:17 -07:00
John-David Dalton
4ff12e0426 Fix recent typo in isEqual.
Former-commit-id: 84cdf90de59c8184a0ac1f78704f61dcc88c91f1
2012-07-25 01:35:56 -07:00
John-David Dalton
0d7bdb6fa8 Make _.range coerce arguments to numbers.
Former-commit-id: 5408800fef0bc8a418c298a112049232b6d85e78
2012-07-25 01:32:28 -07:00
John-David Dalton
6d488b6a81 Update minified build and rebuild documentation.
Former-commit-id: 5f0ea311b51723c225774d735f67fc58f55a4d6e
2012-07-24 02:15:20 -07:00
John-David Dalton
f1d0263ffa Add csp Content Security Policy build.
Former-commit-id: f922046de5150c098eb7f6d3b6901552117631e7
2012-07-24 02:13:31 -07:00
John-David Dalton
24035caadb Keep objects in the stack array of _.isEqual in case a cyclical object is used in a sibling property.
Former-commit-id: 33ffcec837322f4d479f64f9ec6b14184bf5f7a2
2012-07-24 02:13:07 -07:00
John-David Dalton
ed89a3e0f8 Check for custom isEqual methods in _.isEqual before other comparisons and add cross-browser support for arguments objects.
Former-commit-id: 56e40f670309aeb0dcda7868c8aa84b5909db1f1
2012-07-24 01:45:34 -07:00
John-David Dalton
7088ab89f1 Add deep clone support via the deep argument to _.clone.
Former-commit-id: d4fad45364489efb957d29e201846e8c1875b9ed
2012-07-24 01:41:38 -07:00
John-David Dalton
d4688bd76b Adjust _.wrap docs and rebuild/update the minified build/docs.
Former-commit-id: b33c4489bde49c98e7e161d0f831e07388a9da22
2012-07-20 03:22:13 -04:00
John-David Dalton
befe0fccaf Cleanup _.isEqual comments and ensure _.isEmpty/_.size detect arguments objects correctly.
Former-commit-id: 75b044d27b990d55393bf27234aad6ce369f6abe
2012-07-20 02:06:13 -04:00
John-David Dalton
a192410498 Remove Narwhal link from README.md.
Former-commit-id: b8bd53c9a19ef18480db14cfa82545aa872416b1
2012-07-19 09:15:04 -04:00
John-David Dalton
d43ede3a11 Update vendor/underscore and backbone.
Former-commit-id: 614926653b0b1669f8a5533666adb0aecbd46c03
2012-07-19 01:32:47 -04:00
John-David Dalton
9848ffb77f Add _.countBy and related unit tests, benchmarks, and documentation.
Former-commit-id: 915eaf414883a1dc344a558b08c7a1337ab5a225
2012-07-19 01:29:51 -04:00
John-David Dalton
7487497d1f Update minified build and rebuild documentation.
Former-commit-id: e674d7a30c1e5745b0acf90e38aa5757b15a3423
2012-07-18 04:52:13 -04:00
John-David Dalton
be11c848f4 Add "underscore" build.
Former-commit-id: 44e9f4543631cbf342ae7571cf540214623352db
2012-07-18 04:51:08 -04:00
John-David Dalton
9836b274b9 Add _.drop, unit tests, and cleanup documentation for _.extend, _.defaults, and _.pick.
Former-commit-id: a45b0c45d52fdbe5f71984412d631f3dfe87965b
2012-07-18 03:55:16 -04:00
John-David Dalton
a96d14566f Add Underscore ticket reference to Fix cross-browser object iteration bugs bullet in README.md.
Former-commit-id: 9814daf6b81f1c60b3c6681c10c1fc7b94ac61b8
2012-07-18 02:15:16 -04:00
John-David Dalton
1c8cd8c168 Ensure reEvaluateDelimiter is assigned an initial value if evaluateDelimiter is undefined by default. [closes #52]
Former-commit-id: 6cefeeca164901e611009ce1413e5080027592c9
2012-07-18 01:53:15 -04:00
John-David Dalton
5f5806a98e Update CDN link and rebuild documentation.
Former-commit-id: 32210baffaed31b7fb4daed58a0c0a4f22daf4f0
2012-07-17 01:41:21 -04:00
John-David Dalton
624b045ac0 Add unit tests and code comments for the conditional compilation patch.
Former-commit-id: 557aa43dc7c8db738452a9f3afb8ff2aadf8061a
2012-07-17 01:39:51 -04:00
Jay Phelps
0f8bae950e Ensure running in IE if concated with other files that enable conditional compilation /*@cc_on @/`.
Former-commit-id: cf2d9c01ce92b8b85149116d48145bc4342471a4
2012-07-16 20:07:55 -04:00
John-David Dalton
fcede42903 Add Unit testing video link to README and minor cleanup to build.js help text.
Former-commit-id: 504b69acede1bb4759ba942d01dfd0b899715f00
2012-07-16 18:28:34 -04:00
63 changed files with 10821 additions and 6352 deletions

1
.gitignore vendored
View File

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

14
.jamignore Normal file
View File

@@ -0,0 +1,14 @@
*.custom.*
.*
build.js
index.js
build/
doc/*.php
node_modules/
perf/*.sh
test/*.sh
test/test-build.js
test/template/
vendor/closure-compiler
vendor/docdown
vendor/uglifyjs

View File

@@ -1,7 +1,5 @@
*.custom.*
*.min.*
.*
dist/
doc/*.php
node_modules/
perf/*.html
@@ -11,7 +9,7 @@ test/*-ui.js
test/*.sh
vendor/backbone/
vendor/docdown/
vendor/qunit/qunit/*.css
vendor/underscore/
vendor/requirejs/
vendor/firebug-lite/
vendor/qunit/qunit/*.css
vendor/requirejs/
vendor/underscore/test/

4
.travis.yml Normal file
View File

@@ -0,0 +1,4 @@
language: node_js
node_js:
- 0.6
- 0.8

254
README.md
View File

@@ -1,15 +1,17 @@
# Lo-Dash <sup>v0.4.2</sup>
# Lo-Dash <sup>v0.8.0</sup>
[![build status](https://secure.travis-ci.org/bestiejs/lodash.png)](http://travis-ci.org/bestiejs/lodash)
A drop-in replacement<sup>[*](https://github.com/bestiejs/lodash/wiki/Drop-in-Disclaimer)</sup> for Underscore.js, from the devs behind [jsPerf.com](http://jsperf.com), 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).
A drop-in replacement<sup>[*](https://github.com/bestiejs/lodash/wiki/Drop-in-Disclaimer)</sup> for Underscore.js, from the devs behind [jsPerf.com](http://jsperf.com), delivering [performance](http://lodash.com/benchmarks), [bug fixes](https://github.com/bestiejs/lodash#resolved-underscorejs-issues), and [additional features](http://lodash.com/#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.2/lodash.js)
* [Production source](https://raw.github.com/bestiejs/lodash/v0.4.2/lodash.min.js)
* CDN copies of ≤ [v0.4.1](http://cdnjs.cloudflare.com/ajax/libs/lodash.js/0.4.1/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
* [Development build](https://raw.github.com/bestiejs/lodash/v0.8.0/lodash.js)
* [Production build](https://raw.github.com/bestiejs/lodash/v0.8.0/lodash.min.js)
* [Underscore build](https://raw.github.com/bestiejs/lodash/v0.8.0/lodash.underscore.min.js) tailored for projects already using Underscore
* CDN copies of ≤ [v0.8.0](http://cdnjs.cloudflare.com/ajax/libs/lodash.js/0.8.0/lodash.min.js) are available on [cdnjs](http://cdnjs.com/) thanks to [CloudFlare](http://www.cloudflare.com/)
* For optimal file size, [create a custom build](https://github.com/bestiejs/lodash#custom-builds) with only the features you need
## Dive in
@@ -24,121 +26,165 @@ 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](https://vimeo.com/44154599)
* [Optimizations and custom builds](https://vimeo.com/44154601)
* [Lo-Dash optimizations and custom builds](https://vimeo.com/44154601)
* [Lo-Dashs origin and why its a better utility belt](https://vimeo.com/44154600)
* [Unit testing in Lo-Dash](https://vimeo.com/45865290)
* [Lo-Dashs approach to native method use](https://vimeo.com/48576012)
## Features
* 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
* [_.clone](http://lodash.com/docs#clone) supports *“deep”* cloning
* [_.forEach](http://lodash.com/docs#forEach) is chainable and supports exiting iteration early
* [_.forIn](http://lodash.com/docs#forIn) for iterating over an objects own and inherited properties
* [_.forOwn](http://lodash.com/docs#forOwn) for iterating over an objects own properties
* [_.groupBy](http://lodash.com/docs#groupBy), [_.sortedIndex](http://lodash.com/docs#sortedIndex), and [_.uniq](http://lodash.com/docs#uniq) accept a `thisArg` argument
* [_.indexOf](http://lodash.com/docs#indexOf) and [_.lastIndexOf](http://lodash.com/docs#lastIndexOf) accept a `fromIndex` argument
* [_.partial](http://lodash.com/docs#partial) for more functional fun
* [_.lateBind](http://lodash.com/docs#lateBind) for late binding
* [_.merge](http://lodash.com/docs#merge) for a *“deep”* [_.extend](http://lodash.com/docs#extend)
* [_.partial](http://lodash.com/docs#partial) for partial application without `this` binding
* [_.pick](http://lodash.com/docs#pick) and [_.omit](http://lodash.com/docs#omit) accept `callback` and `thisArg` arguments
* [_.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
[and more…](http://lodash.com/docs "_.countBy, _.every, _.filter, _.find, _.forEach, _.groupBy, _.invoke, _.map, _.pluck, _.reduce, _.reduceRight, _.reject, _.some, _.sortBy, _.where") accept strings
## Support
Lo-Dash has been tested in at least Chrome 5-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.
Lo-Dash has been tested in at least Chrome 5-22, Firefox 1-15, IE 6-10, Opera 9.25-12, Safari 3-6, Node.js 0.4.8-0.8.11, Narwhal 0.3.2, RingoJS 0.8, and Rhino 1.7RC5.
## Custom builds
Custom builds make it easy to create lightweight versions of Lo-Dash containing only the methods you need.
We handle all the method dependency and alias mapping for you.
To top it off, we handle all method dependency and alias mapping for you.
* Backbone builds, containing all methods required by Backbone, may be created using the `backbone` modifier argument.
~~~ bash
* Backbone builds, with only methods required by Backbone, may be created using the `backbone` modifier argument.
```bash
lodash backbone
~~~
```
* CSP builds, supporting default Content Security Policy restrictions, may be created using the `csp` modifier argument.
```bash
lodash csp
```
* Legacy builds, tailored for older browsers without [ES5 support](http://es5.github.com/), may be created using the `legacy` modifier argument.
~~~ bash
```bash
lodash legacy
~~~
```
* Mobile builds, with IE < 9 bug fixes and method compilation removed, may be created using the `mobile` modifier argument.
~~~ bash
```bash
lodash mobile
~~~
```
* Strict builds, with `_.bindAll`, `_.extend`, and `_.defaults` in [strict mode](http://es5.github.com/#C), may be created using the `strict` modifier argument.
~~~ bash
* Strict builds, with `_.bindAll`, `_.defaults`, and `_.extend` in [strict mode](http://es5.github.com/#C), may be created using the `strict` modifier argument.
```bash
lodash strict
~~~
```
Custom builds may be created in three ways:
* Underscore builds, tailored for projects already using Underscore, may be created using the `underscore` modifier argument.
```bash
lodash underscore
```
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
Custom builds may be created using the following commands:
* Use the `category` argument to pass comma separated categories of methods to include in the build.<br>
Valid categories (case-insensitive) are *“arrays”*, *“chaining”*, *“collections”*, *“functions”*, *“objects”*, and *“utilities”*.
```bash
lodash category=collections,functions
lodash category="collections, functions"
~~~
```
2. Use the `exclude` argument to pass the names of methods to exclude from the build.
~~~ bash
lodash exclude=union,uniq,zip
lodash exclude="union, uniq, zip"
~~~
* Use the `exports` argument to pass comma separated names of ways to export the `LoDash` function.<br>
Valid exports are *“amd”*, *“commonjs”*, *“global”*, *“node”*, and *“none”*.
```bash
lodash exports=amd,commonjs,node
lodash exports="amd, commonjs, node"
```
3. Use the `include` argument to pass the names of methods to include in the build.
~~~ bash
* Use the `iife` argument to specify code to replace the immediately-invoked function expression that wraps Lo-Dash.
```bash
lodash iife="!function(window,undefined){%output%}(this)"
```
* Use the `include` argument to pass comma separated method/category names to include in the build.
```bash
lodash include=each,filter,map
lodash include="each, filter, map"
~~~
```
All arguments, except `exclude` with `include` and `legacy` with `mobile`, may be combined.
* Use the `minus` argument to pass comma separated method/category names to remove from those included in the build.
```bash
lodash underscore minus=result,shuffle
lodash underscore minus="result, shuffle"
```
~~~ bash
lodash backbone legacy category=utilities exclude=first,last
lodash backbone mobile strict category=functions include=pick,uniq
~~~
* Use the `plus` argument to pass comma separated method/category names to add to those included in the build.
```bash
lodash backbone plus=random,template
lodash backbone plus="random, template"
```
* Use the `template` argument to pass the file path pattern used to match template files to precompile
```bash
lodash template="./*.jst"
```
* Use the `settings` argument to pass the template settings used when precompiling templates
```bash
lodash settings="{interpolate:/\\{\\{([\\s\\S]+?)\\}\\}/g}"
```
All arguments, except `legacy` with `csp` or `mobile`, may be combined.<br>
Unless specified by `-o` or `--output`, all files created are saved to the current working directory.
The following options are also supported:
* `-c`, `--stdout`&nbsp;&nbsp;&nbsp;&nbsp; Write output to standard output
* `-d`, `--debug`&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Write only the debug output
* `-h`, `--help`&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Display help information
* `-m`, `--minify`&nbsp;&nbsp;&nbsp;&nbsp; Write only the minified output
* `-o`, `--output`&nbsp;&nbsp;&nbsp;&nbsp; Write output to a given path/filename
* `-s`, `--silent`&nbsp;&nbsp;&nbsp;&nbsp; Skip status updates normally logged to the console
* `-V`, `--version`&nbsp;&nbsp; Output current version of Lo-Dash
The `lodash` command-line utility is available when Lo-Dash is installed as a global package (i.e. `npm install -g lodash`).
Custom builds are saved to `lodash.custom.js` and `lodash.custom.min.js`.
## Installation and usage
In browsers:
~~~ html
```html
<script src="lodash.js"></script>
~~~
```
Using [npm](http://npmjs.org/):
~~~ bash
```bash
npm install lodash
npm install -g lodash
~~~
```
In [Node.js](http://nodejs.org/) and [RingoJS v0.8.0+](http://ringojs.org/):
~~~ js
```js
var _ = require('lodash');
~~~
```
In [Narwhal](http://narwhaljs.org/) and [RingoJS v0.7.0-](http://ringojs.org/):
In [RingoJS v0.7.0-](http://ringojs.org/):
~~~ js
```js
var _ = require('lodash')._;
~~~
```
In [Rhino](http://www.mozilla.org/rhino/):
~~~ js
```js
load('lodash.js');
~~~
```
In an AMD loader like [RequireJS](http://requirejs.org/):
~~~ js
```js
require({
'paths': {
'underscore': 'path/to/lodash'
@@ -147,46 +193,35 @@ require({
['underscore'], function(_) {
console.log(_.VERSION);
});
~~~
```
## Closed Underscore.js issues <sup>(20+)</sup>
## Resolved Underscore.js issues
* 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.2/test/test.js#L364-370)]
* Ensure array-like objects with invalid `length` properties are treated like regular objects [[test](https://github.com/bestiejs/lodash/blob/v0.4.2/test/test.js#L315-321), [test](https://github.com/bestiejs/lodash/blob/v0.4.2/test/test.js#L665-679), [test](https://github.com/bestiejs/lodash/blob/v0.4.2/test/test.js#L921-924)]
* Ensure *"Arrays"* methods allow falsey `array` arguments [[test](https://github.com/bestiejs/lodash/blob/v0.4.2/test/test.js#L989-1027)]
* 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.2/test/test.js#L148-157), [test](https://github.com/bestiejs/lodash/blob/v0.4.2/test/test.js#L323-341), [test](https://github.com/bestiejs/lodash/blob/v0.4.2/test/test.js#L681-698), [test](https://github.com/bestiejs/lodash/blob/v0.4.2/test/test.js#L926-929)]
* Ensure templates compiled with errors are inspectable [[#666](https://github.com/documentcloud/underscore/issues/666), [test](https://github.com/bestiejs/lodash/blob/v0.4.2/test/test.js#L795-802)]
* Fix cross-browser object iteration bugs [[#376](https://github.com/documentcloud/underscore/issues/376), [test](https://github.com/bestiejs/lodash/blob/v0.4.2/test/test.js#L224-236), [test](https://github.com/bestiejs/lodash/blob/v0.4.2/test/test.js#L375-400), [test](https://github.com/bestiejs/lodash/blob/v0.4.2/test/test.js#L496-507), [test](https://github.com/bestiejs/lodash/blob/v0.4.2/test/test.js#L515-517), [test](https://github.com/bestiejs/lodash/blob/v0.4.2/test/test.js#L535-555), [test](https://github.com/bestiejs/lodash/blob/v0.4.2/test/test.js#L725-727)]
* 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.2/test/test.js#L96-102)]
* 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.2/test/test.js#L80-94)]
* `_(…)` should return passed wrapper instances [[test](https://github.com/bestiejs/lodash/blob/v0.4.2/test/test.js#L114-117)]
* `_.contains` should work with strings [[#667](https://github.com/documentcloud/underscore/pull/667), [test](https://github.com/bestiejs/lodash/blob/v0.4.2/test/test.js#L148-157)]
* `_.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.2/test/test.js#L205-208)]
* `_.forEach` should be chainable [[#142](https://github.com/documentcloud/underscore/issues/142), [test](https://github.com/bestiejs/lodash/blob/v0.4.2/test/test.js#L310-313)]
* `_.groupBy` should add values to own, not inherited, properties [[test](https://github.com/bestiejs/lodash/blob/v0.4.2/test/test.js#L415-422)]
* `_isNaN(new Number(NaN))` should return `true` [[test](https://github.com/bestiejs/lodash/blob/v0.4.2/test/test.js#L525-527)]
* `_.reduceRight` should pass correct callback arguments when iterating objects [[test](https://github.com/bestiejs/lodash/blob/v0.4.2/test/test.js#L649-663)]
* `_.size` should return the `length` of string values [[test](https://github.com/bestiejs/lodash/blob/v0.4.2/test/test.js#L708-710)]
* `_.size` shouldn't error on falsey values [[#650](https://github.com/documentcloud/underscore/pull/650), [test](https://github.com/bestiejs/lodash/blob/v0.4.2/test/test.js#L712-719)]
* `_.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.2/test/test.js#L721-723)]
* `_.sortedIndex` should support arrays with high `length` values [[test](https://github.com/bestiejs/lodash/blob/v0.4.2/test/test.js#L765-774)]
* `_.template` should not augment the `options` object [[test](https://github.com/bestiejs/lodash/blob/v0.4.2/test/test.js#L789-793)]
* `_.throttle` should work when called in a loop [[#502](https://github.com/documentcloud/underscore/issues/502), [test](https://github.com/bestiejs/lodash/blob/v0.4.2/test/test.js#L872-882)]
* `_.zipObject` should accept less than two arguments [[test](https://github.com/bestiejs/lodash/blob/v0.4.2/test/test.js#L951-953)]
* Add AMD loader support [[#431](https://github.com/documentcloud/underscore/pull/431), [test](https://github.com/bestiejs/lodash/blob/v0.8.0/test/test.js#L118-140)]
* Allow iteration of objects with a `length` property [[#799](https://github.com/documentcloud/underscore/pull/799), [test](https://github.com/bestiejs/lodash/blob/v0.8.0/test/test.js#L510-516)]
* 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.8.0/test/test.js#L470-487)]
* Fix cross-browser object iteration bugs [[#60](https://github.com/documentcloud/underscore/issues/60), [#376](https://github.com/documentcloud/underscore/issues/376), [test](https://github.com/bestiejs/lodash/blob/v0.8.0/test/test.js#L526-546)]
* Methods should work on pages with incorrectly shimmed native methods [[#7](https://github.com/documentcloud/underscore/issues/7), [#742](https://github.com/documentcloud/underscore/issues/742), [test](https://github.com/bestiejs/lodash/blob/v0.8.0/test/test.js#L142-148)]
* `_.clone` should allow `deep` cloning [[#595](https://github.com/documentcloud/underscore/pull/595), [test](https://github.com/bestiejs/lodash/blob/v0.8.0/test/test.js#L215-224)]
* `_.contains` should work with strings [[#667](https://github.com/documentcloud/underscore/pull/667), [test](https://github.com/bestiejs/lodash/blob/v0.8.0/test/test.js#L267-276)]
* `_.extend` should recursively extend objects [[#379](https://github.com/documentcloud/underscore/pull/379), [#718](https://github.com/documentcloud/underscore/issues/718), [test](https://github.com/bestiejs/lodash/blob/v0.8.0/test/test.js#L946-968)]
* `_.forEach` should be chainable [[#142](https://github.com/documentcloud/underscore/issues/142), [test](https://github.com/bestiejs/lodash/blob/v0.8.0/test/test.js#L465-468)]
* `_.forEach` should allow exiting iteration early [[#211](https://github.com/documentcloud/underscore/issues/211), [test](https://github.com/bestiejs/lodash/blob/v0.8.0/test/test.js#L556-570)]
* `_.isEmpty` should support jQuery/MooTools DOM query collections [[#690](https://github.com/documentcloud/underscore/pull/690), [test](https://github.com/bestiejs/lodash/blob/v0.8.0/test/test.js#L703-708)]
* `_.isObject` should avoid V8 bug [#2291](http://code.google.com/p/v8/issues/detail?id=2291) [[#605](https://github.com/documentcloud/underscore/issues/605), [test](https://github.com/bestiejs/lodash/blob/v0.8.0/test/test.js#L763-775)]
* `_.keys` should work with `arguments` objects cross-browser [[#396](https://github.com/documentcloud/underscore/issues/396), [test](https://github.com/bestiejs/lodash/blob/v0.8.0/test/test.js#L856-858)]
* `_.range` should coerce arguments to numbers [[#634](https://github.com/documentcloud/underscore/issues/634), [#683](https://github.com/documentcloud/underscore/issues/683), [test](https://github.com/bestiejs/lodash/blob/v0.8.0/test/test.js#L1222-1225)]
* `_.throttle` should work when called in a loop [[#502](https://github.com/documentcloud/underscore/issues/502), [test](https://github.com/bestiejs/lodash/blob/v0.8.0/test/test.js#L1525-1535)]
## Optimized methods <sup>(50+)</sup>
* `_.bind`
* `_.bindAll`
* `_.clone`
* `_.compact`
* `_.contains`, `_.include`
* `_.defaults`
* `_.defer`
* `_.difference`
* `_.each`
* `_.escape`
* `_.every`, `_.all`
* `_.extend`
* `_.filter`, `_.select`
@@ -197,11 +232,10 @@ require({
* `_.groupBy`
* `_.indexOf`
* `_.intersection`
* `_.invert`
* `_.invoke`
* `_.isArguments`
* `_.isDate`
* `_.isEmpty`
* `_.isEqual`
* `_.isFinite`
* `_.isFunction`
* `_.isObject`
@@ -215,6 +249,8 @@ require({
* `_.memoize`
* `_.min`
* `_.mixin`
* `_.omit`
* `_.pairs`
* `_.pick`
* `_.pluck`
* `_.reduce`, `_.foldl`, `_.inject`
@@ -226,11 +262,11 @@ require({
* `_.sortedIndex`
* `_.template`
* `_.throttle`
* `_.times`
* `_.toArray`
* `_.union`
* `_.uniq`, `_.unique`
* `_.values`
* `_.where`
* `_.without`
* `_.wrap`
* `_.zip`
@@ -238,39 +274,31 @@ require({
## Release Notes
### <sup>v0.4.2</sup>
### <sup>v0.8.0</sup>
* Added `strict` build
* Ensured `_.bindAll`, `_.extend`, and `_.defaults` avoid strict mode errors when attempting to augment read-only properties
* Optimized the iteration of large arrays in `_.difference`, `_.intersection`, and `_.without`
* Fixed build bugs related to removing variables
#### Compatibility Warnings ####
### <sup>v0.4.1</sup>
* Made `_.random` return `0` or `1` when no arguments are passed
* Moved late bind functionality from `_.bind` to [_.lateBind](http://lodash.com/docs#lateBind)
* Removed first argument falsey checks from methods
* Removed support for custom `clone`, `isEqual`, `toArray` methods from<br>
`_.clone`, `_.isEqual`, and `_.toArray`
* Fixed `_.template` regression
* Optimized build process to detect and remove more unused variables
#### Changes ####
### <sup>v0.4.0</sup>
* 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`
* Added `-d`/`--debug`, `-m/--minify`, `minus`, `plus`, `settings`, and `template` build options
* Added `_.isPlainObject` and `_.lateBind`
* Allowed `_.sortedIndex` to accept a property name as the `callback` argument
* Ensured methods accept a `thisArg` of `null`
* Fixed the `iife` build option to accept more values
* Made `_.times` return an array of `callback` results
* Simplified `_.max`, `_.min`, and `_.reduceRight`
The full changelog is available [here](https://github.com/bestiejs/lodash/wiki/Changelog).
## BestieJS
Lo-Dash 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.
Lo-Dash 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.
## Author

1651
build.js

File diff suppressed because it is too large Load Diff

View File

@@ -9,23 +9,19 @@
spawn = require('child_process').spawn;
/** The directory that is the base of the repository */
var basePath = path.join(__dirname, '../');
var basePath = fs.realpathSync(path.join(__dirname, '..'));
/** The directory where the Closure Compiler is located */
var closurePath = path.join(basePath, 'vendor', 'closure-compiler', 'compiler.jar');
/** The distribution directory */
var distPath = path.join(basePath, 'dist');
/** Load other modules */
var preprocess = require(path.join(__dirname, 'pre-compile')),
postprocess = require(path.join(__dirname, 'post-compile')),
uglifyJS = require(path.join(basePath, 'vendor', 'uglifyjs', 'uglify-js'));
var preprocess = require('./pre-compile'),
postprocess = require('./post-compile'),
uglifyJS = require('../vendor/uglifyjs/uglify-js');
/** Closure Compiler command-line options */
var closureOptions = [
'--compilation_level=ADVANCED_OPTIMIZATIONS',
'--language_in=ECMASCRIPT5_STRICT',
'--warning_level=QUIET'
];
@@ -38,12 +34,40 @@
* The exposed `minify` function minifies a given Lo-Dash `source` and invokes
* the `onComplete` callback when finished.
*
* @param {String} source The source to minify.
* @param {String} workingName The name to give temporary files creates during the minification process.
* @param {Function} onComplete A function called when minification has completed.
* @param {Array|String} [source=''] The source to minify or array of commands.
* @param {Object} [options={}] The options object.
*/
function minify(source, workingName, onComplete) {
new Minify(source, workingName, onComplete);
function minify(source, options) {
source || (source = '');
options || (options = {});
// juggle arguments
if (Array.isArray(source)) {
// convert commands to an options object
options = source;
var filePath = options[options.length - 1],
isSilent = options.indexOf('-s') > -1 || options.indexOf('--silent') > -1,
isTemplate = options.indexOf('-t') > -1 || options.indexOf('--template') > -1,
outputPath = path.join(path.dirname(filePath), path.basename(filePath, '.js') + '.min.js');
outputPath = options.reduce(function(result, value, index) {
if (/-o|--output/.test(value)) {
result = options[index + 1];
result = path.join(fs.realpathSync(path.dirname(result)), path.basename(result));
}
return result;
}, outputPath);
options = {
'isSilent': isSilent,
'isTemplate': isTemplate,
'outputPath': outputPath
};
source = fs.readFileSync(filePath, 'utf8');
}
new Minify(source, options);
}
/**
@@ -52,24 +76,27 @@
* @private
* @constructor
* @param {String} source The source to minify.
* @param {String} workingName The name to give temporary files creates during the minification process.
* @param {Function} onComplete A function called when minification has completed.
* @param {Object} options The options object.
*/
function Minify(source, workingName, onComplete) {
// create the destination directory if it doesn't exist
if (!fs.existsSync(distPath)) {
// avoid errors when called as a npm executable
try {
fs.mkdirSync(distPath);
} catch(e) { }
function Minify(source, options) {
// juggle arguments
if (typeof source == 'object' && source) {
options = source || options;
source = options.source || '';
}
this.compiled = {};
this.hybrid = {};
this.uglified = {};
this.onComplete = onComplete;
this.source = source = preprocess(source);
this.workingName = workingName;
this.isSilent = !!options.isSilent;
this.isTemplate = !!options.isTemplate;
this.outputPath = options.outputPath;
source = preprocess(source, options);
this.source = source;
this.onComplete = options.onComplete || function(source) {
fs.writeFileSync(this.outputPath, source, 'utf8');
};
// begin the minification process
closureCompile.call(this, source, onClosureCompile.bind(this));
@@ -87,10 +114,19 @@
* @param {Function} callback The function to call once the process completes.
*/
function closureCompile(source, message, callback) {
var options = closureOptions.slice();
// use simple optimizations when minifying template files
if (this.isTemplate) {
options = options.map(function(value) {
return value.replace(/^(compilation_level)=.+$/, '$1=SIMPLE_OPTIMIZATIONS');
});
}
// the standard error stream, standard output stream, and Closure Compiler process
var error = '',
output = '',
compiler = spawn('java', ['-jar', closurePath].concat(closureOptions));
compiler = spawn('java', ['-jar', closurePath].concat(options));
// juggle arguments
if (typeof message == 'function') {
@@ -98,10 +134,12 @@
message = null;
}
console.log(message == null
? 'Compressing ' + this.workingName + ' using the Closure Compiler...'
: message
);
if (!this.isSilent) {
console.log(message == null
? 'Compressing ' + path.basename(this.outputPath, '.js') + ' using the Closure Compiler...'
: message
);
}
compiler.stdout.on('data', function(data) {
// append the data to the output stream
@@ -149,10 +187,12 @@
message = null;
}
console.log(message == null
? 'Compressing ' + this.workingName + ' using UglifyJS...'
: message
);
if (!this.isSilent) {
console.log(message == null
? 'Compressing ' + path.basename(this.outputPath, '.js') + ' using UglifyJS...'
: message
);
}
try {
result = ugly.gen_code(
@@ -203,9 +243,12 @@
if (exception) {
throw exception;
}
if (!this.isSilent) {
console.log('Done. Size: %d bytes.', result.length);
}
// store the gzipped result and report the size
this.compiled.gzip = result;
console.log('Done. Size: %d bytes.', result.length);
// next, minify the source using only UglifyJS
uglify.call(this, this.source, onUglify.bind(this));
@@ -238,11 +281,13 @@
if (exception) {
throw exception;
}
var message = 'Compressing ' + this.workingName + ' using hybrid minification...';
if (!this.isSilent) {
console.log('Done. Size: %d bytes.', result.length);
}
var message = 'Compressing ' + path.basename(this.outputPath, '.js') + ' using hybrid minification...';
// store the gzipped result and report the size
this.uglified.gzip = result;
console.log('Done. Size: %d bytes.', result.length);
// next, minify the Closure Compiler minified source using UglifyJS
uglify.call(this, this.compiled.source, message, onHybrid.bind(this));
@@ -275,9 +320,11 @@
if (exception) {
throw exception;
}
if (!this.isSilent) {
console.log('Done. Size: %d bytes.', result.length);
}
// store the gzipped result and report the size
this.hybrid.gzip = result;
console.log('Done. Size: %d bytes.', result.length);
// finish by choosing the smallest compressed file
onComplete.call(this);
@@ -291,24 +338,8 @@
function onComplete() {
var compiled = this.compiled,
hybrid = this.hybrid,
name = this.workingName,
uglified = this.uglified;
// 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 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)
var min = Math.min(compiled.gzip.length, hybrid.gzip.length, uglified.gzip.length);
@@ -334,14 +365,11 @@
// was invoked directly (e.g. `node minify.js source.js`) and write to
// `<filename>.min.js`
(function() {
var filePath = process.argv[2],
dirPath = path.dirname(filePath),
source = fs.readFileSync(filePath, 'utf8'),
workingName = path.basename(filePath, '.js') + '.min';
minify(source, workingName, function(result) {
fs.writeFileSync(path.join(dirPath, workingName + '.js'), result, 'utf8');
});
var options = process.argv;
if (options.length < 3) {
return;
}
minify(options);
}());
}
}());

View File

@@ -6,11 +6,15 @@
var fs = require('fs');
/** The minimal license/copyright template */
var licenseTemplate =
'/*!\n' +
' Lo-Dash @VERSION lodash.com/license\n' +
' Underscore.js 1.3.3 github.com/documentcloud/underscore/blob/master/LICENSE\n' +
'*/';
var licenseTemplate = {
'lodash':
'/*!\n' +
' Lo-Dash @VERSION lodash.com/license\n' +
' Underscore.js 1.4.0 underscorejs.org/LICENSE\n' +
'*/',
'underscore':
'/*! Underscore.js @VERSION underscorejs.org/LICENSE */'
};
/*--------------------------------------------------------------------------*/
@@ -22,29 +26,29 @@
* @returns {String} Returns the processed source.
*/
function postprocess(source) {
// exit early if snippet isn't found
// move vars exposed by Closure Compiler into the IIFE
source = source.replace(/^((?:(['"])use strict\2;)?(?:var (?:[a-z]+=(?:!0|!1|null)[,;])+)?)([\s\S]*?function[^)]+\){)/, '$3$1');
// unescape properties (i.e. foo["bar"] => foo.bar)
source = source.replace(/(\w)\["([^."]+)"\]/g, function(match, left, right) {
return /\W/.test(right) ? match : (left + '.' + right);
});
// correct AMD module definition for AMD build optimizers
source = source.replace(/("function")\s*==\s*(typeof define)\s*&&\s*\(?\s*("object")\s*==\s*(typeof define\.amd)\s*&&\s*(define\.amd)\s*\)?/, '$2==$1&&$4==$3&&$5');
// add trailing semicolon
if (source) {
source = source.replace(/[\s;]*$/, ';');
}
// exit early if version snippet isn't found
var snippet = /VERSION\s*[=:]\s*([\'"])(.*?)\1/.exec(source);
if (!snippet) {
return source;
}
// set the version
var license = licenseTemplate.replace('@VERSION', snippet[2]);
// move vars exposed by Closure Compiler into the IIFE
source = source.replace(/^([^(\n]+)\s*(\(function[^)]+\){)/, '$2$1');
// unescape properties (i.e. foo["bar"] => foo.bar)
source = source.replace(/(\w)\["([^."]+)"\]/g, '$1.$2');
// correct AMD module definition for AMD build optimizers
source = source.replace(/("function")==(typeof define)&&\(?("object")==(typeof define\.amd)(&&define\.amd)\)?/, '$2==$1&&$4==$3$5');
// add license
source = license + '\n;' + source;
// add trailing semicolon
return source.replace(/[\s;]*$/, ';');
return licenseTemplate[/call\(this\);?$/.test(source) ? 'underscore' : 'lodash']
.replace('@VERSION', snippet[2]) + '\n;' + source;
}
/*--------------------------------------------------------------------------*/
@@ -52,13 +56,20 @@
// expose `postprocess`
if (module != require.main) {
module.exports = postprocess;
} else {
}
else {
// 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() {
var source = fs.readFileSync(process.argv[2], 'utf8');
fs.writeFileSync(process.argv[2], postprocess(source), 'utf8');
var options = process.argv;
if (options.length < 3) {
return;
}
var filePath = options[options.length - 1],
source = fs.readFileSync(filePath, 'utf8');
fs.writeFileSync(filePath, postprocess(source), 'utf8');
}());
}
}());

View File

@@ -7,43 +7,69 @@
/** Used to minify variables embedded in compiled strings */
var compiledVars = [
'accumulator',
'args',
'arrayClass',
'bind',
'argsIndex',
'argsLength',
'callback',
'className',
'collection',
'compareAscending',
'concat',
'createCallback',
'ctor',
'funcClass',
'funcs',
'hasOwnProperty',
'identity',
'index',
'isFunc',
'iteratee',
'iterateeIndex',
'iteratorBind',
'length',
'methodName',
'nativeKeys',
'noaccum',
'object',
'objectTypes',
'ownIndex',
'ownProps',
'prop',
'propertyIsEnumerable',
'propIndex',
'props',
'property',
'propertyIsEnumerable',
'result',
'skipProto',
'slice',
'stringClass',
'target',
'thisArg',
'toString',
'value'
'undefined',
'value',
// lesser used variables
'accumulator',
'args',
'arrayLikeClasses',
'ArrayProto',
'bind',
'callee',
'className',
'compareAscending',
'forIn',
'found',
'funcs',
'indexOf',
'indicator',
'isArguments',
'isArr',
'isArray',
'isFunc',
'isFunction',
'isPlainObject',
'methodName',
'noaccum',
'noop',
'objectClass',
'objectTypes',
'pass',
'properties',
'property',
'propsLength',
'source',
'stackA',
'stackB',
'stackLength',
'target'
];
/** Used to minify `compileIterator` option properties */
@@ -53,7 +79,6 @@
'arrayBranch',
'beforeLoop',
'bottom',
'exit',
'firstArg',
'hasDontEnumBug',
'inLoop',
@@ -61,6 +86,7 @@
'isKeysFast',
'object',
'objectBranch',
'noArgsEnum',
'noCharByIndex',
'shadowed',
'top',
@@ -70,12 +96,15 @@
/** Used to minify variables and string values to a single character */
var minNames = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
minNames.push.apply(minNames, minNames.map(function(value) {
return value + value;
}));
/** Used to protect the specified properties from getting minified */
var propWhitelist = [
'_',
'_chain',
'_wrapped',
'__chain__',
'__wrapped__',
'after',
'all',
'amd',
@@ -90,6 +119,7 @@
'compact',
'compose',
'contains',
'countBy',
'criteria',
'debounce',
'defaults',
@@ -97,12 +127,13 @@
'delay',
'detect',
'difference',
'drop',
'each',
'environment',
'escape',
'escape',
'evaluate',
'every',
'exports',
'extend',
'filter',
'find',
@@ -119,11 +150,13 @@
'head',
'identity',
'include',
'index',
'indexOf',
'initial',
'inject',
'interpolate',
'intersection',
'invert',
'invoke',
'isArguments',
'isArray',
@@ -140,24 +173,31 @@
'isNull',
'isNumber',
'isObject',
'isPlainObject',
'isRegExp',
'isString',
'isUndefined',
'keys',
'last',
'lastIndexOf',
'lateBind',
'map',
'max',
'memoize',
'merge',
'methods',
'min',
'mixin',
'noConflict',
'object',
'omit',
'once',
'opera',
'pairs',
'partial',
'pick',
'pluck',
'random',
'range',
'reduce',
'reduceRight',
@@ -176,10 +216,12 @@
'take',
'tap',
'template',
'templates',
'templateSettings',
'throttle',
'times',
'toArray',
'unescape',
'union',
'uniq',
'unique',
@@ -188,10 +230,14 @@
'values',
'variable',
'VERSION',
'where',
'without',
'wrap',
'zip',
'zipObject'
// properties used by underscore.js
'_chain',
'_wrapped'
];
/*--------------------------------------------------------------------------*/
@@ -199,25 +245,33 @@
/**
* Pre-process a given Lo-Dash `source`, preparing it for minification.
*
* @param {String} source The source to process.
* @param {String} [source=''] The source to process.
* @param {Object} [options={}] The options object.
* @returns {String} Returns the processed source.
*/
function preprocess(source) {
// remove copyright to add later in post-compile.js
source = source.replace(/\/\*![\s\S]+?\*\//, '');
function preprocess(source, options) {
source || (source = '');
options || (options = {});
// remove unrecognized JSDoc tags so Closure Compiler won't complain
source = source.replace(/@(?:alias|category)\b.*/g, '');
if (options.isTemplate) {
return source;
}
// remove copyright to add later in post-compile.js
source = source.replace(/\/\*![\s\S]+?\*\//, '');
// 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');
source = source.replace(/__e *= *_\['escape']/g, '__e=_.escape');
// remove brackets from `_.escape()` in underscore.js `_.template`
source = source.replace(/_\['escape'\]\(__t'/g, '_.escape(__t');
// remove brackets from `collection.indexOf` in `_.contains`
source = source.replace("collection['indexOf'](target)", 'collection.indexOf(target)');
@@ -228,13 +282,16 @@
// remove whitespace from string literals
source = source.replace(/'(?:(?=(\\?))\1.)*?'/g, function(string) {
// 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 string.replace(/\[object |delete |else |function | in |return\s+[\w']|throw |typeof |use strict|var |@ |'\\n'|\\\\n|\\n|\s+/g, function(match) {
return match == false || match == '\\n' ? '' : match;
});
});
// remove whitespace from `_.template` related regexpes
source = source.replace(/(?:reDelimiterCode\w+|reEmptyString\w+|reInsertVariable) *=.+/g, function(match) {
// add newline to `+"__p+='"` in underscore.js `_.template`
source = source.replace(/\+"__p\+='"/g, '+"\\n__p+=\'"');
// remove whitespace from `_.template` related regexes
source = source.replace(/(?:reEmptyString\w+|reInsertVariable) *=.+/g, function(match) {
return match.replace(/ |\\n/g, '');
});
@@ -247,35 +304,59 @@
.replace("') {\\n'", "'){'")
// remove `useSourceURL` variable
source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *try *\{(?:\s*\/\/.*\n)* *var useSourceURL[\s\S]+?catch[^}]+}\n/, '');
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[^}]+}/, '');
source = source.replace(/(?:\s*\/\/.*\n)* *var sourceURL[^;]+;|\+ *sourceURL/g, '');
// minify internal properties used by `_.sortBy`
// minify internal properties used by 'compareAscending', `_.merge`, and `_.sortBy`
(function() {
var properties = ['criteria', 'value'],
snippets = source.match(/( +)(?:function compareAscending|var sortBy)\b[\s\S]+?\n\1}/g);
var properties = ['criteria', 'index', 'value'],
snippets = source.match(/( +)(?:function compareAscending|var merge|var sortBy)\b[\s\S]+?\n\1}/g);
if (!snippets) {
return;
}
snippets.forEach(function(snippet) {
var modified = snippet,
isSortBy = /var sortBy\b/.test(modified),
isCompilable = /(?:var merge|var sortBy)\b/.test(modified),
isInlined = !/\bcreateIterator\b/.test(modified);
// minify properties
properties.forEach(function(property, 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] + "':");
var reBracketProp = RegExp("\\['(" + property + ")'\\]", 'g'),
reDotProp = RegExp('\\.' + property + '\\b', 'g'),
rePropColon = RegExp("([^?\\s])\\s*([\"'])?\\b" + property + "\\2 *:", 'g');
if (isCompilable) {
// add quotes around properties in the inlined `_.merge` and `_.sortBy`
// of the mobile build so Closure Compiler won't mung them
if (isInlined) {
modified = modified
.replace(reBracketProp, "['" + minNames[index] + "']")
.replace(reDotProp, "['" + minNames[index] + "']")
.replace(rePropColon, "$1'" + minNames[index] + "':");
}
else {
modified = modified
.replace(reBracketProp, '.' + minNames[index])
.replace(reDotProp, '.' + minNames[index])
.replace(rePropColon, '$1' + minNames[index] + ':');
}
}
else {
modified = modified
.replace(reBracketProp, "['" + minNames[index] + "']")
.replace(reDotProp, '.' + minNames[index])
.replace(rePropColon, "$1'" + minNames[index] + "':")
// correct `value.source` in regexp branch of `_.clone`
if (property == 'source') {
modified = modified.replace("value['" + minNames[index] + "']", "value['source']");
}
}
modified = modified.replace(RegExp('\\b' + property + '\\b', 'g'), minNames[index]);
});
// replace with modified snippet
source = source.replace(snippet, modified);
});
@@ -340,7 +421,7 @@
else {
// minify property name strings
modified = modified.replace(RegExp("'" + property + "'", 'g'), "'" + minNames[index] + "'");
// minify property names in regexps and accessors
// minify property names in regexes and accessors
if (isCreateIterator) {
modified = modified.replace(RegExp('([\\.|/])' + property + '\\b' , 'g'), '$1' + minNames[index]);
}
@@ -366,8 +447,17 @@
// was invoked directly (e.g. `node pre-compile.js source.js`) and write to
// the same file
(function() {
var source = fs.readFileSync(process.argv[2], 'utf8');
fs.writeFileSync(process.argv[2], preprocess(source), 'utf8');
var options = process.argv;
if (options.length < 3) {
return;
}
var filePath = options[options.length - 1],
isTemplate = options.indexOf('-t') > -1 || options.indexOf('--template') > -1,
source = fs.readFileSync(filePath, 'utf8');
fs.writeFileSync(filePath, preprocess(source, {
'isTemplate': isTemplate
}), 'utf8');
}());
}
}());

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

1
index.js Executable file
View File

@@ -0,0 +1 @@
module.exports = require('./lodash');

5253
lodash.js

File diff suppressed because it is too large Load Diff

70
lodash.min.js vendored
View File

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

35
lodash.underscore.min.js vendored Normal file
View File

@@ -0,0 +1,35 @@
/*!
Lo-Dash 0.8.0 lodash.com/license
Underscore.js 1.4.0 underscorejs.org/LICENSE
*/
;(function(e,t){function s(e,t,r){var s,t=T(t,r),o=e.length,r=-1;if(o===+o){for(;++r<o;)if(s=e[r],t(s,r,e))return n}else for(r in e)if(st.call(e,r)&&(s=e[r],t(s,r,e)))return n;return i}function o(e,t,n,r){var s,o,u=n,a=3>arguments.length,t=T(t,r),f=e.length;s=-1;if(f===+f)for(a&&(u=e[++s]);++s<f;)o=e[s],u=t(u,o,s,e);else for(s in e)st.call(e,s)&&(o=e[s],u=a?(a=i,o):t(u,o,s,e));return u}function u(e,t){var n,r,i,s=e.length;n=-1;if(s===+s)for(i=Array(s);++n<s;)r=e[n],i[n]=r[t];else for(n in i=[],e)st
.call(e,n)&&(r=e[n],i.push(r[t]));return i}function a(e,t,n){var r,i,t=T(t,n),s=e.length,n=-1;if(s===+s)for(i=Array(s);++n<s;)r=e[n],i[n]=t(r,n,e);else for(n in i=[],e)st.call(e,n)&&(r=e[n],i.push(t(r,n,e)));return i}function f(e,t,n){var r,t=T(t,n),i=e.length,n=-1;if(i===+i)for(;++n<i;)r=e[n],t(r,n,e);else for(n in e)st.call(e,n)&&(r=e[n],t(r,n,e));return e}function l(e,t,n){var r,t=T(t,n),i=e.length,n=-1;if(i===+i){for(;++n<i;)if(r=e[n],t(r,n,e))return r}else for(n in e)if(st.call(e,n)&&(r=e[n]
,t(r,n,e)))return r}function c(e,t,n){var r,i=[],t=T(t,n),s=e.length,n=-1;if(s===+s)for(;++n<s;)r=e[n],t(r,n,e)&&i.push(r);else for(n in e)st.call(e,n)&&(r=e[n],t(r,n,e)&&i.push(r));return i}function h(e,t,r){var s,t=T(t,r),o=e.length,r=-1;if(o===+o){for(;++r<o;)if(s=e[r],!t(s,r,e))return i}else for(r in e)if(st.call(e,r)&&(s=e[r],!t(s,r,e)))return i;return n}function p(e,t){var r,s,o=e.length;r=-1;if(o===+o){if(ft.call(e)==Tt)return-1<e.indexOf(t);for(;++r<o;)if(s=e[r],s===t)return n}else for(r in
e)if(st.call(e,r)&&(s=e[r],s===t))return n;return i}function d(e){var t,n,r=[];for(t in e)st.call(e,t)&&(n=e[t],r.push(n));return r}function v(e){var t,n,r=[];for(t in e)n=e[t],A(n)&&r.push(t);return r.sort(),r}function m(e,t,n){var r,t=T(t,n);for(r in e)n=e[r],t(n,r,e);return e}function g(e){for(var t,n,r=e,i=1,s=arguments.length;i<s;i++)if(r=arguments[i])for(t in r)n=r[t],e[t]=n;return e}function y(e){var t,n=[];if(!e||!Lt[typeof e])throw TypeError();for(t in e)st.call(e,t)&&n.push(t);return n}
function b(e){var t,n,r={};for(t in e)st.call(e,t)&&(n=e[t],r[n]=t);return r}function w(e){if(e&&e.__wrapped__)return e;if(!(this instanceof w))return new w(e);this.__wrapped__=e}function E(e,t){return function(n){return-1<H(e,n,t||0)}}function S(e,n){var r=e.b,i=n.b,e=e.a,n=n.a;if(e!==n){if(e>n||e===t)return 1;if(e<n||n===t)return-1}return r<i?-1:1}function x(e,t,n){function r(){var i=arguments,s=t;return n.length&&(i=i.length?n.concat(at.call(i)):n),this instanceof r?(k.prototype=e.prototype,s=new
k,(i=e.apply(s,i))&&Lt[typeof i]?i:s):e.apply(s,i)}return r}function T(e,n){return e?"function"!=typeof e?function(t){return t[e]}:n!==t?function(t,r,i){return e.call(n,t,r,i)}:e:R}function N(e){return"\\"+At[e]}function C(e){return Mt[e]}function k(){}function L(e){return _t[e]}function A(e){return"function"==typeof e}function O(e){var t=i;if(!e||"object"!=typeof e||isArguments(e))return t;var n=e.constructor;return!A(n)||n instanceof n?(m(e,function(e,n){t=n}),t===i||st.call(e,t)):t}function M(
e,t,s,o){if(e==r||t==r)return e===t;if(e===t)return 0!==e||1/e==1/t;if(Lt[typeof e]||Lt[typeof t])e=e.__wrapped__||e,t=t.__wrapped__||t;var u=ft.call(e);if(u!=ft.call(t))return i;switch(u){case bt:case wt:return+e==+t;case Et:return e!=+e?t!=+t:0==e?1/e==1/t:e==+t;case xt:case Tt:return e==t+""}var a=Ot(e);if(!a&&u!=St)return i;s||(s=[]),o||(o=[]);for(u=s.length;u--;)if(s[u]==e)return o[u]==t;var f=n,u=0;s.push(e),o.push(t);if(a){u=e.length;if(f=u==t.length)for(;u--&&(f=M(e[u],t[u],s,o)););return f
}a=e.constructor,f=t.constructor;if(a!=f&&(!A(a)||!(a instanceof a&&A(f)&&f instanceof f)))return i;for(var l in e)if(st.call(e,l)&&(u++,!st.call(t,l)||!M(e[l],t[l],s,o)))return i;for(l in t)if(st.call(t,l)&&!(u--))return i;return n}function _(e,t,n,r){var s=e.length,o=3>arguments.length;if(s!==+s)var u=Dt(e),s=u.length;return f(e,function(a,f,l){f=u?u[--s]:--s,n=o?(o=i,e[f]):t.call(r,n,e[f],f,l)}),n}function D(e,t,n){return t==r||n?e[0]:at.call(e,0,t)}function P(e,t){for(var n,r=-1,i=e.length,s=
[];++r<i;)n=e[r],Ot(n)?ot.apply(s,t?n:P(n)):s.push(n);return s}function H(e,t,n){var r=-1,i=e.length;if(n){if("number"!=typeof n)return r=F(e,t),e[r]===t?r:-1;r=(0>n?mt(0,i+n):n)-1}for(;++r<i;)if(e[r]===t)return r;return-1}function B(e,t,n){for(var r=-Infinity,i=-1,s=e?e.length:0,o=r,t=T(t,n);++i<s;)n=t(e[i],i,e),n>r&&(r=n,o=e[i]);return o}function j(e,t,n){return at.call(e,t==r||n?1:t)}function F(e,t,n,r){for(var i=0,s=e.length,n=T(n,r),t=n(t);i<s;)r=i+s>>>1,n(e[r])<t?i=r+1:s=r;return i}function I
(e,t,n,r){var s=-1,o=e.length,u=[],a=[];"function"==typeof t&&(r=n,n=t,t=i);for(n=T(n,r);++s<o;)if(r=n(e[s],s,e),t?!s||a[a.length-1]!==r:0>H(a,r))a.push(r),u.push(e[s]);return u}function q(e,t){return kt||lt&&2<arguments.length?lt.call.apply(lt,arguments):x(e,t,at.call(arguments,2))}function R(e){return e}function U(e){f(v(e),function(t){var r=w[t]=e[t];w.prototype[t]=function(){var e=[this.__wrapped__];return arguments.length&&ot.apply(e,arguments),e=r.apply(w,e),this.__chain__&&(e=new w(e),e.__chain__=
n),e}})}var n=!0,r=null,i=!1,z="object"==typeof exports&&exports&&("object"==typeof global&&global&&global==global.global&&(e=global),exports),W=Array.prototype,X=Object.prototype,V=0,$=e._,J=/[-?+=!~*%&^<>|{(\/]|\[\D|\b(?:delete|in|instanceof|new|typeof|void)\b/,K=/&(?:amp|lt|gt|quot|#x27);/g,Q=/\b__p\+='';/g,G=/\b(__p\+=)''\+/g,Y=/(__e\(.*?\)|\b__t\))\+'';/g,Z=/(?:__e|__t=)\(\s*(?![\d\s"']|this\.)/g,et=RegExp("^"+(X.valueOf+"").replace(/[.*+?^=!:${}()|[\]\/\\]/g,"\\$&").replace(/valueOf|for [^\]]+/g
,".+?")+"$"),tt=/($^)/,nt=/[&<>"']/g,rt=/['\n\r\t\u2028\u2029\\]/g,it=W.concat,st=X.hasOwnProperty,ot=W.push,ut=X.propertyIsEnumerable,at=W.slice,ft=X.toString,lt=et.test(lt=at.bind)&&lt,ct=Math.floor,ht=et.test(ht=Object.getPrototypeOf)&&ht,pt=et.test(pt=Array.isArray)&&pt,dt=e.isFinite,vt=et.test(vt=Object.keys)&&vt,mt=Math.max,gt=Math.min,yt=Math.random,bt="[object Boolean]",wt="[object Date]",Et="[object Number]",St="[object Object]",xt="[object RegExp]",Tt="[object String]",Nt=e.clearTimeout
,Ct=e.setTimeout,kt=lt&&/\n|Opera/.test(lt+ft.call(e.opera)),Lt={"boolean":i,"function":n,object:n,number:i,string:i,"undefined":i,unknown:n},At={"\\":"\\","'":"'","\n":"n","\r":"r"," ":"t","\u2028":"u2028","\u2029":"u2029"};w.templateSettings={escape:/<%-([\s\S]+?)%>/g,evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,variable:""},w.isArguments=function(e){return"[object Arguments]"==ft.call(e)},w.isArguments(arguments)||(w.isArguments=function(e){return!!e&&!!st.call(e,"callee")});var Ot=
pt||function(e){return"[object Array]"==ft.call(e)};A(/x/)&&(A=function(e){return"[object Function]"==ft.call(e)});var X=ht?function(e){if(!e||"object"!=typeof e)return i;var t=e.valueOf,n="function"==typeof t&&(n=ht(t))&&ht(n);return n?e==n||ht(e)==n&&!isArguments(e):O(e)}:O,Mt={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;"},_t=b(Mt),Dt=vt?function(e){return"function"==typeof e&&ut.call(e,"prototype")?y(e):vt(e)}:y;w.VERSION="0.8.0",w.after=function(e,t){return 1>e?t():function(){if(1>--
e)return t.apply(this,arguments)}},w.bind=q,w.bindAll=function(e){var t,n=e,r=e,i=arguments,s=i.length;if(1<s){for(t=1;t<s;t++)r[i[t]]=q(r[i[t]],r);return r}for(t in n)A(r[t])&&(r[t]=q(r[t],r));return r},w.chain=function(e){return e=new w(e),e.__chain__=n,e},w.clone=function(e){return e&&Lt[typeof e]?Ot(e)?at.call(e):g({},e):e},w.compact=function(e){for(var t=-1,n=e.length,r=[];++t<n;)e[t]&&r.push(e[t]);return r},w.compose=function(){var e=arguments;return function(){for(var t=arguments,n=e.length
;n--;)t=[e[n].apply(this,t)];return t[0]}},w.contains=p,w.countBy=function(e,t,n){var r,i={},t=T(t,n),s=e.length,n=-1;if(s===+s)for(;++n<s;)r=e[n],r=t(r,n,e),st.call(i,r)?i[r]++:i[r]=1;else for(n in e)st.call(e,n)&&(r=e[n],r=t(r,n,e),st.call(i,r)?i[r]++:i[r]=1);return i},w.debounce=function(e,t,n){function i(){a=r,n||(o=e.apply(u,s))}var s,o,u,a;return function(){var r=n&&!a;return s=arguments,u=this,Nt(a),a=Ct(i,t),r&&(o=e.apply(u,s)),o}},w.defaults=function(e){for(var t,n,i=e,s=1,o=arguments.length
;s<o;s++)if(i=arguments[s])for(t in i)n=i[t],e[t]==r&&(e[t]=n);return e},w.defer=function(e){var n=at.call(arguments,1);return Ct(function(){return e.apply(t,n)},1)},w.delay=function(e,n){var r=at.call(arguments,2);return Ct(function(){return e.apply(t,r)},n)},w.difference=function(e){for(var t=-1,n=e.length,r=it.apply(W,arguments),r=E(r,n),i=[];++t<n;)r(e[t])||i.push(e[t]);return i},w.escape=function(e){return e==r?"":(e+"").replace(nt,C)},w.every=h,w.extend=g,w.filter=c,w.find=l,w.first=D,w.flatten=
P,w.forEach=f,w.forIn=m,w.forOwn=function(e,t,n){var r,t=T(t,n);for(r in e)st.call(e,r)&&(n=e[r],t(n,r,e));return e},w.functions=v,w.groupBy=function(e,t,n){var r,i={},t=T(t,n),s=e.length,n=-1;if(s===+s)for(;++n<s;){r=e[n];var o=t(r,n,e);(st.call(i,o)?i[o]:i[o]=[]).push(r)}else for(n in e)st.call(e,n)&&(r=e[n],o=t(r,n,e),(st.call(i,o)?i[o]:i[o]=[]).push(r));return i},w.has=function(e,t){return st.call(e,t)},w.identity=R,w.indexOf=H,w.initial=function(e,t,n){return at.call(e,0,-(t==r||n?1:t))},w.intersection=
function(e){var t,n=arguments.length,r=[],i=-1,s=e.length,o=[];e:for(;++i<s;)if(t=e[i],0>H(o,t)){for(var u=1;u<n;u++)if(!(r[u]||(r[u]=E(arguments[u])))(t))continue e;o.push(t)}return o},w.invert=b,w.invoke=function(e,t){var n,r,i=e,s,o=at.call(arguments,2),u="function"==typeof t,a=i.length;n=-1;if(a===+a)for(s=Array(a);++n<a;)r=i[n],s[n]=(u?t:r[t]).apply(r,o);else for(n in s=[],i)st.call(i,n)&&(r=i[n],s.push((u?t:r[t]).apply(r,o)));return s},w.isArray=Ot,w.isBoolean=function(e){return e===n||e===
i||ft.call(e)==bt},w.isDate=function(e){return ft.call(e)==wt},w.isElement=function(e){return e?1===e.nodeType:i},w.isEmpty=function(e){var t;if(!e)return n;var r=ft.call(e),s=e.length;if(Ot(e)||r==Tt||r==St&&s===+s&&A(e.splice))return!s;for(t in e)if(st.call(e,t))return i;return n},w.isEqual=M,w.isFinite=function(e){return dt(e)&&ft.call(e)==Et},w.isFunction=A,w.isNaN=function(e){return ft.call(e)==Et&&e!=+e},w.isNull=function(e){return e===r},w.isNumber=function(e){return ft.call(e)==Et},w.isObject=
function(e){return e?Lt[typeof e]:i},w.isPlainObject=X,w.isRegExp=function(e){return ft.call(e)==xt},w.isString=function(e){return ft.call(e)==Tt},w.isUndefined=function(e){return e===t},w.keys=Dt,w.last=function(e,t,n){var i=e.length;return t==r||n?e[i-1]:at.call(e,-t||i)},w.lastIndexOf=function(e,t,n){var r=e.length;for(n&&"number"==typeof n&&(r=(0>n?mt(0,r+n):gt(n,r-1))+1);r--;)if(e[r]===t)return r;return-1},w.map=a,w.max=B,w.memoize=function(e,t){var n={};return function(){var r=t?t.apply(this
,arguments):arguments[0];return st.call(n,r)?n[r]:n[r]=e.apply(this,arguments)}},w.min=function(e,t,n){for(var r=Infinity,i=-1,s=e?e.length:0,o=r,t=T(t,n);++i<s;)n=t(e[i],i,e),n<r&&(r=n,o=e[i]);return o},w.mixin=U,w.noConflict=function(){return e._=$,this},w.object=function(e,t){for(var n=-1,r=e.length,i={};++n<r;)t?i[e[n]]=t[n]:i[e[n][0]]=e[n][1];return i},w.omit=function(e,t,n){var r,i,s=e,o={},u="function"==typeof t;if(u)t=T(t,n);else var a=it.apply(W,arguments);for(r in s)if(i=s[r],u?!t(i,r,e
):0>H(a,r))o[r]=i;return o},w.once=function(e){var t,s=i;return function(){return s?t:(s=n,t=e.apply(this,arguments),e=r,t)}},w.pairs=function(e){var t,n,r=[];for(t in e)st.call(e,t)&&(n=e[t],r.push([t,n]));return r},w.pick=function(e,t,n){var r,i,s=e,o={};if("function"!=typeof t){var s=it.apply(W,arguments),u=s.length;for(r=1;r<u;r++)i=s[r],i in e&&(o[i]=e[i])}else for(r in t=T(t,n),s)i=s[r],t(i,r,e)&&(o[r]=i);return o},w.pluck=u,w.random=function(e,t){return e==r&&t==r&&(t=1),e=+e||0,t==r&&(t=e
,e=0),e+ct(yt()*((+t||0)-e+1))},w.range=function(e,t,n){e=+e||0,n=+n||1,t==r&&(t=e,e=0);for(var i=-1,t=mt(0,Math.ceil((t-e)/n)),s=Array(t);++i<t;)s[i]=e,e+=n;return s},w.reduce=o,w.reduceRight=_,w.reject=function(e,t,n){var r,i=[],t=T(t,n),s=e.length,n=-1;if(s===+s)for(;++n<s;)r=e[n],!t(r,n,e)&&i.push(r);else for(n in e)st.call(e,n)&&(r=e[n],!t(r,n,e)&&i.push(r));return i},w.rest=j,w.result=function(e,t){var n=e?e[t]:r;return A(n)?e[t]():n},w.shuffle=function(e){for(var t,n=-1,r=e.length,i=Array(
r);++n<r;)t=ct(yt()*(n+1)),i[n]=i[t],i[t]=e[n];return i},w.size=function(e){var t=e?e.length:0;return t===+t?t:Dt(e).length},w.some=s,w.sortBy=function(e,t,n){var r,i,t=T(t,n),s=e.length,n=-1;if(s===+s)for(i=Array(s);++n<s;)r=e[n],i[n]={a:t(r,n,e),b:n,c:r};else for(n in i=[],e)st.call(e,n)&&(r=e[n],i.push({a:t(r,n,e),b:n,c:r}));i.sort(S);for(s=i.length;s--;)i[s]=i[s].c;return i},w.sortedIndex=F,w.tap=function(e,t){return t(e),e},w.template=function(e,t,n){n||(n={});var r,i,s=0,o=w.templateSettings
,u="__p += '",a=n.variable||o.variable,f=a;e.replace(RegExp((n.escape||o.escape||tt).source+"|"+(n.interpolate||o.interpolate||tt).source+"|"+(n.evaluate||o.evaluate||tt).source+"|$","g"),function(t,n,i,o,a){u+=e.slice(s,a).replace(rt,N),u+=n?"'+__e("+n+")+'":o?"';"+o+";__p+='":i?"'+((__t=("+i+"))==null?'':__t)+'":"",r||(r=o||J.test(n||i)),s=a+t.length}),u+="';",f||(a="obj",r?u="with("+a+"){"+u+"}":(n=RegExp("(\\(\\s*)"+a+"\\."+a+"\\b","g"),u=u.replace(Z,"$&"+a+".").replace(n,"$1__d"))),u=(r?u.replace
(Q,""):u).replace(G,"$1").replace(Y,"$1;"),u="function("+a+"){"+(f?"":a+"||("+a+"={});")+"var __t,__p='',__e=_.escape"+(r?",__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}":(f?"":",__d="+a+"."+a+"||"+a)+";")+u+"return __p}";try{i=Function("_","return "+u)(w)}catch(l){throw l.source=u,l}return t?i(t):(i.source=u,i)},w.throttle=function(e,t){function n(){a=new Date,u=r,s=e.apply(o,i)}var i,s,o,u,a=0;return function(){var r=new Date,f=t-(r-a);return i=arguments,o=this,0>=f?(a=
r,s=e.apply(o,i)):u||(u=Ct(n,f)),s}},w.times=function(e,t,n){for(var e=+e||0,r=-1,i=Array(e);++r<e;)i[r]=t.call(n,r);return i},w.toArray=function(e){var t=e?e.length:0;return t===+t?"string"==typeof e?e.split(""):at.call(e):d(e)},w.unescape=function(e){return e==r?"":(e+"").replace(K,L)},w.union=function(){for(var e=-1,t=it.apply(W,arguments),n=t.length,r=[];++e<n;)0>H(r,t[e])&&r.push(t[e]);return r},w.uniq=I,w.uniqueId=function(e){var t=V++;return e?e+t:t},w.values=d,w.where=function(e,t){var r,
i,s=[],o=[];m(t,function(e,t){o.push(t)});var u=o.length,a=e.length;r=-1;if(a===+a)for(;++r<a;){i=e[r];var f;f=n;for(var l=0;l<u&&(f=o[l],f=i[f]===t[f]);l++);f&&s.push(i)}else for(r in e)if(st.call(e,r)){i=e[r],f=n;for(l=0;l<u&&(f=o[l],f=i[f]===t[f]);l++);f&&s.push(i)}return s},w.without=function(e){for(var t=-1,n=e.length,r=E(arguments,1),i=[];++t<n;)r(e[t])||i.push(e[t]);return i},w.wrap=function(e,t){return function(){var n=[e];return arguments.length&&ot.apply(n,arguments),t.apply(this,n)}},w
.zip=function(e){for(var t=-1,n=B(u(arguments,"length")),r=Array(n);++t<n;)r[t]=u(arguments,t);return r},w.all=h,w.any=s,w.collect=a,w.detect=l,w.drop=j,w.each=f,w.foldl=o,w.foldr=_,w.head=D,w.include=p,w.inject=o,w.methods=v,w.select=c,w.tail=j,w.take=D,w.unique=I,U(w),w.prototype.chain=function(){return this.__chain__=n,this},w.prototype.value=function(){return this.__wrapped__},f("pop push reverse shift sort splice unshift".split(" "),function(e){var t=W[e];w.prototype[e]=function(){var e=this
.__wrapped__;return t.apply(e,arguments),this.__chain__&&(e=new w(e),e.__chain__=n),e}}),f(["concat","join","slice"],function(e){var t=W[e];w.prototype[e]=function(){var e=t.apply(this.__wrapped__,arguments);return this.__chain__&&(e=new w(e),e.__chain__=n),e}}),z?"object"==typeof module&&module&&module.exports==z?(module.exports=w)._=w:z._=w:e._=w})(this);

View File

@@ -1,9 +1,9 @@
{
"name": "lodash",
"version": "0.4.2",
"description": "A drop-in replacement for Underscore.js that delivers performance improvements, bug fixes, and additional features.",
"version": "0.8.0",
"description": "A drop-in replacement for Underscore.js delivering performance, bug fixes, and additional features.",
"homepage": "http://lodash.com",
"main": "lodash",
"main": "./lodash",
"keywords": [
"browser",
"client",
@@ -21,7 +21,7 @@
],
"author": {
"name": "John-David Dalton",
"email": "john@fusejs.com",
"email": "john.david.dalton@gmail.com",
"web": "http://allyoucanleet.com/"
},
"bugs": {
@@ -42,8 +42,11 @@
"node",
"rhino"
],
"jam": {
"main": "./lodash.js"
},
"scripts": {
"build": "node build",
"test": "node test/test"
"test": "node test/test && node test/test-build"
}
}

View File

@@ -20,14 +20,16 @@
<script>
var lodash = _.noConflict();
</script>
<script src="../vendor/underscore/underscore.js"></script>
<script src="../vendor/underscore/underscore-min.js"></script>
<script src="../vendor/platform.js/platform.js"></script>
<script src="../vendor/benchmark.js/benchmark.js"></script>
<script src="../vendor/firebug-lite/src/firebug-lite-debug.js"></script>
<script src="perf.js"></script>
<script>
(function() {
var useApplet = !/[?&]nojava=true(?:&|$)/.test(location.search);
var measured,
perfNow,
begin = new Date;
function init() {
var fbUI = document.getElementById('FirebugUI'),
@@ -38,18 +40,18 @@
return setTimeout(init, 15);
}
fbUI.style.height = fbDoc.body.style.height = fbDoc.documentElement.style.height = '100%';
// give applet time to initialize
lodash.delay(run, useApplet ? 500 : 15);
setTimeout(run, 15);
}
if (useApplet) {
// using innerHTML avoids an alert in some versions of IE6
var div = document.createElement('div');
div.innerHTML = '<applet code=nano archive="../vendor/benchmark.js/nano.jar">';
document.body.insertBefore(div.lastChild, document.body.firstChild);
// is the applet permitted?
if (!/[?&]nojava=true(?:&|$)/.test(location.search)) {
// is the applet really needed?
while (!(measured = new Date - begin)) { }
if (measured != 1 && !((perfNow = window.performance) && typeof (perfNow.now || perfNow.webkitNow) == 'function')) {
// load applet
document.write('<applet code="nano" archive="../vendor/benchmark.js/nano.jar"></applet>');
}
}
window.onload = init;
}());
</script>

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -32,9 +32,6 @@
Object.keys = Object._keys;
delete Object._keys;
// set to test `_.noConflict`
_ = 1;
// load Lo-Dash again to overwrite the existing `_` value
document.write('<script src="../' + QUnit.config.lodashFilename + '.js"><\/script>');
@@ -47,6 +44,7 @@
<script>
// load Lo-Dash as a module
var lodashModule,
shimmedModule,
underscoreModule;
window.require && require({
@@ -54,16 +52,28 @@
'urlArgs': 't=' + (+new Date),
'paths': {
'lodash': '../../' + QUnit.config.lodashFilename,
'underscore': './../../' + QUnit.config.lodashFilename
'shimmed': './../../' + QUnit.config.lodashFilename,
'underscore': '../underscore/../../' + QUnit.config.lodashFilename
},
'shim': {
'shimmed': {
'exports': '_'
}
}
},
['lodash', 'underscore'], function(lodash, underscore) {
lodashModule = lodash.noConflict();
lodashModule.moduleName = 'lodash';
underscoreModule = underscore.noConflict();
underscoreModule.moduleName = 'underscore';
['lodash', 'shimmed', 'underscore'], function(lodash, shimmed, underscore) {
if (lodash && lodash.noConflict) {
lodashModule = lodash.noConflict();
lodashModule.moduleName = 'lodash';
}
if (shimmed.noConflict) {
shimmedModule = shimmed.noConflict();
shimmedModule.moduleName = 'shimmed';
}
if (underscore && underscore.noConflict) {
underscoreModule = underscore.noConflict();
underscoreModule.moduleName = 'underscore';
}
require(['test.js']);
});

View File

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

3
test/template/a.jst Normal file
View File

@@ -0,0 +1,3 @@
<ul>
<% _.forEach(people, function(name) { %><li><%= name %></li><% }); %>
</ul>

1
test/template/b.jst Normal file
View File

@@ -0,0 +1 @@
<% print("Hello " + epithet); %>.

1
test/template/c.tpl Normal file
View File

@@ -0,0 +1 @@
Hello {{ name }}!

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

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -18,8 +18,11 @@
// restored later on, if `noConflict` is used.
var previousBackbone = root.Backbone;
// Create a local reference to splice.
var splice = Array.prototype.splice;
// Create a local reference to array methods.
var ArrayProto = Array.prototype;
var push = ArrayProto.push;
var slice = ArrayProto.slice;
var splice = ArrayProto.splice;
// The top-level namespace. All public Backbone classes and modules will
// be attached to this. Exported for both CommonJS and the browser.
@@ -134,6 +137,9 @@
rest = [];
events = events.split(eventSplitter);
// Fill up `rest` with the callback arguments. Since we're only copying
// the tail of `arguments`, a loop is much faster than Array#slice.
for (i = 1, length = arguments.length; i < length; i++) {
rest[i - 1] = arguments[i];
}
@@ -180,7 +186,7 @@
attributes || (attributes = {});
if (options && options.collection) this.collection = options.collection;
if (options && options.parse) attributes = this.parse(attributes);
if (defaults = getValue(this, 'defaults')) {
if (defaults = _.result(this, 'defaults')) {
attributes = _.extend({}, defaults, attributes);
}
this.attributes = {};
@@ -295,7 +301,7 @@
// If the new and previous value differ, record the change. If not,
// then remove changes for this attribute.
if (!_.isEqual(prev[attr], val) || (_.has(now, attr) != _.has(prev, attr))) {
if (!_.isEqual(prev[attr], val) || (_.has(now, attr) !== _.has(prev, attr))) {
this.changed[attr] = val;
if (!options.silent) this._pending[attr] = true;
} else {
@@ -333,9 +339,7 @@
options.success = function(resp, status, xhr) {
if (!model.set(model.parse(resp, xhr), options)) return false;
if (success) success(model, resp, options);
model.trigger('sync', model, resp, options);
};
options.error = Backbone.wrapError(options.error, model, options);
return this.sync('read', this, options);
},
@@ -380,11 +384,9 @@
if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
if (!model.set(serverAttrs, options)) return false;
if (success) success(model, resp, options);
model.trigger('sync', model, resp, options);
};
// Finish configuring and sending the Ajax request.
options.error = Backbone.wrapError(options.error, model, options);
var xhr = this.sync(this.isNew() ? 'create' : 'update', this, options);
// When using `wait`, reset attributes to original values unless
@@ -412,7 +414,6 @@
options.success = function(resp) {
if (options.wait || model.isNew()) destroy();
if (success) success(model, resp, options);
if (!model.isNew()) model.trigger('sync', model, resp, options);
};
if (this.isNew()) {
@@ -420,7 +421,6 @@
return false;
}
options.error = Backbone.wrapError(options.error, model, options);
var xhr = this.sync('delete', this, options);
if (!options.wait) destroy();
return xhr;
@@ -430,9 +430,9 @@
// using Backbone's restful methods, override this to change the endpoint
// that will be called.
url: function() {
var base = getValue(this, 'urlRoot') || getValue(this.collection, 'url') || urlError();
var base = _.result(this, 'urlRoot') || _.result(this.collection, 'url') || urlError();
if (this.isNew()) return base;
return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
return base + (base.charAt(base.length - 1) === '/' ? '' : '/') + encodeURIComponent(this.id);
},
// **parse** converts a response into the hash of attributes to be `set` on
@@ -524,8 +524,8 @@
// Check if the model is currently in a valid state. It's only possible to
// get into an *invalid* state if you're using silent changes.
isValid: function() {
return !this.validate || !this.validate(this.attributes);
isValid: function(options) {
return !this.validate || !this.validate(this.attributes, options);
},
// Run validation against the next complete set of model attributes,
@@ -536,11 +536,8 @@
attrs = _.extend({}, this.attributes, attrs);
var error = this.validate(attrs, options);
if (!error) return true;
if (options && options.error) {
options.error(this, error, options);
} else {
this.trigger('error', this, error, options);
}
if (options && options.error) options.error(this, error, options);
this.trigger('error', this, error, options);
return false;
}
@@ -555,10 +552,13 @@
var Collection = Backbone.Collection = function(models, options) {
options || (options = {});
if (options.model) this.model = options.model;
if (options.comparator !== undefined) this.comparator = options.comparator;
if (options.comparator !== void 0) this.comparator = options.comparator;
this._reset();
this.initialize.apply(this, arguments);
if (models) this.reset(models, {silent: true, parse: options.parse});
if (models) {
if (options.parse) models = this.parse(models);
this.reset(models, {silent: true, parse: options.parse});
}
};
// Define the Collection's inheritable methods.
@@ -586,61 +586,51 @@
// Add a model, or list of models to the set. Pass **silent** to avoid
// firing the `add` event for every new model.
add: function(models, options) {
var i, index, length, model, cid, id, cids = {}, ids = {}, dups = [];
options || (options = {});
var i, args, length, model, existing;
var at = options && options.at;
models = _.isArray(models) ? models.slice() : [models];
// Begin by turning bare objects into model references, and preventing
// invalid models or duplicate models from being added.
// invalid models from being added.
for (i = 0, length = models.length; i < length; i++) {
if (!(model = models[i] = this._prepareModel(models[i], options))) {
throw new Error("Can't add an invalid model to a collection");
}
cid = model.cid;
id = model.id;
if (cids[cid] || this._byCid[cid] || ((id != null) && (ids[id] || this._byId[id]))) {
dups.push(i);
if (models[i] = this._prepareModel(models[i], options)) continue;
throw new Error("Can't add an invalid model to a collection");
}
for (i = models.length - 1; i >= 0; i--) {
model = models[i];
existing = model.id != null && this._byId[model.id];
// If a duplicate is found, splice it out and optionally merge it into
// the existing model.
if (existing || this._byCid[model.cid]) {
if (options && options.merge && existing) {
existing.set(model, options);
}
models.splice(i, 1);
continue;
}
cids[cid] = ids[id] = model;
}
// Remove duplicates.
i = dups.length;
while (i--) {
dups[i] = models.splice(dups[i], 1)[0];
}
// Listen to added models' events, and index models for lookup by
// `id` and by `cid`.
for (i = 0, length = models.length; i < length; i++) {
(model = models[i]).on('all', this._onModelEvent, this);
// Listen to added models' events, and index models for lookup by
// `id` and by `cid`.
model.on('all', this._onModelEvent, this);
this._byCid[model.cid] = model;
if (model.id != null) this._byId[model.id] = model;
}
// Insert models into the collection, re-sorting if needed, and triggering
// `add` events unless silenced.
this.length += length;
index = options.at != null ? options.at : this.models.length;
splice.apply(this.models, [index, 0].concat(models));
// Merge in duplicate models.
if (options.merge) {
for (i = 0, length = dups.length; i < length; i++) {
if (model = this._byId[dups[i].id]) {
model.set(dups[i], options);
}
}
}
// Update `length` and splice in new models.
this.length += models.length;
args = [at != null ? at : this.models.length, 0];
push.apply(args, models);
splice.apply(this.models, args);
// Sort the collection if appropriate.
if (this.comparator && options.at == null) this.sort({silent: true});
if (this.comparator && at == null) this.sort({silent: true});
if (options.silent) return this;
for (i = 0, length = this.models.length; i < length; i++) {
if (!cids[(model = this.models[i]).cid]) continue;
options.index = i;
if (options && options.silent) return this;
// Trigger `add` events.
while (model = models.shift()) {
model.trigger('add', model, this, options);
}
@@ -734,35 +724,35 @@
// normal circumstances, as the set will maintain sort order as each item
// is added.
sort: function(options) {
options || (options = {});
if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
var boundComparator = _.bind(this.comparator, this);
if (this.comparator.length == 1) {
this.models = this.sortBy(boundComparator);
} else {
this.models.sort(boundComparator);
if (!this.comparator) {
throw new Error('Cannot sort a set without a comparator');
}
if (!options.silent) this.trigger('reset', this, options);
if (_.isString(this.comparator) || this.comparator.length === 1) {
this.models = this.sortBy(this.comparator, this);
} else {
this.models.sort(_.bind(this.comparator, this));
}
if (!options || !options.silent) this.trigger('reset', this, options);
return this;
},
// Pluck an attribute from each model in the collection.
pluck: function(attr) {
return _.map(this.models, function(model){ return model.get(attr); });
return _.invoke(this.models, 'get', attr);
},
// When you have more items than you want to add or remove individually,
// you can reset the entire set with a new list of models, without firing
// any `add` or `remove` events. Fires `reset` when finished.
reset: function(models, options) {
models || (models = []);
options || (options = {});
for (var i = 0, l = this.models.length; i < l; i++) {
this._removeReference(this.models[i]);
}
this._reset();
this.add(models, _.extend({silent: true}, options));
if (!options.silent) this.trigger('reset', this, options);
if (models) this.add(models, _.extend({silent: true}, options));
if (!options || !options.silent) this.trigger('reset', this, options);
return this;
},
@@ -771,15 +761,13 @@
// models to the collection instead of resetting.
fetch: function(options) {
options = options ? _.clone(options) : {};
if (options.parse === undefined) options.parse = true;
if (options.parse === void 0) options.parse = true;
var collection = this;
var success = options.success;
options.success = function(resp, status, xhr) {
collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
if (success) success(collection, resp, options);
collection.trigger('sync', collection, resp, options);
};
options.error = Backbone.wrapError(options.error, collection, options);
return this.sync('read', this, options);
},
@@ -787,14 +775,14 @@
// collection immediately, unless `wait: true` is passed, in which case we
// wait for the server to agree.
create: function(model, options) {
var coll = this;
var collection = this;
options = options ? _.clone(options) : {};
model = this._prepareModel(model, options);
if (!model) return false;
if (!options.wait) coll.add(model, options);
if (!options.wait) collection.add(model, options);
var success = options.success;
options.success = function(model, resp, options) {
if (options.wait) coll.add(model, options);
if (options.wait) collection.add(model, options);
if (success) success(model, resp, options);
};
model.save(null, options);
@@ -842,9 +830,7 @@
// Internal method to remove a model's ties to a collection.
_removeReference: function(model) {
if (this == model.collection) {
delete model.collection;
}
if (this === model.collection) delete model.collection;
model.off('all', this._onModelEvent, this);
},
@@ -853,10 +839,8 @@
// events simply proxy through. "add" and "remove" events that originate
// in other collections are ignored.
_onModelEvent: function(event, model, collection, options) {
if ((event == 'add' || event == 'remove') && collection != this) return;
if (event == 'destroy') {
this.remove(model, options);
}
if ((event === 'add' || event === 'remove') && collection !== this) return;
if (event === 'destroy') this.remove(model, options);
if (model && event === 'change:' + model.idAttribute) {
delete this._byId[model.previous(model.idAttribute)];
if (model.id != null) this._byId[model.id] = model;
@@ -867,16 +851,32 @@
});
// Underscore methods that we want to implement on the Collection.
var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find',
'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any',
'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex',
'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf',
'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy'];
var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl',
'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select',
'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
'max', 'min', 'sortedIndex', 'toArray', 'size', 'first', 'head', 'take',
'initial', 'rest', 'tail', 'last', 'without', 'indexOf', 'shuffle',
'lastIndexOf', 'isEmpty'];
// Mix in each Underscore method as a proxy to `Collection#models`.
_.each(methods, function(method) {
Collection.prototype[method] = function() {
return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
var args = slice.call(arguments);
args.unshift(this.models);
return _[method].apply(_, args);
};
});
// Underscore methods that take a property name as an argument.
var attributeMethods = ['groupBy', 'countBy', 'sortBy'];
// Use attributes instead of properties.
_.each(attributeMethods, function(method) {
Collection.prototype[method] = function(value, context) {
var iterator = _.isFunction(value) ? value : function(model) {
return model.get(value);
};
return _[method](this.models, iterator, context);
};
});
@@ -912,7 +912,6 @@
// });
//
route: function(route, name, callback) {
Backbone.history || (Backbone.history = new History);
if (!_.isRegExp(route)) route = this._routeToRegExp(route);
if (!callback) callback = this[name];
Backbone.history.route(route, _.bind(function(fragment) {
@@ -927,6 +926,7 @@
// Simple proxy to `Backbone.history` to save a fragment into the history.
navigate: function(fragment, options) {
Backbone.history.navigate(fragment, options);
return this;
},
// Bind all defined routes to `Backbone.history`. We have to reverse the
@@ -965,16 +965,23 @@
// Handles cross-browser history management, based on URL fragments. If the
// browser does not support `onhashchange`, falls back to polling.
var History = Backbone.History = function(options) {
var History = Backbone.History = function() {
this.handlers = [];
_.bindAll(this, 'checkUrl');
this.location = options && options.location || root.location;
this.history = options && options.history || root.history;
// #1653 - Ensure that `History` can be used outside of the browser.
if (typeof window !== 'undefined') {
this.location = window.location;
this.history = window.history;
}
};
// Cached regex for cleaning leading hashes and slashes .
// Cached regex for cleaning leading hashes and slashes.
var routeStripper = /^[#\/]/;
// Cached regex for stripping leading and trailing slashes.
var rootStripper = /^\/+|\/+$/g;
// Cached regex for detecting MSIE.
var isExplorer = /msie [\w.]+/;
@@ -1004,7 +1011,7 @@
if (fragment == null) {
if (this._hasPushState || !this._wantsHashChange || forcePushState) {
fragment = this.location.pathname;
var root = this.options.root.replace(trailingSlash, '');
var root = this.root.replace(trailingSlash, '');
if (!fragment.indexOf(root)) fragment = fragment.substr(root.length);
} else {
fragment = this.getHash();
@@ -1022,6 +1029,7 @@
// Figure out the initial configuration. Do we need an iframe?
// Is pushState desired ... is it available?
this.options = _.extend({}, {root: '/'}, this.options, options);
this.root = this.options.root;
this._wantsHashChange = this.options.hashChange !== false;
this._wantsPushState = !!this.options.pushState;
this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState);
@@ -1029,6 +1037,9 @@
var docMode = document.documentMode;
var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
// Normalize root to always include a leading and trailing slash.
this.root = ('/' + this.root + '/').replace(rootStripper, '/');
if (oldIE && this._wantsHashChange) {
this.iframe = Backbone.$('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
this.navigate(fragment);
@@ -1048,13 +1059,13 @@
// opened by a non-pushState browser.
this.fragment = fragment;
var loc = this.location;
var atRoot = (loc.pathname == this.options.root) && !loc.search;
var atRoot = (loc.pathname.replace(/[^/]$/, '$&/') === this.root) && !loc.search;
// If we've started off with a route from a `pushState`-enabled browser,
// but we're currently in a browser that doesn't support it...
if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) {
this.fragment = this.getFragment(null, true);
this.location.replace(this.options.root + this.location.search + '#' + this.fragment);
this.location.replace(this.root + this.location.search + '#' + this.fragment);
// Return immediately as browser will do redirect to new url
return true;
@@ -1062,12 +1073,10 @@
// in a browser where it could be `pushState`-based instead...
} else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
this.fragment = this.getHash().replace(routeStripper, '');
this.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment);
this.history.replaceState({}, document.title, this.root + this.fragment);
}
if (!this.options.silent) {
return this.loadUrl();
}
if (!this.options.silent) return this.loadUrl();
},
// Disable Backbone.history, perhaps temporarily. Not useful in a real app,
@@ -1088,10 +1097,10 @@
// calls `loadUrl`, normalizing across the hidden iframe.
checkUrl: function(e) {
var current = this.getFragment();
if (current == this.fragment && this.iframe) {
if (current === this.fragment && this.iframe) {
current = this.getFragment(this.getHash(this.iframe));
}
if (current == this.fragment) return false;
if (current === this.fragment) return false;
if (this.iframe) this.navigate(current);
this.loadUrl() || this.loadUrl(this.getHash());
},
@@ -1120,10 +1129,10 @@
navigate: function(fragment, options) {
if (!History.started) return false;
if (!options || options === true) options = {trigger: options};
var frag = (fragment || '').replace(routeStripper, '');
if (this.fragment == frag) return;
this.fragment = frag;
var url = (frag.indexOf(this.options.root) != 0 ? this.options.root : '') + frag;
fragment = this.getFragment(fragment || '');
if (this.fragment === fragment) return;
this.fragment = fragment;
var url = this.root + fragment;
// If pushState is available, we use it to set the fragment as a real URL.
if (this._hasPushState) {
@@ -1132,13 +1141,13 @@
// If hash changes haven't been explicitly disabled, update the hash
// fragment to store history.
} else if (this._wantsHashChange) {
this._updateHash(this.location, frag, options.replace);
if (this.iframe && (frag != this.getFragment(this.getHash(this.iframe)))) {
this._updateHash(this.location, fragment, options.replace);
if (this.iframe && (fragment !== this.getFragment(this.getHash(this.iframe)))) {
// Opening and closing the iframe tricks IE7 and earlier to push a
// history entry on hash-tag change. When replace is true, we don't
// want this.
if(!options.replace) this.iframe.document.open().close();
this._updateHash(this.iframe.location, frag, options.replace);
this._updateHash(this.iframe.location, fragment, options.replace);
}
// If you've told us that you explicitly don't want fallback hashchange-
@@ -1153,13 +1162,19 @@
// a new one to the browser history.
_updateHash: function(location, fragment, replace) {
if (replace) {
location.replace(location.href.replace(/(javascript:|#).*$/, '') + '#' + fragment);
var href = location.href.replace(/(javascript:|#).*$/, '');
location.replace(href + '#' + fragment);
} else {
location.hash = fragment;
// #1649 - Some browsers require that `hash` contains a leading #.
location.hash = '#' + fragment;
}
}
});
// Create the default Backbone.history.
Backbone.history = new History;
// Backbone.View
// -------------
@@ -1202,9 +1217,19 @@
return this;
},
// Clean up references to this view in order to prevent latent effects and
// memory leaks.
dispose: function() {
this.undelegateEvents();
if (this.model) this.model.off(null, null, this);
if (this.collection) this.collection.off(null, null, this);
return this;
},
// Remove this view from the DOM. Note that the view isn't present in the
// DOM by default, so calling this method may be a no-op.
remove: function() {
this.dispose();
this.$el.remove();
return this;
},
@@ -1247,7 +1272,7 @@
// This only works for delegate-able events: not `focus`, `blur`, and
// not `change`, `submit`, and `reset` in Internet Explorer.
delegateEvents: function(events) {
if (!(events || (events = getValue(this, 'events')))) return;
if (!(events || (events = _.result(this, 'events')))) return;
this.undelegateEvents();
for (var key in events) {
var method = events[key];
@@ -1290,10 +1315,10 @@
// an element from the `id`, `className` and `tagName` properties.
_ensureElement: function() {
if (!this.el) {
var attrs = _.extend({}, getValue(this, 'attributes'));
if (this.id) attrs.id = this.id;
if (this.className) attrs['class'] = this.className;
this.setElement(this.make(getValue(this, 'tagName'), attrs), false);
var attrs = _.extend({}, _.result(this, 'attributes'));
if (this.id) attrs.id = _.result(this, 'id');
if (this.className) attrs['class'] = _.result(this, 'className');
this.setElement(this.make(_.result(this, 'tagName'), attrs), false);
} else {
this.setElement(this.el, false);
}
@@ -1301,16 +1326,6 @@
});
// The self-propagating extend function that Backbone classes use.
var extend = function(protoProps, classProps) {
var child = inherits(this, protoProps, classProps);
child.extend = this.extend;
return child;
};
// Set up inheritance for the model, collection, and view.
Model.extend = Collection.extend = Router.extend = View.extend = extend;
// Backbone.sync
// -------------
@@ -1348,11 +1363,11 @@
// Ensure that we have a URL.
if (!options.url) {
params.url = getValue(model, 'url') || urlError();
params.url = _.result(model, 'url') || urlError();
}
// Ensure that we have the appropriate request data.
if (!options.data && model && (method == 'create' || method == 'update')) {
if (!options.data && model && (method === 'create' || method === 'update')) {
params.contentType = 'application/json';
params.data = JSON.stringify(model);
}
@@ -1380,6 +1395,18 @@
params.processData = false;
}
var success = options.success;
options.success = function(resp, status, xhr) {
if (success) success(resp, status, xhr);
model.trigger('sync', model, resp, options);
};
var error = options.error;
options.error = function(xhr, status, thrown) {
if (error) error(model, xhr, options);
model.trigger('error', model, xhr, options);
};
// Make the request, allowing the user to override any Ajax options.
return Backbone.ajax(_.extend(params, options));
};
@@ -1389,69 +1416,47 @@
return Backbone.$.ajax.apply(Backbone.$, arguments);
};
// Wrap an optional error callback with a fallback error event.
Backbone.wrapError = function(onError, originalModel, options) {
return function(model, resp) {
resp = model === originalModel ? resp : model;
if (onError) {
onError(originalModel, resp, options);
} else {
originalModel.trigger('error', originalModel, resp, options);
}
};
};
// Helpers
// -------
// Shared empty constructor function to aid in prototype-chain creation.
var ctor = function(){};
// Helper function to correctly set up the prototype chain, for subclasses.
// Similar to `goog.inherits`, but uses a hash of prototype properties and
// class properties to be extended.
var inherits = function(parent, protoProps, staticProps) {
var extend = function(protoProps, staticProps) {
var parent = this;
var child;
// The constructor function for the new subclass is either defined by you
// (the "constructor" property in your `extend` definition), or defaulted
// by us to simply call the parent's constructor.
if (protoProps && protoProps.hasOwnProperty('constructor')) {
if (protoProps && _.has(protoProps, 'constructor')) {
child = protoProps.constructor;
} else {
child = function(){ parent.apply(this, arguments); };
}
// Inherit class (static) properties from parent.
_.extend(child, parent);
// Set the prototype chain to inherit from `parent`, without calling
// `parent`'s constructor function.
ctor.prototype = parent.prototype;
child.prototype = new ctor();
function Surrogate(){ this.constructor = child; };
Surrogate.prototype = parent.prototype;
child.prototype = new Surrogate;
// Add prototype properties (instance properties) to the subclass,
// if supplied.
if (protoProps) _.extend(child.prototype, protoProps);
// Add static properties to the constructor function, if supplied.
if (staticProps) _.extend(child, staticProps);
_.extend(child, parent, staticProps);
// Correctly set child's `prototype.constructor`.
child.prototype.constructor = child;
// Set a convenience property in case the parent's prototype is needed later.
// Set a convenience property in case the parent's prototype is needed
// later.
child.__super__ = parent.prototype;
return child;
};
// Helper function to get a value from a Backbone object as a property
// or as a function.
var getValue = function(object, prop) {
if (!(object && object[prop])) return null;
return _.isFunction(object[prop]) ? object[prop]() : object[prop];
};
// Set up inheritance for the model, collection, router, and view.
Model.extend = Collection.extend = Router.extend = View.extend = extend;
// Throw an error when a URL is needed, and none is supplied.
var urlError = function() {

View File

@@ -1,13 +1,12 @@
$(document).ready(function() {
var lastRequest = null;
var sync = Backbone.sync;
var a, b, c, d, e, col, otherCol;
module("Backbone.Collection", {
module("Backbone.Collection", _.extend(new Environment, {
setup: function() {
Environment.prototype.setup.apply(this, arguments);
a = new Backbone.Model({id: 3, label: 'a'});
b = new Backbone.Model({id: 2, label: 'b'});
c = new Backbone.Model({id: 1, label: 'c'});
@@ -15,23 +14,11 @@ $(document).ready(function() {
e = null;
col = new Backbone.Collection([a,b,c,d]);
otherCol = new Backbone.Collection();
Backbone.sync = function(method, model, options) {
lastRequest = {
method: method,
model: model,
options: options
};
};
},
teardown: function() {
Backbone.sync = sync;
}
});
}));
test("Collection: new and sort", 7, function() {
test("new and sort", 7, function() {
equal(col.first(), a, "a should be first");
equal(col.last(), d, "d should be last");
col.comparator = function(a, b) {
@@ -47,13 +34,37 @@ $(document).ready(function() {
equal(col.length, 4);
});
test("Collection: get, getByCid", 3, function() {
test("String comparator.", 1, function() {
var collection = new Backbone.Collection([
{id: 3},
{id: 1},
{id: 2}
], {comparator: 'id'});
deepEqual(collection.pluck('id'), [1, 2, 3]);
});
test("new and parse", 3, function() {
var Collection = Backbone.Collection.extend({
parse : function(data) {
return _.filter(data, function(datum) {
return datum.a % 2 === 0;
});
}
});
var models = [{a: 1}, {a: 2}, {a: 3}, {a: 4}];
var collection = new Collection(models, {parse: true});
strictEqual(collection.length, 2);
strictEqual(collection.first().get('a'), 2);
strictEqual(collection.last().get('a'), 4);
});
test("get, getByCid", 3, function() {
equal(col.get(0), d);
equal(col.get(2), b);
equal(col.getByCid(col.first().cid), col.first());
});
test("Collection: get with non-default ids", 2, function() {
test("get with non-default ids", 2, function() {
var col = new Backbone.Collection();
var MongoModel = Backbone.Model.extend({
idAttribute: '_id'
@@ -65,7 +76,7 @@ $(document).ready(function() {
equal(col.get(101), model);
});
test("Collection: update index when id changes", 3, function() {
test("update index when id changes", 3, function() {
var col = new Backbone.Collection();
col.add([
{id : 0, name : 'one'},
@@ -78,15 +89,15 @@ $(document).ready(function() {
equal(col.get(101).get('name'), 'one');
});
test("Collection: at", 1, function() {
test("at", 1, function() {
equal(col.at(2), c);
});
test("Collection: pluck", 1, function() {
test("pluck", 1, function() {
equal(col.pluck('label').join(' '), 'a b c d');
});
test("Collection: add", 11, function() {
test("add", 10, function() {
var added, opts, secondAdded;
added = opts = secondAdded = null;
e = new Backbone.Model({id: 10, label : 'e'});
@@ -96,7 +107,6 @@ $(document).ready(function() {
});
col.on('add', function(model, collection, options){
added = model.get('label');
equal(options.index, 4);
opts = options;
});
col.add(e, {amazing: true});
@@ -118,7 +128,7 @@ $(document).ready(function() {
equal(atCol.last(), h);
});
test("Collection: add multiple models", 6, function() {
test("add multiple models", 6, function() {
var col = new Backbone.Collection([{at: 0}, {at: 1}, {at: 9}]);
col.add([{at: 2}, {at: 3}, {at: 4}, {at: 5}, {at: 6}, {at: 7}, {at: 8}], {at: 2});
for (var i = 0; i <= 5; i++) {
@@ -126,7 +136,7 @@ $(document).ready(function() {
}
});
test("Collection: add; at should have preference over comparator", 1, function() {
test("add; at should have preference over comparator", 1, function() {
var Col = Backbone.Collection.extend({
comparator: function(a,b) {
return a.id > b.id ? -1 : 1;
@@ -139,19 +149,19 @@ $(document).ready(function() {
equal(col.pluck('id').join(' '), '3 1 2');
});
test("Collection: can't add model to collection twice", function() {
test("can't add model to collection twice", function() {
var col = new Backbone.Collection([{id: 1}, {id: 2}, {id: 1}, {id: 2}, {id: 3}]);
equal(col.pluck('id').join(' '), '1 2 3');
});
test("Collection: can't add different model with same id to collection twice", 1, function() {
test("can't add different model with same id to collection twice", 1, function() {
var col = new Backbone.Collection;
col.unshift({id: 101});
col.add({id: 101});
equal(col.length, 1);
});
test("Collection: merge in duplicate models with {merge: true}", 3, function() {
test("merge in duplicate models with {merge: true}", 3, function() {
var col = new Backbone.Collection;
col.add([{id: 1, name: 'Moe'}, {id: 2, name: 'Curly'}, {id: 3, name: 'Larry'}]);
col.add({id: 1, name: 'Moses'});
@@ -162,7 +172,7 @@ $(document).ready(function() {
equal(col.first().get('name'), 'Tim');
});
test("Collection: add model to multiple collections", 10, function() {
test("add model to multiple collections", 10, function() {
var counter = 0;
var e = new Backbone.Model({id: 10, label : 'e'});
e.on('add', function(model, collection) {
@@ -190,7 +200,7 @@ $(document).ready(function() {
equal(e.collection, colE);
});
test("Collection: add model with parse", 1, function() {
test("add model with parse", 1, function() {
var Model = Backbone.Model.extend({
parse: function(obj) {
obj.value += 1;
@@ -204,7 +214,7 @@ $(document).ready(function() {
equal(col.at(0).get('value'), 2);
});
test("Collection: add model to collection with sort()-style comparator", 3, function() {
test("add model to collection with sort()-style comparator", 3, function() {
var col = new Backbone.Collection;
col.comparator = function(a, b) {
return a.get('name') < b.get('name') ? -1 : 1;
@@ -220,7 +230,7 @@ $(document).ready(function() {
equal(col.indexOf(tom), 2);
});
test("Collection: comparator that depends on `this`", 1, function() {
test("comparator that depends on `this`", 2, function() {
var col = new Backbone.Collection;
col.negative = function(num) {
return -num;
@@ -229,10 +239,15 @@ $(document).ready(function() {
return this.negative(a.id);
};
col.add([{id: 1}, {id: 2}, {id: 3}]);
equal(col.pluck('id').join(' '), '3 2 1');
deepEqual(col.pluck('id'), [3, 2, 1]);
col.comparator = function(a, b) {
return this.negative(b.id) - this.negative(a.id);
};
col.sort();
deepEqual(col.pluck('id'), [1, 2, 3]);
});
test("Collection: remove", 5, function() {
test("remove", 5, function() {
var removed = null;
var otherRemoved = null;
col.on('remove', function(model, col, options) {
@@ -249,20 +264,20 @@ $(document).ready(function() {
equal(otherRemoved, null);
});
test("Collection: shift and pop", 2, function() {
test("shift and pop", 2, function() {
var col = new Backbone.Collection([{a: 'a'}, {b: 'b'}, {c: 'c'}]);
equal(col.shift().get('a'), 'a');
equal(col.pop().get('c'), 'c');
});
test("Collection: slice", 2, function() {
test("slice", 2, function() {
var col = new Backbone.Collection([{a: 'a'}, {b: 'b'}, {c: 'c'}]);
var array = col.slice(1, 3);
equal(array.length, 2);
equal(array[0].get('b'), 'b');
});
test("Collection: events are unbound on remove", 3, function() {
test("events are unbound on remove", 3, function() {
var counter = 0;
var dj = new Backbone.Model();
var emcees = new Backbone.Collection([dj]);
@@ -275,7 +290,7 @@ $(document).ready(function() {
equal(counter, 1);
});
test("Collection: remove in multiple collections", 7, function() {
test("remove in multiple collections", 7, function() {
var modelData = {
id : 5,
title : 'Othello'
@@ -299,7 +314,7 @@ $(document).ready(function() {
equal(passed, true);
});
test("Collection: remove same model in multiple collection", 16, function() {
test("remove same model in multiple collection", 16, function() {
var counter = 0;
var e = new Backbone.Model({id: 5, title: 'Othello'});
e.on('remove', function(model, collection) {
@@ -333,7 +348,7 @@ $(document).ready(function() {
equal(counter, 2);
});
test("Collection: model destroy removes from all collections", 3, function() {
test("model destroy removes from all collections", 3, function() {
var e = new Backbone.Model({id: 5, title: 'Othello'});
e.sync = function(method, model, options) { options.success({}); };
var colE = new Backbone.Collection([e]);
@@ -355,25 +370,29 @@ $(document).ready(function() {
equal(undefined, e.collection);
});
test("Collection: fetch", 4, function() {
col.fetch();
equal(lastRequest.method, 'read');
equal(lastRequest.model, col);
equal(lastRequest.options.parse, true);
test("fetch", 4, function() {
var collection = new Backbone.Collection;
collection.url = '/test';
collection.fetch();
equal(this.syncArgs.method, 'read');
equal(this.syncArgs.model, collection);
equal(this.syncArgs.options.parse, true);
col.fetch({parse: false});
equal(lastRequest.options.parse, false);
collection.fetch({parse: false});
equal(this.syncArgs.options.parse, false);
});
test("Collection: create", 4, function() {
var model = col.create({label: 'f'}, {wait: true});
equal(lastRequest.method, 'create');
equal(lastRequest.model, model);
test("create", 4, function() {
var collection = new Backbone.Collection;
collection.url = '/test';
var model = collection.create({label: 'f'}, {wait: true});
equal(this.syncArgs.method, 'create');
equal(this.syncArgs.model, model);
equal(model.get('label'), 'f');
equal(model.collection, col);
equal(model.collection, collection);
});
test("Collection: create enforces validation", 1, function() {
test("create enforces validation", 1, function() {
var ValidatingModel = Backbone.Model.extend({
validate: function(attrs) {
return "fail";
@@ -386,7 +405,7 @@ $(document).ready(function() {
equal(col.create({"foo":"bar"}), false);
});
test("Collection: a failing create runs the error callback", 1, function() {
test("a failing create runs the error callback", 1, function() {
var ValidatingModel = Backbone.Model.extend({
validate: function(attrs) {
return "fail";
@@ -402,7 +421,7 @@ $(document).ready(function() {
equal(flag, true);
});
test("collection: initialize", 1, function() {
test("initialize", 1, function() {
var Collection = Backbone.Collection.extend({
initialize: function() {
this.one = 1;
@@ -412,11 +431,11 @@ $(document).ready(function() {
equal(coll.one, 1);
});
test("Collection: toJSON", 1, function() {
test("toJSON", 1, function() {
equal(JSON.stringify(col), '[{"id":3,"label":"a"},{"id":2,"label":"b"},{"id":1,"label":"c"},{"id":0,"label":"d"}]');
});
test("Collection: where", 6, function() {
test("where", 6, function() {
var coll = new Backbone.Collection([
{a: 1},
{a: 1},
@@ -432,7 +451,7 @@ $(document).ready(function() {
equal(coll.where({a: 1, b: 2}).length, 1);
});
test("Collection: Underscore methods", 13, function() {
test("Underscore methods", 13, function() {
equal(col.map(function(model){ return model.get('label'); }).join(' '), 'a b c d');
equal(col.any(function(model){ return model.id === 100; }), false);
equal(col.any(function(model){ return model.id === 0; }), true);
@@ -452,7 +471,7 @@ $(document).ready(function() {
[4, 0]);
});
test("Collection: reset", 10, function() {
test("reset", 10, function() {
var resetCount = 0;
var models = col.models;
col.on('reset', function() { resetCount += 1; });
@@ -471,7 +490,7 @@ $(document).ready(function() {
ok(_.isEqual(col.last().attributes, d.attributes));
});
test("Collection: reset passes caller options", 3, function() {
test("reset passes caller options", 3, function() {
var Model = Backbone.Model.extend({
initialize: function(attrs, options) {
this.model_parameter = options.model_parameter;
@@ -485,14 +504,14 @@ $(document).ready(function() {
});
});
test("Collection: trigger custom events on models", 1, function() {
test("trigger custom events on models", 1, function() {
var fired = null;
a.on("custom", function() { fired = true; });
a.trigger("custom");
equal(fired, true);
});
test("Collection: add does not alter arguments", 2, function(){
test("add does not alter arguments", 2, function(){
var attrs = {};
var models = [attrs];
new Backbone.Collection().add(models);
@@ -501,16 +520,17 @@ $(document).ready(function() {
});
test("#714: access `model.collection` in a brand new model.", 2, function() {
var col = new Backbone.Collection;
var collection = new Backbone.Collection;
collection.url = '/test';
var Model = Backbone.Model.extend({
set: function(attrs) {
equal(attrs.prop, 'value');
equal(this.collection, col);
equal(this.collection, collection);
return this;
}
});
col.model = Model;
col.create({prop: 'value'});
collection.model = Model;
collection.create({prop: 'value'});
});
test("#574, remove its own reference to the .models array.", 2, function() {
@@ -542,24 +562,7 @@ $(document).ready(function() {
});
});
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() {
test("throwing during add leaves consistent state", 4, function() {
var col = new Backbone.Collection();
col.on('test', function() { ok(false); });
col.model = Backbone.Model.extend({
@@ -573,7 +576,7 @@ $(document).ready(function() {
equal(col.length, 0);
});
test("Collection: multiple copies of the same model", 3, function() {
test("multiple copies of the same model", 3, function() {
var col = new Backbone.Collection();
var model = new Backbone.Model();
col.add([model, model]);
@@ -606,7 +609,7 @@ $(document).ready(function() {
ok(!collection.get('undefined'));
});
test("Collection: falsy comparator", 4, function(){
test("falsy comparator", 4, function(){
var Col = Backbone.Collection.extend({
comparator: function(model){ return model.id; }
});
@@ -636,20 +639,15 @@ $(document).ready(function() {
});
test("#1412 - Trigger 'sync' event.", 2, function() {
var collection = new Backbone.Collection([], {
model: Backbone.Model.extend({
sync: function(method, model, options) {
options.success();
}
})
});
collection.sync = function(method, model, options) { options.success(); };
var collection = new Backbone.Collection;
collection.url = '/test';
collection.on('sync', function() { ok(true); });
Backbone.ajax = function(settings){ settings.success(); };
collection.fetch();
collection.create({id: 1});
});
test("#1447 - create with wait adds model.", function() {
test("#1447 - create with wait adds model.", 1, function() {
var collection = new Backbone.Collection;
var model = new Backbone.Model;
model.sync = function(method, model, options){ options.success(); };
@@ -657,7 +655,7 @@ $(document).ready(function() {
collection.create(model, {wait: true});
});
test("#1448 - add sorts collection after merge.", function() {
test("#1448 - add sorts collection after merge.", 1, function() {
var collection = new Backbone.Collection([
{id: 1, x: 1},
{id: 2, x: 2}
@@ -666,4 +664,41 @@ $(document).ready(function() {
collection.add({id: 1, x: 3}, {merge: true});
deepEqual(collection.pluck('id'), [2, 1]);
});
test("#1655 - groupBy can be used with a string argument.", 3, function() {
var collection = new Backbone.Collection([{x: 1}, {x: 2}]);
var grouped = collection.groupBy('x');
strictEqual(_.keys(grouped).length, 2);
strictEqual(grouped[1][0].get('x'), 1);
strictEqual(grouped[2][0].get('x'), 2);
});
test("#1655 - sortBy can be used with a string argument.", 1, function() {
var collection = new Backbone.Collection([{x: 3}, {x: 1}, {x: 2}]);
var values = _.map(collection.sortBy('x'), function(model) {
return model.get('x');
});
deepEqual(values, [1, 2, 3]);
});
test("#1604 - Removal during iteration.", 0, function() {
var collection = new Backbone.Collection([{}, {}]);
collection.on('add', function() {
collection.at(0).destroy();
});
collection.add({}, {at: 0});
});
test("#1638 - `sort` during `add` triggers correctly.", function() {
var collection = new Backbone.Collection;
collection.comparator = function(model) { return model.get('x'); };
var added = [];
collection.on('add', function(model) {
model.set({x: 3});
collection.sort();
added.push(model.id);
});
collection.add([{id: 1, x: 1}, {id: 2, x: 2}]);
deepEqual(added, [1, 2]);
});
});

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

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

View File

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

View File

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

View File

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

View File

@@ -20,9 +20,11 @@ $(document).ready(function() {
_.extend(this, _.pick($('<a></a>', {href: href})[0],
'href',
'hash',
'host',
'search',
'fragment',
'pathname'
'pathname',
'protocol'
));
// In IE, anchor.pathname does not contain a leading slash though
// window.location.pathname does.
@@ -39,7 +41,7 @@ $(document).ready(function() {
setup: function() {
location = new Location('http://example.com');
Backbone.history = new Backbone.History({location: location});
Backbone.history = _.extend(new Backbone.History, {location: location});
router = new Router({testing: 101});
Backbone.history.interval = 9;
Backbone.history.start({pushState: false});
@@ -124,11 +126,11 @@ $(document).ready(function() {
});
test("Router: initialize", 1, function() {
test("initialize", 1, function() {
equal(router.testing, 101);
});
test("Router: routes (simple)", 4, function() {
test("routes (simple)", 4, function() {
location.replace('http://example.com#search/news');
Backbone.history.checkUrl();
equal(router.query, 'news');
@@ -137,26 +139,26 @@ $(document).ready(function() {
equal(lastArgs[0], 'news');
});
test("Router: routes (two part)", 2, function() {
test("routes (two part)", 2, function() {
location.replace('http://example.com#search/nyc/p10');
Backbone.history.checkUrl();
equal(router.query, 'nyc');
equal(router.page, '10');
});
test("Router: routes via navigate", 2, function() {
test("routes via navigate", 2, function() {
Backbone.history.navigate('search/manhattan/p20', {trigger: true});
equal(router.query, 'manhattan');
equal(router.page, '20');
});
test("Router: routes via navigate for backwards-compatibility", 2, function() {
test("routes via navigate for backwards-compatibility", 2, function() {
Backbone.history.navigate('search/manhattan/p20', true);
equal(router.query, 'manhattan');
equal(router.page, '20');
});
test("Router: route precedence via navigate", 6, function(){
test("route precedence via navigate", 6, function(){
// check both 0.9.x and backwards-compatibility options
_.each([ { trigger: true }, true ], function( options ){
Backbone.history.navigate('contacts', options);
@@ -176,13 +178,13 @@ $(document).ready(function() {
Backbone.history.navigate('/route');
});
test("Router: use implicit callback if none provided", 1, function() {
test("use implicit callback if none provided", 1, function() {
router.count = 0;
router.navigate('implicit', {trigger: true});
equal(router.count, 1);
});
test("Router: routes via navigate with {replace: true}", 1, function() {
test("routes via navigate with {replace: true}", 1, function() {
location.replace('http://example.com#start_here');
Backbone.history.checkUrl();
location.replace = function(href) {
@@ -191,13 +193,13 @@ $(document).ready(function() {
Backbone.history.navigate('end_here', {replace: true});
});
test("Router: routes (splats)", 1, function() {
test("routes (splats)", 1, function() {
location.replace('http://example.com#splat/long-list/of/splatted_99args/end');
Backbone.history.checkUrl();
equal(router.args, 'long-list/of/splatted_99args');
});
test("Router: routes (complex)", 3, function() {
test("routes (complex)", 3, function() {
location.replace('http://example.com#one/two/three/complex-part/four/five/six/seven');
Backbone.history.checkUrl();
equal(router.first, 'one/two/three');
@@ -205,7 +207,7 @@ $(document).ready(function() {
equal(router.rest, 'four/five/six/seven');
});
test("Router: routes (query)", 5, function() {
test("routes (query)", 5, function() {
location.replace('http://example.com#mandel?a=b&c=d');
Backbone.history.checkUrl();
equal(router.entity, 'mandel');
@@ -215,13 +217,13 @@ $(document).ready(function() {
equal(lastArgs[1], 'a=b&c=d');
});
test("Router: routes (anything)", 1, function() {
test("routes (anything)", 1, function() {
location.replace('http://example.com#doesnt-match-a-route');
Backbone.history.checkUrl();
equal(router.anything, 'doesnt-match-a-route');
});
test("Router: fires event when router doesn't have callback on it", 1, function() {
test("fires event when router doesn't have callback on it", 1, function() {
router.on("route:noCallback", function(){ ok(true); });
location.replace('http://example.com#noCallback');
Backbone.history.checkUrl();
@@ -231,28 +233,25 @@ $(document).ready(function() {
location.replace('http://example.com/root/foo');
Backbone.history.stop();
Backbone.history = new Backbone.History({location: location});
Backbone.history = _.extend(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 = _.extend(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();
Backbone.history.navigate = function(){ ok(Backbone.History.started); };
Backbone.history.start();
// If this is not an old IE navigate will not be called.
if (!history.iframe) ok(true);
if (!Backbone.history.iframe) ok(true);
});
test("Router: route callback gets passed decoded values", 3, function() {
test("route callback gets passed decoded values", 3, function() {
var route = 'has%2Fslash/complex-has%23hash/has%20space';
Backbone.history.navigate(route, {trigger: true});
equal(router.first, 'has/slash');
@@ -260,7 +259,7 @@ $(document).ready(function() {
equal(router.rest, 'has space');
});
test("Router: correctly handles URLs with % (#868)", 3, function() {
test("correctly handles URLs with % (#868)", 3, function() {
location.replace('http://example.com#search/fat%3A1.5%25');
Backbone.history.checkUrl();
location.replace('http://example.com#search/fat');
@@ -273,7 +272,7 @@ $(document).ready(function() {
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 = _.extend(new Backbone.History, {location: location});
Backbone.history.start({hashChange: false});
var fragment = Backbone.history.getFragment();
strictEqual(fragment, location.pathname.replace(/^\//, ''));
@@ -282,7 +281,7 @@ $(document).ready(function() {
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 = _.extend(new Backbone.History, {location: location});
Backbone.history.start({hashChange: false, root: '/root/'});
location.assign = function(pathname) {
strictEqual(pathname, '/root/fragment');
@@ -293,7 +292,7 @@ $(document).ready(function() {
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 = _.extend(new Backbone.History, {location: location});
Backbone.history.start({hashChange: false, root: '/root/', silent: true});
strictEqual(Backbone.history.getFragment(), '');
});
@@ -301,7 +300,7 @@ $(document).ready(function() {
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({
Backbone.history = _.extend(new Backbone.History, {
location: location,
history: {
pushState: function(state, title, url) {
@@ -318,4 +317,136 @@ $(document).ready(function() {
strictEqual(Backbone.history.fragment, 'x');
});
test("Normalize root.", 1, function() {
Backbone.history.stop();
location.replace('http://example.com/root');
Backbone.history = _.extend(new Backbone.History, {
location: location,
history: {
pushState: function(state, title, url) {
strictEqual(url, '/root/fragment');
}
}
});
Backbone.history.start({
pushState: true,
root: '/root',
hashChange: false
});
Backbone.history.navigate('fragment');
});
test("Normalize root.", 1, function() {
Backbone.history.stop();
location.replace('http://example.com/root#fragment');
Backbone.history = _.extend(new Backbone.History, {
location: location,
history: {
pushState: function(state, title, url) {},
replaceState: function(state, title, url) {
strictEqual(url, '/root/fragment');
}
}
});
Backbone.history.start({
pushState: true,
root: '/root'
});
});
test("Normalize root.", 1, function() {
Backbone.history.stop();
location.replace('http://example.com/root');
Backbone.history = _.extend(new Backbone.History, {location: location});
Backbone.history.loadUrl = function() { ok(true); };
Backbone.history.start({
pushState: true,
root: '/root'
});
});
test("Normalize root - leading slash.", 1, function() {
Backbone.history.stop();
location.replace('http://example.com/root');
Backbone.history = _.extend(new Backbone.History, {
location: location,
history: {
pushState: function(){},
replaceState: function(){}
}
});
Backbone.history.start({root: 'root'});
strictEqual(Backbone.history.root, '/root/');
});
test("Transition from hashChange to pushState.", 1, function() {
Backbone.history.stop();
location.replace('http://example.com/root#x/y');
Backbone.history = _.extend(new Backbone.History, {
location: location,
history: {
pushState: function(){},
replaceState: function(state, title, url){
strictEqual(url, '/root/x/y');
}
}
});
Backbone.history.start({
root: 'root',
pushState: true
});
});
test("#1619: Router: Normalize empty root", 1, function() {
Backbone.history.stop();
location.replace('http://example.com/');
Backbone.history = _.extend(new Backbone.History, {
location: location,
history: {
pushState: function(){},
replaceState: function(){}
}
});
Backbone.history.start({root: ''});
strictEqual(Backbone.history.root, '/');
});
test("#1619: Router: nagivate with empty root", 1, function() {
Backbone.history.stop();
location.replace('http://example.com/');
Backbone.history = _.extend(new Backbone.History, {
location: location,
history: {
pushState: function(state, title, url) {
strictEqual(url, '/fragment');
}
}
});
Backbone.history.start({
pushState: true,
root: '',
hashChange: false
});
Backbone.history.navigate('fragment');
});
test("Transition from pushState to hashChange.", 1, function() {
Backbone.history.stop();
location.replace('http://example.com/root/x/y?a=b');
location.replace = function(url) {
strictEqual(url, '/root/?a=b#x/y');
};
Backbone.history = _.extend(new Backbone.History, {
location: location,
history: {
pushState: null,
replaceState: null
}
});
Backbone.history.start({
root: 'root',
pushState: true
});
});
});

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,30 +1,35 @@
# Benchmark.js <sup>v1.0.0-pre</sup>
# Benchmark.js <sup>v1.0.0</sup>
[![build status](https://secure.travis-ci.org/bestiejs/benchmark.js.png)](http://travis-ci.org/bestiejs/benchmark.js)
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
## Download
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.
* [Development source](https://raw.github.com/bestiejs/benchmark.js/v1.0.0/benchmark.js)
## Documentation
## Dive in
The documentation for Benchmark.js can be viewed here: <http://benchmarkjs.com/docs>
Weve got [API docs](http://benchmarkjs.com/docs) and [unit tests](http://benchmarkjs.com/tests).
For a list of upcoming features, check out our [roadmap](https://github.com/bestiejs/benchmark.js/wiki/Roadmap).
## Support
Benchmark.js has been tested in at least Adobe AIR 3.1, Chrome 5-21, Firefox 1-15, IE 6-9, Opera 9.25-12, Safari 3-6, Node.js 0.8.8, Narwhal 0.3.2, RingoJS 0.8, and Rhino 1.7RC5.
## Installation and usage
In a browser or Adobe AIR:
~~~ html
```html
<script src="benchmark.js"></script>
~~~
```
Optionally, expose Javas nanosecond timer by adding the `nano` applet to the `<body>`:
~~~ html
```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):
@@ -32,37 +37,37 @@ Or enable Chromes microsecond timer by using the [command line switch](http:/
Via [npm](http://npmjs.org/):
~~~ bash
```bash
npm install benchmark
~~~
```
In [Node.js](http://nodejs.org/) and [RingoJS v0.8.0+](http://ringojs.org/):
~~~ js
```js
var Benchmark = require('benchmark');
~~~
```
Optionally, use the [microtime module](https://github.com/wadey/node-microtime) by Wade Simmons:
~~~ bash
```bash
npm install microtime
~~~
```
In [Narwhal](http://narwhaljs.org/) and [RingoJS v0.7.0-](http://ringojs.org/):
In [RingoJS v0.7.0-](http://ringojs.org/):
~~~ js
```js
var Benchmark = require('benchmark').Benchmark;
~~~
```
In [Rhino](http://www.mozilla.org/rhino/):
~~~ js
```js
load('benchmark.js');
~~~
```
In an AMD loader like [RequireJS](http://requirejs.org/):
~~~ js
```js
require({
'paths': {
'benchmark': 'path/to/benchmark'
@@ -84,11 +89,11 @@ require({
Benchmark.platform = platform;
console.log(Benchmark.platform.name);
});
~~~
```
Usage example:
~~~ js
```js
var suite = new Benchmark.Suite;
// add tests
@@ -112,12 +117,11 @@ suite.add('RegExp#test', function() {
// > 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
## BestieJS
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>
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.
## Authors

View File

@@ -1,5 +1,5 @@
/*!
* Benchmark.js v1.0.0-pre <http://benchmarkjs.com/>
* Benchmark.js v1.0.0 <http://benchmarkjs.com/>
* Copyright 2010-2012 Mathias Bynens <http://mths.be/>
* Based on JSLitmus.js, copyright Robert Kieffer <http://broofa.com/>
* Modified by John-David Dalton <http://allyoucanleet.com/>
@@ -244,6 +244,36 @@
} catch(e) {
support.getAllKeys = false;
}
/**
* Detect if own properties are iterated before inherited properties (all but IE < 9).
*
* @name iteratesOwnLast
* @memberOf Benchmark.support
* @type Boolean
*/
support.iteratesOwnFirst = (function() {
var props = [];
function ctor() { this.x = 1; }
ctor.prototype = { 'y': 1 };
for (var prop in new ctor) { props.push(prop); }
return props[0] == 'x';
}());
/**
* Detect if a node's [[Class]] is resolvable (all but IE < 9)
* and that the JS engine errors when attempting to coerce an object to a
* string without a `toString` property value of `typeof` "function".
*
* @name nodeClass
* @memberOf Benchmark.support
* @type Boolean
*/
try {
support.nodeClass = ({ 'toString': 0 } + '', toString.call(doc || 0) != '[object Object]');
} catch(e) {
support.nodeClass = true;
}
}());
/**
@@ -583,14 +613,17 @@
while (++index < length) {
if ((index - start) in tail) {
object[index] = tail[index - start];
} else {
} else if (index in object) {
delete object[index];
}
}
// delete excess elements
deleteCount = deleteCount > elementCount ? deleteCount - elementCount : 0;
while (deleteCount--) {
delete object[length + deleteCount];
index = length + deleteCount;
if (index in object) {
delete object[index];
}
}
object.length = length;
return result;
@@ -941,7 +974,13 @@
// escape the `{` for Firefox 1
result = (/^[^{]+\{([\s\S]*)}\s*$/.exec(fn) || 0)[1];
}
return (result || '').replace(/^\s+|\s+$/g, '');
// trim string
result = (result || '').replace(/^\s+|\s+$/g, '');
// detect strings containing only the "use strict" directive
return /^(?:\/\*+[\w|\W]*?\*\/|\/\/.*?[\n\r\u2028\u2029]|\s)*(["'])use strict\1;?$/.test(result)
? ''
: result;
}
/**
@@ -994,36 +1033,43 @@
}
/**
* Checks if the specified `value` is an object created by the `Object`
* constructor assuming objects created by the `Object` constructor have no
* inherited enumerable properties and assuming there are no `Object.prototype`
* extensions.
* Checks if a given `value` is an object created by the `Object` constructor
* assuming objects created by the `Object` constructor have no inherited
* enumerable properties and that there are no `Object.prototype` extensions.
*
* @private
* @param {Mixed} value The value to check.
* @returns {Boolean} Returns `true` if `value` is an object, else `false`.
* @returns {Boolean} Returns `true` if the `value` is a plain `Object` object, else `false`.
*/
function isObject(value) {
var ctor,
result = !!value && toString.call(value) == '[object Object]';
if (result && noArgumentsClass) {
// avoid false positives for `arguments` objects in IE < 9
result = !isArguments(value);
function isPlainObject(value) {
// avoid non-objects and false positives for `arguments` objects in IE < 9
var result = false;
if (!(value && typeof value == 'object') || isArguments(value)) {
return result;
}
if (result) {
// IE < 9 presents nodes like `Object` objects:
// IE < 8 are missing the node's constructor property
// IE 8 node constructors are typeof "object"
ctor = value.constructor;
// check if the constructor is `Object` as `Object instanceof Object` is `true`
if ((result = isClassOf(ctor, 'Function') && ctor instanceof ctor)) {
// An object's own properties are iterated before inherited properties.
// If the last iterated key belongs to an object's own property then
// there are no inherited enumerable properties.
forProps(value, function(subValue, subKey) { result = subKey; });
result = result === true || hasKey(value, result);
// IE < 9 presents DOM nodes as `Object` objects except they have `toString`
// methods that are `typeof` "string" and still can coerce nodes to strings.
// Also check that the constructor is `Object` (i.e. `Object instanceof Object`)
var ctor = value.constructor;
if ((support.nodeClass || !(typeof value.toString != 'function' && typeof (value + '') == 'string')) &&
(!isClassOf(ctor, 'Function') || ctor instanceof ctor)) {
// In most environments an object's own properties are iterated before
// its inherited properties. If the last iterated property is an object's
// own property then there are no inherited enumerable properties.
if (support.iteratesOwnFirst) {
forProps(value, function(subValue, subKey) {
result = subKey;
});
return result === false || hasKey(value, result);
}
// IE < 9 iterates inherited properties before own properties. If the first
// iterated property is an object's own property then there are no inherited
// enumerable properties.
forProps(value, function(subValue, subKey) {
result = !hasKey(value, subKey);
return false;
});
return result === false;
}
return result;
}
@@ -1265,7 +1311,7 @@
break;
case '[object Object]':
isObject(value) && (clone = new ctor);
isPlainObject(value) && (clone = {});
break;
case '[object Number]':
@@ -2405,13 +2451,12 @@
var source = {
'setup': getSource(bench.setup, preprocess('m$.setup()')),
'fn': getSource(fn, preprocess('f$(' + fnArg + ')')),
'fn': getSource(fn, preprocess('m$.fn(' + fnArg + ')')),
'fnArg': fnArg,
'teardown': getSource(bench.teardown, preprocess('m$.teardown()'))
};
var compiled = bench.compiled,
count = bench.count = clone.count,
var count = bench.count = clone.count,
decompilable = support.decompilation || stringable,
id = bench.id,
isEmpty = !(source.fn || stringable),
@@ -2433,77 +2478,77 @@
}
}
if (!compiled) {
// compile in setup/teardown functions and the test loop
compiled = bench.compiled = createFunction(preprocess('t$'), interpolate(
preprocess(deferred
? 'var d$=this,#{fnArg}=d$,m$=d$.benchmark._original,f$=m$.fn,su$=m$.setup,td$=m$.teardown;' +
// when `deferred.cycles` is `0` then...
'if(!d$.cycles){' +
// set `deferred.fn`
'd$.fn=function(){var #{fnArg}=d$;if(typeof f$=="function"){try{#{fn}\n}catch(e$){f$(d$)}}else{#{fn}\n}};' +
// set `deferred.teardown`
'd$.teardown=function(){d$.cycles=0;if(typeof td$=="function"){try{#{teardown}\n}catch(e$){td$()}}else{#{teardown}\n}};' +
// execute the benchmark's `setup`
'if(typeof su$=="function"){try{#{setup}\n}catch(e$){su$()}}else{#{setup}\n};' +
// start timer
't$.start(d$);' +
// execute `deferred.fn` and return a dummy object
'}d$.fn();return{}'
// Compile in setup/teardown functions and the test loop.
// Create a new compiled test, instead of using the cached `bench.compiled`,
// to avoid potential engine optimizations enabled over the life of the test.
var compiled = bench.compiled = createFunction(preprocess('t$'), interpolate(
preprocess(deferred
? 'var d$=this,#{fnArg}=d$,m$=d$.benchmark._original,f$=m$.fn,su$=m$.setup,td$=m$.teardown;' +
// when `deferred.cycles` is `0` then...
'if(!d$.cycles){' +
// set `deferred.fn`
'd$.fn=function(){var #{fnArg}=d$;if(typeof f$=="function"){try{#{fn}\n}catch(e$){f$(d$)}}else{#{fn}\n}};' +
// set `deferred.teardown`
'd$.teardown=function(){d$.cycles=0;if(typeof td$=="function"){try{#{teardown}\n}catch(e$){td$()}}else{#{teardown}\n}};' +
// execute the benchmark's `setup`
'if(typeof su$=="function"){try{#{setup}\n}catch(e$){su$()}}else{#{setup}\n};' +
// start timer
't$.start(d$);' +
// execute `deferred.fn` and return a dummy object
'}d$.fn();return{}'
: 'var r$,s$,m$=this,f$=m$.fn,i$=m$.count,n$=t$.ns;#{setup}\n#{begin};' +
'while(i$--){#{fn}\n}#{end};#{teardown}\nreturn{elapsed:r$,uid:"#{uid}"}'),
: 'var r$,s$,m$=this,f$=m$.fn,i$=m$.count,n$=t$.ns;#{setup}\n#{begin};' +
'while(i$--){#{fn}\n}#{end};#{teardown}\nreturn{elapsed:r$,uid:"#{uid}"}'),
source
));
try {
if (isEmpty) {
// Firefox may remove dead code from Function#toString results
// http://bugzil.la/536085
throw new Error('The test "' + name + '" is empty. This may be the result of dead code removal.');
}
else if (!deferred) {
// pretest to determine if compiled code is exits early, usually by a
// rogue `return` statement, by checking for a return object with the uid
bench.count = 1;
compiled = (compiled.call(bench, timer) || {}).uid == uid && compiled;
bench.count = count;
}
} catch(e) {
compiled = null;
clone.error = e || new Error(String(e));
bench.count = count;
}
// fallback when a test exits early or errors during pretest
if (decompilable && !compiled && !deferred && !isEmpty) {
compiled = createFunction(preprocess('t$'), interpolate(
preprocess(
(clone.error && !stringable
? 'var r$,s$,m$=this,f$=m$.fn,i$=m$.count'
: 'function f$(){#{fn}\n}var r$,s$,m$=this,i$=m$.count'
) +
',n$=t$.ns;#{setup}\n#{begin};m$.f$=f$;while(i$--){m$.f$()}#{end};' +
'delete m$.f$;#{teardown}\nreturn{elapsed:r$}'
),
source
));
try {
if (isEmpty) {
// Firefox may remove dead code from Function#toString results
// http://bugzil.la/536085
throw new Error('The test "' + name + '" is empty. This may be the result of dead code removal.');
}
else if (!deferred) {
// pretest to determine if compiled code is exits early, usually by a
// rogue `return` statement, by checking for a return object with the uid
bench.count = 1;
compiled = (compiled.call(bench, timer) || {}).uid == uid && compiled;
bench.count = count;
}
} catch(e) {
compiled = null;
clone.error = e || new Error(String(e));
// pretest one more time to check for errors
bench.count = 1;
compiled.call(bench, timer);
bench.compiled = compiled;
bench.count = count;
delete clone.error;
}
// fallback when a test exits early or errors during pretest
if (decompilable && !compiled && !deferred && !isEmpty) {
compiled = createFunction(preprocess('t$'), interpolate(
preprocess(
(clone.error && !stringable
? 'var r$,s$,m$=this,f$=m$.fn,i$=m$.count'
: 'function f$(){#{fn}\n}var r$,s$,m$=this,i$=m$.count'
) +
',n$=t$.ns;#{setup}\n#{begin};m$.f$=f$;while(i$--){m$.f$()}#{end};' +
'delete m$.f$;#{teardown}\nreturn{elapsed:r$}'
),
source
));
try {
// pretest one more time to check for errors
bench.count = 1;
compiled.call(bench, timer);
catch(e) {
bench.count = count;
if (clone.error) {
compiled = null;
} else {
bench.compiled = compiled;
bench.count = count;
delete clone.error;
}
catch(e) {
bench.count = count;
if (clone.error) {
compiled = null;
} else {
bench.compiled = compiled;
clone.error = e || new Error(String(e));
}
clone.error = e || new Error(String(e));
}
}
}
@@ -3046,6 +3091,7 @@
/**
* The maximum time a benchmark is allowed to run before finishing (secs).
*
* Note: Cycle delays aren't counted toward the maximum time.
*
* @memberOf Benchmark.options
@@ -3219,7 +3265,7 @@
* @memberOf Benchmark
* @type String
*/
'version': '1.0.0-pre',
'version': '1.0.0',
// an object of environment/feature detection flags
'support': support,

View File

@@ -1,4 +1,4 @@
# Docdown <sup>v1.0.0-pre</sup>
# Docdown <sup>v1.0.0</sup>
A simple JSDoc to Markdown documentation generator.
@@ -12,7 +12,7 @@ For a list of upcoming features, check out our [roadmap](https://github.com/jdal
Usage example:
~~~ php
```php
require("docdown.php");
// generate Markdown
@@ -20,18 +20,7 @@ $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

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

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

View File

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

View File

@@ -163,7 +163,7 @@ class Generator {
$member = !$member ? $entry->getMembers(0) : $member;
$result = ($member ? $member . ($entry->isPlugin() ? 'prototype' : '') : '') . $entry->getCall();
$result = preg_replace('/\(\[|\[\]/', '', $result);
$result = preg_replace('/[ =\'"{}.()\]]/', '', $result);
$result = preg_replace('/[ =|\'"{}.()\]]/', '', $result);
$result = preg_replace('/[[#,]/', '-', $result);
return strtolower($result);
}
@@ -208,34 +208,54 @@ class Generator {
$openTag = "\n<!-- div -->\n";
$closeTag = "\n<!-- /div -->\n";
$result = array('# ' . $this->options['title']);
$toc = 'toc';
// initialize $api array
foreach ($this->entries as $entry) {
// skip invalid or private entries
$name = $entry->getName();
if (!$name || $entry->isPrivate()) {
continue;
}
if (!$entry->isPrivate()) {
$name = $entry->getName();
$members = $entry->getMembers();
$members = count($members) ? $members : array('');
$members = $entry->getMembers();
$members = count($members) ? $members : array('');
foreach ($members as $member) {
// create api category arrays
if (!isset($api[$member]) && $member) {
$api[$member] = new Entry('', '', $entry->lang);
$api[$member]->static = array();
$api[$member]->plugin = array();
foreach ($members as $member) {
// create api category arrays
if (!isset($api[$member]) && $member) {
// create temporary entry to be replaced later
$api[$member] = new Entry('', '', $entry->lang);
$api[$member]->static = array();
$api[$member]->plugin = array();
}
// append entry to api category
if (!$member || $entry->isCtor() || ($entry->getType() == 'Object' &&
!preg_match('/[=:]\s*(?:null|undefined)\s*[,;]?$/', $entry->entry))) {
// assign the real entry, replacing the temporary entry if it exist
$member = ($member ? $member . ($entry->isPlugin() ? '#' : '.') : '') . $name;
$entry->static = @$api[$member] ? $api[$member]->static : array();
$entry->plugin = @$api[$member] ? $api[$member]->plugin : array();
$api[$member] = $entry;
foreach ($entry->getAliases() as $alias) {
$api[$member] = $alias;
$alias->static = array();
$alias->plugin = array();
}
// append entry to api category
if (!$member || $entry->isCtor() || ($entry->getType() == 'Object' &&
!preg_match('/[=:]\s*null\s*[,;]?$/', $entry->entry))) {
$member = ($member ? $member . ($entry->isPlugin() ? '#' : '.') : '') . $name;
$entry->static = @$api[$member] ? $api[$member]->static : array();
$entry->plugin = @$api[$member] ? $api[$member]->plugin : array();
$api[$member] = $entry;
}
else if ($entry->isStatic()) {
$api[$member]->static[] = $entry;
foreach ($entry->getAliases() as $alias) {
$api[$member]->static[] = $alias;
}
else if ($entry->isStatic()) {
$api[$member]->static[] = $entry;
} else if (!$entry->isCtor()) {
$api[$member]->plugin[] = $entry;
}
else if (!$entry->isCtor()) {
$api[$member]->plugin[] = $entry;
foreach ($entry->getAliases() as $alias) {
$api[$member]->plugin[] = $alias;
}
}
}
@@ -304,10 +324,15 @@ class Generator {
$compiling = $compiling ? ($result[] = $closeTag) : true;
// assign TOC hash
if (count($result) == 2) {
$toc = $member;
}
// add root entry
array_push(
$result,
$openTag, '## ' . (count($result) == 2 ? '<a id="toc"></a>' : '') . '`' . $member . '`',
$openTag, '## ' . (count($result) == 2 ? '<a id="' . $toc . '"></a>' : '') . '`' . $member . '`',
Generator::interpolate('* [`' . $member . '`](##{hash})', $entry)
);
@@ -340,6 +365,11 @@ class Generator {
$result[] = $openTag;
foreach ($api as $entry) {
// skip aliases
if ($entry->isAlias()) {
continue;
}
// add root entry
$member = $entry->member . $entry->getName();
$compiling = $compiling ? ($result[] = $closeTag) : true;
@@ -364,6 +394,11 @@ class Generator {
// body
foreach ($subentries as $subentry) {
// skip aliases
if ($subentry->isAlias()) {
continue;
}
// description
array_push(
$result,
@@ -371,6 +406,14 @@ class Generator {
Generator::interpolate("### <a id=\"#{hash}\"></a>`#{member}#{separator}#{call}`\n<a href=\"##{hash}\">#</a> [&#x24C8;](#{href} \"View in source\") [&#x24C9;][1]\n\n#{desc}", $subentry)
);
// @alias
if (count($aliases = $subentry->getAliases())) {
array_push($result, '', '#### Aliases');
foreach ($aliases as $index => $alias) {
$aliases[$index] = $alias->getName();
}
$result[] = '*' . implode(', ', $aliases) . '*';
}
// @param
if (count($params = $subentry->getParams())) {
array_push($result, '', '#### Arguments');
@@ -401,7 +444,7 @@ class Generator {
}
// close tags add TOC link reference
array_push($result, $closeTag, $closeTag, '', ' [1]: #toc "Jump back to the TOC."');
array_push($result, $closeTag, $closeTag, '', ' [1]: #' . $toc . ' "Jump back to the TOC."');
// cleanup whitespace
return trim(preg_replace('/ +\n/', "\n", join($result, "\n")));

View File

@@ -1,4 +1,4 @@
# Platform.js <sup>v1.0.0-pre</sup>
# Platform.js <sup>v1.0.0</sup>
A platform detection library that works on nearly all JavaScript platforms<sup><a name="fnref1" href="#fn1">1</a></sup>.
@@ -16,35 +16,39 @@ The documentation for Platform.js can be viewed here: [/doc/README.md](https://g
For a list of upcoming features, check out our [roadmap](https://github.com/bestiejs/platform.js/wiki/Roadmap).
## Support
Platform.js has been tested in at least Adobe AIR 3.1, Chrome 5-21, Firefox 1-14, IE 6-9, Opera 9.25-12, Safari 3-6, Node.js 0.8.6, Narwhal 0.3.2, RingoJS 0.8, and Rhino 1.7RC5.
## Installation and usage
In a browser or Adobe AIR:
~~~ html
```html
<script src="platform.js"></script>
~~~
```
Via [npm](http://npmjs.org/):
~~~ bash
```bash
npm install platform
~~~
```
In [Narwhal](http://narwhaljs.org/), [Node.js](http://nodejs.org/), and [RingoJS](http://ringojs.org/):
In [Node.js](http://nodejs.org/) and [RingoJS](http://ringojs.org/):
~~~ js
```js
var platform = require('platform');
~~~
```
In [Rhino](http://www.mozilla.org/rhino/):
~~~ js
```js
load('platform.js');
~~~
```
In an AMD loader like [RequireJS](http://requirejs.org/):
~~~ js
```js
require({
'paths': {
'platform': 'path/to/platform'
@@ -53,11 +57,11 @@ require({
['platform'], function(platform) {
console.log(platform.name);
});
~~~
```
Usage example:
~~~ js
```js
// on IE10 x86 platform preview running in IE7 compatibility mode on Windows 7 64 bit edition
platform.name; // 'IE'
platform.version; // '10.0'
@@ -81,12 +85,7 @@ info.version; // '11.52'
info.layout; // 'Presto'
info.os; // 'Mac OS X 10.7.2'
info.description; // 'Opera 11.52 (identifying as Firefox 4.0) on Mac OS X 10.7.2'
~~~
## Footnotes
1. Platform.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>
```
## Author

View File

@@ -1,5 +1,5 @@
/*!
* Platform.js v1.0.0-pre <http://mths.be/platform>
* Platform.js v1.0.0 <http://mths.be/platform>
* Copyright 2010-2012 John-David Dalton <http://allyoucanleet.com/>
* Available under MIT license <http://mths.be/mit>
*/
@@ -297,7 +297,7 @@
'Opera Mini',
'Opera',
'Chrome',
{ 'label': 'Chrome Mobile', 'pattern': 'CrMo' },
{ 'label': 'Chrome Mobile', 'pattern': '(?:CriOS|CrMo)' },
{ 'label': 'Firefox', 'pattern': '(?:Firefox|Minefield)' },
{ 'label': 'IE', 'pattern': 'MSIE' },
'Safari'
@@ -512,12 +512,11 @@
/*------------------------------------------------------------------------*/
/**
* Return platform description when the platform object is coerced to a string.
* Returns `platform.description` when the platform object is coerced to a string.
*
* @name toString
* @memberOf platform
* @type Function
* @returns {String} The platform description.
* @returns {String} Returns `platform.description` if available, else an empty string.
*/
function toStringPlatform() {
return this.description || '';
@@ -573,7 +572,7 @@
// detect non-Opera versions (order is important)
if (!version) {
version = getVersion([
'(?:Cloud9|CrMo|Opera ?Mini|Raven|Silk(?!/[\\d.]+$))',
'(?:Cloud9|CriOS|CrMo|Opera ?Mini|Raven|Silk(?!/[\\d.]+$))',
'Version',
qualify(name),
'(?:Firefox|Minefield|NetFront)'
@@ -708,7 +707,7 @@
))
) && !reOpera.test(data = parse.call(forOwn, ua.replace(reOpera, '') + ';')) && data.name) {
// when "indentifying" the UA contains both Opera and the other browser's name
// when "indentifying", the UA contains both Opera and the other browser's name
data = 'ing as ' + data.name + ((data = data.version) ? ' ' + data : '');
if (reOpera.test(name)) {
if (/IE/.test(data) && os == 'Mac OS') {
@@ -716,7 +715,7 @@
}
data = 'identify' + data;
}
// when "masking" the UA contains only the other browser's name
// when "masking", the UA contains only the other browser's name
else {
data = 'mask' + data;
if (operaClass) {
@@ -760,7 +759,7 @@
data = (data = data[0], data < 400 ? 1 : data < 500 ? 2 : data < 526 ? 3 : data < 533 ? 4 : data < 534 ? '4+' : data < 535 ? 5 : '5');
} else {
layout[1] = 'like Chrome';
data = data[1] || (data = data[0], data < 530 ? 1 : data < 532 ? 2 : data < 532.05 ? 3 : data < 533 ? 4 : data < 534.03 ? 5 : data < 534.07 ? 6 : data < 534.10 ? 7 : data < 534.13 ? 8 : data < 534.16 ? 9 : data < 534.24 ? 10 : data < 534.30 ? 11 : data < 535.01 ? 12 : data < 535.02 ? '13+' : data < 535.07 ? 15 : data < 535.11 ? 16 : data < 535.19 ? 17 : data < 535.21 ? 18 : '19');
data = data[1] || (data = data[0], data < 530 ? 1 : data < 532 ? 2 : data < 532.05 ? 3 : data < 533 ? 4 : data < 534.03 ? 5 : data < 534.07 ? 6 : data < 534.10 ? 7 : data < 534.13 ? 8 : data < 534.16 ? 9 : data < 534.24 ? 10 : data < 534.30 ? 11 : data < 535.01 ? 12 : data < 535.02 ? '13+' : data < 535.07 ? 15 : data < 535.11 ? 16 : data < 535.19 ? 17 : data < 536.05 ? 18 : data < 536.10 ? 19 : data < 537.01 ? 20 : '21');
}
// add the postfix of ".x" or "+" for approximate versions
layout[1] += ' ' + (data += typeof data == 'number' ? '.x' : /[.+]/.test(data) ? '' : '+');
@@ -769,6 +768,30 @@
version = data;
}
}
// detect Opera desktop modes
if (name == 'Opera' && (data = /(?:zbov|zvav)$/.exec(os))) {
name += ' ';
description.unshift('desktop mode');
if (data == 'zvav') {
name += 'Mini';
version = null;
} else {
name += 'Mobile';
}
}
// detect Chrome desktop mode
else if (name == 'Safari' && /Chrome/.exec(layout[1])) {
description.unshift('desktop mode');
name = 'Chrome Mobile';
version = null;
if (/Mac OS X/.test(os)) {
manufacturer = 'Apple';
os = 'iOS 4.3+';
} else {
os = null;
}
}
// strip incorrect OS versions
if (version && version.indexOf(data = /[\d.]+$/.exec(os)) == 0 &&
ua.indexOf('/' + data + '-') > -1) {
@@ -793,9 +816,25 @@
if (product) {
description.push((/^on /.test(description[description.length -1]) ? '' : 'on ') + product);
}
// parse OS into an object
if (os) {
data = / ([\d.+]+)$/.exec(os);
os = {
'architecture': 32,
'family': data ? os.replace(data[0], '') : os,
'version': data ? data[1] : null,
'toString': function() {
var version = this.version;
return this.family + (version ? ' ' + version : '') + (this.architecture == 64 ? ' 64-bit' : '');
}
};
}
// add browser/OS architecture
if ((data = /\b(?:AMD|IA|Win|WOW|x86_|x)64\b/i).test(arch) && !/\bi686\b/i.test(arch)) {
os = os && os + (data.test(os) ? '' : ' 64-bit');
if ((data = /\b(?:AMD|IA|Win|WOW|x86_|x)64\b/i.exec(arch)) && !/\bi686\b/i.test(arch)) {
if (os) {
os.architecture = 64;
os.family = os.family.replace(RegExp(' *' + data), '');
}
if (name && (/WOW64/i.test(ua) ||
(useFeatures && /\w(?:86|32)$/.test(nav.cpuClass || nav.platform)))) {
description.unshift('32-bit');
@@ -834,11 +873,46 @@
* The name of the operating system.
*
* @memberOf platform
* @type String|Null
* @type Object
*/
'os': os && (name &&
!(os == os.split(' ')[0] && (os == name.split(' ')[0] || product)) &&
description.push(product ? '(' + os + ')' : 'on ' + os), os),
'os': os
? (name &&
!(os == String(os).split(' ')[0] && (os == name.split(' ')[0] || product)) &&
description.push(product ? '(' + os + ')' : 'on ' + os), os)
: {
/**
* The CPU architecture the OS is built for.
*
* @memberOf platform.os
* @type String|Null
*/
'architecture': null,
/**
* The family of the OS.
*
* @memberOf platform.os
* @type String|Null
*/
'family': null,
/**
* The version of the OS.
*
* @memberOf platform.os
* @type String|Null
*/
'version': null,
/**
* Returns the OS string.
*
* @memberOf platform.os
* @returns {String} The OS string.
*/
'toString': function() { return 'null'; }
},
/**
* The platform description.

View File

@@ -1,15 +1,19 @@
# QUnit CLIB <sup>v1.0.0-pre</sup>
# QUnit CLIB <sup>v1.0.0</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>.
QUnit CLIB helps extend QUnit's CLI support to many common CLI environments.
## Screenshot
![QUnit CLIB brings QUnit to your favorite shell.](http://i.imgur.com/jpu9l.png)
## Support
QUnit CLIB has been tested in at least Node.js 0.4.8-0.8.6, Narwhal v0.3.2, RingoJS v0.8.0, and Rhino v1.7RC3-RC5.
## Usage
~~~ js
```js
(function(window) {
// use a single load function
@@ -39,25 +43,13 @@ QUnit CLIB helps extend QUnit's CLI support to many common CLI environments<sup>
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>
1. QUnit v1.3.0 does not work with Narwhal or Ringo < v0.8.0
2. QUnit v1.3.0 does not work with Narwhal or Ringo < v0.8.0
2. Rhino v1.7RC4 does not support timeout fallbacks `clearTimeout` and `setTimeout`
## Author

View File

@@ -1,5 +1,5 @@
/*!
* QUnit CLI Boilerplate v1.0.0-pre
* QUnit CLI Boilerplate v1.0.0
* 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>
@@ -92,6 +92,8 @@
* @returns {Number} The the ID of the timeout.
*/
function schedule(fn, delay, args, repeated) {
// Rhino 1.7RC4 will error assigning `task` below
// https://bugzilla.mozilla.org/show_bug.cgi?id=775566
var task = ids[++counter] = new JavaAdapter(java.util.TimerTask, {
'run': function() {
fn.apply(global, args);
@@ -169,7 +171,12 @@
// exit out of Node.js
try {
process.exit();
if (details.failed) {
console.error('Error: ' + details.failed + ' of ' + details.total + ' tests failed.');
process.exit(1);
} else {
process.exit(0);
}
} catch(e) { }
}

View File

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

View File

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

View File

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

View File

@@ -1,21 +1,25 @@
/** vim: et:ts=4:sw=4:sts=4
* @license RequireJS 2.0.4 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
* @license RequireJS 2.0.6 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
* Available via the MIT or new BSD license.
* see: http://github.com/jrburke/requirejs for details
*/
/*jslint regexp: true, nomen: true */
//Not using strict: uneven strict support in browsers, #392, and causes
//problems with requirejs.exec()/transpiler plugins that may not be strict.
/*jslint regexp: true, nomen: true, sloppy: true */
/*global window, navigator, document, importScripts, jQuery, setTimeout, opera */
var requirejs, require, define;
(function (global) {
'use strict';
var version = '2.0.4',
var req, s, head, baseElement, dataMain, src,
interactiveScript, currentlyAddingScript, mainScript, subPath,
version = '2.0.6',
commentRegExp = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,
cjsRequireRegExp = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,
jsSuffixRegExp = /\.js$/,
currDirRegExp = /^\.\//,
ostring = Object.prototype.toString,
op = Object.prototype,
ostring = op.toString,
hasOwn = op.hasOwnProperty,
ap = Array.prototype,
aps = ap.slice,
apsp = ap.splice,
@@ -33,9 +37,7 @@ var requirejs, require, define;
contexts = {},
cfg = {},
globalDefQueue = [],
useInteractive = false,
req, s, head, baseElement, dataMain, src,
interactiveScript, currentlyAddingScript, mainScript, subPath;
useInteractive = false;
function isFunction(it) {
return ostring.call(it) === '[object Function]';
@@ -76,7 +78,7 @@ var requirejs, require, define;
}
function hasProp(obj, prop) {
return obj.hasOwnProperty(prop);
return hasOwn.call(obj, prop);
}
/**
@@ -152,7 +154,7 @@ var requirejs, require, define;
//look up paths relative to the moduleName
var args = aps.call(arguments, 0), lastArg;
if (enableBuildCallback &&
isFunction((lastArg = args[args.length - 1]))) {
isFunction((lastArg = args[args.length - 1]))) {
lastArg.__requireJsBuild = true;
}
args.push(relMap);
@@ -169,14 +171,14 @@ var requirejs, require, define;
], function (item) {
var prop = item[1] || item[0];
req[item[0]] = context ? makeContextModuleFunc(context[prop], relMap) :
//If no context, then use default context. Reference from
//contexts instead of early binding to default context, so
//that during builds, the latest instance of the default
//context with its config gets used.
function () {
var ctx = contexts[defContextName];
return ctx[prop].apply(ctx, arguments);
};
//If no context, then use default context. Reference from
//contexts instead of early binding to default context, so
//that during builds, the latest instance of the default
//context with its config gets used.
function () {
var ctx = contexts[defContextName];
return ctx[prop].apply(ctx, arguments);
};
});
}
@@ -221,7 +223,9 @@ var requirejs, require, define;
}
function newContext(contextName) {
var config = {
var inCheckLoaded, Module, context, handlers,
checkLoadedTimeoutId,
config = {
waitSeconds: 7,
baseUrl: './',
paths: {},
@@ -239,9 +243,7 @@ var requirejs, require, define;
//should be executed, by the order they
//load. Important for consistent cycle resolution
//behavior.
waitAry = [],
inCheckLoaded, Module, context, handlers,
checkLoadedTimeoutId;
waitAry = [];
/**
* Trims the . and .. from an array of path segments.
@@ -254,7 +256,7 @@ var requirejs, require, define;
*/
function trimDots(ary) {
var i, part;
for (i = 0; ary[i]; i+= 1) {
for (i = 0; ary[i]; i += 1) {
part = ary[i];
if (part === '.') {
ary.splice(i, 1);
@@ -287,12 +289,12 @@ var requirejs, require, define;
* @returns {String} normalized name
*/
function normalize(name, baseName, applyMap) {
var baseParts = baseName && baseName.split('/'),
var pkgName, pkgConfig, mapValue, nameParts, i, j, nameSegment,
foundMap, foundI, foundStarMap, starI,
baseParts = baseName && baseName.split('/'),
normalizedBaseParts = baseParts,
map = config.map,
starMap = map && map['*'],
pkgName, pkgConfig, mapValue, nameParts, i, j, nameSegment,
foundMap;
starMap = map && map['*'];
//Adjust any relative paths.
if (name && name.charAt(0) === '.') {
@@ -343,28 +345,41 @@ var requirejs, require, define;
for (j = baseParts.length; j > 0; j -= 1) {
mapValue = map[baseParts.slice(0, j).join('/')];
//baseName segment has config, find if it has one for
//baseName segment has config, find if it has one for
//this name.
if (mapValue) {
mapValue = mapValue[nameSegment];
if (mapValue) {
//Match, update name to the new value.
foundMap = mapValue;
foundI = i;
break;
}
}
}
}
if (!foundMap && starMap && starMap[nameSegment]) {
foundMap = starMap[nameSegment];
}
if (foundMap) {
nameParts.splice(0, i, foundMap);
name = nameParts.join('/');
break;
}
//Check for a star map match, but just hold on to it,
//if there is a shorter segment match later in a matching
//config, then favor over this star map.
if (!foundStarMap && starMap && starMap[nameSegment]) {
foundStarMap = starMap[nameSegment];
starI = i;
}
}
if (!foundMap && foundStarMap) {
foundMap = foundStarMap;
foundI = starI;
}
if (foundMap) {
nameParts.splice(0, foundI, foundMap);
name = nameParts.join('/');
}
}
@@ -375,7 +390,7 @@ var requirejs, require, define;
if (isBrowser) {
each(scripts(), function (scriptNode) {
if (scriptNode.getAttribute('data-requiremodule') === name &&
scriptNode.getAttribute('data-requirecontext') === context.contextName) {
scriptNode.getAttribute('data-requirecontext') === context.contextName) {
scriptNode.parentNode.removeChild(scriptNode);
return true;
}
@@ -412,13 +427,13 @@ var requirejs, require, define;
* @returns {Object}
*/
function makeModuleMap(name, parentModuleMap, isNormalized, applyMap) {
var index = name ? name.indexOf('!') : -1,
var url, pluginModule, suffix,
index = name ? name.indexOf('!') : -1,
prefix = null,
parentName = parentModuleMap ? parentModuleMap.name : null,
originalName = name,
isDefine = true,
normalizedName = '',
url, pluginModule, suffix;
normalizedName = '';
//If no name, then it means it is a require call, generate an
//internal name.
@@ -471,8 +486,8 @@ var requirejs, require, define;
originalName: originalName,
isDefine: isDefine,
id: (prefix ?
prefix + '!' + normalizedName :
normalizedName) + suffix
prefix + '!' + normalizedName :
normalizedName) + suffix
};
}
@@ -492,7 +507,7 @@ var requirejs, require, define;
mod = registry[id];
if (hasProp(defined, id) &&
(!mod || mod.defineEmitComplete)) {
(!mod || mod.defineEmitComplete)) {
if (name === 'defined') {
fn(defined[id]);
}
@@ -597,7 +612,7 @@ var requirejs, require, define;
});
}
function findCycle(mod, traced) {
function findCycle(mod, traced, processed) {
var id = mod.map.id,
depArray = mod.depMaps,
foundModule;
@@ -620,28 +635,16 @@ var requirejs, require, define;
var depId = depMap.id,
depMod = registry[depId];
if (!depMod) {
if (!depMod || processed[depId] ||
!depMod.inited || !depMod.enabled) {
return;
}
if (!depMod.inited || !depMod.enabled) {
//Dependency is not inited, so this cannot
//be used to determine a cycle.
foundModule = null;
delete traced[id];
return true;
}
//mixin traced to a new object for each dependency, so that
//sibling dependencies in this object to not generate a
//false positive match on a cycle. Ideally an Object.create
//type of prototype delegation would be used here, but
//optimizing for file size vs. execution speed since hopefully
//the trees are small for circular dependency scans relative
//to the full app perf.
return (foundModule = findCycle(depMod, mixin({}, traced)));
return (foundModule = findCycle(depMod, traced, processed));
});
processed[id] = true;
return foundModule;
}
@@ -659,7 +662,7 @@ var requirejs, require, define;
traced[id] = mod;
each(depArray, function(depMap) {
each(depArray, function (depMap) {
var depId = depMap.id,
depMod = registry[depId],
value;
@@ -699,13 +702,13 @@ var requirejs, require, define;
}
function checkLoaded() {
var waitInterval = config.waitSeconds * 1000,
var map, modId, err, usingPathFallback,
waitInterval = config.waitSeconds * 1000,
//It is possible to disable the wait interval by using waitSeconds of 0.
expired = waitInterval && (context.startTime + waitInterval) < new Date().getTime(),
noLoads = [],
stillLoading = false,
needCycleCheck = true,
map, modId, err, usingPathFallback;
needCycleCheck = true;
//Do not bother if this call was a result of a cycle break.
if (inCheckLoaded) {
@@ -764,7 +767,7 @@ var requirejs, require, define;
return;
}
var cycleMod = findCycle(mod, {}),
var cycleMod = findCycle(mod, {}, {}),
traced = {};
if (cycleMod) {
@@ -819,7 +822,7 @@ var requirejs, require, define;
};
Module.prototype = {
init: function(depMaps, factory, errback, options) {
init: function (depMaps, factory, errback, options) {
options = options || {};
//Do not do more inits if already done. Can happen if there
@@ -916,7 +919,7 @@ var requirejs, require, define;
}
},
load: function() {
load: function () {
var url = this.map.url;
//Regular dependency.
@@ -937,11 +940,11 @@ var requirejs, require, define;
return;
}
var id = this.map.id,
var err, cjsModule,
id = this.map.id,
depExports = this.depExports,
exports = this.exports,
factory = this.factory,
err, cjsModule;
factory = this.factory;
if (!this.inited) {
this.fetch();
@@ -974,9 +977,9 @@ var requirejs, require, define;
//favor a non-undefined return value over exports use.
cjsModule = this.module;
if (cjsModule &&
cjsModule.exports !== undefined &&
//Make sure it is not already the exports value
cjsModule.exports !== this.exports) {
cjsModule.exports !== undefined &&
//Make sure it is not already the exports value
cjsModule.exports !== this.exports) {
exports = cjsModule.exports;
} else if (exports === undefined && this.usingExports) {
//exports already set the defined value.
@@ -1032,15 +1035,15 @@ var requirejs, require, define;
}
},
callPlugin: function() {
callPlugin: function () {
var map = this.map,
id = map.id,
pluginMap = makeModuleMap(map.prefix, null, false, true);
on(pluginMap, 'defined', bind(this, function (plugin) {
var name = this.map.name,
parentName = this.map.parentMap ? this.map.parentMap.name : null,
load, normalizedMap, normalizedMod;
var load, normalizedMap, normalizedMod,
name = this.map.name,
parentName = this.map.parentMap ? this.map.parentMap.name : null;
//If current map is not normalized, wait for that
//normalized name to load instead of continuing.
@@ -1057,12 +1060,12 @@ var requirejs, require, define;
false,
true);
on(normalizedMap,
'defined', bind(this, function (value) {
this.init([], function () { return value; }, null, {
enabled: true,
ignore: true
});
}));
'defined', bind(this, function (value) {
this.init([], function () { return value; }, null, {
enabled: true,
ignore: true
});
}));
normalizedMod = registry[normalizedMap.id];
if (normalizedMod) {
if (this.events.error) {
@@ -1127,9 +1130,9 @@ var requirejs, require, define;
//Use parentName here since the plugin's name is not reliable,
//could be some weird string with no path that actually wants to
//reference the parentName's path.
plugin.load(map.name, makeRequire(map.parentMap, true, function (deps, cb) {
plugin.load(map.name, makeRequire(map.parentMap, true, function (deps, cb, er) {
deps.rjsSkipMap = true;
return context.require(deps, cb);
return context.require(deps, cb, er);
}), load, config);
}));
@@ -1209,7 +1212,7 @@ var requirejs, require, define;
this.check();
},
on: function(name, cb) {
on: function (name, cb) {
var cbs = this.events[name];
if (!cbs) {
cbs = this.events[name] = [];
@@ -1362,7 +1365,12 @@ var requirejs, require, define;
//update the maps for them, since their info, like URLs to load,
//may have changed.
eachProp(registry, function (mod, id) {
mod.map = makeModuleMap(id);
//If module already has init called, since it is too
//late to modify them, and ignore unnormalized ones
//since they are transient.
if (!mod.inited && !mod.map.unnormalized) {
mod.map = makeModuleMap(id);
}
});
//If a deps array or a config callback is specified, then call
@@ -1471,6 +1479,10 @@ var requirejs, require, define;
},
undef: function (id) {
//Bind any waiting define() calls to this context,
//fix for #408
takeGlobalQueue();
var map = makeModuleMap(id, null, true),
mod = registry[id];
@@ -1509,9 +1521,9 @@ var requirejs, require, define;
* @param {String} moduleName the name of the module to potentially complete.
*/
completeLoad: function (moduleName) {
var shim = config.shim[moduleName] || {},
shExports = shim.exports && shim.exports.exports,
found, args, mod;
var found, args, mod,
shim = config.shim[moduleName] || {},
shExports = shim.exports && shim.exports.exports;
takeGlobalQueue();
@@ -1538,9 +1550,7 @@ var requirejs, require, define;
//of those calls/init calls changes the registry.
mod = registry[moduleName];
if (!found &&
!defined[moduleName] &&
mod && !mod.inited) {
if (!found && !defined[moduleName] && mod && !mod.inited) {
if (config.enforceDefine && (!shExports || !getGlobal(shExports))) {
if (hasPathFallback(moduleName)) {
return;
@@ -1633,7 +1643,8 @@ var requirejs, require, define;
}
//Join the path parts together, then figure out if baseUrl is needed.
url = syms.join('/') + (ext || '.js');
url = syms.join('/');
url += (ext || (/\?/.test(url) ? '' : '.js'));
url = (url.charAt(0) === '/' || url.match(/^[\w\+\.\-]+:/) ? '' : config.baseUrl) + url;
}
@@ -1670,7 +1681,7 @@ var requirejs, require, define;
//all old browsers will be supported, but this one was easy enough
//to support and still makes sense.
if (evt.type === 'load' ||
(readyRegExp.test((evt.currentTarget || evt.srcElement).readyState))) {
(readyRegExp.test((evt.currentTarget || evt.srcElement).readyState))) {
//Reset interactive script so a script node is not held onto for
//to long.
interactiveScript = null;
@@ -1710,8 +1721,8 @@ var requirejs, require, define;
req = requirejs = function (deps, callback, errback, optional) {
//Find the right context, use default
var contextName = defContextName,
context, config;
var context, config,
contextName = defContextName;
// Determine if have config object in the call.
if (!isArray(deps) && typeof deps !== 'string') {
@@ -1810,8 +1821,8 @@ var requirejs, require, define;
if (isBrowser) {
//In the browser so use a script tag
node = config.xhtml ?
document.createElementNS('http://www.w3.org/1999/xhtml', 'html:script') :
document.createElement('script');
document.createElementNS('http://www.w3.org/1999/xhtml', 'html:script') :
document.createElement('script');
node.type = config.scriptType || 'text/javascript';
node.charset = 'utf-8';
node.async = true;
@@ -1828,15 +1839,15 @@ var requirejs, require, define;
//UNFORTUNATELY Opera implements attachEvent but does not follow the script
//script execution mode.
if (node.attachEvent &&
//Check if node.attachEvent is artificially added by custom script or
//natively supported by browser
//read https://github.com/jrburke/requirejs/issues/187
//if we can NOT find [native code] then it must NOT natively supported.
//in IE8, node.attachEvent does not have toString()
//Note the test for "[native code" with no closing brace, see:
//https://github.com/jrburke/requirejs/issues/273
!(node.attachEvent.toString && node.attachEvent.toString().indexOf('[native code') < 0) &&
!isOpera) {
//Check if node.attachEvent is artificially added by custom script or
//natively supported by browser
//read https://github.com/jrburke/requirejs/issues/187
//if we can NOT find [native code] then it must NOT natively supported.
//in IE8, node.attachEvent does not have toString()
//Note the test for "[native code" with no closing brace, see:
//https://github.com/jrburke/requirejs/issues/273
!(node.attachEvent.toString && node.attachEvent.toString().indexOf('[native code') < 0) &&
!isOpera) {
//Probably IE. IE (at least 6-8) do not fire
//script onload right after executing the script, so
//we cannot tie the anonymous define call to a name.

View File

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

View File

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

View File

@@ -2,7 +2,7 @@ $(document).ready(function() {
module("Collections");
test("collections: each", function() {
test("each", function() {
_.each([1, 2, 3], function(num, i) {
equal(num, i + 1, 'each iterators provide value and iteration count');
});
@@ -25,13 +25,9 @@ $(document).ready(function() {
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() {
test('map', function() {
var doubled = _.map([1, 2, 3], function(num){ return num * 2; });
equal(doubled.join(', '), '2, 4, 6', 'doubled numbers');
@@ -44,17 +40,19 @@ $(document).ready(function() {
var doubled = _([1, 2, 3]).map(function(num){ return num * 2; });
equal(doubled.join(', '), '2, 4, 6', 'OO-style doubled numbers');
if (document.querySelectorAll) {
var ids = _.map(document.querySelectorAll('#map-test *'), function(n){ return n.id; });
deepEqual(ids, ['id1', 'id2'], 'Can use collection methods on NodeLists.');
}
var ids = _.map($('#map-test').children(), function(n){ return n.id; });
deepEqual(ids, ['id1', 'id2'], 'Can use collection methods on NodeLists.');
deepEqual(ids, ['id1', 'id2'], 'Can use collection methods on jQuery Array-likes.');
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() {
test('reduce', function() {
var sum = _.reduce([1, 2, 3], function(sum, num){ return sum + num; }, 0);
equal(sum, 6, 'can sum up an array');
@@ -71,20 +69,11 @@ $(document).ready(function() {
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() {
test('reduceRight', function() {
var list = _.reduceRight(["foo", "bar", "baz"], function(memo, str){ return memo + str; }, '');
equal(list, 'bazbarfoo', 'can perform right folds');
@@ -94,32 +83,58 @@ $(document).ready(function() {
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');
var sum = _.reduceRight({a: 1, b: 2, c: 3}, function(sum, num){ return sum + num; });
equal(sum, 6, 'default initial value on object');
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');
// Assert that the correct arguments are being passed.
var args,
memo = {},
object = {a: 1, b: 2},
lastKey = _.keys(object).pop();
var expected = lastKey == 'a'
? [memo, 1, 'a', object]
: [memo, 2, 'b', object];
_.reduceRight(object, function() {
args || (args = _.toArray(arguments));
}, memo);
deepEqual(args, expected);
// And again, with numeric keys.
object = {'2': 'a', '1': 'b'};
lastKey = _.keys(object).pop();
args = null;
expected = lastKey == '2'
? [memo, 'a', '2', object]
: [memo, 'b', '1', object];
_.reduceRight(object, function() {
args || (args = _.toArray(arguments));
}, memo);
deepEqual(args, expected);
});
test('collections: find', function() {
test('find', function() {
var array = [1, 2, 3, 4];
strictEqual(_.find(array, function(n) { return n > 2; }), 3, 'should return first found `value`');
strictEqual(_.find(array, function() { return false; }), void 0, 'should return `undefined` if `value` is not found');
});
test('collections: detect', function() {
test('detect', function() {
var result = _.detect([1, 2, 3], function(num){ return num * 2 == 4; });
equal(result, 2, 'found the first "2" and broke the loop');
});
test('collections: select', function() {
test('select', function() {
var evens = _.select([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
equal(evens.join(', '), '2, 4, 6', 'selected each even number');
@@ -127,12 +142,12 @@ $(document).ready(function() {
equal(evens.join(', '), '2, 4, 6', 'aliased as "filter"');
});
test('collections: reject', function() {
test('reject', function() {
var odds = _.reject([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
equal(odds.join(', '), '1, 3, 5', 'rejected each even number');
});
test('collections: all', function() {
test('all', function() {
ok(_.all([], _.identity), 'the empty set');
ok(_.all([true, true, true], _.identity), 'all true values');
ok(!_.all([true, false, true], _.identity), 'one false value');
@@ -141,9 +156,10 @@ $(document).ready(function() {
ok(_.all([1], _.identity) === true, 'cast to boolean - true');
ok(_.all([0], _.identity) === false, 'cast to boolean - false');
ok(_.every([true, true, true], _.identity), 'aliased as "every"');
ok(!_.all([undefined, undefined, undefined], _.identity), 'works with arrays of undefined');
});
test('collections: any', function() {
test('any', function() {
var nativeSome = Array.prototype.some;
Array.prototype.some = null;
ok(!_.any([]), 'the empty set');
@@ -159,21 +175,21 @@ $(document).ready(function() {
Array.prototype.some = nativeSome;
});
test('collections: include', function() {
test('include', function() {
ok(_.include([1,2,3], 2), 'two is in the array');
ok(!_.include([1,3,9], 2), 'two is not in the array');
ok(_.contains({moe:1, larry:3, curly:9}, 3) === true, '_.include on objects checks their values');
ok(_([1,2,3]).include(2), 'OO-style include');
});
test('collections: invoke', function() {
test('invoke', function() {
var list = [[5, 1, 7], [3, 2, 1]];
var result = _.invoke(list, 'sort');
equal(result[0].join(', '), '1, 5, 7', 'first array sorted');
equal(result[1].join(', '), '1, 2, 3', 'second array sorted');
});
test('collections: invoke w/ function reference', function() {
test('invoke w/ function reference', function() {
var list = [[5, 1, 7], [3, 2, 1]];
var result = _.invoke(list, Array.prototype.sort);
equal(result[0].join(', '), '1, 5, 7', 'first array sorted');
@@ -181,7 +197,7 @@ $(document).ready(function() {
});
// Relevant when using ClojureScript
test('collections: invoke when strings have a call method', function() {
test('invoke when strings have a call method', function() {
String.prototype.call = function() {
return 42;
};
@@ -195,12 +211,22 @@ $(document).ready(function() {
equal(s.call, undefined, "call function removed");
});
test('collections: pluck', function() {
test('pluck', function() {
var people = [{name : 'moe', age : 30}, {name : 'curly', age : 50}];
equal(_.pluck(people, 'name').join(', '), 'moe, curly', 'pulls names out of objects');
});
test('collections: max', function() {
test('where', function() {
var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}];
var result = _.where(list, {a: 1});
equal(result.length, 3);
equal(result[result.length - 1].b, 4);
result = _.where(list, {b: 2});
equal(result.length, 2);
equal(result[0].a, 1);
});
test('max', function() {
equal(3, _.max([1, 2, 3]), 'can perform a regular Math.max');
var neg = _.max([1, 2, 3], function(num){ return -num; });
@@ -212,7 +238,7 @@ $(document).ready(function() {
equal(299999, _.max(_.range(1,300000)), "Maximum value of a too-big array");
});
test('collections: min', function() {
test('min', function() {
equal(1, _.min([1, 2, 3]), 'can perform a regular Math.min');
var neg = _.min([1, 2, 3], function(num){ return -num; });
@@ -228,7 +254,7 @@ $(document).ready(function() {
equal(1, _.min(_.range(1,300000)), "Minimum value of a too-big array");
});
test('collections: sortBy', function() {
test('sortBy', function() {
var people = [{name : 'curly', age : 50}, {name : 'moe', age : 30}];
people = _.sortBy(people, function(person){ return person.age; });
equal(_.pluck(people, 'name').join(', '), 'moe, curly', 'stooges sorted by age');
@@ -239,9 +265,32 @@ $(document).ready(function() {
var list = ["one", "two", "three", "four", "five"];
var sorted = _.sortBy(list, 'length');
equal(sorted.join(' '), 'one two four five three', 'sorted by length');
function Pair(x, y) {
this.x = x;
this.y = y;
}
var collection = [
new Pair(1, 1), new Pair(1, 2),
new Pair(1, 3), new Pair(1, 4),
new Pair(1, 5), new Pair(1, 6),
new Pair(2, 1), new Pair(2, 2),
new Pair(2, 3), new Pair(2, 4),
new Pair(2, 5), new Pair(2, 6),
new Pair(undefined, 1), new Pair(undefined, 2),
new Pair(undefined, 3), new Pair(undefined, 4),
new Pair(undefined, 5), new Pair(undefined, 6)
];
var actual = _.sortBy(collection, function(pair) {
return pair.x;
});
deepEqual(actual, collection, 'sortBy should be stable');
});
test('collections: groupBy', function() {
test('groupBy', function() {
var parity = _.groupBy([1, 2, 3, 4, 5, 6], function(num){ return num % 2; });
ok('0' in parity && '1' in parity, 'created a group for each value');
equal(parity[0].join(', '), '2, 4, 6', 'put each even number in the right group');
@@ -251,25 +300,70 @@ $(document).ready(function() {
equal(grouped['3'].join(' '), 'one two six ten');
equal(grouped['4'].join(' '), 'four five nine');
equal(grouped['5'].join(' '), 'three seven eight');
var context = {};
_.groupBy([{}], function(){ ok(this === context); }, context);
grouped = _.groupBy([4.2, 6.1, 6.4], function(num) {
return Math.floor(num) > 4 ? 'hasOwnProperty' : 'constructor';
});
equal(grouped.constructor.length, 1);
equal(grouped.hasOwnProperty.length, 2);
var array = [{}];
_.groupBy(array, function(value, index, obj){ ok(obj === array); });
});
test('collections: sortedIndex', function() {
test('countBy', function() {
var parity = _.countBy([1, 2, 3, 4, 5], function(num){ return num % 2 == 0; });
equal(parity['true'], 2);
equal(parity['false'], 3);
var list = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"];
var grouped = _.countBy(list, 'length');
equal(grouped['3'], 4);
equal(grouped['4'], 3);
equal(grouped['5'], 3);
var context = {};
_.countBy([{}], function(){ ok(this === context); }, context);
grouped = _.countBy([4.2, 6.1, 6.4], function(num) {
return Math.floor(num) > 4 ? 'hasOwnProperty' : 'constructor';
});
equal(grouped.constructor, 1);
equal(grouped.hasOwnProperty, 2);
var array = [{}];
_.countBy(array, function(value, index, obj){ ok(obj === array); });
});
test('sortedIndex', function() {
var numbers = [10, 20, 30, 40, 50], num = 35;
var indexForNum = _.sortedIndex(numbers, num);
equal(indexForNum, 3, '35 should be inserted at index 3');
var indexFor30 = _.sortedIndex(numbers, 30);
equal(indexFor30, 2, '30 should be inserted at index 2');
var objects = [{x: 10}, {x: 20}, {x: 30}, {x: 40}];
var iterator = function(obj){ return obj.x; };
strictEqual(_.sortedIndex(objects, {x: 25}, iterator), 2);
strictEqual(_.sortedIndex(objects, {x: 35}, 'x'), 3);
var context = {1: 2, 2: 3, 3: 4};
iterator = function(obj){ return this[obj]; };
strictEqual(_.sortedIndex([1, 3], 2, iterator, context), 1);
});
test('collections: shuffle', function() {
test('shuffle', function() {
var numbers = _.range(10);
var shuffled = _.shuffle(numbers).sort();
notStrictEqual(numbers, shuffled, 'original object is unmodified');
equal(shuffled.join(','), numbers.join(','), 'contains the same members before and after shuffle');
});
test('collections: toArray', function() {
test('toArray', function() {
ok(!_.isArray(arguments), 'arguments object is not an array');
ok(_.isArray(_.toArray(arguments)), 'arguments object converted into array');
var a = [1,2,3];
@@ -278,19 +372,19 @@ $(document).ready(function() {
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() {
test('size', function() {
equal(_.size({one : 1, two : 2, three : 3}), 3, 'can compute the size of an object');
equal(_.size([1, 2, 3]), 3, 'can compute the size of an array');
var func = function() {
return _.size(arguments);
};
equal(func(1, 2, 3, 4), 4, 'can test the size of the arguments object');
equal(_.size('hello'), 5, 'can compute the size of a string');
});
});

View File

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

View File

@@ -2,7 +2,7 @@ $(document).ready(function() {
module("Objects");
test("objects: keys", function() {
test("keys", function() {
equal(_.keys({one : 1, two : 2}).join(', '), 'one, two', 'can extract the keys from an object');
// the test above is not safe because it relies on for-in enumeration order
var a = []; a[1] = 0;
@@ -14,11 +14,26 @@ $(document).ready(function() {
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("values", function() {
equal(_.values({one: 1, two: 2}).join(', '), '1, 2', 'can extract the values from an object');
equal(_.values({one: 1, two: 2, length: 3}).join(', '), '1, 2, 3', '... even when one of them is "length"');
});
test("objects: functions", function() {
test("pairs", function() {
deepEqual(_.pairs({one: 1, two: 2}), [['one', 1], ['two', 2]], 'can convert an object into pairs');
deepEqual(_.pairs({one: 1, two: 2, length: 3}), [['one', 1], ['two', 2], ['length', 3]], '... even when one of them is "length"');
});
test("invert", function() {
var obj = {first: 'Moe', second: 'Larry', third: 'Curly'};
equal(_.keys(_.invert(obj)).join(' '), 'Moe Larry Curly', 'can invert an object');
ok(_.isEqual(_.invert(_.invert(obj)), obj), 'two inverts gets you back where you started');
var obj = {length: 3};
ok(_.invert(obj)['3'] == 'length', 'can invert an object with "length"')
});
test("functions", function() {
var obj = {a : 'dash', b : _.map, c : (/yo/), d : _.reduce};
ok(_.isEqual(['b', 'd'], _.functions(obj)), 'can grab the function names of any passed-in object');
@@ -27,7 +42,7 @@ $(document).ready(function() {
equal(_.functions(new Animal).join(''), 'run', 'also looks up functions on the prototype');
});
test("objects: extend", function() {
test("extend", function() {
var result;
equal(_.extend({}, {a:'b'}).a, 'b', 'can extend an object with the attributes of another');
equal(_.extend({a:'x'}, {a:'b'}).a, 'b', 'properties in source override destination');
@@ -40,7 +55,7 @@ $(document).ready(function() {
equal(_.keys(result).join(''), 'ab', 'extend does not copy undefined values');
});
test("objects: pick", function() {
test("pick", function() {
var result;
result = _.pick({a:1, b:2, c:3}, 'a', 'c');
ok(_.isEqual(result, {a:1, c:3}), 'can restrict properties to those named');
@@ -48,9 +63,27 @@ $(document).ready(function() {
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');
var Obj = function(){};
Obj.prototype = {a: 1, b: 2, c: 3};
ok(_.isEqual(_.pick(new Obj, 'a', 'c'), {a:1, c: 3}), 'include prototype props');
});
test("objects: defaults", function() {
test("omit", function() {
var result;
result = _.omit({a:1, b:2, c:3}, 'b');
ok(_.isEqual(result, {a:1, c:3}), 'can omit a single named property');
result = _.omit({a:1, b:2, c:3}, 'a', 'c');
ok(_.isEqual(result, {b:2}), 'can omit several named properties');
result = _.omit({a:1, b:2, c:3}, ['b', 'c']);
ok(_.isEqual(result, {a:1}), 'can omit properties named in an array');
var Obj = function(){};
Obj.prototype = {a: 1, b: 2, c: 3};
ok(_.isEqual(_.omit(new Obj, 'b'), {a:1, c: 3}), 'include prototype props');
});
test("defaults", function() {
var result;
var options = {zero: 0, one: 1, empty: "", nan: NaN, string: "string"};
@@ -65,7 +98,7 @@ $(document).ready(function() {
equal(options.word, "word", 'new value is added, first one wins');
});
test("objects: clone", function() {
test("clone", function() {
var moe = {name : 'moe', lucky : [13, 27, 34]};
var clone = _.clone(moe);
equal(clone.name, 'moe', 'the clone as the attributes of the original');
@@ -81,7 +114,7 @@ $(document).ready(function() {
equal(_.clone(null), null, 'non objects should not be changed by clone');
});
test("objects: isEqual", function() {
test("isEqual", function() {
function First() {
this.value = 1;
}
@@ -200,14 +233,6 @@ $(document).ready(function() {
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");
@@ -265,6 +290,12 @@ $(document).ready(function() {
b.push("Curly");
ok(!_.isEqual(a, b), "Arrays containing circular references and different properties are not equal");
// More circular arrays #767.
a = ["everything is checked but", "this", "is not"];
a[1] = a;
b = ["everything is checked but", ["this", "array"], "is not"];
ok(!_.isEqual(a, b), "Comparison of circular references with non-circular references are not equal");
// Circular Objects.
a = {abc: null};
b = {abc: null};
@@ -278,6 +309,12 @@ $(document).ready(function() {
b.def = new Number(63);
ok(!_.isEqual(a, b), "Objects containing circular references and different properties are not equal");
// More circular objects #767.
a = {everything: "is checked", but: "this", is: "not"};
a.but = a;
b = {everything: "is checked", but: {that:"object"}, is: "not"};
ok(!_.isEqual(a, b), "Comparison of circular references with non-circular object references are not equal");
// Cyclic Structures.
a = [{abc: null}];
b = [{abc: null}];
@@ -311,60 +348,11 @@ $(document).ready(function() {
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');
// Objects from another frame.
ok(_.isEqual({}, iObject));
});
test("objects: isEmpty", function() {
test("isEmpty", function() {
ok(!_([1]).isEmpty(), '[1] is not empty');
ok(_.isEmpty([]), '[] is empty');
ok(!_.isEmpty({one : 1}), '{one : 1} is not empty');
@@ -398,17 +386,18 @@ $(document).ready(function() {
parent.iNull = null;\
parent.iBoolean = new Boolean(false);\
parent.iUndefined = undefined;\
parent.iObject = {};\
</script>"
);
iDoc.close();
test("objects: isElement", function() {
test("isElement", function() {
ok(!_.isElement('div'), 'strings are not dom elements');
ok(_.isElement($('html')[0]), 'the html tag is a DOM element');
ok(_.isElement(iElement), 'even from another frame');
});
test("objects: isArguments", function() {
test("isArguments", function() {
var args = (function(){ return arguments; })(1, 2, 3);
ok(!_.isArguments('string'), 'a string is not an arguments object');
ok(!_.isArguments(_.isArguments), 'a function is not an arguments object');
@@ -418,7 +407,7 @@ $(document).ready(function() {
ok(_.isArguments(iArguments), 'even from another frame');
});
test("objects: isObject", function() {
test("isObject", function() {
ok(_.isObject(arguments), 'the arguments object is object');
ok(_.isObject([1, 2, 3]), 'and arrays');
ok(_.isObject($('html')[0]), 'and DOM element');
@@ -433,19 +422,19 @@ $(document).ready(function() {
ok(_.isObject(new String('string')), 'but new String()');
});
test("objects: isArray", function() {
test("isArray", function() {
ok(!_.isArray(arguments), 'the arguments object is not an array');
ok(_.isArray([1, 2, 3]), 'but arrays are');
ok(_.isArray(iArray), 'even from another frame');
});
test("objects: isString", function() {
test("isString", function() {
ok(!_.isString(document.body), 'the document body is not a string');
ok(_.isString([1, 2, 3].join(', ')), 'but strings are');
ok(_.isString(iString), 'even from another frame');
});
test("objects: isNumber", function() {
test("isNumber", function() {
ok(!_.isNumber('string'), 'a string is not a number');
ok(!_.isNumber(arguments), 'the arguments object is not a number');
ok(!_.isNumber(undefined), 'undefined is not a number');
@@ -456,7 +445,7 @@ $(document).ready(function() {
ok(!_.isNumber('1'), 'numeric strings are not numbers');
});
test("objects: isBoolean", function() {
test("isBoolean", function() {
ok(!_.isBoolean(2), 'a number is not a boolean');
ok(!_.isBoolean("string"), 'a string is not a boolean');
ok(!_.isBoolean("false"), 'the string "false" is not a boolean');
@@ -470,27 +459,27 @@ $(document).ready(function() {
ok(_.isBoolean(iBoolean), 'even from another frame');
});
test("objects: isFunction", function() {
test("isFunction", function() {
ok(!_.isFunction([1, 2, 3]), 'arrays are not functions');
ok(!_.isFunction('moe'), 'strings are not functions');
ok(_.isFunction(_.isFunction), 'but functions are');
ok(_.isFunction(iFunction), 'even from another frame');
});
test("objects: isDate", function() {
test("isDate", function() {
ok(!_.isDate(100), 'numbers are not dates');
ok(!_.isDate({}), 'objects are not dates');
ok(_.isDate(new Date()), 'but dates are');
ok(_.isDate(iDate), 'even from another frame');
});
test("objects: isRegExp", function() {
test("isRegExp", function() {
ok(!_.isRegExp(_.identity), 'functions are not RegExps');
ok(_.isRegExp(/identity/), 'but RegExps are');
ok(_.isRegExp(iRegExp), 'even from another frame');
});
test("objects: isFinite", function() {
test("isFinite", function() {
ok(!_.isFinite(undefined), 'undefined is not Finite');
ok(!_.isFinite(null), 'null is not Finite');
ok(!_.isFinite(NaN), 'NaN is not Finite');
@@ -504,22 +493,23 @@ $(document).ready(function() {
ok(_.isFinite(-12.44), 'Floats are Finite');
});
test("objects: isNaN", function() {
test("isNaN", function() {
ok(!_.isNaN(undefined), 'undefined is not NaN');
ok(!_.isNaN(null), 'null is not NaN');
ok(!_.isNaN(0), '0 is not NaN');
ok(_.isNaN(NaN), 'but NaN is');
ok(_.isNaN(iNaN), 'even from another frame');
ok(_.isNaN(new Number(NaN)), 'wrapped NaN is still NaN');
});
test("objects: isNull", function() {
test("isNull", function() {
ok(!_.isNull(undefined), 'undefined is not null');
ok(!_.isNull(NaN), 'NaN is not null');
ok(_.isNull(null), 'but null is');
ok(_.isNull(iNull), 'even from another frame');
});
test("objects: isUndefined", function() {
test("isUndefined", function() {
ok(!_.isUndefined(1), 'numbers are defined');
ok(!_.isUndefined(null), 'null is defined');
ok(!_.isUndefined(false), 'false is defined');
@@ -530,7 +520,7 @@ $(document).ready(function() {
});
if (window.ActiveXObject) {
test("objects: IE host objects", function() {
test("IE host objects", function() {
var xml = new ActiveXObject("Msxml2.DOMDocument.3.0");
ok(!_.isNumber(xml));
ok(!_.isBoolean(xml));
@@ -541,7 +531,7 @@ $(document).ready(function() {
});
}
test("objects: tap", function() {
test("tap", function() {
var intercepted = null;
var interceptor = function(obj) { intercepted = obj; };
var returned = _.tap(1, interceptor);

View File

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

View File

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

View File

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

5
vendor/underscore/underscore-min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,10 +1,7 @@
// Underscore.js 1.3.3
// Underscore.js 1.4.0
// http://underscorejs.org
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Underscore is freely distributable under the MIT license.
// Portions of Underscore are inspired or borrowed from Prototype,
// Oliver Steele's Functional, and John Resig's Micro-Templating.
// For all details and documentation:
// http://documentcloud.github.com/underscore
// Underscore may be freely distributed under the MIT license.
(function() {
@@ -26,6 +23,7 @@
// Create quick reference variables for speed access to core prototypes.
var push = ArrayProto.push,
slice = ArrayProto.slice,
concat = ArrayProto.concat,
unshift = ArrayProto.unshift,
toString = ObjProto.toString,
hasOwnProperty = ObjProto.hasOwnProperty;
@@ -47,7 +45,11 @@
nativeBind = FuncProto.bind;
// Create a safe reference to the Underscore object for use below.
var _ = function(obj) { return new wrapper(obj); };
var _ = function(obj) {
if (obj instanceof _) return obj;
if (!(this instanceof _)) return new _(obj);
this._wrapped = obj;
};
// Export the Underscore object for **Node.js**, with
// backwards-compatibility for the old `require()` API. If we're in
@@ -63,7 +65,7 @@
}
// Current version.
_.VERSION = '1.3.3';
_.VERSION = '1.4.0';
// Collection Functions
// --------------------
@@ -72,7 +74,6 @@
// Handles objects with the built-in `forEach`, arrays, and raw objects.
// Delegates to **ECMAScript 5**'s native `forEach` if available.
var each = _.each = _.forEach = function(obj, iterator, context) {
if (obj == null) return;
if (nativeForEach && obj.forEach === nativeForEach) {
obj.forEach(iterator, context);
} else if (obj.length === +obj.length) {
@@ -92,7 +93,6 @@
// Delegates to **ECMAScript 5**'s native `map` if available.
_.map = _.collect = function(obj, iterator, context) {
var results = [];
if (obj == null) return results;
if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
each(obj, function(value, index, list) {
results[results.length] = iterator.call(context, value, index, list);
@@ -104,7 +104,6 @@
// or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
_.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
var initial = arguments.length > 2;
if (obj == null) obj = [];
if (nativeReduce && obj.reduce === nativeReduce) {
if (context) iterator = _.bind(iterator, context);
return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
@@ -125,14 +124,26 @@
// Delegates to **ECMAScript 5**'s native `reduceRight` if available.
_.reduceRight = _.foldr = function(obj, iterator, memo, context) {
var initial = arguments.length > 2;
if (obj == null) obj = [];
if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
if (context) iterator = _.bind(iterator, context);
return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
return arguments.length > 2 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
}
var reversed = _.toArray(obj).reverse();
if (context && !initial) iterator = _.bind(iterator, context);
return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator);
var length = obj.length;
if (length !== +length) {
var keys = _.keys(obj);
length = keys.length;
}
each(obj, function(value, index, list) {
index = keys ? keys[--length] : --length;
if (!initial) {
memo = obj[index];
initial = true;
} else {
memo = iterator.call(context, memo, obj[index], index, list);
}
});
if (!initial) throw new TypeError('Reduce of empty array with no initial value');
return memo;
};
// Return the first value which passes a truth test. Aliased as `detect`.
@@ -152,7 +163,6 @@
// Aliased as `select`.
_.filter = _.select = function(obj, iterator, context) {
var results = [];
if (obj == null) return results;
if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
each(obj, function(value, index, list) {
if (iterator.call(context, value, index, list)) results[results.length] = value;
@@ -163,7 +173,6 @@
// Return all the elements for which a truth test fails.
_.reject = function(obj, iterator, context) {
var results = [];
if (obj == null) return results;
each(obj, function(value, index, list) {
if (!iterator.call(context, value, index, list)) results[results.length] = value;
});
@@ -174,8 +183,8 @@
// Delegates to **ECMAScript 5**'s native `every` if available.
// Aliased as `all`.
_.every = _.all = function(obj, iterator, context) {
iterator || (iterator = _.identity);
var result = true;
if (obj == null) return result;
if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
each(obj, function(value, index, list) {
if (!(result = result && iterator.call(context, value, index, list))) return breaker;
@@ -189,7 +198,6 @@
var any = _.some = _.any = function(obj, iterator, context) {
iterator || (iterator = _.identity);
var result = false;
if (obj == null) return result;
if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
each(obj, function(value, index, list) {
if (result || (result = iterator.call(context, value, index, list))) return breaker;
@@ -197,11 +205,10 @@
return !!result;
};
// Determine if a given value is included in the array or object using `===`.
// Aliased as `contains`.
_.include = _.contains = function(obj, target) {
// Determine if the array or object contains a given value (using `===`).
// Aliased as `include`.
_.contains = _.include = function(obj, target) {
var found = false;
if (obj == null) return found;
if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
found = any(obj, function(value) {
return value === target;
@@ -222,6 +229,18 @@
return _.map(obj, function(value){ return value[key]; });
};
// Convenience version of a common use case of `filter`: selecting only objects
// with specific `key:value` pairs.
_.where = function(obj, attrs) {
if (_.isEmpty(attrs)) return [];
return _.filter(obj, function(value) {
for (var key in attrs) {
if (attrs[key] !== value[key]) return false;
}
return true;
});
};
// Return the maximum element or (element-based computation).
// Can't optimize arrays of integers longer than 65,535 elements.
// See: https://bugs.webkit.org/show_bug.cgi?id=80797
@@ -258,66 +277,90 @@
var index = 0;
var shuffled = [];
each(obj, function(value) {
rand = Math.floor(Math.random() * ++index);
rand = _.random(index++);
shuffled[index - 1] = shuffled[rand];
shuffled[rand] = value;
});
return shuffled;
};
// An internal function to generate lookup iterators.
var lookupIterator = function(value) {
return _.isFunction(value) ? value : function(obj){ return obj[value]; };
};
// Sort the object's values by a criterion produced by an iterator.
_.sortBy = function(obj, val, context) {
var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; };
_.sortBy = function(obj, value, context) {
var iterator = lookupIterator(value);
return _.pluck(_.map(obj, function(value, index, list) {
return {
value : value,
index : index,
criteria : iterator.call(context, value, index, list)
};
}).sort(function(left, right) {
var a = left.criteria, b = right.criteria;
if (a === void 0) return 1;
if (b === void 0) return -1;
return a < b ? -1 : a > b ? 1 : 0;
var a = left.criteria;
var b = right.criteria;
if (a !== b) {
if (a > b || a === void 0) return 1;
if (a < b || b === void 0) return -1;
}
return left.index < right.index ? -1 : 1;
}), 'value');
};
// Groups the object's values by a criterion. Pass either a string attribute
// to group by, or a function that returns the criterion.
_.groupBy = function(obj, val) {
// An internal function used for aggregate "group by" operations.
var group = function(obj, value, context, behavior) {
var result = {};
var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; };
var iterator = lookupIterator(value);
each(obj, function(value, index) {
var key = iterator(value, index);
(result[key] || (result[key] = [])).push(value);
var key = iterator.call(context, value, index, obj);
behavior(result, key, value);
});
return result;
};
// Groups the object's values by a criterion. Pass either a string attribute
// to group by, or a function that returns the criterion.
_.groupBy = function(obj, value, context) {
return group(obj, value, context, function(result, key, value) {
(_.has(result, key) ? result[key] : (result[key] = [])).push(value);
});
};
// Counts instances of an object that group by a certain criterion. Pass
// either a string attribute to count by, or a function that returns the
// criterion.
_.countBy = function(obj, value, context) {
return group(obj, value, context, function(result, key, value) {
if (!_.has(result, key)) result[key] = 0;
result[key]++;
});
};
// Use a comparator function to figure out the smallest index at which
// an object should be inserted so as to maintain order. Uses binary search.
_.sortedIndex = function(array, obj, iterator) {
iterator || (iterator = _.identity);
var value = iterator(obj);
_.sortedIndex = function(array, obj, iterator, context) {
iterator = iterator == null ? _.identity : lookupIterator(iterator);
var value = iterator.call(context, obj);
var low = 0, high = array.length;
while (low < high) {
var mid = (low + high) >> 1;
iterator(array[mid]) < value ? low = mid + 1 : high = mid;
var mid = (low + high) >>> 1;
iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid;
}
return low;
};
// Safely convert anything iterable into a real, live array.
_.toArray = function(obj) {
if (!obj) return [];
if (_.isArray(obj)) return slice.call(obj);
if (_.isArguments(obj)) return slice.call(obj);
if (obj.toArray && _.isFunction(obj.toArray)) return obj.toArray();
if (!obj) return [];
if (obj.length === +obj.length) return slice.call(obj);
return _.values(obj);
};
// Return the number of elements in an object.
_.size = function(obj) {
return _.isArray(obj) ? obj.length : _.keys(obj).length;
return (obj.length === +obj.length) ? obj.length : _.keys(obj).length;
};
// Array Functions
@@ -348,12 +391,12 @@
}
};
// Returns everything but the first entry of the array. Aliased as `tail`.
// Especially useful on the arguments object. Passing an **index** will return
// the rest of the values in the array from that index onward. The **guard**
// Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
// Especially useful on the arguments object. Passing an **n** will return
// the rest N values in the array. The **guard**
// check allows it to work with `_.map`.
_.rest = _.tail = function(array, index, guard) {
return slice.call(array, (index == null) || guard ? 1 : index);
_.rest = _.tail = _.drop = function(array, n, guard) {
return slice.call(array, (n == null) || guard ? 1 : n);
};
// Trim out all falsy values from an array.
@@ -386,23 +429,23 @@
// Produce a duplicate-free version of the array. If the array has already
// been sorted, you have the option of using a faster algorithm.
// Aliased as `unique`.
_.uniq = _.unique = function(array, isSorted, iterator) {
var initial = iterator ? _.map(array, iterator) : array;
_.uniq = _.unique = function(array, isSorted, iterator, context) {
var initial = iterator ? _.map(array, iterator, context) : array;
var results = [];
_.reduce(initial, function(memo, value, index) {
if (isSorted ? (_.last(memo) !== value || !memo.length) : !_.include(memo, value)) {
memo.push(value);
var seen = [];
each(initial, function(value, index) {
if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) {
seen.push(value);
results.push(array[index]);
}
return memo;
}, []);
});
return results;
};
// Produce an array that contains the union: each distinct element from all of
// the passed-in arrays.
_.union = function() {
return _.uniq(flatten(arguments, true, []));
return _.uniq(concat.apply(ArrayProto, arguments));
};
// Produce an array that contains every item shared between all the
@@ -419,8 +462,8 @@
// Take the difference between one array and a number of other arrays.
// Only the elements present in just the first array will remain.
_.difference = function(array) {
var rest = flatten(slice.call(arguments, 1), true, []);
return _.filter(array, function(value){ return !_.include(rest, value); });
var rest = concat.apply(ArrayProto, slice.call(arguments, 1));
return _.filter(array, function(value){ return !_.contains(rest, value); });
};
// Zip together multiple lists into a single array -- elements that share
@@ -435,12 +478,17 @@
return results;
};
// Zip together two arrays -- an array of keys and an array of values -- into
// a single object.
_.zipObject = function(keys, values) {
// Converts lists into objects. Pass either a single array of `[key, value]`
// pairs, or two parallel arrays of the same length -- one of keys, and one of
// the corresponding values.
_.object = function(list, values) {
var result = {};
for (var i = 0, l = keys.length; i < l; i++) {
result[keys[i]] = values[i];
for (var i = 0, l = list.length; i < l; i++) {
if (values) {
result[list[i]] = values[i];
} else {
result[list[i][0]] = list[i][1];
}
}
return result;
};
@@ -452,22 +500,27 @@
// If the array is large and already in sort order, pass `true`
// for **isSorted** to use binary search.
_.indexOf = function(array, item, isSorted) {
if (array == null) return -1;
var i, l;
var i = 0, l = array.length;
if (isSorted) {
i = _.sortedIndex(array, item);
return array[i] === item ? i : -1;
if (typeof isSorted == 'number') {
i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted);
} else {
i = _.sortedIndex(array, item);
return array[i] === item ? i : -1;
}
}
if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
for (i = 0, l = array.length; i < l; i++) if (array[i] === item) return i;
if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted);
for (; i < l; i++) if (array[i] === item) return i;
return -1;
};
// Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
_.lastIndexOf = function(array, item) {
if (array == null) return -1;
if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item);
var i = array.length;
_.lastIndexOf = function(array, item, from) {
var hasIndex = from != null;
if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) {
return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item);
}
var i = (hasIndex ? from : array.length);
while (i--) if (array[i] === item) return i;
return -1;
};
@@ -560,7 +613,9 @@
context = this; args = arguments;
var later = function() {
timeout = null;
if (more) func.apply(context, args);
if (more) {
result = func.apply(context, args);
}
whenDone();
};
if (!timeout) timeout = setTimeout(later, wait);
@@ -580,17 +635,18 @@
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
_.debounce = function(func, wait, immediate) {
var timeout;
var timeout, result;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
if (!immediate) result = func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
if (callNow) result = func.apply(context, args);
return result;
};
};
@@ -601,7 +657,9 @@
return function() {
if (ran) return memo;
ran = true;
return memo = func.apply(this, arguments);
memo = func.apply(this, arguments);
func = null;
return memo;
};
};
@@ -610,7 +668,8 @@
// conditionally execute the original function.
_.wrap = function(func, wrapper) {
return function() {
var args = [func].concat(slice.call(arguments, 0));
var args = [func];
push.apply(args, arguments);
return wrapper.apply(this, args);
};
};
@@ -652,7 +711,23 @@
// Retrieve the values of an object's properties.
_.values = function(obj) {
return _.map(obj, _.identity);
var values = [];
for (var key in obj) if (_.has(obj, key)) values.push(obj[key]);
return values;
};
// Convert an object into a list of `[key, value]` pairs.
_.pairs = function(obj) {
var pairs = [];
for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]);
return pairs;
};
// Invert the keys and values of an object. The values must be serializable.
_.invert = function(obj) {
var result = {};
for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key;
return result;
};
// Return a sorted list of the function names available on the object.
@@ -677,11 +752,22 @@
// Return a copy of the object only containing the whitelisted properties.
_.pick = function(obj) {
var result = {};
each(flatten(slice.call(arguments, 1), true, []), function(key) {
if (key in obj) result[key] = obj[key];
var copy = {};
var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
each(keys, function(key) {
if (key in obj) copy[key] = obj[key];
});
return result;
return copy;
};
// Return a copy of the object without the blacklisted properties.
_.omit = function(obj) {
var copy = {};
var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
for (var key in obj) {
if (!_.contains(keys, key)) copy[key] = obj[key];
}
return copy;
};
// Fill in a given object with default properties.
@@ -709,18 +795,15 @@
};
// Internal recursive comparison function for `isEqual`.
function eq(a, b, stack) {
var eq = function(a, b, aStack, bStack) {
// Identical objects are equal. `0 === -0`, but they aren't identical.
// See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
if (a === b) return a !== 0 || 1 / a == 1 / b;
// A strict comparison is necessary because `null == undefined`.
if (a == null || b == null) return a === b;
// Unwrap any wrapped objects.
if (a._chain) a = a._wrapped;
if (b._chain) b = b._wrapped;
// Invoke a custom `isEqual` method if one is provided.
if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b);
if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a);
if (a instanceof _) a = a._wrapped;
if (b instanceof _) b = b._wrapped;
// Compare `[[Class]]` names.
var className = toString.call(a);
if (className != toString.call(b)) return false;
@@ -750,14 +833,15 @@
if (typeof a != 'object' || typeof b != 'object') return false;
// Assume equality for cyclic structures. The algorithm for detecting cyclic
// structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
var length = stack.length;
var length = aStack.length;
while (length--) {
// Linear search. Performance is inversely proportional to the number of
// unique nested structures.
if (stack[length] == a) return true;
if (aStack[length] == a) return bStack[length] == b;
}
// Add the first object to the stack of traversed objects.
stack.push(a);
aStack.push(a);
bStack.push(b);
var size = 0, result = true;
// Recursively compare objects and arrays.
if (className == '[object Array]') {
@@ -767,20 +851,24 @@
if (result) {
// Deep compare the contents, ignoring non-numeric properties.
while (size--) {
// Ensure commutative equality for sparse arrays.
if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break;
if (!(result = eq(a[size], b[size], aStack, bStack))) break;
}
}
} else {
// Objects with different constructors are not equivalent.
if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false;
// Objects with different constructors are not equivalent, but `Object`s
// from different frames are.
var aCtor = a.constructor, bCtor = b.constructor;
if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) &&
_.isFunction(bCtor) && (bCtor instanceof bCtor))) {
return false;
}
// Deep compare objects.
for (var key in a) {
if (_.has(a, key)) {
// Count the expected number of properties.
size++;
// Deep compare each member.
if (!(result = _.has(b, key) && eq(a[key], b[key], stack))) break;
if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break;
}
}
// Ensure that both objects contain the same number of properties.
@@ -792,13 +880,14 @@
}
}
// Remove the first object from the stack of traversed objects.
stack.pop();
aStack.pop();
bStack.pop();
return result;
}
};
// Perform a deep comparison to check if two objects are equal.
_.isEqual = function(a, b) {
return eq(a, b, []);
return eq(a, b, [], []);
};
// Is a given array, string, or object empty?
@@ -812,7 +901,7 @@
// Is a given value a DOM element?
_.isElement = function(obj) {
return !!(obj && obj.nodeType == 1);
return !!(obj && obj.nodeType === 1);
};
// Is a given value an array?
@@ -832,7 +921,7 @@
return toString.call(obj) == '[object ' + name + ']';
};
});
// Define a fallback version of the method in browsers (ahem, IE), where
// there isn't any inspectable "Arguments" type.
if (!_.isArguments(arguments)) {
@@ -841,15 +930,21 @@
};
}
// Optimize `isFunction` if appropriate.
if (typeof (/./) !== 'function') {
_.isFunction = function(obj) {
return typeof obj === 'function';
};
}
// Is a given object a finite number?
_.isFinite = function(obj) {
return _.isNumber(obj) && isFinite(obj);
};
// Is the given value `NaN`?
// Is the given value `NaN`? (NaN is the only number which does not equal itself).
_.isNaN = function(obj) {
// `NaN` is the only value for which `===` is not reflexive.
return obj !== obj;
return _.isNumber(obj) && obj != +obj;
};
// Is a given value a boolean?
@@ -893,25 +988,43 @@
for (var i = 0; i < n; i++) iterator.call(context, i);
};
// Return a random integer between min and max (inclusive).
_.random = function(min, max) {
if (max == null) {
max = min;
min = 0;
}
return min + (0 | Math.random() * (max - min + 1));
};
// List of HTML entities for escaping.
var htmlEscapes = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#x27;',
'/': '&#x2F;'
var entityMap = {
escape: {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#x27;',
'/': '&#x2F;'
}
};
entityMap.unescape = _.invert(entityMap.escape);
// Regexes containing the keys and values listed immediately above.
var entityRegexes = {
escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'),
unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g')
};
// Regex containing the keys listed immediately above.
var htmlEscaper = /[&<>"'\/]/g;
// Escape a string for HTML interpolation.
_.escape = function(string) {
return ('' + string).replace(htmlEscaper, function(match) {
return htmlEscapes[match];
});
};
// Functions for escaping and unescaping strings to/from HTML interpolation.
_.each(['escape', 'unescape'], function(method) {
_[method] = function(string) {
if (string == null) return '';
return ('' + string).replace(entityRegexes[method], function(match) {
return entityMap[method][match];
});
};
});
// If the value of the named property is a function then invoke it;
// otherwise, return it.
@@ -921,11 +1034,15 @@
return _.isFunction(value) ? value.call(object) : value;
};
// Add your own custom functions to the Underscore object, ensuring that
// they're correctly added to the OOP wrapper as well.
// Add your own custom functions to the Underscore object.
_.mixin = function(obj) {
each(_.functions(obj), function(name){
addToWrapper(name, _[name] = obj[name]);
var func = _[name] = obj[name];
_.prototype[name] = function() {
var args = [this._wrapped];
push.apply(args, arguments);
return result.call(this, func.apply(_, args));
};
});
};
@@ -948,63 +1065,63 @@
// When customizing `templateSettings`, if you don't want to define an
// interpolation, evaluation or escaping regex, we need one that is
// guaranteed not to match.
var noMatch = /.^/;
var noMatch = /(.)^/;
// Certain characters need to be escaped so that they can be put into a
// string literal.
var escapes = {
'\\': '\\',
"'": "'",
r: '\r',
n: '\n',
t: '\t',
u2028: '\u2028',
u2029: '\u2029'
"'": "'",
'\\': '\\',
'\r': 'r',
'\n': 'n',
'\t': 't',
'\u2028': 'u2028',
'\u2029': 'u2029'
};
for (var key in escapes) escapes[escapes[key]] = key;
var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
var unescaper = /\\(\\|'|r|n|t|u2028|u2029)/g;
// Within an interpolation, evaluation, or escaping, remove HTML escaping
// that had been previously added.
var unescape = function(code) {
return code.replace(unescaper, function(match, escape) {
return escapes[escape];
});
};
// JavaScript micro-templating, similar to John Resig's implementation.
// Underscore templating handles arbitrary delimiters, preserves whitespace,
// and correctly escapes quotes within interpolated code.
_.template = function(text, data, settings) {
settings = _.defaults(settings || {}, _.templateSettings);
settings = _.defaults({}, settings, _.templateSettings);
// Compile the template source, taking care to escape characters that
// cannot be included in a string literal and then unescape them in code
// blocks.
var source = "__p+='" + text
.replace(escaper, function(match) {
return '\\' + escapes[match];
})
.replace(settings.escape || noMatch, function(match, code) {
return "'+\n((__t=(" + unescape(code) + "))==null?'':_.escape(__t))+\n'";
})
.replace(settings.interpolate || noMatch, function(match, code) {
return "'+\n((__t=(" + unescape(code) + "))==null?'':__t)+\n'";
})
.replace(settings.evaluate || noMatch, function(match, code) {
return "';\n" + unescape(code) + "\n__p+='";
}) + "';\n";
// Combine delimiters into one regular expression via alternation.
var matcher = new RegExp([
(settings.escape || noMatch).source,
(settings.interpolate || noMatch).source,
(settings.evaluate || noMatch).source
].join('|') + '|$', 'g');
// Compile the template source, escaping string literals appropriately.
var index = 0;
var source = "__p+='";
text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
source += text.slice(index, offset)
.replace(escaper, function(match) { return '\\' + escapes[match]; });
source +=
escape ? "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'" :
interpolate ? "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'" :
evaluate ? "';\n" + evaluate + "\n__p+='" : '';
index = offset + match.length;
});
source += "';\n";
// If a variable is not specified, place data values in local scope.
if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
source = "var __t,__p='',__j=Array.prototype.join," +
"print=function(){__p+=__j.call(arguments,'')};\n" +
"print=function(){__p+=__j.call(arguments,'');};\n" +
source + "return __p;\n";
var render = new Function(settings.variable || 'obj', '_', source);
try {
var render = new Function(settings.variable || 'obj', '_', source);
} catch (e) {
e.source = source;
throw e;
}
if (data) return render(data, _);
var template = function(data) {
return render.call(this, data, _);
@@ -1021,29 +1138,15 @@
return _(obj).chain();
};
// The OOP Wrapper
// OOP
// ---------------
// If Underscore is called as a function, it returns a wrapped object that
// can be used OO-style. This wrapper holds altered versions of all the
// underscore functions. Wrapped objects may be chained.
var wrapper = function(obj) { this._wrapped = obj; };
// Expose `wrapper.prototype` as `_.prototype`
_.prototype = wrapper.prototype;
// Helper function to continue chaining intermediate results.
var result = function(obj, chain) {
return chain ? _(obj).chain() : obj;
};
// A method to easily add functions to the OOP wrapper.
var addToWrapper = function(name, func) {
wrapper.prototype[name] = function() {
var args = slice.call(arguments);
unshift.call(args, this._wrapped);
return result(func.apply(_, args), this._chain);
};
var result = function(obj) {
return this._chain ? _(obj).chain() : obj;
};
// Add all of the Underscore functions to the wrapper object.
@@ -1052,31 +1155,35 @@
// Add all mutator Array functions to the wrapper.
each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
var method = ArrayProto[name];
wrapper.prototype[name] = function() {
_.prototype[name] = function() {
var obj = this._wrapped;
method.apply(obj, arguments);
if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0];
return result(obj, this._chain);
return result.call(this, obj);
};
});
// Add all accessor Array functions to the wrapper.
each(['concat', 'join', 'slice'], function(name) {
var method = ArrayProto[name];
wrapper.prototype[name] = function() {
return result(method.apply(this._wrapped, arguments), this._chain);
_.prototype[name] = function() {
return result.call(this, method.apply(this._wrapped, arguments));
};
});
// Start chaining a wrapped Underscore object.
wrapper.prototype.chain = function() {
this._chain = true;
return this;
};
_.extend(_.prototype, {
// Extracts the result from a wrapped and chained object.
wrapper.prototype.value = function() {
return this._wrapped;
};
// Start chaining a wrapped Underscore object.
chain: function() {
this._chain = true;
return this;
},
// Extracts the result from a wrapped and chained object.
value: function() {
return this._wrapped;
}
});
}).call(this);