mirror of
https://github.com/whoisclebs/lodash.git
synced 2026-02-02 08:07:50 +00:00
Compare commits
478 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c65250ec39 | ||
|
|
607eec6ff0 | ||
|
|
14fb3bde60 | ||
|
|
6782bc8ca2 | ||
|
|
3db6841305 | ||
|
|
f478fa8029 | ||
|
|
d255f8400f | ||
|
|
a1545c60d6 | ||
|
|
ed48603ab5 | ||
|
|
23a0507439 | ||
|
|
482e013887 | ||
|
|
265727c30f | ||
|
|
4d3f4e096b | ||
|
|
7629bca62d | ||
|
|
2712f17262 | ||
|
|
86a37559ba | ||
|
|
b504a557f4 | ||
|
|
e849b46f6b | ||
|
|
738dc50a6f | ||
|
|
3138523b5b | ||
|
|
537643a4c3 | ||
|
|
04bfe098ed | ||
|
|
c507887bdd | ||
|
|
ee261313ac | ||
|
|
c9c152bc8a | ||
|
|
6c8e19a321 | ||
|
|
218e3b66f2 | ||
|
|
1d3488fa8e | ||
|
|
f4c1682592 | ||
|
|
28ac6623c7 | ||
|
|
16ab179e1d | ||
|
|
a8ff5385dc | ||
|
|
9dc33c6086 | ||
|
|
b17c576705 | ||
|
|
96f8f2891b | ||
|
|
581d73afe0 | ||
|
|
1baefebe8f | ||
|
|
39fb05033f | ||
|
|
c6093c084c | ||
|
|
dfec6d9a6d | ||
|
|
08b1261c75 | ||
|
|
2ad6faae3a | ||
|
|
4eff301db3 | ||
|
|
447e43e8f4 | ||
|
|
7457001275 | ||
|
|
fcabd5e79e | ||
|
|
594002316c | ||
|
|
eccf92ebaf | ||
|
|
ad9ddc5621 | ||
|
|
1825dd916e | ||
|
|
dfcde8171e | ||
|
|
b0361183df | ||
|
|
71fe13b249 | ||
|
|
442f51224d | ||
|
|
974678f500 | ||
|
|
8329409c03 | ||
|
|
06c2ac2724 | ||
|
|
04e9e8f9d8 | ||
|
|
7a3330cb8c | ||
|
|
cae29941ea | ||
|
|
15cf2ad076 | ||
|
|
2f16f6dc23 | ||
|
|
528aebffe4 | ||
|
|
e82b2f0cbe | ||
|
|
73e64f3e4a | ||
|
|
15f5b1f830 | ||
|
|
1b0a77f9a8 | ||
|
|
0f82ae3644 | ||
|
|
de3b01b54b | ||
|
|
c17e3646d5 | ||
|
|
26aed3e89e | ||
|
|
74caea8f8f | ||
|
|
2cf07fa072 | ||
|
|
1181cd2fd7 | ||
|
|
850d55ab45 | ||
|
|
a0c91b8754 | ||
|
|
937bdc05c8 | ||
|
|
453755bfa1 | ||
|
|
400b6fbb61 | ||
|
|
8c4e882fa3 | ||
|
|
84eec23793 | ||
|
|
7dfe69f6a5 | ||
|
|
ada6115073 | ||
|
|
e1643566f9 | ||
|
|
053a9a6317 | ||
|
|
425b976cc0 | ||
|
|
5f5f1b1e09 | ||
|
|
e6f4935c7b | ||
|
|
6452e5976b | ||
|
|
2383dff317 | ||
|
|
de3e5042fb | ||
|
|
0dcc35152c | ||
|
|
8eecfcbfaf | ||
|
|
efa3e60f89 | ||
|
|
782df659bc | ||
|
|
0aa158824f | ||
|
|
da7fb44e5d | ||
|
|
4b59ef802e | ||
|
|
be5789cd56 | ||
|
|
9adf0e5d59 | ||
|
|
e2de22470e | ||
|
|
3b7ab2e553 | ||
|
|
8617dedc46 | ||
|
|
0b8f1a9a58 | ||
|
|
16748c0920 | ||
|
|
01ec585a44 | ||
|
|
b5f2295f37 | ||
|
|
d6ca0199b7 | ||
|
|
2257c5e547 | ||
|
|
b5a3d39799 | ||
|
|
a26c3e9d3c | ||
|
|
98a09532eb | ||
|
|
d736789bbc | ||
|
|
82a6cfc03a | ||
|
|
62e9c7c75a | ||
|
|
eb32bd45e7 | ||
|
|
4b95f07923 | ||
|
|
dea8ad4c49 | ||
|
|
96553b217c | ||
|
|
3f79ed5d91 | ||
|
|
1e4283aad3 | ||
|
|
1dda31a28c | ||
|
|
10c87012be | ||
|
|
bda00bf512 | ||
|
|
c8f871ff2a | ||
|
|
58dc0b1aef | ||
|
|
4cd4d8f31a | ||
|
|
b751fd738d | ||
|
|
f97c6fb94c | ||
|
|
219138ade6 | ||
|
|
9086d189b9 | ||
|
|
d1178defe0 | ||
|
|
691a561a95 | ||
|
|
aaaac93bd0 | ||
|
|
fd790566b2 | ||
|
|
db3b429784 | ||
|
|
40ed3278d4 | ||
|
|
d174b13111 | ||
|
|
1708663b32 | ||
|
|
6fdce4ad0d | ||
|
|
fff8d5f07d | ||
|
|
9c8e1f4706 | ||
|
|
0a1036c78f | ||
|
|
0e881a7972 | ||
|
|
839e52ba30 | ||
|
|
8f7d5dcb4d | ||
|
|
1104b28bc1 | ||
|
|
48521cb1e4 | ||
|
|
17e113dafb | ||
|
|
c33825a904 | ||
|
|
b07ef98c8f | ||
|
|
b3d68893df | ||
|
|
1329599987 | ||
|
|
2adf3f364c | ||
|
|
436ee34a02 | ||
|
|
4a6e17b214 | ||
|
|
6e9cbccde6 | ||
|
|
a0cb8ec124 | ||
|
|
21217dfda3 | ||
|
|
25ba18e570 | ||
|
|
a210377f35 | ||
|
|
07b9ca457f | ||
|
|
5f1372d39c | ||
|
|
01b84c79f0 | ||
|
|
4017443b1e | ||
|
|
fd2a17d244 | ||
|
|
126804f7c3 | ||
|
|
5167bbf59e | ||
|
|
8bcdfa2793 | ||
|
|
2f9cb6a91e | ||
|
|
662be14535 | ||
|
|
d6d065cd61 | ||
|
|
ac7f045708 | ||
|
|
ffe02ad7bf | ||
|
|
db1a87d10c | ||
|
|
4bae41ffd8 | ||
|
|
be36fb979f | ||
|
|
0f66d763e0 | ||
|
|
06f4743f51 | ||
|
|
9cc11b8774 | ||
|
|
de821e55ae | ||
|
|
463b5c6e49 | ||
|
|
65ab5fdb34 | ||
|
|
d2f7a035b3 | ||
|
|
bc0b924283 | ||
|
|
56ad6af5b2 | ||
|
|
c50bb3a5a9 | ||
|
|
d993a62263 | ||
|
|
82bc52b909 | ||
|
|
f31598f916 | ||
|
|
6a9efd8ac6 | ||
|
|
a9dddb6066 | ||
|
|
40cf5c99ef | ||
|
|
42f58cbbb3 | ||
|
|
fd9c780015 | ||
|
|
383b92b207 | ||
|
|
7036ed5e2f | ||
|
|
30666aa111 | ||
|
|
9614d68baa | ||
|
|
c25fb4c743 | ||
|
|
09d5222b1f | ||
|
|
426ca78bf7 | ||
|
|
a77a0945fe | ||
|
|
5311a0f903 | ||
|
|
d421656be8 | ||
|
|
04459eaa50 | ||
|
|
3beda8eea0 | ||
|
|
5e894be06b | ||
|
|
bc8f93b596 | ||
|
|
2fb93bac99 | ||
|
|
d0c94c1aef | ||
|
|
77242bfb95 | ||
|
|
5d2d86bffc | ||
|
|
8492c13e72 | ||
|
|
e4eb5dadda | ||
|
|
8532dc4b75 | ||
|
|
1ca26ce676 | ||
|
|
d8e3e823a7 | ||
|
|
473dd7660b | ||
|
|
5afeed56ef | ||
|
|
b91b04f652 | ||
|
|
25de44a23d | ||
|
|
00cfb95971 | ||
|
|
f4ba0e1191 | ||
|
|
6499643769 | ||
|
|
f85287a444 | ||
|
|
483bc9ad87 | ||
|
|
93b89a93c1 | ||
|
|
10064c046c | ||
|
|
a5aecb9d0e | ||
|
|
de1e889f1d | ||
|
|
16f89b40c3 | ||
|
|
0e387d2cda | ||
|
|
837c15e4f9 | ||
|
|
ae7e353169 | ||
|
|
6c1bbd2344 | ||
|
|
27247e8e56 | ||
|
|
08249d78ea | ||
|
|
827091e522 | ||
|
|
13d901bf8d | ||
|
|
e69bdabc99 | ||
|
|
e8d19265e4 | ||
|
|
49e3a49dc5 | ||
|
|
82a7c01898 | ||
|
|
c0d7dbf639 | ||
|
|
569caa0bf2 | ||
|
|
f88ea1ee7d | ||
|
|
d5a8fa0b97 | ||
|
|
3f8f96edea | ||
|
|
04fb4aff28 | ||
|
|
83c6fb089e | ||
|
|
9d8d17b964 | ||
|
|
39d4842ff5 | ||
|
|
cad8473986 | ||
|
|
5f085ccb52 | ||
|
|
b406246689 | ||
|
|
cbe46afdff | ||
|
|
a59d6dc3c7 | ||
|
|
2afb2dd5fd | ||
|
|
4fc3c969d3 | ||
|
|
1796ce324b | ||
|
|
5e04c7f827 | ||
|
|
20fcede440 | ||
|
|
012c1833f2 | ||
|
|
32e8e03256 | ||
|
|
dca653cb92 | ||
|
|
0805eca979 | ||
|
|
17935a78ff | ||
|
|
e16918ee32 | ||
|
|
78471b4595 | ||
|
|
c30bcdd515 | ||
|
|
ac78c5f4e5 | ||
|
|
57a990ce25 | ||
|
|
24825b42a2 | ||
|
|
4f7323f7fc | ||
|
|
a228be85e2 | ||
|
|
fa565bdbdf | ||
|
|
2dc53223e5 | ||
|
|
958ac72805 | ||
|
|
f7297b84e7 | ||
|
|
9a7d9e7bb8 | ||
|
|
fa9df75cf7 | ||
|
|
e3ec76418b | ||
|
|
102d6d8c84 | ||
|
|
a742b5f3e2 | ||
|
|
a2a3bb291f | ||
|
|
b7c0ac7d67 | ||
|
|
13b1fc6b44 | ||
|
|
3939fcf6e7 | ||
|
|
13abbb81af | ||
|
|
019f0153c8 | ||
|
|
8abc2925e0 | ||
|
|
996c9a032a | ||
|
|
22d3794d22 | ||
|
|
ba948a38e9 | ||
|
|
e8a522c4df | ||
|
|
c60f3da32e | ||
|
|
0c92d3cbb2 | ||
|
|
e4fc8dd6fe | ||
|
|
1a849e2de0 | ||
|
|
ffdd79f86b | ||
|
|
e87e46b1b6 | ||
|
|
3ca81a4ff7 | ||
|
|
5477d3c292 | ||
|
|
08300183b3 | ||
|
|
095c77f22c | ||
|
|
09926e63a3 | ||
|
|
87d70f29a1 | ||
|
|
3a7661b111 | ||
|
|
ec976953cd | ||
|
|
1837562b50 | ||
|
|
f3bec4fc37 | ||
|
|
c2117ef4fd | ||
|
|
910804ecd1 | ||
|
|
ce5ae1dfdd | ||
|
|
2c31411ffb | ||
|
|
84d69fa2a1 | ||
|
|
2aea296d19 | ||
|
|
2b0bffc362 | ||
|
|
83356142c1 | ||
|
|
24dcc6947c | ||
|
|
a7e3136a0b | ||
|
|
71639cfea7 | ||
|
|
8c2d39fb82 | ||
|
|
e060d29337 | ||
|
|
79e9156d2f | ||
|
|
9100db55b0 | ||
|
|
141c10f6fe | ||
|
|
4613ab9dc3 | ||
|
|
061ad41b51 | ||
|
|
3a0007cd18 | ||
|
|
0044917943 | ||
|
|
0337c04278 | ||
|
|
4585acf70b | ||
|
|
2ad9bbae25 | ||
|
|
cf075df7d1 | ||
|
|
11cd9905c4 | ||
|
|
cf462542e9 | ||
|
|
b63f25a1ae | ||
|
|
7de69a21c5 | ||
|
|
21783e4ea0 | ||
|
|
53e5a756d7 | ||
|
|
8c911a2fd0 | ||
|
|
ce440e9f43 | ||
|
|
6465f8d8e6 | ||
|
|
4bdf28059a | ||
|
|
21010c6540 | ||
|
|
a5a6cabac6 | ||
|
|
f460c77f2c | ||
|
|
1c69d9213e | ||
|
|
c02c2d3b2c | ||
|
|
7adf5e763b | ||
|
|
0e4afefc7f | ||
|
|
c7f290f42e | ||
|
|
bb553b8a6a | ||
|
|
21eda2a1a3 | ||
|
|
48c13c990f | ||
|
|
a2665529f6 | ||
|
|
8d35d78eff | ||
|
|
a82a364c22 | ||
|
|
cc150ff9d2 | ||
|
|
feeb38293d | ||
|
|
5b9271ccfe | ||
|
|
e60bbc2fb7 | ||
|
|
387cc184e6 | ||
|
|
a830ddcb43 | ||
|
|
35fea30191 | ||
|
|
24b672b968 | ||
|
|
de6a3c5ab1 | ||
|
|
260ff6de3e | ||
|
|
7cb37411c9 | ||
|
|
e4e41e5ef8 | ||
|
|
985f4aafca | ||
|
|
0c25dd44b3 | ||
|
|
285f0bc6dd | ||
|
|
07a370fd24 | ||
|
|
95f07ea38a | ||
|
|
2890b92b21 | ||
|
|
8b80c5d0b5 | ||
|
|
04425786a1 | ||
|
|
15b14e12e2 | ||
|
|
b328972c4d | ||
|
|
da3156047f | ||
|
|
f9f08ba54f | ||
|
|
b43684262f | ||
|
|
2120f78bd7 | ||
|
|
2d896f22d6 | ||
|
|
9fa0aebfe9 | ||
|
|
2ddc3af5ff | ||
|
|
fd80e096ea | ||
|
|
fab2d69fce | ||
|
|
83d08e3aba | ||
|
|
bfea6bcacf | ||
|
|
feff34b021 | ||
|
|
79fbade92a | ||
|
|
bf508e453e | ||
|
|
47b51c22fa | ||
|
|
ebd16105f2 | ||
|
|
01fb1a5775 | ||
|
|
b8c2a05db9 | ||
|
|
4087dc5fe4 | ||
|
|
408029e6e0 | ||
|
|
23ff403529 | ||
|
|
be4f81f584 | ||
|
|
361c91e610 | ||
|
|
2aa2ea9675 | ||
|
|
e084225edf | ||
|
|
9ce342205b | ||
|
|
0edf50e00e | ||
|
|
1ea19daad9 | ||
|
|
32b302314e | ||
|
|
ee197e02a2 | ||
|
|
7cd6ffec2a | ||
|
|
0077580838 | ||
|
|
4d85a79fd1 | ||
|
|
a35139bb61 | ||
|
|
f72b833724 | ||
|
|
0e16bac6e2 | ||
|
|
e17564b362 | ||
|
|
90b66eddf5 | ||
|
|
2862a5849f | ||
|
|
7e839231ed | ||
|
|
fa56a4bb73 | ||
|
|
dff950748c | ||
|
|
b7374e3f8e | ||
|
|
4244b92b08 | ||
|
|
4f688028ad | ||
|
|
5a9a18501d | ||
|
|
896b8f7cf1 | ||
|
|
95bd1b014f | ||
|
|
dbdf8642bb | ||
|
|
c3cd9007d2 | ||
|
|
8079fb5bc5 | ||
|
|
80d0b5d4ed | ||
|
|
86bd847bf9 | ||
|
|
943004844a | ||
|
|
4ff12e0426 | ||
|
|
0d7bdb6fa8 | ||
|
|
6d488b6a81 | ||
|
|
f1d0263ffa | ||
|
|
24035caadb | ||
|
|
ed89a3e0f8 | ||
|
|
7088ab89f1 | ||
|
|
d4688bd76b | ||
|
|
befe0fccaf | ||
|
|
a192410498 | ||
|
|
d43ede3a11 | ||
|
|
9848ffb77f | ||
|
|
7487497d1f | ||
|
|
be11c848f4 | ||
|
|
9836b274b9 | ||
|
|
a96d14566f | ||
|
|
1c8cd8c168 | ||
|
|
5f5806a98e | ||
|
|
624b045ac0 | ||
|
|
0f8bae950e | ||
|
|
fcede42903 | ||
|
|
5defe7d975 | ||
|
|
7e79903fe8 | ||
|
|
a2fa52504c | ||
|
|
2629f85e73 | ||
|
|
8577816234 | ||
|
|
e58d47a3b2 | ||
|
|
c9c83ee7e6 | ||
|
|
139693dce6 | ||
|
|
f98193d822 | ||
|
|
8052f1ac9d | ||
|
|
4f78a06993 | ||
|
|
6c4b4b392b | ||
|
|
268fe34238 | ||
|
|
cc620205d6 | ||
|
|
f0d7c97b7b | ||
|
|
660a6e9e4c | ||
|
|
3386c2a7a5 | ||
|
|
9530efb4d4 | ||
|
|
4293515b3d | ||
|
|
61105e0679 | ||
|
|
79a289c7e2 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,4 +1,5 @@
|
|||||||
*.custom.*
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
dist/
|
*.custom.*
|
||||||
node_modules/
|
node_modules/
|
||||||
|
vendor/closure-compiler/
|
||||||
|
vendor/uglifyjs/
|
||||||
|
|||||||
12
.jamignore
Normal file
12
.jamignore
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
.*
|
||||||
|
*.custom.*
|
||||||
|
*.md
|
||||||
|
*.txt
|
||||||
|
build.js
|
||||||
|
index.js
|
||||||
|
build/
|
||||||
|
doc/
|
||||||
|
node_modules/
|
||||||
|
perf/
|
||||||
|
test/
|
||||||
|
vendor/
|
||||||
22
.npmignore
22
.npmignore
@@ -1,17 +1,25 @@
|
|||||||
*.custom.*
|
|
||||||
*.min.*
|
|
||||||
.*
|
.*
|
||||||
dist/
|
*.custom.*
|
||||||
|
*.d.ts
|
||||||
doc/*.php
|
doc/*.php
|
||||||
node_modules/
|
node_modules/
|
||||||
perf/*.html
|
perf/*.html
|
||||||
|
perf/*-ui.js
|
||||||
perf/*.sh
|
perf/*.sh
|
||||||
test/*.html
|
test/*.html
|
||||||
test/*-ui.js
|
test/*-ui.js
|
||||||
test/*.sh
|
test/*.sh
|
||||||
|
vendor/*.gz
|
||||||
vendor/backbone/
|
vendor/backbone/
|
||||||
vendor/docdown/
|
vendor/benchmark.js/*.jar
|
||||||
vendor/qunit/qunit/*.css
|
vendor/closure-compiler
|
||||||
vendor/underscore/
|
vendor/docdown
|
||||||
vendor/requirejs/
|
|
||||||
vendor/firebug-lite/
|
vendor/firebug-lite/
|
||||||
|
vendor/json3/
|
||||||
|
vendor/jquery/
|
||||||
|
vendor/qunit/qunit/*.css
|
||||||
|
vendor/qunit/qunit/*-1.8.0.js
|
||||||
|
vendor/requirejs/
|
||||||
|
vendor/underscore/*-min.js
|
||||||
|
vendor/uglifyjs
|
||||||
|
vendor/underscore/test/
|
||||||
|
|||||||
7
.travis.yml
Normal file
7
.travis.yml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
language: node_js
|
||||||
|
node_js:
|
||||||
|
- 0.6
|
||||||
|
- 0.8
|
||||||
|
before_script:
|
||||||
|
- "curl -H 'Accept: application/vnd.github.v3.raw' https://api.github.com/repos/bestiejs/lodash/git/blobs/aa29a2ecf6f51d4da5a2a418c0d4ea0e368ee80d | tar xvz -C vendor"
|
||||||
|
- "curl -H 'Accept: application/vnd.github.v3.raw' https://api.github.com/repos/bestiejs/lodash/git/blobs/9869c4443fb22598235d1019fcc8245be41e8889 | tar xvz -C vendor"
|
||||||
298
README.md
298
README.md
@@ -1,15 +1,17 @@
|
|||||||
# Lo-Dash <sup>v0.4.1</sup>
|
# Lo-Dash <sup>v0.9.2</sup>
|
||||||
|
[](http://travis-ci.org/bestiejs/lodash)
|
||||||
|
|
||||||
A drop-in replacement for Underscore.js, from the devs behind [jsPerf.com](http://jsperf.com), that delivers [performance improvements](http://lodash.com/benchmarks), [bug fixes](https://github.com/bestiejs/lodash#closed-underscorejs-issues), and [additional features](https://github.com/bestiejs/lodash#features).
|
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-Dash’s performance is gained by avoiding slower native methods, instead opting for simplified non-ES5 compliant methods optimized for common usage, and by leveraging function compilation to reduce the number of overall function calls.
|
Lo-Dash’s performance is gained by avoiding slower native methods, instead opting for simplified non-ES5 compliant methods optimized for common usage, and by leveraging function compilation to reduce the number of overall function calls.
|
||||||
|
|
||||||
## Download
|
## Download
|
||||||
|
|
||||||
* [Development source](https://raw.github.com/bestiejs/lodash/v0.4.1/lodash.js)
|
* [Development build](https://raw.github.com/bestiejs/lodash/v0.9.2/lodash.js)
|
||||||
* [Production source](https://raw.github.com/bestiejs/lodash/v0.4.1/lodash.min.js)
|
* [Production build](https://raw.github.com/bestiejs/lodash/v0.9.2/lodash.min.js)
|
||||||
* CDN copies of ≤ [v0.3.2](http://cdnjs.cloudflare.com/ajax/libs/lodash.js/0.3.2/lodash.min.js) are available on [cdnjs](http://cdnjs.com/) thanks to [CloudFlare](http://www.cloudflare.com/)
|
* [Underscore build](https://raw.github.com/bestiejs/lodash/v0.9.2/lodash.underscore.min.js) tailored for projects already using Underscore
|
||||||
* For optimal performance, [create a custom build](https://github.com/bestiejs/lodash#custom-builds) with only the features you need
|
* CDN copies of ≤ v0.9.2’s [Production](http://cdnjs.cloudflare.com/ajax/libs/lodash.js/0.9.2/lodash.min.js), [Underscore](http://cdnjs.cloudflare.com/ajax/libs/lodash.js/0.9.2/lodash.underscore.min.js), and [Development](http://cdnjs.cloudflare.com/ajax/libs/lodash.js/0.9.2/lodash.js) builds 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
|
## Dive in
|
||||||
|
|
||||||
@@ -24,116 +26,176 @@ For a list of upcoming features, check out our [roadmap](https://github.com/best
|
|||||||
For more information check out these screencasts over Lo-Dash:
|
For more information check out these screencasts over Lo-Dash:
|
||||||
|
|
||||||
* [Introducing Lo-Dash](https://vimeo.com/44154599)
|
* [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-Dash’s origin and why it’s a better utility belt](https://vimeo.com/44154600)
|
* [Lo-Dash’s origin and why it’s a better utility belt](https://vimeo.com/44154600)
|
||||||
|
* [Unit testing in Lo-Dash](https://vimeo.com/45865290)
|
||||||
|
* [Lo-Dash’s approach to native method use](https://vimeo.com/48576012)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
* AMD loader support ([RequireJS](http://requirejs.org/), [curl.js](https://github.com/cujojs/curl), etc.)
|
* AMD loader support ([RequireJS](http://requirejs.org/), [curl.js](https://github.com/cujojs/curl), etc.)
|
||||||
* [_.bind](http://lodash.com/docs#bind) supports *"lazy"* binding
|
* [_.clone](http://lodash.com/docs#clone) supports *“deep”* cloning
|
||||||
* [_.debounce](http://lodash.com/docs#debounce)’ed functions match [_.throttle](http://lodash.com/docs#throttle)’ed functions’ return value behavior
|
* [_.contains](http://lodash.com/docs#contains) accepts a `fromIndex` argument
|
||||||
* [_.forEach](http://lodash.com/docs#forEach) is chainable
|
* [_.forEach](http://lodash.com/docs#forEach) is chainable and supports exiting iteration early
|
||||||
* [_.forIn](http://lodash.com/docs#forIn) for iterating over an object’s own and inherited properties
|
* [_.forIn](http://lodash.com/docs#forIn) for iterating over an object’s own and inherited properties
|
||||||
* [_.forOwn](http://lodash.com/docs#forOwn) for iterating over an object’s own properties
|
* [_.forOwn](http://lodash.com/docs#forOwn) for iterating over an object’s 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
|
* [_.isPlainObject](http://lodash.com/docs#isPlainObject) checks if values are created by the `Object` constructor
|
||||||
* [_.indexOf](http://lodash.com/docs#indexOf) and [_.lastIndexOf](http://lodash.com/docs#lastIndexOf) accept a `fromIndex` argument
|
* [_.lateBind](http://lodash.com/docs#lateBind) for late binding
|
||||||
* [_.partial](http://lodash.com/docs#partial) for more functional fun
|
* [_.merge](http://lodash.com/docs#merge) for a *“deep”* [_.extend](http://lodash.com/docs#extend)
|
||||||
* [_.template](http://lodash.com/docs#template) utilizes [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl) for easier debugging
|
* [_.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) accepts `callback` and `thisArg` arguments
|
||||||
|
* [_.template](http://lodash.com/docs#template) supports [ES6 delimiters](http://people.mozilla.org/~jorendorff/es6-draft.html#sec-7.8.6) and 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),
|
* [_.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, _.max, _.min, _.pluck, _.reduce, _.reduceRight, _.reject, _.shuffle, _.some, _.sortBy, _.where") accept strings
|
||||||
|
|
||||||
## Support
|
## 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~23, Firefox 1~16, IE 6-10, Opera 9.25-12, Safari 3-6, Node.js 0.4.8-0.8.14, Narwhal 0.3.2, RingoJS 0.8, and Rhino 1.7RC5.
|
||||||
|
|
||||||
## Custom builds
|
## Custom builds
|
||||||
|
|
||||||
Custom builds make it easy to create lightweight versions of Lo-Dash containing only the methods you need.
|
Custom builds make it easy to create lightweight versions of Lo-Dash containing only the methods you need.
|
||||||
We handle all the method dependency and alias mapping for you.
|
To top it off, we handle all method dependency and alias mapping for you.
|
||||||
|
|
||||||
* Backbone builds, containing all methods required by Backbone, may be created using the `backbone` modifier argument.
|
* Backbone builds, with only methods required by Backbone, may be created using the `backbone` modifier argument.
|
||||||
~~~ bash
|
```bash
|
||||||
lodash backbone
|
lodash backbone
|
||||||
~~~
|
```
|
||||||
|
|
||||||
|
* 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.
|
* 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
|
lodash legacy
|
||||||
~~~
|
```
|
||||||
|
|
||||||
* Mobile builds, with IE < 9 bug fixes and method compilation removed, may be created using the `mobile` modifier argument.
|
* Mobile builds, with IE < 9 bug fixes and method compilation removed, may be created using the `mobile` modifier argument.
|
||||||
~~~ bash
|
```bash
|
||||||
lodash mobile
|
lodash mobile
|
||||||
~~~
|
```
|
||||||
|
|
||||||
Custom builds may be created in three ways:
|
* 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
|
||||||
|
```
|
||||||
|
|
||||||
1. Use the `category` argument to pass the categories of methods to include in the build.<br>
|
* Underscore builds, tailored for projects already using Underscore, may be created using the `underscore` modifier argument.
|
||||||
Valid categories are *"arrays"*, *"chaining"*, *"collections"*, *"functions"*, *"objects"*, and *"utilities"*.
|
```bash
|
||||||
~~~ bash
|
lodash underscore
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
lodash category="collections, functions"
|
lodash category="collections, functions"
|
||||||
~~~
|
```
|
||||||
|
|
||||||
2. Use the `include` argument to pass the names of methods to include in the build.
|
* Use the `exports` argument to pass comma separated names of ways to export the `LoDash` function.<br>
|
||||||
~~~ bash
|
Valid exports are *“amd”*, *“commonjs”*, *“global”*, *“node”*, and *“none”*.
|
||||||
|
```bash
|
||||||
|
lodash exports=amd,commonjs,node
|
||||||
|
lodash exports="amd, commonjs, node"
|
||||||
|
```
|
||||||
|
|
||||||
|
* Use the `iife` argument to specify code to replace the immediately-invoked function expression that wraps Lo-Dash.
|
||||||
|
```bash
|
||||||
|
lodash iife="!function(window,undefined){%output%}(this)"
|
||||||
|
```
|
||||||
|
|
||||||
|
* Use the `include` argument to pass comma separated method/category names to include in the build.
|
||||||
|
```bash
|
||||||
lodash include=each,filter,map
|
lodash include=each,filter,map
|
||||||
lodash include="each, filter, map"
|
lodash include="each, filter, map"
|
||||||
~~~
|
```
|
||||||
|
|
||||||
3. Use the `exclude` argument to pass the names of methods to exclude from the build.
|
* Use the `minus` argument to pass comma separated method/category names to remove from those included in the build.
|
||||||
~~~ bash
|
```bash
|
||||||
lodash exclude=union,uniq,zip
|
lodash underscore minus=result,shuffle
|
||||||
lodash exclude="union, uniq, zip"
|
lodash underscore minus="result, shuffle"
|
||||||
~~~
|
```
|
||||||
|
|
||||||
All arguments, except `include` with `exclude` and `mobile` with `legacy`, may be combined.
|
* 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"
|
||||||
|
```
|
||||||
|
|
||||||
~~~ bash
|
* Use the `template` argument to pass the file path pattern used to match template files to precompile.
|
||||||
lodash backbone mobile category=functions include=pick,uniq
|
```bash
|
||||||
lodash backbone legacy category=utilities exclude=first,last
|
lodash template="./*.jst"
|
||||||
~~~
|
```
|
||||||
|
|
||||||
|
* Use the `settings` argument to pass the template settings used when precompiling templates.
|
||||||
|
```bash
|
||||||
|
lodash settings="{interpolate:/\\{\\{([\\s\\S]+?)\\}\\}/g}"
|
||||||
|
```
|
||||||
|
|
||||||
|
* Use the `moduleId` argument to specify the AMD module ID of Lo-Dash, which defaults to “lodash”, used by precompiled templates.
|
||||||
|
```bash
|
||||||
|
lodash moduleId="underscore"
|
||||||
|
```
|
||||||
|
|
||||||
|
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` Write output to standard output
|
||||||
|
* `-d`, `--debug` Write only the debug output
|
||||||
|
* `-h`, `--help` Display help information
|
||||||
|
* `-m`, `--minify` Write only the minified output
|
||||||
|
* `-o`, `--output` Write output to a given path/filename
|
||||||
|
* `-s`, `--silent` Skip status updates normally logged to the console
|
||||||
|
* `-V`, `--version` Output current version of Lo-Dash
|
||||||
|
|
||||||
The `lodash` command-line utility is available when Lo-Dash is installed as a global package (i.e. `npm install -g lodash`).
|
The `lodash` command-line utility is available when Lo-Dash is installed as a global package (i.e. `npm install -g lodash`).
|
||||||
|
|
||||||
Custom builds are saved to `lodash.custom.js` and `lodash.custom.min.js`.
|
|
||||||
|
|
||||||
## Installation and usage
|
## Installation and usage
|
||||||
|
|
||||||
In browsers:
|
In browsers:
|
||||||
|
|
||||||
~~~ html
|
```html
|
||||||
<script src="lodash.js"></script>
|
<script src="lodash.js"></script>
|
||||||
~~~
|
```
|
||||||
|
|
||||||
Using [npm](http://npmjs.org/):
|
Using [npm](http://npmjs.org/):
|
||||||
|
|
||||||
~~~ bash
|
```bash
|
||||||
npm install lodash
|
npm install lodash
|
||||||
|
|
||||||
npm install -g lodash
|
npm install -g lodash
|
||||||
~~~
|
npm link lodash
|
||||||
|
```
|
||||||
|
|
||||||
In [Node.js](http://nodejs.org/) and [RingoJS v0.8.0+](http://ringojs.org/):
|
In [Node.js](http://nodejs.org/) and [RingoJS v0.8.0+](http://ringojs.org/):
|
||||||
|
|
||||||
~~~ js
|
```js
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
~~~
|
```
|
||||||
|
|
||||||
In [Narwhal](http://narwhaljs.org/) and [RingoJS v0.7.0-](http://ringojs.org/):
|
**Note:** If Lo-Dash is installed globally, [run `npm link lodash`](http://blog.nodejs.org/2011/03/23/npm-1-0-global-vs-local-installation/) in your project’s root directory before requiring it.
|
||||||
|
|
||||||
~~~ js
|
In [RingoJS v0.7.0-](http://ringojs.org/):
|
||||||
|
|
||||||
|
```js
|
||||||
var _ = require('lodash')._;
|
var _ = require('lodash')._;
|
||||||
~~~
|
```
|
||||||
|
|
||||||
In [Rhino](http://www.mozilla.org/rhino/):
|
In [Rhino](http://www.mozilla.org/rhino/):
|
||||||
|
|
||||||
~~~ js
|
```js
|
||||||
load('lodash.js');
|
load('lodash.js');
|
||||||
~~~
|
```
|
||||||
|
|
||||||
In an AMD loader like [RequireJS](http://requirejs.org/):
|
In an AMD loader like [RequireJS](http://requirejs.org/):
|
||||||
|
|
||||||
~~~ js
|
```js
|
||||||
require({
|
require({
|
||||||
'paths': {
|
'paths': {
|
||||||
'underscore': 'path/to/lodash'
|
'underscore': 'path/to/lodash'
|
||||||
@@ -142,123 +204,35 @@ require({
|
|||||||
['underscore'], function(_) {
|
['underscore'], function(_) {
|
||||||
console.log(_.VERSION);
|
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.1/test/test.js#L306-312)]
|
* Allow iteration of objects with a `length` property [[#799](https://github.com/documentcloud/underscore/pull/799), [test](https://github.com/bestiejs/lodash/blob/v0.9.2/test/test.js#L545-551)]
|
||||||
* Ensure array-like objects with invalid `length` properties are treated like regular objects [[test](https://github.com/bestiejs/lodash/blob/v0.4.1/test/test.js#L257-263), [test](https://github.com/bestiejs/lodash/blob/v0.4.1/test/test.js#L607-621), [test](https://github.com/bestiejs/lodash/blob/v0.4.1/test/test.js#L863-866)]
|
* 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.9.2/test/test.js#L558-582)]
|
||||||
* Ensure *"Arrays"* methods allow falsey `array` arguments [[test](https://github.com/bestiejs/lodash/blob/v0.4.1/test/test.js#L931-970)]
|
* 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.9.2/test/test.js#L140-146)]
|
||||||
* 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.1/test/test.js#L265-283), [test](https://github.com/bestiejs/lodash/blob/v0.4.1/test/test.js#L623-640), [test](https://github.com/bestiejs/lodash/blob/v0.4.1/test/test.js#L868-871)]
|
* `_.isEmpty` should support jQuery/MooTools DOM query collections [[#690](https://github.com/documentcloud/underscore/pull/690), [test](https://github.com/bestiejs/lodash/blob/v0.9.2/test/test.js#L747-752)]
|
||||||
* Ensure templates compiled with errors are inspectable [[#666](https://github.com/documentcloud/underscore/issues/666), [test](https://github.com/bestiejs/lodash/blob/v0.4.1/test/test.js#L737-744)]
|
* `_.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.9.2/test/test.js#L828-840)]
|
||||||
* Fix cross-browser object iteration bugs [[#376](https://github.com/documentcloud/underscore/issues/376), [test](https://github.com/bestiejs/lodash/blob/v0.4.1/test/test.js#L195-207), [test](https://github.com/bestiejs/lodash/blob/v0.4.1/test/test.js#L317-342), [test](https://github.com/bestiejs/lodash/blob/v0.4.1/test/test.js#L438-449), [test](https://github.com/bestiejs/lodash/blob/v0.4.1/test/test.js#L457-459), [test](https://github.com/bestiejs/lodash/blob/v0.4.1/test/test.js#L477-497), [test](https://github.com/bestiejs/lodash/blob/v0.4.1/test/test.js#L667-669)]
|
* `_.keys` should work with `arguments` objects cross-browser [[#396](https://github.com/documentcloud/underscore/issues/396), [test](https://github.com/bestiejs/lodash/blob/v0.9.2/test/test.js#L921-923)]
|
||||||
* Handle arrays with `undefined` values correctly in IE < 9 [[#601](https://github.com/documentcloud/underscore/issues/601)]
|
* `_.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.9.2/test/test.js#L1337-1340)]
|
||||||
* 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.1/test/test.js#L86-92)]
|
|
||||||
* Register as an AMD module, but still export to global [[#431](https://github.com/documentcloud/underscore/pull/431), [test](https://github.com/bestiejs/lodash/blob/v0.4.1/test/test.js#L70-84)]
|
|
||||||
* `_(…)` should return passed wrapper instances [[test](https://github.com/bestiejs/lodash/blob/v0.4.1/test/test.js#L104-107)]
|
|
||||||
* `_.contains` should work with strings [[#667](https://github.com/documentcloud/underscore/pull/667), [test](https://github.com/bestiejs/lodash/blob/v0.4.1/test/test.js#L138-147)]
|
|
||||||
* `_.escape` should return an empty string when passed `null` or `undefined` [[#407](https://github.com/documentcloud/underscore/issues/427), [test](https://github.com/bestiejs/lodash/blob/v0.4.1/test/test.js#L176-179)]
|
|
||||||
* `_.forEach` should be chainable [[#142](https://github.com/documentcloud/underscore/issues/142), [test](https://github.com/bestiejs/lodash/blob/v0.4.1/test/test.js#L252-255)]
|
|
||||||
* `_.groupBy` should add values to own, not inherited, properties [[test](https://github.com/bestiejs/lodash/blob/v0.4.1/test/test.js#L357-364)]
|
|
||||||
* `_isNaN(new Number(NaN))` should return `true` [[test](https://github.com/bestiejs/lodash/blob/v0.4.1/test/test.js#L467-469)]
|
|
||||||
* `_.reduceRight` should pass correct callback arguments when iterating objects [[test](https://github.com/bestiejs/lodash/blob/v0.4.1/test/test.js#L591-605)]
|
|
||||||
* `_.size` should return the `length` of string values [[test](https://github.com/bestiejs/lodash/blob/v0.4.1/test/test.js#L650-652)]
|
|
||||||
* `_.size` shouldn't error on falsey values [[#650](https://github.com/documentcloud/underscore/pull/650), [test](https://github.com/bestiejs/lodash/blob/v0.4.1/test/test.js#L654-661)]
|
|
||||||
* `_.size` should work with `arguments` objects cross-browser [[#653](https://github.com/documentcloud/underscore/issues/653), [test](https://github.com/bestiejs/lodash/blob/v0.4.1/test/test.js#L663-665)]
|
|
||||||
* `_.sortedIndex` should support arrays with high `length` values [[test](https://github.com/bestiejs/lodash/blob/v0.4.1/test/test.js#L707-716)]
|
|
||||||
* `_.template` should not augment the `options` object [[test](https://github.com/bestiejs/lodash/blob/v0.4.1/test/test.js#L731-735)]
|
|
||||||
* `_.throttle` should work when called in a tight loop [[#502](https://github.com/documentcloud/underscore/issues/502), [test](https://github.com/bestiejs/lodash/blob/v0.4.1/test/test.js#L814-824)]
|
|
||||||
* `_.zipObject` should accept less than two arguments [[test](https://github.com/bestiejs/lodash/blob/v0.4.1/test/test.js#L893-895)]
|
|
||||||
|
|
||||||
## Optimized methods <sup>(50+)</sup>
|
|
||||||
|
|
||||||
* `_.bind`
|
|
||||||
* `_.bindAll`
|
|
||||||
* `_.clone`
|
|
||||||
* `_.compact`
|
|
||||||
* `_.contains`, `_.include`
|
|
||||||
* `_.defaults`
|
|
||||||
* `_.defer`
|
|
||||||
* `_.difference`
|
|
||||||
* `_.each`
|
|
||||||
* `_.escape`
|
|
||||||
* `_.every`, `_.all`
|
|
||||||
* `_.extend`
|
|
||||||
* `_.filter`, `_.select`
|
|
||||||
* `_.find`, `_.detect`
|
|
||||||
* `_.flatten`
|
|
||||||
* `_.forEach`, `_.each`
|
|
||||||
* `_.functions`, `_.methods`
|
|
||||||
* `_.groupBy`
|
|
||||||
* `_.indexOf`
|
|
||||||
* `_.intersection`
|
|
||||||
* `_.invoke`
|
|
||||||
* `_.isArguments`
|
|
||||||
* `_.isDate`
|
|
||||||
* `_.isEmpty`
|
|
||||||
* `_.isEqual`
|
|
||||||
* `_.isFinite`
|
|
||||||
* `_.isFunction`
|
|
||||||
* `_.isObject`
|
|
||||||
* `_.isNumber`
|
|
||||||
* `_.isRegExp`
|
|
||||||
* `_.isString`
|
|
||||||
* `_.keys`
|
|
||||||
* `_.lastIndexOf`
|
|
||||||
* `_.map`, `_.collect`
|
|
||||||
* `_.max`
|
|
||||||
* `_.memoize`
|
|
||||||
* `_.min`
|
|
||||||
* `_.mixin`
|
|
||||||
* `_.pick`
|
|
||||||
* `_.pluck`
|
|
||||||
* `_.reduce`, `_.foldl`, `_.inject`
|
|
||||||
* `_.reject`
|
|
||||||
* `_.result`
|
|
||||||
* `_.shuffle`
|
|
||||||
* `_.some`, `_.any`
|
|
||||||
* `_.sortBy`
|
|
||||||
* `_.sortedIndex`
|
|
||||||
* `_.template`
|
|
||||||
* `_.throttle`
|
|
||||||
* `_.times`
|
|
||||||
* `_.toArray`
|
|
||||||
* `_.union`
|
|
||||||
* `_.uniq`, `_.unique`
|
|
||||||
* `_.values`
|
|
||||||
* `_.without`
|
|
||||||
* `_.wrap`
|
|
||||||
* `_.zip`
|
|
||||||
* plus all `_(…)` method wrappers
|
|
||||||
|
|
||||||
## Release Notes
|
## Release Notes
|
||||||
|
|
||||||
### <sup>v0.4.1</sup>
|
### <sup>v0.9.2</sup>
|
||||||
|
|
||||||
* Fixed `_.template` regression
|
* Added `fromIndex` argument to `_.contains`
|
||||||
* Optimized build process to detect and remove more unused variables
|
* Added `moduleId` build option
|
||||||
|
* Added Closure Compiler *“simple”* optimizations to the build process
|
||||||
### <sup>v0.4.0</sup>
|
* Added support for strings in `_.max` and `_.min`
|
||||||
|
* Added support for ES6 template delimiters to `_.template`
|
||||||
* Added `bin` and `scripts` entries to package.json
|
* Ensured re-minification of Lo-Dash by third parties avoids Closure Compiler bugs
|
||||||
* Added `legacy` build option
|
* Optimized `_.every`, `_.find`, `_.some`, and `_.uniq`
|
||||||
* 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`
|
|
||||||
|
|
||||||
The full changelog is available [here](https://github.com/bestiejs/lodash/wiki/Changelog).
|
The full changelog is available [here](https://github.com/bestiejs/lodash/wiki/Changelog).
|
||||||
|
|
||||||
## BestieJS
|
## 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
|
## Author
|
||||||
|
|
||||||
|
|||||||
365
build/minify.js
365
build/minify.js
@@ -8,26 +8,25 @@
|
|||||||
path = require('path'),
|
path = require('path'),
|
||||||
spawn = require('child_process').spawn;
|
spawn = require('child_process').spawn;
|
||||||
|
|
||||||
/** The directory that is the base of the repository */
|
/** The path of 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 */
|
/** The path of the directory where the Closure Compiler is located */
|
||||||
var closurePath = path.join(basePath, 'vendor', 'closure-compiler', 'compiler.jar');
|
var closurePath = path.join(basePath, 'vendor', 'closure-compiler', 'compiler.jar');
|
||||||
|
|
||||||
/** The distribution directory */
|
|
||||||
var distPath = path.join(basePath, 'dist');
|
|
||||||
|
|
||||||
/** Load other modules */
|
/** Load other modules */
|
||||||
var preprocess = require(path.join(__dirname, 'pre-compile')),
|
var preprocess = require('./pre-compile.js'),
|
||||||
postprocess = require(path.join(__dirname, 'post-compile')),
|
postprocess = require('./post-compile.js'),
|
||||||
uglifyJS = require(path.join(basePath, 'vendor', 'uglifyjs', 'uglify-js'));
|
uglifyJS = require('../vendor/uglifyjs/uglify-js.js');
|
||||||
|
|
||||||
/** Closure Compiler command-line options */
|
/** The Closure Compiler command-line options */
|
||||||
var closureOptions = [
|
var closureOptions = ['--warning_level=QUIET'];
|
||||||
'--compilation_level=ADVANCED_OPTIMIZATIONS',
|
|
||||||
'--language_in=ECMASCRIPT5_STRICT',
|
/** The Closure Compiler optimization modes */
|
||||||
'--warning_level=QUIET'
|
var optimizationModes = {
|
||||||
];
|
'simple': 'SIMPLE_OPTIMIZATIONS',
|
||||||
|
'advanced': 'ADVANCED_OPTIMIZATIONS'
|
||||||
|
};
|
||||||
|
|
||||||
/** Reassign `existsSync` for older versions of Node */
|
/** Reassign `existsSync` for older versions of Node */
|
||||||
fs.existsSync || (fs.existsSync = path.existsSync);
|
fs.existsSync || (fs.existsSync = path.existsSync);
|
||||||
@@ -35,15 +34,52 @@
|
|||||||
/*--------------------------------------------------------------------------*/
|
/*--------------------------------------------------------------------------*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The exposed `minify` function minifies a given Lo-Dash `source` and invokes
|
* Minifies a given Lo-Dash `source` and invokes the `options.onComplete`
|
||||||
* the `onComplete` callback when finished.
|
* callback when finished. The `onComplete` callback is invoked with one
|
||||||
|
* argument; (outputSource).
|
||||||
*
|
*
|
||||||
* @param {String} source The source to minify.
|
* @param {Array|String} [source=''] The source to minify or array of commands.
|
||||||
* @param {String} workingName The name to give temporary files creates during the minification process.
|
* -o, --output - Write output to a given path/filename.
|
||||||
* @param {Function} onComplete A function called when minification has completed.
|
* -s, --silent - Skip status updates normally logged to the console.
|
||||||
|
* -t, --template - Applies template specific minifier options.
|
||||||
|
*
|
||||||
|
* @param {Object} [options={}] The options object.
|
||||||
|
* outputPath - Write output to a given path/filename.
|
||||||
|
* isSilent - Skip status updates normally logged to the console.
|
||||||
|
* isTemplate - Applies template specific minifier options.
|
||||||
|
* onComplete - The function called once minification has finished.
|
||||||
*/
|
*/
|
||||||
function minify(source, workingName, onComplete) {
|
function minify(source, options) {
|
||||||
new Minify(source, workingName, onComplete);
|
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,27 +88,35 @@
|
|||||||
* @private
|
* @private
|
||||||
* @constructor
|
* @constructor
|
||||||
* @param {String} source The source to minify.
|
* @param {String} source The source to minify.
|
||||||
* @param {String} workingName The name to give temporary files creates during the minification process.
|
* @param {Object} options The options object.
|
||||||
* @param {Function} onComplete A function called when minification has completed.
|
* outputPath - Write output to a given path/filename.
|
||||||
|
* isSilent - Skip status updates normally logged to the console.
|
||||||
|
* isTemplate - Applies template specific minifier options.
|
||||||
|
* onComplete - The function called once minification has finished.
|
||||||
*/
|
*/
|
||||||
function Minify(source, workingName, onComplete) {
|
function Minify(source, options) {
|
||||||
// create the destination directory if it doesn't exist
|
// juggle arguments
|
||||||
if (!fs.existsSync(distPath)) {
|
if (typeof source == 'object' && source) {
|
||||||
// avoid errors when called as a npm executable
|
options = source || options;
|
||||||
try {
|
source = options.source || '';
|
||||||
fs.mkdirSync(distPath);
|
|
||||||
} catch(e) { }
|
|
||||||
}
|
}
|
||||||
|
this.compiled = { 'simple': {}, 'advanced': {} };
|
||||||
this.compiled = {};
|
this.hybrid = { 'simple': {}, 'advanced': {} };
|
||||||
this.hybrid = {};
|
|
||||||
this.uglified = {};
|
this.uglified = {};
|
||||||
this.onComplete = onComplete;
|
|
||||||
this.source = source = preprocess(source);
|
this.isSilent = !!options.isSilent;
|
||||||
this.workingName = workingName;
|
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
|
// begin the minification process
|
||||||
closureCompile.call(this, source, onClosureCompile.bind(this));
|
closureCompile.call(this, source, 'simple', onClosureSimpleCompile.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*--------------------------------------------------------------------------*/
|
/*--------------------------------------------------------------------------*/
|
||||||
@@ -83,26 +127,22 @@
|
|||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
* @param {String} source The JavaScript source to minify.
|
* @param {String} source The JavaScript source to minify.
|
||||||
* @param {String} [message] The message to log.
|
* @param {String} mode The optimization mode.
|
||||||
* @param {Function} callback The function to call once the process completes.
|
* @param {Function} callback The function called once the process has completed.
|
||||||
*/
|
*/
|
||||||
function closureCompile(source, message, callback) {
|
function closureCompile(source, mode, callback) {
|
||||||
// the standard error stream, standard output stream, and Closure Compiler process
|
// use simple optimizations when minifying template files
|
||||||
|
var options = closureOptions.slice();
|
||||||
|
options.push('--compilation_level=' + optimizationModes[this.isTemplate ? 'simple' : mode]);
|
||||||
|
|
||||||
|
// the standard error stream, standard output stream, and the Closure Compiler process
|
||||||
var error = '',
|
var error = '',
|
||||||
output = '',
|
output = '',
|
||||||
compiler = spawn('java', ['-jar', closurePath].concat(closureOptions));
|
compiler = spawn('java', ['-jar', closurePath].concat(options));
|
||||||
|
|
||||||
// juggle arguments
|
if (!this.isSilent) {
|
||||||
if (typeof message == 'function') {
|
console.log('Compressing ' + path.basename(this.outputPath, '.js') + ' using the Closure Compiler (' + mode + ')...');
|
||||||
callback = message;
|
|
||||||
message = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(message == null
|
|
||||||
? 'Compressing ' + this.workingName + ' using the Closure Compiler...'
|
|
||||||
: message
|
|
||||||
);
|
|
||||||
|
|
||||||
compiler.stdout.on('data', function(data) {
|
compiler.stdout.on('data', function(data) {
|
||||||
// append the data to the output stream
|
// append the data to the output stream
|
||||||
output += data;
|
output += data;
|
||||||
@@ -114,9 +154,8 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
compiler.on('exit', function(status) {
|
compiler.on('exit', function(status) {
|
||||||
var exception = null;
|
|
||||||
|
|
||||||
// `status` contains the process exit code
|
// `status` contains the process exit code
|
||||||
|
var exception = null;
|
||||||
if (status) {
|
if (status) {
|
||||||
exception = new Error(error);
|
exception = new Error(error);
|
||||||
exception.status = status;
|
exception.status = status;
|
||||||
@@ -135,25 +174,17 @@
|
|||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
* @param {String} source The JavaScript source to minify.
|
* @param {String} source The JavaScript source to minify.
|
||||||
* @param {String} [message] The message to log.
|
* @param {String} label The label to log.
|
||||||
* @param {Function} callback The function to call once the process completes.
|
* @param {Function} callback The function called once the process has completed.
|
||||||
*/
|
*/
|
||||||
function uglify(source, message, callback) {
|
function uglify(source, label, callback) {
|
||||||
var exception,
|
var exception,
|
||||||
result,
|
result,
|
||||||
ugly = uglifyJS.uglify;
|
ugly = uglifyJS.uglify;
|
||||||
|
|
||||||
// juggle arguments
|
if (!this.isSilent) {
|
||||||
if (typeof message == 'function') {
|
console.log('Compressing ' + path.basename(this.outputPath, '.js') + ' using ' + label + '...');
|
||||||
callback = message;
|
|
||||||
message = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(message == null
|
|
||||||
? 'Compressing ' + this.workingName + ' using UglifyJS...'
|
|
||||||
: message
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
result = ugly.gen_code(
|
result = ugly.gen_code(
|
||||||
// enable unsafe transformations
|
// enable unsafe transformations
|
||||||
@@ -177,42 +208,79 @@
|
|||||||
/*--------------------------------------------------------------------------*/
|
/*--------------------------------------------------------------------------*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `closureCompile()` callback.
|
* The Closure Compiler callback for simple optimizations.
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
* @param {Object|Undefined} exception The error object.
|
* @param {Object|Undefined} exception The error object.
|
||||||
* @param {String} result The resulting minified source.
|
* @param {String} result The resulting minified source.
|
||||||
*/
|
*/
|
||||||
function onClosureCompile(exception, result) {
|
function onClosureSimpleCompile(exception, result) {
|
||||||
if (exception) {
|
if (exception) {
|
||||||
throw exception;
|
throw exception;
|
||||||
}
|
}
|
||||||
// store the post-processed Closure Compiler result and gzip it
|
result = postprocess(result);
|
||||||
this.compiled.source = result = postprocess(result);
|
this.compiled.simple.source = result;
|
||||||
gzip(result, onClosureGzip.bind(this));
|
gzip(result, onClosureSimpleGzip.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Closure Compiler `gzip` callback.
|
* The Closure Compiler `gzip` callback for simple optimizations.
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
* @param {Object|Undefined} exception The error object.
|
* @param {Object|Undefined} exception The error object.
|
||||||
* @param {Buffer} result The resulting gzipped source.
|
* @param {Buffer} result The resulting gzipped source.
|
||||||
*/
|
*/
|
||||||
function onClosureGzip(exception, result) {
|
function onClosureSimpleGzip(exception, result) {
|
||||||
if (exception) {
|
if (exception) {
|
||||||
throw exception;
|
throw exception;
|
||||||
}
|
}
|
||||||
// store the gzipped result and report the size
|
if (!this.isSilent) {
|
||||||
this.compiled.gzip = result;
|
console.log('Done. Size: %d bytes.', result.length);
|
||||||
console.log('Done. Size: %d bytes.', result.length);
|
}
|
||||||
|
this.compiled.simple.gzip = result;
|
||||||
|
|
||||||
// next, minify the source using only UglifyJS
|
// next, compile the source using advanced optimizations
|
||||||
uglify.call(this, this.source, onUglify.bind(this));
|
closureCompile.call(this, this.source, 'advanced', onClosureAdvancedCompile.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `uglify()` callback.
|
* The Closure Compiler callback for advanced optimizations.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Object|Undefined} exception The error object.
|
||||||
|
* @param {String} result The resulting minified source.
|
||||||
|
*/
|
||||||
|
function onClosureAdvancedCompile(exception, result) {
|
||||||
|
if (exception) {
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
|
result = postprocess(result);
|
||||||
|
this.compiled.advanced.source = result;
|
||||||
|
gzip(result, onClosureAdvancedGzip.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Closure Compiler `gzip` callback for advanced optimizations.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Object|Undefined} exception The error object.
|
||||||
|
* @param {Buffer} result The resulting gzipped source.
|
||||||
|
*/
|
||||||
|
function onClosureAdvancedGzip(exception, result) {
|
||||||
|
if (exception) {
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
|
if (!this.isSilent) {
|
||||||
|
console.log('Done. Size: %d bytes.', result.length);
|
||||||
|
}
|
||||||
|
this.compiled.advanced.gzip = result;
|
||||||
|
|
||||||
|
// next, minify the source using only UglifyJS
|
||||||
|
uglify.call(this, this.source, 'UglifyJS', onUglify.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The UglifyJS callback.
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
* @param {Object|Undefined} exception The error object.
|
* @param {Object|Undefined} exception The error object.
|
||||||
@@ -222,8 +290,8 @@
|
|||||||
if (exception) {
|
if (exception) {
|
||||||
throw exception;
|
throw exception;
|
||||||
}
|
}
|
||||||
// store the post-processed Uglified result and gzip it
|
result = postprocess(result);
|
||||||
this.uglified.source = result = postprocess(result);
|
this.uglified.source = result;
|
||||||
gzip(result, onUglifyGzip.bind(this));
|
gzip(result, onUglifyGzip.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,89 +306,115 @@
|
|||||||
if (exception) {
|
if (exception) {
|
||||||
throw exception;
|
throw exception;
|
||||||
}
|
}
|
||||||
var message = 'Compressing ' + this.workingName + ' using hybrid minification...';
|
if (!this.isSilent) {
|
||||||
|
console.log('Done. Size: %d bytes.', result.length);
|
||||||
// store the gzipped result and report the size
|
}
|
||||||
this.uglified.gzip = result;
|
this.uglified.gzip = result;
|
||||||
console.log('Done. Size: %d bytes.', result.length);
|
|
||||||
|
|
||||||
// next, minify the Closure Compiler minified source using UglifyJS
|
// next, minify the already Closure Compiler simple optimized source using UglifyJS
|
||||||
uglify.call(this, this.compiled.source, message, onHybrid.bind(this));
|
uglify.call(this, this.compiled.simple.source, 'hybrid (simple)', onSimpleHybrid.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The hybrid `uglify()` callback.
|
* The hybrid callback for simple optimizations.
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
* @param {Object|Undefined} exception The error object.
|
* @param {Object|Undefined} exception The error object.
|
||||||
* @param {String} result The resulting minified source.
|
* @param {String} result The resulting minified source.
|
||||||
*/
|
*/
|
||||||
function onHybrid(exception, result) {
|
function onSimpleHybrid(exception, result) {
|
||||||
if (exception) {
|
if (exception) {
|
||||||
throw exception;
|
throw exception;
|
||||||
}
|
}
|
||||||
// store the post-processed Uglified result and gzip it
|
result = postprocess(result);
|
||||||
this.hybrid.source = result = postprocess(result);
|
this.hybrid.simple.source = result;
|
||||||
gzip(result, onHybridGzip.bind(this));
|
gzip(result, onSimpleHybridGzip.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The hybrid `gzip` callback.
|
* The hybrid `gzip` callback for simple optimizations.
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
* @param {Object|Undefined} exception The error object.
|
* @param {Object|Undefined} exception The error object.
|
||||||
* @param {Buffer} result The resulting gzipped source.
|
* @param {Buffer} result The resulting gzipped source.
|
||||||
*/
|
*/
|
||||||
function onHybridGzip(exception, result) {
|
function onSimpleHybridGzip(exception, result) {
|
||||||
if (exception) {
|
if (exception) {
|
||||||
throw exception;
|
throw exception;
|
||||||
}
|
}
|
||||||
// store the gzipped result and report the size
|
if (!this.isSilent) {
|
||||||
this.hybrid.gzip = result;
|
console.log('Done. Size: %d bytes.', result.length);
|
||||||
console.log('Done. Size: %d bytes.', result.length);
|
}
|
||||||
|
this.hybrid.simple.gzip = result;
|
||||||
|
|
||||||
|
// next, minify the already Closure Compiler advance optimized source using UglifyJS
|
||||||
|
uglify.call(this, this.compiled.advanced.source, 'hybrid (advanced)', onAdvancedHybrid.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The hybrid callback for advanced optimizations.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Object|Undefined} exception The error object.
|
||||||
|
* @param {String} result The resulting minified source.
|
||||||
|
*/
|
||||||
|
function onAdvancedHybrid(exception, result) {
|
||||||
|
if (exception) {
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
|
result = postprocess(result);
|
||||||
|
this.hybrid.advanced.source = result;
|
||||||
|
gzip(result, onAdvancedHybridGzip.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The hybrid `gzip` callback for advanced optimizations.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Object|Undefined} exception The error object.
|
||||||
|
* @param {Buffer} result The resulting gzipped source.
|
||||||
|
*/
|
||||||
|
function onAdvancedHybridGzip(exception, result) {
|
||||||
|
if (exception) {
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
|
if (!this.isSilent) {
|
||||||
|
console.log('Done. Size: %d bytes.', result.length);
|
||||||
|
}
|
||||||
|
this.hybrid.advanced.gzip = result;
|
||||||
|
|
||||||
// finish by choosing the smallest compressed file
|
// finish by choosing the smallest compressed file
|
||||||
onComplete.call(this);
|
onComplete.call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The callback executed after JavaScript source is minified and gzipped.
|
* The callback executed after the source is minified and gzipped.
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
function onComplete() {
|
function onComplete() {
|
||||||
var compiled = this.compiled,
|
var compiledSimple = this.compiled.simple,
|
||||||
hybrid = this.hybrid,
|
compiledAdvanced = this.compiled.advanced,
|
||||||
name = this.workingName,
|
uglified = this.uglified,
|
||||||
uglified = this.uglified;
|
hybridSimple = this.hybrid.simple,
|
||||||
|
hybridAdvanced = this.hybrid.advanced;
|
||||||
// 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
|
// select the smallest gzipped file and use its minified counterpart as the
|
||||||
// official minified release (ties go to Closure Compiler)
|
// official minified release (ties go to the Closure Compiler)
|
||||||
var min = Math.min(compiled.gzip.length, hybrid.gzip.length, uglified.gzip.length);
|
var min = Math.min(
|
||||||
|
compiledSimple.gzip.length,
|
||||||
// pass the minified source to the minify instances "onComplete" callback
|
compiledAdvanced.gzip.length,
|
||||||
this.onComplete(
|
uglified.gzip.length,
|
||||||
compiled.gzip.length == min
|
hybridSimple.gzip.length,
|
||||||
? compiled.source
|
hybridAdvanced.gzip.length
|
||||||
: uglified.gzip.length == min
|
|
||||||
? uglified.source
|
|
||||||
: hybrid.source
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// pass the minified source to the "onComplete" callback
|
||||||
|
[compiledSimple, compiledAdvanced, uglified, hybridSimple, hybridAdvanced].some(function(data) {
|
||||||
|
if (data.gzip.length == min) {
|
||||||
|
this.onComplete(data.source);
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*--------------------------------------------------------------------------*/
|
/*--------------------------------------------------------------------------*/
|
||||||
@@ -334,14 +428,11 @@
|
|||||||
// was invoked directly (e.g. `node minify.js source.js`) and write to
|
// was invoked directly (e.g. `node minify.js source.js`) and write to
|
||||||
// `<filename>.min.js`
|
// `<filename>.min.js`
|
||||||
(function() {
|
(function() {
|
||||||
var filePath = process.argv[2],
|
var options = process.argv;
|
||||||
dirPath = path.dirname(filePath),
|
if (options.length < 3) {
|
||||||
source = fs.readFileSync(filePath, 'utf8'),
|
return;
|
||||||
workingName = path.basename(filePath, '.js') + '.min';
|
}
|
||||||
|
minify(options);
|
||||||
minify(source, workingName, function(result) {
|
|
||||||
fs.writeFileSync(path.join(dirPath, workingName + '.js'), result, 'utf8');
|
|
||||||
});
|
|
||||||
}());
|
}());
|
||||||
}
|
}
|
||||||
}());
|
}());
|
||||||
|
|||||||
@@ -6,11 +6,15 @@
|
|||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
|
|
||||||
/** The minimal license/copyright template */
|
/** The minimal license/copyright template */
|
||||||
var licenseTemplate =
|
var licenseTemplate = {
|
||||||
'/*!\n' +
|
'lodash':
|
||||||
' Lo-Dash @VERSION lodash.com/license\n' +
|
'/*!\n' +
|
||||||
' Underscore.js 1.3.3 github.com/documentcloud/underscore/blob/master/LICENSE\n' +
|
' Lo-Dash @VERSION lodash.com/license\n' +
|
||||||
'*/';
|
' Underscore.js 1.4.2 underscorejs.org/LICENSE\n' +
|
||||||
|
'*/',
|
||||||
|
'underscore':
|
||||||
|
'/*! Underscore.js @VERSION underscorejs.org/LICENSE */'
|
||||||
|
};
|
||||||
|
|
||||||
/*--------------------------------------------------------------------------*/
|
/*--------------------------------------------------------------------------*/
|
||||||
|
|
||||||
@@ -22,32 +26,32 @@
|
|||||||
* @returns {String} Returns the processed source.
|
* @returns {String} Returns the processed source.
|
||||||
*/
|
*/
|
||||||
function postprocess(source) {
|
function postprocess(source) {
|
||||||
// exit early if snippet isn't found
|
// move vars exposed by the Closure Compiler into the IIFE
|
||||||
|
source = source.replace(/^((?:(['"])use strict\2;)?(?:var (?:[a-z]+=(?:!0|!1|null)[,;])+)?)([\s\S]*?function[^)]+\){)/, '$3$1');
|
||||||
|
|
||||||
|
// correct overly aggressive Closure Compiler advanced optimizations
|
||||||
|
source = source.replace(/prototype\s*=\s*{\s*valueOf\s*:\s*1\s*}/, 'prototype={valueOf:1,y: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);
|
var snippet = /VERSION\s*[=:]\s*([\'"])(.*?)\1/.exec(source);
|
||||||
if (!snippet) {
|
if (!snippet) {
|
||||||
return source;
|
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');
|
|
||||||
|
|
||||||
// use double quotes consistently
|
|
||||||
source = source.replace(/'use strict'/, '"use strict"');
|
|
||||||
|
|
||||||
// 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
|
// add license
|
||||||
source = license + '\n;' + source;
|
return licenseTemplate[/call\(this\);?$/.test(source) ? 'underscore' : 'lodash']
|
||||||
|
.replace('@VERSION', snippet[2]) + '\n;' + source;
|
||||||
// add trailing semicolon
|
|
||||||
return source.replace(/[\s;]*$/, ';');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*--------------------------------------------------------------------------*/
|
/*--------------------------------------------------------------------------*/
|
||||||
@@ -55,13 +59,20 @@
|
|||||||
// expose `postprocess`
|
// expose `postprocess`
|
||||||
if (module != require.main) {
|
if (module != require.main) {
|
||||||
module.exports = postprocess;
|
module.exports = postprocess;
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
// read the Lo-Dash source file from the first argument if the script
|
// read the Lo-Dash source file from the first argument if the script
|
||||||
// was invoked directly (e.g. `node post-compile.js source.js`) and write to
|
// was invoked directly (e.g. `node post-compile.js source.js`) and write to
|
||||||
// the same file
|
// the same file
|
||||||
(function() {
|
(function() {
|
||||||
var source = fs.readFileSync(process.argv[2], 'utf8');
|
var options = process.argv;
|
||||||
fs.writeFileSync(process.argv[2], postprocess(source), 'utf8');
|
if (options.length < 3) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var filePath = options[options.length - 1],
|
||||||
|
source = fs.readFileSync(filePath, 'utf8');
|
||||||
|
|
||||||
|
fs.writeFileSync(filePath, postprocess(source), 'utf8');
|
||||||
}());
|
}());
|
||||||
}
|
}
|
||||||
}());
|
}());
|
||||||
|
|||||||
106
build/post-install.js
Normal file
106
build/post-install.js
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
;(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/** Load Node modules */
|
||||||
|
var exec = require('child_process').exec,
|
||||||
|
fs = require('fs'),
|
||||||
|
https = require('https'),
|
||||||
|
path = require('path'),
|
||||||
|
tar = require('../vendor/tar/tar.js'),
|
||||||
|
zlib = require('zlib');
|
||||||
|
|
||||||
|
/** The path of the directory that is the base of the repository */
|
||||||
|
var basePath = fs.realpathSync(path.join(__dirname, '..'));
|
||||||
|
|
||||||
|
/** The path of the `vendor` directory */
|
||||||
|
var vendorPath = path.join(basePath, 'vendor');
|
||||||
|
|
||||||
|
/** The Git object ID of `closure-compiler.tar.gz` */
|
||||||
|
var closureId = 'aa29a2ecf6f51d4da5a2a418c0d4ea0e368ee80d';
|
||||||
|
|
||||||
|
/** The Git object ID of `uglifyjs.tar.gz` */
|
||||||
|
var uglifyId = '9869c4443fb22598235d1019fcc8245be41e8889';
|
||||||
|
|
||||||
|
/*--------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches a required `.tar.gz` dependency with the given Git object ID from
|
||||||
|
* the Lo-Dash repo on GitHub. The object ID may be obtained by running
|
||||||
|
* `git hash-object path/to/dependency.tar.gz`.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Object} options The options object.
|
||||||
|
*
|
||||||
|
* id - The Git object ID of the `.tar.gz` file.
|
||||||
|
* onComplete - The function, invoked with one argument (exception),
|
||||||
|
* called once the extraction has finished.
|
||||||
|
* path - The path of the extraction directory.
|
||||||
|
* title - The dependency's title used in status updates logged to the console.
|
||||||
|
*/
|
||||||
|
function getDependency(options) {
|
||||||
|
options || (options = {});
|
||||||
|
|
||||||
|
var onComplete = options.onComplete,
|
||||||
|
title = options.title;
|
||||||
|
|
||||||
|
function callback(exception) {
|
||||||
|
if (exception) {
|
||||||
|
console.error('There was a problem downloading ' + title + '.');
|
||||||
|
}
|
||||||
|
onComplete(exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Downloading ' + title + '...');
|
||||||
|
|
||||||
|
https.get({
|
||||||
|
'host': 'api.github.com',
|
||||||
|
'path': '/repos/bestiejs/lodash/git/blobs/' + options.id,
|
||||||
|
'headers': {
|
||||||
|
// By default, all GitHub blob API endpoints return a JSON document
|
||||||
|
// containing Base64-encoded blob data. Overriding the `Accept` header
|
||||||
|
// with the GitHub raw media type returns the blob data directly.
|
||||||
|
'Accept': 'application/vnd.github.v3.raw'
|
||||||
|
}
|
||||||
|
}, function(response) {
|
||||||
|
var parser = new tar.Extract({
|
||||||
|
'path': options.path
|
||||||
|
})
|
||||||
|
.on('end', callback)
|
||||||
|
.on('error', callback);
|
||||||
|
|
||||||
|
response.pipe(zlib.createUnzip()).pipe(parser);
|
||||||
|
})
|
||||||
|
.on('error', callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*--------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
exec('npm -g root', function(exception, stdout) {
|
||||||
|
if (exception) {
|
||||||
|
console.error('There was a problem loading the npm registry.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
// exit early if not a global install
|
||||||
|
if (path.resolve(basePath, '..') != stdout.trim()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// download the Closure Compiler
|
||||||
|
getDependency({
|
||||||
|
'title': 'the Closure Compiler',
|
||||||
|
'id': closureId,
|
||||||
|
'path': vendorPath,
|
||||||
|
'onComplete':function(exceptionA) {
|
||||||
|
// download UglifyJS
|
||||||
|
getDependency({
|
||||||
|
'title': 'UglifyJS',
|
||||||
|
'id': uglifyId,
|
||||||
|
'path': vendorPath,
|
||||||
|
'onComplete': function(exceptionB) {
|
||||||
|
process.exit(exceptionA || exceptionB ? 1 : 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}());
|
||||||
@@ -7,74 +7,58 @@
|
|||||||
|
|
||||||
/** Used to minify variables embedded in compiled strings */
|
/** Used to minify variables embedded in compiled strings */
|
||||||
var compiledVars = [
|
var compiledVars = [
|
||||||
'accumulator',
|
'argsIndex',
|
||||||
'args',
|
'argsLength',
|
||||||
'arrayClass',
|
|
||||||
'callback',
|
'callback',
|
||||||
'className',
|
|
||||||
'collection',
|
'collection',
|
||||||
'compareAscending',
|
'createCallback',
|
||||||
'ctor',
|
'ctor',
|
||||||
'funcClass',
|
|
||||||
'hasOwnProperty',
|
'hasOwnProperty',
|
||||||
'identity',
|
|
||||||
'index',
|
'index',
|
||||||
'isFunc',
|
'isArguments',
|
||||||
|
'isString',
|
||||||
'iteratee',
|
'iteratee',
|
||||||
'iteratorBind',
|
|
||||||
'length',
|
'length',
|
||||||
'methodName',
|
|
||||||
'nativeKeys',
|
'nativeKeys',
|
||||||
'noaccum',
|
|
||||||
'object',
|
'object',
|
||||||
'objectTypes',
|
'objectTypes',
|
||||||
'prop',
|
'ownIndex',
|
||||||
'propIndex',
|
'ownProps',
|
||||||
'props',
|
|
||||||
'property',
|
|
||||||
'propertyIsEnumerable',
|
'propertyIsEnumerable',
|
||||||
'result',
|
'result',
|
||||||
'skipProto',
|
'skipProto',
|
||||||
'slice',
|
|
||||||
'source',
|
|
||||||
'sourceIndex',
|
|
||||||
'stringClass',
|
|
||||||
'target',
|
|
||||||
'thisArg',
|
'thisArg',
|
||||||
'toString',
|
|
||||||
'value'
|
'value'
|
||||||
];
|
];
|
||||||
|
|
||||||
/** Used to minify `compileIterator` option properties */
|
/** Used to minify `compileIterator` option properties */
|
||||||
var iteratorOptions = [
|
var iteratorOptions = [
|
||||||
'args',
|
'args',
|
||||||
'array',
|
'arrayLoop',
|
||||||
'arrayBranch',
|
|
||||||
'beforeLoop',
|
|
||||||
'bottom',
|
'bottom',
|
||||||
'exit',
|
|
||||||
'firstArg',
|
'firstArg',
|
||||||
'hasDontEnumBug',
|
'hasDontEnumBug',
|
||||||
'inLoop',
|
|
||||||
'init',
|
|
||||||
'isKeysFast',
|
'isKeysFast',
|
||||||
'iteratee',
|
'objectLoop',
|
||||||
'object',
|
'noArgsEnum',
|
||||||
'objectBranch',
|
|
||||||
'noCharByIndex',
|
'noCharByIndex',
|
||||||
'shadowed',
|
'shadowed',
|
||||||
'top',
|
'top',
|
||||||
'useHas'
|
'useHas',
|
||||||
|
'useStrict'
|
||||||
];
|
];
|
||||||
|
|
||||||
/** Used to minify variables and string values to a single character */
|
/** Used to minify variables and string values to a single character */
|
||||||
var minNames = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
|
var minNames = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
|
||||||
|
minNames.push.apply(minNames, minNames.map(function(value) {
|
||||||
|
return value + value;
|
||||||
|
}));
|
||||||
|
|
||||||
/** Used to protect the specified properties from getting minified */
|
/** Used to protect the specified properties from getting minified */
|
||||||
var propWhitelist = [
|
var propWhitelist = [
|
||||||
'_',
|
'_',
|
||||||
'_chain',
|
'__chain__',
|
||||||
'_wrapped',
|
'__wrapped__',
|
||||||
'after',
|
'after',
|
||||||
'all',
|
'all',
|
||||||
'amd',
|
'amd',
|
||||||
@@ -83,12 +67,12 @@
|
|||||||
'bind',
|
'bind',
|
||||||
'bindAll',
|
'bindAll',
|
||||||
'chain',
|
'chain',
|
||||||
'clearTimeout',
|
|
||||||
'clone',
|
'clone',
|
||||||
'collect',
|
'collect',
|
||||||
'compact',
|
'compact',
|
||||||
'compose',
|
'compose',
|
||||||
'contains',
|
'contains',
|
||||||
|
'countBy',
|
||||||
'criteria',
|
'criteria',
|
||||||
'debounce',
|
'debounce',
|
||||||
'defaults',
|
'defaults',
|
||||||
@@ -96,12 +80,13 @@
|
|||||||
'delay',
|
'delay',
|
||||||
'detect',
|
'detect',
|
||||||
'difference',
|
'difference',
|
||||||
|
'drop',
|
||||||
'each',
|
'each',
|
||||||
'environment',
|
'environment',
|
||||||
'escape',
|
'escape',
|
||||||
'escape',
|
|
||||||
'evaluate',
|
'evaluate',
|
||||||
'every',
|
'every',
|
||||||
|
'exports',
|
||||||
'extend',
|
'extend',
|
||||||
'filter',
|
'filter',
|
||||||
'find',
|
'find',
|
||||||
@@ -113,16 +98,19 @@
|
|||||||
'forIn',
|
'forIn',
|
||||||
'forOwn',
|
'forOwn',
|
||||||
'functions',
|
'functions',
|
||||||
|
'global',
|
||||||
'groupBy',
|
'groupBy',
|
||||||
'has',
|
'has',
|
||||||
'head',
|
'head',
|
||||||
'identity',
|
'identity',
|
||||||
'include',
|
'include',
|
||||||
|
'index',
|
||||||
'indexOf',
|
'indexOf',
|
||||||
'initial',
|
'initial',
|
||||||
'inject',
|
'inject',
|
||||||
'interpolate',
|
'interpolate',
|
||||||
'intersection',
|
'intersection',
|
||||||
|
'invert',
|
||||||
'invoke',
|
'invoke',
|
||||||
'isArguments',
|
'isArguments',
|
||||||
'isArray',
|
'isArray',
|
||||||
@@ -139,24 +127,31 @@
|
|||||||
'isNull',
|
'isNull',
|
||||||
'isNumber',
|
'isNumber',
|
||||||
'isObject',
|
'isObject',
|
||||||
|
'isPlainObject',
|
||||||
'isRegExp',
|
'isRegExp',
|
||||||
'isString',
|
'isString',
|
||||||
'isUndefined',
|
'isUndefined',
|
||||||
'keys',
|
'keys',
|
||||||
'last',
|
'last',
|
||||||
'lastIndexOf',
|
'lastIndexOf',
|
||||||
|
'lateBind',
|
||||||
'map',
|
'map',
|
||||||
'max',
|
'max',
|
||||||
'memoize',
|
'memoize',
|
||||||
|
'merge',
|
||||||
'methods',
|
'methods',
|
||||||
'min',
|
'min',
|
||||||
'mixin',
|
'mixin',
|
||||||
'noConflict',
|
'noConflict',
|
||||||
|
'object',
|
||||||
|
'omit',
|
||||||
'once',
|
'once',
|
||||||
'opera',
|
'opera',
|
||||||
|
'pairs',
|
||||||
'partial',
|
'partial',
|
||||||
'pick',
|
'pick',
|
||||||
'pluck',
|
'pluck',
|
||||||
|
'random',
|
||||||
'range',
|
'range',
|
||||||
'reduce',
|
'reduce',
|
||||||
'reduceRight',
|
'reduceRight',
|
||||||
@@ -164,7 +159,6 @@
|
|||||||
'rest',
|
'rest',
|
||||||
'result',
|
'result',
|
||||||
'select',
|
'select',
|
||||||
'setTimeout',
|
|
||||||
'shuffle',
|
'shuffle',
|
||||||
'size',
|
'size',
|
||||||
'some',
|
'some',
|
||||||
@@ -179,6 +173,7 @@
|
|||||||
'throttle',
|
'throttle',
|
||||||
'times',
|
'times',
|
||||||
'toArray',
|
'toArray',
|
||||||
|
'unescape',
|
||||||
'union',
|
'union',
|
||||||
'uniq',
|
'uniq',
|
||||||
'unique',
|
'unique',
|
||||||
@@ -187,10 +182,14 @@
|
|||||||
'values',
|
'values',
|
||||||
'variable',
|
'variable',
|
||||||
'VERSION',
|
'VERSION',
|
||||||
|
'where',
|
||||||
'without',
|
'without',
|
||||||
'wrap',
|
'wrap',
|
||||||
'zip',
|
'zip',
|
||||||
'zipObject'
|
|
||||||
|
// properties used by underscore.js
|
||||||
|
'_chain',
|
||||||
|
'_wrapped'
|
||||||
];
|
];
|
||||||
|
|
||||||
/*--------------------------------------------------------------------------*/
|
/*--------------------------------------------------------------------------*/
|
||||||
@@ -198,25 +197,33 @@
|
|||||||
/**
|
/**
|
||||||
* Pre-process a given Lo-Dash `source`, preparing it for minification.
|
* 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.
|
* @returns {String} Returns the processed source.
|
||||||
*/
|
*/
|
||||||
function preprocess(source) {
|
function preprocess(source, options) {
|
||||||
|
source || (source = '');
|
||||||
|
options || (options = {});
|
||||||
|
|
||||||
|
// remove unrecognized JSDoc tags so the 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
|
// remove copyright to add later in post-compile.js
|
||||||
source = source.replace(/\/\*![\s\S]+?\*\//, '');
|
source = source.replace(/\/\*![\s\S]+?\*\//, '');
|
||||||
|
|
||||||
// remove unrecognized JSDoc tags so Closure Compiler won't complain
|
// add brackets to whitelisted properties so the Closure Compiler won't mung them
|
||||||
source = source.replace(/@(?:alias|category)\b.*/g, '');
|
|
||||||
|
|
||||||
// add brackets to whitelisted properties so Closure Compiler won't mung them
|
|
||||||
// http://code.google.com/closure/compiler/docs/api-tutorial3.html#export
|
// http://code.google.com/closure/compiler/docs/api-tutorial3.html#export
|
||||||
source = source.replace(RegExp('\\.(' + propWhitelist.join('|') + ')\\b', 'g'), "['$1']");
|
source = source.replace(RegExp('\\.(' + propWhitelist.join('|') + ')\\b', 'g'), "['$1']");
|
||||||
|
|
||||||
// remove brackets from `_.escape()` in `tokenizeEscape`
|
|
||||||
source = source.replace(/_\['escape']\("/, '_.escape("');
|
|
||||||
|
|
||||||
// remove brackets from `_.escape()` in `_.template`
|
// 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`
|
// remove brackets from `collection.indexOf` in `_.contains`
|
||||||
source = source.replace("collection['indexOf'](target)", 'collection.indexOf(target)');
|
source = source.replace("collection['indexOf'](target)", 'collection.indexOf(target)');
|
||||||
@@ -227,13 +234,16 @@
|
|||||||
// remove whitespace from string literals
|
// remove whitespace from string literals
|
||||||
source = source.replace(/'(?:(?=(\\?))\1.)*?'/g, function(string) {
|
source = source.replace(/'(?:(?=(\\?))\1.)*?'/g, function(string) {
|
||||||
// avoids removing the '\n' of the `stringEscapes` object
|
// 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;
|
return match == false || match == '\\n' ? '' : match;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// remove whitespace from `_.template` related regexpes
|
// add newline to `+"__p+='"` in underscore.js `_.template`
|
||||||
source = source.replace(/(?:reDelimiterCode\w+|reEmptyString\w+|reInsertVariable) *=.+/g, function(match) {
|
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, '');
|
return match.replace(/ |\\n/g, '');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -246,35 +256,34 @@
|
|||||||
.replace("') {\\n'", "'){'")
|
.replace("') {\\n'", "'){'")
|
||||||
|
|
||||||
// remove `useSourceURL` variable
|
// 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`
|
// 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' and `_.sortBy`
|
||||||
(function() {
|
(function() {
|
||||||
var properties = ['criteria', 'value'],
|
var properties = ['criteria', 'index', 'value'],
|
||||||
snippets = source.match(/( +)(?:function compareAscending|var sortBy)\b[\s\S]+?\n\1}/g);
|
snippets = source.match(/( +)function (?:compareAscending|sortBy)\b[\s\S]+?\n\1}/g);
|
||||||
|
|
||||||
if (!snippets) {
|
if (!snippets) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
snippets.forEach(function(snippet) {
|
snippets.forEach(function(snippet) {
|
||||||
var modified = snippet,
|
var modified = snippet;
|
||||||
isSortBy = /var sortBy\b/.test(modified),
|
|
||||||
isInlined = !/\bcreateIterator\b/.test(modified);
|
|
||||||
|
|
||||||
// minify properties
|
// minify properties
|
||||||
properties.forEach(function(property, index) {
|
properties.forEach(function(property, index) {
|
||||||
// add quotes around properties in the inlined `_.sortBy` of the mobile
|
var reBracketProp = RegExp("\\['(" + property + ")'\\]", 'g'),
|
||||||
// build so Closure Compiler won't mung them
|
reDotProp = RegExp('\\.' + property + '\\b', 'g'),
|
||||||
if (isSortBy && isInlined) {
|
rePropColon = RegExp("([^?\\s])\\s*([\"'])?\\b" + property + "\\2 *:", 'g');
|
||||||
modified = modified
|
|
||||||
.replace(RegExp('\\.' + property + '\\b', 'g'), "['" + minNames[index] + "']")
|
modified = modified
|
||||||
.replace(RegExp('\\b' + property + ' *:', 'g'), "'" + minNames[index] + "':");
|
.replace(reBracketProp, "['" + minNames[index] + "']")
|
||||||
}
|
.replace(reDotProp, "['" + minNames[index] + "']")
|
||||||
modified = modified.replace(RegExp('\\b' + property + '\\b', 'g'), minNames[index]);
|
.replace(rePropColon, "$1'" + minNames[index] + "':");
|
||||||
});
|
});
|
||||||
|
|
||||||
// replace with modified snippet
|
// replace with modified snippet
|
||||||
source = source.replace(snippet, modified);
|
source = source.replace(snippet, modified);
|
||||||
});
|
});
|
||||||
@@ -304,7 +313,7 @@
|
|||||||
isIteratorTemplate = /var iteratorTemplate\b/.test(snippet),
|
isIteratorTemplate = /var iteratorTemplate\b/.test(snippet),
|
||||||
modified = snippet;
|
modified = snippet;
|
||||||
|
|
||||||
// add brackets to whitelisted properties so Closure Compiler won't mung them
|
// add brackets to whitelisted properties so the Closure Compiler won't mung them
|
||||||
modified = modified.replace(RegExp('\\.(' + iteratorOptions.join('|') + ')\\b', 'g'), "['$1']");
|
modified = modified.replace(RegExp('\\.(' + iteratorOptions.join('|') + ')\\b', 'g'), "['$1']");
|
||||||
|
|
||||||
if (isCreateIterator) {
|
if (isCreateIterator) {
|
||||||
@@ -332,17 +341,11 @@
|
|||||||
modified = modified.replace(RegExp('\\b' + property + '\\b', 'g'), minNames[index]);
|
modified = modified.replace(RegExp('\\b' + property + '\\b', 'g'), minNames[index]);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (property == 'array' || property == 'object') {
|
// minify property name strings
|
||||||
// minify "array" and "object" sub property names
|
modified = modified.replace(RegExp("'" + property + "'", 'g'), "'" + minNames[index] + "'");
|
||||||
modified = modified.replace(RegExp("'" + property + "'( *[\\]:])", 'g'), "'" + minNames[index] + "'$1");
|
// minify property names in accessors
|
||||||
}
|
if (isCreateIterator) {
|
||||||
else {
|
modified = modified.replace(RegExp('\\.' + property + '\\b' , 'g'), '.' + minNames[index]);
|
||||||
// minify property name strings
|
|
||||||
modified = modified.replace(RegExp("'" + property + "'", 'g'), "'" + minNames[index] + "'");
|
|
||||||
// minify property names in regexps and accessors
|
|
||||||
if (isCreateIterator) {
|
|
||||||
modified = modified.replace(RegExp('([\\.|/])' + property + '\\b' , 'g'), '$1' + minNames[index]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -365,8 +368,17 @@
|
|||||||
// was invoked directly (e.g. `node pre-compile.js source.js`) and write to
|
// was invoked directly (e.g. `node pre-compile.js source.js`) and write to
|
||||||
// the same file
|
// the same file
|
||||||
(function() {
|
(function() {
|
||||||
var source = fs.readFileSync(process.argv[2], 'utf8');
|
var options = process.argv;
|
||||||
fs.writeFileSync(process.argv[2], preprocess(source), 'utf8');
|
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');
|
||||||
}());
|
}());
|
||||||
}
|
}
|
||||||
}());
|
}());
|
||||||
|
|||||||
4607
doc/README.md
4607
doc/README.md
File diff suppressed because it is too large
Load Diff
@@ -20,9 +20,10 @@
|
|||||||
|
|
||||||
// generate Markdown
|
// generate Markdown
|
||||||
$markdown = docdown(array(
|
$markdown = docdown(array(
|
||||||
'path' => '../' . $file,
|
'path' => '../' . $file,
|
||||||
'title' => 'Lo-Dash <sup>v0.4.1</sup>',
|
'title' => 'Lo-Dash <sup>v0.9.2</sup>',
|
||||||
'url' => 'https://github.com/bestiejs/lodash/blob/master/lodash.js'
|
'toc' => 'categories',
|
||||||
|
'url' => 'https://github.com/bestiejs/lodash/blob/master/lodash.js'
|
||||||
));
|
));
|
||||||
|
|
||||||
// save to a .md file
|
// save to a .md file
|
||||||
|
|||||||
71
lodash.min.js
vendored
71
lodash.min.js
vendored
@@ -1,35 +1,40 @@
|
|||||||
/*!
|
/*!
|
||||||
Lo-Dash 0.4.1 lodash.com/license
|
Lo-Dash 0.9.2 lodash.com/license
|
||||||
Underscore.js 1.3.3 github.com/documentcloud/underscore/blob/master/LICENSE
|
Underscore.js 1.4.2 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(){for(var e,t,n,s=-1,o=arguments.length,u={e:"",f:"",j:"",q:"",c:{d:""},n:{d:""}};++s<o;)for(t in e=arguments[s],e)n=(n=e[t])==r?"":n,/d|i/.test(t)?("string"==typeof n&&(n={b:n,m:n}),u.c[t]=n.b,u.n[t]=n.m):u[t]=n;e=u.a,t=/^[^,]+/.exec(e)[0],u.l=u.l||t,u.g=t,u.h=vt,u.k=bt,u.p=$,u.r=u.r!==i,"o"in u||(u.o=gt),u.f||(u.f="if(!"+t+")return A");if("f"!=t||!u.c.i)u.c=r;t="var A",u.
|
;(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||Y);if(i)for(var s={},n=t-1;++n<r;){var o=e[n]+"";(yt.call(s,o)?s[o]:s[o]=[]).push(e[n])}return function(n){if(i){var r=n+"";return yt.call(s,r)&&-1<q(s[r],n)}return-1<q(e,n,t)}}function u(e){return e.charCodeAt(0)}function a(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 f
|
||||||
j&&(t+="="+u.j),t+=";"+u.f+";"+u.q+";var l,n="+u.l+";",u.c&&(t+="var p=n.length;l=-1;",u.n&&(t+="if(p===p>>>0){"),u.o&&(t+="if(I.call(n)==F){n=n.split('')}"),t+=u.c.d+";while(++l<p){"+u.c.i+"}",u.n&&(t+="}"));if(u.n){u.c&&(t+="else{"),u.h||(t+="var B=typeof n=='function'&&z.call(n,'prototype');"),u.k&&u.r?t+="var x=r(n),w=-1,p=x.length;"+u.n.d+";while(++w<p){l=x[w];if(!(B&&l=='prototype')){"+u.n.i+"}}":(t+=u.n.d+";for(l in n){",u.h?(u.r&&(t+="if(j.call(n,l)){"),t+=u.n.i+";",u.r&&(t+="}")):(t+="if(!(B&&l=='prototype')"
|
(e,t,n){function r(){var u=arguments,a=s?this:t;return i||(e=t[o]),n.length&&(u=u.length?n.concat(Et.call(u)):n),this instanceof r?(d.prototype=e.prototype,a=new d,u=e.apply(a,u),T(u)?u:a):e.apply(a,u)}var i=x(e),s=!n,o=e;return s&&(n=t),r}function l(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:X}function c(){for(var e={b:"",c:"",e:It,f:Kt,g:"",h:Ut,i:Xt,j:pt,k:"",l:n},t,r=0;t=arguments[r];r++)for(var i in t)e[i]=t[i];t=e.a,e.d=/^[^,]+/
|
||||||
,u.r&&(t+="&&j.call(n,l)"),t+="){"+u.n.i+"}"),t+="}");if(u.h){t+="var h=n.constructor;";for(n=0;7>n;n++)t+="l='"+u.p[n]+"';if(","constructor"==u.p[n]&&(t+="!(h&&h.prototype===n)&&"),t+="j.call(n,l)){"+u.n.i+"}"}u.c&&(t+="}")}return t+=u.e+";return A",Function("c,g,i,j,k,o,u,r,z,C,F,I",'"use strict";return function('+e+"){"+t+"}")(ot,a,ft,G,k,h,Et,st,Z,et,ht,tt)}function a(e,n){return e=e.a,n=n.a,e===t?1:n===t?-1:e<n?-1:e>n?1:0}function f(e,t){return K[t]}function l(e){return"\\"+St[e]}function c(
|
.exec(t)[0],r="var h,v,k="+e.d+",s="+e.d+";if(!"+e.d+")return s;"+e.k+";",e.b?(r+="var l=k.length;h=-1;if(typeof l=='number'){",e.i&&(r+="if(j(k)){k=k.split('')}"),r+="while(++h<l){v=k[h];"+e.b+"}}else {"):e.h&&(r+="var l=k.length;h=-1;if(l&&i(k)){while(++h<l){v=k[h+=''];"+e.g+"}}else {"),e.e||(r+="var t=typeof k=='function'&&r.call(k,'prototype');");if(e.f&&e.l)r+="var p=-1,q=o[typeof k]?m(k):[],l=q.length;while(++p<l){h=q[p];",e.e||(r+="if(!(t&&h=='prototype')){"),r+="v=k[h];"+e.g+"",e.e||(r+="}"
|
||||||
e){return wt[e]}function h(e,t){return function(n,r,i){return e.call(t,n,r,i)}}function p(){}function d(e,t){if(F.test(t))return"<e%-"+t+"%>";var n=K.length;return K[n]="'+__e("+t+")+'",J+n}function v(e,t,n,r){return e=K.length,t?K[e]="';"+t+";__p+='":n?K[e]="'+__e("+n+")+'":r&&(K[e]="'+((__t=("+r+"))==null?'':__t)+'"),J+e}function m(e,t){if(F.test(t))return"<e%="+t+"%>";var n=K.length;return K[n]="'+((__t=("+t+"))==null?'':__t)+'",J+n}function g(e,t,n,r){if(!e)return n;var i=e.length,s=3>arguments
|
);else{r+="for(h in k){";if(!e.e||e.l)r+="if(",e.e||(r+="!(t&&h=='prototype')"),!e.e&&e.l&&(r+="&&"),e.l&&(r+="g.call(k,h)"),r+="){";r+="v=k[h];"+e.g+";";if(!e.e||e.l)r+="}"}r+="}";if(e.e){r+="var f=k.constructor;";for(i=0;7>i;i++)r+="h='"+e.j[i]+"';if(","constructor"==e.j[i]&&(r+="!(f&&f.prototype===k)&&"),r+="g.call(k,h)){v=k[h];"+e.g+"}"}if(e.b||e.h)r+="}";return r+=e.c+";return s",Function("e,g,i,j,o,m,r","return function("+t+"){"+r+"}")(l,yt,m,N,Gt,kt,wt)}function h(e){return"\\"+Yt[e]}function p
|
||||||
.length;r&&(t=h(t,r));if(i===i>>>0){var o=gt&&tt.call(e)==ht?e.split(""):e;for(i&&s&&(n=o[--i]);i--;)n=t(n,o[i],i,e);return n}o=Gt(e);for((i=o.length)&&s&&(n=e[o[--i]]);i--;)s=o[i],n=t(n,e[s],s,e);return n}function y(e,t,n){if(e)return t==r||n?e[0]:et.call(e,0,t)}function b(e,t){var n=[];if(!e)return n;for(var r,i=-1,s=e.length;++i<s;)r=e[i],Kt(r)?Y.apply(n,t?r:b(r)):n.push(r);return n}function w(e,t,n){if(!e)return-1;var r=-1,i=e.length;if(n){if("number"!=typeof n)return r=x(e,t),e[r]===t?r:-1;r=
|
(e){return sn[e]}function d(){}function v(e){return on[e]}function m(e){return St.call(e)==Mt}function g(e){var t=i;if(!e||"object"!=typeof e||m(e))return t;var n=e.constructor;return(!Vt||"function"==typeof e.toString||"string"!=typeof (e+""))&&(!x(n)||n instanceof n)?qt?(nn(e,function(e,n,r){return t=!yt.call(r,n),i}),t===i):(nn(e,function(e,n){t=n}),t===i||yt.call(e,t)):t}function y(e){var t=[];return rn(e,function(e,n){t.push(n)}),t}function b(e,t,n,s,o){if(e==r)return e;n&&(t=i);if(n=T(e)){var u=
|
||||||
(0>n?Math.max(0,i+n):n)-1}for(;++r<i;)if(e[r]===t)return r;return-1}function E(e,t,n){var r=-Infinity,i=r;if(!e)return i;var s=-1,o=e.length;if(!t){for(;++s<o;)e[s]>i&&(i=e[s]);return i}for(n&&(t=h(t,n));++s<o;)n=t(e[s],s,e),n>r&&(r=n,i=e[s]);return i}function S(e,t,n){return e?et.call(e,t==r||n?1:t):[]}function x(e,t,n,r){if(!e)return 0;var i=0,s=e.length;if(n){r&&(n=N(n,r));for(t=n(t);i<s;)r=i+s>>>1,n(e[r])<t?i=r+1:s=r}else for(;i<s;)r=i+s>>>1,e[r]<t?i=r+1:s=r;return i}function T(e,t,n,r){var s=
|
St.call(e);if(!Qt[u]||zt&&m(e))return e;var a=u==_t,n=a||(u==Bt?ln(e):n)}if(!n||!t)return n?a?Et.call(e):an({},e):e;n=e.constructor;switch(u){case Dt:case Pt:return new n(+e);case Ht:case Ft:return new n(e);case jt:return n(e.source,st.exec(e))}s||(s=[]),o||(o=[]);for(u=s.length;u--;)if(s[u]==e)return o[u];var f=a?n(e.length):{};return s.push(e),o.push(f),(a?hn:rn)(e,function(e,n){f[n]=b(e,t,r,s,o)}),f}function w(e){var t=[];return nn(e,function(e,n){x(e)&&t.push(n)}),t.sort()}function E(e){var t=
|
||||||
[];if(!e)return s;var o=-1,u=e.length,a=[];"function"==typeof t&&(r=n,n=t,t=i);for(n?r&&(n=h(n,r)):n=k;++o<u;)if(r=n(e[o],o,e),t?!o||a[a.length-1]!==r:0>w(a,r))a.push(r),s.push(e[o]);return s}function N(e,t){function n(){var o=arguments,u=t;return i||(e=t[r]),s.length&&(o=o.length?Q.apply(s,o):s),this instanceof n?(p.prototype=e.prototype,u=new p,(o=e.apply(u,o))&&Et[typeof o]?o:u):e.apply(u,o)}var r,i=tt.call(e)==ft;if(i){if(yt||nt&&2<arguments.length)return nt.call.apply(nt,arguments)}else r=t,
|
{};return rn(e,function(e,n){t[e]=n}),t}function S(e,t,s,o){if(e===t)return 0!==e||1/e==1/t;if(e==r||t==r)return e===t;var u=St.call(e);if(u!=St.call(t))return i;switch(u){case Dt:case Pt:return+e==+t;case Ht:return e!=+e?t!=+t:0==e?1/e==1/t:e==+t;case jt:case Ft:return e==t+""}var a=u==_t||u==Mt;if(zt&&!a&&(a=m(e))&&!m(t))return i;if(!a){if(e.__wrapped__||t.__wrapped__)return S(e.__wrapped__||e,t.__wrapped__||t);if(u!=Bt||Vt&&("function"!=typeof e.toString&&"string"==typeof (e+"")||"function"!=typeof
|
||||||
t=e;var s=et.call(arguments,2);return n}function C(e,t,s){s||(s=[]);if(e===t)return 0!==e||1/e==1/t;if(e==r||t==r)return e===t;e._chain&&(e=e._wrapped),t._chain&&(t=t._wrapped);if(e.isEqual&&tt.call(e.isEqual)==ft)return e.isEqual(t);if(t.isEqual&&tt.call(t.isEqual)==ft)return t.isEqual(e);var o=tt.call(e);if(o!=tt.call(t))return i;switch(o){case ht:return e==""+t;case lt:return e!=+e?t!=+t:0==e?1/e==1/t:e==+t;case ut:case at:return+e==+t;case ct:return e.source==t.source&&e.global==t.global&&e.multiline==
|
t.toString&&"string"==typeof (t+"")))return i;var u=e.constructor,f=t.constructor;if(u!=f&&(!x(u)||!(u instanceof u&&x(f)&&f instanceof f)))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=S(e[l],t[l],s,o)););return f}for(var c in e)if(yt.call(e,c)&&(l++,!yt.call(t,c)||!S(e[c],t[c],s,o)))return i;for(c in t)if(yt.call(t,c)&&!(l--))return i;if(It)for(;7>++u;)if(c=pt[u],yt.call(e,c)&&(!yt.call
|
||||||
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==ot){if(f=e.length,a=f==t.length)for(;f--&&(a=C(e[f],t[f],s)););}else{if("constructor"in e!="constructor"in t||e.constructor!=t.constructor)return i;for(var l in e)if(G.call(e,l)&&(f++,!(a=G.call(t,l)&&C(e[l],t[l],s))))break;if(a){for(l in t)if(G.call(t,l)&&!(f--))break;a=!f}if(a&&vt)for(;7>++u&&(l=$[u],!G.call(e,l)||!!(a=G.call(t,l
|
(t,c)||!S(e[c],t[c],s,o)))return i;return n}function x(e){return"function"==typeof e}function T(e){return e?Gt[typeof e]:i}function N(e){return St.call(e)==Ft}function C(e,t,n){var i=arguments,s=0,o=2,u=i[3],a=i[4];n!==Q&&(u=[],a=[],o=i.length);for(;++s<o;)rn(i[s],function(t,n){var i,s,o;if(t&&((s=fn(t))||ln(t))){for(var f=u.length;f--;)if(i=u[f]==t)break;i?e[n]=a[f]:(u.push(t),a.push(o=(o=e[n],s)?fn(o)?o:[]:ln(o)?o:{}),e[n]=C(o,t,Q,u,a))}else t!=r&&(e[n]=t)});return e}function k(e){var t=[];return rn
|
||||||
)&&C(e[l],t[l],s))););}return s.pop(),a}function k(e){return e}function L(e){Ht(Jt(e),function(t){var r=s[t]=e[t];o.prototype[t]=function(){var e=[this._wrapped];return arguments.length&&Y.apply(e,arguments),e=r.apply(s,e),this._chain&&(e=new o(e),e._chain=n),e}})}var n=!0,r=null,i=!1,A,O,M,_,D="object"==typeof exports&&exports&&("object"==typeof global&&global&&global==global.global&&(e=global),exports),P=Array.prototype,H=Object.prototype,B=0,j=e._,F=/[-+=!~*%&^<>|{(\/]|\[\D|\b(?:delete|in|instanceof|new|typeof|void)\b/
|
(e,function(e){t.push(e)}),t}function L(e,t,n){var r=-1,i=e?e.length:0,n=(0>n?Lt(0,i+n):n)||0;return"number"==typeof i?-1<(N(e)?e.indexOf(t,n):q(e,t,n)):j(e,function(e){return++r>=n&&e===t})}function A(e,t,r){var i=n,t=l(t,r);if(fn(e))for(var r=-1,s=e.length;++r<s&&(i=!!t(e[r],r,e)););else hn(e,function(e,n,r){return i=!!t(e,n,r)});return i}function O(e,t,n){var r=[],t=l(t,n);return hn(e,function(e,n,i){t(e,n,i)&&r.push(e)}),r}function M(e,t,n){var r,t=l(t,n);return hn(e,function(e,n,s){if(t(e,n,
|
||||||
,I=/\b__p\+='';/g,q=/\b(__p\+=)''\+/g,R=/(__e\(.*?\)|\b__t\))\+'';/g,U=/(?:__e|__t=)\(\s*(?![\d\s"']|this\.)/g,z=RegExp("^"+(H.valueOf+"").replace(/[.*+?^=!:${}()|[\]\/\\]/g,"\\$&").replace(/valueOf|for [^\]]+/g,".+?")+"$"),W=/__token__(\d+)/g,X=/[&<"']/g,V=/['\n\r\t\u2028\u2029\\]/g,$="constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf".split(" "),J="__token__",K=[],Q=P.concat,G=H.hasOwnProperty,Y=P.push,Z=H.propertyIsEnumerable,et=P.slice,tt=H.toString
|
s))return r=e,i}),r}function _(e,t,n){var r=-1,i=e?e.length:0,s=Array("number"==typeof i?i:0),t=l(t,n);if(fn(e))for(;++r<i;)s[r]=t(e[r],r,e);else hn(e,function(e,n,i){s[++r]=t(e,n,i)});return s}function D(e,t,n){var r=-Infinity,i=-1,s=e?e.length:0,o=r;if(t||!fn(e))t=!t&&N(e)?u:l(t,n),hn(e,function(e,n,i){n=t(e,n,i),n>r&&(r=n,o=e)});else for(;++i<s;)e[i]>o&&(o=e[i]);return o}function P(e,t){var n=[];return hn(e,function(e){n.push(e[t])}),n}function H(e,t,n,r){var s=3>arguments.length,t=l(t,r);return hn
|
||||||
,nt=z.test(nt=et.bind)&&nt,rt=z.test(rt=Array.isArray)&&rt,it=e.isFinite,st=z.test(st=Object.keys)&&st,ot="[object Array]",ut="[object Boolean]",at="[object Date]",ft="[object Function]",lt="[object Number]",ct="[object RegExp]",ht="[object String]",pt=e.clearTimeout,dt=e.setTimeout,vt=!Z.call({valueOf:0},"valueOf"),mt="x"!=et.call("x")[0],gt="xx"!="x"[0]+Object("x")[0],yt=nt&&/\n|Opera/.test(nt+tt.call(e.opera)),bt=st&&/^.+$|true/.test(st+!!e.attachEvent),wt={"&":"&","<":"<",'"':""","'"
|
(e,function(e,r,o){n=s?(s=i,e):t(n,e,r,o)}),n}function B(e,t,n,r){var s=e,o=e?e.length:0,u=3>arguments.length;if("number"!=typeof o)var a=cn(e),o=a.length;else Xt&&N(e)&&(s=e.split(""));return hn(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 j(e,t,n){var r,t=l(t,n);if(fn(e))for(var n=-1,i=e.length;++n<i&&!(r=t(e[n],n,e)););else hn(e,function(e,n,i){return!(r=t(e,n,i))});return!!r}function F(e,t,n){if(e)return t==r||n?e[0]:Et.call(e,0,t)}function I(e,t){for(var n=-1
|
||||||
:"'"},Et={"boolean":i,"function":n,object:n,number:i,string:i,"undefined":i},St={"\\":"\\","'":"'","\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 xt={a:"f,d,H",j:"f",q:"if(!d){d=k}else if(H)d=o(d,H)",i:"d(n[l],l,f)"},Tt={j:"true",i:"if(!d(n[l],l,f))return!A"},Nt={a:"t",j:"t",q:"for(var D,E=1,p=arguments.length;E<p;E++){D=arguments[E];"+(vt?"if(D){":""),l:"D"
|
,r=e?e.length:0,i=[];++n<r;){var s=e[n];fn(s)?bt.apply(i,t?s:I(s)):i.push(s)}return i}function q(e,t,n){var r=-1,i=e?e.length:0;if("number"==typeof n)r=(0>n?Lt(0,i+n):n||0)-1;else if(n)return r=U(e,t),e[r]===t?r:-1;for(;++r<i;)if(e[r]===t)return r;return-1}function R(e,t,n){return e?Et.call(e,t==r||n?1:t):[]}function U(e,t,n,r){for(var i=0,s=e?e.length:i,n=n?l(n,r):X,t=n(t);i<s;)r=i+s>>>1,n(e[r])<t?i=r+1:s=r;return i}function z(e,t,n,r){var s=-1,o=e?e.length:0,u=[],a=u;"function"==typeof t&&(r=n,
|
||||||
,r:i,i:"A[l]=n[l]",e:(vt?"}":"")+"}"},Ct={j:"[]",i:"d(n[l],l,f)&&A.push(n[l])"},kt={q:"if(H)d=o(d,H)"},Lt={i:{m:xt.i}},At={j:"",f:"if(!f)return[]",d:{b:"A=Array(p)",m:"A="+(bt?"Array(p)":"[]")},i:{b:"A[l]=d(n[l],l,f)",m:"A"+(bt?"[w]=":".push")+"(d(n[l],l,f))"}},Ot=u({a:"t",f:"if(!(t&&u[typeof t]))throw TypeError()",j:"[]",i:"A.push(l)"}),Mt=u({a:"f,G",j:"false",o:i,d:{b:"if(I.call(n)==F)return f.indexOf(G)>-1"},i:"if(n[l]===G)return true"}),_t=u(xt,Tt),Dt=u(xt,Ct),Pt=u(xt,kt,{j:"",i:"if(d(n[l],l,f))return n[l]"
|
n=t,t=i);var f=!t&&74<o;if(f)var c={};n&&(a=[],n=l(n,r));for(;++s<o;){var r=e[s],h=n?n(r,s,e):r;f&&(a=yt.call(c,h+"")?c[h]:c[h]=[]);if(t?!s||a[a.length-1]!==h:0>q(a,h))(n||f)&&a.push(h),u.push(r)}return u}function W(e,t){return Jt||xt&&2<arguments.length?xt.call.apply(xt,arguments):f(e,t,Et.call(arguments,2))}function X(e){return e}function V(e){hn(w(e),function(t){var r=s[t]=e[t];s.prototype[t]=function(){var e=[this.__wrapped__];return bt.apply(e,arguments),e=r.apply(s,e),this.__chain__&&(e=new
|
||||||
}),Ht=u(xt,kt),Bt=u(xt,{j:"{}",q:"var v,m=typeof d=='function';if(m&&H)d=o(d,H)",i:"v=m?d(n[l],l,f):n[l][d];(j.call(A,v)?A[v]:A[v]=[]).push(n[l])"}),jt=u(At,{a:"f,q",q:"var b=C.call(arguments,2),m=typeof q=='function'",i:{b:"A[l]=(m?q:n[l][q]).apply(n[l],b)",m:"A"+(bt?"[w]=":".push")+"((m?q:n[l][q]).apply(n[l],b))"}}),Ft=u(xt,At),It=u(At,{a:"f,y",i:{b:"A[l]=n[l][y]",m:"A"+(bt?"[w]=":".push")+"(n[l][y])"}}),qt=u({a:"f,d,a,H",j:"a",q:"var s=arguments.length<3;if(H)d=o(d,H)",d:{b:"if(s)A=f[++l]"},i:
|
s(e),e.__chain__=n),e}})}var n=!0,r=null,i=!1,$="object"==typeof exports&&exports,J="object"==typeof global&&global;J.global===J&&(e=J);var K=[],Q=new function(){},G=0,Y=30,Z=e._,et=/[-?+=!~*%&^<>|{(\/]|\[\D|\b(?:delete|in|instanceof|new|typeof|void)\b/,tt=/&(?:amp|lt|gt|quot|#x27);/g,nt=/\b__p\+='';/g,rt=/\b(__p\+=)''\+/g,it=/(__e\(.*?\)|\b__t\))\+'';/g,st=/\w*$/,ot=/(?:__e|__t=)\(\s*(?![\d\s"']|this\.)/g,ut=RegExp("^"+(Q.valueOf+"").replace(/[.*+?^=!:${}()|[\]\/\\]/g,"\\$&").replace(/valueOf|for [^\]]+/g
|
||||||
{b:"A=d(A,n[l],l,f)",m:"A=s?(s=false,n[l]):d(A,n[l],l,f)"}}),Rt=u(xt,Ct,{i:"!"+Ct.i}),Ut=u(xt,Tt,{j:"false",i:Tt.i.replace("!","")}),zt=u(xt,At,{q:"if(typeof d=='string'){var v=d;d=function(f){return f[v]}}else if(H)d=o(d,H)",i:{b:"A[l]={a:d(n[l],l,f),b:n[l]}",m:"A"+(bt?"[w]=":".push")+"({a:d(n[l],l,f),b:n[l]})"},e:"A.sort(g);p=A.length;while(p--){A[p]=A[p].b}"}),Wt=u(Nt,{i:"if(A[l]==null)"+Nt.i}),Xt=u(Nt),Vt=u(xt,kt,Lt,{r:i}),$t=u(xt,kt,Lt),Jt=u({a:"t",j:"[]",r:i,i:"if(I.call(n[l])==i)A.push(l)"
|
,".+?")+"$"),at=/\$\{((?:(?=\\?)\\?[\s\S])*?)}/g,ft=/<%=([\s\S]+?)%>/g,lt=/($^)/,ct=/[&<>"']/g,ht=/['\n\r\t\u2028\u2029\\]/g,pt="constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf".split(" "),dt=Math.ceil,vt=K.concat,mt=Math.floor,gt=ut.test(gt=Object.getPrototypeOf)&>,yt=Q.hasOwnProperty,bt=K.push,wt=Q.propertyIsEnumerable,Et=K.slice,St=Q.toString,xt=ut.test(xt=Et.bind)&&xt,Tt=ut.test(Tt=Array.isArray)&&Tt,Nt=e.isFinite,Ct=e.isNaN,kt=ut.test(kt=Object
|
||||||
,e:"A.sort()"});Ht({Arguments:"[object Arguments]",Date:at,Function:ft,Number:lt,RegExp:ct,String:ht},function(e,t){s["is"+t]=function(t){return tt.call(t)==e}}),s.isArguments(arguments)||(s.isArguments=function(e){return!!e&&!!G.call(e,"callee")});var Kt=rt||function(e){return tt.call(e)==ot},Qt=u({a:"J",j:"true",q:"var e=I.call(J);if(e==c||e==F)return!J.length",i:{m:"return false"}}),Gt=st?function(e){return"function"==typeof e&&Z.call(e,"prototype")?Ot(e):st(e)}:Ot,Yt=u({a:"t",j:"[]",i:"A.push(n[l])"
|
.keys)&&kt,Lt=Math.max,At=Math.min,Ot=Math.random,Mt="[object Arguments]",_t="[object Array]",Dt="[object Boolean]",Pt="[object Date]",Ht="[object Number]",Bt="[object Object]",jt="[object RegExp]",Ft="[object String]",It,qt,Rt=(Rt={0:1,length:1},K.splice.call(Rt,0,1),Rt[0]),Ut=n;(function(){function e(){this.x=1}var t=[];e.prototype={valueOf:1,y:1};for(var n in new e)t.push(n);for(n in arguments)Ut=!n;It=!/valueOf/.test(t),qt="x"!=t[0]})(1);var zt=!m(arguments),Wt="x"!=Et.call("x")[0],Xt="xx"!="x"
|
||||||
});s.VERSION="0.4.1",s.after=function(e,t){return 1>e?t():function(){if(1>--e)return t.apply(this,arguments)}},s.bind=N,s.bindAll=function(e){var t=arguments,n=1;1==t.length&&(n=0,t=Jt(e));for(var r=t.length;n<r;n++)e[t[n]]=N(e[t[n]],e);return e},s.chain=function(e){return e=new o(e),e._chain=n,e},s.clone=function(e){return e&&Et[typeof e]?Kt(e)?e.slice():Xt({},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=
|
[0]+Object("x")[0];try{var Vt=("[object Object]",St.call(e.document||0)==Bt)}catch($t){}var Jt=xt&&/\n|Opera/.test(xt+St.call(e.opera)),Kt=kt&&/^.+$|true/.test(kt+!!e.attachEvent),Qt={};Qt[Mt]=Qt["[object Function]"]=i,Qt[_t]=Qt[Dt]=Qt[Pt]=Qt[Ht]=Qt[Bt]=Qt[jt]=Qt[Ft]=n;var Gt={"boolean":i,"function":n,object:n,number:i,string:i,"undefined":i},Yt={"\\":"\\","'":"'","\n":"n","\r":"r"," ":"t","\u2028":"u2028","\u2029":"u2029"};s.templateSettings={escape:/<%-([\s\S]+?)%>/g,evaluate:/<%([\s\S]+?)%>/g,
|
||||||
arguments;return function(){for(var t=arguments,n=e.length;n--;)t=[e[n].apply(this,t)];return t[0]}},s.contains=Mt,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,pt(a),a=dt(i,t),r&&(o=e.apply(u,s)),o}},s.defaults=Wt,s.defer=function(e){var n=et.call(arguments,1);return dt(function(){return e.apply(t,n)},1)},s.delay=function(e,n){var r=et.call(arguments,2);return dt(function(){return e.apply(t,r)},n)},s.difference=function(
|
interpolate:ft,variable:""};var Zt={a:"d,c,u",k:"c=e(c,u)",b:"if(c(v,h,d)===false)return s",g:"if(c(v,h,d)===false)return s"},en={l:i,a:"n",k:"for(var a=1,b=arguments.length;a<b;a++){if(k=arguments[a]){",g:"s[h]=v",c:"}}"},tn={b:r};zt&&(m=function(e){return e?yt.call(e,"callee"):i});var nn=c(Zt,tn,{l:i}),rn=c(Zt,tn),sn={"&":"&","<":"<",">":">",'"':""","'":"'"},on=E(sn),un=c(en,{g:"if(s[h]==null)"+en.g}),an=c(en),fn=Tt||function(e){return St.call(e)==_t};x(/x/)&&(x=function(e){
|
||||||
e){var t=[];if(!e)return t;for(var n=-1,r=e.length,i=Q.apply(t,arguments);++n<r;)0>w(i,e[n],r)&&t.push(e[n]);return t},s.escape=function(e){return e==r?"":(e+"").replace(X,c)},s.every=_t,s.extend=Xt,s.filter=Dt,s.find=Pt,s.first=y,s.flatten=b,s.forEach=Ht,s.forIn=Vt,s.forOwn=$t,s.functions=Jt,s.groupBy=Bt,s.has=function(e,t){return G.call(e,t)},s.identity=k,s.indexOf=w,s.initial=function(e,t,n){return e?et.call(e,0,-(t==r||n?1:t)):[]},s.intersection=function(e){var t=[];if(!e)return t;for(var n,r=-1
|
return"[object Function]"==St.call(e)});var ln=gt?function(e){if(!e||"object"!=typeof e)return i;var t=e.valueOf,n="function"==typeof t&&(n=gt(t))&>(n);return n?e==n||gt(e)==n&&!m(e):g(e)}:g,cn=kt?function(e){return"function"==typeof e&&wt.call(e,"prototype")?y(e):T(e)?kt(e):[]}:y,hn=c(Zt);s.VERSION="0.9.2",s.after=function(e,t){return 1>e?t():function(){if(1>--e)return t.apply(this,arguments)}},s.bind=W,s.bindAll=function(e){for(var t=arguments,n=1<t.length?0:(t=w(e),-1),r=t.length;++n<r;){var i=
|
||||||
,i=e.length,s=et.call(arguments,1);++r<i;)n=e[r],0>w(t,n)&&_t(s,function(e){return-1<w(e,n)})&&t.push(n);return t},s.invoke=jt,s.isArray=Kt,s.isBoolean=function(e){return e===n||e===i||tt.call(e)==ut},s.isElement=function(e){return!!e&&1==e.nodeType},s.isEmpty=Qt,s.isEqual=C,s.isFinite=function(e){return it(e)&&tt.call(e)==lt},s.isNaN=function(e){return tt.call(e)==lt&&e!=+e},s.isNull=function(e){return e===r},s.isObject=function(e){return e&&Et[typeof e]},s.isUndefined=function(e){return e===t},
|
t[n];e[i]=W(e[i],e)}return e},s.chain=function(e){return e=new s(e),e.__chain__=n,e},s.clone=b,s.compact=function(e){for(var t=-1,n=e?e.length:0,r=[];++t<n;){var i=e[t];i&&r.push(i)}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=L,s.countBy=function(e,t,n){var r={},t=l(t,n);return hn(e,function(e,n,i){n=t(e,n,i),yt.call(r,n)?r[n]++:r[n]=1}),r},s.debounce=function(e,t,n){function i(){a=r,n||(o=e.apply
|
||||||
s.keys=Gt,s.last=function(e,t,n){if(e){var i=e.length;return t==r||n?e[i-1]:et.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=Ft,s.max=E,s.memoize=function(e,t){var n={};return function(){var r=t?t.apply(this,arguments):arguments[0];return G.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
|
(u,s))}var s,o,u,a;return function(){var r=n&&!a;return s=arguments,u=this,clearTimeout(a),a=setTimeout(i,t),r&&(o=e.apply(u,s)),o}},s.defaults=un,s.defer=function(e){var n=Et.call(arguments,1);return setTimeout(function(){e.apply(t,n)},1)},s.delay=function(e,n){var r=Et.call(arguments,2);return setTimeout(function(){e.apply(t,r)},n)},s.difference=function(e){for(var t=-1,n=e?e.length:0,r=vt.apply(K,arguments),r=o(r,n),i=[];++t<n;){var s=e[t];r(s)||i.push(s)}return i},s.escape=function(e){return e==
|
||||||
;if(!t){for(;++s<o;)e[s]<i&&(i=e[s]);return i}for(n&&(t=h(t,n));++s<o;)n=t(e[s],s,e),n<r&&(r=n,i=e[s]);return i},s.mixin=L,s.noConflict=function(){return e._=j,this},s.once=function(e){var t,r=i;return function(){return r?t:(r=n,t=e.apply(this,arguments))}},s.partial=function(e){var t=et.call(arguments,1),n=t.length;return function(){var r;return r=arguments,r.length&&(t.length=n,Y.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=Q.apply
|
r?"":(e+"").replace(ct,p)},s.every=A,s.extend=an,s.filter=O,s.find=M,s.first=F,s.flatten=I,s.forEach=hn,s.forIn=nn,s.forOwn=rn,s.functions=w,s.groupBy=function(e,t,n){var r={},t=l(t,n);return hn(e,function(e,n,i){n=t(e,n,i),(yt.call(r,n)?r[n]:r[n]=[]).push(e)}),r},s.has=function(e,t){return e?yt.call(e,t):i},s.identity=X,s.indexOf=q,s.initial=function(e,t,n){return e?Et.call(e,0,-(t==r||n?1:t)):[]},s.intersection=function(e){var t=arguments,n=t.length,r={},i=[];return hn(e,function(e){if(0>q(i,e)
|
||||||
(P,arguments),i=r.length,s={};++n<i;)t=r[n],t in e&&(s[t]=e[t]);return s},s.pluck=It,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=qt,s.reduceRight=g,s.reject=Rt,s.rest=S,s.result=function(e,t){if(!e)return r;var n=e[t];return tt.call(n)==ft?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=
|
){for(var s=n;--s;)if(!(r[s]||(r[s]=o(t[s])))(e))return;i.push(e)}}),i},s.invert=E,s.invoke=function(e,t){var n=Et.call(arguments,2),r="function"==typeof t,i=[];return hn(e,function(e){i.push((r?t:e[t]).apply(e,n))}),i},s.isArguments=m,s.isArray=fn,s.isBoolean=function(e){return e===n||e===i||St.call(e)==Dt},s.isDate=function(e){return St.call(e)==Pt},s.isElement=function(e){return e?1===e.nodeType:i},s.isEmpty=function(e){var t=n;if(!e)return t;var r=St.call(e),s=e.length;return r==_t||r==Ft||r==
|
||||||
function(e){if(!e)return 0;var t=e.length;return t===t>>>0?e.length:Gt(e).length},s.some=Ut,s.sortBy=zt,s.sortedIndex=x,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,c=s.templateSettings,n=n.variable;i==r&&(i=c.escape),u==r&&(u=c.evaluate),a==r&&(a=c.interpolate),i&&(e=e.replace(i,d)),a&&(e=e.replace(a,m)),u!=A&&(A=u,_=RegExp((u?u.source:"($^)")+"|<e%-([\\s\\S]+?)%>|<e%=([\\s\\S]+?)%>","g")),i=K.length,e=e.replace(_,v),
|
Mt||zt&&m(e)||r==Bt&&"number"==typeof s&&x(e.splice)?!s:(rn(e,function(){return t=i}),t)},s.isEqual=S,s.isFinite=function(e){return Nt(e)&&!Ct(parseFloat(e))},s.isFunction=x,s.isNaN=function(e){return St.call(e)==Ht&&e!=+e},s.isNull=function(e){return e===r},s.isNumber=function(e){return St.call(e)==Ht},s.isObject=T,s.isPlainObject=ln,s.isRegExp=function(e){return St.call(e)==jt},s.isString=N,s.isUndefined=function(e){return e===t},s.keys=cn,s.last=function(e,t,n){if(e){var i=e.length;return t==r||
|
||||||
i=i!=K.length,e="__p += '"+e.replace(V,l).replace(W,f)+"';",K.length=0,n||(n=c.variable||O||"obj",i?e="with("+n+"){"+e+"}":(n!=O&&(O=n,M=RegExp("(\\(\\s*)"+n+"\\."+n+"\\b","g")),e=e.replace(U,"$&"+n+".").replace(M,"$1__d"))),e=(i?e.replace(I,""):e).replace(q,"$1").replace(R,"$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)(
|
n?e[i-1]:Et.call(e,-t||i)}},s.lastIndexOf=function(e,t,n){var r=e?e.length:0;for("number"==typeof n&&(r=(0>n?Lt(0,r+n):At(n,r-1))+1);r--;)if(e[r]===t)return r;return-1},s.lateBind=function(e,t){return f(t,e,Et.call(arguments,2))},s.map=_,s.max=D,s.memoize=function(e,t){var n={};return function(){var r=t?t.apply(this,arguments):arguments[0];return yt.call(n,r)?n[r]:n[r]=e.apply(this,arguments)}},s.merge=C,s.min=function(e,t,n){var r=Infinity,i=-1,s=e?e.length:0,o=r;if(t||!fn(e))t=!t&&N(e)?u:l(t,n)
|
||||||
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=dt(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&&tt.call(e.toArray)==ft)return e.toArray();var t=e.length;return t===t>>>0?(mt?tt.call(e)==ht:"string"==typeof
|
,hn(e,function(e,n,i){n=t(e,n,i),n<r&&(r=n,o=e)});else for(;++i<s;)e[i]<o&&(o=e[i]);return o},s.mixin=V,s.noConflict=function(){return e._=Z,this},s.object=function(e,t){for(var n=-1,r=e?e.length:0,i={};++n<r;){var s=e[n];t?i[s]=t[n]:i[s[0]]=s[1]}return i},s.omit=function(e,t,n){var r="function"==typeof t,i={};if(r)t=l(t,n);else var s=vt.apply(K,arguments);return nn(e,function(e,n,o){if(r?!t(e,n,o):0>q(s,n,1))i[n]=e}),i},s.once=function(e){var t,s=i;return function(){return s?t:(s=n,t=e.apply(this
|
||||||
e)?e.split(""):et.call(e):Yt(e)},s.union=function(){for(var e=-1,t=[],n=Q.apply(t,arguments),r=n.length;++e<r;)0>w(t,n[e])&&t.push(n[e]);return t},s.uniq=T,s.uniqueId=function(e){var t=B++;return e?e+t:t},s.values=Yt,s.without=function(e){var t=[];if(!e)return t;for(var n=-1,r=e.length;++n<r;)0>w(arguments,e[n],1)&&t.push(e[n]);return t},s.wrap=function(e,t){return function(){var n=[e];return arguments.length&&Y.apply(n,arguments),t.apply(this,n)}},s.zip=function(e){if(!e)return[];for(var t=-1,n=
|
,arguments),e=r,t)}},s.pairs=function(e){var t=[];return rn(e,function(e,n){t.push([n,e])}),t},s.partial=function(e){return f(e,Et.call(arguments,1))},s.pick=function(e,t,n){var r={};if("function"!=typeof t)for(var i=0,s=vt.apply(K,arguments),o=s.length;++i<o;){var u=s[i];u in e&&(r[u]=e[u])}else t=l(t,n),nn(e,function(e,n,i){t(e,n,i)&&(r[n]=e)});return r},s.pluck=P,s.random=function(e,t){return e==r&&t==r&&(t=1),e=+e||0,t==r&&(t=e,e=0),e+mt(Ot()*((+t||0)-e+1))},s.range=function(e,t,n){e=+e||0,n=+
|
||||||
E(It(arguments,"length")),r=Array(n);++t<n;)r[t]=It(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=_t,s.any=Ut,s.collect=Ft,s.detect=Pt,s.each=Ht,s.foldl=qt,s.foldr=g,s.head=y,s.include=Mt,s.inject=qt,s.methods=Jt,s.select=Dt,s.tail=S,s.take=y,s.unique=T,o.prototype=s.prototype,L(s),o.prototype.chain=function(){return this._chain=n,this},o.prototype.value=function(){return this._wrapped},Ht("pop push reverse shift sort splice unshift"
|
n||1,t==r&&(t=e,e=0);for(var i=-1,t=Lt(0,dt((t-e)/n)),s=Array(t);++i<t;)s[i]=e,e+=n;return s},s.reduce=H,s.reduceRight=B,s.reject=function(e,t,n){return t=l(t,n),O(e,function(e,n,r){return!t(e,n,r)})},s.rest=R,s.result=function(e,t){var n=e?e[t]:r;return x(n)?e[t]():n},s.shuffle=function(e){var t=-1,n=Array(e?e.length:0);return hn(e,function(e){var r=mt(Ot()*(++t+1));n[t]=n[r],n[r]=e}),n},s.size=function(e){var t=e?e.length:0;return"number"==typeof t?t:cn(e).length},s.some=j,s.sortBy=function(e,t
|
||||||
.split(" "),function(e){var t=P[e];o.prototype[e]=function(){var e=this._wrapped;return t.apply(e,arguments),e.length===0&&delete e[0],this._chain&&(e=new o(e),e._chain=n),e}}),Ht(["concat","join","slice"],function(e){var t=P[e];o.prototype[e]=function(){var e=t.apply(this._wrapped,arguments);return this._chain&&(e=new o(e),e._chain=n),e}}),typeof define=="function"&&typeof define.amd=="object"&&define.amd?(e._=s,define(function(){return s})):D?"object"==typeof module&&module&&module.s==D?(module
|
,n){var r=[],t=l(t,n);hn(e,function(e,n,i){r.push({a:t(e,n,i),b:n,c:e})}),e=r.length;for(r.sort(a);e--;)r[e]=r[e].c;return r},s.sortedIndex=U,s.tap=function(e,t){return t(e),e},s.template=function(e,t,n){e||(e=""),n||(n={});var r,i,o=s.templateSettings,u=0,a=n.interpolate||o.interpolate||lt,f="__p += '",l=n.variable||o.variable,c=l;e.replace(RegExp((n.escape||o.escape||lt).source+"|"+a.source+"|"+(a===ft?at:lt).source+"|"+(n.evaluate||o.evaluate||lt).source+"|$","g"),function(t,n,i,s,o,a){i||(i=s
|
||||||
.s=s)._=s:D._=s:e._=s})(this);
|
),f+=e.slice(u,a).replace(ht,h),f+=n?"'+__e("+n+")+'":o?"';"+o+";__p+='":i?"'+((__t=("+i+"))==null?'':__t)+'":"",r||(r=o||et.test(n||i)),u=a+t.length}),f+="';",c||(l="obj",r?f="with("+l+"){"+f+"}":(n=RegExp("(\\(\\s*)"+l+"\\."+l+"\\b","g"),f=f.replace(ot,"$&"+l+".").replace(n,"$1__d"))),f=(r?f.replace(nt,""):f).replace(rt,"$1").replace(it,"$1;"),f="function("+l+"){"+(c?"":l+"||("+l+"={});")+"var __t,__p='',__e=_.escape"+(r?",__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}":
|
||||||
|
(c?"":",__d="+l+"."+l+"||"+l)+";")+f+"return __p}";try{i=Function("_","return "+f)(s)}catch(p){throw p.source=f,p}return t?i(t):(i.source=f,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?(clearTimeout(u),a=r,s=e.apply(o,i)):u||(u=setTimeout(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){return e&&"number"==typeof
|
||||||
|
e.length?(Wt?N(e):"string"==typeof e)?e.split(""):Et.call(e):k(e)},s.unescape=function(e){return e==r?"":(e+"").replace(tt,v)},s.union=function(){return z(vt.apply(K,arguments))},s.uniq=z,s.uniqueId=function(e){var t=G++;return e?e+t:t},s.values=k,s.where=function(e,t){var n=[];return nn(t,function(e,t){n.push(t)}),O(e,function(e){for(var r=n.length;r--;){var i=e[n[r]]===t[n[r]];if(!i)break}return!!i})},s.without=function(e){for(var t=-1,n=e?e.length:0,r=o(arguments,1,20),i=[];++t<n;){var s=e[t];
|
||||||
|
r(s)||i.push(s)}return i},s.wrap=function(e,t){return function(){var n=[e];return bt.apply(n,arguments),t.apply(this,n)}},s.zip=function(e){for(var t=-1,n=e?D(P(arguments,"length")):0,r=Array(n);++t<n;)r[t]=P(arguments,t);return r},s.all=A,s.any=j,s.collect=_,s.detect=M,s.drop=R,s.each=hn,s.foldl=H,s.foldr=B,s.head=F,s.include=L,s.inject=H,s.methods=w,s.select=O,s.tail=R,s.take=F,s.unique=z,V(s),s.prototype.chain=function(){return this.__chain__=n,this},s.prototype.value=function(){return this.__wrapped__
|
||||||
|
},hn("pop push reverse shift sort splice unshift".split(" "),function(e){var t=K[e];s.prototype[e]=function(){var e=this.__wrapped__;return t.apply(e,arguments),Rt&&e.length===0&&delete e[0],this.__chain__&&(e=new s(e),e.__chain__=n),e}}),hn(["concat","join","slice"],function(e){var t=K[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})):$?"object"==typeof module&&module&&module.exports==$?(module.exports=s)._=s:$._=s:e._=s})(this);
|
||||||
31
lodash.underscore.min.js
vendored
Normal file
31
lodash.underscore.min.js
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/*!
|
||||||
|
Lo-Dash 0.9.2 lodash.com/license
|
||||||
|
Underscore.js 1.4.2 underscorejs.org/LICENSE
|
||||||
|
*/
|
||||||
|
;(function(e,t){function n(e){if(e&&e.__wrapped__)return e;if(!(this instanceof n))return new n(e);this.__wrapped__=e}function r(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 i(e,t,n){function r(){var i=arguments,s=t;return n.length&&(i=i.length?n.concat(Z.call(i)):n),this instanceof r?(a.prototype=e.prototype,s=new a,i=e.apply(s,i),v(i)?i:s):e.apply(s,i)}return r}function s(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:H}function o(e){return"\\"+yt[e]}function u(e){return Et[e]}function a(){}function f(e){return St[e]}function l(e){var t=[];return wt(e,function(e,n){t.push(n)}),t}function c(e){var t=[];return bt(e,function(e,n){d(e)&&t.push(n)}),t.sort()}function h(e){var t={};return wt(e,function(e,n){t[e]=n}),t}function p(e,t,n,r){if(e===t)return 0!==e||1/e==1/t;if(null==e||null==t)return e===t;var i=et.call(e);if(i!=et.call(t))return!1;switch(i){case ft:case lt
|
||||||
|
:return+e==+t;case ct:return e!=+e?t!=+t:0==e?1/e==1/t:e==+t;case pt:case dt:return e==t+""}var s=i==at;if(!s){if(e.__wrapped__||t.__wrapped__)return p(e.__wrapped__||e,t.__wrapped__||t);if(i!=ht)return!1;var i=e.constructor,o=t.constructor;if(i!=o&&(!d(i)||!(i instanceof i&&d(o)&&o instanceof o)))return!1}n||(n=[]),r||(r=[]);for(i=n.length;i--;)if(n[i]==e)return r[i]==t;i=!0,o=0,n.push(e),r.push(t);if(s){o=e.length;if(i=o==t.length)for(;o--&&(i=p(e[o],t[o],n,r)););return i}for(var u in e)if(G.call
|
||||||
|
(e,u)&&(o++,!G.call(t,u)||!p(e[u],t[u],n,r)))return!1;for(u in t)if(G.call(t,u)&&!(o--))return!1;return!0}function d(e){return"function"==typeof e}function v(e){return e?gt[typeof e]:!1}function m(e){return et.call(e)==dt}function g(e){var t=[];return wt(e,function(e){t.push(e)}),t}function y(e,t){return"number"==typeof (e?e.length:0)?-1<O(e,t):k(e,function(e){return e===t})}function b(e,t,n){var r=!0,t=s(t,n);if(Nt(e))for(var n=-1,i=e.length;++n<i&&(r=!!t(e[n],n,e)););else kt(e,function(e,n,i){return!
|
||||||
|
(r=!!t(e,n,i))&&R});return r}function w(e,t,n){var r=[],t=s(t,n);return kt(e,function(e,n,i){t(e,n,i)&&r.push(e)}),r}function E(e,t,n){var r,t=s(t,n);return kt(e,function(e,n,i){if(t(e,n,i))return r=e,!1}),r}function S(e,t,n){var r=-1,i=e?e.length:0,o=Array("number"==typeof i?i:0),t=s(t,n);if(Nt(e))for(;++r<i;)o[r]=t(e[r],r,e);else kt(e,function(e,n,i){o[++r]=t(e,n,i)});return o}function x(e,t,n){var r=-Infinity,i=-1,o=e?e.length:0,u=r;if(t||!Nt(e))t=s(t,n),kt(e,function(e,n,i){n=t(e,n,i),n>r&&(r=
|
||||||
|
n,u=e)});else for(;++i<o;)e[i]>u&&(u=e[i]);return u}function T(e,t){var n=[];return kt(e,function(e){n.push(e[t])}),n}function N(e,t,n,r){var i=3>arguments.length,t=s(t,r);return kt(e,function(e,r,s){n=i?(i=!1,e):t(n,e,r,s)}),n}function C(e,t,n,r){var i=e?e.length:0,s=3>arguments.length;if("number"!=typeof i)var o=Ct(e),i=o.length;return kt(e,function(u,a,f){a=o?o[--i]:--i,n=s?(s=!1,e[a]):t.call(r,n,e[a],a,f)}),n}function k(e,t,n){var r,t=s(t,n);if(Nt(e))for(var n=-1,i=e.length;++n<i&&!(r=t(e[n],
|
||||||
|
n,e)););else kt(e,function(e,n,i){return(r=t(e,n,i))&&R});return!!r}function L(e,t,n){if(e)return null==t||n?e[0]:Z.call(e,0,t)}function A(e,t){for(var n=-1,r=e?e.length:0,i=[];++n<r;){var s=e[n];Nt(s)?Y.apply(i,t?s:A(s)):i.push(s)}return i}function O(e,t,n){var r=-1,i=e?e.length:0;if("number"==typeof n)r=(0>n?st(0,i+n):n||0)-1;else if(n)return r=_(e,t),e[r]===t?r:-1;for(;++r<i;)if(e[r]===t)return r;return-1}function M(e,t,n){return e?Z.call(e,null==t||n?1:t):[]}function _(e,t,n,r){for(var i=0,o=
|
||||||
|
e?e.length:i,n=n?s(n,r):H,t=n(t);i<o;)r=i+o>>>1,n(e[r])<t?i=r+1:o=r;return i}function D(e,t,n,r){var i=-1,o=e?e.length:0,u=[],a=u;n&&(a=[],n=s(n,r));for(;++i<o;){var r=e[i],f=n?n(r,i,e):r;if(t?!i||a[a.length-1]!==f:0>O(a,f))n&&a.push(f),u.push(r)}return u}function P(e,t){return mt||tt&&2<arguments.length?tt.call.apply(tt,arguments):i(e,t,Z.call(arguments,2))}function H(e){return e}function B(e){kt(c(e),function(t){var r=n[t]=e[t];n.prototype[t]=function(){var e=[this.__wrapped__];return Y.apply(e
|
||||||
|
,arguments),e=r.apply(n,e),this.__chain__&&(e=new n(e),e.__chain__=!0),e}})}var j="object"==typeof exports&&exports,F="object"==typeof global&&global;F.global===F&&(e=F);var I=[],F=new function(){},q=0,R=F,U=e._,z=/&(?:amp|lt|gt|quot|#x27);/g,W=RegExp("^"+(F.valueOf+"").replace(/[.*+?^=!:${}()|[\]\/\\]/g,"\\$&").replace(/valueOf|for [^\]]+/g,".+?")+"$"),X=/($^)/,V=/[&<>"']/g,$=/['\n\r\t\u2028\u2029\\]/g,J=Math.ceil,K=I.concat,Q=Math.floor,G=F.hasOwnProperty,Y=I.push,Z=I.slice,et=F.toString,tt=W.test
|
||||||
|
(tt=Z.bind)&&tt,nt=W.test(nt=Array.isArray)&&nt,rt=e.isFinite,it=W.test(it=Object.keys)&&it,st=Math.max,ot=Math.min,ut=Math.random,at="[object Array]",ft="[object Boolean]",lt="[object Date]",ct="[object Number]",ht="[object Object]",pt="[object RegExp]",dt="[object String]",vt=(vt={0:1,length:1},I.splice.call(vt,0,1),vt[0]),mt=tt&&/\n|Opera/.test(tt+et.call(e.opera)),gt={"boolean":!1,"function":!0,object:!0,number:!1,string:!1,"undefined":!1},yt={"\\":"\\","'":"'","\n":"n","\r":"r"," ":"t","\u2028"
|
||||||
|
:"u2028","\u2029":"u2029"};n.templateSettings={escape:/<%-([\s\S]+?)%>/g,evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,variable:""},n.isArguments=function(e){return"[object Arguments]"==et.call(e)},n.isArguments(arguments)||(n.isArguments=function(e){return e?G.call(e,"callee"):!1});var bt=function(e,t){var n,r;if(!e)return e;t=s(t);for(n in e)if(r=e[n],t(r,n,e)===R)break;return e},wt=function(e,t){var n,r;if(!e)return e;t=s(t);for(n in e)if(G.call(e,n)&&(r=e[n],t(r,n,e)===R))break;return e
|
||||||
|
},Et={"&":"&","<":"<",">":">",'"':""","'":"'"},St=h(Et),xt=function(e){var t,n,r=e;if(!e)return e;for(var i=1,s=arguments.length;i<s;i++)if(r=arguments[i])for(t in r)n=r[t],null==e[t]&&(e[t]=n);return e},Tt=function(e){var t,n,r=e;if(!e)return e;for(var i=1,s=arguments.length;i<s;i++)if(r=arguments[i])for(t in r)n=r[t],e[t]=n;return e},Nt=nt||function(e){return et.call(e)==at};d(/x/)&&(d=function(e){return"[object Function]"==et.call(e)});var Ct=it?function(e){return v(e)?it(e
|
||||||
|
):[]}:l,kt=function(e,t,n){var r;if(!e)return e;var t=s(t,n),i=e.length,n=-1;if("number"==typeof i){for(;++n<i;)if(r=e[n],t(r,n,e)===R)return e}else for(n in e)if(G.call(e,n)&&(r=e[n],t(r,n,e)===R))return e};n.VERSION="0.9.2",n.after=function(e,t){return 1>e?t():function(){if(1>--e)return t.apply(this,arguments)}},n.bind=P,n.bindAll=function(e){for(var t=arguments,n=1<t.length?0:(t=c(e),-1),r=t.length;++n<r;){var i=t[n];e[i]=P(e[i],e)}return e},n.chain=function(e){return e=new n(e),e.__chain__=!0
|
||||||
|
,e},n.clone=function(e){return e&>[typeof e]?Nt(e)?Z.call(e):Tt({},e):e},n.compact=function(e){for(var t=-1,n=e?e.length:0,r=[];++t<n;){var i=e[t];i&&r.push(i)}return r},n.compose=function(){var e=arguments;return function(){for(var t=arguments,n=e.length;n--;)t=[e[n].apply(this,t)];return t[0]}},n.contains=y,n.countBy=function(e,t,n){var r={},t=s(t,n);return kt(e,function(e,n,i){n=t(e,n,i),G.call(r,n)?r[n]++:r[n]=1}),r},n.debounce=function(e,t,n){function r(){u=null,n||(s=e.apply(o,i))}var i,s
|
||||||
|
,o,u;return function(){var a=n&&!u;return i=arguments,o=this,clearTimeout(u),u=setTimeout(r,t),a&&(s=e.apply(o,i)),s}},n.defaults=xt,n.defer=function(e){var n=Z.call(arguments,1);return setTimeout(function(){e.apply(t,n)},1)},n.delay=function(e,n){var r=Z.call(arguments,2);return setTimeout(function(){e.apply(t,r)},n)},n.difference=function(e){for(var t=-1,n=e.length,r=K.apply(I,arguments),i=[];++t<n;){var s=e[t];0>O(r,s,n)&&i.push(s)}return i},n.escape=function(e){return null==e?"":(e+"").replace
|
||||||
|
(V,u)},n.every=b,n.extend=Tt,n.filter=w,n.find=E,n.first=L,n.flatten=A,n.forEach=kt,n.functions=c,n.groupBy=function(e,t,n){var r={},t=s(t,n);return kt(e,function(e,n,i){n=t(e,n,i),(G.call(r,n)?r[n]:r[n]=[]).push(e)}),r},n.has=function(e,t){return e?G.call(e,t):!1},n.identity=H,n.indexOf=O,n.initial=function(e,t,n){return e?Z.call(e,0,-(null==t||n?1:t)):[]},n.intersection=function(e){var t=arguments,n=t.length,r=[];return kt(e,function(e){if(0>O(r,e)){for(var i=n;--i;)if(0>O(t[i],e))return;r.push
|
||||||
|
(e)}}),r},n.invert=h,n.invoke=function(e,t){var n=Z.call(arguments,2),r="function"==typeof t,i=[];return kt(e,function(e){i.push((r?t:e[t]).apply(e,n))}),i},n.isArray=Nt,n.isBoolean=function(e){return!0===e||!1===e||et.call(e)==ft},n.isDate=function(e){return et.call(e)==lt},n.isElement=function(e){return e?1===e.nodeType:!1},n.isEmpty=function(e){if(!e)return!0;if(Nt(e)||m(e))return!e.length;for(var t in e)if(G.call(e,t))return!1;return!0},n.isEqual=p,n.isFinite=function(e){return rt(e)&&et.call
|
||||||
|
(e)==ct},n.isFunction=d,n.isNaN=function(e){return et.call(e)==ct&&e!=+e},n.isNull=function(e){return null===e},n.isNumber=function(e){return et.call(e)==ct},n.isObject=v,n.isRegExp=function(e){return et.call(e)==pt},n.isString=m,n.isUndefined=function(e){return e===t},n.keys=Ct,n.last=function(e,t,n){if(e){var r=e.length;return null==t||n?e[r-1]:Z.call(e,-t||r)}},n.lastIndexOf=function(e,t,n){var r=e?e.length:0;for("number"==typeof n&&(r=(0>n?st(0,r+n):ot(n,r-1))+1);r--;)if(e[r]===t)return r;return-1
|
||||||
|
},n.map=S,n.max=x,n.memoize=function(e,t){var n={};return function(){var r=t?t.apply(this,arguments):arguments[0];return G.call(n,r)?n[r]:n[r]=e.apply(this,arguments)}},n.min=function(e,t,n){var r=Infinity,i=-1,o=e?e.length:0,u=r;if(t||!Nt(e))t=s(t,n),kt(e,function(e,n,i){n=t(e,n,i),n<r&&(r=n,u=e)});else for(;++i<o;)e[i]<u&&(u=e[i]);return u},n.mixin=B,n.noConflict=function(){return e._=U,this},n.object=function(e,t){for(var n=-1,r=e?e.length:0,i={};++n<r;){var s=e[n];t?i[s]=t[n]:i[s[0]]=s[1]}return i
|
||||||
|
},n.omit=function(e){var t=K.apply(I,arguments),n={};return bt(e,function(e,r){0>O(t,r,1)&&(n[r]=e)}),n},n.once=function(e){var t,n=!1;return function(){return n?t:(n=!0,t=e.apply(this,arguments),e=null,t)}},n.pairs=function(e){var t=[];return wt(e,function(e,n){t.push([n,e])}),t},n.pick=function(e){for(var t=0,n=K.apply(I,arguments),r=n.length,i={};++t<r;){var s=n[t];s in e&&(i[s]=e[s])}return i},n.pluck=T,n.random=function(e,t){return null==e&&null==t&&(t=1),e=+e||0,null==t&&(t=e,e=0),e+Q(ut()*
|
||||||
|
((+t||0)-e+1))},n.range=function(e,t,n){e=+e||0,n=+n||1,null==t&&(t=e,e=0);for(var r=-1,t=st(0,J((t-e)/n)),i=Array(t);++r<t;)i[r]=e,e+=n;return i},n.reduce=N,n.reduceRight=C,n.reject=function(e,t,n){return t=s(t,n),w(e,function(e,n,r){return!t(e,n,r)})},n.rest=M,n.result=function(e,t){var n=e?e[t]:null;return d(n)?e[t]():n},n.shuffle=function(e){var t=-1,n=Array(e?e.length:0);return kt(e,function(e){var r=Q(ut()*(++t+1));n[t]=n[r],n[r]=e}),n},n.size=function(e){var t=e?e.length:0;return"number"==typeof
|
||||||
|
t?t:Ct(e).length},n.some=k,n.sortBy=function(e,t,n){var i=[],t=s(t,n);kt(e,function(e,n,r){i.push({a:t(e,n,r),b:n,c:e})}),e=i.length;for(i.sort(r);e--;)i[e]=i[e].c;return i},n.sortedIndex=_,n.tap=function(e,t){return t(e),e},n.template=function(e,t,r){e||(e="");var r=xt({},r,n.templateSettings),i=0,s="__p += '",u=r.variable;e.replace(RegExp((r.escape||X).source+"|"+(r.interpolate||X).source+"|"+(r.evaluate||X).source+"|$","g"),function(t,n,r,u,a){s+=e.slice(i,a).replace($,o),s+=n?"'+_['escape']("+
|
||||||
|
n+")+'":u?"';"+u+";__p+='":r?"'+((__t=("+r+"))==null?'':__t)+'":"",i=a+t.length}),s+="';",u||(u="obj",s="with("+u+"||{}){"+s+"}"),s="function("+u+"){var __t,__p='',__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}"+s+"return __p}";try{var a=Function("_","return "+s)(n)}catch(f){throw f.source=s,f}return t?a(t):(a.source=s,a)},n.throttle=function(e,t){function n(){u=new Date,o=null,i=e.apply(s,r)}var r,i,s,o,u=0;return function(){var a=new Date,f=t-(a-u);return r=arguments,s=this
|
||||||
|
,0>=f?(clearTimeout(o),u=a,i=e.apply(s,r)):o||(o=setTimeout(n,f)),i}},n.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},n.toArray=function(e){return e&&"number"==typeof e.length?"string"==typeof e?e.split(""):Z.call(e):g(e)},n.unescape=function(e){return null==e?"":(e+"").replace(z,f)},n.union=function(){return D(K.apply(I,arguments))},n.uniq=D,n.uniqueId=function(e){var t=q++;return e?e+t:t},n.values=g,n.where=function(e,t){var n=[];return bt(t,function(e,
|
||||||
|
t){n.push(t)}),w(e,function(e){for(var r=n.length;r--;){var i=e[n[r]]===t[n[r]];if(!i)break}return!!i})},n.without=function(e){for(var t=-1,n=e.length,r=[];++t<n;){var i=e[t];0>O(arguments,i,1)&&r.push(i)}return r},n.wrap=function(e,t){return function(){var n=[e];return Y.apply(n,arguments),t.apply(this,n)}},n.zip=function(e){for(var t=-1,n=e?x(T(arguments,"length")):0,r=Array(n);++t<n;)r[t]=T(arguments,t);return r},n.all=b,n.any=k,n.collect=S,n.detect=E,n.drop=M,n.each=kt,n.foldl=N,n.foldr=C,n.head=
|
||||||
|
L,n.include=y,n.inject=N,n.methods=c,n.select=w,n.tail=M,n.take=L,n.unique=D,B(n),n.prototype.chain=function(){return this.__chain__=!0,this},n.prototype.value=function(){return this.__wrapped__},kt("pop push reverse shift sort splice unshift".split(" "),function(e){var t=I[e];n.prototype[e]=function(){var e=this.__wrapped__;return t.apply(e,arguments),vt&&e.length===0&&delete e[0],this.__chain__&&(e=new n(e),e.__chain__=!0),e}}),kt(["concat","join","slice"],function(e){var t=I[e];n.prototype[e]=
|
||||||
|
function(){var e=t.apply(this.__wrapped__,arguments);return this.__chain__&&(e=new n(e),e.__chain__=!0),e}}),j?"object"==typeof module&&module&&module.exports==j?(module.exports=n)._=n:j._=n:e._=n})(this);
|
||||||
14
package.json
14
package.json
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "lodash",
|
"name": "lodash",
|
||||||
"version": "0.4.1",
|
"version": "0.9.2",
|
||||||
"description": "A drop-in replacement for Underscore.js that delivers performance improvements, bug fixes, and additional features.",
|
"description": "A drop-in replacement for Underscore.js delivering performance, bug fixes, and additional features.",
|
||||||
"homepage": "http://lodash.com",
|
"homepage": "http://lodash.com",
|
||||||
"main": "lodash",
|
"main": "./lodash",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"browser",
|
"browser",
|
||||||
"client",
|
"client",
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
],
|
],
|
||||||
"author": {
|
"author": {
|
||||||
"name": "John-David Dalton",
|
"name": "John-David Dalton",
|
||||||
"email": "john@fusejs.com",
|
"email": "john.david.dalton@gmail.com",
|
||||||
"web": "http://allyoucanleet.com/"
|
"web": "http://allyoucanleet.com/"
|
||||||
},
|
},
|
||||||
"bugs": {
|
"bugs": {
|
||||||
@@ -42,8 +42,12 @@
|
|||||||
"node",
|
"node",
|
||||||
"rhino"
|
"rhino"
|
||||||
],
|
],
|
||||||
|
"jam": {
|
||||||
|
"main": "./lodash.js"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "node build",
|
"build": "node build",
|
||||||
"test": "node test/test"
|
"test": "node test/test && node test/test-build",
|
||||||
|
"install": "node build/post-install"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,36 +13,69 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
left: -9999em;
|
left: -9999em;
|
||||||
}
|
}
|
||||||
|
#FirebugUI {
|
||||||
|
top: 2em;
|
||||||
|
}
|
||||||
|
#perf-toolbar {
|
||||||
|
background-color: #EEE;
|
||||||
|
color: #5E740B;
|
||||||
|
font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
|
||||||
|
font-size: small;
|
||||||
|
padding: 0.5em 0 0.5em 2em;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script src="../lodash.min.js"></script>
|
<div id="perf-toolbar"></div>
|
||||||
|
<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-ui.js"></script>
|
||||||
|
<script>
|
||||||
|
document.write('<script src="../' + ui.buildPath + '"><\/script>');
|
||||||
|
</script>
|
||||||
<script>
|
<script>
|
||||||
var lodash = _.noConflict();
|
var lodash = _.noConflict();
|
||||||
</script>
|
</script>
|
||||||
<script src="../vendor/underscore/underscore.js"></script>
|
<script>
|
||||||
<script src="../vendor/benchmark.js/benchmark.js"></script>
|
document.write('<script src="../' + ui.otherPath + '"><\/script>');
|
||||||
<script src="../vendor/firebug-lite/src/firebug-lite-debug.js"></script>
|
</script>
|
||||||
|
<script src="perf.js"></script>
|
||||||
<script>
|
<script>
|
||||||
(function() {
|
(function() {
|
||||||
if (!/[?&]nojava=true(?:&|$)/.test(location.search)) {
|
var measured,
|
||||||
// using innerHTML avoids an alert in some versions of IE6
|
perfNow,
|
||||||
var div = document.createElement('div');
|
begin = new Date;
|
||||||
div.innerHTML = '<applet code=nano archive="../vendor/benchmark.js/nano.jar">';
|
|
||||||
document.body.insertBefore(div.lastChild, document.body.firstChild);
|
function init() {
|
||||||
|
var fbUI = document.getElementById('FirebugUI'),
|
||||||
|
fbDoc = fbUI && (fbDoc = fbUI.contentWindow || fbUI.contentDocument).document || fbDoc,
|
||||||
|
fbCommandLine = fbDoc && fbDoc.getElementById('fbCommandLine');
|
||||||
|
|
||||||
|
if (!fbCommandLine) {
|
||||||
|
return setTimeout(init, 15);
|
||||||
|
}
|
||||||
|
fbUI.style.height = (
|
||||||
|
Math.max(document.documentElement.clientHeight, document.body.clientHeight) -
|
||||||
|
document.getElementById('perf-toolbar').clientHeight
|
||||||
|
) + 'px';
|
||||||
|
|
||||||
|
fbDoc.body.style.height = fbDoc.documentElement.style.height = '100%';
|
||||||
|
setTimeout(run, 15);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
}());
|
}());
|
||||||
|
|
||||||
window.onload = function() {
|
|
||||||
var fbUI = document.getElementById('FirebugUI'),
|
|
||||||
fbDoc = (fbDoc = fbUI.contentWindow || fbUI.contentDocument).document || fbDoc,
|
|
||||||
sibling = document.getElementsByTagName('script')[0],
|
|
||||||
script = document.createElement('script');
|
|
||||||
|
|
||||||
fbUI.style.height = fbDoc.body.style.height = fbDoc.documentElement.style.height = '100%';
|
|
||||||
script.src = 'perf.js?t=' + (+new Date);
|
|
||||||
sibling.parentNode.insertBefore(script, sibling);
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
130
perf/perf-ui.js
Normal file
130
perf/perf-ui.js
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
;(function(window) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/** The Lo-Dash build to load */
|
||||||
|
var build = (/build=([^&]+)/.exec(location.search) || [])[1];
|
||||||
|
|
||||||
|
/** The other library to load */
|
||||||
|
var other = (/other=([^&]+)/.exec(location.search) || [])[1];
|
||||||
|
|
||||||
|
/** The `ui` object */
|
||||||
|
var ui = {};
|
||||||
|
|
||||||
|
/*--------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers an event listener on an element.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Element} element The element.
|
||||||
|
* @param {String} eventName The name of the event.
|
||||||
|
* @param {Function} handler The event handler.
|
||||||
|
* @returns {Element} The element.
|
||||||
|
*/
|
||||||
|
function addListener(element, eventName, handler) {
|
||||||
|
if (typeof element.addEventListener != 'undefined') {
|
||||||
|
element.addEventListener(eventName, handler, false);
|
||||||
|
} else if (typeof element.attachEvent != 'undefined') {
|
||||||
|
element.attachEvent('on' + eventName, handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*--------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
// expose Lo-Dash build file path
|
||||||
|
ui.buildPath = (function() {
|
||||||
|
switch (build) {
|
||||||
|
case 'lodash-dev': return 'lodash.js';
|
||||||
|
case 'lodash-underscore': return 'lodash.underscore.min.js';
|
||||||
|
case 'lodash-custom': return 'lodash.custom.min.js';
|
||||||
|
}
|
||||||
|
return 'lodash.min.js';
|
||||||
|
}());
|
||||||
|
|
||||||
|
// expose other library file path
|
||||||
|
ui.otherPath = (function() {
|
||||||
|
switch (other) {
|
||||||
|
case 'lodash-dev': return 'lodash.js';
|
||||||
|
case 'lodash-prod': return 'lodash.min.js';
|
||||||
|
case 'lodash-underscore': return 'lodash.underscore.min.js';
|
||||||
|
case 'lodash-custom': return 'lodash.custom.min.js';
|
||||||
|
case 'underscore-dev': return 'vendor/underscore/underscore.js';
|
||||||
|
}
|
||||||
|
return 'vendor/underscore/underscore-min.js';
|
||||||
|
}());
|
||||||
|
|
||||||
|
// initialize controls
|
||||||
|
addListener(window, 'load', function() {
|
||||||
|
function eventHandler(event) {
|
||||||
|
var search = location.search.replace(/^\?|&?(?:build|other)=[^&]*&?/g, '');
|
||||||
|
if (event.stopPropagation) {
|
||||||
|
event.stopPropagation();
|
||||||
|
} else {
|
||||||
|
event.cancelBubble = true;
|
||||||
|
}
|
||||||
|
location.href =
|
||||||
|
location.href.split('?')[0] + '?' +
|
||||||
|
(search ? search + '&' : '') +
|
||||||
|
'build=' + buildList[buildList.selectedIndex].value + '&' +
|
||||||
|
'other=' + otherList[otherList.selectedIndex].value;
|
||||||
|
}
|
||||||
|
|
||||||
|
var span1 = document.createElement('span');
|
||||||
|
span1.style.cssText = 'float:right';
|
||||||
|
span1.innerHTML =
|
||||||
|
'<label for="perf-build">Build: </label>' +
|
||||||
|
'<select id="perf-build">' +
|
||||||
|
'<option value="lodash-dev">Lo-Dash</option>' +
|
||||||
|
'<option value="lodash-prod">Lo-Dash (minified)</option>' +
|
||||||
|
'<option value="lodash-underscore">Lo-Dash (underscore)</option>' +
|
||||||
|
'<option value="lodash-custom">Lo-Dash (custom)</option>' +
|
||||||
|
'</select>';
|
||||||
|
|
||||||
|
var span2 = document.createElement('span');
|
||||||
|
span2.style.cssText = 'float:right';
|
||||||
|
span2.innerHTML =
|
||||||
|
'<label for="perf-other">Other Library: </label>' +
|
||||||
|
'<select id="perf-other">' +
|
||||||
|
'<option value="underscore-dev">Underscore</option>' +
|
||||||
|
'<option value="underscore-prod">Underscore (minified)</option>' +
|
||||||
|
'<option value="lodash-dev">Lo-Dash</option>' +
|
||||||
|
'<option value="lodash-prod">Lo-Dash (minified)</option>' +
|
||||||
|
'<option value="lodash-underscore">Lo-Dash (underscore)</option>' +
|
||||||
|
'<option value="lodash-custom">Lo-Dash (custom)</option>' +
|
||||||
|
'</select>';
|
||||||
|
|
||||||
|
var buildList = span1.lastChild,
|
||||||
|
otherList = span2.lastChild,
|
||||||
|
toolbar = document.getElementById('perf-toolbar');
|
||||||
|
|
||||||
|
toolbar.appendChild(span2);
|
||||||
|
toolbar.appendChild(span1);
|
||||||
|
|
||||||
|
buildList.selectedIndex = (function() {
|
||||||
|
switch (build) {
|
||||||
|
case 'lodash-dev': return 0;
|
||||||
|
case 'lodash-underscore': return 2;
|
||||||
|
case 'lodash-custom': return 3;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}());
|
||||||
|
|
||||||
|
otherList.selectedIndex = (function() {
|
||||||
|
switch (other) {
|
||||||
|
case 'underscore-dev': return 0;
|
||||||
|
case 'lodash-dev': return 2;
|
||||||
|
case 'lodash-prod': return 3;
|
||||||
|
case 'lodash-underscore': return 4;
|
||||||
|
case 'lodash-custom': return 5;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}());
|
||||||
|
|
||||||
|
addListener(buildList, 'change', eventHandler);
|
||||||
|
addListener(otherList, 'change', eventHandler);
|
||||||
|
});
|
||||||
|
|
||||||
|
// expose `ui`
|
||||||
|
window.ui = ui;
|
||||||
|
|
||||||
|
}(this));
|
||||||
1884
perf/perf.js
1884
perf/perf.js
File diff suppressed because it is too large
Load Diff
@@ -3,14 +3,11 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>Backbone Test Suite</title>
|
<title>Backbone Test Suite</title>
|
||||||
<link rel="stylesheet" href="../vendor/backbone/test/vendor/qunit.css">
|
<link rel="stylesheet" href="../vendor/qunit/qunit/qunit.css">
|
||||||
<style>
|
<style>
|
||||||
body > #qunit-header {
|
body > #qunit-header {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
#jslitmus {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -21,15 +18,22 @@
|
|||||||
<h1>Test</h1>
|
<h1>Test</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="../vendor/backbone/test/vendor/json2.js"></script>
|
<script src="../vendor/json3/lib/json3.js"></script>
|
||||||
<script src="../vendor/backbone/test/vendor/jquery-1.7.1.js"></script>
|
<script src="../vendor/jquery/jquery.js"></script>
|
||||||
<script src="../vendor/backbone/test/vendor/qunit.js"></script>
|
<script src="../vendor/platform.js/platform.js"></script>
|
||||||
<script src="../vendor/backbone/test/vendor/jslitmus.js"></script>
|
<script>
|
||||||
|
// avoid syntax errors for `QUnit.throws` in older Firefoxes
|
||||||
|
document.write(platform.name == 'Firefox' && /^1\b/.test(platform.version)
|
||||||
|
? '<script src="../vendor/qunit/qunit/qunit-1.8.0.js"><\/script>'
|
||||||
|
: '<script src="../vendor/qunit/qunit/qunit.js"><\/script>'
|
||||||
|
);
|
||||||
|
</script>
|
||||||
<script src="test-ui.js"></script>
|
<script src="test-ui.js"></script>
|
||||||
<script>
|
<script>
|
||||||
document.write('<script src="../' + QUnit.config.lodashFilename + '.js"><\/script>');
|
document.write('<script src="../' + ui.buildPath + '"><\/script>');
|
||||||
</script>
|
</script>
|
||||||
<script src="../vendor/backbone/backbone.js"></script>
|
<script src="../vendor/backbone/backbone.js"></script>
|
||||||
|
<script src="../vendor/backbone/test/environment.js"></script>
|
||||||
<script src="../vendor/backbone/test/noconflict.js"></script>
|
<script src="../vendor/backbone/test/noconflict.js"></script>
|
||||||
<script src="../vendor/backbone/test/events.js"></script>
|
<script src="../vendor/backbone/test/events.js"></script>
|
||||||
<script src="../vendor/backbone/test/model.js"></script>
|
<script src="../vendor/backbone/test/model.js"></script>
|
||||||
|
|||||||
@@ -7,7 +7,14 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="qunit"></div>
|
<div id="qunit"></div>
|
||||||
<script src="../vendor/qunit/qunit/qunit.js"></script>
|
<script src="../vendor/platform.js/platform.js"></script>
|
||||||
|
<script>
|
||||||
|
// avoid syntax errors for `QUnit.throws` in older Firefoxes
|
||||||
|
document.write(platform.name == 'Firefox' && /^1\b/.test(platform.version)
|
||||||
|
? '<script src="../vendor/qunit/qunit/qunit-1.8.0.js"><\/script>'
|
||||||
|
: '<script src="../vendor/qunit/qunit/qunit.js"><\/script>'
|
||||||
|
);
|
||||||
|
</script>
|
||||||
<script src="test-ui.js"></script>
|
<script src="test-ui.js"></script>
|
||||||
<script>
|
<script>
|
||||||
// set a bad shim
|
// set a bad shim
|
||||||
@@ -15,7 +22,7 @@
|
|||||||
Object.keys = function() { return []; };
|
Object.keys = function() { return []; };
|
||||||
|
|
||||||
// load Lo-Dash and expose it to the bad `Object.keys` shim
|
// load Lo-Dash and expose it to the bad `Object.keys` shim
|
||||||
document.write('<script src="../' + QUnit.config.lodashFilename + '.js"><\/script>');
|
document.write('<script src="../' + ui.buildPath + '"><\/script>');
|
||||||
</script>
|
</script>
|
||||||
<script>
|
<script>
|
||||||
// store Lo-Dash to test for bad shim detection
|
// store Lo-Dash to test for bad shim detection
|
||||||
@@ -25,11 +32,8 @@
|
|||||||
Object.keys = Object._keys;
|
Object.keys = Object._keys;
|
||||||
delete Object._keys;
|
delete Object._keys;
|
||||||
|
|
||||||
// set to test `_.noConflict`
|
|
||||||
_ = 1;
|
|
||||||
|
|
||||||
// load Lo-Dash again to overwrite the existing `_` value
|
// load Lo-Dash again to overwrite the existing `_` value
|
||||||
document.write('<script src="../' + QUnit.config.lodashFilename + '.js"><\/script>');
|
document.write('<script src="../' + ui.buildPath + '"><\/script>');
|
||||||
|
|
||||||
// load test.js if not using require.js
|
// load test.js if not using require.js
|
||||||
document.write(QUnit.urlParams.norequire
|
document.write(QUnit.urlParams.norequire
|
||||||
@@ -40,25 +44,53 @@
|
|||||||
<script>
|
<script>
|
||||||
// load Lo-Dash as a module
|
// load Lo-Dash as a module
|
||||||
var lodashModule,
|
var lodashModule,
|
||||||
|
shimmedModule,
|
||||||
underscoreModule;
|
underscoreModule;
|
||||||
|
|
||||||
window.require && require({
|
window.require && require(
|
||||||
'baseUrl': '../vendor/requirejs/',
|
(function() {
|
||||||
'urlArgs': 't=' + (+new Date),
|
var modulePath = ui.buildPath.replace(/\.js$/, '');
|
||||||
'paths': {
|
return {
|
||||||
'lodash': '../../' + QUnit.config.lodashFilename,
|
'baseUrl': '../vendor/requirejs/',
|
||||||
'underscore': './../../' + QUnit.config.lodashFilename
|
'urlArgs': 't=' + (+new Date),
|
||||||
|
'paths': {
|
||||||
|
'lodash': '../../' + modulePath,
|
||||||
|
'shimmed': './../../' + modulePath,
|
||||||
|
'underscore': '../underscore/../../' + modulePath
|
||||||
|
},
|
||||||
|
'shim': {
|
||||||
|
'shimmed': {
|
||||||
|
'exports': '_'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}()),
|
||||||
|
['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';
|
||||||
}
|
}
|
||||||
},
|
|
||||||
['lodash', 'underscore'], function(lodash, underscore) {
|
|
||||||
lodashModule = lodash.noConflict();
|
|
||||||
lodashModule.moduleName = 'lodash';
|
|
||||||
|
|
||||||
underscoreModule = underscore.noConflict();
|
|
||||||
underscoreModule.moduleName = 'underscore';
|
|
||||||
|
|
||||||
require(['test.js']);
|
require(['test.js']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// set a more readable browser name
|
||||||
|
window.onload = function() {
|
||||||
|
var timeoutId = setInterval(function() {
|
||||||
|
var ua = document.getElementById('qunit-userAgent');
|
||||||
|
if (ua) {
|
||||||
|
ua.innerHTML = platform;
|
||||||
|
clearInterval(timeoutId);
|
||||||
|
}
|
||||||
|
}, 15);
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
cd "$(dirname "$0")"
|
cd "$(dirname "$0")"
|
||||||
for cmd in rhino ringo narwhal node; do
|
for cmd in rhino ringo narwhal node; do
|
||||||
echo ""
|
echo ""
|
||||||
echo "Testing in $cmd..."
|
echo "Testing in $cmd..."
|
||||||
$cmd test.js
|
$cmd test.js
|
||||||
done
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Testing build..."
|
||||||
|
node test-build.js
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Testing in a browser..."
|
echo "Testing in a browser..."
|
||||||
open index.html
|
open index.html
|
||||||
|
|||||||
3
test/template/a.jst
Normal file
3
test/template/a.jst
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<ul>
|
||||||
|
<% _.forEach(people, function(name) { %><li><%= name %></li><% }); %>
|
||||||
|
</ul>
|
||||||
1
test/template/b.jst
Normal file
1
test/template/b.jst
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<% print("Hello " + epithet); %>.
|
||||||
1
test/template/c.tpl
Normal file
1
test/template/c.tpl
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Hello {{ name }}!
|
||||||
1048
test/test-build.js
Normal file
1048
test/test-build.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -10,16 +10,20 @@
|
|||||||
/** A flag to determine if RequireJS should be loaded */
|
/** A flag to determine if RequireJS should be loaded */
|
||||||
var norequire = /[?&]norequire=true(?:&|$)/.test(location.search);
|
var norequire = /[?&]norequire=true(?:&|$)/.test(location.search);
|
||||||
|
|
||||||
|
/** The `ui` object */
|
||||||
|
var ui = {};
|
||||||
|
|
||||||
/*--------------------------------------------------------------------------*/
|
/*--------------------------------------------------------------------------*/
|
||||||
|
|
||||||
// assign `QUnit.config` properties
|
// expose Lo-Dash build file path
|
||||||
QUnit.config.lodashFilename = (function() {
|
ui.buildPath = (function() {
|
||||||
switch (build) {
|
switch (build) {
|
||||||
case 'prod': return 'lodash.min';
|
case 'lodash-prod': return 'lodash.min.js';
|
||||||
case 'custom': return 'lodash.custom.min';
|
case 'lodash-underscore': return 'lodash.underscore.min.js';
|
||||||
case 'custom-debug': return 'lodash.custom';
|
case 'lodash-custom': return 'lodash.custom.min.js';
|
||||||
|
case 'lodash-custom-debug': return 'lodash.custom.js';
|
||||||
}
|
}
|
||||||
return 'lodash';
|
return 'lodash.js';
|
||||||
}());
|
}());
|
||||||
|
|
||||||
// assign `QUnit.urlParams` properties
|
// assign `QUnit.urlParams` properties
|
||||||
@@ -28,7 +32,7 @@
|
|||||||
'norequire': norequire
|
'norequire': norequire
|
||||||
});
|
});
|
||||||
|
|
||||||
// initialize the build dropdown
|
// initialize controls
|
||||||
addEvent(window, 'load', function() {
|
addEvent(window, 'load', function() {
|
||||||
function eventHandler(event) {
|
function eventHandler(event) {
|
||||||
var search = location.search.replace(/^\?|&?(?:build|norequire)=[^&]*&?/g, '');
|
var search = location.search.replace(/^\?|&?(?:build|norequire)=[^&]*&?/g, '');
|
||||||
@@ -39,52 +43,60 @@
|
|||||||
}
|
}
|
||||||
location.href =
|
location.href =
|
||||||
location.href.split('?')[0] + '?' +
|
location.href.split('?')[0] + '?' +
|
||||||
(search ? search + '&' : '') + 'build=' +
|
(search ? search + '&' : '') +
|
||||||
dropdown[dropdown.selectedIndex].value +
|
'build=' + buildList[buildList.selectedIndex].value +
|
||||||
(checkbox.checked ? '&norequire=true' : '');
|
(checkbox.checked ? '&norequire=true' : '');
|
||||||
}
|
}
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
var header = document.getElementById('qunit-header');
|
var toolbar = document.getElementById('qunit-testrunner-toolbar');
|
||||||
if (header) {
|
if (toolbar) {
|
||||||
header.appendChild(label1);
|
toolbar.appendChild(span1);
|
||||||
header.appendChild(label2);
|
toolbar.appendChild(span2);
|
||||||
|
|
||||||
dropdown.selectedIndex = (function() {
|
buildList.selectedIndex = (function() {
|
||||||
switch (build) {
|
switch (build) {
|
||||||
case 'prod': return 1;
|
case 'lodash-prod': return 1;
|
||||||
case 'custom': return 2;
|
case 'lodash-underscore': return 2;
|
||||||
case 'custom-debug': return 3;
|
case 'lodash-custom': return 3;
|
||||||
|
case 'lodash-custom-debug': return 4;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}());
|
}());
|
||||||
|
|
||||||
checkbox.checked = norequire;
|
checkbox.checked = norequire;
|
||||||
addEvent(checkbox, 'click', eventHandler);
|
addEvent(checkbox, 'click', eventHandler);
|
||||||
addEvent(dropdown, 'change', eventHandler);
|
addEvent(buildList, 'change', eventHandler);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
setTimeout(init, 15);
|
setTimeout(init, 15);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var label1 = document.createElement('label');
|
var span1 = document.createElement('span');
|
||||||
label1.innerHTML =
|
span1.innerHTML =
|
||||||
'<input name="norequire" type="checkbox">norequire</label>';
|
'<input id="qunit-norequire" type="checkbox">' +
|
||||||
|
'<label for="qunit-norequire">No RequireJS</label>';
|
||||||
|
|
||||||
var label2 = document.createElement('label');
|
var span2 = document.createElement('span');
|
||||||
label2.innerHTML =
|
span2.style.cssText = 'float:right';
|
||||||
'<select name="build">' +
|
span2.innerHTML =
|
||||||
'<option value="dev">developement</option>' +
|
'<label for="qunit-build">Build: </label>' +
|
||||||
'<option value="prod">production</option>' +
|
'<select id="qunit-build">' +
|
||||||
'<option value="custom">custom</option>' +
|
'<option value="lodash-dev">Developement</option>' +
|
||||||
'<option value="custom-debug">custom (debug)</option>' +
|
'<option value="lodash-prod">Production</option>' +
|
||||||
'</select>build';
|
'<option value="lodash-underscore">Underscore</option>' +
|
||||||
|
'<option value="lodash-custom">Custom</option>' +
|
||||||
|
'<option value="lodash-custom-debug">Custom (debug)</option>' +
|
||||||
|
'</select>';
|
||||||
|
|
||||||
var checkbox = label1.firstChild,
|
var checkbox = span1.firstChild,
|
||||||
dropdown = label2.firstChild;
|
buildList = span2.lastChild;
|
||||||
|
|
||||||
init();
|
init();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// expose `ui`
|
||||||
|
window.ui = ui;
|
||||||
|
|
||||||
}(this));
|
}(this));
|
||||||
|
|||||||
1373
test/test.js
1373
test/test.js
File diff suppressed because it is too large
Load Diff
@@ -3,9 +3,9 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>Underscore Test Suite</title>
|
<title>Underscore Test Suite</title>
|
||||||
<link rel="stylesheet" href="../vendor/underscore/test/vendor/qunit.css">
|
<link rel="stylesheet" href="../vendor/qunit/qunit/qunit.css">
|
||||||
<style>
|
<style>
|
||||||
#jslitmus, iframe {
|
iframe, img {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -17,14 +17,21 @@
|
|||||||
<div id="id1"></div>
|
<div id="id1"></div>
|
||||||
<div id="id2"></div>
|
<div id="id2"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<img id="chart_image" src="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==">
|
||||||
</div>
|
</div>
|
||||||
<script src="../vendor/backbone/test/vendor/json2.js"></script>
|
<script src="../vendor/json3/lib/json3.js"></script>
|
||||||
<script src="../vendor/underscore/test/vendor/jquery.js"></script>
|
<script src="../vendor/jquery/jquery.js"></script>
|
||||||
<script src="../vendor/underscore/test/vendor/qunit.js"></script>
|
<script src="../vendor/platform.js/platform.js"></script>
|
||||||
<script src="../vendor/underscore/test/vendor/jslitmus.js"></script>
|
<script>
|
||||||
|
// avoid syntax errors for `QUnit.throws` in older Firefoxes
|
||||||
|
document.write(platform.name == 'Firefox' && /^1\b/.test(platform.version)
|
||||||
|
? '<script src="../vendor/qunit/qunit/qunit-1.8.0.js"><\/script>'
|
||||||
|
: '<script src="../vendor/qunit/qunit/qunit.js"><\/script>'
|
||||||
|
);
|
||||||
|
</script>
|
||||||
<script src="test-ui.js"></script>
|
<script src="test-ui.js"></script>
|
||||||
<script>
|
<script>
|
||||||
document.write('<script src="../' + QUnit.config.lodashFilename + '.js"><\/script>');
|
document.write('<script src="../' + ui.buildPath + '"><\/script>');
|
||||||
</script>
|
</script>
|
||||||
<script src="../vendor/underscore/test/collections.js"></script>
|
<script src="../vendor/underscore/test/collections.js"></script>
|
||||||
<script src="../vendor/underscore/test/arrays.js"></script>
|
<script src="../vendor/underscore/test/arrays.js"></script>
|
||||||
|
|||||||
495
vendor/backbone/backbone.js
vendored
495
vendor/backbone/backbone.js
vendored
@@ -10,7 +10,7 @@
|
|||||||
// Initial Setup
|
// Initial Setup
|
||||||
// -------------
|
// -------------
|
||||||
|
|
||||||
// Save a reference to the global object (`window` in the browser, `global`
|
// Save a reference to the global object (`window` in the browser, `exports`
|
||||||
// on the server).
|
// on the server).
|
||||||
var root = this;
|
var root = this;
|
||||||
|
|
||||||
@@ -18,8 +18,11 @@
|
|||||||
// restored later on, if `noConflict` is used.
|
// restored later on, if `noConflict` is used.
|
||||||
var previousBackbone = root.Backbone;
|
var previousBackbone = root.Backbone;
|
||||||
|
|
||||||
// Create a local reference to splice.
|
// Create a local reference to array methods.
|
||||||
var splice = Array.prototype.splice;
|
var ArrayProto = Array.prototype;
|
||||||
|
var push = ArrayProto.push;
|
||||||
|
var slice = ArrayProto.slice;
|
||||||
|
var splice = ArrayProto.splice;
|
||||||
|
|
||||||
// The top-level namespace. All public Backbone classes and modules will
|
// The top-level namespace. All public Backbone classes and modules will
|
||||||
// be attached to this. Exported for both CommonJS and the browser.
|
// be attached to this. Exported for both CommonJS and the browser.
|
||||||
@@ -59,7 +62,7 @@
|
|||||||
Backbone.emulateJSON = false;
|
Backbone.emulateJSON = false;
|
||||||
|
|
||||||
// Backbone.Events
|
// Backbone.Events
|
||||||
// -----------------
|
// ---------------
|
||||||
|
|
||||||
// Regular expression used to split event strings
|
// Regular expression used to split event strings
|
||||||
var eventSplitter = /\s+/;
|
var eventSplitter = /\s+/;
|
||||||
@@ -134,6 +137,9 @@
|
|||||||
|
|
||||||
rest = [];
|
rest = [];
|
||||||
events = events.split(eventSplitter);
|
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++) {
|
for (i = 1, length = arguments.length; i < length; i++) {
|
||||||
rest[i - 1] = arguments[i];
|
rest[i - 1] = arguments[i];
|
||||||
}
|
}
|
||||||
@@ -177,22 +183,22 @@
|
|||||||
// is automatically generated and assigned for you.
|
// is automatically generated and assigned for you.
|
||||||
var Model = Backbone.Model = function(attributes, options) {
|
var Model = Backbone.Model = function(attributes, options) {
|
||||||
var defaults;
|
var defaults;
|
||||||
attributes || (attributes = {});
|
var attrs = attributes || {};
|
||||||
if (options && options.collection) this.collection = options.collection;
|
if (options && options.collection) this.collection = options.collection;
|
||||||
if (options && options.parse) attributes = this.parse(attributes);
|
|
||||||
if (defaults = getValue(this, 'defaults')) {
|
|
||||||
attributes = _.extend({}, defaults, attributes);
|
|
||||||
}
|
|
||||||
this.attributes = {};
|
this.attributes = {};
|
||||||
this._escapedAttributes = {};
|
this._escapedAttributes = {};
|
||||||
this.cid = _.uniqueId('c');
|
this.cid = _.uniqueId('c');
|
||||||
this.changed = {};
|
this.changed = {};
|
||||||
this._silent = {};
|
this._changes = {};
|
||||||
this._pending = {};
|
this._pending = {};
|
||||||
this.set(attributes, {silent: true});
|
if (options && options.parse) attrs = this.parse(attrs);
|
||||||
|
if (defaults = _.result(this, 'defaults')) {
|
||||||
|
attrs = _.extend({}, defaults, attrs);
|
||||||
|
}
|
||||||
|
this.set(attrs, {silent: true});
|
||||||
// Reset change tracking.
|
// Reset change tracking.
|
||||||
this.changed = {};
|
this.changed = {};
|
||||||
this._silent = {};
|
this._changes = {};
|
||||||
this._pending = {};
|
this._pending = {};
|
||||||
this._previousAttributes = _.clone(this.attributes);
|
this._previousAttributes = _.clone(this.attributes);
|
||||||
this.initialize.apply(this, arguments);
|
this.initialize.apply(this, arguments);
|
||||||
@@ -204,14 +210,18 @@
|
|||||||
// A hash of attributes whose current and previous value differ.
|
// A hash of attributes whose current and previous value differ.
|
||||||
changed: null,
|
changed: null,
|
||||||
|
|
||||||
// A hash of attributes that have silently changed since the last time
|
// A hash of attributes that have changed since the last time `change`
|
||||||
// `change` was called. Will become pending attributes on the next call.
|
// was called.
|
||||||
_silent: null,
|
_changes: null,
|
||||||
|
|
||||||
// A hash of attributes that have changed since the last `'change'` event
|
// A hash of attributes that have changed since the last `change` event
|
||||||
// began.
|
// began.
|
||||||
_pending: null,
|
_pending: null,
|
||||||
|
|
||||||
|
// A hash of attributes with the current model state to determine if
|
||||||
|
// a `change` should be recorded within a nested `change` block.
|
||||||
|
_changing : null,
|
||||||
|
|
||||||
// The default name for the JSON `id` attribute is `"id"`. MongoDB and
|
// The default name for the JSON `id` attribute is `"id"`. MongoDB and
|
||||||
// CouchDB users may want to set this to `"_id"`.
|
// CouchDB users may want to set this to `"_id"`.
|
||||||
idAttribute: 'id',
|
idAttribute: 'id',
|
||||||
@@ -251,23 +261,23 @@
|
|||||||
|
|
||||||
// Set a hash of model attributes on the object, firing `"change"` unless
|
// Set a hash of model attributes on the object, firing `"change"` unless
|
||||||
// you choose to silence it.
|
// you choose to silence it.
|
||||||
set: function(key, value, options) {
|
set: function(key, val, options) {
|
||||||
var attrs, attr, val;
|
var attr, attrs;
|
||||||
|
if (key == null) return this;
|
||||||
|
|
||||||
// Handle both `"key", value` and `{key: value}` -style arguments.
|
// Handle both `"key", value` and `{key: value}` -style arguments.
|
||||||
if (_.isObject(key) || key == null) {
|
if (_.isObject(key)) {
|
||||||
attrs = key;
|
attrs = key;
|
||||||
options = value;
|
options = val;
|
||||||
} else {
|
} else {
|
||||||
attrs = {};
|
(attrs = {})[key] = val;
|
||||||
attrs[key] = value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract attributes and options.
|
// Extract attributes and options.
|
||||||
options || (options = {});
|
var silent = options && options.silent;
|
||||||
if (!attrs) return this;
|
var unset = options && options.unset;
|
||||||
if (attrs instanceof Model) attrs = attrs.attributes;
|
if (attrs instanceof Model) attrs = attrs.attributes;
|
||||||
if (options.unset) for (attr in attrs) attrs[attr] = void 0;
|
if (unset) for (attr in attrs) attrs[attr] = void 0;
|
||||||
|
|
||||||
// Run validation.
|
// Run validation.
|
||||||
if (!this._validate(attrs, options)) return false;
|
if (!this._validate(attrs, options)) return false;
|
||||||
@@ -275,7 +285,7 @@
|
|||||||
// Check for changes of `id`.
|
// Check for changes of `id`.
|
||||||
if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
|
if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
|
||||||
|
|
||||||
var changes = options.changes = {};
|
var changing = this._changing;
|
||||||
var now = this.attributes;
|
var now = this.attributes;
|
||||||
var escaped = this._escapedAttributes;
|
var escaped = this._escapedAttributes;
|
||||||
var prev = this._previousAttributes || {};
|
var prev = this._previousAttributes || {};
|
||||||
@@ -285,27 +295,30 @@
|
|||||||
val = attrs[attr];
|
val = attrs[attr];
|
||||||
|
|
||||||
// If the new and current value differ, record the change.
|
// If the new and current value differ, record the change.
|
||||||
if (!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr))) {
|
if (!_.isEqual(now[attr], val) || (unset && _.has(now, attr))) {
|
||||||
delete escaped[attr];
|
delete escaped[attr];
|
||||||
(options.silent ? this._silent : changes)[attr] = true;
|
this._changes[attr] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update or delete the current value.
|
// Update or delete the current value.
|
||||||
options.unset ? delete now[attr] : now[attr] = val;
|
unset ? delete now[attr] : now[attr] = val;
|
||||||
|
|
||||||
// If the new and previous value differ, record the change. If not,
|
// If the new and previous value differ, record the change. If not,
|
||||||
// then remove changes for this attribute.
|
// 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;
|
this.changed[attr] = val;
|
||||||
if (!options.silent) this._pending[attr] = true;
|
if (!silent) this._pending[attr] = true;
|
||||||
} else {
|
} else {
|
||||||
delete this.changed[attr];
|
delete this.changed[attr];
|
||||||
delete this._pending[attr];
|
delete this._pending[attr];
|
||||||
|
if (!changing) delete this._changes[attr];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (changing && _.isEqual(now[attr], changing[attr])) delete this._changes[attr];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fire the `"change"` events.
|
// Fire the `"change"` events.
|
||||||
if (!options.silent) this.change(options);
|
if (!silent) this.change(options);
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -333,25 +346,22 @@
|
|||||||
options.success = function(resp, status, xhr) {
|
options.success = function(resp, status, xhr) {
|
||||||
if (!model.set(model.parse(resp, xhr), options)) return false;
|
if (!model.set(model.parse(resp, xhr), options)) return false;
|
||||||
if (success) success(model, resp, options);
|
if (success) success(model, resp, options);
|
||||||
model.trigger('sync', model, resp, options);
|
|
||||||
};
|
};
|
||||||
options.error = Backbone.wrapError(options.error, model, options);
|
|
||||||
return this.sync('read', this, options);
|
return this.sync('read', this, options);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Set a hash of model attributes, and sync the model to the server.
|
// Set a hash of model attributes, and sync the model to the server.
|
||||||
// If the server returns an attributes hash that differs, the model's
|
// If the server returns an attributes hash that differs, the model's
|
||||||
// state will be `set` again.
|
// state will be `set` again.
|
||||||
save: function(key, value, options) {
|
save: function(key, val, options) {
|
||||||
var attrs, current, done;
|
var attrs, current, done;
|
||||||
|
|
||||||
// Handle both `("key", value)` and `({key: value})` -style calls.
|
// Handle both `"key", value` and `{key: value}` -style arguments.
|
||||||
if (_.isObject(key) || key == null) {
|
if (key == null || _.isObject(key)) {
|
||||||
attrs = key;
|
attrs = key;
|
||||||
options = value;
|
options = val;
|
||||||
} else {
|
} else if (key != null) {
|
||||||
attrs = {};
|
(attrs = {})[key] = val;
|
||||||
attrs[key] = value;
|
|
||||||
}
|
}
|
||||||
options = options ? _.clone(options) : {};
|
options = options ? _.clone(options) : {};
|
||||||
|
|
||||||
@@ -368,7 +378,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Do not persist invalid models.
|
// Do not persist invalid models.
|
||||||
if (!attrs && !this.isValid()) return false;
|
if (!attrs && !this._validate(null, options)) return false;
|
||||||
|
|
||||||
// After a successful server-side save, the client is (optionally)
|
// After a successful server-side save, the client is (optionally)
|
||||||
// updated with the server-side state.
|
// updated with the server-side state.
|
||||||
@@ -380,11 +390,9 @@
|
|||||||
if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
|
if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
|
||||||
if (!model.set(serverAttrs, options)) return false;
|
if (!model.set(serverAttrs, options)) return false;
|
||||||
if (success) success(model, resp, options);
|
if (success) success(model, resp, options);
|
||||||
model.trigger('sync', model, resp, options);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Finish configuring and sending the Ajax request.
|
// Finish configuring and sending the Ajax request.
|
||||||
options.error = Backbone.wrapError(options.error, model, options);
|
|
||||||
var xhr = this.sync(this.isNew() ? 'create' : 'update', this, options);
|
var xhr = this.sync(this.isNew() ? 'create' : 'update', this, options);
|
||||||
|
|
||||||
// When using `wait`, reset attributes to original values unless
|
// When using `wait`, reset attributes to original values unless
|
||||||
@@ -412,7 +420,6 @@
|
|||||||
options.success = function(resp) {
|
options.success = function(resp) {
|
||||||
if (options.wait || model.isNew()) destroy();
|
if (options.wait || model.isNew()) destroy();
|
||||||
if (success) success(model, resp, options);
|
if (success) success(model, resp, options);
|
||||||
if (!model.isNew()) model.trigger('sync', model, resp, options);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.isNew()) {
|
if (this.isNew()) {
|
||||||
@@ -420,7 +427,6 @@
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
options.error = Backbone.wrapError(options.error, model, options);
|
|
||||||
var xhr = this.sync('delete', this, options);
|
var xhr = this.sync('delete', this, options);
|
||||||
if (!options.wait) destroy();
|
if (!options.wait) destroy();
|
||||||
return xhr;
|
return xhr;
|
||||||
@@ -430,9 +436,9 @@
|
|||||||
// using Backbone's restful methods, override this to change the endpoint
|
// using Backbone's restful methods, override this to change the endpoint
|
||||||
// that will be called.
|
// that will be called.
|
||||||
url: function() {
|
url: function() {
|
||||||
var base = getValue(this, 'urlRoot') || getValue(this.collection, 'url') || urlError();
|
var base = _.result(this, 'urlRoot') || _.result(this.collection, 'url') || urlError();
|
||||||
if (this.isNew()) return base;
|
if (this.isNew()) return base;
|
||||||
return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
|
return base + (base.charAt(base.length - 1) === '/' ? '' : '/') + encodeURIComponent(this.id);
|
||||||
},
|
},
|
||||||
|
|
||||||
// **parse** converts a response into the hash of attributes to be `set` on
|
// **parse** converts a response into the hash of attributes to be `set` on
|
||||||
@@ -455,18 +461,25 @@
|
|||||||
// a `"change:attribute"` event for each changed attribute.
|
// a `"change:attribute"` event for each changed attribute.
|
||||||
// Calling this will cause all objects observing the model to update.
|
// Calling this will cause all objects observing the model to update.
|
||||||
change: function(options) {
|
change: function(options) {
|
||||||
options || (options = {});
|
|
||||||
var changing = this._changing;
|
var changing = this._changing;
|
||||||
this._changing = true;
|
var current = this._changing = {};
|
||||||
|
|
||||||
// Silent changes become pending changes.
|
// Silent changes become pending changes.
|
||||||
for (var attr in this._silent) this._pending[attr] = true;
|
for (var attr in this._changes) this._pending[attr] = true;
|
||||||
|
|
||||||
// Silent changes are triggered.
|
// Trigger 'change:attr' for any new or silent changes.
|
||||||
var changes = _.extend({}, options.changes, this._silent);
|
var changes = this._changes;
|
||||||
this._silent = {};
|
this._changes = {};
|
||||||
|
|
||||||
|
// Set the correct state for this._changing values
|
||||||
|
var triggers = [];
|
||||||
for (var attr in changes) {
|
for (var attr in changes) {
|
||||||
this.trigger('change:' + attr, this, this.get(attr), options);
|
current[attr] = this.get(attr);
|
||||||
|
triggers.push(attr);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i=0, l=triggers.length; i < l; i++) {
|
||||||
|
this.trigger('change:' + triggers[i], this, current[triggers[i]], options);
|
||||||
}
|
}
|
||||||
if (changing) return this;
|
if (changing) return this;
|
||||||
|
|
||||||
@@ -476,13 +489,13 @@
|
|||||||
this.trigger('change', this, options);
|
this.trigger('change', this, options);
|
||||||
// Pending and silent changes still remain.
|
// Pending and silent changes still remain.
|
||||||
for (var attr in this.changed) {
|
for (var attr in this.changed) {
|
||||||
if (this._pending[attr] || this._silent[attr]) continue;
|
if (this._pending[attr] || this._changes[attr]) continue;
|
||||||
delete this.changed[attr];
|
delete this.changed[attr];
|
||||||
}
|
}
|
||||||
this._previousAttributes = _.clone(this.attributes);
|
this._previousAttributes = _.clone(this.attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._changing = false;
|
this._changing = null;
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -524,23 +537,20 @@
|
|||||||
|
|
||||||
// Check if the model is currently in a valid state. It's only possible to
|
// Check if the model is currently in a valid state. It's only possible to
|
||||||
// get into an *invalid* state if you're using silent changes.
|
// get into an *invalid* state if you're using silent changes.
|
||||||
isValid: function() {
|
isValid: function(options) {
|
||||||
return !this.validate || !this.validate(this.attributes);
|
return !this.validate || !this.validate(this.attributes, options);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Run validation against the next complete set of model attributes,
|
// Run validation against the next complete set of model attributes,
|
||||||
// returning `true` if all is well. If a specific `error` callback has
|
// returning `true` if all is well. If a specific `error` callback has
|
||||||
// been passed, call that instead of firing the general `"error"` event.
|
// been passed, call that instead of firing the general `"error"` event.
|
||||||
_validate: function(attrs, options) {
|
_validate: function(attrs, options) {
|
||||||
if (options.silent || !this.validate) return true;
|
if (options && options.silent || !this.validate) return true;
|
||||||
attrs = _.extend({}, this.attributes, attrs);
|
attrs = _.extend({}, this.attributes, attrs);
|
||||||
var error = this.validate(attrs, options);
|
var error = this.validate(attrs, options);
|
||||||
if (!error) return true;
|
if (!error) return true;
|
||||||
if (options && options.error) {
|
if (options && options.error) options.error(this, error, options);
|
||||||
options.error(this, error, options);
|
this.trigger('error', this, error, options);
|
||||||
} else {
|
|
||||||
this.trigger('error', this, error, options);
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -555,10 +565,13 @@
|
|||||||
var Collection = Backbone.Collection = function(models, options) {
|
var Collection = Backbone.Collection = function(models, options) {
|
||||||
options || (options = {});
|
options || (options = {});
|
||||||
if (options.model) this.model = options.model;
|
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._reset();
|
||||||
this.initialize.apply(this, arguments);
|
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.
|
// Define the Collection's inheritable methods.
|
||||||
@@ -586,61 +599,51 @@
|
|||||||
// Add a model, or list of models to the set. Pass **silent** to avoid
|
// Add a model, or list of models to the set. Pass **silent** to avoid
|
||||||
// firing the `add` event for every new model.
|
// firing the `add` event for every new model.
|
||||||
add: function(models, options) {
|
add: function(models, options) {
|
||||||
var i, index, length, model, cid, id, cids = {}, ids = {}, dups = [];
|
var i, args, length, model, existing;
|
||||||
options || (options = {});
|
var at = options && options.at;
|
||||||
models = _.isArray(models) ? models.slice() : [models];
|
models = _.isArray(models) ? models.slice() : [models];
|
||||||
|
|
||||||
// Begin by turning bare objects into model references, and preventing
|
// 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++) {
|
for (i = 0, length = models.length; i < length; i++) {
|
||||||
if (!(model = models[i] = this._prepareModel(models[i], options))) {
|
if (models[i] = this._prepareModel(models[i], options)) continue;
|
||||||
throw new Error("Can't add an invalid model to a collection");
|
throw new Error("Can't add an invalid model to a collection");
|
||||||
}
|
}
|
||||||
cid = model.cid;
|
|
||||||
id = model.id;
|
for (i = models.length - 1; i >= 0; i--) {
|
||||||
if (cids[cid] || this._byCid[cid] || ((id != null) && (ids[id] || this._byId[id]))) {
|
model = models[i];
|
||||||
dups.push(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;
|
continue;
|
||||||
}
|
}
|
||||||
cids[cid] = ids[id] = model;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove duplicates.
|
// Listen to added models' events, and index models for lookup by
|
||||||
i = dups.length;
|
// `id` and by `cid`.
|
||||||
while (i--) {
|
model.on('all', this._onModelEvent, this);
|
||||||
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);
|
|
||||||
this._byCid[model.cid] = model;
|
this._byCid[model.cid] = model;
|
||||||
if (model.id != null) this._byId[model.id] = model;
|
if (model.id != null) this._byId[model.id] = model;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert models into the collection, re-sorting if needed, and triggering
|
// Update `length` and splice in new models.
|
||||||
// `add` events unless silenced.
|
this.length += models.length;
|
||||||
this.length += length;
|
args = [at != null ? at : this.models.length, 0];
|
||||||
index = options.at != null ? options.at : this.models.length;
|
push.apply(args, models);
|
||||||
splice.apply(this.models, [index, 0].concat(models));
|
splice.apply(this.models, args);
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort the collection if appropriate.
|
// 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;
|
if (options && options.silent) return this;
|
||||||
for (i = 0, length = this.models.length; i < length; i++) {
|
|
||||||
if (!cids[(model = this.models[i]).cid]) continue;
|
// Trigger `add` events.
|
||||||
options.index = i;
|
while (model = models.shift()) {
|
||||||
model.trigger('add', model, this, options);
|
model.trigger('add', model, this, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -734,35 +737,35 @@
|
|||||||
// normal circumstances, as the set will maintain sort order as each item
|
// normal circumstances, as the set will maintain sort order as each item
|
||||||
// is added.
|
// is added.
|
||||||
sort: function(options) {
|
sort: function(options) {
|
||||||
options || (options = {});
|
if (!this.comparator) {
|
||||||
if (!this.comparator) throw new Error('Cannot sort a set without a 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 (!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;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Pluck an attribute from each model in the collection.
|
// Pluck an attribute from each model in the collection.
|
||||||
pluck: function(attr) {
|
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,
|
// 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
|
// you can reset the entire set with a new list of models, without firing
|
||||||
// any `add` or `remove` events. Fires `reset` when finished.
|
// any `add` or `remove` events. Fires `reset` when finished.
|
||||||
reset: function(models, options) {
|
reset: function(models, options) {
|
||||||
models || (models = []);
|
|
||||||
options || (options = {});
|
|
||||||
for (var i = 0, l = this.models.length; i < l; i++) {
|
for (var i = 0, l = this.models.length; i < l; i++) {
|
||||||
this._removeReference(this.models[i]);
|
this._removeReference(this.models[i]);
|
||||||
}
|
}
|
||||||
this._reset();
|
this._reset();
|
||||||
this.add(models, _.extend({silent: true}, options));
|
if (models) this.add(models, _.extend({silent: true}, options));
|
||||||
if (!options.silent) this.trigger('reset', this, options);
|
if (!options || !options.silent) this.trigger('reset', this, options);
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -771,15 +774,13 @@
|
|||||||
// models to the collection instead of resetting.
|
// models to the collection instead of resetting.
|
||||||
fetch: function(options) {
|
fetch: function(options) {
|
||||||
options = options ? _.clone(options) : {};
|
options = options ? _.clone(options) : {};
|
||||||
if (options.parse === undefined) options.parse = true;
|
if (options.parse === void 0) options.parse = true;
|
||||||
var collection = this;
|
var collection = this;
|
||||||
var success = options.success;
|
var success = options.success;
|
||||||
options.success = function(resp, status, xhr) {
|
options.success = function(resp, status, xhr) {
|
||||||
collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
|
collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
|
||||||
if (success) success(collection, resp, options);
|
if (success) success(collection, resp, options);
|
||||||
collection.trigger('sync', collection, resp, options);
|
|
||||||
};
|
};
|
||||||
options.error = Backbone.wrapError(options.error, collection, options);
|
|
||||||
return this.sync('read', this, options);
|
return this.sync('read', this, options);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -787,14 +788,14 @@
|
|||||||
// collection immediately, unless `wait: true` is passed, in which case we
|
// collection immediately, unless `wait: true` is passed, in which case we
|
||||||
// wait for the server to agree.
|
// wait for the server to agree.
|
||||||
create: function(model, options) {
|
create: function(model, options) {
|
||||||
var coll = this;
|
var collection = this;
|
||||||
options = options ? _.clone(options) : {};
|
options = options ? _.clone(options) : {};
|
||||||
model = this._prepareModel(model, options);
|
model = this._prepareModel(model, options);
|
||||||
if (!model) return false;
|
if (!model) return false;
|
||||||
if (!options.wait) coll.add(model, options);
|
if (!options.wait) collection.add(model, options);
|
||||||
var success = options.success;
|
var success = options.success;
|
||||||
options.success = function(model, resp, options) {
|
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);
|
if (success) success(model, resp, options);
|
||||||
};
|
};
|
||||||
model.save(null, options);
|
model.save(null, options);
|
||||||
@@ -842,9 +843,7 @@
|
|||||||
|
|
||||||
// Internal method to remove a model's ties to a collection.
|
// Internal method to remove a model's ties to a collection.
|
||||||
_removeReference: function(model) {
|
_removeReference: function(model) {
|
||||||
if (this == model.collection) {
|
if (this === model.collection) delete model.collection;
|
||||||
delete model.collection;
|
|
||||||
}
|
|
||||||
model.off('all', this._onModelEvent, this);
|
model.off('all', this._onModelEvent, this);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -853,10 +852,8 @@
|
|||||||
// events simply proxy through. "add" and "remove" events that originate
|
// events simply proxy through. "add" and "remove" events that originate
|
||||||
// in other collections are ignored.
|
// in other collections are ignored.
|
||||||
_onModelEvent: function(event, model, collection, options) {
|
_onModelEvent: function(event, model, collection, options) {
|
||||||
if ((event == 'add' || event == 'remove') && collection != this) return;
|
if ((event === 'add' || event === 'remove') && collection !== this) return;
|
||||||
if (event == 'destroy') {
|
if (event === 'destroy') this.remove(model, options);
|
||||||
this.remove(model, options);
|
|
||||||
}
|
|
||||||
if (model && event === 'change:' + model.idAttribute) {
|
if (model && event === 'change:' + model.idAttribute) {
|
||||||
delete this._byId[model.previous(model.idAttribute)];
|
delete this._byId[model.previous(model.idAttribute)];
|
||||||
if (model.id != null) this._byId[model.id] = model;
|
if (model.id != null) this._byId[model.id] = model;
|
||||||
@@ -867,21 +864,37 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Underscore methods that we want to implement on the Collection.
|
// Underscore methods that we want to implement on the Collection.
|
||||||
var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find',
|
var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl',
|
||||||
'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any',
|
'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select',
|
||||||
'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex',
|
'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
|
||||||
'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf',
|
'max', 'min', 'sortedIndex', 'toArray', 'size', 'first', 'head', 'take',
|
||||||
'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy'];
|
'initial', 'rest', 'tail', 'last', 'without', 'indexOf', 'shuffle',
|
||||||
|
'lastIndexOf', 'isEmpty'];
|
||||||
|
|
||||||
// Mix in each Underscore method as a proxy to `Collection#models`.
|
// Mix in each Underscore method as a proxy to `Collection#models`.
|
||||||
_.each(methods, function(method) {
|
_.each(methods, function(method) {
|
||||||
Collection.prototype[method] = function() {
|
Collection.prototype[method] = function() {
|
||||||
return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
|
var args = slice.call(arguments);
|
||||||
|
args.unshift(this.models);
|
||||||
|
return _[method].apply(_, args);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// Backbone.Router
|
// Backbone.Router
|
||||||
// -------------------
|
// ---------------
|
||||||
|
|
||||||
// Routers map faux-URLs to actions, and fire events when routes are
|
// Routers map faux-URLs to actions, and fire events when routes are
|
||||||
// matched. Creating a new one sets its `routes` hash, if not set statically.
|
// matched. Creating a new one sets its `routes` hash, if not set statically.
|
||||||
@@ -894,9 +907,10 @@
|
|||||||
|
|
||||||
// Cached regular expressions for matching named param parts and splatted
|
// Cached regular expressions for matching named param parts and splatted
|
||||||
// parts of route strings.
|
// parts of route strings.
|
||||||
|
var optionalParam = /\((.*?)\)/g;
|
||||||
var namedParam = /:\w+/g;
|
var namedParam = /:\w+/g;
|
||||||
var splatParam = /\*\w+/g;
|
var splatParam = /\*\w+/g;
|
||||||
var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g;
|
var escapeRegExp = /[-{}[\]+?.,\\^$|#\s]/g;
|
||||||
|
|
||||||
// Set up all inheritable **Backbone.Router** properties and methods.
|
// Set up all inheritable **Backbone.Router** properties and methods.
|
||||||
_.extend(Router.prototype, Events, {
|
_.extend(Router.prototype, Events, {
|
||||||
@@ -912,7 +926,6 @@
|
|||||||
// });
|
// });
|
||||||
//
|
//
|
||||||
route: function(route, name, callback) {
|
route: function(route, name, callback) {
|
||||||
Backbone.history || (Backbone.history = new History);
|
|
||||||
if (!_.isRegExp(route)) route = this._routeToRegExp(route);
|
if (!_.isRegExp(route)) route = this._routeToRegExp(route);
|
||||||
if (!callback) callback = this[name];
|
if (!callback) callback = this[name];
|
||||||
Backbone.history.route(route, _.bind(function(fragment) {
|
Backbone.history.route(route, _.bind(function(fragment) {
|
||||||
@@ -927,6 +940,7 @@
|
|||||||
// Simple proxy to `Backbone.history` to save a fragment into the history.
|
// Simple proxy to `Backbone.history` to save a fragment into the history.
|
||||||
navigate: function(fragment, options) {
|
navigate: function(fragment, options) {
|
||||||
Backbone.history.navigate(fragment, options);
|
Backbone.history.navigate(fragment, options);
|
||||||
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Bind all defined routes to `Backbone.history`. We have to reverse the
|
// Bind all defined routes to `Backbone.history`. We have to reverse the
|
||||||
@@ -934,12 +948,9 @@
|
|||||||
// routes can be defined at the bottom of the route map.
|
// routes can be defined at the bottom of the route map.
|
||||||
_bindRoutes: function() {
|
_bindRoutes: function() {
|
||||||
if (!this.routes) return;
|
if (!this.routes) return;
|
||||||
var routes = [];
|
var route, routes = _.keys(this.routes);
|
||||||
for (var route in this.routes) {
|
while ((route = routes.pop()) != null) {
|
||||||
routes.unshift([route, this.routes[route]]);
|
this.route(route, this.routes[route]);
|
||||||
}
|
|
||||||
for (var i = 0, l = routes.length; i < l; i++) {
|
|
||||||
this.route(routes[i][0], routes[i][1], this[routes[i][1]]);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -947,6 +958,7 @@
|
|||||||
// against the current location hash.
|
// against the current location hash.
|
||||||
_routeToRegExp: function(route) {
|
_routeToRegExp: function(route) {
|
||||||
route = route.replace(escapeRegExp, '\\$&')
|
route = route.replace(escapeRegExp, '\\$&')
|
||||||
|
.replace(optionalParam, '(?:$1)?')
|
||||||
.replace(namedParam, '([^\/]+)')
|
.replace(namedParam, '([^\/]+)')
|
||||||
.replace(splatParam, '(.*?)');
|
.replace(splatParam, '(.*?)');
|
||||||
return new RegExp('^' + route + '$');
|
return new RegExp('^' + route + '$');
|
||||||
@@ -965,15 +977,22 @@
|
|||||||
|
|
||||||
// Handles cross-browser history management, based on URL fragments. If the
|
// Handles cross-browser history management, based on URL fragments. If the
|
||||||
// browser does not support `onhashchange`, falls back to polling.
|
// browser does not support `onhashchange`, falls back to polling.
|
||||||
var History = Backbone.History = function(options) {
|
var History = Backbone.History = function() {
|
||||||
this.handlers = [];
|
this.handlers = [];
|
||||||
_.bindAll(this, 'checkUrl');
|
_.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 = /^[#\/]/;
|
var routeStripper = /^[#\/]|\s+$/;
|
||||||
|
|
||||||
|
// Cached regex for stripping leading and trailing slashes.
|
||||||
|
var rootStripper = /^\/+|\/+$/g;
|
||||||
|
|
||||||
// Cached regex for detecting MSIE.
|
// Cached regex for detecting MSIE.
|
||||||
var isExplorer = /msie [\w.]+/;
|
var isExplorer = /msie [\w.]+/;
|
||||||
@@ -1004,7 +1023,7 @@
|
|||||||
if (fragment == null) {
|
if (fragment == null) {
|
||||||
if (this._hasPushState || !this._wantsHashChange || forcePushState) {
|
if (this._hasPushState || !this._wantsHashChange || forcePushState) {
|
||||||
fragment = this.location.pathname;
|
fragment = this.location.pathname;
|
||||||
var root = this.options.root.replace(trailingSlash, '');
|
var root = this.root.replace(trailingSlash, '');
|
||||||
if (!fragment.indexOf(root)) fragment = fragment.substr(root.length);
|
if (!fragment.indexOf(root)) fragment = fragment.substr(root.length);
|
||||||
} else {
|
} else {
|
||||||
fragment = this.getHash();
|
fragment = this.getHash();
|
||||||
@@ -1022,6 +1041,7 @@
|
|||||||
// Figure out the initial configuration. Do we need an iframe?
|
// Figure out the initial configuration. Do we need an iframe?
|
||||||
// Is pushState desired ... is it available?
|
// Is pushState desired ... is it available?
|
||||||
this.options = _.extend({}, {root: '/'}, this.options, options);
|
this.options = _.extend({}, {root: '/'}, this.options, options);
|
||||||
|
this.root = this.options.root;
|
||||||
this._wantsHashChange = this.options.hashChange !== false;
|
this._wantsHashChange = this.options.hashChange !== false;
|
||||||
this._wantsPushState = !!this.options.pushState;
|
this._wantsPushState = !!this.options.pushState;
|
||||||
this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState);
|
this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState);
|
||||||
@@ -1029,6 +1049,9 @@
|
|||||||
var docMode = document.documentMode;
|
var docMode = document.documentMode;
|
||||||
var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
|
var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
|
||||||
|
|
||||||
|
// Normalize root to always include a leading and trailing slash.
|
||||||
|
this.root = ('/' + this.root + '/').replace(rootStripper, '/');
|
||||||
|
|
||||||
if (oldIE && this._wantsHashChange) {
|
if (oldIE && this._wantsHashChange) {
|
||||||
this.iframe = Backbone.$('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
|
this.iframe = Backbone.$('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
|
||||||
this.navigate(fragment);
|
this.navigate(fragment);
|
||||||
@@ -1048,13 +1071,13 @@
|
|||||||
// opened by a non-pushState browser.
|
// opened by a non-pushState browser.
|
||||||
this.fragment = fragment;
|
this.fragment = fragment;
|
||||||
var loc = this.location;
|
var loc = this.location;
|
||||||
var atRoot = (loc.pathname == this.options.root) && !loc.search;
|
var atRoot = loc.pathname.replace(/[^\/]$/, '$&/') === this.root;
|
||||||
|
|
||||||
// If we've started off with a route from a `pushState`-enabled browser,
|
// If we've started off with a route from a `pushState`-enabled browser,
|
||||||
// but we're currently in a browser that doesn't support it...
|
// but we're currently in a browser that doesn't support it...
|
||||||
if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) {
|
if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) {
|
||||||
this.fragment = this.getFragment(null, true);
|
this.fragment = this.getFragment(null, true);
|
||||||
this.location.replace(this.options.root + this.location.search + '#' + this.fragment);
|
this.location.replace(this.root + this.location.search + '#' + this.fragment);
|
||||||
// Return immediately as browser will do redirect to new url
|
// Return immediately as browser will do redirect to new url
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
@@ -1062,12 +1085,10 @@
|
|||||||
// in a browser where it could be `pushState`-based instead...
|
// in a browser where it could be `pushState`-based instead...
|
||||||
} else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
|
} else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
|
||||||
this.fragment = this.getHash().replace(routeStripper, '');
|
this.fragment = this.getHash().replace(routeStripper, '');
|
||||||
this.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment);
|
this.history.replaceState({}, document.title, this.root + this.fragment + loc.search);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.options.silent) {
|
if (!this.options.silent) return this.loadUrl();
|
||||||
return this.loadUrl();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Disable Backbone.history, perhaps temporarily. Not useful in a real app,
|
// Disable Backbone.history, perhaps temporarily. Not useful in a real app,
|
||||||
@@ -1088,10 +1109,10 @@
|
|||||||
// calls `loadUrl`, normalizing across the hidden iframe.
|
// calls `loadUrl`, normalizing across the hidden iframe.
|
||||||
checkUrl: function(e) {
|
checkUrl: function(e) {
|
||||||
var current = this.getFragment();
|
var current = this.getFragment();
|
||||||
if (current == this.fragment && this.iframe) {
|
if (current === this.fragment && this.iframe) {
|
||||||
current = this.getFragment(this.getHash(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);
|
if (this.iframe) this.navigate(current);
|
||||||
this.loadUrl() || this.loadUrl(this.getHash());
|
this.loadUrl() || this.loadUrl(this.getHash());
|
||||||
},
|
},
|
||||||
@@ -1120,10 +1141,10 @@
|
|||||||
navigate: function(fragment, options) {
|
navigate: function(fragment, options) {
|
||||||
if (!History.started) return false;
|
if (!History.started) return false;
|
||||||
if (!options || options === true) options = {trigger: options};
|
if (!options || options === true) options = {trigger: options};
|
||||||
var frag = (fragment || '').replace(routeStripper, '');
|
fragment = this.getFragment(fragment || '');
|
||||||
if (this.fragment == frag) return;
|
if (this.fragment === fragment) return;
|
||||||
this.fragment = frag;
|
this.fragment = fragment;
|
||||||
var url = (frag.indexOf(this.options.root) != 0 ? this.options.root : '') + frag;
|
var url = this.root + fragment;
|
||||||
|
|
||||||
// If pushState is available, we use it to set the fragment as a real URL.
|
// If pushState is available, we use it to set the fragment as a real URL.
|
||||||
if (this._hasPushState) {
|
if (this._hasPushState) {
|
||||||
@@ -1132,13 +1153,13 @@
|
|||||||
// If hash changes haven't been explicitly disabled, update the hash
|
// If hash changes haven't been explicitly disabled, update the hash
|
||||||
// fragment to store history.
|
// fragment to store history.
|
||||||
} else if (this._wantsHashChange) {
|
} else if (this._wantsHashChange) {
|
||||||
this._updateHash(this.location, frag, options.replace);
|
this._updateHash(this.location, fragment, options.replace);
|
||||||
if (this.iframe && (frag != this.getFragment(this.getHash(this.iframe)))) {
|
if (this.iframe && (fragment !== this.getFragment(this.getHash(this.iframe)))) {
|
||||||
// Opening and closing the iframe tricks IE7 and earlier to push a
|
// Opening and closing the iframe tricks IE7 and earlier to push a
|
||||||
// history entry on hash-tag change. When replace is true, we don't
|
// history entry on hash-tag change. When replace is true, we don't
|
||||||
// want this.
|
// want this.
|
||||||
if(!options.replace) this.iframe.document.open().close();
|
if(!options.replace) this.iframe.document.open().close();
|
||||||
this._updateHash(this.iframe.location, frag, options.replace);
|
this._updateHash(this.iframe.location, fragment, options.replace);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If you've told us that you explicitly don't want fallback hashchange-
|
// If you've told us that you explicitly don't want fallback hashchange-
|
||||||
@@ -1153,13 +1174,19 @@
|
|||||||
// a new one to the browser history.
|
// a new one to the browser history.
|
||||||
_updateHash: function(location, fragment, replace) {
|
_updateHash: function(location, fragment, replace) {
|
||||||
if (replace) {
|
if (replace) {
|
||||||
location.replace(location.href.replace(/(javascript:|#).*$/, '') + '#' + fragment);
|
var href = location.href.replace(/(javascript:|#).*$/, '');
|
||||||
|
location.replace(href + '#' + fragment);
|
||||||
} else {
|
} 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
|
// Backbone.View
|
||||||
// -------------
|
// -------------
|
||||||
|
|
||||||
@@ -1202,9 +1229,19 @@
|
|||||||
return this;
|
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) this.model.off(null, null, this);
|
||||||
|
if (this.collection && this.collection.off) this.collection.off(null, null, this);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
// Remove this view from the DOM. Note that the view isn't present in the
|
// 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.
|
// DOM by default, so calling this method may be a no-op.
|
||||||
remove: function() {
|
remove: function() {
|
||||||
|
this.dispose();
|
||||||
this.$el.remove();
|
this.$el.remove();
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
@@ -1247,7 +1284,7 @@
|
|||||||
// This only works for delegate-able events: not `focus`, `blur`, and
|
// This only works for delegate-able events: not `focus`, `blur`, and
|
||||||
// not `change`, `submit`, and `reset` in Internet Explorer.
|
// not `change`, `submit`, and `reset` in Internet Explorer.
|
||||||
delegateEvents: function(events) {
|
delegateEvents: function(events) {
|
||||||
if (!(events || (events = getValue(this, 'events')))) return;
|
if (!(events || (events = _.result(this, 'events')))) return;
|
||||||
this.undelegateEvents();
|
this.undelegateEvents();
|
||||||
for (var key in events) {
|
for (var key in events) {
|
||||||
var method = events[key];
|
var method = events[key];
|
||||||
@@ -1277,10 +1314,7 @@
|
|||||||
// attached directly to the view.
|
// attached directly to the view.
|
||||||
_configure: function(options) {
|
_configure: function(options) {
|
||||||
if (this.options) options = _.extend({}, this.options, options);
|
if (this.options) options = _.extend({}, this.options, options);
|
||||||
for (var i = 0, l = viewOptions.length; i < l; i++) {
|
_.extend(this, _.pick(options, viewOptions));
|
||||||
var attr = viewOptions[i];
|
|
||||||
if (options[attr]) this[attr] = options[attr];
|
|
||||||
}
|
|
||||||
this.options = options;
|
this.options = options;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1290,27 +1324,17 @@
|
|||||||
// an element from the `id`, `className` and `tagName` properties.
|
// an element from the `id`, `className` and `tagName` properties.
|
||||||
_ensureElement: function() {
|
_ensureElement: function() {
|
||||||
if (!this.el) {
|
if (!this.el) {
|
||||||
var attrs = _.extend({}, getValue(this, 'attributes'));
|
var attrs = _.extend({}, _.result(this, 'attributes'));
|
||||||
if (this.id) attrs.id = this.id;
|
if (this.id) attrs.id = _.result(this, 'id');
|
||||||
if (this.className) attrs['class'] = this.className;
|
if (this.className) attrs['class'] = _.result(this, 'className');
|
||||||
this.setElement(this.make(getValue(this, 'tagName'), attrs), false);
|
this.setElement(this.make(_.result(this, 'tagName'), attrs), false);
|
||||||
} else {
|
} else {
|
||||||
this.setElement(this.el, false);
|
this.setElement(_.result(this, 'el'), false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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
|
// Backbone.sync
|
||||||
// -------------
|
// -------------
|
||||||
|
|
||||||
@@ -1341,45 +1365,60 @@
|
|||||||
var type = methodMap[method];
|
var type = methodMap[method];
|
||||||
|
|
||||||
// Default options, unless specified.
|
// Default options, unless specified.
|
||||||
options || (options = {});
|
_.defaults(options || (options = {}), {
|
||||||
|
emulateHTTP: Backbone.emulateHTTP,
|
||||||
|
emulateJSON: Backbone.emulateJSON
|
||||||
|
});
|
||||||
|
|
||||||
// Default JSON-request options.
|
// Default JSON-request options.
|
||||||
var params = {type: type, dataType: 'json'};
|
var params = {type: type, dataType: 'json'};
|
||||||
|
|
||||||
// Ensure that we have a URL.
|
// Ensure that we have a URL.
|
||||||
if (!options.url) {
|
if (!options.url) {
|
||||||
params.url = getValue(model, 'url') || urlError();
|
params.url = _.result(model, 'url') || urlError();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that we have the appropriate request data.
|
// Ensure that we have the appropriate request data.
|
||||||
if (!options.data && model && (method == 'create' || method == 'update')) {
|
if (!options.data && model && (method === 'create' || method === 'update')) {
|
||||||
params.contentType = 'application/json';
|
params.contentType = 'application/json';
|
||||||
params.data = JSON.stringify(model);
|
params.data = JSON.stringify(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
// For older servers, emulate JSON by encoding the request into an HTML-form.
|
// For older servers, emulate JSON by encoding the request into an HTML-form.
|
||||||
if (Backbone.emulateJSON) {
|
if (options.emulateJSON) {
|
||||||
params.contentType = 'application/x-www-form-urlencoded';
|
params.contentType = 'application/x-www-form-urlencoded';
|
||||||
params.data = params.data ? {model: params.data} : {};
|
params.data = params.data ? {model: params.data} : {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// For older servers, emulate HTTP by mimicking the HTTP method with `_method`
|
// For older servers, emulate HTTP by mimicking the HTTP method with `_method`
|
||||||
// And an `X-HTTP-Method-Override` header.
|
// And an `X-HTTP-Method-Override` header.
|
||||||
if (Backbone.emulateHTTP) {
|
if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE')) {
|
||||||
if (type === 'PUT' || type === 'DELETE') {
|
params.type = 'POST';
|
||||||
if (Backbone.emulateJSON) params.data._method = type;
|
if (options.emulateJSON) params.data._method = type;
|
||||||
params.type = 'POST';
|
var beforeSend = options.beforeSend;
|
||||||
params.beforeSend = function(xhr) {
|
options.beforeSend = function(xhr) {
|
||||||
xhr.setRequestHeader('X-HTTP-Method-Override', type);
|
xhr.setRequestHeader('X-HTTP-Method-Override', type);
|
||||||
};
|
if (beforeSend) return beforeSend.apply(this, arguments);
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't process data on a non-GET request.
|
// Don't process data on a non-GET request.
|
||||||
if (params.type !== 'GET' && !Backbone.emulateJSON) {
|
if (params.type !== 'GET' && !options.emulateJSON) {
|
||||||
params.processData = false;
|
params.processData = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var success = options.success;
|
||||||
|
options.success = function(resp, status, xhr) {
|
||||||
|
if (success) success(resp, status, xhr);
|
||||||
|
model.trigger('sync', model, resp, options);
|
||||||
|
};
|
||||||
|
|
||||||
|
var error = options.error;
|
||||||
|
options.error = function(xhr, status, thrown) {
|
||||||
|
if (error) error(model, xhr, options);
|
||||||
|
model.trigger('error', model, xhr, options);
|
||||||
|
};
|
||||||
|
|
||||||
// Make the request, allowing the user to override any Ajax options.
|
// Make the request, allowing the user to override any Ajax options.
|
||||||
return Backbone.ajax(_.extend(params, options));
|
return Backbone.ajax(_.extend(params, options));
|
||||||
};
|
};
|
||||||
@@ -1389,69 +1428,47 @@
|
|||||||
return Backbone.$.ajax.apply(Backbone.$, arguments);
|
return Backbone.$.ajax.apply(Backbone.$, arguments);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Wrap an optional error callback with a fallback error event.
|
|
||||||
Backbone.wrapError = function(onError, originalModel, options) {
|
|
||||||
return function(model, resp) {
|
|
||||||
resp = model === originalModel ? resp : model;
|
|
||||||
if (onError) {
|
|
||||||
onError(originalModel, resp, options);
|
|
||||||
} else {
|
|
||||||
originalModel.trigger('error', originalModel, resp, options);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
// -------
|
// -------
|
||||||
|
|
||||||
// Shared empty constructor function to aid in prototype-chain creation.
|
|
||||||
var ctor = function(){};
|
|
||||||
|
|
||||||
// Helper function to correctly set up the prototype chain, for subclasses.
|
// Helper function to correctly set up the prototype chain, for subclasses.
|
||||||
// Similar to `goog.inherits`, but uses a hash of prototype properties and
|
// Similar to `goog.inherits`, but uses a hash of prototype properties and
|
||||||
// class properties to be extended.
|
// class properties to be extended.
|
||||||
var inherits = function(parent, protoProps, staticProps) {
|
var extend = function(protoProps, staticProps) {
|
||||||
|
var parent = this;
|
||||||
var child;
|
var child;
|
||||||
|
|
||||||
// The constructor function for the new subclass is either defined by you
|
// The constructor function for the new subclass is either defined by you
|
||||||
// (the "constructor" property in your `extend` definition), or defaulted
|
// (the "constructor" property in your `extend` definition), or defaulted
|
||||||
// by us to simply call the parent's constructor.
|
// by us to simply call the parent's constructor.
|
||||||
if (protoProps && protoProps.hasOwnProperty('constructor')) {
|
if (protoProps && _.has(protoProps, 'constructor')) {
|
||||||
child = protoProps.constructor;
|
child = protoProps.constructor;
|
||||||
} else {
|
} else {
|
||||||
child = function(){ parent.apply(this, arguments); };
|
child = function(){ parent.apply(this, arguments); };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inherit class (static) properties from parent.
|
// Add static properties to the constructor function, if supplied.
|
||||||
_.extend(child, parent);
|
_.extend(child, parent, staticProps);
|
||||||
|
|
||||||
// Set the prototype chain to inherit from `parent`, without calling
|
// Set the prototype chain to inherit from `parent`, without calling
|
||||||
// `parent`'s constructor function.
|
// `parent`'s constructor function.
|
||||||
ctor.prototype = parent.prototype;
|
var Surrogate = function(){ this.constructor = child; };
|
||||||
child.prototype = new ctor();
|
Surrogate.prototype = parent.prototype;
|
||||||
|
child.prototype = new Surrogate;
|
||||||
|
|
||||||
// Add prototype properties (instance properties) to the subclass,
|
// Add prototype properties (instance properties) to the subclass,
|
||||||
// if supplied.
|
// if supplied.
|
||||||
if (protoProps) _.extend(child.prototype, protoProps);
|
if (protoProps) _.extend(child.prototype, protoProps);
|
||||||
|
|
||||||
// Add static properties to the constructor function, if supplied.
|
// Set a convenience property in case the parent's prototype is needed
|
||||||
if (staticProps) _.extend(child, staticProps);
|
// later.
|
||||||
|
|
||||||
// Correctly set child's `prototype.constructor`.
|
|
||||||
child.prototype.constructor = child;
|
|
||||||
|
|
||||||
// Set a convenience property in case the parent's prototype is needed later.
|
|
||||||
child.__super__ = parent.prototype;
|
child.__super__ = parent.prototype;
|
||||||
|
|
||||||
return child;
|
return child;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper function to get a value from a Backbone object as a property
|
// Set up inheritance for the model, collection, router, view and history.
|
||||||
// or as a function.
|
Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;
|
||||||
var getValue = function(object, prop) {
|
|
||||||
if (!(object && object[prop])) return null;
|
|
||||||
return _.isFunction(object[prop]) ? object[prop]() : object[prop];
|
|
||||||
};
|
|
||||||
|
|
||||||
// Throw an error when a URL is needed, and none is supplied.
|
// Throw an error when a URL is needed, and none is supplied.
|
||||||
var urlError = function() {
|
var urlError = function() {
|
||||||
|
|||||||
246
vendor/backbone/test/collection.js
vendored
246
vendor/backbone/test/collection.js
vendored
@@ -1,13 +1,12 @@
|
|||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
|
|
||||||
var lastRequest = null;
|
|
||||||
var sync = Backbone.sync;
|
|
||||||
|
|
||||||
var a, b, c, d, e, col, otherCol;
|
var a, b, c, d, e, col, otherCol;
|
||||||
|
|
||||||
module("Backbone.Collection", {
|
module("Backbone.Collection", _.extend(new Environment, {
|
||||||
|
|
||||||
setup: function() {
|
setup: function() {
|
||||||
|
Environment.prototype.setup.apply(this, arguments);
|
||||||
|
|
||||||
a = new Backbone.Model({id: 3, label: 'a'});
|
a = new Backbone.Model({id: 3, label: 'a'});
|
||||||
b = new Backbone.Model({id: 2, label: 'b'});
|
b = new Backbone.Model({id: 2, label: 'b'});
|
||||||
c = new Backbone.Model({id: 1, label: 'c'});
|
c = new Backbone.Model({id: 1, label: 'c'});
|
||||||
@@ -15,23 +14,11 @@ $(document).ready(function() {
|
|||||||
e = null;
|
e = null;
|
||||||
col = new Backbone.Collection([a,b,c,d]);
|
col = new Backbone.Collection([a,b,c,d]);
|
||||||
otherCol = new Backbone.Collection();
|
otherCol = new Backbone.Collection();
|
||||||
|
|
||||||
Backbone.sync = function(method, model, options) {
|
|
||||||
lastRequest = {
|
|
||||||
method: method,
|
|
||||||
model: model,
|
|
||||||
options: options
|
|
||||||
};
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
teardown: function() {
|
|
||||||
Backbone.sync = sync;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
}));
|
||||||
|
|
||||||
test("Collection: new and sort", 7, function() {
|
test("new and sort", 7, function() {
|
||||||
equal(col.first(), a, "a should be first");
|
equal(col.first(), a, "a should be first");
|
||||||
equal(col.last(), d, "d should be last");
|
equal(col.last(), d, "d should be last");
|
||||||
col.comparator = function(a, b) {
|
col.comparator = function(a, b) {
|
||||||
@@ -47,13 +34,37 @@ $(document).ready(function() {
|
|||||||
equal(col.length, 4);
|
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(0), d);
|
||||||
equal(col.get(2), b);
|
equal(col.get(2), b);
|
||||||
equal(col.getByCid(col.first().cid), col.first());
|
equal(col.getByCid(col.first().cid), col.first());
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Collection: get with non-default ids", 2, function() {
|
test("get with non-default ids", 2, function() {
|
||||||
var col = new Backbone.Collection();
|
var col = new Backbone.Collection();
|
||||||
var MongoModel = Backbone.Model.extend({
|
var MongoModel = Backbone.Model.extend({
|
||||||
idAttribute: '_id'
|
idAttribute: '_id'
|
||||||
@@ -65,7 +76,7 @@ $(document).ready(function() {
|
|||||||
equal(col.get(101), model);
|
equal(col.get(101), model);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Collection: update index when id changes", 3, function() {
|
test("update index when id changes", 3, function() {
|
||||||
var col = new Backbone.Collection();
|
var col = new Backbone.Collection();
|
||||||
col.add([
|
col.add([
|
||||||
{id : 0, name : 'one'},
|
{id : 0, name : 'one'},
|
||||||
@@ -78,15 +89,15 @@ $(document).ready(function() {
|
|||||||
equal(col.get(101).get('name'), 'one');
|
equal(col.get(101).get('name'), 'one');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Collection: at", 1, function() {
|
test("at", 1, function() {
|
||||||
equal(col.at(2), c);
|
equal(col.at(2), c);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Collection: pluck", 1, function() {
|
test("pluck", 1, function() {
|
||||||
equal(col.pluck('label').join(' '), 'a b c d');
|
equal(col.pluck('label').join(' '), 'a b c d');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Collection: add", 11, function() {
|
test("add", 10, function() {
|
||||||
var added, opts, secondAdded;
|
var added, opts, secondAdded;
|
||||||
added = opts = secondAdded = null;
|
added = opts = secondAdded = null;
|
||||||
e = new Backbone.Model({id: 10, label : 'e'});
|
e = new Backbone.Model({id: 10, label : 'e'});
|
||||||
@@ -96,7 +107,6 @@ $(document).ready(function() {
|
|||||||
});
|
});
|
||||||
col.on('add', function(model, collection, options){
|
col.on('add', function(model, collection, options){
|
||||||
added = model.get('label');
|
added = model.get('label');
|
||||||
equal(options.index, 4);
|
|
||||||
opts = options;
|
opts = options;
|
||||||
});
|
});
|
||||||
col.add(e, {amazing: true});
|
col.add(e, {amazing: true});
|
||||||
@@ -118,7 +128,7 @@ $(document).ready(function() {
|
|||||||
equal(atCol.last(), h);
|
equal(atCol.last(), h);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Collection: add multiple models", 6, function() {
|
test("add multiple models", 6, function() {
|
||||||
var col = new Backbone.Collection([{at: 0}, {at: 1}, {at: 9}]);
|
var col = new Backbone.Collection([{at: 0}, {at: 1}, {at: 9}]);
|
||||||
col.add([{at: 2}, {at: 3}, {at: 4}, {at: 5}, {at: 6}, {at: 7}, {at: 8}], {at: 2});
|
col.add([{at: 2}, {at: 3}, {at: 4}, {at: 5}, {at: 6}, {at: 7}, {at: 8}], {at: 2});
|
||||||
for (var i = 0; i <= 5; i++) {
|
for (var i = 0; i <= 5; i++) {
|
||||||
@@ -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({
|
var Col = Backbone.Collection.extend({
|
||||||
comparator: function(a,b) {
|
comparator: function(a,b) {
|
||||||
return a.id > b.id ? -1 : 1;
|
return a.id > b.id ? -1 : 1;
|
||||||
@@ -139,19 +149,19 @@ $(document).ready(function() {
|
|||||||
equal(col.pluck('id').join(' '), '3 1 2');
|
equal(col.pluck('id').join(' '), '3 1 2');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Collection: can't add model to collection twice", function() {
|
test("can't add model to collection twice", function() {
|
||||||
var col = new Backbone.Collection([{id: 1}, {id: 2}, {id: 1}, {id: 2}, {id: 3}]);
|
var col = new Backbone.Collection([{id: 1}, {id: 2}, {id: 1}, {id: 2}, {id: 3}]);
|
||||||
equal(col.pluck('id').join(' '), '1 2 3');
|
equal(col.pluck('id').join(' '), '1 2 3');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Collection: can't add different model with same id to collection twice", 1, function() {
|
test("can't add different model with same id to collection twice", 1, function() {
|
||||||
var col = new Backbone.Collection;
|
var col = new Backbone.Collection;
|
||||||
col.unshift({id: 101});
|
col.unshift({id: 101});
|
||||||
col.add({id: 101});
|
col.add({id: 101});
|
||||||
equal(col.length, 1);
|
equal(col.length, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Collection: merge in duplicate models with {merge: true}", 3, function() {
|
test("merge in duplicate models with {merge: true}", 3, function() {
|
||||||
var col = new Backbone.Collection;
|
var col = new Backbone.Collection;
|
||||||
col.add([{id: 1, name: 'Moe'}, {id: 2, name: 'Curly'}, {id: 3, name: 'Larry'}]);
|
col.add([{id: 1, name: 'Moe'}, {id: 2, name: 'Curly'}, {id: 3, name: 'Larry'}]);
|
||||||
col.add({id: 1, name: 'Moses'});
|
col.add({id: 1, name: 'Moses'});
|
||||||
@@ -162,7 +172,7 @@ $(document).ready(function() {
|
|||||||
equal(col.first().get('name'), 'Tim');
|
equal(col.first().get('name'), 'Tim');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Collection: add model to multiple collections", 10, function() {
|
test("add model to multiple collections", 10, function() {
|
||||||
var counter = 0;
|
var counter = 0;
|
||||||
var e = new Backbone.Model({id: 10, label : 'e'});
|
var e = new Backbone.Model({id: 10, label : 'e'});
|
||||||
e.on('add', function(model, collection) {
|
e.on('add', function(model, collection) {
|
||||||
@@ -190,7 +200,7 @@ $(document).ready(function() {
|
|||||||
equal(e.collection, colE);
|
equal(e.collection, colE);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Collection: add model with parse", 1, function() {
|
test("add model with parse", 1, function() {
|
||||||
var Model = Backbone.Model.extend({
|
var Model = Backbone.Model.extend({
|
||||||
parse: function(obj) {
|
parse: function(obj) {
|
||||||
obj.value += 1;
|
obj.value += 1;
|
||||||
@@ -204,7 +214,7 @@ $(document).ready(function() {
|
|||||||
equal(col.at(0).get('value'), 2);
|
equal(col.at(0).get('value'), 2);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Collection: add model to collection with sort()-style comparator", 3, function() {
|
test("add model to collection with sort()-style comparator", 3, function() {
|
||||||
var col = new Backbone.Collection;
|
var col = new Backbone.Collection;
|
||||||
col.comparator = function(a, b) {
|
col.comparator = function(a, b) {
|
||||||
return a.get('name') < b.get('name') ? -1 : 1;
|
return a.get('name') < b.get('name') ? -1 : 1;
|
||||||
@@ -220,7 +230,7 @@ $(document).ready(function() {
|
|||||||
equal(col.indexOf(tom), 2);
|
equal(col.indexOf(tom), 2);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Collection: comparator that depends on `this`", 1, function() {
|
test("comparator that depends on `this`", 2, function() {
|
||||||
var col = new Backbone.Collection;
|
var col = new Backbone.Collection;
|
||||||
col.negative = function(num) {
|
col.negative = function(num) {
|
||||||
return -num;
|
return -num;
|
||||||
@@ -229,10 +239,15 @@ $(document).ready(function() {
|
|||||||
return this.negative(a.id);
|
return this.negative(a.id);
|
||||||
};
|
};
|
||||||
col.add([{id: 1}, {id: 2}, {id: 3}]);
|
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 removed = null;
|
||||||
var otherRemoved = null;
|
var otherRemoved = null;
|
||||||
col.on('remove', function(model, col, options) {
|
col.on('remove', function(model, col, options) {
|
||||||
@@ -249,20 +264,20 @@ $(document).ready(function() {
|
|||||||
equal(otherRemoved, null);
|
equal(otherRemoved, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Collection: shift and pop", 2, function() {
|
test("shift and pop", 2, function() {
|
||||||
var col = new Backbone.Collection([{a: 'a'}, {b: 'b'}, {c: 'c'}]);
|
var col = new Backbone.Collection([{a: 'a'}, {b: 'b'}, {c: 'c'}]);
|
||||||
equal(col.shift().get('a'), 'a');
|
equal(col.shift().get('a'), 'a');
|
||||||
equal(col.pop().get('c'), 'c');
|
equal(col.pop().get('c'), 'c');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Collection: slice", 2, function() {
|
test("slice", 2, function() {
|
||||||
var col = new Backbone.Collection([{a: 'a'}, {b: 'b'}, {c: 'c'}]);
|
var col = new Backbone.Collection([{a: 'a'}, {b: 'b'}, {c: 'c'}]);
|
||||||
var array = col.slice(1, 3);
|
var array = col.slice(1, 3);
|
||||||
equal(array.length, 2);
|
equal(array.length, 2);
|
||||||
equal(array[0].get('b'), 'b');
|
equal(array[0].get('b'), 'b');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Collection: events are unbound on remove", 3, function() {
|
test("events are unbound on remove", 3, function() {
|
||||||
var counter = 0;
|
var counter = 0;
|
||||||
var dj = new Backbone.Model();
|
var dj = new Backbone.Model();
|
||||||
var emcees = new Backbone.Collection([dj]);
|
var emcees = new Backbone.Collection([dj]);
|
||||||
@@ -275,7 +290,7 @@ $(document).ready(function() {
|
|||||||
equal(counter, 1);
|
equal(counter, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Collection: remove in multiple collections", 7, function() {
|
test("remove in multiple collections", 7, function() {
|
||||||
var modelData = {
|
var modelData = {
|
||||||
id : 5,
|
id : 5,
|
||||||
title : 'Othello'
|
title : 'Othello'
|
||||||
@@ -299,7 +314,7 @@ $(document).ready(function() {
|
|||||||
equal(passed, true);
|
equal(passed, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Collection: remove same model in multiple collection", 16, function() {
|
test("remove same model in multiple collection", 16, function() {
|
||||||
var counter = 0;
|
var counter = 0;
|
||||||
var e = new Backbone.Model({id: 5, title: 'Othello'});
|
var e = new Backbone.Model({id: 5, title: 'Othello'});
|
||||||
e.on('remove', function(model, collection) {
|
e.on('remove', function(model, collection) {
|
||||||
@@ -333,7 +348,7 @@ $(document).ready(function() {
|
|||||||
equal(counter, 2);
|
equal(counter, 2);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Collection: model destroy removes from all collections", 3, function() {
|
test("model destroy removes from all collections", 3, function() {
|
||||||
var e = new Backbone.Model({id: 5, title: 'Othello'});
|
var e = new Backbone.Model({id: 5, title: 'Othello'});
|
||||||
e.sync = function(method, model, options) { options.success({}); };
|
e.sync = function(method, model, options) { options.success({}); };
|
||||||
var colE = new Backbone.Collection([e]);
|
var colE = new Backbone.Collection([e]);
|
||||||
@@ -355,25 +370,29 @@ $(document).ready(function() {
|
|||||||
equal(undefined, e.collection);
|
equal(undefined, e.collection);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Collection: fetch", 4, function() {
|
test("fetch", 4, function() {
|
||||||
col.fetch();
|
var collection = new Backbone.Collection;
|
||||||
equal(lastRequest.method, 'read');
|
collection.url = '/test';
|
||||||
equal(lastRequest.model, col);
|
collection.fetch();
|
||||||
equal(lastRequest.options.parse, true);
|
equal(this.syncArgs.method, 'read');
|
||||||
|
equal(this.syncArgs.model, collection);
|
||||||
|
equal(this.syncArgs.options.parse, true);
|
||||||
|
|
||||||
col.fetch({parse: false});
|
collection.fetch({parse: false});
|
||||||
equal(lastRequest.options.parse, false);
|
equal(this.syncArgs.options.parse, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Collection: create", 4, function() {
|
test("create", 4, function() {
|
||||||
var model = col.create({label: 'f'}, {wait: true});
|
var collection = new Backbone.Collection;
|
||||||
equal(lastRequest.method, 'create');
|
collection.url = '/test';
|
||||||
equal(lastRequest.model, model);
|
var model = collection.create({label: 'f'}, {wait: true});
|
||||||
|
equal(this.syncArgs.method, 'create');
|
||||||
|
equal(this.syncArgs.model, model);
|
||||||
equal(model.get('label'), 'f');
|
equal(model.get('label'), 'f');
|
||||||
equal(model.collection, col);
|
equal(model.collection, collection);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Collection: create enforces validation", 1, function() {
|
test("create enforces validation", 1, function() {
|
||||||
var ValidatingModel = Backbone.Model.extend({
|
var ValidatingModel = Backbone.Model.extend({
|
||||||
validate: function(attrs) {
|
validate: function(attrs) {
|
||||||
return "fail";
|
return "fail";
|
||||||
@@ -386,7 +405,7 @@ $(document).ready(function() {
|
|||||||
equal(col.create({"foo":"bar"}), false);
|
equal(col.create({"foo":"bar"}), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Collection: a failing create runs the error callback", 1, function() {
|
test("a failing create runs the error callback", 1, function() {
|
||||||
var ValidatingModel = Backbone.Model.extend({
|
var ValidatingModel = Backbone.Model.extend({
|
||||||
validate: function(attrs) {
|
validate: function(attrs) {
|
||||||
return "fail";
|
return "fail";
|
||||||
@@ -402,7 +421,7 @@ $(document).ready(function() {
|
|||||||
equal(flag, true);
|
equal(flag, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("collection: initialize", 1, function() {
|
test("initialize", 1, function() {
|
||||||
var Collection = Backbone.Collection.extend({
|
var Collection = Backbone.Collection.extend({
|
||||||
initialize: function() {
|
initialize: function() {
|
||||||
this.one = 1;
|
this.one = 1;
|
||||||
@@ -412,11 +431,11 @@ $(document).ready(function() {
|
|||||||
equal(coll.one, 1);
|
equal(coll.one, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Collection: toJSON", 1, function() {
|
test("toJSON", 1, function() {
|
||||||
equal(JSON.stringify(col), '[{"id":3,"label":"a"},{"id":2,"label":"b"},{"id":1,"label":"c"},{"id":0,"label":"d"}]');
|
equal(JSON.stringify(col), '[{"id":3,"label":"a"},{"id":2,"label":"b"},{"id":1,"label":"c"},{"id":0,"label":"d"}]');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Collection: where", 6, function() {
|
test("where", 6, function() {
|
||||||
var coll = new Backbone.Collection([
|
var coll = new Backbone.Collection([
|
||||||
{a: 1},
|
{a: 1},
|
||||||
{a: 1},
|
{a: 1},
|
||||||
@@ -432,7 +451,7 @@ $(document).ready(function() {
|
|||||||
equal(coll.where({a: 1, b: 2}).length, 1);
|
equal(coll.where({a: 1, b: 2}).length, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Collection: Underscore methods", 13, function() {
|
test("Underscore methods", 13, function() {
|
||||||
equal(col.map(function(model){ return model.get('label'); }).join(' '), 'a b c d');
|
equal(col.map(function(model){ return model.get('label'); }).join(' '), 'a b c d');
|
||||||
equal(col.any(function(model){ return model.id === 100; }), false);
|
equal(col.any(function(model){ return model.id === 100; }), false);
|
||||||
equal(col.any(function(model){ return model.id === 0; }), true);
|
equal(col.any(function(model){ return model.id === 0; }), true);
|
||||||
@@ -452,7 +471,7 @@ $(document).ready(function() {
|
|||||||
[4, 0]);
|
[4, 0]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Collection: reset", 10, function() {
|
test("reset", 10, function() {
|
||||||
var resetCount = 0;
|
var resetCount = 0;
|
||||||
var models = col.models;
|
var models = col.models;
|
||||||
col.on('reset', function() { resetCount += 1; });
|
col.on('reset', function() { resetCount += 1; });
|
||||||
@@ -471,7 +490,7 @@ $(document).ready(function() {
|
|||||||
ok(_.isEqual(col.last().attributes, d.attributes));
|
ok(_.isEqual(col.last().attributes, d.attributes));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Collection: reset passes caller options", 3, function() {
|
test("reset passes caller options", 3, function() {
|
||||||
var Model = Backbone.Model.extend({
|
var Model = Backbone.Model.extend({
|
||||||
initialize: function(attrs, options) {
|
initialize: function(attrs, options) {
|
||||||
this.model_parameter = options.model_parameter;
|
this.model_parameter = options.model_parameter;
|
||||||
@@ -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;
|
var fired = null;
|
||||||
a.on("custom", function() { fired = true; });
|
a.on("custom", function() { fired = true; });
|
||||||
a.trigger("custom");
|
a.trigger("custom");
|
||||||
equal(fired, true);
|
equal(fired, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Collection: add does not alter arguments", 2, function(){
|
test("add does not alter arguments", 2, function(){
|
||||||
var attrs = {};
|
var attrs = {};
|
||||||
var models = [attrs];
|
var models = [attrs];
|
||||||
new Backbone.Collection().add(models);
|
new Backbone.Collection().add(models);
|
||||||
@@ -501,16 +520,17 @@ $(document).ready(function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("#714: access `model.collection` in a brand new model.", 2, function() {
|
test("#714: access `model.collection` in a brand new model.", 2, function() {
|
||||||
var col = new Backbone.Collection;
|
var collection = new Backbone.Collection;
|
||||||
|
collection.url = '/test';
|
||||||
var Model = Backbone.Model.extend({
|
var Model = Backbone.Model.extend({
|
||||||
set: function(attrs) {
|
set: function(attrs) {
|
||||||
equal(attrs.prop, 'value');
|
equal(attrs.prop, 'value');
|
||||||
equal(this.collection, col);
|
equal(this.collection, collection);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
col.model = Model;
|
collection.model = Model;
|
||||||
col.create({prop: 'value'});
|
collection.create({prop: 'value'});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("#574, remove its own reference to the .models array.", 2, function() {
|
test("#574, remove its own reference to the .models array.", 2, function() {
|
||||||
@@ -542,24 +562,7 @@ $(document).ready(function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Collection: index with comparator", 4, function() {
|
test("throwing during add leaves consistent state", 4, function() {
|
||||||
var counter = 0;
|
|
||||||
var col = new Backbone.Collection([{id: 2}, {id: 4}], {
|
|
||||||
comparator: function(model){ return model.id; }
|
|
||||||
}).on('add', function(model, colleciton, options){
|
|
||||||
if (model.id == 1) {
|
|
||||||
equal(options.index, 0);
|
|
||||||
equal(counter++, 0);
|
|
||||||
}
|
|
||||||
if (model.id == 3) {
|
|
||||||
equal(options.index, 2);
|
|
||||||
equal(counter++, 1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
col.add([{id: 3}, {id: 1}]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Collection: throwing during add leaves consistent state", 4, function() {
|
|
||||||
var col = new Backbone.Collection();
|
var col = new Backbone.Collection();
|
||||||
col.on('test', function() { ok(false); });
|
col.on('test', function() { ok(false); });
|
||||||
col.model = Backbone.Model.extend({
|
col.model = Backbone.Model.extend({
|
||||||
@@ -573,7 +576,7 @@ $(document).ready(function() {
|
|||||||
equal(col.length, 0);
|
equal(col.length, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Collection: multiple copies of the same model", 3, function() {
|
test("multiple copies of the same model", 3, function() {
|
||||||
var col = new Backbone.Collection();
|
var col = new Backbone.Collection();
|
||||||
var model = new Backbone.Model();
|
var model = new Backbone.Model();
|
||||||
col.add([model, model]);
|
col.add([model, model]);
|
||||||
@@ -606,7 +609,7 @@ $(document).ready(function() {
|
|||||||
ok(!collection.get('undefined'));
|
ok(!collection.get('undefined'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Collection: falsy comparator", 4, function(){
|
test("falsy comparator", 4, function(){
|
||||||
var Col = Backbone.Collection.extend({
|
var Col = Backbone.Collection.extend({
|
||||||
comparator: function(model){ return model.id; }
|
comparator: function(model){ return model.id; }
|
||||||
});
|
});
|
||||||
@@ -636,20 +639,15 @@ $(document).ready(function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("#1412 - Trigger 'sync' event.", 2, function() {
|
test("#1412 - Trigger 'sync' event.", 2, function() {
|
||||||
var collection = new Backbone.Collection([], {
|
var collection = new Backbone.Collection;
|
||||||
model: Backbone.Model.extend({
|
collection.url = '/test';
|
||||||
sync: function(method, model, options) {
|
|
||||||
options.success();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
collection.sync = function(method, model, options) { options.success(); };
|
|
||||||
collection.on('sync', function() { ok(true); });
|
collection.on('sync', function() { ok(true); });
|
||||||
|
Backbone.ajax = function(settings){ settings.success(); };
|
||||||
collection.fetch();
|
collection.fetch();
|
||||||
collection.create({id: 1});
|
collection.create({id: 1});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("#1447 - create with wait adds model.", function() {
|
test("#1447 - create with wait adds model.", 1, function() {
|
||||||
var collection = new Backbone.Collection;
|
var collection = new Backbone.Collection;
|
||||||
var model = new Backbone.Model;
|
var model = new Backbone.Model;
|
||||||
model.sync = function(method, model, options){ options.success(); };
|
model.sync = function(method, model, options){ options.success(); };
|
||||||
@@ -657,7 +655,7 @@ $(document).ready(function() {
|
|||||||
collection.create(model, {wait: true});
|
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([
|
var collection = new Backbone.Collection([
|
||||||
{id: 1, x: 1},
|
{id: 1, x: 1},
|
||||||
{id: 2, x: 2}
|
{id: 2, x: 2}
|
||||||
@@ -666,4 +664,56 @@ $(document).ready(function() {
|
|||||||
collection.add({id: 1, x: 3}, {merge: true});
|
collection.add({id: 1, x: 3}, {merge: true});
|
||||||
deepEqual(collection.pluck('id'), [2, 1]);
|
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]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("fetch parses models by default", 1, function() {
|
||||||
|
var model = {};
|
||||||
|
var Collection = Backbone.Collection.extend({
|
||||||
|
url: 'test',
|
||||||
|
model: Backbone.Model.extend({
|
||||||
|
parse: function(resp) {
|
||||||
|
strictEqual(resp, model);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
new Collection().fetch();
|
||||||
|
this.ajaxSettings.success([model]);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
45
vendor/backbone/test/environment.js
vendored
Normal file
45
vendor/backbone/test/environment.js
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
(function() {
|
||||||
|
|
||||||
|
var Environment = this.Environment = function(){};
|
||||||
|
|
||||||
|
_.extend(Environment.prototype, {
|
||||||
|
|
||||||
|
ajax: Backbone.ajax,
|
||||||
|
|
||||||
|
sync: Backbone.sync,
|
||||||
|
|
||||||
|
emulateHTTP: Backbone.emulateHTTP,
|
||||||
|
|
||||||
|
emulateJSON: Backbone.emulateJSON,
|
||||||
|
|
||||||
|
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;
|
||||||
|
Backbone.emulateHTTP = this.emulateHTTP;
|
||||||
|
Backbone.emulateJSON = this.emulateJSON;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
})();
|
||||||
20
vendor/backbone/test/events.js
vendored
20
vendor/backbone/test/events.js
vendored
@@ -2,7 +2,7 @@ $(document).ready(function() {
|
|||||||
|
|
||||||
module("Backbone.Events");
|
module("Backbone.Events");
|
||||||
|
|
||||||
test("Events: on and trigger", 2, function() {
|
test("on and trigger", 2, function() {
|
||||||
var obj = { counter: 0 };
|
var obj = { counter: 0 };
|
||||||
_.extend(obj,Backbone.Events);
|
_.extend(obj,Backbone.Events);
|
||||||
obj.on('event', function() { obj.counter += 1; });
|
obj.on('event', function() { obj.counter += 1; });
|
||||||
@@ -15,7 +15,7 @@ $(document).ready(function() {
|
|||||||
equal(obj.counter, 5, 'counter should be incremented five times.');
|
equal(obj.counter, 5, 'counter should be incremented five times.');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Events: binding and triggering multiple events", 4, function() {
|
test("binding and triggering multiple events", 4, function() {
|
||||||
var obj = { counter: 0 };
|
var obj = { counter: 0 };
|
||||||
_.extend(obj,Backbone.Events);
|
_.extend(obj,Backbone.Events);
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ $(document).ready(function() {
|
|||||||
equal(obj.counter, 5);
|
equal(obj.counter, 5);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Events: trigger all for each event", 3, function() {
|
test("trigger all for each event", 3, function() {
|
||||||
var a, b, obj = { counter: 0 };
|
var a, b, obj = { counter: 0 };
|
||||||
_.extend(obj, Backbone.Events);
|
_.extend(obj, Backbone.Events);
|
||||||
obj.on('all', function(event) {
|
obj.on('all', function(event) {
|
||||||
@@ -49,7 +49,7 @@ $(document).ready(function() {
|
|||||||
equal(obj.counter, 2);
|
equal(obj.counter, 2);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Events: on, then unbind all functions", 1, function() {
|
test("on, then unbind all functions", 1, function() {
|
||||||
var obj = { counter: 0 };
|
var obj = { counter: 0 };
|
||||||
_.extend(obj,Backbone.Events);
|
_.extend(obj,Backbone.Events);
|
||||||
var callback = function() { obj.counter += 1; };
|
var callback = function() { obj.counter += 1; };
|
||||||
@@ -60,7 +60,7 @@ $(document).ready(function() {
|
|||||||
equal(obj.counter, 1, 'counter should have only been incremented once.');
|
equal(obj.counter, 1, 'counter should have only been incremented once.');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Events: bind two callbacks, unbind only one", 2, function() {
|
test("bind two callbacks, unbind only one", 2, function() {
|
||||||
var obj = { counterA: 0, counterB: 0 };
|
var obj = { counterA: 0, counterB: 0 };
|
||||||
_.extend(obj,Backbone.Events);
|
_.extend(obj,Backbone.Events);
|
||||||
var callback = function() { obj.counterA += 1; };
|
var callback = function() { obj.counterA += 1; };
|
||||||
@@ -73,7 +73,7 @@ $(document).ready(function() {
|
|||||||
equal(obj.counterB, 2, 'counterB should have been incremented twice.');
|
equal(obj.counterB, 2, 'counterB should have been incremented twice.');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Events: unbind a callback in the midst of it firing", 1, function() {
|
test("unbind a callback in the midst of it firing", 1, function() {
|
||||||
var obj = {counter: 0};
|
var obj = {counter: 0};
|
||||||
_.extend(obj, Backbone.Events);
|
_.extend(obj, Backbone.Events);
|
||||||
var callback = function() {
|
var callback = function() {
|
||||||
@@ -87,7 +87,7 @@ $(document).ready(function() {
|
|||||||
equal(obj.counter, 1, 'the callback should have been unbound.');
|
equal(obj.counter, 1, 'the callback should have been unbound.');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Events: two binds that unbind themeselves", 2, function() {
|
test("two binds that unbind themeselves", 2, function() {
|
||||||
var obj = { counterA: 0, counterB: 0 };
|
var obj = { counterA: 0, counterB: 0 };
|
||||||
_.extend(obj,Backbone.Events);
|
_.extend(obj,Backbone.Events);
|
||||||
var incrA = function(){ obj.counterA += 1; obj.off('event', incrA); };
|
var incrA = function(){ obj.counterA += 1; obj.off('event', incrA); };
|
||||||
@@ -101,7 +101,7 @@ $(document).ready(function() {
|
|||||||
equal(obj.counterB, 1, 'counterB should have only been incremented once.');
|
equal(obj.counterB, 1, 'counterB should have only been incremented once.');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Events: bind a callback with a supplied context", 1, function () {
|
test("bind a callback with a supplied context", 1, function () {
|
||||||
var TestClass = function () {
|
var TestClass = function () {
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
@@ -114,7 +114,7 @@ $(document).ready(function() {
|
|||||||
obj.trigger('event');
|
obj.trigger('event');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Events: nested trigger with unbind", 1, function () {
|
test("nested trigger with unbind", 1, function () {
|
||||||
var obj = { counter: 0 };
|
var obj = { counter: 0 };
|
||||||
_.extend(obj, Backbone.Events);
|
_.extend(obj, Backbone.Events);
|
||||||
var incr1 = function(){ obj.counter += 1; obj.off('event', incr1); obj.trigger('event'); };
|
var incr1 = function(){ obj.counter += 1; obj.off('event', incr1); obj.trigger('event'); };
|
||||||
@@ -125,7 +125,7 @@ $(document).ready(function() {
|
|||||||
equal(obj.counter, 3, 'counter should have been incremented three times');
|
equal(obj.counter, 3, 'counter should have been incremented three times');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Events: callback list is not altered during trigger", 2, function () {
|
test("callback list is not altered during trigger", 2, function () {
|
||||||
var counter = 0, obj = _.extend({}, Backbone.Events);
|
var counter = 0, obj = _.extend({}, Backbone.Events);
|
||||||
var incr = function(){ counter++; };
|
var incr = function(){ counter++; };
|
||||||
obj.on('event', function(){ obj.on('event', incr).on('all', incr); })
|
obj.on('event', function(){ obj.on('event', incr).on('all', incr); })
|
||||||
|
|||||||
239
vendor/backbone/test/model.js
vendored
239
vendor/backbone/test/model.js
vendored
@@ -1,22 +1,15 @@
|
|||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
|
|
||||||
// Variable to catch the last request.
|
|
||||||
var lastRequest = null;
|
|
||||||
// Variable to catch ajax params.
|
|
||||||
var ajaxParams = null;
|
|
||||||
var sync = Backbone.sync;
|
|
||||||
var ajax = Backbone.ajax;
|
|
||||||
var urlRoot = null;
|
|
||||||
|
|
||||||
var proxy = Backbone.Model.extend();
|
var proxy = Backbone.Model.extend();
|
||||||
var klass = Backbone.Collection.extend({
|
var klass = Backbone.Collection.extend({
|
||||||
url : function() { return '/collection'; }
|
url : function() { return '/collection'; }
|
||||||
});
|
});
|
||||||
var doc, collection;
|
var doc, collection;
|
||||||
|
|
||||||
module("Backbone.Model", {
|
module("Backbone.Model", _.extend(new Environment, {
|
||||||
|
|
||||||
setup: function() {
|
setup: function() {
|
||||||
|
Environment.prototype.setup.apply(this, arguments);
|
||||||
doc = new proxy({
|
doc = new proxy({
|
||||||
id : '1-the-tempest',
|
id : '1-the-tempest',
|
||||||
title : "The Tempest",
|
title : "The Tempest",
|
||||||
@@ -25,29 +18,11 @@ $(document).ready(function() {
|
|||||||
});
|
});
|
||||||
collection = new klass();
|
collection = new klass();
|
||||||
collection.add(doc);
|
collection.add(doc);
|
||||||
|
|
||||||
Backbone.sync = function(method, model, options) {
|
|
||||||
lastRequest = {
|
|
||||||
method: method,
|
|
||||||
model: model,
|
|
||||||
options: options
|
|
||||||
};
|
|
||||||
sync.apply(this, arguments);
|
|
||||||
};
|
|
||||||
Backbone.ajax = function(params) { ajaxParams = params; };
|
|
||||||
urlRoot = Backbone.Model.prototype.urlRoot;
|
|
||||||
Backbone.Model.prototype.urlRoot = '/';
|
|
||||||
},
|
|
||||||
|
|
||||||
teardown: function() {
|
|
||||||
Backbone.sync = sync;
|
|
||||||
Backbone.ajax = ajax;
|
|
||||||
Backbone.Model.prototype.urlRoot = urlRoot;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
}));
|
||||||
|
|
||||||
test("Model: initialize", 3, function() {
|
test("initialize", 3, function() {
|
||||||
var Model = Backbone.Model.extend({
|
var Model = Backbone.Model.extend({
|
||||||
initialize: function() {
|
initialize: function() {
|
||||||
this.one = 1;
|
this.one = 1;
|
||||||
@@ -59,7 +34,7 @@ $(document).ready(function() {
|
|||||||
equal(model.collection, collection);
|
equal(model.collection, collection);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Model: initialize with attributes and options", 1, function() {
|
test("initialize with attributes and options", 1, function() {
|
||||||
var Model = Backbone.Model.extend({
|
var Model = Backbone.Model.extend({
|
||||||
initialize: function(attributes, options) {
|
initialize: function(attributes, options) {
|
||||||
this.one = options.one;
|
this.one = options.one;
|
||||||
@@ -69,7 +44,7 @@ $(document).ready(function() {
|
|||||||
equal(model.one, 1);
|
equal(model.one, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Model: initialize with parsed attributes", 1, function() {
|
test("initialize with parsed attributes", 1, function() {
|
||||||
var Model = Backbone.Model.extend({
|
var Model = Backbone.Model.extend({
|
||||||
parse: function(obj) {
|
parse: function(obj) {
|
||||||
obj.value += 1;
|
obj.value += 1;
|
||||||
@@ -80,7 +55,18 @@ $(document).ready(function() {
|
|||||||
equal(model.get('value'), 2);
|
equal(model.get('value'), 2);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Model: url", 3, function() {
|
test("parse can return null", 1, function() {
|
||||||
|
var Model = Backbone.Model.extend({
|
||||||
|
parse: function(obj) {
|
||||||
|
obj.value += 1;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var model = new Model({value: 1}, {parse: true});
|
||||||
|
equal(JSON.stringify(model.toJSON()), "{}");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("url", 3, function() {
|
||||||
doc.urlRoot = null;
|
doc.urlRoot = null;
|
||||||
equal(doc.url(), '/collection/1-the-tempest');
|
equal(doc.url(), '/collection/1-the-tempest');
|
||||||
doc.collection.url = '/collection/';
|
doc.collection.url = '/collection/';
|
||||||
@@ -90,7 +76,7 @@ $(document).ready(function() {
|
|||||||
doc.collection = collection;
|
doc.collection = collection;
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Model: url when using urlRoot, and uri encoding", 2, function() {
|
test("url when using urlRoot, and uri encoding", 2, function() {
|
||||||
var Model = Backbone.Model.extend({
|
var Model = Backbone.Model.extend({
|
||||||
urlRoot: '/collection'
|
urlRoot: '/collection'
|
||||||
});
|
});
|
||||||
@@ -100,7 +86,7 @@ $(document).ready(function() {
|
|||||||
equal(model.url(), '/collection/%2B1%2B');
|
equal(model.url(), '/collection/%2B1%2B');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Model: url when using urlRoot as a function to determine urlRoot at runtime", 2, function() {
|
test("url when using urlRoot as a function to determine urlRoot at runtime", 2, function() {
|
||||||
var Model = Backbone.Model.extend({
|
var Model = Backbone.Model.extend({
|
||||||
urlRoot: function() {
|
urlRoot: function() {
|
||||||
return '/nested/' + this.get('parent_id') + '/collection';
|
return '/nested/' + this.get('parent_id') + '/collection';
|
||||||
@@ -113,7 +99,7 @@ $(document).ready(function() {
|
|||||||
equal(model.url(), '/nested/1/collection/2');
|
equal(model.url(), '/nested/1/collection/2');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Model: clone", 8, function() {
|
test("clone", 8, function() {
|
||||||
var a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3});
|
var a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3});
|
||||||
var b = a.clone();
|
var b = a.clone();
|
||||||
equal(a.get('foo'), 1);
|
equal(a.get('foo'), 1);
|
||||||
@@ -127,7 +113,7 @@ $(document).ready(function() {
|
|||||||
equal(b.get('foo'), 1, "Changing a parent attribute does not change the clone.");
|
equal(b.get('foo'), 1, "Changing a parent attribute does not change the clone.");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Model: isNew", 6, function() {
|
test("isNew", 6, function() {
|
||||||
var a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3});
|
var a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3});
|
||||||
ok(a.isNew(), "it should be new");
|
ok(a.isNew(), "it should be new");
|
||||||
a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3, 'id': -5 });
|
a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3, 'id': -5 });
|
||||||
@@ -139,12 +125,12 @@ $(document).ready(function() {
|
|||||||
ok(!new Backbone.Model({ 'id': -5 }).isNew(), "is false for a negative integer");
|
ok(!new Backbone.Model({ 'id': -5 }).isNew(), "is false for a negative integer");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Model: get", 2, function() {
|
test("get", 2, function() {
|
||||||
equal(doc.get('title'), 'The Tempest');
|
equal(doc.get('title'), 'The Tempest');
|
||||||
equal(doc.get('author'), 'Bill Shakespeare');
|
equal(doc.get('author'), 'Bill Shakespeare');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Model: escape", 5, function() {
|
test("escape", 5, function() {
|
||||||
equal(doc.escape('title'), 'The Tempest');
|
equal(doc.escape('title'), 'The Tempest');
|
||||||
doc.set({audience: 'Bill & Bob'});
|
doc.set({audience: 'Bill & Bob'});
|
||||||
equal(doc.escape('audience'), 'Bill & Bob');
|
equal(doc.escape('audience'), 'Bill & Bob');
|
||||||
@@ -156,7 +142,7 @@ $(document).ready(function() {
|
|||||||
equal(doc.escape('audience'), '');
|
equal(doc.escape('audience'), '');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Model: has", 10, function() {
|
test("has", 10, function() {
|
||||||
var model = new Backbone.Model();
|
var model = new Backbone.Model();
|
||||||
|
|
||||||
strictEqual(model.has('name'), false);
|
strictEqual(model.has('name'), false);
|
||||||
@@ -186,7 +172,7 @@ $(document).ready(function() {
|
|||||||
strictEqual(model.has('undefined'), false);
|
strictEqual(model.has('undefined'), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Model: set and unset", 8, function() {
|
test("set and unset", 8, function() {
|
||||||
var a = new Backbone.Model({id: 'id', foo: 1, bar: 2, baz: 3});
|
var a = new Backbone.Model({id: 'id', foo: 1, bar: 2, baz: 3});
|
||||||
var changeCount = 0;
|
var changeCount = 0;
|
||||||
a.on("change:foo", function() { changeCount += 1; });
|
a.on("change:foo", function() { changeCount += 1; });
|
||||||
@@ -209,7 +195,7 @@ $(document).ready(function() {
|
|||||||
equal(a.id, undefined, "Unsetting the id should remove the id property.");
|
equal(a.id, undefined, "Unsetting the id should remove the id property.");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Model: multiple unsets", 1, function() {
|
test("multiple unsets", 1, function() {
|
||||||
var i = 0;
|
var i = 0;
|
||||||
var counter = function(){ i++; };
|
var counter = function(){ i++; };
|
||||||
var model = new Backbone.Model({a: 1});
|
var model = new Backbone.Model({a: 1});
|
||||||
@@ -220,7 +206,7 @@ $(document).ready(function() {
|
|||||||
equal(i, 2, 'Unset does not fire an event for missing attributes.');
|
equal(i, 2, 'Unset does not fire an event for missing attributes.');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Model: unset and changedAttributes", 2, function() {
|
test("unset and changedAttributes", 2, function() {
|
||||||
var model = new Backbone.Model({a: 1});
|
var model = new Backbone.Model({a: 1});
|
||||||
model.unset('a', {silent: true});
|
model.unset('a', {silent: true});
|
||||||
var changedAttributes = model.changedAttributes();
|
var changedAttributes = model.changedAttributes();
|
||||||
@@ -230,7 +216,7 @@ $(document).ready(function() {
|
|||||||
ok('a' in changedAttributes, 'changedAttributes should contain unset properties when running changedAttributes again after an unset.');
|
ok('a' in changedAttributes, 'changedAttributes should contain unset properties when running changedAttributes again after an unset.');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Model: using a non-default id attribute.", 5, function() {
|
test("using a non-default id attribute.", 5, function() {
|
||||||
var MongoModel = Backbone.Model.extend({idAttribute : '_id'});
|
var MongoModel = Backbone.Model.extend({idAttribute : '_id'});
|
||||||
var model = new MongoModel({id: 'eye-dee', _id: 25, title: 'Model'});
|
var model = new MongoModel({id: 'eye-dee', _id: 25, title: 'Model'});
|
||||||
equal(model.get('id'), 'eye-dee');
|
equal(model.get('id'), 'eye-dee');
|
||||||
@@ -241,13 +227,13 @@ $(document).ready(function() {
|
|||||||
equal(model.isNew(), true);
|
equal(model.isNew(), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Model: set an empty string", 1, function() {
|
test("set an empty string", 1, function() {
|
||||||
var model = new Backbone.Model({name : "Model"});
|
var model = new Backbone.Model({name : "Model"});
|
||||||
model.set({name : ''});
|
model.set({name : ''});
|
||||||
equal(model.get('name'), '');
|
equal(model.get('name'), '');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Model: clear", 3, function() {
|
test("clear", 3, function() {
|
||||||
var changed;
|
var changed;
|
||||||
var model = new Backbone.Model({id: 1, name : "Model"});
|
var model = new Backbone.Model({id: 1, name : "Model"});
|
||||||
model.on("change:name", function(){ changed = true; });
|
model.on("change:name", function(){ changed = true; });
|
||||||
@@ -260,7 +246,7 @@ $(document).ready(function() {
|
|||||||
equal(model.get('name'), undefined);
|
equal(model.get('name'), undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Model: defaults", 4, function() {
|
test("defaults", 4, function() {
|
||||||
var Defaulted = Backbone.Model.extend({
|
var Defaulted = Backbone.Model.extend({
|
||||||
defaults: {
|
defaults: {
|
||||||
"one": 1,
|
"one": 1,
|
||||||
@@ -283,7 +269,7 @@ $(document).ready(function() {
|
|||||||
equal(model.get('two'), null);
|
equal(model.get('two'), null);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Model: change, hasChanged, changedAttributes, previous, previousAttributes", 12, function() {
|
test("change, hasChanged, changedAttributes, previous, previousAttributes", 12, function() {
|
||||||
var model = new Backbone.Model({name : "Tim", age : 10});
|
var model = new Backbone.Model({name : "Tim", age : 10});
|
||||||
equal(model.changedAttributes(), false);
|
equal(model.changedAttributes(), false);
|
||||||
model.on('change', function() {
|
model.on('change', function() {
|
||||||
@@ -304,14 +290,14 @@ $(document).ready(function() {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Model: changedAttributes", 3, function() {
|
test("changedAttributes", 3, function() {
|
||||||
var model = new Backbone.Model({a: 'a', b: 'b'});
|
var model = new Backbone.Model({a: 'a', b: 'b'});
|
||||||
equal(model.changedAttributes(), false);
|
equal(model.changedAttributes(), false);
|
||||||
equal(model.changedAttributes({a: 'a'}), false);
|
equal(model.changedAttributes({a: 'a'}), false);
|
||||||
equal(model.changedAttributes({a: 'b'}).a, 'b');
|
equal(model.changedAttributes({a: 'b'}).a, 'b');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Model: change with options", 2, function() {
|
test("change with options", 2, function() {
|
||||||
var value;
|
var value;
|
||||||
var model = new Backbone.Model({name: 'Rob'});
|
var model = new Backbone.Model({name: 'Rob'});
|
||||||
model.on('change', function(model, options) {
|
model.on('change', function(model, options) {
|
||||||
@@ -324,7 +310,7 @@ $(document).ready(function() {
|
|||||||
equal(value, 'Ms. Sue');
|
equal(value, 'Ms. Sue');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Model: change after initialize", 1, function () {
|
test("change after initialize", 1, function () {
|
||||||
var changed = 0;
|
var changed = 0;
|
||||||
var attrs = {id: 1, label: 'c'};
|
var attrs = {id: 1, label: 'c'};
|
||||||
var obj = new Backbone.Model(attrs);
|
var obj = new Backbone.Model(attrs);
|
||||||
@@ -333,16 +319,18 @@ $(document).ready(function() {
|
|||||||
equal(changed, 0);
|
equal(changed, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Model: save within change event", 1, function () {
|
test("save within change event", 1, function () {
|
||||||
|
var env = this;
|
||||||
var model = new Backbone.Model({firstName : "Taylor", lastName: "Swift"});
|
var model = new Backbone.Model({firstName : "Taylor", lastName: "Swift"});
|
||||||
|
model.url = '/test';
|
||||||
model.on('change', function () {
|
model.on('change', function () {
|
||||||
model.save();
|
model.save();
|
||||||
ok(_.isEqual(lastRequest.model, model));
|
ok(_.isEqual(env.syncArgs.model, model));
|
||||||
});
|
});
|
||||||
model.set({lastName: 'Hicks'});
|
model.set({lastName: 'Hicks'});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Model: validate after save", 1, function() {
|
test("validate after save", 1, function() {
|
||||||
var lastError, model = new Backbone.Model();
|
var lastError, model = new Backbone.Model();
|
||||||
model.validate = function(attrs) {
|
model.validate = function(attrs) {
|
||||||
if (attrs.admin) return "Can't change admin status.";
|
if (attrs.admin) return "Can't change admin status.";
|
||||||
@@ -357,7 +345,7 @@ $(document).ready(function() {
|
|||||||
equal(lastError, "Can't change admin status.");
|
equal(lastError, "Can't change admin status.");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Model: isValid", 5, function() {
|
test("isValid", 5, function() {
|
||||||
var model = new Backbone.Model({valid: true});
|
var model = new Backbone.Model({valid: true});
|
||||||
model.validate = function(attrs) {
|
model.validate = function(attrs) {
|
||||||
if (!attrs.valid) return "invalid";
|
if (!attrs.valid) return "invalid";
|
||||||
@@ -369,13 +357,13 @@ $(document).ready(function() {
|
|||||||
equal(model.isValid(), false);
|
equal(model.isValid(), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Model: save", 2, function() {
|
test("save", 2, function() {
|
||||||
doc.save({title : "Henry V"});
|
doc.save({title : "Henry V"});
|
||||||
equal(lastRequest.method, 'update');
|
equal(this.syncArgs.method, 'update');
|
||||||
ok(_.isEqual(lastRequest.model, doc));
|
ok(_.isEqual(this.syncArgs.model, doc));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Model: save in positional style", 1, function() {
|
test("save in positional style", 1, function() {
|
||||||
var model = new Backbone.Model();
|
var model = new Backbone.Model();
|
||||||
model.sync = function(method, model, options) {
|
model.sync = function(method, model, options) {
|
||||||
options.success();
|
options.success();
|
||||||
@@ -386,29 +374,29 @@ $(document).ready(function() {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
test("Model: fetch", 2, function() {
|
test("fetch", 2, function() {
|
||||||
doc.fetch();
|
doc.fetch();
|
||||||
equal(lastRequest.method, 'read');
|
equal(this.syncArgs.method, 'read');
|
||||||
ok(_.isEqual(lastRequest.model, doc));
|
ok(_.isEqual(this.syncArgs.model, doc));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Model: destroy", 3, function() {
|
test("destroy", 3, function() {
|
||||||
doc.destroy();
|
doc.destroy();
|
||||||
equal(lastRequest.method, 'delete');
|
equal(this.syncArgs.method, 'delete');
|
||||||
ok(_.isEqual(lastRequest.model, doc));
|
ok(_.isEqual(this.syncArgs.model, doc));
|
||||||
|
|
||||||
var newModel = new Backbone.Model;
|
var newModel = new Backbone.Model;
|
||||||
equal(newModel.destroy(), false);
|
equal(newModel.destroy(), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Model: non-persisted destroy", 1, function() {
|
test("non-persisted destroy", 1, function() {
|
||||||
var a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3});
|
var a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3});
|
||||||
a.sync = function() { throw "should not be called"; };
|
a.sync = function() { throw "should not be called"; };
|
||||||
a.destroy();
|
a.destroy();
|
||||||
ok(true, "non-persisted model should not call sync");
|
ok(true, "non-persisted model should not call sync");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Model: validate", 7, function() {
|
test("validate", 7, function() {
|
||||||
var lastError;
|
var lastError;
|
||||||
var model = new Backbone.Model();
|
var model = new Backbone.Model();
|
||||||
model.validate = function(attrs) {
|
model.validate = function(attrs) {
|
||||||
@@ -429,7 +417,7 @@ $(document).ready(function() {
|
|||||||
equal(model.get('a'), 100);
|
equal(model.get('a'), 100);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Model: validate on unset and clear", 6, function() {
|
test("validate on unset and clear", 6, function() {
|
||||||
var error;
|
var error;
|
||||||
var model = new Backbone.Model({name: "One"});
|
var model = new Backbone.Model({name: "One"});
|
||||||
model.validate = function(attrs) {
|
model.validate = function(attrs) {
|
||||||
@@ -451,7 +439,7 @@ $(document).ready(function() {
|
|||||||
equal(model.get('name'), undefined);
|
equal(model.get('name'), undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Model: validate with error callback", 8, function() {
|
test("validate with error callback", 8, function() {
|
||||||
var lastError, boundError;
|
var lastError, boundError;
|
||||||
var model = new Backbone.Model();
|
var model = new Backbone.Model();
|
||||||
model.validate = function(attrs) {
|
model.validate = function(attrs) {
|
||||||
@@ -472,10 +460,10 @@ $(document).ready(function() {
|
|||||||
equal(result, false);
|
equal(result, false);
|
||||||
equal(model.get('a'), 100);
|
equal(model.get('a'), 100);
|
||||||
equal(lastError, "Can't change admin status.");
|
equal(lastError, "Can't change admin status.");
|
||||||
equal(boundError, undefined);
|
equal(boundError, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Model: defaults always extend attrs (#459)", 2, function() {
|
test("defaults always extend attrs (#459)", 2, function() {
|
||||||
var Defaulted = Backbone.Model.extend({
|
var Defaulted = Backbone.Model.extend({
|
||||||
defaults: {one: 1},
|
defaults: {one: 1},
|
||||||
initialize : function(attrs, opts) {
|
initialize : function(attrs, opts) {
|
||||||
@@ -486,7 +474,7 @@ $(document).ready(function() {
|
|||||||
var emptyattrs = new Defaulted();
|
var emptyattrs = new Defaulted();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Model: Inherit class properties", 6, function() {
|
test("Inherit class properties", 6, function() {
|
||||||
var Parent = Backbone.Model.extend({
|
var Parent = Backbone.Model.extend({
|
||||||
instancePropSame: function() {},
|
instancePropSame: function() {},
|
||||||
instancePropDiff: function() {}
|
instancePropDiff: function() {}
|
||||||
@@ -510,7 +498,7 @@ $(document).ready(function() {
|
|||||||
notEqual(Child.prototype.instancePropDiff, undefined);
|
notEqual(Child.prototype.instancePropDiff, undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Model: Nested change events don't clobber previous attributes", 4, function() {
|
test("Nested change events don't clobber previous attributes", 4, function() {
|
||||||
new Backbone.Model()
|
new Backbone.Model()
|
||||||
.on('change:state', function(model, newState) {
|
.on('change:state', function(model, newState) {
|
||||||
equal(model.previous('state'), undefined);
|
equal(model.previous('state'), undefined);
|
||||||
@@ -595,8 +583,9 @@ $(document).ready(function() {
|
|||||||
|
|
||||||
test("save with `wait` succeeds without `validate`", 1, function() {
|
test("save with `wait` succeeds without `validate`", 1, function() {
|
||||||
var model = new Backbone.Model();
|
var model = new Backbone.Model();
|
||||||
|
model.url = '/test';
|
||||||
model.save({x: 1}, {wait: true});
|
model.save({x: 1}, {wait: true});
|
||||||
ok(lastRequest.model === model);
|
ok(this.syncArgs.model === model);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("`hasChanged` for falsey keys", 2, function() {
|
test("`hasChanged` for falsey keys", 2, function() {
|
||||||
@@ -616,18 +605,20 @@ $(document).ready(function() {
|
|||||||
test("`save` with `wait` sends correct attributes", 5, function() {
|
test("`save` with `wait` sends correct attributes", 5, function() {
|
||||||
var changed = 0;
|
var changed = 0;
|
||||||
var model = new Backbone.Model({x: 1, y: 2});
|
var model = new Backbone.Model({x: 1, y: 2});
|
||||||
|
model.url = '/test';
|
||||||
model.on('change:x', function() { changed++; });
|
model.on('change:x', function() { changed++; });
|
||||||
model.save({x: 3}, {wait: true});
|
model.save({x: 3}, {wait: true});
|
||||||
deepEqual(JSON.parse(ajaxParams.data), {x: 3, y: 2});
|
deepEqual(JSON.parse(this.ajaxSettings.data), {x: 3, y: 2});
|
||||||
equal(model.get('x'), 1);
|
equal(model.get('x'), 1);
|
||||||
equal(changed, 0);
|
equal(changed, 0);
|
||||||
lastRequest.options.success({});
|
this.syncArgs.options.success({});
|
||||||
equal(model.get('x'), 3);
|
equal(model.get('x'), 3);
|
||||||
equal(changed, 1);
|
equal(changed, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("a failed `save` with `wait` doesn't leave attributes behind", 1, function() {
|
test("a failed `save` with `wait` doesn't leave attributes behind", 1, function() {
|
||||||
var model = new Backbone.Model;
|
var model = new Backbone.Model;
|
||||||
|
model.url = '/test';
|
||||||
model.save({x: 1}, {wait: true});
|
model.save({x: 1}, {wait: true});
|
||||||
equal(model.get('x'), void 0);
|
equal(model.get('x'), void 0);
|
||||||
});
|
});
|
||||||
@@ -644,6 +635,7 @@ $(document).ready(function() {
|
|||||||
|
|
||||||
test("save with wait validates attributes", 1, function() {
|
test("save with wait validates attributes", 1, function() {
|
||||||
var model = new Backbone.Model();
|
var model = new Backbone.Model();
|
||||||
|
model.url = '/test';
|
||||||
model.validate = function() { ok(true); };
|
model.validate = function() { ok(true); };
|
||||||
model.save({x: 1}, {wait: true});
|
model.save({x: 1}, {wait: true});
|
||||||
});
|
});
|
||||||
@@ -759,9 +751,9 @@ $(document).ready(function() {
|
|||||||
model.set({b: 2}, {silent: true});
|
model.set({b: 2}, {silent: true});
|
||||||
});
|
});
|
||||||
model.set({b: 0});
|
model.set({b: 0});
|
||||||
deepEqual(changes, [0, 1, 1]);
|
deepEqual(changes, [0, 1]);
|
||||||
model.change();
|
model.change();
|
||||||
deepEqual(changes, [0, 1, 1, 2, 1]);
|
deepEqual(changes, [0, 1, 2, 1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("nested set multiple times", 1, function() {
|
test("nested set multiple times", 1, function() {
|
||||||
@@ -776,24 +768,6 @@ $(document).ready(function() {
|
|||||||
model.set({a: true});
|
model.set({a: true});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Backbone.wrapError triggers `'error'`", 12, function() {
|
|
||||||
var resp = {};
|
|
||||||
var options = {};
|
|
||||||
var model = new Backbone.Model();
|
|
||||||
model.on('error', error);
|
|
||||||
var callback = Backbone.wrapError(null, model, options);
|
|
||||||
callback(model, resp);
|
|
||||||
callback(resp);
|
|
||||||
callback = Backbone.wrapError(error, model, options);
|
|
||||||
callback(model, resp);
|
|
||||||
callback(resp);
|
|
||||||
function error(_model, _resp, _options) {
|
|
||||||
ok(model === _model);
|
|
||||||
ok(resp === _resp);
|
|
||||||
ok(options === _options);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test("#1179 - isValid returns true in the absence of validate.", 1, function() {
|
test("#1179 - isValid returns true in the absence of validate.", 1, function() {
|
||||||
var model = new Backbone.Model();
|
var model = new Backbone.Model();
|
||||||
model.validate = null;
|
model.validate = null;
|
||||||
@@ -831,8 +805,9 @@ $(document).ready(function() {
|
|||||||
|
|
||||||
test("#1412 - Trigger 'sync' event.", 3, function() {
|
test("#1412 - Trigger 'sync' event.", 3, function() {
|
||||||
var model = new Backbone.Model({id: 1});
|
var model = new Backbone.Model({id: 1});
|
||||||
model.sync = function(method, model, options) { options.success(); };
|
model.url = '/test';
|
||||||
model.on('sync', function() { ok(true); });
|
model.on('sync', function(){ ok(true); });
|
||||||
|
Backbone.ajax = function(settings){ settings.success(); };
|
||||||
model.fetch();
|
model.fetch();
|
||||||
model.save();
|
model.save();
|
||||||
model.destroy();
|
model.destroy();
|
||||||
@@ -852,4 +827,74 @@ $(document).ready(function() {
|
|||||||
strictEqual(model.save(), false);
|
strictEqual(model.save(), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("#1377 - Save without attrs triggers 'error'.", 1, function() {
|
||||||
|
var Model = Backbone.Model.extend({
|
||||||
|
url: '/test/',
|
||||||
|
sync: function(method, model, options){ options.success(); },
|
||||||
|
validate: function(){ return 'invalid'; }
|
||||||
|
});
|
||||||
|
var model = new Model({id: 1});
|
||||||
|
model.on('error', function(){ ok(true); });
|
||||||
|
model.save();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("#1545 - `undefined` can be passed to a model constructor without coersion", function() {
|
||||||
|
var Model = Backbone.Model.extend({
|
||||||
|
defaults: { one: 1 },
|
||||||
|
initialize : function(attrs, opts) {
|
||||||
|
equal(attrs, undefined);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var emptyattrs = new Model();
|
||||||
|
var undefinedattrs = new Model(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
asyncTest("#1478 - Model `save` does not trigger change on unchanged attributes", 0, function() {
|
||||||
|
var Model = Backbone.Model.extend({
|
||||||
|
sync: function(method, model, options) {
|
||||||
|
setTimeout(function(){
|
||||||
|
options.success();
|
||||||
|
start();
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
new Model({x: true})
|
||||||
|
.on('change:x', function(){ ok(false); })
|
||||||
|
.save(null, {wait: true});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("#1664 - Changing from one value, silently to another, back to original does not trigger change.", 0, function() {
|
||||||
|
var model = new Backbone.Model({x:1});
|
||||||
|
model.on('change:x', function() { ok(false); });
|
||||||
|
model.set({x:2},{silent:true});
|
||||||
|
model.set({x:3},{silent:true});
|
||||||
|
model.set({x:1});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("#1664 - multiple silent changes nested inside a change event", 2, function() {
|
||||||
|
var changes = [];
|
||||||
|
var model = new Backbone.Model();
|
||||||
|
model.on('change', function() {
|
||||||
|
model.set({a:'c'}, {silent:true});
|
||||||
|
model.set({b:2}, {silent:true});
|
||||||
|
model.unset('c', {silent:true});
|
||||||
|
model.set({a:'a'}, {silent:true});
|
||||||
|
model.set({b:1}, {silent:true});
|
||||||
|
model.set({c:'item'}, {silent:true});
|
||||||
|
});
|
||||||
|
model.on('change:a change:b change:c', function(model, val) { changes.push(val); });
|
||||||
|
model.set({a:'a', b:1, c:'item'});
|
||||||
|
deepEqual(changes, ['a',1,'item']);
|
||||||
|
model.change();
|
||||||
|
deepEqual(changes, ['a',1,'item']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("#1791 - `attributes` is available for `parse`", function() {
|
||||||
|
var Model = Backbone.Model.extend({
|
||||||
|
parse: function() { this.has('a'); } // shouldn't throw an error
|
||||||
|
});
|
||||||
|
var model = new Model(null, {parse: true});
|
||||||
|
expect(0);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
2
vendor/backbone/test/noconflict.js
vendored
2
vendor/backbone/test/noconflict.js
vendored
@@ -2,7 +2,7 @@ $(document).ready(function() {
|
|||||||
|
|
||||||
module("Backbone.noConflict");
|
module("Backbone.noConflict");
|
||||||
|
|
||||||
test('Backbone.noConflict', 2, function() {
|
test('noConflict', 2, function() {
|
||||||
var noconflictBackbone = Backbone.noConflict();
|
var noconflictBackbone = Backbone.noConflict();
|
||||||
equal(window.Backbone, undefined, 'Returned window.Backbone');
|
equal(window.Backbone, undefined, 'Returned window.Backbone');
|
||||||
window.Backbone = noconflictBackbone;
|
window.Backbone = noconflictBackbone;
|
||||||
|
|||||||
237
vendor/backbone/test/router.js
vendored
237
vendor/backbone/test/router.js
vendored
@@ -20,9 +20,11 @@ $(document).ready(function() {
|
|||||||
_.extend(this, _.pick($('<a></a>', {href: href})[0],
|
_.extend(this, _.pick($('<a></a>', {href: href})[0],
|
||||||
'href',
|
'href',
|
||||||
'hash',
|
'hash',
|
||||||
|
'host',
|
||||||
'search',
|
'search',
|
||||||
'fragment',
|
'fragment',
|
||||||
'pathname'
|
'pathname',
|
||||||
|
'protocol'
|
||||||
));
|
));
|
||||||
// In IE, anchor.pathname does not contain a leading slash though
|
// In IE, anchor.pathname does not contain a leading slash though
|
||||||
// window.location.pathname does.
|
// window.location.pathname does.
|
||||||
@@ -39,7 +41,7 @@ $(document).ready(function() {
|
|||||||
|
|
||||||
setup: function() {
|
setup: function() {
|
||||||
location = new Location('http://example.com');
|
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});
|
router = new Router({testing: 101});
|
||||||
Backbone.history.interval = 9;
|
Backbone.history.interval = 9;
|
||||||
Backbone.history.start({pushState: false});
|
Backbone.history.start({pushState: false});
|
||||||
@@ -67,6 +69,7 @@ $(document).ready(function() {
|
|||||||
"contacts": "contacts",
|
"contacts": "contacts",
|
||||||
"contacts/new": "newContact",
|
"contacts/new": "newContact",
|
||||||
"contacts/:id": "loadContact",
|
"contacts/:id": "loadContact",
|
||||||
|
"optional(/:item)": "optionalItem",
|
||||||
"splat/*args/end": "splat",
|
"splat/*args/end": "splat",
|
||||||
"*first/complex-:part/*rest": "complex",
|
"*first/complex-:part/*rest": "complex",
|
||||||
":entity?*args": "query",
|
":entity?*args": "query",
|
||||||
@@ -103,6 +106,10 @@ $(document).ready(function() {
|
|||||||
this.contact = 'load';
|
this.contact = 'load';
|
||||||
},
|
},
|
||||||
|
|
||||||
|
optionalItem: function(arg){
|
||||||
|
this.arg = arg !== undefined ? arg : null;
|
||||||
|
},
|
||||||
|
|
||||||
splat : function(args) {
|
splat : function(args) {
|
||||||
this.args = args;
|
this.args = args;
|
||||||
},
|
},
|
||||||
@@ -124,11 +131,11 @@ $(document).ready(function() {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Router: initialize", 1, function() {
|
test("initialize", 1, function() {
|
||||||
equal(router.testing, 101);
|
equal(router.testing, 101);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Router: routes (simple)", 4, function() {
|
test("routes (simple)", 4, function() {
|
||||||
location.replace('http://example.com#search/news');
|
location.replace('http://example.com#search/news');
|
||||||
Backbone.history.checkUrl();
|
Backbone.history.checkUrl();
|
||||||
equal(router.query, 'news');
|
equal(router.query, 'news');
|
||||||
@@ -137,26 +144,26 @@ $(document).ready(function() {
|
|||||||
equal(lastArgs[0], 'news');
|
equal(lastArgs[0], 'news');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Router: routes (two part)", 2, function() {
|
test("routes (two part)", 2, function() {
|
||||||
location.replace('http://example.com#search/nyc/p10');
|
location.replace('http://example.com#search/nyc/p10');
|
||||||
Backbone.history.checkUrl();
|
Backbone.history.checkUrl();
|
||||||
equal(router.query, 'nyc');
|
equal(router.query, 'nyc');
|
||||||
equal(router.page, '10');
|
equal(router.page, '10');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Router: routes via navigate", 2, function() {
|
test("routes via navigate", 2, function() {
|
||||||
Backbone.history.navigate('search/manhattan/p20', {trigger: true});
|
Backbone.history.navigate('search/manhattan/p20', {trigger: true});
|
||||||
equal(router.query, 'manhattan');
|
equal(router.query, 'manhattan');
|
||||||
equal(router.page, '20');
|
equal(router.page, '20');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Router: routes via navigate for backwards-compatibility", 2, function() {
|
test("routes via navigate for backwards-compatibility", 2, function() {
|
||||||
Backbone.history.navigate('search/manhattan/p20', true);
|
Backbone.history.navigate('search/manhattan/p20', true);
|
||||||
equal(router.query, 'manhattan');
|
equal(router.query, 'manhattan');
|
||||||
equal(router.page, '20');
|
equal(router.page, '20');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Router: route precedence via navigate", 6, function(){
|
test("route precedence via navigate", 6, function(){
|
||||||
// check both 0.9.x and backwards-compatibility options
|
// check both 0.9.x and backwards-compatibility options
|
||||||
_.each([ { trigger: true }, true ], function( options ){
|
_.each([ { trigger: true }, true ], function( options ){
|
||||||
Backbone.history.navigate('contacts', options);
|
Backbone.history.navigate('contacts', options);
|
||||||
@@ -176,13 +183,13 @@ $(document).ready(function() {
|
|||||||
Backbone.history.navigate('/route');
|
Backbone.history.navigate('/route');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Router: use implicit callback if none provided", 1, function() {
|
test("use implicit callback if none provided", 1, function() {
|
||||||
router.count = 0;
|
router.count = 0;
|
||||||
router.navigate('implicit', {trigger: true});
|
router.navigate('implicit', {trigger: true});
|
||||||
equal(router.count, 1);
|
equal(router.count, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Router: routes via navigate with {replace: true}", 1, function() {
|
test("routes via navigate with {replace: true}", 1, function() {
|
||||||
location.replace('http://example.com#start_here');
|
location.replace('http://example.com#start_here');
|
||||||
Backbone.history.checkUrl();
|
Backbone.history.checkUrl();
|
||||||
location.replace = function(href) {
|
location.replace = function(href) {
|
||||||
@@ -191,13 +198,22 @@ $(document).ready(function() {
|
|||||||
Backbone.history.navigate('end_here', {replace: true});
|
Backbone.history.navigate('end_here', {replace: true});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Router: routes (splats)", 1, function() {
|
test("routes (splats)", 1, function() {
|
||||||
location.replace('http://example.com#splat/long-list/of/splatted_99args/end');
|
location.replace('http://example.com#splat/long-list/of/splatted_99args/end');
|
||||||
Backbone.history.checkUrl();
|
Backbone.history.checkUrl();
|
||||||
equal(router.args, 'long-list/of/splatted_99args');
|
equal(router.args, 'long-list/of/splatted_99args');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Router: routes (complex)", 3, function() {
|
test("routes (optional)", 2, function() {
|
||||||
|
location.replace('http://example.com#optional');
|
||||||
|
Backbone.history.checkUrl();
|
||||||
|
equal(router.arg, null);
|
||||||
|
location.replace('http://example.com#optional/thing');
|
||||||
|
Backbone.history.checkUrl();
|
||||||
|
equal(router.arg, 'thing');
|
||||||
|
});
|
||||||
|
|
||||||
|
test("routes (complex)", 3, function() {
|
||||||
location.replace('http://example.com#one/two/three/complex-part/four/five/six/seven');
|
location.replace('http://example.com#one/two/three/complex-part/four/five/six/seven');
|
||||||
Backbone.history.checkUrl();
|
Backbone.history.checkUrl();
|
||||||
equal(router.first, 'one/two/three');
|
equal(router.first, 'one/two/three');
|
||||||
@@ -205,7 +221,7 @@ $(document).ready(function() {
|
|||||||
equal(router.rest, 'four/five/six/seven');
|
equal(router.rest, 'four/five/six/seven');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Router: routes (query)", 5, function() {
|
test("routes (query)", 5, function() {
|
||||||
location.replace('http://example.com#mandel?a=b&c=d');
|
location.replace('http://example.com#mandel?a=b&c=d');
|
||||||
Backbone.history.checkUrl();
|
Backbone.history.checkUrl();
|
||||||
equal(router.entity, 'mandel');
|
equal(router.entity, 'mandel');
|
||||||
@@ -215,13 +231,13 @@ $(document).ready(function() {
|
|||||||
equal(lastArgs[1], 'a=b&c=d');
|
equal(lastArgs[1], 'a=b&c=d');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Router: routes (anything)", 1, function() {
|
test("routes (anything)", 1, function() {
|
||||||
location.replace('http://example.com#doesnt-match-a-route');
|
location.replace('http://example.com#doesnt-match-a-route');
|
||||||
Backbone.history.checkUrl();
|
Backbone.history.checkUrl();
|
||||||
equal(router.anything, 'doesnt-match-a-route');
|
equal(router.anything, 'doesnt-match-a-route');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Router: fires event when router doesn't have callback on it", 1, function() {
|
test("fires event when router doesn't have callback on it", 1, function() {
|
||||||
router.on("route:noCallback", function(){ ok(true); });
|
router.on("route:noCallback", function(){ ok(true); });
|
||||||
location.replace('http://example.com#noCallback');
|
location.replace('http://example.com#noCallback');
|
||||||
Backbone.history.checkUrl();
|
Backbone.history.checkUrl();
|
||||||
@@ -231,28 +247,25 @@ $(document).ready(function() {
|
|||||||
location.replace('http://example.com/root/foo');
|
location.replace('http://example.com/root/foo');
|
||||||
|
|
||||||
Backbone.history.stop();
|
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});
|
Backbone.history.start({root: '/root', hashChange: false, silent: true});
|
||||||
strictEqual(Backbone.history.getFragment(), 'foo');
|
strictEqual(Backbone.history.getFragment(), 'foo');
|
||||||
|
|
||||||
Backbone.history.stop();
|
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});
|
Backbone.history.start({root: '/root/', hashChange: false, silent: true});
|
||||||
strictEqual(Backbone.history.getFragment(), 'foo');
|
strictEqual(Backbone.history.getFragment(), 'foo');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("#1003 - History is started before navigate is called", 1, function() {
|
test("#1003 - History is started before navigate is called", 1, function() {
|
||||||
var history = new Backbone.History();
|
|
||||||
history.navigate = function(){
|
|
||||||
ok(Backbone.History.started);
|
|
||||||
};
|
|
||||||
Backbone.history.stop();
|
Backbone.history.stop();
|
||||||
history.start();
|
Backbone.history.navigate = function(){ ok(Backbone.History.started); };
|
||||||
|
Backbone.history.start();
|
||||||
// If this is not an old IE navigate will not be called.
|
// If this is not an old IE navigate will not be called.
|
||||||
if (!history.iframe) ok(true);
|
if (!Backbone.history.iframe) ok(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Router: route callback gets passed decoded values", 3, function() {
|
test("route callback gets passed decoded values", 3, function() {
|
||||||
var route = 'has%2Fslash/complex-has%23hash/has%20space';
|
var route = 'has%2Fslash/complex-has%23hash/has%20space';
|
||||||
Backbone.history.navigate(route, {trigger: true});
|
Backbone.history.navigate(route, {trigger: true});
|
||||||
equal(router.first, 'has/slash');
|
equal(router.first, 'has/slash');
|
||||||
@@ -260,7 +273,7 @@ $(document).ready(function() {
|
|||||||
equal(router.rest, 'has space');
|
equal(router.rest, 'has space');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Router: correctly handles URLs with % (#868)", 3, function() {
|
test("correctly handles URLs with % (#868)", 3, function() {
|
||||||
location.replace('http://example.com#search/fat%3A1.5%25');
|
location.replace('http://example.com#search/fat%3A1.5%25');
|
||||||
Backbone.history.checkUrl();
|
Backbone.history.checkUrl();
|
||||||
location.replace('http://example.com#search/fat');
|
location.replace('http://example.com#search/fat');
|
||||||
@@ -273,7 +286,7 @@ $(document).ready(function() {
|
|||||||
test("#1185 - Use pathname when hashChange is not wanted.", 1, function() {
|
test("#1185 - Use pathname when hashChange is not wanted.", 1, function() {
|
||||||
Backbone.history.stop();
|
Backbone.history.stop();
|
||||||
location.replace('http://example.com/path/name#hash');
|
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});
|
Backbone.history.start({hashChange: false});
|
||||||
var fragment = Backbone.history.getFragment();
|
var fragment = Backbone.history.getFragment();
|
||||||
strictEqual(fragment, location.pathname.replace(/^\//, ''));
|
strictEqual(fragment, location.pathname.replace(/^\//, ''));
|
||||||
@@ -282,7 +295,7 @@ $(document).ready(function() {
|
|||||||
test("#1206 - Strip leading slash before location.assign.", 1, function() {
|
test("#1206 - Strip leading slash before location.assign.", 1, function() {
|
||||||
Backbone.history.stop();
|
Backbone.history.stop();
|
||||||
location.replace('http://example.com/root/');
|
location.replace('http://example.com/root/');
|
||||||
Backbone.history = new Backbone.History({location: location});
|
Backbone.history = _.extend(new Backbone.History, {location: location});
|
||||||
Backbone.history.start({hashChange: false, root: '/root/'});
|
Backbone.history.start({hashChange: false, root: '/root/'});
|
||||||
location.assign = function(pathname) {
|
location.assign = function(pathname) {
|
||||||
strictEqual(pathname, '/root/fragment');
|
strictEqual(pathname, '/root/fragment');
|
||||||
@@ -293,7 +306,7 @@ $(document).ready(function() {
|
|||||||
test("#1387 - Root fragment without trailing slash.", 1, function() {
|
test("#1387 - Root fragment without trailing slash.", 1, function() {
|
||||||
Backbone.history.stop();
|
Backbone.history.stop();
|
||||||
location.replace('http://example.com/root');
|
location.replace('http://example.com/root');
|
||||||
Backbone.history = new Backbone.History({location: location});
|
Backbone.history = _.extend(new Backbone.History, {location: location});
|
||||||
Backbone.history.start({hashChange: false, root: '/root/', silent: true});
|
Backbone.history.start({hashChange: false, root: '/root/', silent: true});
|
||||||
strictEqual(Backbone.history.getFragment(), '');
|
strictEqual(Backbone.history.getFragment(), '');
|
||||||
});
|
});
|
||||||
@@ -301,7 +314,7 @@ $(document).ready(function() {
|
|||||||
test("#1366 - History does not prepend root to fragment.", 2, function() {
|
test("#1366 - History does not prepend root to fragment.", 2, function() {
|
||||||
Backbone.history.stop();
|
Backbone.history.stop();
|
||||||
location.replace('http://example.com/root/');
|
location.replace('http://example.com/root/');
|
||||||
Backbone.history = new Backbone.History({
|
Backbone.history = _.extend(new Backbone.History, {
|
||||||
location: location,
|
location: location,
|
||||||
history: {
|
history: {
|
||||||
pushState: function(state, title, url) {
|
pushState: function(state, title, url) {
|
||||||
@@ -318,4 +331,170 @@ $(document).ready(function() {
|
|||||||
strictEqual(Backbone.history.fragment, 'x');
|
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
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("#1695 - hashChange to pushState with search.", 1, function() {
|
||||||
|
Backbone.history.stop();
|
||||||
|
location.replace('http://example.com/root?a=b#x/y');
|
||||||
|
Backbone.history = _.extend(new Backbone.History, {
|
||||||
|
location: location,
|
||||||
|
history: {
|
||||||
|
pushState: function(){},
|
||||||
|
replaceState: function(state, title, url){
|
||||||
|
strictEqual(url, '/root/x/y?a=b');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Backbone.history.start({
|
||||||
|
root: 'root',
|
||||||
|
pushState: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("#1746 - Router allows empty route.", 1, function() {
|
||||||
|
var Router = Backbone.Router.extend({
|
||||||
|
routes: {'': 'empty'},
|
||||||
|
empty: function(){},
|
||||||
|
route: function(route){
|
||||||
|
strictEqual(route, '');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
new Router;
|
||||||
|
});
|
||||||
|
|
||||||
|
test("#1794 - Trailing space in fragments.", 1, function() {
|
||||||
|
var history = new Backbone.History;
|
||||||
|
strictEqual(history.getFragment('fragment '), 'fragment');
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
194
vendor/backbone/test/sync.js
vendored
194
vendor/backbone/test/sync.js
vendored
@@ -1,8 +1,5 @@
|
|||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
|
|
||||||
var ajax = Backbone.ajax;
|
|
||||||
var lastRequest = null;
|
|
||||||
|
|
||||||
var Library = Backbone.Collection.extend({
|
var Library = Backbone.Collection.extend({
|
||||||
url : function() { return '/library'; }
|
url : function() { return '/library'; }
|
||||||
});
|
});
|
||||||
@@ -14,132 +11,133 @@ $(document).ready(function() {
|
|||||||
length : 123
|
length : 123
|
||||||
};
|
};
|
||||||
|
|
||||||
module("Backbone.sync", {
|
module("Backbone.sync", _.extend(new Environment, {
|
||||||
|
|
||||||
setup : function() {
|
setup : function() {
|
||||||
library = new Library();
|
Environment.prototype.setup.apply(this, arguments);
|
||||||
Backbone.ajax = function(obj) {
|
library = new Library;
|
||||||
lastRequest = obj;
|
|
||||||
};
|
|
||||||
library.create(attrs, {wait: false});
|
library.create(attrs, {wait: false});
|
||||||
},
|
},
|
||||||
|
|
||||||
teardown: function() {
|
teardown: function() {
|
||||||
Backbone.ajax = ajax;
|
Environment.prototype.teardown.apply(this, arguments);
|
||||||
|
Backbone.emulateHTTP = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
}));
|
||||||
|
|
||||||
test("sync: read", 4, function() {
|
test("read", 4, function() {
|
||||||
library.fetch();
|
library.fetch();
|
||||||
equal(lastRequest.url, '/library');
|
equal(this.ajaxSettings.url, '/library');
|
||||||
equal(lastRequest.type, 'GET');
|
equal(this.ajaxSettings.type, 'GET');
|
||||||
equal(lastRequest.dataType, 'json');
|
equal(this.ajaxSettings.dataType, 'json');
|
||||||
ok(_.isEmpty(lastRequest.data));
|
ok(_.isEmpty(this.ajaxSettings.data));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("sync: passing data", 3, function() {
|
test("passing data", 3, function() {
|
||||||
library.fetch({data: {a: 'a', one: 1}});
|
library.fetch({data: {a: 'a', one: 1}});
|
||||||
equal(lastRequest.url, '/library');
|
equal(this.ajaxSettings.url, '/library');
|
||||||
equal(lastRequest.data.a, 'a');
|
equal(this.ajaxSettings.data.a, 'a');
|
||||||
equal(lastRequest.data.one, 1);
|
equal(this.ajaxSettings.data.one, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("sync: create", 6, function() {
|
test("create", 6, function() {
|
||||||
equal(lastRequest.url, '/library');
|
equal(this.ajaxSettings.url, '/library');
|
||||||
equal(lastRequest.type, 'POST');
|
equal(this.ajaxSettings.type, 'POST');
|
||||||
equal(lastRequest.dataType, 'json');
|
equal(this.ajaxSettings.dataType, 'json');
|
||||||
var data = JSON.parse(lastRequest.data);
|
var data = JSON.parse(this.ajaxSettings.data);
|
||||||
equal(data.title, 'The Tempest');
|
equal(data.title, 'The Tempest');
|
||||||
equal(data.author, 'Bill Shakespeare');
|
equal(data.author, 'Bill Shakespeare');
|
||||||
equal(data.length, 123);
|
equal(data.length, 123);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("sync: update", 7, function() {
|
test("update", 7, function() {
|
||||||
library.first().save({id: '1-the-tempest', author: 'William Shakespeare'});
|
library.first().save({id: '1-the-tempest', author: 'William Shakespeare'});
|
||||||
equal(lastRequest.url, '/library/1-the-tempest');
|
equal(this.ajaxSettings.url, '/library/1-the-tempest');
|
||||||
equal(lastRequest.type, 'PUT');
|
equal(this.ajaxSettings.type, 'PUT');
|
||||||
equal(lastRequest.dataType, 'json');
|
equal(this.ajaxSettings.dataType, 'json');
|
||||||
var data = JSON.parse(lastRequest.data);
|
var data = JSON.parse(this.ajaxSettings.data);
|
||||||
equal(data.id, '1-the-tempest');
|
equal(data.id, '1-the-tempest');
|
||||||
equal(data.title, 'The Tempest');
|
equal(data.title, 'The Tempest');
|
||||||
equal(data.author, 'William Shakespeare');
|
equal(data.author, 'William Shakespeare');
|
||||||
equal(data.length, 123);
|
equal(data.length, 123);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("sync: update with emulateHTTP and emulateJSON", 7, function() {
|
test("update with emulateHTTP and emulateJSON", 7, function() {
|
||||||
Backbone.emulateHTTP = Backbone.emulateJSON = true;
|
library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}, {
|
||||||
library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'});
|
emulateHTTP: true,
|
||||||
equal(lastRequest.url, '/library/2-the-tempest');
|
emulateJSON: true
|
||||||
equal(lastRequest.type, 'POST');
|
});
|
||||||
equal(lastRequest.dataType, 'json');
|
equal(this.ajaxSettings.url, '/library/2-the-tempest');
|
||||||
equal(lastRequest.data._method, 'PUT');
|
equal(this.ajaxSettings.type, 'POST');
|
||||||
var data = JSON.parse(lastRequest.data.model);
|
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.id, '2-the-tempest');
|
||||||
equal(data.author, 'Tim Shakespeare');
|
equal(data.author, 'Tim Shakespeare');
|
||||||
equal(data.length, 123);
|
equal(data.length, 123);
|
||||||
Backbone.emulateHTTP = Backbone.emulateJSON = false;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
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'}, {
|
||||||
library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'});
|
emulateHTTP: true
|
||||||
equal(lastRequest.url, '/library/2-the-tempest');
|
});
|
||||||
equal(lastRequest.type, 'POST');
|
equal(this.ajaxSettings.url, '/library/2-the-tempest');
|
||||||
equal(lastRequest.contentType, 'application/json');
|
equal(this.ajaxSettings.type, 'POST');
|
||||||
var data = JSON.parse(lastRequest.data);
|
equal(this.ajaxSettings.contentType, 'application/json');
|
||||||
|
var data = JSON.parse(this.ajaxSettings.data);
|
||||||
equal(data.id, '2-the-tempest');
|
equal(data.id, '2-the-tempest');
|
||||||
equal(data.author, 'Tim Shakespeare');
|
equal(data.author, 'Tim Shakespeare');
|
||||||
equal(data.length, 123);
|
equal(data.length, 123);
|
||||||
Backbone.emulateHTTP = false;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
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'}, {
|
||||||
library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'});
|
emulateJSON: true
|
||||||
equal(lastRequest.url, '/library/2-the-tempest');
|
});
|
||||||
equal(lastRequest.type, 'PUT');
|
equal(this.ajaxSettings.url, '/library/2-the-tempest');
|
||||||
equal(lastRequest.contentType, 'application/x-www-form-urlencoded');
|
equal(this.ajaxSettings.type, 'PUT');
|
||||||
var data = JSON.parse(lastRequest.data.model);
|
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.id, '2-the-tempest');
|
||||||
equal(data.author, 'Tim Shakespeare');
|
equal(data.author, 'Tim Shakespeare');
|
||||||
equal(data.length, 123);
|
equal(data.length, 123);
|
||||||
Backbone.emulateJSON = false;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("sync: read model", 3, function() {
|
test("read model", 3, function() {
|
||||||
library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'});
|
library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'});
|
||||||
library.first().fetch();
|
library.first().fetch();
|
||||||
equal(lastRequest.url, '/library/2-the-tempest');
|
equal(this.ajaxSettings.url, '/library/2-the-tempest');
|
||||||
equal(lastRequest.type, 'GET');
|
equal(this.ajaxSettings.type, 'GET');
|
||||||
ok(_.isEmpty(lastRequest.data));
|
ok(_.isEmpty(this.ajaxSettings.data));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("sync: destroy", 3, function() {
|
test("destroy", 3, function() {
|
||||||
library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'});
|
library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'});
|
||||||
library.first().destroy({wait: true});
|
library.first().destroy({wait: true});
|
||||||
equal(lastRequest.url, '/library/2-the-tempest');
|
equal(this.ajaxSettings.url, '/library/2-the-tempest');
|
||||||
equal(lastRequest.type, 'DELETE');
|
equal(this.ajaxSettings.type, 'DELETE');
|
||||||
equal(lastRequest.data, null);
|
equal(this.ajaxSettings.data, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("sync: destroy with emulateHTTP", 3, function() {
|
test("destroy with emulateHTTP", 3, function() {
|
||||||
library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'});
|
library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'});
|
||||||
Backbone.emulateHTTP = Backbone.emulateJSON = true;
|
library.first().destroy({
|
||||||
library.first().destroy();
|
emulateHTTP: true,
|
||||||
equal(lastRequest.url, '/library/2-the-tempest');
|
emulateJSON: true
|
||||||
equal(lastRequest.type, 'POST');
|
});
|
||||||
equal(JSON.stringify(lastRequest.data), '{"_method":"DELETE"}');
|
equal(this.ajaxSettings.url, '/library/2-the-tempest');
|
||||||
Backbone.emulateHTTP = Backbone.emulateJSON = false;
|
equal(this.ajaxSettings.type, 'POST');
|
||||||
|
equal(JSON.stringify(this.ajaxSettings.data), '{"_method":"DELETE"}');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("sync: urlError", 2, function() {
|
test("urlError", 2, function() {
|
||||||
var model = new Backbone.Model();
|
var model = new Backbone.Model();
|
||||||
raises(function() {
|
raises(function() {
|
||||||
model.fetch();
|
model.fetch();
|
||||||
});
|
});
|
||||||
model.fetch({url: '/one/two'});
|
model.fetch({url: '/one/two'});
|
||||||
equal(lastRequest.url, '/one/two');
|
equal(this.ajaxSettings.url, '/one/two');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("#1052 - `options` is optional.", 0, function() {
|
test("#1052 - `options` is optional.", 0, function() {
|
||||||
@@ -157,4 +155,58 @@ $(document).ready(function() {
|
|||||||
Backbone.sync('create', model);
|
Backbone.sync('create', model);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Call provided error callback on error.", 1, function() {
|
||||||
|
var model = new Backbone.Model;
|
||||||
|
model.url = '/test';
|
||||||
|
Backbone.sync('read', model, {
|
||||||
|
error: function() { ok(true); }
|
||||||
|
});
|
||||||
|
this.ajaxSettings.error();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Use Backbone.emulateHTTP as default.', 2, function() {
|
||||||
|
var model = new Backbone.Model;
|
||||||
|
model.url = '/test';
|
||||||
|
|
||||||
|
Backbone.emulateHTTP = true;
|
||||||
|
model.sync('create', model);
|
||||||
|
strictEqual(this.ajaxSettings.emulateHTTP, true);
|
||||||
|
|
||||||
|
Backbone.emulateHTTP = false;
|
||||||
|
model.sync('create', model);
|
||||||
|
strictEqual(this.ajaxSettings.emulateHTTP, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Use Backbone.emulateJSON as default.', 2, function() {
|
||||||
|
var model = new Backbone.Model;
|
||||||
|
model.url = '/test';
|
||||||
|
|
||||||
|
Backbone.emulateJSON = true;
|
||||||
|
model.sync('create', model);
|
||||||
|
strictEqual(this.ajaxSettings.emulateJSON, true);
|
||||||
|
|
||||||
|
Backbone.emulateJSON = false;
|
||||||
|
model.sync('create', model);
|
||||||
|
strictEqual(this.ajaxSettings.emulateJSON, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("#1756 - Call user provided beforeSend function.", 4, function() {
|
||||||
|
Backbone.emulateHTTP = true;
|
||||||
|
var model = new Backbone.Model;
|
||||||
|
model.url = '/test';
|
||||||
|
var xhr = {
|
||||||
|
setRequestHeader: function(header, value) {
|
||||||
|
strictEqual(header, 'X-HTTP-Method-Override');
|
||||||
|
strictEqual(value, 'DELETE');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
model.sync('delete', model, {
|
||||||
|
beforeSend: function(_xhr) {
|
||||||
|
ok(_xhr === xhr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
strictEqual(this.ajaxSettings.beforeSend(xhr), false);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
9266
vendor/backbone/test/vendor/jquery-1.7.1.js
vendored
9266
vendor/backbone/test/vendor/jquery-1.7.1.js
vendored
File diff suppressed because it is too large
Load Diff
649
vendor/backbone/test/vendor/jslitmus.js
vendored
649
vendor/backbone/test/vendor/jslitmus.js
vendored
@@ -1,649 +0,0 @@
|
|||||||
// JSLitmus.js
|
|
||||||
//
|
|
||||||
// Copyright (c) 2010, Robert Kieffer, http://broofa.com
|
|
||||||
// Available under MIT license (http://en.wikipedia.org/wiki/MIT_License)
|
|
||||||
|
|
||||||
(function() {
|
|
||||||
// Private methods and state
|
|
||||||
|
|
||||||
// Get platform info but don't go crazy trying to recognize everything
|
|
||||||
// that's out there. This is just for the major platforms and OSes.
|
|
||||||
var platform = 'unknown platform', ua = navigator.userAgent;
|
|
||||||
|
|
||||||
// Detect OS
|
|
||||||
var oses = ['Windows','iPhone OS','(Intel |PPC )?Mac OS X','Linux'].join('|');
|
|
||||||
var pOS = new RegExp('((' + oses + ') [^ \);]*)').test(ua) ? RegExp.$1 : null;
|
|
||||||
if (!pOS) pOS = new RegExp('((' + oses + ')[^ \);]*)').test(ua) ? RegExp.$1 : null;
|
|
||||||
|
|
||||||
// Detect browser
|
|
||||||
var pName = /(Chrome|MSIE|Safari|Opera|Firefox)/.test(ua) ? RegExp.$1 : null;
|
|
||||||
|
|
||||||
// Detect version
|
|
||||||
var vre = new RegExp('(Version|' + pName + ')[ \/]([^ ;]*)');
|
|
||||||
var pVersion = (pName && vre.test(ua)) ? RegExp.$2 : null;
|
|
||||||
var platform = (pOS && pName && pVersion) ? pName + ' ' + pVersion + ' on ' + pOS : 'unknown platform';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A smattering of methods that are needed to implement the JSLitmus testbed.
|
|
||||||
*/
|
|
||||||
var jsl = {
|
|
||||||
/**
|
|
||||||
* Enhanced version of escape()
|
|
||||||
*/
|
|
||||||
escape: function(s) {
|
|
||||||
s = s.replace(/,/g, '\\,');
|
|
||||||
s = escape(s);
|
|
||||||
s = s.replace(/\+/g, '%2b');
|
|
||||||
s = s.replace(/ /g, '+');
|
|
||||||
return s;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get an element by ID.
|
|
||||||
*/
|
|
||||||
$: function(id) {
|
|
||||||
return document.getElementById(id);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Null function
|
|
||||||
*/
|
|
||||||
F: function() {},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the status shown in the UI
|
|
||||||
*/
|
|
||||||
status: function(msg) {
|
|
||||||
var el = jsl.$('jsl_status');
|
|
||||||
if (el) el.innerHTML = msg || '';
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a number to an abbreviated string like, "15K" or "10M"
|
|
||||||
*/
|
|
||||||
toLabel: function(n) {
|
|
||||||
if (n == Infinity) {
|
|
||||||
return 'Infinity';
|
|
||||||
} else if (n > 1e9) {
|
|
||||||
n = Math.round(n/1e8);
|
|
||||||
return n/10 + 'B';
|
|
||||||
} else if (n > 1e6) {
|
|
||||||
n = Math.round(n/1e5);
|
|
||||||
return n/10 + 'M';
|
|
||||||
} else if (n > 1e3) {
|
|
||||||
n = Math.round(n/1e2);
|
|
||||||
return n/10 + 'K';
|
|
||||||
}
|
|
||||||
return n;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy properties from src to dst
|
|
||||||
*/
|
|
||||||
extend: function(dst, src) {
|
|
||||||
for (var k in src) dst[k] = src[k]; return dst;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Like Array.join(), but for the key-value pairs in an object
|
|
||||||
*/
|
|
||||||
join: function(o, delimit1, delimit2) {
|
|
||||||
if (o.join) return o.join(delimit1); // If it's an array
|
|
||||||
var pairs = [];
|
|
||||||
for (var k in o) pairs.push(k + delimit1 + o[k]);
|
|
||||||
return pairs.join(delimit2);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Array#indexOf isn't supported in IE, so we use this as a cross-browser solution
|
|
||||||
*/
|
|
||||||
indexOf: function(arr, o) {
|
|
||||||
if (arr.indexOf) return arr.indexOf(o);
|
|
||||||
for (var i = 0; i < this.length; i++) if (arr[i] === o) return i;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test manages a single test (created with
|
|
||||||
* JSLitmus.test())
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var Test = function (name, f) {
|
|
||||||
if (!f) throw new Error('Undefined test function');
|
|
||||||
if (!/function[^\(]*\(([^,\)]*)/.test(f.toString())) {
|
|
||||||
throw new Error('"' + name + '" test: Test is not a valid Function object');
|
|
||||||
}
|
|
||||||
this.loopArg = RegExp.$1;
|
|
||||||
this.name = name;
|
|
||||||
this.f = f;
|
|
||||||
};
|
|
||||||
|
|
||||||
jsl.extend(Test, /** @lends Test */ {
|
|
||||||
/** Calibration tests for establishing iteration loop overhead */
|
|
||||||
CALIBRATIONS: [
|
|
||||||
new Test('calibrating loop', function(count) {while (count--);}),
|
|
||||||
new Test('calibrating function', jsl.F)
|
|
||||||
],
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Run calibration tests. Returns true if calibrations are not yet
|
|
||||||
* complete (in which case calling code should run the tests yet again).
|
|
||||||
* onCalibrated - Callback to invoke when calibrations have finished
|
|
||||||
*/
|
|
||||||
calibrate: function(onCalibrated) {
|
|
||||||
for (var i = 0; i < Test.CALIBRATIONS.length; i++) {
|
|
||||||
var cal = Test.CALIBRATIONS[i];
|
|
||||||
if (cal.running) return true;
|
|
||||||
if (!cal.count) {
|
|
||||||
cal.isCalibration = true;
|
|
||||||
cal.onStop = onCalibrated;
|
|
||||||
//cal.MIN_TIME = .1; // Do calibrations quickly
|
|
||||||
cal.run(2e4);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
jsl.extend(Test.prototype, {/** @lends Test.prototype */
|
|
||||||
/** Initial number of iterations */
|
|
||||||
INIT_COUNT: 10,
|
|
||||||
/** Max iterations allowed (i.e. used to detect bad looping functions) */
|
|
||||||
MAX_COUNT: 1e9,
|
|
||||||
/** Minimum time a test should take to get valid results (secs) */
|
|
||||||
MIN_TIME: .5,
|
|
||||||
|
|
||||||
/** Callback invoked when test state changes */
|
|
||||||
onChange: jsl.F,
|
|
||||||
|
|
||||||
/** Callback invoked when test is finished */
|
|
||||||
onStop: jsl.F,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset test state
|
|
||||||
*/
|
|
||||||
reset: function() {
|
|
||||||
delete this.count;
|
|
||||||
delete this.time;
|
|
||||||
delete this.running;
|
|
||||||
delete this.error;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Run the test (in a timeout). We use a timeout to make sure the browser
|
|
||||||
* has a chance to finish rendering any UI changes we've made, like
|
|
||||||
* updating the status message.
|
|
||||||
*/
|
|
||||||
run: function(count) {
|
|
||||||
count = count || this.INIT_COUNT;
|
|
||||||
jsl.status(this.name + ' x ' + count);
|
|
||||||
this.running = true;
|
|
||||||
var me = this;
|
|
||||||
setTimeout(function() {me._run(count);}, 200);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The nuts and bolts code that actually runs a test
|
|
||||||
*/
|
|
||||||
_run: function(count) {
|
|
||||||
var me = this;
|
|
||||||
|
|
||||||
// Make sure calibration tests have run
|
|
||||||
if (!me.isCalibration && Test.calibrate(function() {me.run(count);})) return;
|
|
||||||
this.error = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
var start, f = this.f, now, i = count;
|
|
||||||
|
|
||||||
// Start the timer
|
|
||||||
start = new Date();
|
|
||||||
|
|
||||||
// Now for the money shot. If this is a looping function ...
|
|
||||||
if (this.loopArg) {
|
|
||||||
// ... let it do the iteration itself
|
|
||||||
f(count);
|
|
||||||
} else {
|
|
||||||
// ... otherwise do the iteration for it
|
|
||||||
while (i--) f();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get time test took (in secs)
|
|
||||||
this.time = Math.max(1,new Date() - start)/1000;
|
|
||||||
|
|
||||||
// Store iteration count and per-operation time taken
|
|
||||||
this.count = count;
|
|
||||||
this.period = this.time/count;
|
|
||||||
|
|
||||||
// Do we need to do another run?
|
|
||||||
this.running = this.time <= this.MIN_TIME;
|
|
||||||
|
|
||||||
// ... if so, compute how many times we should iterate
|
|
||||||
if (this.running) {
|
|
||||||
// Bump the count to the nearest power of 2
|
|
||||||
var x = this.MIN_TIME/this.time;
|
|
||||||
var pow = Math.pow(2, Math.max(1, Math.ceil(Math.log(x)/Math.log(2))));
|
|
||||||
count *= pow;
|
|
||||||
if (count > this.MAX_COUNT) {
|
|
||||||
throw new Error('Max count exceeded. If this test uses a looping function, make sure the iteration loop is working properly.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// Exceptions are caught and displayed in the test UI
|
|
||||||
this.reset();
|
|
||||||
this.error = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Figure out what to do next
|
|
||||||
if (this.running) {
|
|
||||||
me.run(count);
|
|
||||||
} else {
|
|
||||||
jsl.status('');
|
|
||||||
me.onStop(me);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finish up
|
|
||||||
this.onChange(this);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the number of operations per second for this test.
|
|
||||||
*
|
|
||||||
* @param normalize if true, iteration loop overhead taken into account
|
|
||||||
*/
|
|
||||||
getHz: function(/**Boolean*/ normalize) {
|
|
||||||
var p = this.period;
|
|
||||||
|
|
||||||
// Adjust period based on the calibration test time
|
|
||||||
if (normalize && !this.isCalibration) {
|
|
||||||
var cal = Test.CALIBRATIONS[this.loopArg ? 0 : 1];
|
|
||||||
|
|
||||||
// If the period is within 20% of the calibration time, then zero the
|
|
||||||
// it out
|
|
||||||
p = p < cal.period*1.2 ? 0 : p - cal.period;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Math.round(1/p);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a friendly string describing the test
|
|
||||||
*/
|
|
||||||
toString: function() {
|
|
||||||
return this.name + ' - ' + this.time/this.count + ' secs';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// CSS we need for the UI
|
|
||||||
var STYLESHEET = '<style> \
|
|
||||||
#jslitmus {font-family:sans-serif; font-size: 12px;} \
|
|
||||||
#jslitmus a {text-decoration: none;} \
|
|
||||||
#jslitmus a:hover {text-decoration: underline;} \
|
|
||||||
#jsl_status { \
|
|
||||||
margin-top: 10px; \
|
|
||||||
font-size: 10px; \
|
|
||||||
color: #888; \
|
|
||||||
} \
|
|
||||||
A IMG {border:none} \
|
|
||||||
#test_results { \
|
|
||||||
margin-top: 10px; \
|
|
||||||
font-size: 12px; \
|
|
||||||
font-family: sans-serif; \
|
|
||||||
border-collapse: collapse; \
|
|
||||||
border-spacing: 0px; \
|
|
||||||
} \
|
|
||||||
#test_results th, #test_results td { \
|
|
||||||
border: solid 1px #ccc; \
|
|
||||||
vertical-align: top; \
|
|
||||||
padding: 3px; \
|
|
||||||
} \
|
|
||||||
#test_results th { \
|
|
||||||
vertical-align: bottom; \
|
|
||||||
background-color: #ccc; \
|
|
||||||
padding: 1px; \
|
|
||||||
font-size: 10px; \
|
|
||||||
} \
|
|
||||||
#test_results #test_platform { \
|
|
||||||
color: #444; \
|
|
||||||
text-align:center; \
|
|
||||||
} \
|
|
||||||
#test_results .test_row { \
|
|
||||||
color: #006; \
|
|
||||||
cursor: pointer; \
|
|
||||||
} \
|
|
||||||
#test_results .test_nonlooping { \
|
|
||||||
border-left-style: dotted; \
|
|
||||||
border-left-width: 2px; \
|
|
||||||
} \
|
|
||||||
#test_results .test_looping { \
|
|
||||||
border-left-style: solid; \
|
|
||||||
border-left-width: 2px; \
|
|
||||||
} \
|
|
||||||
#test_results .test_name {white-space: nowrap;} \
|
|
||||||
#test_results .test_pending { \
|
|
||||||
} \
|
|
||||||
#test_results .test_running { \
|
|
||||||
font-style: italic; \
|
|
||||||
} \
|
|
||||||
#test_results .test_done {} \
|
|
||||||
#test_results .test_done { \
|
|
||||||
text-align: right; \
|
|
||||||
font-family: monospace; \
|
|
||||||
} \
|
|
||||||
#test_results .test_error {color: #600;} \
|
|
||||||
#test_results .test_error .error_head {font-weight:bold;} \
|
|
||||||
#test_results .test_error .error_body {font-size:85%;} \
|
|
||||||
#test_results .test_row:hover td { \
|
|
||||||
background-color: #ffc; \
|
|
||||||
text-decoration: underline; \
|
|
||||||
} \
|
|
||||||
#chart { \
|
|
||||||
margin: 10px 0px; \
|
|
||||||
width: 250px; \
|
|
||||||
} \
|
|
||||||
#chart img { \
|
|
||||||
border: solid 1px #ccc; \
|
|
||||||
margin-bottom: 5px; \
|
|
||||||
} \
|
|
||||||
#chart #tiny_url { \
|
|
||||||
height: 40px; \
|
|
||||||
width: 250px; \
|
|
||||||
} \
|
|
||||||
#jslitmus_credit { \
|
|
||||||
font-size: 10px; \
|
|
||||||
color: #888; \
|
|
||||||
margin-top: 8px; \
|
|
||||||
} \
|
|
||||||
</style>';
|
|
||||||
|
|
||||||
// HTML markup for the UI
|
|
||||||
var MARKUP = '<div id="jslitmus"> \
|
|
||||||
<button onclick="JSLitmus.runAll(event)">Run Tests</button> \
|
|
||||||
<button id="stop_button" disabled="disabled" onclick="JSLitmus.stop()">Stop Tests</button> \
|
|
||||||
<br \> \
|
|
||||||
<br \> \
|
|
||||||
<input type="checkbox" style="vertical-align: middle" id="test_normalize" checked="checked" onchange="JSLitmus.renderAll()""> Normalize results \
|
|
||||||
<table id="test_results"> \
|
|
||||||
<colgroup> \
|
|
||||||
<col /> \
|
|
||||||
<col width="100" /> \
|
|
||||||
</colgroup> \
|
|
||||||
<tr><th id="test_platform" colspan="2">' + platform + '</th></tr> \
|
|
||||||
<tr><th>Test</th><th>Ops/sec</th></tr> \
|
|
||||||
<tr id="test_row_template" class="test_row" style="display:none"> \
|
|
||||||
<td class="test_name"></td> \
|
|
||||||
<td class="test_result">Ready</td> \
|
|
||||||
</tr> \
|
|
||||||
</table> \
|
|
||||||
<div id="jsl_status"></div> \
|
|
||||||
<div id="chart" style="display:none"> \
|
|
||||||
<a id="chart_link" target="_blank"><img id="chart_image"></a> \
|
|
||||||
TinyURL (for chart): \
|
|
||||||
<iframe id="tiny_url" frameBorder="0" scrolling="no" src=""></iframe> \
|
|
||||||
</div> \
|
|
||||||
<a id="jslitmus_credit" title="JSLitmus home page" href="http://code.google.com/p/jslitmus" target="_blank">Powered by JSLitmus</a> \
|
|
||||||
</div>';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The public API for creating and running tests
|
|
||||||
*/
|
|
||||||
window.JSLitmus = {
|
|
||||||
/** The list of all tests that have been registered with JSLitmus.test */
|
|
||||||
_tests: [],
|
|
||||||
/** The queue of tests that need to be run */
|
|
||||||
_queue: [],
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The parsed query parameters the current page URL. This is provided as a
|
|
||||||
* convenience for test functions - it's not used by JSLitmus proper
|
|
||||||
*/
|
|
||||||
params: {},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize
|
|
||||||
*/
|
|
||||||
_init: function() {
|
|
||||||
// Parse query params into JSLitmus.params[] hash
|
|
||||||
var match = (location + '').match(/([^?#]*)(#.*)?$/);
|
|
||||||
if (match) {
|
|
||||||
var pairs = match[1].split('&');
|
|
||||||
for (var i = 0; i < pairs.length; i++) {
|
|
||||||
var pair = pairs[i].split('=');
|
|
||||||
if (pair.length > 1) {
|
|
||||||
var key = pair.shift();
|
|
||||||
var value = pair.length > 1 ? pair.join('=') : pair[0];
|
|
||||||
this.params[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write out the stylesheet. We have to do this here because IE
|
|
||||||
// doesn't honor sheets written after the document has loaded.
|
|
||||||
document.write(STYLESHEET);
|
|
||||||
|
|
||||||
// Setup the rest of the UI once the document is loaded
|
|
||||||
if (window.addEventListener) {
|
|
||||||
window.addEventListener('load', this._setup, false);
|
|
||||||
} else if (document.addEventListener) {
|
|
||||||
document.addEventListener('load', this._setup, false);
|
|
||||||
} else if (window.attachEvent) {
|
|
||||||
window.attachEvent('onload', this._setup);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set up the UI
|
|
||||||
*/
|
|
||||||
_setup: function() {
|
|
||||||
var el = jsl.$('jslitmus_container');
|
|
||||||
if (!el) document.body.appendChild(el = document.createElement('div'));
|
|
||||||
|
|
||||||
el.innerHTML = MARKUP;
|
|
||||||
|
|
||||||
// Render the UI for all our tests
|
|
||||||
for (var i=0; i < JSLitmus._tests.length; i++)
|
|
||||||
JSLitmus.renderTest(JSLitmus._tests[i]);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* (Re)render all the test results
|
|
||||||
*/
|
|
||||||
renderAll: function() {
|
|
||||||
for (var i = 0; i < JSLitmus._tests.length; i++)
|
|
||||||
JSLitmus.renderTest(JSLitmus._tests[i]);
|
|
||||||
JSLitmus.renderChart();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* (Re)render the chart graphics
|
|
||||||
*/
|
|
||||||
renderChart: function() {
|
|
||||||
var url = JSLitmus.chartUrl();
|
|
||||||
jsl.$('chart_link').href = url;
|
|
||||||
jsl.$('chart_image').src = url;
|
|
||||||
jsl.$('chart').style.display = '';
|
|
||||||
|
|
||||||
// Update the tiny URL
|
|
||||||
jsl.$('tiny_url').src = 'http://tinyurl.com/api-create.php?url='+escape(url);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* (Re)render the results for a specific test
|
|
||||||
*/
|
|
||||||
renderTest: function(test) {
|
|
||||||
// Make a new row if needed
|
|
||||||
if (!test._row) {
|
|
||||||
var trow = jsl.$('test_row_template');
|
|
||||||
if (!trow) return;
|
|
||||||
|
|
||||||
test._row = trow.cloneNode(true);
|
|
||||||
test._row.style.display = '';
|
|
||||||
test._row.id = '';
|
|
||||||
test._row.onclick = function() {JSLitmus._queueTest(test);};
|
|
||||||
test._row.title = 'Run ' + test.name + ' test';
|
|
||||||
trow.parentNode.appendChild(test._row);
|
|
||||||
test._row.cells[0].innerHTML = test.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
var cell = test._row.cells[1];
|
|
||||||
var cns = [test.loopArg ? 'test_looping' : 'test_nonlooping'];
|
|
||||||
|
|
||||||
if (test.error) {
|
|
||||||
cns.push('test_error');
|
|
||||||
cell.innerHTML =
|
|
||||||
'<div class="error_head">' + test.error + '</div>' +
|
|
||||||
'<ul class="error_body"><li>' +
|
|
||||||
jsl.join(test.error, ': ', '</li><li>') +
|
|
||||||
'</li></ul>';
|
|
||||||
} else {
|
|
||||||
if (test.running) {
|
|
||||||
cns.push('test_running');
|
|
||||||
cell.innerHTML = 'running';
|
|
||||||
} else if (jsl.indexOf(JSLitmus._queue, test) >= 0) {
|
|
||||||
cns.push('test_pending');
|
|
||||||
cell.innerHTML = 'pending';
|
|
||||||
} else if (test.count) {
|
|
||||||
cns.push('test_done');
|
|
||||||
var hz = test.getHz(jsl.$('test_normalize').checked);
|
|
||||||
cell.innerHTML = hz != Infinity ? hz : '∞';
|
|
||||||
cell.title = 'Looped ' + test.count + ' times in ' + test.time + ' seconds';
|
|
||||||
} else {
|
|
||||||
cell.innerHTML = 'ready';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cell.className = cns.join(' ');
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new test
|
|
||||||
*/
|
|
||||||
test: function(name, f) {
|
|
||||||
// Create the Test object
|
|
||||||
var test = new Test(name, f);
|
|
||||||
JSLitmus._tests.push(test);
|
|
||||||
|
|
||||||
// Re-render if the test state changes
|
|
||||||
test.onChange = JSLitmus.renderTest;
|
|
||||||
|
|
||||||
// Run the next test if this one finished
|
|
||||||
test.onStop = function(test) {
|
|
||||||
if (JSLitmus.onTestFinish) JSLitmus.onTestFinish(test);
|
|
||||||
JSLitmus.currentTest = null;
|
|
||||||
JSLitmus._nextTest();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Render the new test
|
|
||||||
this.renderTest(test);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add all tests to the run queue
|
|
||||||
*/
|
|
||||||
runAll: function(e) {
|
|
||||||
e = e || window.event;
|
|
||||||
var reverse = e && e.shiftKey, len = JSLitmus._tests.length;
|
|
||||||
for (var i = 0; i < len; i++) {
|
|
||||||
JSLitmus._queueTest(JSLitmus._tests[!reverse ? i : (len - i - 1)]);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove all tests from the run queue. The current test has to finish on
|
|
||||||
* it's own though
|
|
||||||
*/
|
|
||||||
stop: function() {
|
|
||||||
while (JSLitmus._queue.length) {
|
|
||||||
var test = JSLitmus._queue.shift();
|
|
||||||
JSLitmus.renderTest(test);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Run the next test in the run queue
|
|
||||||
*/
|
|
||||||
_nextTest: function() {
|
|
||||||
if (!JSLitmus.currentTest) {
|
|
||||||
var test = JSLitmus._queue.shift();
|
|
||||||
if (test) {
|
|
||||||
jsl.$('stop_button').disabled = false;
|
|
||||||
JSLitmus.currentTest = test;
|
|
||||||
test.run();
|
|
||||||
JSLitmus.renderTest(test);
|
|
||||||
if (JSLitmus.onTestStart) JSLitmus.onTestStart(test);
|
|
||||||
} else {
|
|
||||||
jsl.$('stop_button').disabled = true;
|
|
||||||
JSLitmus.renderChart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a test to the run queue
|
|
||||||
*/
|
|
||||||
_queueTest: function(test) {
|
|
||||||
if (jsl.indexOf(JSLitmus._queue, test) >= 0) return;
|
|
||||||
JSLitmus._queue.push(test);
|
|
||||||
JSLitmus.renderTest(test);
|
|
||||||
JSLitmus._nextTest();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a Google Chart URL that shows the data for all tests
|
|
||||||
*/
|
|
||||||
chartUrl: function() {
|
|
||||||
var n = JSLitmus._tests.length, markers = [], data = [];
|
|
||||||
var d, min = 0, max = -1e10;
|
|
||||||
var normalize = jsl.$('test_normalize').checked;
|
|
||||||
|
|
||||||
// Gather test data
|
|
||||||
for (var i=0; i < JSLitmus._tests.length; i++) {
|
|
||||||
var test = JSLitmus._tests[i];
|
|
||||||
if (test.count) {
|
|
||||||
var hz = test.getHz(normalize);
|
|
||||||
var v = hz != Infinity ? hz : 0;
|
|
||||||
data.push(v);
|
|
||||||
markers.push('t' + jsl.escape(test.name + '(' + jsl.toLabel(hz)+ ')') + ',000000,0,' +
|
|
||||||
markers.length + ',10');
|
|
||||||
max = Math.max(v, max);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (markers.length <= 0) return null;
|
|
||||||
|
|
||||||
// Build chart title
|
|
||||||
var title = document.getElementsByTagName('title');
|
|
||||||
title = (title && title.length) ? title[0].innerHTML : null;
|
|
||||||
var chart_title = [];
|
|
||||||
if (title) chart_title.push(title);
|
|
||||||
chart_title.push('Ops/sec (' + platform + ')');
|
|
||||||
|
|
||||||
// Build labels
|
|
||||||
var labels = [jsl.toLabel(min), jsl.toLabel(max)];
|
|
||||||
|
|
||||||
var w = 250, bw = 15;
|
|
||||||
var bs = 5;
|
|
||||||
var h = markers.length*(bw + bs) + 30 + chart_title.length*20;
|
|
||||||
|
|
||||||
var params = {
|
|
||||||
chtt: escape(chart_title.join('|')),
|
|
||||||
chts: '000000,10',
|
|
||||||
cht: 'bhg', // chart type
|
|
||||||
chd: 't:' + data.join(','), // data set
|
|
||||||
chds: min + ',' + max, // max/min of data
|
|
||||||
chxt: 'x', // label axes
|
|
||||||
chxl: '0:|' + labels.join('|'), // labels
|
|
||||||
chsp: '0,1',
|
|
||||||
chm: markers.join('|'), // test names
|
|
||||||
chbh: [bw, 0, bs].join(','), // bar widths
|
|
||||||
// chf: 'bg,lg,0,eeeeee,0,eeeeee,.5,ffffff,1', // gradient
|
|
||||||
chs: w + 'x' + h
|
|
||||||
};
|
|
||||||
return 'http://chart.apis.google.com/chart?' + jsl.join(params, '=', '&');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
JSLitmus._init();
|
|
||||||
})();
|
|
||||||
481
vendor/backbone/test/vendor/json2.js
vendored
481
vendor/backbone/test/vendor/json2.js
vendored
@@ -1,481 +0,0 @@
|
|||||||
/*
|
|
||||||
http://www.JSON.org/json2.js
|
|
||||||
2009-09-29
|
|
||||||
|
|
||||||
Public Domain.
|
|
||||||
|
|
||||||
NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
|
|
||||||
|
|
||||||
See http://www.JSON.org/js.html
|
|
||||||
|
|
||||||
|
|
||||||
This code should be minified before deployment.
|
|
||||||
See http://javascript.crockford.com/jsmin.html
|
|
||||||
|
|
||||||
USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
|
|
||||||
NOT CONTROL.
|
|
||||||
|
|
||||||
|
|
||||||
This file creates a global JSON object containing two methods: stringify
|
|
||||||
and parse.
|
|
||||||
|
|
||||||
JSON.stringify(value, replacer, space)
|
|
||||||
value any JavaScript value, usually an object or array.
|
|
||||||
|
|
||||||
replacer an optional parameter that determines how object
|
|
||||||
values are stringified for objects. It can be a
|
|
||||||
function or an array of strings.
|
|
||||||
|
|
||||||
space an optional parameter that specifies the indentation
|
|
||||||
of nested structures. If it is omitted, the text will
|
|
||||||
be packed without extra whitespace. If it is a number,
|
|
||||||
it will specify the number of spaces to indent at each
|
|
||||||
level. If it is a string (such as '\t' or ' '),
|
|
||||||
it contains the characters used to indent at each level.
|
|
||||||
|
|
||||||
This method produces a JSON text from a JavaScript value.
|
|
||||||
|
|
||||||
When an object value is found, if the object contains a toJSON
|
|
||||||
method, its toJSON method will be called and the result will be
|
|
||||||
stringified. A toJSON method does not serialize: it returns the
|
|
||||||
value represented by the name/value pair that should be serialized,
|
|
||||||
or undefined if nothing should be serialized. The toJSON method
|
|
||||||
will be passed the key associated with the value, and this will be
|
|
||||||
bound to the value
|
|
||||||
|
|
||||||
For example, this would serialize Dates as ISO strings.
|
|
||||||
|
|
||||||
Date.prototype.toJSON = function (key) {
|
|
||||||
function f(n) {
|
|
||||||
// Format integers to have at least two digits.
|
|
||||||
return n < 10 ? '0' + n : n;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.getUTCFullYear() + '-' +
|
|
||||||
f(this.getUTCMonth() + 1) + '-' +
|
|
||||||
f(this.getUTCDate()) + 'T' +
|
|
||||||
f(this.getUTCHours()) + ':' +
|
|
||||||
f(this.getUTCMinutes()) + ':' +
|
|
||||||
f(this.getUTCSeconds()) + 'Z';
|
|
||||||
};
|
|
||||||
|
|
||||||
You can provide an optional replacer method. It will be passed the
|
|
||||||
key and value of each member, with this bound to the containing
|
|
||||||
object. The value that is returned from your method will be
|
|
||||||
serialized. If your method returns undefined, then the member will
|
|
||||||
be excluded from the serialization.
|
|
||||||
|
|
||||||
If the replacer parameter is an array of strings, then it will be
|
|
||||||
used to select the members to be serialized. It filters the results
|
|
||||||
such that only members with keys listed in the replacer array are
|
|
||||||
stringified.
|
|
||||||
|
|
||||||
Values that do not have JSON representations, such as undefined or
|
|
||||||
functions, will not be serialized. Such values in objects will be
|
|
||||||
dropped; in arrays they will be replaced with null. You can use
|
|
||||||
a replacer function to replace those with JSON values.
|
|
||||||
JSON.stringify(undefined) returns undefined.
|
|
||||||
|
|
||||||
The optional space parameter produces a stringification of the
|
|
||||||
value that is filled with line breaks and indentation to make it
|
|
||||||
easier to read.
|
|
||||||
|
|
||||||
If the space parameter is a non-empty string, then that string will
|
|
||||||
be used for indentation. If the space parameter is a number, then
|
|
||||||
the indentation will be that many spaces.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
text = JSON.stringify(['e', {pluribus: 'unum'}]);
|
|
||||||
// text is '["e",{"pluribus":"unum"}]'
|
|
||||||
|
|
||||||
|
|
||||||
text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
|
|
||||||
// text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
|
|
||||||
|
|
||||||
text = JSON.stringify([new Date()], function (key, value) {
|
|
||||||
return this[key] instanceof Date ?
|
|
||||||
'Date(' + this[key] + ')' : value;
|
|
||||||
});
|
|
||||||
// text is '["Date(---current time---)"]'
|
|
||||||
|
|
||||||
|
|
||||||
JSON.parse(text, reviver)
|
|
||||||
This method parses a JSON text to produce an object or array.
|
|
||||||
It can throw a SyntaxError exception.
|
|
||||||
|
|
||||||
The optional reviver parameter is a function that can filter and
|
|
||||||
transform the results. It receives each of the keys and values,
|
|
||||||
and its return value is used instead of the original value.
|
|
||||||
If it returns what it received, then the structure is not modified.
|
|
||||||
If it returns undefined then the member is deleted.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
// Parse the text. Values that look like ISO date strings will
|
|
||||||
// be converted to Date objects.
|
|
||||||
|
|
||||||
myData = JSON.parse(text, function (key, value) {
|
|
||||||
var a;
|
|
||||||
if (typeof value === 'string') {
|
|
||||||
a =
|
|
||||||
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
|
|
||||||
if (a) {
|
|
||||||
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
|
|
||||||
+a[5], +a[6]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
});
|
|
||||||
|
|
||||||
myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
|
|
||||||
var d;
|
|
||||||
if (typeof value === 'string' &&
|
|
||||||
value.slice(0, 5) === 'Date(' &&
|
|
||||||
value.slice(-1) === ')') {
|
|
||||||
d = new Date(value.slice(5, -1));
|
|
||||||
if (d) {
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
This is a reference implementation. You are free to copy, modify, or
|
|
||||||
redistribute.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*jslint evil: true, strict: false */
|
|
||||||
|
|
||||||
/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
|
|
||||||
call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
|
|
||||||
getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
|
|
||||||
lastIndex, length, parse, prototype, push, replace, slice, stringify,
|
|
||||||
test, toJSON, toString, valueOf
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
// Create a JSON object only if one does not already exist. We create the
|
|
||||||
// methods in a closure to avoid creating global variables.
|
|
||||||
|
|
||||||
if (!this.JSON) {
|
|
||||||
this.JSON = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
(function () {
|
|
||||||
|
|
||||||
function f(n) {
|
|
||||||
// Format integers to have at least two digits.
|
|
||||||
return n < 10 ? '0' + n : n;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof Date.prototype.toJSON !== 'function') {
|
|
||||||
|
|
||||||
Date.prototype.toJSON = function (key) {
|
|
||||||
|
|
||||||
return isFinite(this.valueOf()) ?
|
|
||||||
this.getUTCFullYear() + '-' +
|
|
||||||
f(this.getUTCMonth() + 1) + '-' +
|
|
||||||
f(this.getUTCDate()) + 'T' +
|
|
||||||
f(this.getUTCHours()) + ':' +
|
|
||||||
f(this.getUTCMinutes()) + ':' +
|
|
||||||
f(this.getUTCSeconds()) + 'Z' : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
String.prototype.toJSON =
|
|
||||||
Number.prototype.toJSON =
|
|
||||||
Boolean.prototype.toJSON = function (key) {
|
|
||||||
return this.valueOf();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
|
|
||||||
escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
|
|
||||||
gap,
|
|
||||||
indent,
|
|
||||||
meta = { // table of character substitutions
|
|
||||||
'\b': '\\b',
|
|
||||||
'\t': '\\t',
|
|
||||||
'\n': '\\n',
|
|
||||||
'\f': '\\f',
|
|
||||||
'\r': '\\r',
|
|
||||||
'"' : '\\"',
|
|
||||||
'\\': '\\\\'
|
|
||||||
},
|
|
||||||
rep;
|
|
||||||
|
|
||||||
|
|
||||||
function quote(string) {
|
|
||||||
|
|
||||||
// If the string contains no control characters, no quote characters, and no
|
|
||||||
// backslash characters, then we can safely slap some quotes around it.
|
|
||||||
// Otherwise we must also replace the offending characters with safe escape
|
|
||||||
// sequences.
|
|
||||||
|
|
||||||
escapable.lastIndex = 0;
|
|
||||||
return escapable.test(string) ?
|
|
||||||
'"' + string.replace(escapable, function (a) {
|
|
||||||
var c = meta[a];
|
|
||||||
return typeof c === 'string' ? c :
|
|
||||||
'\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
|
|
||||||
}) + '"' :
|
|
||||||
'"' + string + '"';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function str(key, holder) {
|
|
||||||
|
|
||||||
// Produce a string from holder[key].
|
|
||||||
|
|
||||||
var i, // The loop counter.
|
|
||||||
k, // The member key.
|
|
||||||
v, // The member value.
|
|
||||||
length,
|
|
||||||
mind = gap,
|
|
||||||
partial,
|
|
||||||
value = holder[key];
|
|
||||||
|
|
||||||
// If the value has a toJSON method, call it to obtain a replacement value.
|
|
||||||
|
|
||||||
if (value && typeof value === 'object' &&
|
|
||||||
typeof value.toJSON === 'function') {
|
|
||||||
value = value.toJSON(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we were called with a replacer function, then call the replacer to
|
|
||||||
// obtain a replacement value.
|
|
||||||
|
|
||||||
if (typeof rep === 'function') {
|
|
||||||
value = rep.call(holder, key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// What happens next depends on the value's type.
|
|
||||||
|
|
||||||
switch (typeof value) {
|
|
||||||
case 'string':
|
|
||||||
return quote(value);
|
|
||||||
|
|
||||||
case 'number':
|
|
||||||
|
|
||||||
// JSON numbers must be finite. Encode non-finite numbers as null.
|
|
||||||
|
|
||||||
return isFinite(value) ? String(value) : 'null';
|
|
||||||
|
|
||||||
case 'boolean':
|
|
||||||
case 'null':
|
|
||||||
|
|
||||||
// If the value is a boolean or null, convert it to a string. Note:
|
|
||||||
// typeof null does not produce 'null'. The case is included here in
|
|
||||||
// the remote chance that this gets fixed someday.
|
|
||||||
|
|
||||||
return String(value);
|
|
||||||
|
|
||||||
// If the type is 'object', we might be dealing with an object or an array or
|
|
||||||
// null.
|
|
||||||
|
|
||||||
case 'object':
|
|
||||||
|
|
||||||
// Due to a specification blunder in ECMAScript, typeof null is 'object',
|
|
||||||
// so watch out for that case.
|
|
||||||
|
|
||||||
if (!value) {
|
|
||||||
return 'null';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make an array to hold the partial results of stringifying this object value.
|
|
||||||
|
|
||||||
gap += indent;
|
|
||||||
partial = [];
|
|
||||||
|
|
||||||
// Is the value an array?
|
|
||||||
|
|
||||||
if (Object.prototype.toString.apply(value) === '[object Array]') {
|
|
||||||
|
|
||||||
// The value is an array. Stringify every element. Use null as a placeholder
|
|
||||||
// for non-JSON values.
|
|
||||||
|
|
||||||
length = value.length;
|
|
||||||
for (i = 0; i < length; i += 1) {
|
|
||||||
partial[i] = str(i, value) || 'null';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Join all of the elements together, separated with commas, and wrap them in
|
|
||||||
// brackets.
|
|
||||||
|
|
||||||
v = partial.length === 0 ? '[]' :
|
|
||||||
gap ? '[\n' + gap +
|
|
||||||
partial.join(',\n' + gap) + '\n' +
|
|
||||||
mind + ']' :
|
|
||||||
'[' + partial.join(',') + ']';
|
|
||||||
gap = mind;
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the replacer is an array, use it to select the members to be stringified.
|
|
||||||
|
|
||||||
if (rep && typeof rep === 'object') {
|
|
||||||
length = rep.length;
|
|
||||||
for (i = 0; i < length; i += 1) {
|
|
||||||
k = rep[i];
|
|
||||||
if (typeof k === 'string') {
|
|
||||||
v = str(k, value);
|
|
||||||
if (v) {
|
|
||||||
partial.push(quote(k) + (gap ? ': ' : ':') + v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// Otherwise, iterate through all of the keys in the object.
|
|
||||||
|
|
||||||
for (k in value) {
|
|
||||||
if (Object.hasOwnProperty.call(value, k)) {
|
|
||||||
v = str(k, value);
|
|
||||||
if (v) {
|
|
||||||
partial.push(quote(k) + (gap ? ': ' : ':') + v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Join all of the member texts together, separated with commas,
|
|
||||||
// and wrap them in braces.
|
|
||||||
|
|
||||||
v = partial.length === 0 ? '{}' :
|
|
||||||
gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
|
|
||||||
mind + '}' : '{' + partial.join(',') + '}';
|
|
||||||
gap = mind;
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the JSON object does not yet have a stringify method, give it one.
|
|
||||||
|
|
||||||
if (typeof JSON.stringify !== 'function') {
|
|
||||||
JSON.stringify = function (value, replacer, space) {
|
|
||||||
|
|
||||||
// The stringify method takes a value and an optional replacer, and an optional
|
|
||||||
// space parameter, and returns a JSON text. The replacer can be a function
|
|
||||||
// that can replace values, or an array of strings that will select the keys.
|
|
||||||
// A default replacer method can be provided. Use of the space parameter can
|
|
||||||
// produce text that is more easily readable.
|
|
||||||
|
|
||||||
var i;
|
|
||||||
gap = '';
|
|
||||||
indent = '';
|
|
||||||
|
|
||||||
// If the space parameter is a number, make an indent string containing that
|
|
||||||
// many spaces.
|
|
||||||
|
|
||||||
if (typeof space === 'number') {
|
|
||||||
for (i = 0; i < space; i += 1) {
|
|
||||||
indent += ' ';
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the space parameter is a string, it will be used as the indent string.
|
|
||||||
|
|
||||||
} else if (typeof space === 'string') {
|
|
||||||
indent = space;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there is a replacer, it must be a function or an array.
|
|
||||||
// Otherwise, throw an error.
|
|
||||||
|
|
||||||
rep = replacer;
|
|
||||||
if (replacer && typeof replacer !== 'function' &&
|
|
||||||
(typeof replacer !== 'object' ||
|
|
||||||
typeof replacer.length !== 'number')) {
|
|
||||||
throw new Error('JSON.stringify');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make a fake root object containing our value under the key of ''.
|
|
||||||
// Return the result of stringifying the value.
|
|
||||||
|
|
||||||
return str('', {'': value});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// If the JSON object does not yet have a parse method, give it one.
|
|
||||||
|
|
||||||
if (typeof JSON.parse !== 'function') {
|
|
||||||
JSON.parse = function (text, reviver) {
|
|
||||||
|
|
||||||
// The parse method takes a text and an optional reviver function, and returns
|
|
||||||
// a JavaScript value if the text is a valid JSON text.
|
|
||||||
|
|
||||||
var j;
|
|
||||||
|
|
||||||
function walk(holder, key) {
|
|
||||||
|
|
||||||
// The walk method is used to recursively walk the resulting structure so
|
|
||||||
// that modifications can be made.
|
|
||||||
|
|
||||||
var k, v, value = holder[key];
|
|
||||||
if (value && typeof value === 'object') {
|
|
||||||
for (k in value) {
|
|
||||||
if (Object.hasOwnProperty.call(value, k)) {
|
|
||||||
v = walk(value, k);
|
|
||||||
if (v !== undefined) {
|
|
||||||
value[k] = v;
|
|
||||||
} else {
|
|
||||||
delete value[k];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return reviver.call(holder, key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Parsing happens in four stages. In the first stage, we replace certain
|
|
||||||
// Unicode characters with escape sequences. JavaScript handles many characters
|
|
||||||
// incorrectly, either silently deleting them, or treating them as line endings.
|
|
||||||
|
|
||||||
cx.lastIndex = 0;
|
|
||||||
if (cx.test(text)) {
|
|
||||||
text = text.replace(cx, function (a) {
|
|
||||||
return '\\u' +
|
|
||||||
('0000' + a.charCodeAt(0).toString(16)).slice(-4);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// In the second stage, we run the text against regular expressions that look
|
|
||||||
// for non-JSON patterns. We are especially concerned with '()' and 'new'
|
|
||||||
// because they can cause invocation, and '=' because it can cause mutation.
|
|
||||||
// But just to be safe, we want to reject all unexpected forms.
|
|
||||||
|
|
||||||
// We split the second stage into 4 regexp operations in order to work around
|
|
||||||
// crippling inefficiencies in IE's and Safari's regexp engines. First we
|
|
||||||
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
|
|
||||||
// replace all simple value tokens with ']' characters. Third, we delete all
|
|
||||||
// open brackets that follow a colon or comma or that begin the text. Finally,
|
|
||||||
// we look to see that the remaining characters are only whitespace or ']' or
|
|
||||||
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
|
|
||||||
|
|
||||||
if (/^[\],:{}\s]*$/.
|
|
||||||
test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
|
|
||||||
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
|
|
||||||
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
|
|
||||||
|
|
||||||
// In the third stage we use the eval function to compile the text into a
|
|
||||||
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
|
|
||||||
// in JavaScript: it can begin a block or an object literal. We wrap the text
|
|
||||||
// in parens to eliminate the ambiguity.
|
|
||||||
|
|
||||||
j = eval('(' + text + ')');
|
|
||||||
|
|
||||||
// In the optional fourth stage, we recursively walk the new structure, passing
|
|
||||||
// each name/value pair to a reviver function for possible transformation.
|
|
||||||
|
|
||||||
return typeof reviver === 'function' ?
|
|
||||||
walk({'': j}, '') : j;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the text is not JSON parseable, then a SyntaxError is thrown.
|
|
||||||
|
|
||||||
throw new SyntaxError('JSON.parse');
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}());
|
|
||||||
236
vendor/backbone/test/vendor/qunit.css
vendored
236
vendor/backbone/test/vendor/qunit.css
vendored
@@ -1,236 +0,0 @@
|
|||||||
/**
|
|
||||||
* QUnit v1.8.0 - A JavaScript Unit Testing Framework
|
|
||||||
*
|
|
||||||
* http://docs.jquery.com/QUnit
|
|
||||||
*
|
|
||||||
* Copyright (c) 2012 John Resig, Jörn Zaefferer
|
|
||||||
* Dual licensed under the MIT (MIT-LICENSE.txt)
|
|
||||||
* or GPL (GPL-LICENSE.txt) licenses.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/** Font Family and Sizes */
|
|
||||||
|
|
||||||
#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
|
|
||||||
font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
|
|
||||||
#qunit-tests { font-size: smaller; }
|
|
||||||
|
|
||||||
|
|
||||||
/** Resets */
|
|
||||||
|
|
||||||
#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/** Header */
|
|
||||||
|
|
||||||
#qunit-header {
|
|
||||||
padding: 0.5em 0 0.5em 1em;
|
|
||||||
|
|
||||||
color: #8699a4;
|
|
||||||
background-color: #0d3349;
|
|
||||||
|
|
||||||
font-size: 1.5em;
|
|
||||||
line-height: 1em;
|
|
||||||
font-weight: normal;
|
|
||||||
|
|
||||||
border-radius: 15px 15px 0 0;
|
|
||||||
-moz-border-radius: 15px 15px 0 0;
|
|
||||||
-webkit-border-top-right-radius: 15px;
|
|
||||||
-webkit-border-top-left-radius: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-header a {
|
|
||||||
text-decoration: none;
|
|
||||||
color: #c2ccd1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-header a:hover,
|
|
||||||
#qunit-header a:focus {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-header label {
|
|
||||||
display: inline-block;
|
|
||||||
padding-left: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-banner {
|
|
||||||
height: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-testrunner-toolbar {
|
|
||||||
padding: 0.5em 0 0.5em 2em;
|
|
||||||
color: #5E740B;
|
|
||||||
background-color: #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-userAgent {
|
|
||||||
padding: 0.5em 0 0.5em 2.5em;
|
|
||||||
background-color: #2b81af;
|
|
||||||
color: #fff;
|
|
||||||
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/** Tests: Pass/Fail */
|
|
||||||
|
|
||||||
#qunit-tests {
|
|
||||||
list-style-position: inside;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-tests li {
|
|
||||||
padding: 0.4em 0.5em 0.4em 2.5em;
|
|
||||||
border-bottom: 1px solid #fff;
|
|
||||||
list-style-position: inside;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-tests li strong {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-tests li a {
|
|
||||||
padding: 0.5em;
|
|
||||||
color: #c2ccd1;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
#qunit-tests li a:hover,
|
|
||||||
#qunit-tests li a:focus {
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-tests ol {
|
|
||||||
margin-top: 0.5em;
|
|
||||||
padding: 0.5em;
|
|
||||||
|
|
||||||
background-color: #fff;
|
|
||||||
|
|
||||||
border-radius: 15px;
|
|
||||||
-moz-border-radius: 15px;
|
|
||||||
-webkit-border-radius: 15px;
|
|
||||||
|
|
||||||
box-shadow: inset 0px 2px 13px #999;
|
|
||||||
-moz-box-shadow: inset 0px 2px 13px #999;
|
|
||||||
-webkit-box-shadow: inset 0px 2px 13px #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-tests table {
|
|
||||||
border-collapse: collapse;
|
|
||||||
margin-top: .2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-tests th {
|
|
||||||
text-align: right;
|
|
||||||
vertical-align: top;
|
|
||||||
padding: 0 .5em 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-tests td {
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-tests pre {
|
|
||||||
margin: 0;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-tests del {
|
|
||||||
background-color: #e0f2be;
|
|
||||||
color: #374e0c;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-tests ins {
|
|
||||||
background-color: #ffcaca;
|
|
||||||
color: #500;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*** Test Counts */
|
|
||||||
|
|
||||||
#qunit-tests b.counts { color: black; }
|
|
||||||
#qunit-tests b.passed { color: #5E740B; }
|
|
||||||
#qunit-tests b.failed { color: #710909; }
|
|
||||||
|
|
||||||
#qunit-tests li li {
|
|
||||||
margin: 0.5em;
|
|
||||||
padding: 0.4em 0.5em 0.4em 0.5em;
|
|
||||||
background-color: #fff;
|
|
||||||
border-bottom: none;
|
|
||||||
list-style-position: inside;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*** Passing Styles */
|
|
||||||
|
|
||||||
#qunit-tests li li.pass {
|
|
||||||
color: #5E740B;
|
|
||||||
background-color: #fff;
|
|
||||||
border-left: 26px solid #C6E746;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
|
|
||||||
#qunit-tests .pass .test-name { color: #366097; }
|
|
||||||
|
|
||||||
#qunit-tests .pass .test-actual,
|
|
||||||
#qunit-tests .pass .test-expected { color: #999999; }
|
|
||||||
|
|
||||||
#qunit-banner.qunit-pass { background-color: #C6E746; }
|
|
||||||
|
|
||||||
/*** Failing Styles */
|
|
||||||
|
|
||||||
#qunit-tests li li.fail {
|
|
||||||
color: #710909;
|
|
||||||
background-color: #fff;
|
|
||||||
border-left: 26px solid #EE5757;
|
|
||||||
white-space: pre;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-tests > li:last-child {
|
|
||||||
border-radius: 0 0 15px 15px;
|
|
||||||
-moz-border-radius: 0 0 15px 15px;
|
|
||||||
-webkit-border-bottom-right-radius: 15px;
|
|
||||||
-webkit-border-bottom-left-radius: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-tests .fail { color: #000000; background-color: #EE5757; }
|
|
||||||
#qunit-tests .fail .test-name,
|
|
||||||
#qunit-tests .fail .module-name { color: #000000; }
|
|
||||||
|
|
||||||
#qunit-tests .fail .test-actual { color: #EE5757; }
|
|
||||||
#qunit-tests .fail .test-expected { color: green; }
|
|
||||||
|
|
||||||
#qunit-banner.qunit-fail { background-color: #EE5757; }
|
|
||||||
|
|
||||||
|
|
||||||
/** Result */
|
|
||||||
|
|
||||||
#qunit-testresult {
|
|
||||||
padding: 0.5em 0.5em 0.5em 2.5em;
|
|
||||||
|
|
||||||
color: #2b81af;
|
|
||||||
background-color: #D2E0E6;
|
|
||||||
|
|
||||||
border-bottom: 1px solid white;
|
|
||||||
}
|
|
||||||
#qunit-testresult .module-name {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Fixture */
|
|
||||||
|
|
||||||
#qunit-fixture {
|
|
||||||
position: absolute;
|
|
||||||
top: -10000px;
|
|
||||||
left: -10000px;
|
|
||||||
width: 1000px;
|
|
||||||
height: 1000px;
|
|
||||||
}
|
|
||||||
290
vendor/backbone/test/view.js
vendored
290
vendor/backbone/test/view.js
vendored
@@ -7,33 +7,37 @@ $(document).ready(function() {
|
|||||||
setup: function() {
|
setup: function() {
|
||||||
view = new Backbone.View({
|
view = new Backbone.View({
|
||||||
id : 'test-view',
|
id : 'test-view',
|
||||||
className : 'test-view'
|
className : 'test-view',
|
||||||
|
other : 'non-special-option'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("View: constructor", 4, function() {
|
test("constructor", 6, function() {
|
||||||
equal(view.el.id, 'test-view');
|
equal(view.el.id, 'test-view');
|
||||||
equal(view.el.className, 'test-view');
|
equal(view.el.className, 'test-view');
|
||||||
|
equal(view.el.other, void 0);
|
||||||
equal(view.options.id, 'test-view');
|
equal(view.options.id, 'test-view');
|
||||||
equal(view.options.className, 'test-view');
|
equal(view.options.className, 'test-view');
|
||||||
|
equal(view.options.other, 'non-special-option');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("View: jQuery", 2, function() {
|
test("jQuery", 1, function() {
|
||||||
view.setElement(document.body);
|
var view = new Backbone.View;
|
||||||
ok(view.$('#qunit-header a').get(0).innerHTML.match(/Backbone Test Suite/));
|
view.setElement('<p><a><b>test</b></a></p>');
|
||||||
ok(view.$('#qunit-header a').get(1).innerHTML.match(/Backbone Speed Suite/));
|
strictEqual(view.$('a b').html(), 'test');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("View: make", 3, function() {
|
test("make", 3, function() {
|
||||||
var div = view.make('div', {id: 'test-div'}, "one two three");
|
var div = view.make('div', {id: 'test-div'}, "one two three");
|
||||||
|
|
||||||
equal(div.tagName.toLowerCase(), 'div');
|
equal(div.tagName.toLowerCase(), 'div');
|
||||||
equal(div.id, 'test-div');
|
equal(div.id, 'test-div');
|
||||||
equal($(div).text(), 'one two three');
|
equal($(div).text(), 'one two three');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("View: make can take falsy values for content", 2, function() {
|
test("make can take falsy values for content", 2, function() {
|
||||||
var div = view.make('div', {id: 'test-div'}, 0);
|
var div = view.make('div', {id: 'test-div'}, 0);
|
||||||
equal($(div).text(), '0');
|
equal($(div).text(), '0');
|
||||||
|
|
||||||
@@ -41,142 +45,178 @@ $(document).ready(function() {
|
|||||||
equal($(div).text(), '');
|
equal($(div).text(), '');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("View: initialize", 1, function() {
|
test("initialize", 1, function() {
|
||||||
var View = Backbone.View.extend({
|
var View = Backbone.View.extend({
|
||||||
initialize: function() {
|
initialize: function() {
|
||||||
this.one = 1;
|
this.one = 1;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
var view = new View;
|
|
||||||
equal(view.one, 1);
|
strictEqual(new View().one, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("View: delegateEvents", 6, function() {
|
test("delegateEvents", 6, function() {
|
||||||
var counter = 0;
|
var counter1 = 0, counter2 = 0;
|
||||||
var counter2 = 0;
|
|
||||||
view.setElement(document.body);
|
var view = new Backbone.View({el: '<p><a id="test"></a></p>'});
|
||||||
view.increment = function(){ counter++; };
|
view.increment = function(){ counter1++; };
|
||||||
view.$el.bind('click', function(){ counter2++; });
|
view.$el.on('click', function(){ counter2++; });
|
||||||
var events = {"click #qunit-banner": "increment"};
|
|
||||||
|
var events = {'click #test': 'increment'};
|
||||||
|
|
||||||
view.delegateEvents(events);
|
view.delegateEvents(events);
|
||||||
$('#qunit-banner').trigger('click');
|
view.$('#test').trigger('click');
|
||||||
equal(counter, 1);
|
equal(counter1, 1);
|
||||||
equal(counter2, 1);
|
equal(counter2, 1);
|
||||||
$('#qunit-banner').trigger('click');
|
|
||||||
equal(counter, 2);
|
view.$('#test').trigger('click');
|
||||||
|
equal(counter1, 2);
|
||||||
equal(counter2, 2);
|
equal(counter2, 2);
|
||||||
|
|
||||||
view.delegateEvents(events);
|
view.delegateEvents(events);
|
||||||
$('#qunit-banner').trigger('click');
|
view.$('#test').trigger('click');
|
||||||
equal(counter, 3);
|
equal(counter1, 3);
|
||||||
equal(counter2, 3);
|
equal(counter2, 3);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("View: delegateEvents allows functions for callbacks", 3, function() {
|
test("delegateEvents allows functions for callbacks", 3, function() {
|
||||||
|
var view = new Backbone.View({el: '<p></p>'});
|
||||||
view.counter = 0;
|
view.counter = 0;
|
||||||
view.setElement("#qunit-banner");
|
|
||||||
var events = {"click": function() { this.counter++; }};
|
var events = {
|
||||||
|
click: function() {
|
||||||
|
this.counter++;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
view.delegateEvents(events);
|
view.delegateEvents(events);
|
||||||
$('#qunit-banner').trigger('click');
|
view.$el.trigger('click');
|
||||||
equal(view.counter, 1);
|
equal(view.counter, 1);
|
||||||
$('#qunit-banner').trigger('click');
|
|
||||||
|
view.$el.trigger('click');
|
||||||
equal(view.counter, 2);
|
equal(view.counter, 2);
|
||||||
|
|
||||||
view.delegateEvents(events);
|
view.delegateEvents(events);
|
||||||
$('#qunit-banner').trigger('click');
|
view.$el.trigger('click');
|
||||||
equal(view.counter, 3);
|
equal(view.counter, 3);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("View: undelegateEvents", 6, function() {
|
test("undelegateEvents", 6, function() {
|
||||||
var counter = 0;
|
var counter1 = 0, counter2 = 0;
|
||||||
var counter2 = 0;
|
|
||||||
view.setElement(document.body);
|
var view = new Backbone.View({el: '<p><a id="test"></a></p>'});
|
||||||
view.increment = function(){ counter++; };
|
view.increment = function(){ counter1++; };
|
||||||
$(view.el).unbind('click');
|
view.$el.on('click', function(){ counter2++; });
|
||||||
$(view.el).bind('click', function(){ counter2++; });
|
|
||||||
var events = {"click #qunit-userAgent": "increment"};
|
var events = {'click #test': 'increment'};
|
||||||
|
|
||||||
view.delegateEvents(events);
|
view.delegateEvents(events);
|
||||||
$('#qunit-userAgent').trigger('click');
|
view.$('#test').trigger('click');
|
||||||
equal(counter, 1);
|
equal(counter1, 1);
|
||||||
equal(counter2, 1);
|
equal(counter2, 1);
|
||||||
|
|
||||||
view.undelegateEvents();
|
view.undelegateEvents();
|
||||||
$('#qunit-userAgent').trigger('click');
|
view.$('#test').trigger('click');
|
||||||
equal(counter, 1);
|
equal(counter1, 1);
|
||||||
equal(counter2, 2);
|
equal(counter2, 2);
|
||||||
|
|
||||||
view.delegateEvents(events);
|
view.delegateEvents(events);
|
||||||
$('#qunit-userAgent').trigger('click');
|
view.$('#test').trigger('click');
|
||||||
equal(counter, 2);
|
equal(counter1, 2);
|
||||||
equal(counter2, 3);
|
equal(counter2, 3);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("View: _ensureElement with DOM node el", 1, function() {
|
test("_ensureElement with DOM node el", 1, function() {
|
||||||
var ViewClass = Backbone.View.extend({
|
var View = Backbone.View.extend({
|
||||||
el: document.body
|
el: document.body
|
||||||
});
|
});
|
||||||
var view = new ViewClass;
|
|
||||||
equal(view.el, document.body);
|
equal(new View().el, document.body);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("View: _ensureElement with string el", 3, function() {
|
test("_ensureElement with string el", 3, function() {
|
||||||
var ViewClass = Backbone.View.extend({
|
var View = Backbone.View.extend({
|
||||||
el: "body"
|
el: "body"
|
||||||
});
|
});
|
||||||
var view = new ViewClass;
|
strictEqual(new View().el, document.body);
|
||||||
strictEqual(view.el, document.body);
|
|
||||||
|
|
||||||
ViewClass = Backbone.View.extend({
|
View = Backbone.View.extend({
|
||||||
el: "#testElement > h1"
|
el: "#testElement > h1"
|
||||||
});
|
});
|
||||||
view = new ViewClass;
|
strictEqual(new View().el, $("#testElement > h1").get(0));
|
||||||
strictEqual(view.el, $("#testElement > h1").get(0));
|
|
||||||
|
|
||||||
ViewClass = Backbone.View.extend({
|
View = Backbone.View.extend({
|
||||||
el: "#nonexistent"
|
el: "#nonexistent"
|
||||||
});
|
});
|
||||||
view = new ViewClass;
|
ok(!new View().el);
|
||||||
ok(!view.el);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("View: with attributes", 2, function() {
|
test("with className and id functions", 2, function() {
|
||||||
var view = new Backbone.View({attributes : {'class': 'one', id: 'two'}});
|
var View = Backbone.View.extend({
|
||||||
equal(view.el.className, 'one');
|
className: function() {
|
||||||
equal(view.el.id, 'two');
|
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() {
|
test("with attributes", 2, function() {
|
||||||
var viewClass = Backbone.View.extend({
|
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() {
|
attributes: function() {
|
||||||
return {'class': 'dynamic'};
|
return {'class': 'dynamic'};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
equal((new viewClass).el.className, 'dynamic');
|
|
||||||
|
strictEqual(new View().el.className, 'dynamic');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("View: multiple views per element", 3, function() {
|
test("multiple views per element", 3, function() {
|
||||||
var count = 0, ViewClass = Backbone.View.extend({
|
var count = 0;
|
||||||
el: $("body"),
|
var $el = $('<p></p>');
|
||||||
|
|
||||||
|
var View = Backbone.View.extend({
|
||||||
|
el: $el,
|
||||||
events: {
|
events: {
|
||||||
"click": "click"
|
click: function() {
|
||||||
},
|
count++;
|
||||||
click: function() {
|
}
|
||||||
count++;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var view1 = new ViewClass;
|
var view1 = new View;
|
||||||
$("body").trigger("click");
|
$el.trigger("click");
|
||||||
equal(1, count);
|
equal(1, count);
|
||||||
|
|
||||||
var view2 = new ViewClass;
|
var view2 = new View;
|
||||||
$("body").trigger("click");
|
$el.trigger("click");
|
||||||
equal(3, count);
|
equal(3, count);
|
||||||
|
|
||||||
view1.delegateEvents();
|
view1.delegateEvents();
|
||||||
$("body").trigger("click");
|
$el.trigger("click");
|
||||||
equal(5, count);
|
equal(5, count);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("View: custom events, with namespaces", 2, function() {
|
test("custom events, with namespaces", 2, function() {
|
||||||
var count = 0;
|
var count = 0;
|
||||||
var ViewClass = Backbone.View.extend({
|
|
||||||
|
var View = Backbone.View.extend({
|
||||||
el: $('body'),
|
el: $('body'),
|
||||||
events: function() {
|
events: function() {
|
||||||
return {"fake$event.namespaced": "run"};
|
return {"fake$event.namespaced": "run"};
|
||||||
@@ -186,9 +226,10 @@ $(document).ready(function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var view = new ViewClass;
|
var view = new View;
|
||||||
$('body').trigger('fake$event').trigger('fake$event');
|
$('body').trigger('fake$event').trigger('fake$event');
|
||||||
equal(count, 2);
|
equal(count, 2);
|
||||||
|
|
||||||
$('body').unbind('.namespaced');
|
$('body').unbind('.namespaced');
|
||||||
$('body').trigger('fake$event');
|
$('body').trigger('fake$event');
|
||||||
equal(count, 2);
|
equal(count, 2);
|
||||||
@@ -196,35 +237,98 @@ $(document).ready(function() {
|
|||||||
|
|
||||||
test("#1048 - setElement uses provided object.", 2, function() {
|
test("#1048 - setElement uses provided object.", 2, function() {
|
||||||
var $el = $('body');
|
var $el = $('body');
|
||||||
|
|
||||||
var view = new Backbone.View({el: $el});
|
var view = new Backbone.View({el: $el});
|
||||||
ok(view.$el === $el);
|
ok(view.$el === $el);
|
||||||
|
|
||||||
view.setElement($el = $($el));
|
view.setElement($el = $($el));
|
||||||
ok(view.$el === $el);
|
ok(view.$el === $el);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("#986 - Undelegate before changing element.", 1, function() {
|
test("#986 - Undelegate before changing element.", 1, function() {
|
||||||
var a = $('<button></button>');
|
var button1 = $('<button></button>');
|
||||||
var b = $('<button></button>');
|
var button2 = $('<button></button>');
|
||||||
|
|
||||||
var View = Backbone.View.extend({
|
var View = Backbone.View.extend({
|
||||||
events: {click: function(e) { ok(view.el === e.target); }}
|
events: {
|
||||||
|
click: function(e) {
|
||||||
|
ok(view.el === e.target);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
var view = new View({el: a});
|
|
||||||
view.setElement(b);
|
var view = new View({el: button1});
|
||||||
a.trigger('click');
|
view.setElement(button2);
|
||||||
b.trigger('click');
|
|
||||||
|
button1.trigger('click');
|
||||||
|
button2.trigger('click');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("#1172 - Clone attributes object", 2, function() {
|
test("#1172 - Clone attributes object", 2, function() {
|
||||||
var View = Backbone.View.extend({attributes: {foo: 'bar'}});
|
var View = Backbone.View.extend({
|
||||||
var v1 = new View({id: 'foo'});
|
attributes: {foo: 'bar'}
|
||||||
strictEqual(v1.el.id, 'foo');
|
});
|
||||||
var v2 = new View();
|
|
||||||
ok(!v2.el.id);
|
var view1 = new View({id: 'foo'});
|
||||||
|
strictEqual(view1.el.id, 'foo');
|
||||||
|
|
||||||
|
var view2 = new View();
|
||||||
|
ok(!view2.el.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("#1228 - tagName can be provided as a function", 1, function() {
|
test("#1228 - tagName can be provided as a function", 1, function() {
|
||||||
var View = Backbone.View.extend({tagName: function(){ return 'p'; }});
|
var View = Backbone.View.extend({
|
||||||
|
tagName: function() {
|
||||||
|
return 'p';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
ok(new View().$el.is('p'));
|
ok(new View().$el.is('p'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("dispose", 0, function() {
|
||||||
|
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("dispose with non Backbone objects", 0, function() {
|
||||||
|
var view = new Backbone.View({model: {}, collection: {}});
|
||||||
|
view.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("view#remove calls dispose.", 1, function() {
|
||||||
|
var view = new Backbone.View();
|
||||||
|
|
||||||
|
view.dispose = function() { ok(true); };
|
||||||
|
view.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Provide function for el.", 1, function() {
|
||||||
|
var View = Backbone.View.extend({
|
||||||
|
el: function() {
|
||||||
|
return "<p><a></a></p>";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var view = new View;
|
||||||
|
ok(view.$el.is('p:has(a)'));
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
58
vendor/benchmark.js/README.md
vendored
58
vendor/benchmark.js/README.md
vendored
@@ -1,30 +1,35 @@
|
|||||||
# Benchmark.js <sup>v1.0.0-pre</sup>
|
# Benchmark.js <sup>v1.0.0</sup>
|
||||||
|
[](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/).
|
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>
|
We’ve 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).
|
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
|
## Installation and usage
|
||||||
|
|
||||||
In a browser or Adobe AIR:
|
In a browser or Adobe AIR:
|
||||||
|
|
||||||
~~~ html
|
```html
|
||||||
<script src="benchmark.js"></script>
|
<script src="benchmark.js"></script>
|
||||||
~~~
|
```
|
||||||
|
|
||||||
Optionally, expose Java’s nanosecond timer by adding the `nano` applet to the `<body>`:
|
Optionally, expose Java’s nanosecond timer by adding the `nano` applet to the `<body>`:
|
||||||
|
|
||||||
~~~ html
|
```html
|
||||||
<applet code="nano" archive="nano.jar"></applet>
|
<applet code="nano" archive="nano.jar"></applet>
|
||||||
~~~
|
```
|
||||||
|
|
||||||
Or enable Chrome’s microsecond timer by using the [command line switch](http://peter.sh/experiments/chromium-command-line-switches/#enable-benchmarking):
|
Or enable Chrome’s microsecond timer by using the [command line switch](http://peter.sh/experiments/chromium-command-line-switches/#enable-benchmarking):
|
||||||
|
|
||||||
@@ -32,37 +37,37 @@ Or enable Chrome’s microsecond timer by using the [command line switch](http:/
|
|||||||
|
|
||||||
Via [npm](http://npmjs.org/):
|
Via [npm](http://npmjs.org/):
|
||||||
|
|
||||||
~~~ bash
|
```bash
|
||||||
npm install benchmark
|
npm install benchmark
|
||||||
~~~
|
```
|
||||||
|
|
||||||
In [Node.js](http://nodejs.org/) and [RingoJS v0.8.0+](http://ringojs.org/):
|
In [Node.js](http://nodejs.org/) and [RingoJS v0.8.0+](http://ringojs.org/):
|
||||||
|
|
||||||
~~~ js
|
```js
|
||||||
var Benchmark = require('benchmark');
|
var Benchmark = require('benchmark');
|
||||||
~~~
|
```
|
||||||
|
|
||||||
Optionally, use the [microtime module](https://github.com/wadey/node-microtime) by Wade Simmons:
|
Optionally, use the [microtime module](https://github.com/wadey/node-microtime) by Wade Simmons:
|
||||||
|
|
||||||
~~~ bash
|
```bash
|
||||||
npm install microtime
|
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;
|
var Benchmark = require('benchmark').Benchmark;
|
||||||
~~~
|
```
|
||||||
|
|
||||||
In [Rhino](http://www.mozilla.org/rhino/):
|
In [Rhino](http://www.mozilla.org/rhino/):
|
||||||
|
|
||||||
~~~ js
|
```js
|
||||||
load('benchmark.js');
|
load('benchmark.js');
|
||||||
~~~
|
```
|
||||||
|
|
||||||
In an AMD loader like [RequireJS](http://requirejs.org/):
|
In an AMD loader like [RequireJS](http://requirejs.org/):
|
||||||
|
|
||||||
~~~ js
|
```js
|
||||||
require({
|
require({
|
||||||
'paths': {
|
'paths': {
|
||||||
'benchmark': 'path/to/benchmark'
|
'benchmark': 'path/to/benchmark'
|
||||||
@@ -84,11 +89,11 @@ require({
|
|||||||
Benchmark.platform = platform;
|
Benchmark.platform = platform;
|
||||||
console.log(Benchmark.platform.name);
|
console.log(Benchmark.platform.name);
|
||||||
});
|
});
|
||||||
~~~
|
```
|
||||||
|
|
||||||
Usage example:
|
Usage example:
|
||||||
|
|
||||||
~~~ js
|
```js
|
||||||
var suite = new Benchmark.Suite;
|
var suite = new Benchmark.Suite;
|
||||||
|
|
||||||
// add tests
|
// add tests
|
||||||
@@ -112,12 +117,11 @@ suite.add('RegExp#test', function() {
|
|||||||
// > RegExp#test x 4,161,532 +-0.99% (59 cycles)
|
// > RegExp#test x 4,161,532 +-0.99% (59 cycles)
|
||||||
// > String#indexOf x 6,139,623 +-1.00% (131 cycles)
|
// > String#indexOf x 6,139,623 +-1.00% (131 cycles)
|
||||||
// > Fastest is String#indexOf
|
// > 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.
|
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.
|
||||||
<a name="fn1" title="Jump back to footnote 1 in the text." href="#fnref1">↩</a>
|
|
||||||
|
|
||||||
## Authors
|
## Authors
|
||||||
|
|
||||||
|
|||||||
307
vendor/benchmark.js/benchmark.js
vendored
307
vendor/benchmark.js/benchmark.js
vendored
@@ -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/>
|
* Copyright 2010-2012 Mathias Bynens <http://mths.be/>
|
||||||
* Based on JSLitmus.js, copyright Robert Kieffer <http://broofa.com/>
|
* Based on JSLitmus.js, copyright Robert Kieffer <http://broofa.com/>
|
||||||
* Modified by John-David Dalton <http://allyoucanleet.com/>
|
* Modified by John-David Dalton <http://allyoucanleet.com/>
|
||||||
@@ -244,6 +244,36 @@
|
|||||||
} catch(e) {
|
} catch(e) {
|
||||||
support.getAllKeys = false;
|
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;
|
||||||
|
}
|
||||||
}());
|
}());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -290,6 +320,7 @@
|
|||||||
/** Math shortcuts */
|
/** Math shortcuts */
|
||||||
var abs = Math.abs,
|
var abs = Math.abs,
|
||||||
floor = Math.floor,
|
floor = Math.floor,
|
||||||
|
log = Math.log,
|
||||||
max = Math.max,
|
max = Math.max,
|
||||||
min = Math.min,
|
min = Math.min,
|
||||||
pow = Math.pow,
|
pow = Math.pow,
|
||||||
@@ -583,14 +614,17 @@
|
|||||||
while (++index < length) {
|
while (++index < length) {
|
||||||
if ((index - start) in tail) {
|
if ((index - start) in tail) {
|
||||||
object[index] = tail[index - start];
|
object[index] = tail[index - start];
|
||||||
} else {
|
} else if (index in object) {
|
||||||
delete object[index];
|
delete object[index];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// delete excess elements
|
// delete excess elements
|
||||||
deleteCount = deleteCount > elementCount ? deleteCount - elementCount : 0;
|
deleteCount = deleteCount > elementCount ? deleteCount - elementCount : 0;
|
||||||
while (deleteCount--) {
|
while (deleteCount--) {
|
||||||
delete object[length + deleteCount];
|
index = length + deleteCount;
|
||||||
|
if (index in object) {
|
||||||
|
delete object[index];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
object.length = length;
|
object.length = length;
|
||||||
return result;
|
return result;
|
||||||
@@ -912,6 +946,20 @@
|
|||||||
(/^[\s(]*function[^(]*\(([^\s,)]+)/.exec(fn) || 0)[1]) || '';
|
(/^[\s(]*function[^(]*\(([^\s,)]+)/.exec(fn) || 0)[1]) || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the geometric mean (log-average) of a sample.
|
||||||
|
* See http://en.wikipedia.org/wiki/Geometric_mean#Relationship_with_arithmetic_mean_of_logarithms.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Array} sample The sample.
|
||||||
|
* @returns {Number} The geometric mean.
|
||||||
|
*/
|
||||||
|
function getGeometricMean(sample) {
|
||||||
|
return pow(Math.E, reduce(sample, function(sum, x) {
|
||||||
|
return sum + log(x);
|
||||||
|
}) / sample.length) || 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Computes the arithmetic mean of a sample.
|
* Computes the arithmetic mean of a sample.
|
||||||
*
|
*
|
||||||
@@ -920,9 +968,9 @@
|
|||||||
* @returns {Number} The mean.
|
* @returns {Number} The mean.
|
||||||
*/
|
*/
|
||||||
function getMean(sample) {
|
function getMean(sample) {
|
||||||
return reduce(sample, function(sum, x) {
|
return (reduce(sample, function(sum, x) {
|
||||||
return sum + x;
|
return sum + x;
|
||||||
}) / sample.length || 0;
|
}) / sample.length) || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -941,7 +989,13 @@
|
|||||||
// escape the `{` for Firefox 1
|
// escape the `{` for Firefox 1
|
||||||
result = (/^[^{]+\{([\s\S]*)}\s*$/.exec(fn) || 0)[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 +1048,43 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the specified `value` is an object created by the `Object`
|
* Checks if a given `value` is an object created by the `Object` constructor
|
||||||
* constructor assuming objects created by the `Object` constructor have no
|
* assuming objects created by the `Object` constructor have no inherited
|
||||||
* inherited enumerable properties and assuming there are no `Object.prototype`
|
* enumerable properties and that there are no `Object.prototype` extensions.
|
||||||
* extensions.
|
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
* @param {Mixed} value The value to check.
|
* @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) {
|
function isPlainObject(value) {
|
||||||
var ctor,
|
// avoid non-objects and false positives for `arguments` objects in IE < 9
|
||||||
result = !!value && toString.call(value) == '[object Object]';
|
var result = false;
|
||||||
|
if (!(value && typeof value == 'object') || isArguments(value)) {
|
||||||
if (result && noArgumentsClass) {
|
return result;
|
||||||
// avoid false positives for `arguments` objects in IE < 9
|
|
||||||
result = !isArguments(value);
|
|
||||||
}
|
}
|
||||||
if (result) {
|
// IE < 9 presents DOM nodes as `Object` objects except they have `toString`
|
||||||
// IE < 9 presents nodes like `Object` objects:
|
// methods that are `typeof` "string" and still can coerce nodes to strings.
|
||||||
// IE < 8 are missing the node's constructor property
|
// Also check that the constructor is `Object` (i.e. `Object instanceof Object`)
|
||||||
// IE 8 node constructors are typeof "object"
|
var ctor = value.constructor;
|
||||||
ctor = value.constructor;
|
if ((support.nodeClass || !(typeof value.toString != 'function' && typeof (value + '') == 'string')) &&
|
||||||
// check if the constructor is `Object` as `Object instanceof Object` is `true`
|
(!isClassOf(ctor, 'Function') || ctor instanceof ctor)) {
|
||||||
if ((result = isClassOf(ctor, 'Function') && ctor instanceof ctor)) {
|
// In most environments an object's own properties are iterated before
|
||||||
// An object's own properties are iterated before inherited properties.
|
// its inherited properties. If the last iterated property is an object's
|
||||||
// If the last iterated key belongs to an object's own property then
|
// own property then there are no inherited enumerable properties.
|
||||||
// there are no inherited enumerable properties.
|
if (support.iteratesOwnFirst) {
|
||||||
forProps(value, function(subValue, subKey) { result = subKey; });
|
forProps(value, function(subValue, subKey) {
|
||||||
result = result === true || hasKey(value, result);
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -1265,7 +1326,7 @@
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case '[object Object]':
|
case '[object Object]':
|
||||||
isObject(value) && (clone = new ctor);
|
isPlainObject(value) && (clone = {});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case '[object Number]':
|
case '[object Number]':
|
||||||
@@ -2030,6 +2091,10 @@
|
|||||||
event.aborted = me.aborted;
|
event.aborted = me.aborted;
|
||||||
},
|
},
|
||||||
'onComplete': function(event) {
|
'onComplete': function(event) {
|
||||||
|
me.score = getGeometricMean(map(me, function(bench) {
|
||||||
|
return bench.reference / (bench.times.period * 1e6);
|
||||||
|
})) || 0;
|
||||||
|
|
||||||
me.running = false;
|
me.running = false;
|
||||||
me.emit(event);
|
me.emit(event);
|
||||||
}
|
}
|
||||||
@@ -2405,13 +2470,12 @@
|
|||||||
|
|
||||||
var source = {
|
var source = {
|
||||||
'setup': getSource(bench.setup, preprocess('m$.setup()')),
|
'setup': getSource(bench.setup, preprocess('m$.setup()')),
|
||||||
'fn': getSource(fn, preprocess('f$(' + fnArg + ')')),
|
'fn': getSource(fn, preprocess('m$.fn(' + fnArg + ')')),
|
||||||
'fnArg': fnArg,
|
'fnArg': fnArg,
|
||||||
'teardown': getSource(bench.teardown, preprocess('m$.teardown()'))
|
'teardown': getSource(bench.teardown, preprocess('m$.teardown()'))
|
||||||
};
|
};
|
||||||
|
|
||||||
var compiled = bench.compiled,
|
var count = bench.count = clone.count,
|
||||||
count = bench.count = clone.count,
|
|
||||||
decompilable = support.decompilation || stringable,
|
decompilable = support.decompilation || stringable,
|
||||||
id = bench.id,
|
id = bench.id,
|
||||||
isEmpty = !(source.fn || stringable),
|
isEmpty = !(source.fn || stringable),
|
||||||
@@ -2433,77 +2497,77 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!compiled) {
|
// Compile in setup/teardown functions and the test loop.
|
||||||
// compile in setup/teardown functions and the test loop
|
// Create a new compiled test, instead of using the cached `bench.compiled`,
|
||||||
compiled = bench.compiled = createFunction(preprocess('t$'), interpolate(
|
// to avoid potential engine optimizations enabled over the life of the test.
|
||||||
preprocess(deferred
|
var compiled = bench.compiled = createFunction(preprocess('t$'), interpolate(
|
||||||
? 'var d$=this,#{fnArg}=d$,m$=d$.benchmark._original,f$=m$.fn,su$=m$.setup,td$=m$.teardown;' +
|
preprocess(deferred
|
||||||
// when `deferred.cycles` is `0` then...
|
? 'var d$=this,#{fnArg}=d$,m$=d$.benchmark._original,f$=m$.fn,su$=m$.setup,td$=m$.teardown;' +
|
||||||
'if(!d$.cycles){' +
|
// when `deferred.cycles` is `0` then...
|
||||||
// set `deferred.fn`
|
'if(!d$.cycles){' +
|
||||||
'd$.fn=function(){var #{fnArg}=d$;if(typeof f$=="function"){try{#{fn}\n}catch(e$){f$(d$)}}else{#{fn}\n}};' +
|
// set `deferred.fn`
|
||||||
// set `deferred.teardown`
|
'd$.fn=function(){var #{fnArg}=d$;if(typeof f$=="function"){try{#{fn}\n}catch(e$){f$(d$)}}else{#{fn}\n}};' +
|
||||||
'd$.teardown=function(){d$.cycles=0;if(typeof td$=="function"){try{#{teardown}\n}catch(e$){td$()}}else{#{teardown}\n}};' +
|
// set `deferred.teardown`
|
||||||
// execute the benchmark's `setup`
|
'd$.teardown=function(){d$.cycles=0;if(typeof td$=="function"){try{#{teardown}\n}catch(e$){td$()}}else{#{teardown}\n}};' +
|
||||||
'if(typeof su$=="function"){try{#{setup}\n}catch(e$){su$()}}else{#{setup}\n};' +
|
// execute the benchmark's `setup`
|
||||||
// start timer
|
'if(typeof su$=="function"){try{#{setup}\n}catch(e$){su$()}}else{#{setup}\n};' +
|
||||||
't$.start(d$);' +
|
// start timer
|
||||||
// execute `deferred.fn` and return a dummy object
|
't$.start(d$);' +
|
||||||
'}d$.fn();return{}'
|
// 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};' +
|
: '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}"}'),
|
'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
|
source
|
||||||
));
|
));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (isEmpty) {
|
// pretest one more time to check for errors
|
||||||
// Firefox may remove dead code from Function#toString results
|
bench.count = 1;
|
||||||
// http://bugzil.la/536085
|
compiled.call(bench, timer);
|
||||||
throw new Error('The test "' + name + '" is empty. This may be the result of dead code removal.');
|
bench.compiled = compiled;
|
||||||
}
|
|
||||||
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;
|
bench.count = count;
|
||||||
|
delete clone.error;
|
||||||
}
|
}
|
||||||
// fallback when a test exits early or errors during pretest
|
catch(e) {
|
||||||
if (decompilable && !compiled && !deferred && !isEmpty) {
|
bench.count = count;
|
||||||
compiled = createFunction(preprocess('t$'), interpolate(
|
if (clone.error) {
|
||||||
preprocess(
|
compiled = null;
|
||||||
(clone.error && !stringable
|
} else {
|
||||||
? '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);
|
|
||||||
bench.compiled = compiled;
|
bench.compiled = compiled;
|
||||||
bench.count = count;
|
clone.error = e || new Error(String(e));
|
||||||
delete clone.error;
|
|
||||||
}
|
|
||||||
catch(e) {
|
|
||||||
bench.count = count;
|
|
||||||
if (clone.error) {
|
|
||||||
compiled = null;
|
|
||||||
} else {
|
|
||||||
bench.compiled = compiled;
|
|
||||||
clone.error = e || new Error(String(e));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2698,20 +2762,18 @@
|
|||||||
sample = bench.stats.sample;
|
sample = bench.stats.sample;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a number of clones to the queue.
|
* Adds a clone to the queue.
|
||||||
*/
|
*/
|
||||||
function enqueue(count) {
|
function enqueue() {
|
||||||
while (count--) {
|
queue.push(bench.clone({
|
||||||
queue.push(bench.clone({
|
'_original': bench,
|
||||||
'_original': bench,
|
'events': {
|
||||||
'events': {
|
'abort': [update],
|
||||||
'abort': [update],
|
'cycle': [update],
|
||||||
'cycle': [update],
|
'error': [update],
|
||||||
'error': [update],
|
'start': [update]
|
||||||
'start': [update]
|
}
|
||||||
}
|
}));
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2817,14 +2879,14 @@
|
|||||||
}
|
}
|
||||||
// if time permits, increase sample size to reduce the margin of error
|
// if time permits, increase sample size to reduce the margin of error
|
||||||
if (queue.length < 2 && !maxedOut) {
|
if (queue.length < 2 && !maxedOut) {
|
||||||
enqueue(1);
|
enqueue();
|
||||||
}
|
}
|
||||||
// abort the invoke cycle when done
|
// abort the invoke cycle when done
|
||||||
event.aborted = done;
|
event.aborted = done;
|
||||||
}
|
}
|
||||||
|
|
||||||
// init queue and begin
|
// init queue and begin
|
||||||
enqueue(minSamples);
|
enqueue();
|
||||||
invoke(queue, {
|
invoke(queue, {
|
||||||
'name': 'run',
|
'name': 'run',
|
||||||
'args': { 'async': async },
|
'args': { 'async': async },
|
||||||
@@ -3048,6 +3110,7 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The maximum time a benchmark is allowed to run before finishing (secs).
|
* The maximum time a benchmark is allowed to run before finishing (secs).
|
||||||
|
*
|
||||||
* Note: Cycle delays aren't counted toward the maximum time.
|
* Note: Cycle delays aren't counted toward the maximum time.
|
||||||
*
|
*
|
||||||
* @memberOf Benchmark.options
|
* @memberOf Benchmark.options
|
||||||
@@ -3125,7 +3188,15 @@
|
|||||||
* @memberOf Benchmark.options
|
* @memberOf Benchmark.options
|
||||||
* @type Function
|
* @type Function
|
||||||
*/
|
*/
|
||||||
'onStart': undefined
|
'onStart': undefined,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The reference time taken to execute the test once (usecs).
|
||||||
|
*
|
||||||
|
* @memberOf Benchmark.options
|
||||||
|
* @type Number
|
||||||
|
*/
|
||||||
|
'reference': 0
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -3221,7 +3292,7 @@
|
|||||||
* @memberOf Benchmark
|
* @memberOf Benchmark
|
||||||
* @type String
|
* @type String
|
||||||
*/
|
*/
|
||||||
'version': '1.0.0-pre',
|
'version': '1.0.0',
|
||||||
|
|
||||||
// an object of environment/feature detection flags
|
// an object of environment/feature detection flags
|
||||||
'support': support,
|
'support': support,
|
||||||
@@ -3682,6 +3753,14 @@
|
|||||||
*/
|
*/
|
||||||
'length': 0,
|
'length': 0,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A score computed using the normalized result of each benchmark in the suite.
|
||||||
|
*
|
||||||
|
* @memberOf Benchmark.Suite
|
||||||
|
* @type Number
|
||||||
|
*/
|
||||||
|
'score': 0,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A flag to indicate if the suite is aborted.
|
* A flag to indicate if the suite is aborted.
|
||||||
*
|
*
|
||||||
|
|||||||
17
vendor/docdown/README.md
vendored
17
vendor/docdown/README.md
vendored
@@ -1,4 +1,4 @@
|
|||||||
# Docdown <sup>v1.0.0-pre</sup>
|
# Docdown <sup>v1.0.0</sup>
|
||||||
|
|
||||||
A simple JSDoc to Markdown documentation generator.
|
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:
|
Usage example:
|
||||||
|
|
||||||
~~~ php
|
```php
|
||||||
require("docdown.php");
|
require("docdown.php");
|
||||||
|
|
||||||
// generate Markdown
|
// generate Markdown
|
||||||
@@ -20,18 +20,7 @@ $markdown = docdown(array(
|
|||||||
"path" => $filepath,
|
"path" => $filepath,
|
||||||
"url" => "https://github.com/username/project/blob/master/my.js"
|
"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
|
## Author
|
||||||
|
|
||||||
|
|||||||
2
vendor/docdown/docdown.php
vendored
2
vendor/docdown/docdown.php
vendored
@@ -8,7 +8,7 @@ require(dirname(__FILE__) . '/src/DocDown/Generator.php');
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates Markdown from JSDoc entries in a given file.
|
* Generates Markdown from JSDoc entries in a given file.
|
||||||
*
|
*
|
||||||
* @param {Array} [$options=array()] The options array.
|
* @param {Array} [$options=array()] The options array.
|
||||||
* @returns {String} The generated Markdown.
|
* @returns {String} The generated Markdown.
|
||||||
* @example
|
* @example
|
||||||
|
|||||||
215
vendor/docdown/src/DocDown/Alias.php
vendored
Normal file
215
vendor/docdown/src/DocDown/Alias.php
vendored
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
<?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->_category = $owner->getCategory();
|
||||||
|
$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 `category` data.
|
||||||
|
*
|
||||||
|
* @memberOf Alias
|
||||||
|
* @returns {String} The owner entry's `category` data.
|
||||||
|
*/
|
||||||
|
public function getCategory() {
|
||||||
|
return $this->_category;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
240
vendor/docdown/src/DocDown/Entry.php
vendored
240
vendor/docdown/src/DocDown/Entry.php
vendored
@@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
require(dirname(__FILE__) . "/Alias.php");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class to simplify parsing a single JSDoc entry.
|
* A class to simplify parsing a single JSDoc entry.
|
||||||
*/
|
*/
|
||||||
@@ -37,7 +39,7 @@ class Entry {
|
|||||||
* @constructor
|
* @constructor
|
||||||
* @param {String} $entry The documentation entry to analyse.
|
* @param {String} $entry The documentation entry to analyse.
|
||||||
* @param {String} $source The source code.
|
* @param {String} $source The source code.
|
||||||
* @param {String} $lang The language highlighter used for code examples.
|
* @param {String} [$lang ='js'] The language highlighter used for code examples.
|
||||||
*/
|
*/
|
||||||
public function __construct( $entry, $source, $lang = 'js' ) {
|
public function __construct( $entry, $source, $lang = 'js' ) {
|
||||||
$this->entry = $entry;
|
$this->entry = $entry;
|
||||||
@@ -70,16 +72,46 @@ class Entry {
|
|||||||
* @returns {Boolean} Returns `true` if the entry is a function reference, else `false`.
|
* @returns {Boolean} Returns `true` if the entry is a function reference, else `false`.
|
||||||
*/
|
*/
|
||||||
private function isFunction() {
|
private function isFunction() {
|
||||||
return !!(
|
if (!isset($this->_isFunction)) {
|
||||||
$this->isCtor() ||
|
$this->_isFunction = !!(
|
||||||
count($this->getParams()) ||
|
$this->isCtor() ||
|
||||||
count($this->getReturns()) ||
|
count($this->getParams()) ||
|
||||||
preg_match('/\*\s*@function\b/', $this->entry)
|
count($this->getReturns()) ||
|
||||||
);
|
preg_match('/\* *@function\b/', $this->entry)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return $this->_isFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*--------------------------------------------------------------------------*/
|
/*--------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the entry's `alias` objects.
|
||||||
|
*
|
||||||
|
* @memberOf Entry
|
||||||
|
* @param {Number} $index The index of the array value to return.
|
||||||
|
* @returns {Array|String} The entry's `alias` objects.
|
||||||
|
*/
|
||||||
|
public function getAliases( $index = null ) {
|
||||||
|
if (!isset($this->_aliases)) {
|
||||||
|
preg_match('#\* *@alias\s+([^\n]+)#', $this->entry, $result);
|
||||||
|
|
||||||
|
if (count($result)) {
|
||||||
|
$result = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1]));
|
||||||
|
$result = preg_split('/,\s*/', $result);
|
||||||
|
natsort($result);
|
||||||
|
|
||||||
|
foreach ($result as $resultIndex => $value) {
|
||||||
|
$result[$resultIndex] = new Alias($value, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->_aliases = $result;
|
||||||
|
}
|
||||||
|
return $index !== null
|
||||||
|
? @$this->_aliases[$index]
|
||||||
|
: $this->_aliases;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts the function call from the entry.
|
* Extracts the function call from the entry.
|
||||||
*
|
*
|
||||||
@@ -87,13 +119,17 @@ class Entry {
|
|||||||
* @returns {String} The function call.
|
* @returns {String} The function call.
|
||||||
*/
|
*/
|
||||||
public function getCall() {
|
public function getCall() {
|
||||||
|
if (isset($this->_call)) {
|
||||||
|
return $this->_call;
|
||||||
|
}
|
||||||
|
|
||||||
preg_match('#\*/\s*(?:function ([^(]*)|(.*?)(?=[:=,]|return\b))#', $this->entry, $result);
|
preg_match('#\*/\s*(?:function ([^(]*)|(.*?)(?=[:=,]|return\b))#', $this->entry, $result);
|
||||||
if ($result = array_pop($result)) {
|
if ($result = array_pop($result)) {
|
||||||
$result = array_pop(explode('var ', trim(trim(array_pop(explode('.', $result))), "'")));
|
$result = array_pop(explode('var ', trim(trim(array_pop(explode('.', $result))), "'")));
|
||||||
}
|
}
|
||||||
// resolve name
|
// resolve name
|
||||||
// avoid $this->getName() because it calls $this->getCall()
|
// avoid $this->getName() because it calls $this->getCall()
|
||||||
preg_match('#\*\s*@name\s+([^\n]+)#', $this->entry, $name);
|
preg_match('#\* *@name\s+([^\n]+)#', $this->entry, $name);
|
||||||
if (count($name)) {
|
if (count($name)) {
|
||||||
$name = trim($name[1]);
|
$name = trim($name[1]);
|
||||||
} else {
|
} else {
|
||||||
@@ -111,178 +147,263 @@ class Entry {
|
|||||||
$result = $name .'('. implode(array_slice($result, 1), ', ') .')';
|
$result = $name .'('. implode(array_slice($result, 1), ', ') .')';
|
||||||
$result = str_replace(', [', ' [, ', str_replace('], [', ', ', $result));
|
$result = str_replace(', [', ' [, ', str_replace('], [', ', ', $result));
|
||||||
}
|
}
|
||||||
return $result ? $result : $name;
|
|
||||||
|
$this->_call = $result ? $result : $name;
|
||||||
|
return $this->_call;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts the entry description.
|
* Extracts the entry's `category` data.
|
||||||
*
|
*
|
||||||
* @memberOf Entry
|
* @memberOf Entry
|
||||||
* @returns {String} The entry description.
|
* @returns {String} The entry's `category` data.
|
||||||
|
*/
|
||||||
|
public function getCategory() {
|
||||||
|
if (isset($this->_category)) {
|
||||||
|
return $this->_category;
|
||||||
|
}
|
||||||
|
|
||||||
|
preg_match('#\* *@category\s+([^\n]+)#', $this->entry, $result);
|
||||||
|
if (count($result)) {
|
||||||
|
$result = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1]));
|
||||||
|
} else {
|
||||||
|
$result = $this->getType() == 'Function' ? 'Methods' : 'Properties';
|
||||||
|
}
|
||||||
|
$this->_category = $result;
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the entry's description.
|
||||||
|
*
|
||||||
|
* @memberOf Entry
|
||||||
|
* @returns {String} The entry's description.
|
||||||
*/
|
*/
|
||||||
public function getDesc() {
|
public function getDesc() {
|
||||||
|
if (isset($this->_desc)) {
|
||||||
|
return $this->_desc;
|
||||||
|
}
|
||||||
|
|
||||||
preg_match('#/\*\*(?:\s*\*)?([\s\S]*?)(?=\*\s\@[a-z]|\*/)#', $this->entry, $result);
|
preg_match('#/\*\*(?:\s*\*)?([\s\S]*?)(?=\*\s\@[a-z]|\*/)#', $this->entry, $result);
|
||||||
if (count($result)) {
|
if (count($result)) {
|
||||||
$type = $this->getType();
|
$type = $this->getType();
|
||||||
$result = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1]));
|
$result = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1]));
|
||||||
$result = ($type == 'Function' ? '' : '(' . str_replace('|', ', ', trim($type, '{}')) . '): ') . $result;
|
$result = ($type == 'Function' ? '' : '(' . str_replace('|', ', ', trim($type, '{}')) . '): ') . $result;
|
||||||
}
|
}
|
||||||
|
$this->_desc = $result;
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts the entry `example` data.
|
* Extracts the entry's `example` data.
|
||||||
*
|
*
|
||||||
* @memberOf Entry
|
* @memberOf Entry
|
||||||
* @returns {String} The entry `example` data.
|
* @returns {String} The entry's `example` data.
|
||||||
*/
|
*/
|
||||||
public function getExample() {
|
public function getExample() {
|
||||||
preg_match('#\*\s*@example\s+([\s\S]*?)(?=\*\s\@[a-z]|\*/)#', $this->entry, $result);
|
if (isset($this->_example)) {
|
||||||
|
return $this->_example;
|
||||||
|
}
|
||||||
|
|
||||||
|
preg_match('#\* *@example\s+([\s\S]*?)(?=\*\s\@[a-z]|\*/)#', $this->entry, $result);
|
||||||
if (count($result)) {
|
if (count($result)) {
|
||||||
$result = trim(preg_replace('/(?:^|\n)\s*\* ?/', "\n", $result[1]));
|
$result = trim(preg_replace('/(?:^|\n)\s*\* ?/', "\n", $result[1]));
|
||||||
$result = '~~~ ' . $this->lang . "\n" . $result . "\n~~~";
|
$result = '```' . $this->lang . "\n" . $result . "\n```";
|
||||||
}
|
}
|
||||||
|
$this->_example = $result;
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves the line number of the entry.
|
* Resolves the entry's line number.
|
||||||
*
|
*
|
||||||
* @memberOf Entry
|
* @memberOf Entry
|
||||||
* @returns {Number} The line number.
|
* @returns {Number} The entry's line number.
|
||||||
*/
|
*/
|
||||||
public function getLineNumber() {
|
public function getLineNumber() {
|
||||||
preg_match_all('/\n/', substr($this->source, 0, strrpos($this->source, $this->entry) + strlen($this->entry)), $lines);
|
if (!isset($this->_lineNumber)) {
|
||||||
return count(array_pop($lines)) + 1;
|
preg_match_all('/\n/', substr($this->source, 0, strrpos($this->source, $this->entry) + strlen($this->entry)), $lines);
|
||||||
|
$this->_lineNumber = count(array_pop($lines)) + 1;
|
||||||
|
}
|
||||||
|
return $this->_lineNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts the entry `member` data.
|
* Extracts the entry's `member` data.
|
||||||
*
|
*
|
||||||
* @memberOf Entry
|
* @memberOf Entry
|
||||||
* @param {Number} $index The index of the array value to return.
|
* @param {Number} $index The index of the array value to return.
|
||||||
* @returns {Array|String} The entry `member` data.
|
* @returns {Array|String} The entry's `member` data.
|
||||||
*/
|
*/
|
||||||
public function getMembers( $index = null ) {
|
public function getMembers( $index = null ) {
|
||||||
preg_match('#\*\s*@member(?:Of)?\s+([^\n]+)#', $this->entry, $result);
|
if (!isset($this->_members)) {
|
||||||
if (count($result)) {
|
preg_match('#\* *@member(?:Of)?\s+([^\n]+)#', $this->entry, $result);
|
||||||
$result = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1]));
|
if (count($result)) {
|
||||||
$result = preg_split('/,\s*/', $result);
|
$result = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1]));
|
||||||
|
$result = preg_split('/,\s*/', $result);
|
||||||
|
natsort($result);
|
||||||
|
}
|
||||||
|
$this->_members = $result;
|
||||||
}
|
}
|
||||||
return $index !== null ? @$result[$index] : $result;
|
return $index !== null
|
||||||
|
? @$this->_members[$index]
|
||||||
|
: $this->_members;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts the entry `name` data.
|
* Extracts the entry's `name` data.
|
||||||
*
|
*
|
||||||
* @memberOf Entry
|
* @memberOf Entry
|
||||||
* @returns {String} The entry `name` data.
|
* @returns {String} The entry's `name` data.
|
||||||
*/
|
*/
|
||||||
public function getName() {
|
public function getName() {
|
||||||
preg_match('#\*\s*@name\s+([^\n]+)#', $this->entry, $result);
|
if (isset($this->_name)) {
|
||||||
|
return $this->_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
preg_match('#\* *@name\s+([^\n]+)#', $this->entry, $result);
|
||||||
if (count($result)) {
|
if (count($result)) {
|
||||||
$result = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1]));
|
$result = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1]));
|
||||||
} else {
|
} else {
|
||||||
$result = array_shift(explode('(', $this->getCall()));
|
$result = array_shift(explode('(', $this->getCall()));
|
||||||
}
|
}
|
||||||
|
$this->_name = $result;
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts the entry `param` data.
|
* Extracts the entry's `param` data.
|
||||||
*
|
*
|
||||||
* @memberOf Entry
|
* @memberOf Entry
|
||||||
* @param {Number} $index The index of the array value to return.
|
* @param {Number} $index The index of the array value to return.
|
||||||
* @returns {Array} The entry `param` data.
|
* @returns {Array} The entry's `param` data.
|
||||||
*/
|
*/
|
||||||
public function getParams( $index = null ) {
|
public function getParams( $index = null ) {
|
||||||
preg_match_all('#\*\s*@param\s+\{([^}]+)\}\s+(\[[^]]+\]|[$\w]+)\s+([\s\S]*?)(?=\*\s\@[a-z]|\*/)#i', $this->entry, $result);
|
if (!isset($this->_params)) {
|
||||||
if (count($result = array_filter(array_slice($result, 1)))) {
|
preg_match_all('#\* *@param\s+\{([^}]+)\}\s+(\[.+\]|[$\w|]+(?:\[.+\])?)\s+([\s\S]*?)(?=\*\s\@[a-z]|\*/)#i', $this->entry, $result);
|
||||||
// repurpose array
|
if (count($result = array_filter(array_slice($result, 1)))) {
|
||||||
foreach ($result as $param) {
|
// repurpose array
|
||||||
foreach ($param as $key => $value) {
|
foreach ($result as $param) {
|
||||||
if (!is_array($result[0][$key])) {
|
foreach ($param as $key => $value) {
|
||||||
$result[0][$key] = array();
|
if (!is_array($result[0][$key])) {
|
||||||
|
$result[0][$key] = array();
|
||||||
|
}
|
||||||
|
$result[0][$key][] = trim(preg_replace('/(?:^|\n)\s*\* */', ' ', $value));
|
||||||
}
|
}
|
||||||
$result[0][$key][] = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $value));
|
|
||||||
}
|
}
|
||||||
|
$result = $result[0];
|
||||||
}
|
}
|
||||||
$result = $result[0];
|
$this->_params = $result;
|
||||||
}
|
}
|
||||||
return $index !== null ? @$result[$index] : $result;
|
return $index !== null
|
||||||
|
? @$this->_params[$index]
|
||||||
|
: $this->_params;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts the entry `returns` data.
|
* Extracts the entry's `returns` data.
|
||||||
*
|
*
|
||||||
* @memberOf Entry
|
* @memberOf Entry
|
||||||
* @returns {String} The entry `returns` data.
|
* @returns {String} The entry's `returns` data.
|
||||||
*/
|
*/
|
||||||
public function getReturns() {
|
public function getReturns() {
|
||||||
preg_match('#\*\s*@returns\s+\{([^}]+)\}\s+([\s\S]*?)(?=\*\s\@[a-z]|\*/)#', $this->entry, $result);
|
if (isset($this->_returns)) {
|
||||||
|
return $this->_returns;
|
||||||
|
}
|
||||||
|
|
||||||
|
preg_match('#\* *@returns\s+\{([^}]+)\}\s+([\s\S]*?)(?=\*\s\@[a-z]|\*/)#', $this->entry, $result);
|
||||||
if (count($result)) {
|
if (count($result)) {
|
||||||
$result = array_map('trim', array_slice($result, 1));
|
$result = array_map('trim', array_slice($result, 1));
|
||||||
$result[0] = str_replace('|', ', ', $result[0]);
|
$result[0] = str_replace('|', ', ', $result[0]);
|
||||||
$result[1] = preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1]);
|
$result[1] = preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1]);
|
||||||
}
|
}
|
||||||
|
$this->_returns = $result;
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts the entry `type` data.
|
* Extracts the entry's `type` data.
|
||||||
*
|
*
|
||||||
* @memberOf Entry
|
* @memberOf Entry
|
||||||
* @returns {String} The entry `type` data.
|
* @returns {String} The entry's `type` data.
|
||||||
*/
|
*/
|
||||||
public function getType() {
|
public function getType() {
|
||||||
preg_match('#\*\s*@type\s+([^\n]+)#', $this->entry, $result);
|
if (isset($this->_type)) {
|
||||||
|
return $this->_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
preg_match('#\* *@type\s+([^\n]+)#', $this->entry, $result);
|
||||||
if (count($result)) {
|
if (count($result)) {
|
||||||
$result = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1]));
|
$result = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1]));
|
||||||
} else {
|
} else {
|
||||||
$result = $this->isFunction() ? 'Function' : 'Unknown';
|
$result = $this->isFunction() ? 'Function' : 'Unknown';
|
||||||
}
|
}
|
||||||
|
$this->_type = $result;
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if an entry is a constructor.
|
* Checks if the entry is an alias.
|
||||||
*
|
*
|
||||||
* @memberOf Entry
|
* @memberOf Entry
|
||||||
* @returns {Boolean} Returns true if a constructor, else false.
|
* @returns {Boolean} Returns `false`.
|
||||||
|
*/
|
||||||
|
public function isAlias() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the entry is a constructor.
|
||||||
|
*
|
||||||
|
* @memberOf Entry
|
||||||
|
* @returns {Boolean} Returns `true` if a constructor, else `false`.
|
||||||
*/
|
*/
|
||||||
public function isCtor() {
|
public function isCtor() {
|
||||||
return !!preg_match('/\*\s*@constructor\b/', $this->entry);
|
if (!isset($this->_isCtor)) {
|
||||||
|
$this->_isCtor = !!preg_match('/\* *@constructor\b/', $this->entry);
|
||||||
|
}
|
||||||
|
return $this->_isCtor;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if an entry *is* assigned to a prototype.
|
* Checks if the entry *is* assigned to a prototype.
|
||||||
*
|
*
|
||||||
* @memberOf Entry
|
* @memberOf Entry
|
||||||
* @returns {Boolean} Returns true if assigned to a prototype, else false.
|
* @returns {Boolean} Returns `true` if assigned to a prototype, else `false`.
|
||||||
*/
|
*/
|
||||||
public function isPlugin() {
|
public function isPlugin() {
|
||||||
return !$this->isCtor() && !$this->isPrivate() && !$this->isStatic();
|
if (!isset($this->_isPlugin)) {
|
||||||
|
$this->_isPlugin = !$this->isCtor() && !$this->isPrivate() && !$this->isStatic();
|
||||||
|
}
|
||||||
|
return $this->_isPlugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if an entry is private.
|
* Checks if the entry is private.
|
||||||
*
|
*
|
||||||
* @memberOf Entry
|
* @memberOf Entry
|
||||||
* @returns {Boolean} Returns true if private, else false.
|
* @returns {Boolean} Returns `true` if private, else `false`.
|
||||||
*/
|
*/
|
||||||
public function isPrivate() {
|
public function isPrivate() {
|
||||||
return !!preg_match('/\*\s*@private\b/', $this->entry) || strrpos($this->entry, '@') === false;
|
if (!isset($this->_isPrivate)) {
|
||||||
|
$this->_isPrivate = !!preg_match('/\* *@private\b/', $this->entry) || !preg_match('/\* *@[a-z]+\b/', $this->entry);
|
||||||
|
}
|
||||||
|
return $this->_isPrivate;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if an entry is *not* assigned to a prototype.
|
* Checks if the entry is *not* assigned to a prototype.
|
||||||
*
|
*
|
||||||
* @memberOf Entry
|
* @memberOf Entry
|
||||||
* @returns {Boolean} Returns true if not assigned to a prototype, else false.
|
* @returns {Boolean} Returns `true` if not assigned to a prototype, else `false`.
|
||||||
*/
|
*/
|
||||||
public function isStatic() {
|
public function isStatic() {
|
||||||
|
if (isset($this->_isStatic)) {
|
||||||
|
return $this->_isStatic;
|
||||||
|
}
|
||||||
|
|
||||||
$public = !$this->isPrivate();
|
$public = !$this->isPrivate();
|
||||||
$result = $public && !!preg_match('/\*\s*@static\b/', $this->entry);
|
$result = $public && !!preg_match('/\* *@static\b/', $this->entry);
|
||||||
|
|
||||||
// set in cases where it isn't explicitly stated
|
// set in cases where it isn't explicitly stated
|
||||||
if ($public && !$result) {
|
if ($public && !$result) {
|
||||||
@@ -298,6 +419,7 @@ class Entry {
|
|||||||
$result = true;
|
$result = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$this->_isStatic = $result;
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
385
vendor/docdown/src/DocDown/Generator.php
vendored
385
vendor/docdown/src/DocDown/Generator.php
vendored
@@ -7,6 +7,15 @@ require(dirname(__FILE__) . "/Entry.php");
|
|||||||
*/
|
*/
|
||||||
class Generator {
|
class Generator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The HTML for the close tag.
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
* @memberOf Generator
|
||||||
|
* @type String
|
||||||
|
*/
|
||||||
|
public $closeTag = "\n<!-- /div -->\n";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An array of JSDoc entries.
|
* An array of JSDoc entries.
|
||||||
*
|
*
|
||||||
@@ -15,6 +24,15 @@ class Generator {
|
|||||||
*/
|
*/
|
||||||
public $entries = array();
|
public $entries = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The HTML for the open tag.
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
* @memberOf Generator
|
||||||
|
* @type String
|
||||||
|
*/
|
||||||
|
public $openTag = "\n<!-- div -->\n";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An options array used to configure the generator.
|
* An options array used to configure the generator.
|
||||||
*
|
*
|
||||||
@@ -24,7 +42,7 @@ class Generator {
|
|||||||
public $options = array();
|
public $options = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The entire file's source code.
|
* The file's source code.
|
||||||
*
|
*
|
||||||
* @memberOf Generator
|
* @memberOf Generator
|
||||||
* @type String
|
* @type String
|
||||||
@@ -65,6 +83,9 @@ class Generator {
|
|||||||
if (!isset($options['lang'])) {
|
if (!isset($options['lang'])) {
|
||||||
$options['lang'] = 'js';
|
$options['lang'] = 'js';
|
||||||
}
|
}
|
||||||
|
if (!isset($options['toc'])) {
|
||||||
|
$options['toc'] = 'properties';
|
||||||
|
}
|
||||||
|
|
||||||
$this->options = $options;
|
$this->options = $options;
|
||||||
$this->source = str_replace(PHP_EOL, "\n", $options['source']);
|
$this->source = str_replace(PHP_EOL, "\n", $options['source']);
|
||||||
@@ -86,7 +107,7 @@ class Generator {
|
|||||||
* @param {String} $string The string to format.
|
* @param {String} $string The string to format.
|
||||||
* @returns {String} The formatted string.
|
* @returns {String} The formatted string.
|
||||||
*/
|
*/
|
||||||
private static function format($string) {
|
private static function format( $string ) {
|
||||||
$counter = 0;
|
$counter = 0;
|
||||||
|
|
||||||
// tokenize inline code snippets
|
// tokenize inline code snippets
|
||||||
@@ -121,7 +142,7 @@ class Generator {
|
|||||||
* @param {Array|Object} $object The template object.
|
* @param {Array|Object} $object The template object.
|
||||||
* @returns {String} The modified string.
|
* @returns {String} The modified string.
|
||||||
*/
|
*/
|
||||||
private static function interpolate($string, $object) {
|
private static function interpolate( $string, $object ) {
|
||||||
preg_match_all('/#\{([^}]+)\}/', $string, $tokens);
|
preg_match_all('/#\{([^}]+)\}/', $string, $tokens);
|
||||||
$tokens = array_unique(array_pop($tokens));
|
$tokens = array_unique(array_pop($tokens));
|
||||||
|
|
||||||
@@ -149,6 +170,63 @@ class Generator {
|
|||||||
|
|
||||||
/*--------------------------------------------------------------------------*/
|
/*--------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the given `$entries` to the `$result` array.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @memberOf Generator
|
||||||
|
* @param {Array} $result The result array to modify.
|
||||||
|
* @param {Array} $entries The entries to add to the `$result`.
|
||||||
|
*/
|
||||||
|
private function addEntries( &$result, $entries ) {
|
||||||
|
foreach ($entries as $entry) {
|
||||||
|
// skip aliases
|
||||||
|
if ($entry->isAlias()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// name and description
|
||||||
|
array_push(
|
||||||
|
$result,
|
||||||
|
$this->openTag,
|
||||||
|
Generator::interpolate("### <a id=\"#{hash}\"></a>`#{member}#{separator}#{call}`\n<a href=\"##{hash}\">#</a> [Ⓢ](#{href} \"View in source\") [Ⓣ][1]\n\n#{desc}", $entry)
|
||||||
|
);
|
||||||
|
|
||||||
|
// @alias
|
||||||
|
if (count($aliases = $entry->getAliases())) {
|
||||||
|
array_push($result, '', '#### Aliases');
|
||||||
|
foreach ($aliases as $index => $alias) {
|
||||||
|
$aliases[$index] = $alias->getName();
|
||||||
|
}
|
||||||
|
$result[] = '*' . implode(', ', $aliases) . '*';
|
||||||
|
}
|
||||||
|
// @param
|
||||||
|
if (count($params = $entry->getParams())) {
|
||||||
|
array_push($result, '', '#### Arguments');
|
||||||
|
foreach ($params as $index => $param) {
|
||||||
|
$result[] = Generator::interpolate('#{num}. `#{name}` (#{type}): #{desc}', array(
|
||||||
|
'desc' => $param[2],
|
||||||
|
'name' => $param[1],
|
||||||
|
'num' => $index + 1,
|
||||||
|
'type' => $param[0]
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// @returns
|
||||||
|
if (count($returns = $entry->getReturns())) {
|
||||||
|
array_push(
|
||||||
|
$result, '',
|
||||||
|
'#### Returns',
|
||||||
|
Generator::interpolate('(#{type}): #{desc}', array('desc' => $returns[1], 'type' => $returns[0]))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// @example
|
||||||
|
if ($example = $entry->getExample()) {
|
||||||
|
array_push($result, '', '#### Example', $example);
|
||||||
|
}
|
||||||
|
array_push($result, "\n* * *", $this->closeTag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves the entry's hash used to navigate the documentation.
|
* Resolves the entry's hash used to navigate the documentation.
|
||||||
*
|
*
|
||||||
@@ -163,7 +241,7 @@ class Generator {
|
|||||||
$member = !$member ? $entry->getMembers(0) : $member;
|
$member = !$member ? $entry->getMembers(0) : $member;
|
||||||
$result = ($member ? $member . ($entry->isPlugin() ? 'prototype' : '') : '') . $entry->getCall();
|
$result = ($member ? $member . ($entry->isPlugin() ? 'prototype' : '') : '') . $entry->getCall();
|
||||||
$result = preg_replace('/\(\[|\[\]/', '', $result);
|
$result = preg_replace('/\(\[|\[\]/', '', $result);
|
||||||
$result = preg_replace('/[ =\'"{}.()\]]/', '', $result);
|
$result = preg_replace('/[ =|\'"{}.()\]]/', '', $result);
|
||||||
$result = preg_replace('/[[#,]/', '-', $result);
|
$result = preg_replace('/[[#,]/', '-', $result);
|
||||||
return strtolower($result);
|
return strtolower($result);
|
||||||
}
|
}
|
||||||
@@ -204,43 +282,85 @@ class Generator {
|
|||||||
*/
|
*/
|
||||||
public function generate() {
|
public function generate() {
|
||||||
$api = array();
|
$api = array();
|
||||||
|
$byCategory = $this->options['toc'] == 'categories';
|
||||||
|
$categories = array();
|
||||||
|
$closeTag = $this->closeTag;
|
||||||
$compiling = false;
|
$compiling = false;
|
||||||
$openTag = "\n<!-- div -->\n";
|
$openTag = $this->openTag;
|
||||||
$closeTag = "\n<!-- /div -->\n";
|
|
||||||
$result = array('# ' . $this->options['title']);
|
$result = array('# ' . $this->options['title']);
|
||||||
|
$toc = 'toc';
|
||||||
|
|
||||||
// initialize $api array
|
// initialize $api array
|
||||||
foreach ($this->entries as $entry) {
|
foreach ($this->entries as $entry) {
|
||||||
|
// skip invalid or private entries
|
||||||
|
$name = $entry->getName();
|
||||||
|
if (!$name || $entry->isPrivate()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!$entry->isPrivate()) {
|
$members = $entry->getMembers();
|
||||||
$name = $entry->getName();
|
$members = count($members) ? $members : array('');
|
||||||
$members = $entry->getMembers();
|
|
||||||
$members = count($members) ? $members : array('');
|
|
||||||
|
|
||||||
foreach ($members as $member) {
|
foreach ($members as $member) {
|
||||||
// create api category arrays
|
// create api category arrays
|
||||||
if (!isset($api[$member]) && $member) {
|
if ($member && !isset($api[$member])) {
|
||||||
$api[$member] = new Entry('', '', $entry->lang);
|
// create temporary entry to be replaced later
|
||||||
$api[$member]->static = array();
|
$api[$member] = new stdClass;
|
||||||
$api[$member]->plugin = array();
|
$api[$member]->static = array();
|
||||||
|
$api[$member]->plugin = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
// append entry to api member
|
||||||
|
if (!$member || $entry->isCtor() || ($entry->getType() == 'Object' &&
|
||||||
|
!preg_match('/[=:]\s*(?:null|undefined)\s*[,;]?$/', $entry->entry))) {
|
||||||
|
|
||||||
|
// assign the real entry, replacing the temporary entry if it exist
|
||||||
|
$member = ($member ? $member . ($entry->isPlugin() ? '#' : '.') : '') . $name;
|
||||||
|
$entry->static = @$api[$member] ? $api[$member]->static : array();
|
||||||
|
$entry->plugin = @$api[$member] ? $api[$member]->plugin : array();
|
||||||
|
|
||||||
|
$api[$member] = $entry;
|
||||||
|
foreach ($entry->getAliases() as $alias) {
|
||||||
|
$api[$member] = $alias;
|
||||||
|
$alias->static = array();
|
||||||
|
$alias->plugin = array();
|
||||||
}
|
}
|
||||||
// append entry to api category
|
}
|
||||||
if (!$member || $entry->isCtor() || ($entry->getType() == 'Object' &&
|
else if ($entry->isStatic()) {
|
||||||
!preg_match('/[=:]\s*null\s*[,;]?$/', $entry->entry))) {
|
$api[$member]->static[] = $entry;
|
||||||
$member = ($member ? $member . ($entry->isPlugin() ? '#' : '.') : '') . $name;
|
foreach ($entry->getAliases() as $alias) {
|
||||||
$entry->static = @$api[$member] ? $api[$member]->static : array();
|
$api[$member]->static[] = $alias;
|
||||||
$entry->plugin = @$api[$member] ? $api[$member]->plugin : array();
|
|
||||||
$api[$member] = $entry;
|
|
||||||
}
|
}
|
||||||
else if ($entry->isStatic()) {
|
}
|
||||||
$api[$member]->static[] = $entry;
|
else if (!$entry->isCtor()) {
|
||||||
} else if (!$entry->isCtor()) {
|
$api[$member]->plugin[] = $entry;
|
||||||
$api[$member]->plugin[] = $entry;
|
foreach ($entry->getAliases() as $alias) {
|
||||||
|
$api[$member]->plugin[] = $alias;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add properties to each entry
|
||||||
|
foreach ($api as $entry) {
|
||||||
|
$entry->hash = $this->getHash($entry);
|
||||||
|
$entry->href = $this->getLineUrl($entry);
|
||||||
|
|
||||||
|
$member = $entry->getMembers(0);
|
||||||
|
$member = ($member ? $member . ($entry->isPlugin() ? '.prototype.' : '.') : '') . $entry->getName();
|
||||||
|
$entry->member = preg_replace('/' . $entry->getName() . '$/', '', $member);
|
||||||
|
|
||||||
|
// add properties to static and plugin sub-entries
|
||||||
|
foreach (array('static', 'plugin') as $kind) {
|
||||||
|
foreach ($entry->{$kind} as $subentry) {
|
||||||
|
$subentry->hash = $this->getHash($subentry);
|
||||||
|
$subentry->href = $this->getLineUrl($subentry);
|
||||||
|
$subentry->member = $member;
|
||||||
|
$subentry->separator = $this->getSeparator($subentry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*------------------------------------------------------------------------*/
|
/*------------------------------------------------------------------------*/
|
||||||
|
|
||||||
// custom sort for root level entries
|
// custom sort for root level entries
|
||||||
@@ -248,19 +368,19 @@ class Generator {
|
|||||||
function sortCompare($a, $b) {
|
function sortCompare($a, $b) {
|
||||||
$score = array( 'a' => 0, 'b' => 0);
|
$score = array( 'a' => 0, 'b' => 0);
|
||||||
foreach (array( 'a' => $a, 'b' => $b) as $key => $value) {
|
foreach (array( 'a' => $a, 'b' => $b) as $key => $value) {
|
||||||
// capitalized keys that represent constructor properties are last
|
// capitalized properties are last
|
||||||
if (preg_match('/[#.][A-Z]/', $value)) {
|
if (preg_match('/[#.][A-Z]/', $value)) {
|
||||||
$score[$key] = 0;
|
$score[$key] = 0;
|
||||||
}
|
}
|
||||||
// lowercase keys with prototype properties are next to last
|
// lowercase prototype properties are next to last
|
||||||
else if (preg_match('/#[a-z]/', $value)) {
|
else if (preg_match('/#[a-z]/', $value)) {
|
||||||
$score[$key] = 1;
|
$score[$key] = 1;
|
||||||
}
|
}
|
||||||
// lowercase keys with static properties next to first
|
// lowercase static properties next to first
|
||||||
else if (preg_match('/\.[a-z]/', $value)) {
|
else if (preg_match('/\.[a-z]/', $value)) {
|
||||||
$score[$key] = 2;
|
$score[$key] = 2;
|
||||||
}
|
}
|
||||||
// lowercase keys with no properties are first
|
// root properties are first
|
||||||
else if (preg_match('/^[^#.]+$/', $value)) {
|
else if (preg_match('/^[^#.]+$/', $value)) {
|
||||||
$score[$key] = 3;
|
$score[$key] = 3;
|
||||||
}
|
}
|
||||||
@@ -290,43 +410,90 @@ class Generator {
|
|||||||
|
|
||||||
/*------------------------------------------------------------------------*/
|
/*------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
// add categories
|
||||||
|
foreach ($api as $entry) {
|
||||||
|
$categories[$entry->getCategory()][] = $entry;
|
||||||
|
foreach (array('static', 'plugin') as $kind) {
|
||||||
|
foreach ($entry->{$kind} as $subentry) {
|
||||||
|
$categories[$subentry->getCategory()][] = $subentry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort categories
|
||||||
|
ksort($categories);
|
||||||
|
|
||||||
|
foreach(array('Methods', 'Properties') as $category) {
|
||||||
|
if (isset($categories[$category])) {
|
||||||
|
$entries = $categories[$category];
|
||||||
|
unset($categories[$category]);
|
||||||
|
$categories[$category] = $entries;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------------*/
|
||||||
|
|
||||||
// compile TOC
|
// compile TOC
|
||||||
$result[] = $openTag;
|
$result[] = $openTag;
|
||||||
|
|
||||||
foreach ($api as $key => $entry) {
|
// compile TOC by categories
|
||||||
$entry->hash = $this->getHash($entry);
|
if ($byCategory) {
|
||||||
$entry->href = $this->getLineUrl($entry);
|
foreach ($categories as $category => $entries) {
|
||||||
|
if ($compiling) {
|
||||||
$member = $entry->getMembers(0);
|
$result[] = $closeTag;
|
||||||
$member = ($member ? $member . ($entry->isPlugin() ? '.prototype.' : '.') : '') . $entry->getName();
|
} else {
|
||||||
|
$compiling = true;
|
||||||
$entry->member = preg_replace('/' . $entry->getName() . '$/', '', $member);
|
|
||||||
|
|
||||||
$compiling = $compiling ? ($result[] = $closeTag) : true;
|
|
||||||
|
|
||||||
// add root entry
|
|
||||||
array_push(
|
|
||||||
$result,
|
|
||||||
$openTag, '## ' . (count($result) == 2 ? '<a id="toc"></a>' : '') . '`' . $member . '`',
|
|
||||||
Generator::interpolate('* [`' . $member . '`](##{hash})', $entry)
|
|
||||||
);
|
|
||||||
|
|
||||||
// add static and plugin sub-entries
|
|
||||||
foreach (array('static', 'plugin') as $kind) {
|
|
||||||
if ($kind == 'plugin' && count($entry->plugin)) {
|
|
||||||
array_push(
|
|
||||||
$result,
|
|
||||||
$closeTag,
|
|
||||||
$openTag,
|
|
||||||
'## `' . $member . ($entry->isCtor() ? '.prototype`' : '`')
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
foreach ($entry->{$kind} as $subentry) {
|
// assign TOC hash
|
||||||
$subentry->hash = $this->getHash($subentry);
|
if (count($result) == 2) {
|
||||||
$subentry->href = $this->getLineUrl($subentry);
|
$toc = $category;
|
||||||
$subentry->member = $member;
|
}
|
||||||
$subentry->separator = $this->getSeparator($subentry);
|
// add category
|
||||||
$result[] = Generator::interpolate('* [`#{member}#{separator}#{name}`](##{hash})', $subentry);
|
array_push(
|
||||||
|
$result,
|
||||||
|
$openTag, '## ' . (count($result) == 2 ? '<a id="' . $toc . '"></a>' : '') . '`' . $category . '`'
|
||||||
|
);
|
||||||
|
// add entries
|
||||||
|
foreach ($entries as $entry) {
|
||||||
|
$result[] = Generator::interpolate('* [`#{member}#{separator}#{name}`](##{hash})', $entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// compile TOC by namespace
|
||||||
|
else {
|
||||||
|
foreach ($api as $entry) {
|
||||||
|
if ($compiling) {
|
||||||
|
$result[] = $closeTag;
|
||||||
|
} else {
|
||||||
|
$compiling = true;
|
||||||
|
}
|
||||||
|
$member = $entry->member . $entry->getName();
|
||||||
|
|
||||||
|
// assign TOC hash
|
||||||
|
if (count($result) == 2) {
|
||||||
|
$toc = $member;
|
||||||
|
}
|
||||||
|
// add root entry
|
||||||
|
array_push(
|
||||||
|
$result,
|
||||||
|
$openTag, '## ' . (count($result) == 2 ? '<a id="' . $toc . '"></a>' : '') . '`' . $member . '`',
|
||||||
|
Generator::interpolate('* [`' . $member . '`](##{hash})', $entry)
|
||||||
|
);
|
||||||
|
|
||||||
|
// add static and plugin sub-entries
|
||||||
|
foreach (array('static', 'plugin') as $kind) {
|
||||||
|
if ($kind == 'plugin' && count($entry->plugin)) {
|
||||||
|
array_push(
|
||||||
|
$result,
|
||||||
|
$closeTag,
|
||||||
|
$openTag,
|
||||||
|
'## `' . $member . ($entry->isCtor() ? '.prototype`' : '`')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
foreach ($entry->{$kind} as $subentry) {
|
||||||
|
$subentry->member = $member;
|
||||||
|
$result[] = Generator::interpolate('* [`#{member}#{separator}#{name}`](##{hash})', $subentry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -339,69 +506,57 @@ class Generator {
|
|||||||
$compiling = false;
|
$compiling = false;
|
||||||
$result[] = $openTag;
|
$result[] = $openTag;
|
||||||
|
|
||||||
foreach ($api as $entry) {
|
if ($byCategory) {
|
||||||
// add root entry
|
foreach ($categories as $category => $entries) {
|
||||||
$member = $entry->member . $entry->getName();
|
if ($compiling) {
|
||||||
$compiling = $compiling ? ($result[] = $closeTag) : true;
|
$result[] = $closeTag;
|
||||||
|
} else {
|
||||||
array_push($result, $openTag, '## `' . $member . '`');
|
$compiling = true;
|
||||||
|
|
||||||
foreach (array($entry, 'static', 'plugin') as $kind) {
|
|
||||||
$subentries = is_string($kind) ? $entry->{$kind} : array($kind);
|
|
||||||
|
|
||||||
// title
|
|
||||||
if ($kind != 'static' && $entry->getType() != 'Object' &&
|
|
||||||
count($subentries) && $subentries[0] != $kind) {
|
|
||||||
if ($kind == 'plugin') {
|
|
||||||
$result[] = $closeTag;
|
|
||||||
}
|
|
||||||
array_push(
|
|
||||||
$result,
|
|
||||||
$openTag,
|
|
||||||
'## `' . $member . ($kind == 'plugin' ? '.prototype`' : '`')
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
if ($category != 'Methods' && $category != 'Properties') {
|
||||||
|
$category = '“' . $category . '” Methods';
|
||||||
|
}
|
||||||
|
array_push($result, $openTag, '## `' . $category . '`');
|
||||||
|
$this->addEntries($result, $entries);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
foreach ($api as $entry) {
|
||||||
|
// skip aliases
|
||||||
|
if ($entry->isAlias()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($compiling) {
|
||||||
|
$result[] = $closeTag;
|
||||||
|
} else {
|
||||||
|
$compiling = true;
|
||||||
|
}
|
||||||
|
// add root entry name
|
||||||
|
$member = $entry->member . $entry->getName();
|
||||||
|
array_push($result, $openTag, '## `' . $member . '`');
|
||||||
|
|
||||||
// body
|
foreach (array($entry, 'static', 'plugin') as $kind) {
|
||||||
foreach ($subentries as $subentry) {
|
$subentries = is_string($kind) ? $entry->{$kind} : array($kind);
|
||||||
// description
|
|
||||||
array_push(
|
|
||||||
$result,
|
|
||||||
$openTag,
|
|
||||||
Generator::interpolate("### <a id=\"#{hash}\"></a>`#{member}#{separator}#{call}`\n<a href=\"##{hash}\">#</a> [Ⓢ](#{href} \"View in source\") [Ⓣ][1]\n\n#{desc}", $subentry)
|
|
||||||
);
|
|
||||||
|
|
||||||
// @param
|
// add sub-entry name
|
||||||
if (count($params = $subentry->getParams())) {
|
if ($kind != 'static' && $entry->getType() != 'Object' &&
|
||||||
array_push($result, '', '#### Arguments');
|
count($subentries) && $subentries[0] != $kind) {
|
||||||
foreach ($params as $index => $param) {
|
if ($kind == 'plugin') {
|
||||||
$result[] = Generator::interpolate('#{num}. `#{name}` (#{type}): #{desc}', array(
|
$result[] = $closeTag;
|
||||||
'desc' => $param[2],
|
|
||||||
'name' => $param[1],
|
|
||||||
'num' => $index + 1,
|
|
||||||
'type' => $param[0]
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// @returns
|
|
||||||
if (count($returns = $subentry->getReturns())) {
|
|
||||||
array_push(
|
array_push(
|
||||||
$result, '',
|
$result,
|
||||||
'#### Returns',
|
$openTag,
|
||||||
Generator::interpolate('(#{type}): #{desc}', array('desc' => $returns[1], 'type' => $returns[0]))
|
'## `' . $member . ($kind == 'plugin' ? '.prototype`' : '`')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// @example
|
$this->addEntries($result, $subentries);
|
||||||
if ($example = $subentry->getExample()) {
|
|
||||||
array_push($result, '', '#### Example', $example);
|
|
||||||
}
|
|
||||||
array_push($result, "\n* * *", $closeTag);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// close tags add TOC link reference
|
// 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
|
// cleanup whitespace
|
||||||
return trim(preg_replace('/ +\n/', "\n", join($result, "\n")));
|
return trim(preg_replace('/ +\n/', "\n", join($result, "\n")));
|
||||||
|
|||||||
1049
vendor/firebug-lite/changelog.txt
vendored
1049
vendor/firebug-lite/changelog.txt
vendored
File diff suppressed because it is too large
Load Diff
21
vendor/jquery/MIT-LICENSE.txt
vendored
Normal file
21
vendor/jquery/MIT-LICENSE.txt
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
Copyright 2012 jQuery Foundation and other contributors
|
||||||
|
http://jquery.com/
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
417
vendor/jquery/README.md
vendored
Normal file
417
vendor/jquery/README.md
vendored
Normal file
@@ -0,0 +1,417 @@
|
|||||||
|
[jQuery](http://jquery.com/) - New Wave JavaScript
|
||||||
|
==================================================
|
||||||
|
|
||||||
|
Contribution Guides
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
In the spirit of open source software development, jQuery always encourages community code contribution. To help you get started and before you jump into writing code, be sure to read these important contribution guidelines thoroughly:
|
||||||
|
|
||||||
|
1. [Getting Involved](http://docs.jquery.com/Getting_Involved)
|
||||||
|
2. [Core Style Guide](http://docs.jquery.com/JQuery_Core_Style_Guidelines)
|
||||||
|
3. [Tips For Bug Patching](http://docs.jquery.com/Tips_for_jQuery_Bug_Patching)
|
||||||
|
|
||||||
|
|
||||||
|
What you need to build your own jQuery
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
In order to build jQuery, you need to have GNU make 3.8 or later, Node.js/npm latest, and git 1.7 or later.
|
||||||
|
(Earlier versions might work OK, but are not tested.)
|
||||||
|
|
||||||
|
Windows users have two options:
|
||||||
|
|
||||||
|
1. Install [msysgit](https://code.google.com/p/msysgit/) (Full installer for official Git),
|
||||||
|
[GNU make for Windows](http://gnuwin32.sourceforge.net/packages/make.htm), and a
|
||||||
|
[binary version of Node.js](http://node-js.prcn.co.cc/). Make sure all three packages are installed to the same
|
||||||
|
location (by default, this is C:\Program Files\Git).
|
||||||
|
2. Install [Cygwin](http://cygwin.com/) (make sure you install the git, make, and which packages), then either follow
|
||||||
|
the [Node.js build instructions](https://github.com/ry/node/wiki/Building-node.js-on-Cygwin-%28Windows%29) or install
|
||||||
|
the [binary version of Node.js](http://node-js.prcn.co.cc/).
|
||||||
|
|
||||||
|
Mac OS users should install Xcode (comes on your Mac OS install DVD, or downloadable from
|
||||||
|
[Apple's Xcode site](http://developer.apple.com/technologies/xcode.html)) and
|
||||||
|
[Homebrew](http://mxcl.github.com/homebrew/). Once Homebrew is installed, run `brew install git` to install git,
|
||||||
|
and `brew install node` to install Node.js.
|
||||||
|
|
||||||
|
Linux/BSD users should use their appropriate package managers to install make, git, and node, or build from source
|
||||||
|
if you swing that way. Easy-peasy.
|
||||||
|
|
||||||
|
|
||||||
|
How to build your own jQuery
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
First, clone a copy of the main jQuery git repo by running:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone git://github.com/jquery/jquery.git
|
||||||
|
```
|
||||||
|
|
||||||
|
Enter the directory and install the Node dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd jquery && npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Make sure you have `grunt` installed by testing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grunt -version
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Then, to get a complete, minified (w/ Uglify.js), linted (w/ JSHint) version of jQuery, type the following:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grunt
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
The built version of jQuery will be put in the `dist/` subdirectory.
|
||||||
|
|
||||||
|
|
||||||
|
### Modules (new in 1.8)
|
||||||
|
|
||||||
|
Starting in jQuery 1.8, special builds can now be created that optionally exclude or include any of the following modules:
|
||||||
|
|
||||||
|
- ajax
|
||||||
|
- css
|
||||||
|
- dimensions
|
||||||
|
- effects
|
||||||
|
- offset
|
||||||
|
|
||||||
|
|
||||||
|
Before creating a custom build for use in production, be sure to check out the latest stable version:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git pull; git checkout $(git describe --abbrev=0 --tags)
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, make sure all Node dependencies are installed and all Git submodules are checked out:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install && grunt
|
||||||
|
```
|
||||||
|
|
||||||
|
To create a custom build, use the following special `grunt` commands:
|
||||||
|
|
||||||
|
Exclude **ajax**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grunt custom:-ajax
|
||||||
|
```
|
||||||
|
|
||||||
|
Exclude **css**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grunt custom:-css
|
||||||
|
```
|
||||||
|
|
||||||
|
Exclude **deprecated**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grunt custom:-deprecated
|
||||||
|
```
|
||||||
|
|
||||||
|
Exclude **dimensions**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grunt custom:-dimensions
|
||||||
|
```
|
||||||
|
|
||||||
|
Exclude **effects**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grunt custom:-effects
|
||||||
|
```
|
||||||
|
|
||||||
|
Exclude **offset**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grunt custom:-offset
|
||||||
|
```
|
||||||
|
|
||||||
|
Exclude **all** optional modules:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grunt custom:-ajax,-css,-deprecated,-dimensions,-effects,-offset
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Note: dependencies will be handled internally, by the build process.
|
||||||
|
|
||||||
|
|
||||||
|
Running the Unit Tests
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
Start grunt to auto-build jQuery as you work:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd jquery && grunt watch
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Run the unit tests with a local server that supports PHP. No database is required. Pre-configured php local servers are available for Windows and Mac. Here are some options:
|
||||||
|
|
||||||
|
- Windows: [WAMP download](http://www.wampserver.com/en/)
|
||||||
|
- Mac: [MAMP download](http://www.mamp.info/en/index.html)
|
||||||
|
- Linux: [Setting up LAMP](https://www.linux.com/learn/tutorials/288158-easy-lamp-server-installation)
|
||||||
|
- [Mongoose (most platforms)](http://code.google.com/p/mongoose/)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Building to a different directory
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
If you want to build jQuery to a directory that is different from the default location:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grunt && grunt dist:/path/to/special/location/
|
||||||
|
```
|
||||||
|
With this example, the output files would be:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/path/to/special/location/jquery.js
|
||||||
|
/path/to/special/location/jquery.min.js
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to add a permanent copy destination, create a file in `dist/` called ".destination.json". Inside the file, paste and customize the following:
|
||||||
|
|
||||||
|
```json
|
||||||
|
|
||||||
|
{
|
||||||
|
"/Absolute/path/to/other/destination": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Additionally, both methods can be combined.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Updating Submodules
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Update the submodules to what is probably the latest upstream code.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grunt update_submodules
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: This task will also be run any time the default `grunt` command is used.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Git for dummies
|
||||||
|
---------------
|
||||||
|
|
||||||
|
As the source code is handled by the version control system Git, it's useful to know some features used.
|
||||||
|
|
||||||
|
### Submodules ###
|
||||||
|
|
||||||
|
The repository uses submodules, which normally are handled directly by the Makefile, but sometimes you want to
|
||||||
|
be able to work with them manually.
|
||||||
|
|
||||||
|
Following are the steps to manually get the submodules:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/jquery/jquery.git
|
||||||
|
cd jquery
|
||||||
|
git submodule init
|
||||||
|
git submodule update
|
||||||
|
```
|
||||||
|
|
||||||
|
Or:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/jquery/jquery.git
|
||||||
|
cd jquery
|
||||||
|
git submodule update --init
|
||||||
|
```
|
||||||
|
|
||||||
|
Or:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone --recursive https://github.com/jquery/jquery.git
|
||||||
|
cd jquery
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to work inside a submodule, it is possible, but first you need to checkout a branch:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd src/sizzle
|
||||||
|
git checkout master
|
||||||
|
```
|
||||||
|
|
||||||
|
After you've committed your changes to the submodule, you'll update the jquery project to point to the new commit,
|
||||||
|
but remember to push the submodule changes before pushing the new jquery commit:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd src/sizzle
|
||||||
|
git push origin master
|
||||||
|
cd ..
|
||||||
|
git add src/sizzle
|
||||||
|
git commit
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### cleaning ###
|
||||||
|
|
||||||
|
If you want to purge your working directory back to the status of upstream, following commands can be used (remember everything you've worked on is gone after these):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git reset --hard upstream/master
|
||||||
|
git clean -fdx
|
||||||
|
```
|
||||||
|
|
||||||
|
### rebasing ###
|
||||||
|
|
||||||
|
For feature/topic branches, you should always used the `--rebase` flag to `git pull`, or if you are usually handling many temporary "to be in a github pull request" branches, run following to automate this:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git config branch.autosetuprebase local
|
||||||
|
```
|
||||||
|
(see `man git-config` for more information)
|
||||||
|
|
||||||
|
### handling merge conflicts ###
|
||||||
|
|
||||||
|
If you're getting merge conflicts when merging, instead of editing the conflicted files manually, you can use the feature
|
||||||
|
`git mergetool`. Even though the default tool `xxdiff` looks awful/old, it's rather useful.
|
||||||
|
|
||||||
|
Following are some commands that can be used there:
|
||||||
|
|
||||||
|
* `Ctrl + Alt + M` - automerge as much as possible
|
||||||
|
* `b` - jump to next merge conflict
|
||||||
|
* `s` - change the order of the conflicted lines
|
||||||
|
* `u` - undo an merge
|
||||||
|
* `left mouse button` - mark a block to be the winner
|
||||||
|
* `middle mouse button` - mark a line to be the winner
|
||||||
|
* `Ctrl + S` - save
|
||||||
|
* `Ctrl + Q` - quit
|
||||||
|
|
||||||
|
[QUnit](http://docs.jquery.com/QUnit) Reference
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
### Test methods ###
|
||||||
|
|
||||||
|
```js
|
||||||
|
expect( numAssertions );
|
||||||
|
stop();
|
||||||
|
start();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
note: QUnit's eventual addition of an argument to stop/start is ignored in this test suite so that start and stop can be passed as callbacks without worrying about their parameters
|
||||||
|
|
||||||
|
### Test assertions ###
|
||||||
|
|
||||||
|
|
||||||
|
```js
|
||||||
|
ok( value, [message] );
|
||||||
|
equal( actual, expected, [message] );
|
||||||
|
notEqual( actual, expected, [message] );
|
||||||
|
deepEqual( actual, expected, [message] );
|
||||||
|
notDeepEqual( actual, expected, [message] );
|
||||||
|
strictEqual( actual, expected, [message] );
|
||||||
|
notStrictEqual( actual, expected, [message] );
|
||||||
|
raises( block, [expected], [message] );
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Test Suite Convenience Methods Reference (See [test/data/testinit.js](https://github.com/jquery/jquery/blob/master/test/data/testinit.js))
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
### Returns an array of elements with the given IDs ###
|
||||||
|
|
||||||
|
```js
|
||||||
|
q( ... );
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
q("main", "foo", "bar");
|
||||||
|
|
||||||
|
=> [ div#main, span#foo, input#bar ]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Asserts that a selection matches the given IDs ###
|
||||||
|
|
||||||
|
```js
|
||||||
|
t( testName, selector, [ "array", "of", "ids" ] );
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
t("Check for something", "//[a]", ["foo", "baar"]);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Fires a native DOM event without going through jQuery ###
|
||||||
|
|
||||||
|
```js
|
||||||
|
fireNative( node, eventType )
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
fireNative( jQuery("#elem")[0], "click" );
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add random number to url to stop caching ###
|
||||||
|
|
||||||
|
```js
|
||||||
|
url( "some/url.php" );
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
url("data/test.html");
|
||||||
|
|
||||||
|
=> "data/test.html?10538358428943"
|
||||||
|
|
||||||
|
|
||||||
|
url("data/test.php?foo=bar");
|
||||||
|
|
||||||
|
=> "data/test.php?foo=bar&10538358345554"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Load tests in an iframe ###
|
||||||
|
|
||||||
|
Loads a given page constructing a url with fileName: `"./data/" + fileName + ".html"`
|
||||||
|
and fires the given callback on jQuery ready (using the jQuery loading from that page)
|
||||||
|
and passes the iFrame's jQuery to the callback.
|
||||||
|
|
||||||
|
```js
|
||||||
|
testIframe( fileName, testName, callback );
|
||||||
|
```
|
||||||
|
|
||||||
|
Callback arguments:
|
||||||
|
|
||||||
|
```js
|
||||||
|
callback( jQueryFromIFrame, iFrameWindow, iFrameDocument );
|
||||||
|
```
|
||||||
|
|
||||||
|
### Load tests in an iframe (window.iframeCallback) ###
|
||||||
|
|
||||||
|
Loads a given page constructing a url with fileName: `"./data/" + fileName + ".html"`
|
||||||
|
The given callback is fired when window.iframeCallback is called by the page
|
||||||
|
The arguments passed to the callback are the same as the
|
||||||
|
arguments passed to window.iframeCallback, whatever that may be
|
||||||
|
|
||||||
|
```js
|
||||||
|
testIframeWithCallback( testName, fileName, callback );
|
||||||
|
```
|
||||||
|
|
||||||
|
Questions?
|
||||||
|
----------
|
||||||
|
|
||||||
|
If you have any questions, please feel free to ask on the
|
||||||
|
[Developing jQuery Core forum](http://forum.jquery.com/developing-jquery-core) or in #jquery on irc.freenode.net.
|
||||||
File diff suppressed because it is too large
Load Diff
20
vendor/json3/LICENSE
vendored
Normal file
20
vendor/json3/LICENSE
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
Copyright (c) 2012 Kit Cambridge.
|
||||||
|
http://kitcambridge.github.com
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
124
vendor/json3/README.md
vendored
Normal file
124
vendor/json3/README.md
vendored
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
# JSON 3 #
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**JSON 3** is a modern JSON implementation compatible with a variety of JavaScript platforms, including Internet Explorer 6, Opera 7, Safari 2, and Netscape 6. The current version is **3.2.4**.
|
||||||
|
|
||||||
|
- [Development Version](http://cdnjs.cloudflare.com/ajax/libs/json3/3.2.4/json3.js) *(36.5 KB; uncompressed with comments)*
|
||||||
|
- [Production Version](http://cdnjs.cloudflare.com/ajax/libs/json3/3.2.4/json3.min.js) *(3.0 KB; compressed and `gzip`-ped)*
|
||||||
|
|
||||||
|
[JSON](http://json.org/) is a language-independent data interchange format based on a loose subset of the JavaScript grammar. Originally popularized by [Douglas Crockford](http://www.crockford.com/), the format was standardized in the [fifth edition](http://es5.github.com/) of the ECMAScript specification. The 5.1 edition, ratified in June 2011, incorporates several modifications to the grammar pertaining to the serialization of dates.
|
||||||
|
|
||||||
|
JSON 3 exposes two functions: `stringify()` for [serializing](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/JSON/stringify) a JavaScript value to JSON, and `parse()` for [producing](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/JSON/parse) a JavaScript value from a JSON source string. It is a **drop-in replacement** for [JSON 2](http://json.org/js). The functions behave exactly as described in the ECMAScript spec, **except** for the date serialization discrepancy noted below.
|
||||||
|
|
||||||
|
The JSON 3 parser does **not** use `eval` or regular expressions. This provides security and performance benefits in obsolete and mobile environments, where the margin is particularly significant. The complete [benchmark suite](http://jsperf.com/json3) is available on [jsPerf](http://jsperf.com/).
|
||||||
|
|
||||||
|
The project is [hosted on GitHub](http://git.io/json3), along with the [unit tests](http://bestiejs.github.com/json3/test/test_browser.html). It is part of the [BestieJS](https://github.com/bestiejs) family, a collection of best-in-class JavaScript libraries that promote cross-platform support, specification precedents, unit testing, and plenty of documentation.
|
||||||
|
|
||||||
|
# Changes from JSON 2 #
|
||||||
|
|
||||||
|
JSON 3...
|
||||||
|
|
||||||
|
* Correctly serializes primitive wrapper objects.
|
||||||
|
* Throws a `TypeError` when serializing cyclic structures (JSON 2 recurses until the call stack overflows).
|
||||||
|
* Utilizes **feature tests** to detect broken or incomplete *native* JSON implementations (JSON 2 only checks for the presence of the native functions). The tests are only executed once at runtime, so there is no additional performance cost when parsing or serializing values.
|
||||||
|
|
||||||
|
**As of v3.2.3**, JSON 3 is compatible with [Prototype](http://prototypejs.org) 1.6.1 and older.
|
||||||
|
|
||||||
|
In contrast to JSON 2, JSON 3 **does not**...
|
||||||
|
|
||||||
|
* Add `toJSON()` methods to the `Boolean`, `Number`, and `String` prototypes. These are not part of any standard, and are made redundant by the design of the `stringify()` implementation.
|
||||||
|
* Add `toJSON()` or `toISOString()` methods to `Date.prototype`. See the note about date serialization below.
|
||||||
|
|
||||||
|
## Date Serialization
|
||||||
|
|
||||||
|
**JSON 3 deviates from the specification in one important way**: it does not define `Date#toISOString()` or `Date#toJSON()`. This preserves CommonJS compatibility and avoids polluting native prototypes. Instead, date serialization is performed internally by the `stringify()` implementation: if a date object does not define a custom `toJSON()` method, it is serialized as a [simplified ISO 8601 date-time string](http://es5.github.com/#x15.9.1.15).
|
||||||
|
|
||||||
|
**Several native `Date#toJSON()` implementations produce date time strings that do *not* conform to the grammar outlined in the spec**. For instance, all versions of Safari 4, as well as JSON 2, fail to serialize extended years correctly. Furthermore, JSON 2 and older implementations omit the milliseconds from the date-time string (optional in ES 5, but required in 5.1). Finally, in all versions of Safari 4 and 5, serializing an invalid date will produce the string `"Invalid Date"`, rather than `null`. Because these environments exhibit other serialization bugs, however, JSON 3 will override the native `stringify()` implementation.
|
||||||
|
|
||||||
|
Portions of the date serialization code are adapted from the [`date-shim`](https://github.com/Yaffle/date-shim) project.
|
||||||
|
|
||||||
|
# Usage #
|
||||||
|
|
||||||
|
## Web Browsers
|
||||||
|
|
||||||
|
<script src="//cdnjs.cloudflare.com/ajax/libs/json3/3.2.4/json3.min.js"></script>
|
||||||
|
<script>
|
||||||
|
JSON.stringify({"Hello": 123});
|
||||||
|
// => '{"Hello":123}'
|
||||||
|
JSON.parse("[[1, 2, 3], 1, 2, 3, 4]", function (key, value) {
|
||||||
|
if (typeof value == "number") {
|
||||||
|
value = value % 2 ? "Odd" : "Even";
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
// => [["Odd", "Even", "Odd"], "Odd", "Even", "Odd", "Even"]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
## CommonJS Environments
|
||||||
|
|
||||||
|
var JSON3 = require("./path/to/json3");
|
||||||
|
JSON3.parse("[1, 2, 3]");
|
||||||
|
// => [1, 2, 3]
|
||||||
|
|
||||||
|
## JavaScript Engines
|
||||||
|
|
||||||
|
load("path/to/json3.js");
|
||||||
|
JSON.stringify({"Hello": 123, "Good-bye": 456}, ["Hello"], "\t");
|
||||||
|
// => '{\n\t"Hello": 123\n}'
|
||||||
|
|
||||||
|
# Compatibility #
|
||||||
|
|
||||||
|
JSON 3 has been **tested** with the following web browsers, CommonJS environments, and JavaScript engines.
|
||||||
|
|
||||||
|
## Web Browsers
|
||||||
|
|
||||||
|
- Windows [Internet Explorer](http://www.microsoft.com/windows/internet-explorer), version 6.0 and higher
|
||||||
|
- Mozilla [Firefox](http://www.mozilla.com/firefox), version 1.0 and higher
|
||||||
|
- Apple [Safari](http://www.apple.com/safari), version 2.0 and higher
|
||||||
|
- [Opera](http://www.opera.com) 7.02 and higher
|
||||||
|
- [Mozilla](http://sillydog.org/narchive/gecko.php) 1.0, [Netscape](http://sillydog.org/narchive/) 6.2.3, and [SeaMonkey](http://www.seamonkey-project.org/) 1.0 and higher
|
||||||
|
|
||||||
|
## CommonJS Environments
|
||||||
|
|
||||||
|
- [Node](http://nodejs.org/) 0.2.6 and higher
|
||||||
|
- [RingoJS](http://ringojs.org/) 0.4 and higher
|
||||||
|
- [Narwhal](http://narwhaljs.org/) 0.3.2 and higher
|
||||||
|
|
||||||
|
## JavaScript Engines
|
||||||
|
|
||||||
|
- Mozilla [Rhino](http://www.mozilla.org/rhino) 1.5R5 and higher
|
||||||
|
- WebKit [JSC](https://trac.webkit.org/wiki/JSC)
|
||||||
|
- Google [V8](http://code.google.com/p/v8)
|
||||||
|
|
||||||
|
## Known Incompatibilities
|
||||||
|
|
||||||
|
* Attempting to serialize the `arguments` object may produce inconsistent results across environments due to specification version differences. As a workaround, please convert the `arguments` object to an array first: `JSON.stringify([].slice.call(arguments, 0))`.
|
||||||
|
|
||||||
|
## Required Native Methods
|
||||||
|
|
||||||
|
JSON 3 assumes that the following methods exist and function as described in the ECMAScript specification:
|
||||||
|
|
||||||
|
- The `Number`, `String`, `Array`, `Object`, `Date`, `SyntaxError`, and `TypeError` constructors.
|
||||||
|
- `String.fromCharCode`
|
||||||
|
- `Object#toString`
|
||||||
|
- `Function#call`
|
||||||
|
- `Math.floor`
|
||||||
|
- `Number#toString`
|
||||||
|
- `Date#valueOf`
|
||||||
|
- `String.prototype`: `indexOf`, `charCodeAt`, `charAt`, `slice`.
|
||||||
|
- `Array.prototype`: `push`, `pop`, `join`.
|
||||||
|
|
||||||
|
# Contribute #
|
||||||
|
|
||||||
|
Check out a working copy of the JSON 3 source code with [Git](http://git-scm.com/):
|
||||||
|
|
||||||
|
$ git clone git://github.com/bestiejs/json3.git
|
||||||
|
$ cd json3
|
||||||
|
$ git submodule update --init
|
||||||
|
|
||||||
|
If you'd like to contribute a feature or bug fix, you can [fork](http://help.github.com/fork-a-repo/) JSON 3, commit your changes, and [send a pull request](http://help.github.com/send-pull-requests/). Please make sure to update the unit tests in the `test` directory as well.
|
||||||
|
|
||||||
|
Alternatively, you can use the [GitHub issue tracker](https://github.com/bestiejs/json3/issues) to submit bug reports, feature requests, and questions, or send tweets to [@kitcambridge](http://twitter.com/kitcambridge).
|
||||||
|
|
||||||
|
JSON 3 is released under the [MIT License](http://kit.mit-license.org/).
|
||||||
783
vendor/json3/lib/json3.js
vendored
Normal file
783
vendor/json3/lib/json3.js
vendored
Normal file
@@ -0,0 +1,783 @@
|
|||||||
|
/*! JSON v3.2.4 | http://bestiejs.github.com/json3 | Copyright 2012, Kit Cambridge | http://kit.mit-license.org */
|
||||||
|
;(function () {
|
||||||
|
// Convenience aliases.
|
||||||
|
var getClass = {}.toString, isProperty, forEach, undef;
|
||||||
|
|
||||||
|
// Detect the `define` function exposed by asynchronous module loaders. The
|
||||||
|
// strict `define` check is necessary for compatibility with `r.js`.
|
||||||
|
var isLoader = typeof define === "function" && define.amd, JSON3 = !isLoader && typeof exports == "object" && exports;
|
||||||
|
|
||||||
|
if (JSON3 || isLoader) {
|
||||||
|
if (typeof JSON == "object" && JSON) {
|
||||||
|
// Delegate to the native `stringify` and `parse` implementations in
|
||||||
|
// asynchronous module loaders and CommonJS environments.
|
||||||
|
if (isLoader) {
|
||||||
|
JSON3 = JSON;
|
||||||
|
} else {
|
||||||
|
JSON3.stringify = JSON.stringify;
|
||||||
|
JSON3.parse = JSON.parse;
|
||||||
|
}
|
||||||
|
} else if (isLoader) {
|
||||||
|
JSON3 = this.JSON = {};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Export for web browsers and JavaScript engines.
|
||||||
|
JSON3 = this.JSON || (this.JSON = {});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Local variables.
|
||||||
|
var Escapes, toPaddedString, quote, serialize;
|
||||||
|
var fromCharCode, Unescapes, abort, lex, get, walk, update, Index, Source;
|
||||||
|
|
||||||
|
// Test the `Date#getUTC*` methods. Based on work by @Yaffle.
|
||||||
|
var isExtended = new Date(-3509827334573292), floor, Months, getDay;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// The `getUTCFullYear`, `Month`, and `Date` methods return nonsensical
|
||||||
|
// results for certain dates in Opera >= 10.53.
|
||||||
|
isExtended = isExtended.getUTCFullYear() == -109252 && isExtended.getUTCMonth() === 0 && isExtended.getUTCDate() == 1 &&
|
||||||
|
// Safari < 2.0.2 stores the internal millisecond time value correctly,
|
||||||
|
// but clips the values returned by the date methods to the range of
|
||||||
|
// signed 32-bit integers ([-2 ** 31, 2 ** 31 - 1]).
|
||||||
|
isExtended.getUTCHours() == 10 && isExtended.getUTCMinutes() == 37 && isExtended.getUTCSeconds() == 6 && isExtended.getUTCMilliseconds() == 708;
|
||||||
|
} catch (exception) {}
|
||||||
|
|
||||||
|
// Internal: Determines whether the native `JSON.stringify` and `parse`
|
||||||
|
// implementations are spec-compliant. Based on work by Ken Snyder.
|
||||||
|
function has(name) {
|
||||||
|
var stringifySupported, parseSupported, value, serialized = '{"A":[1,true,false,null,"\\u0000\\b\\n\\f\\r\\t"]}', all = name == "json";
|
||||||
|
if (all || name == "json-stringify" || name == "json-parse") {
|
||||||
|
// Test `JSON.stringify`.
|
||||||
|
if (name == "json-stringify" || all) {
|
||||||
|
if ((stringifySupported = typeof JSON3.stringify == "function" && isExtended)) {
|
||||||
|
// A test function object with a custom `toJSON` method.
|
||||||
|
(value = function () {
|
||||||
|
return 1;
|
||||||
|
}).toJSON = value;
|
||||||
|
try {
|
||||||
|
stringifySupported =
|
||||||
|
// Firefox 3.1b1 and b2 serialize string, number, and boolean
|
||||||
|
// primitives as object literals.
|
||||||
|
JSON3.stringify(0) === "0" &&
|
||||||
|
// FF 3.1b1, b2, and JSON 2 serialize wrapped primitives as object
|
||||||
|
// literals.
|
||||||
|
JSON3.stringify(new Number()) === "0" &&
|
||||||
|
JSON3.stringify(new String()) == '""' &&
|
||||||
|
// FF 3.1b1, 2 throw an error if the value is `null`, `undefined`, or
|
||||||
|
// does not define a canonical JSON representation (this applies to
|
||||||
|
// objects with `toJSON` properties as well, *unless* they are nested
|
||||||
|
// within an object or array).
|
||||||
|
JSON3.stringify(getClass) === undef &&
|
||||||
|
// IE 8 serializes `undefined` as `"undefined"`. Safari <= 5.1.7 and
|
||||||
|
// FF 3.1b3 pass this test.
|
||||||
|
JSON3.stringify(undef) === undef &&
|
||||||
|
// Safari <= 5.1.7 and FF 3.1b3 throw `Error`s and `TypeError`s,
|
||||||
|
// respectively, if the value is omitted entirely.
|
||||||
|
JSON3.stringify() === undef &&
|
||||||
|
// FF 3.1b1, 2 throw an error if the given value is not a number,
|
||||||
|
// string, array, object, Boolean, or `null` literal. This applies to
|
||||||
|
// objects with custom `toJSON` methods as well, unless they are nested
|
||||||
|
// inside object or array literals. YUI 3.0.0b1 ignores custom `toJSON`
|
||||||
|
// methods entirely.
|
||||||
|
JSON3.stringify(value) === "1" &&
|
||||||
|
JSON3.stringify([value]) == "[1]" &&
|
||||||
|
// Prototype <= 1.6.1 serializes `[undefined]` as `"[]"` instead of
|
||||||
|
// `"[null]"`.
|
||||||
|
JSON3.stringify([undef]) == "[null]" &&
|
||||||
|
// YUI 3.0.0b1 fails to serialize `null` literals.
|
||||||
|
JSON3.stringify(null) == "null" &&
|
||||||
|
// FF 3.1b1, 2 halts serialization if an array contains a function:
|
||||||
|
// `[1, true, getClass, 1]` serializes as "[1,true,],". These versions
|
||||||
|
// of Firefox also allow trailing commas in JSON objects and arrays.
|
||||||
|
// FF 3.1b3 elides non-JSON values from objects and arrays, unless they
|
||||||
|
// define custom `toJSON` methods.
|
||||||
|
JSON3.stringify([undef, getClass, null]) == "[null,null,null]" &&
|
||||||
|
// Simple serialization test. FF 3.1b1 uses Unicode escape sequences
|
||||||
|
// where character escape codes are expected (e.g., `\b` => `\u0008`).
|
||||||
|
JSON3.stringify({ "A": [value, true, false, null, "\0\b\n\f\r\t"] }) == serialized &&
|
||||||
|
// FF 3.1b1 and b2 ignore the `filter` and `width` arguments.
|
||||||
|
JSON3.stringify(null, value) === "1" &&
|
||||||
|
JSON3.stringify([1, 2], null, 1) == "[\n 1,\n 2\n]" &&
|
||||||
|
// JSON 2, Prototype <= 1.7, and older WebKit builds incorrectly
|
||||||
|
// serialize extended years.
|
||||||
|
JSON3.stringify(new Date(-8.64e15)) == '"-271821-04-20T00:00:00.000Z"' &&
|
||||||
|
// The milliseconds are optional in ES 5, but required in 5.1.
|
||||||
|
JSON3.stringify(new Date(8.64e15)) == '"+275760-09-13T00:00:00.000Z"' &&
|
||||||
|
// Firefox <= 11.0 incorrectly serializes years prior to 0 as negative
|
||||||
|
// four-digit years instead of six-digit years. Credits: @Yaffle.
|
||||||
|
JSON3.stringify(new Date(-621987552e5)) == '"-000001-01-01T00:00:00.000Z"' &&
|
||||||
|
// Safari <= 5.1.5 and Opera >= 10.53 incorrectly serialize millisecond
|
||||||
|
// values less than 1000. Credits: @Yaffle.
|
||||||
|
JSON3.stringify(new Date(-1)) == '"1969-12-31T23:59:59.999Z"';
|
||||||
|
} catch (exception) {
|
||||||
|
stringifySupported = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!all) {
|
||||||
|
return stringifySupported;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Test `JSON.parse`.
|
||||||
|
if (name == "json-parse" || all) {
|
||||||
|
if (typeof JSON3.parse == "function") {
|
||||||
|
try {
|
||||||
|
// FF 3.1b1, b2 will throw an exception if a bare literal is provided.
|
||||||
|
// Conforming implementations should also coerce the initial argument to
|
||||||
|
// a string prior to parsing.
|
||||||
|
if (JSON3.parse("0") === 0 && !JSON3.parse(false)) {
|
||||||
|
// Simple parsing test.
|
||||||
|
value = JSON3.parse(serialized);
|
||||||
|
if ((parseSupported = value.A.length == 5 && value.A[0] == 1)) {
|
||||||
|
try {
|
||||||
|
// Safari <= 5.1.2 and FF 3.1b1 allow unescaped tabs in strings.
|
||||||
|
parseSupported = !JSON3.parse('"\t"');
|
||||||
|
} catch (exception) {}
|
||||||
|
if (parseSupported) {
|
||||||
|
try {
|
||||||
|
// FF 4.0 and 4.0.1 allow leading `+` signs, and leading and
|
||||||
|
// trailing decimal points. FF 4.0, 4.0.1, and IE 9-10 also
|
||||||
|
// allow certain octal literals.
|
||||||
|
parseSupported = JSON3.parse("01") != 1;
|
||||||
|
} catch (exception) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (exception) {
|
||||||
|
parseSupported = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!all) {
|
||||||
|
return parseSupported;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return stringifySupported && parseSupported;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!has("json")) {
|
||||||
|
// Define additional utility methods if the `Date` methods are buggy.
|
||||||
|
if (!isExtended) {
|
||||||
|
floor = Math.floor;
|
||||||
|
// A mapping between the months of the year and the number of days between
|
||||||
|
// January 1st and the first of the respective month.
|
||||||
|
Months = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
|
||||||
|
// Internal: Calculates the number of days between the Unix epoch and the
|
||||||
|
// first day of the given month.
|
||||||
|
getDay = function (year, month) {
|
||||||
|
return Months[month] + 365 * (year - 1970) + floor((year - 1969 + (month = +(month > 1))) / 4) - floor((year - 1901 + month) / 100) + floor((year - 1601 + month) / 400);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal: Determines if a property is a direct property of the given
|
||||||
|
// object. Delegates to the native `Object#hasOwnProperty` method.
|
||||||
|
if (!(isProperty = {}.hasOwnProperty)) {
|
||||||
|
isProperty = function (property) {
|
||||||
|
var members = {}, constructor;
|
||||||
|
if ((members.__proto__ = null, members.__proto__ = {
|
||||||
|
// The *proto* property cannot be set multiple times in recent
|
||||||
|
// versions of Firefox and SeaMonkey.
|
||||||
|
"toString": 1
|
||||||
|
}, members).toString != getClass) {
|
||||||
|
// Safari <= 2.0.3 doesn't implement `Object#hasOwnProperty`, but
|
||||||
|
// supports the mutable *proto* property.
|
||||||
|
isProperty = function (property) {
|
||||||
|
// Capture and break the object's prototype chain (see section 8.6.2
|
||||||
|
// of the ES 5.1 spec). The parenthesized expression prevents an
|
||||||
|
// unsafe transformation by the Closure Compiler.
|
||||||
|
var original = this.__proto__, result = property in (this.__proto__ = null, this);
|
||||||
|
// Restore the original prototype chain.
|
||||||
|
this.__proto__ = original;
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// Capture a reference to the top-level `Object` constructor.
|
||||||
|
constructor = members.constructor;
|
||||||
|
// Use the `constructor` property to simulate `Object#hasOwnProperty` in
|
||||||
|
// other environments.
|
||||||
|
isProperty = function (property) {
|
||||||
|
var parent = (this.constructor || constructor).prototype;
|
||||||
|
return property in this && !(property in parent && this[property] === parent[property]);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
members = null;
|
||||||
|
return isProperty.call(this, property);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal: Normalizes the `for...in` iteration algorithm across
|
||||||
|
// environments. Each enumerated key is yielded to a `callback` function.
|
||||||
|
forEach = function (object, callback) {
|
||||||
|
var size = 0, Properties, members, property, forEach;
|
||||||
|
|
||||||
|
// Tests for bugs in the current environment's `for...in` algorithm. The
|
||||||
|
// `valueOf` property inherits the non-enumerable flag from
|
||||||
|
// `Object.prototype` in older versions of IE, Netscape, and Mozilla.
|
||||||
|
(Properties = function () {
|
||||||
|
this.valueOf = 0;
|
||||||
|
}).prototype.valueOf = 0;
|
||||||
|
|
||||||
|
// Iterate over a new instance of the `Properties` class.
|
||||||
|
members = new Properties();
|
||||||
|
for (property in members) {
|
||||||
|
// Ignore all properties inherited from `Object.prototype`.
|
||||||
|
if (isProperty.call(members, property)) {
|
||||||
|
size++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Properties = members = null;
|
||||||
|
|
||||||
|
// Normalize the iteration algorithm.
|
||||||
|
if (!size) {
|
||||||
|
// A list of non-enumerable properties inherited from `Object.prototype`.
|
||||||
|
members = ["valueOf", "toString", "toLocaleString", "propertyIsEnumerable", "isPrototypeOf", "hasOwnProperty", "constructor"];
|
||||||
|
// IE <= 8, Mozilla 1.0, and Netscape 6.2 ignore shadowed non-enumerable
|
||||||
|
// properties.
|
||||||
|
forEach = function (object, callback) {
|
||||||
|
var isFunction = getClass.call(object) == "[object Function]", property, length;
|
||||||
|
for (property in object) {
|
||||||
|
// Gecko <= 1.0 enumerates the `prototype` property of functions under
|
||||||
|
// certain conditions; IE does not.
|
||||||
|
if (!(isFunction && property == "prototype") && isProperty.call(object, property)) {
|
||||||
|
callback(property);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Manually invoke the callback for each non-enumerable property.
|
||||||
|
for (length = members.length; property = members[--length]; isProperty.call(object, property) && callback(property));
|
||||||
|
};
|
||||||
|
} else if (size == 2) {
|
||||||
|
// Safari <= 2.0.4 enumerates shadowed properties twice.
|
||||||
|
forEach = function (object, callback) {
|
||||||
|
// Create a set of iterated properties.
|
||||||
|
var members = {}, isFunction = getClass.call(object) == "[object Function]", property;
|
||||||
|
for (property in object) {
|
||||||
|
// Store each property name to prevent double enumeration. The
|
||||||
|
// `prototype` property of functions is not enumerated due to cross-
|
||||||
|
// environment inconsistencies.
|
||||||
|
if (!(isFunction && property == "prototype") && !isProperty.call(members, property) && (members[property] = 1) && isProperty.call(object, property)) {
|
||||||
|
callback(property);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// No bugs detected; use the standard `for...in` algorithm.
|
||||||
|
forEach = function (object, callback) {
|
||||||
|
var isFunction = getClass.call(object) == "[object Function]", property, isConstructor;
|
||||||
|
for (property in object) {
|
||||||
|
if (!(isFunction && property == "prototype") && isProperty.call(object, property) && !(isConstructor = property === "constructor")) {
|
||||||
|
callback(property);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Manually invoke the callback for the `constructor` property due to
|
||||||
|
// cross-environment inconsistencies.
|
||||||
|
if (isConstructor || isProperty.call(object, (property = "constructor"))) {
|
||||||
|
callback(property);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return forEach(object, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Public: Serializes a JavaScript `value` as a JSON string. The optional
|
||||||
|
// `filter` argument may specify either a function that alters how object and
|
||||||
|
// array members are serialized, or an array of strings and numbers that
|
||||||
|
// indicates which properties should be serialized. The optional `width`
|
||||||
|
// argument may be either a string or number that specifies the indentation
|
||||||
|
// level of the output.
|
||||||
|
if (!has("json-stringify")) {
|
||||||
|
// Internal: A map of control characters and their escaped equivalents.
|
||||||
|
Escapes = {
|
||||||
|
"\\": "\\\\",
|
||||||
|
'"': '\\"',
|
||||||
|
"\b": "\\b",
|
||||||
|
"\f": "\\f",
|
||||||
|
"\n": "\\n",
|
||||||
|
"\r": "\\r",
|
||||||
|
"\t": "\\t"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Internal: Converts `value` into a zero-padded string such that its
|
||||||
|
// length is at least equal to `width`. The `width` must be <= 6.
|
||||||
|
toPaddedString = function (width, value) {
|
||||||
|
// The `|| 0` expression is necessary to work around a bug in
|
||||||
|
// Opera <= 7.54u2 where `0 == -0`, but `String(-0) !== "0"`.
|
||||||
|
return ("000000" + (value || 0)).slice(-width);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Internal: Double-quotes a string `value`, replacing all ASCII control
|
||||||
|
// characters (characters with code unit values between 0 and 31) with
|
||||||
|
// their escaped equivalents. This is an implementation of the
|
||||||
|
// `Quote(value)` operation defined in ES 5.1 section 15.12.3.
|
||||||
|
quote = function (value) {
|
||||||
|
var result = '"', index = 0, symbol;
|
||||||
|
for (; symbol = value.charAt(index); index++) {
|
||||||
|
// Escape the reverse solidus, double quote, backspace, form feed, line
|
||||||
|
// feed, carriage return, and tab characters.
|
||||||
|
result += '\\"\b\f\n\r\t'.indexOf(symbol) > -1 ? Escapes[symbol] :
|
||||||
|
// If the character is a control character, append its Unicode escape
|
||||||
|
// sequence; otherwise, append the character as-is.
|
||||||
|
(Escapes[symbol] = symbol < " " ? "\\u00" + toPaddedString(2, symbol.charCodeAt(0).toString(16)) : symbol);
|
||||||
|
}
|
||||||
|
return result + '"';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Internal: Recursively serializes an object. Implements the
|
||||||
|
// `Str(key, holder)`, `JO(value)`, and `JA(value)` operations.
|
||||||
|
serialize = function (property, object, callback, properties, whitespace, indentation, stack) {
|
||||||
|
var value = object[property], className, year, month, date, time, hours, minutes, seconds, milliseconds, results, element, index, length, prefix, any, result;
|
||||||
|
if (typeof value == "object" && value) {
|
||||||
|
className = getClass.call(value);
|
||||||
|
if (className == "[object Date]" && !isProperty.call(value, "toJSON")) {
|
||||||
|
if (value > -1 / 0 && value < 1 / 0) {
|
||||||
|
// Dates are serialized according to the `Date#toJSON` method
|
||||||
|
// specified in ES 5.1 section 15.9.5.44. See section 15.9.1.15
|
||||||
|
// for the ISO 8601 date time string format.
|
||||||
|
if (getDay) {
|
||||||
|
// Manually compute the year, month, date, hours, minutes,
|
||||||
|
// seconds, and milliseconds if the `getUTC*` methods are
|
||||||
|
// buggy. Adapted from @Yaffle's `date-shim` project.
|
||||||
|
date = floor(value / 864e5);
|
||||||
|
for (year = floor(date / 365.2425) + 1970 - 1; getDay(year + 1, 0) <= date; year++);
|
||||||
|
for (month = floor((date - getDay(year, 0)) / 30.42); getDay(year, month + 1) <= date; month++);
|
||||||
|
date = 1 + date - getDay(year, month);
|
||||||
|
// The `time` value specifies the time within the day (see ES
|
||||||
|
// 5.1 section 15.9.1.2). The formula `(A % B + B) % B` is used
|
||||||
|
// to compute `A modulo B`, as the `%` operator does not
|
||||||
|
// correspond to the `modulo` operation for negative numbers.
|
||||||
|
time = (value % 864e5 + 864e5) % 864e5;
|
||||||
|
// The hours, minutes, seconds, and milliseconds are obtained by
|
||||||
|
// decomposing the time within the day. See section 15.9.1.10.
|
||||||
|
hours = floor(time / 36e5) % 24;
|
||||||
|
minutes = floor(time / 6e4) % 60;
|
||||||
|
seconds = floor(time / 1e3) % 60;
|
||||||
|
milliseconds = time % 1e3;
|
||||||
|
} else {
|
||||||
|
year = value.getUTCFullYear();
|
||||||
|
month = value.getUTCMonth();
|
||||||
|
date = value.getUTCDate();
|
||||||
|
hours = value.getUTCHours();
|
||||||
|
minutes = value.getUTCMinutes();
|
||||||
|
seconds = value.getUTCSeconds();
|
||||||
|
milliseconds = value.getUTCMilliseconds();
|
||||||
|
}
|
||||||
|
// Serialize extended years correctly.
|
||||||
|
value = (year <= 0 || year >= 1e4 ? (year < 0 ? "-" : "+") + toPaddedString(6, year < 0 ? -year : year) : toPaddedString(4, year)) +
|
||||||
|
"-" + toPaddedString(2, month + 1) + "-" + toPaddedString(2, date) +
|
||||||
|
// Months, dates, hours, minutes, and seconds should have two
|
||||||
|
// digits; milliseconds should have three.
|
||||||
|
"T" + toPaddedString(2, hours) + ":" + toPaddedString(2, minutes) + ":" + toPaddedString(2, seconds) +
|
||||||
|
// Milliseconds are optional in ES 5.0, but required in 5.1.
|
||||||
|
"." + toPaddedString(3, milliseconds) + "Z";
|
||||||
|
} else {
|
||||||
|
value = null;
|
||||||
|
}
|
||||||
|
} else if (typeof value.toJSON == "function" && ((className != "[object Number]" && className != "[object String]" && className != "[object Array]") || isProperty.call(value, "toJSON"))) {
|
||||||
|
// Prototype <= 1.6.1 adds non-standard `toJSON` methods to the
|
||||||
|
// `Number`, `String`, `Date`, and `Array` prototypes. JSON 3
|
||||||
|
// ignores all `toJSON` methods on these objects unless they are
|
||||||
|
// defined directly on an instance.
|
||||||
|
value = value.toJSON(property);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (callback) {
|
||||||
|
// If a replacement function was provided, call it to obtain the value
|
||||||
|
// for serialization.
|
||||||
|
value = callback.call(object, property, value);
|
||||||
|
}
|
||||||
|
if (value === null) {
|
||||||
|
return "null";
|
||||||
|
}
|
||||||
|
className = getClass.call(value);
|
||||||
|
if (className == "[object Boolean]") {
|
||||||
|
// Booleans are represented literally.
|
||||||
|
return "" + value;
|
||||||
|
} else if (className == "[object Number]") {
|
||||||
|
// JSON numbers must be finite. `Infinity` and `NaN` are serialized as
|
||||||
|
// `"null"`.
|
||||||
|
return value > -1 / 0 && value < 1 / 0 ? "" + value : "null";
|
||||||
|
} else if (className == "[object String]") {
|
||||||
|
// Strings are double-quoted and escaped.
|
||||||
|
return quote(value);
|
||||||
|
}
|
||||||
|
// Recursively serialize objects and arrays.
|
||||||
|
if (typeof value == "object") {
|
||||||
|
// Check for cyclic structures. This is a linear search; performance
|
||||||
|
// is inversely proportional to the number of unique nested objects.
|
||||||
|
for (length = stack.length; length--;) {
|
||||||
|
if (stack[length] === value) {
|
||||||
|
// Cyclic structures cannot be serialized by `JSON.stringify`.
|
||||||
|
throw TypeError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add the object to the stack of traversed objects.
|
||||||
|
stack.push(value);
|
||||||
|
results = [];
|
||||||
|
// Save the current indentation level and indent one additional level.
|
||||||
|
prefix = indentation;
|
||||||
|
indentation += whitespace;
|
||||||
|
if (className == "[object Array]") {
|
||||||
|
// Recursively serialize array elements.
|
||||||
|
for (index = 0, length = value.length; index < length; any || (any = true), index++) {
|
||||||
|
element = serialize(index, value, callback, properties, whitespace, indentation, stack);
|
||||||
|
results.push(element === undef ? "null" : element);
|
||||||
|
}
|
||||||
|
result = any ? (whitespace ? "[\n" + indentation + results.join(",\n" + indentation) + "\n" + prefix + "]" : ("[" + results.join(",") + "]")) : "[]";
|
||||||
|
} else {
|
||||||
|
// Recursively serialize object members. Members are selected from
|
||||||
|
// either a user-specified list of property names, or the object
|
||||||
|
// itself.
|
||||||
|
forEach(properties || value, function (property) {
|
||||||
|
var element = serialize(property, value, callback, properties, whitespace, indentation, stack);
|
||||||
|
if (element !== undef) {
|
||||||
|
// According to ES 5.1 section 15.12.3: "If `gap` {whitespace}
|
||||||
|
// is not the empty string, let `member` {quote(property) + ":"}
|
||||||
|
// be the concatenation of `member` and the `space` character."
|
||||||
|
// The "`space` character" refers to the literal space
|
||||||
|
// character, not the `space` {width} argument provided to
|
||||||
|
// `JSON.stringify`.
|
||||||
|
results.push(quote(property) + ":" + (whitespace ? " " : "") + element);
|
||||||
|
}
|
||||||
|
any || (any = true);
|
||||||
|
});
|
||||||
|
result = any ? (whitespace ? "{\n" + indentation + results.join(",\n" + indentation) + "\n" + prefix + "}" : ("{" + results.join(",") + "}")) : "{}";
|
||||||
|
}
|
||||||
|
// Remove the object from the traversed object stack.
|
||||||
|
stack.pop();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Public: `JSON.stringify`. See ES 5.1 section 15.12.3.
|
||||||
|
JSON3.stringify = function (source, filter, width) {
|
||||||
|
var whitespace, callback, properties, index, length, value;
|
||||||
|
if (typeof filter == "function" || typeof filter == "object" && filter) {
|
||||||
|
if (getClass.call(filter) == "[object Function]") {
|
||||||
|
callback = filter;
|
||||||
|
} else if (getClass.call(filter) == "[object Array]") {
|
||||||
|
// Convert the property names array into a makeshift set.
|
||||||
|
properties = {};
|
||||||
|
for (index = 0, length = filter.length; index < length; value = filter[index++], ((getClass.call(value) == "[object String]" || getClass.call(value) == "[object Number]") && (properties[value] = 1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (width) {
|
||||||
|
if (getClass.call(width) == "[object Number]") {
|
||||||
|
// Convert the `width` to an integer and create a string containing
|
||||||
|
// `width` number of space characters.
|
||||||
|
if ((width -= width % 1) > 0) {
|
||||||
|
for (whitespace = "", width > 10 && (width = 10); whitespace.length < width; whitespace += " ");
|
||||||
|
}
|
||||||
|
} else if (getClass.call(width) == "[object String]") {
|
||||||
|
whitespace = width.length <= 10 ? width : width.slice(0, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Opera <= 7.54u2 discards the values associated with empty string keys
|
||||||
|
// (`""`) only if they are used directly within an object member list
|
||||||
|
// (e.g., `!("" in { "": 1})`).
|
||||||
|
return serialize("", (value = {}, value[""] = source, value), callback, properties, whitespace, "", []);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public: Parses a JSON source string.
|
||||||
|
if (!has("json-parse")) {
|
||||||
|
fromCharCode = String.fromCharCode;
|
||||||
|
// Internal: A map of escaped control characters and their unescaped
|
||||||
|
// equivalents.
|
||||||
|
Unescapes = {
|
||||||
|
"\\": "\\",
|
||||||
|
'"': '"',
|
||||||
|
"/": "/",
|
||||||
|
"b": "\b",
|
||||||
|
"t": "\t",
|
||||||
|
"n": "\n",
|
||||||
|
"f": "\f",
|
||||||
|
"r": "\r"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Internal: Resets the parser state and throws a `SyntaxError`.
|
||||||
|
abort = function() {
|
||||||
|
Index = Source = null;
|
||||||
|
throw SyntaxError();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Internal: Returns the next token, or `"$"` if the parser has reached
|
||||||
|
// the end of the source string. A token may be a string, number, `null`
|
||||||
|
// literal, or Boolean literal.
|
||||||
|
lex = function () {
|
||||||
|
var source = Source, length = source.length, symbol, value, begin, position, sign;
|
||||||
|
while (Index < length) {
|
||||||
|
symbol = source.charAt(Index);
|
||||||
|
if ("\t\r\n ".indexOf(symbol) > -1) {
|
||||||
|
// Skip whitespace tokens, including tabs, carriage returns, line
|
||||||
|
// feeds, and space characters.
|
||||||
|
Index++;
|
||||||
|
} else if ("{}[]:,".indexOf(symbol) > -1) {
|
||||||
|
// Parse a punctuator token at the current position.
|
||||||
|
Index++;
|
||||||
|
return symbol;
|
||||||
|
} else if (symbol == '"') {
|
||||||
|
// Advance to the next character and parse a JSON string at the
|
||||||
|
// current position. String tokens are prefixed with the sentinel
|
||||||
|
// `@` character to distinguish them from punctuators.
|
||||||
|
for (value = "@", Index++; Index < length;) {
|
||||||
|
symbol = source.charAt(Index);
|
||||||
|
if (symbol < " ") {
|
||||||
|
// Unescaped ASCII control characters are not permitted.
|
||||||
|
abort();
|
||||||
|
} else if (symbol == "\\") {
|
||||||
|
// Parse escaped JSON control characters, `"`, `\`, `/`, and
|
||||||
|
// Unicode escape sequences.
|
||||||
|
symbol = source.charAt(++Index);
|
||||||
|
if ('\\"/btnfr'.indexOf(symbol) > -1) {
|
||||||
|
// Revive escaped control characters.
|
||||||
|
value += Unescapes[symbol];
|
||||||
|
Index++;
|
||||||
|
} else if (symbol == "u") {
|
||||||
|
// Advance to the first character of the escape sequence.
|
||||||
|
begin = ++Index;
|
||||||
|
// Validate the Unicode escape sequence.
|
||||||
|
for (position = Index + 4; Index < position; Index++) {
|
||||||
|
symbol = source.charAt(Index);
|
||||||
|
// A valid sequence comprises four hexdigits that form a
|
||||||
|
// single hexadecimal value.
|
||||||
|
if (!(symbol >= "0" && symbol <= "9" || symbol >= "a" && symbol <= "f" || symbol >= "A" && symbol <= "F")) {
|
||||||
|
// Invalid Unicode escape sequence.
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Revive the escaped character.
|
||||||
|
value += fromCharCode("0x" + source.slice(begin, Index));
|
||||||
|
} else {
|
||||||
|
// Invalid escape sequence.
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (symbol == '"') {
|
||||||
|
// An unescaped double-quote character marks the end of the
|
||||||
|
// string.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Append the original character as-is.
|
||||||
|
value += symbol;
|
||||||
|
Index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (source.charAt(Index) == '"') {
|
||||||
|
Index++;
|
||||||
|
// Return the revived string.
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
// Unterminated string.
|
||||||
|
abort();
|
||||||
|
} else {
|
||||||
|
// Parse numbers and literals.
|
||||||
|
begin = Index;
|
||||||
|
// Advance the scanner's position past the sign, if one is
|
||||||
|
// specified.
|
||||||
|
if (symbol == "-") {
|
||||||
|
sign = true;
|
||||||
|
symbol = source.charAt(++Index);
|
||||||
|
}
|
||||||
|
// Parse an integer or floating-point value.
|
||||||
|
if (symbol >= "0" && symbol <= "9") {
|
||||||
|
// Leading zeroes are interpreted as octal literals.
|
||||||
|
if (symbol == "0" && (symbol = source.charAt(Index + 1), symbol >= "0" && symbol <= "9")) {
|
||||||
|
// Illegal octal literal.
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
sign = false;
|
||||||
|
// Parse the integer component.
|
||||||
|
for (; Index < length && (symbol = source.charAt(Index), symbol >= "0" && symbol <= "9"); Index++);
|
||||||
|
// Floats cannot contain a leading decimal point; however, this
|
||||||
|
// case is already accounted for by the parser.
|
||||||
|
if (source.charAt(Index) == ".") {
|
||||||
|
position = ++Index;
|
||||||
|
// Parse the decimal component.
|
||||||
|
for (; position < length && (symbol = source.charAt(position), symbol >= "0" && symbol <= "9"); position++);
|
||||||
|
if (position == Index) {
|
||||||
|
// Illegal trailing decimal.
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
Index = position;
|
||||||
|
}
|
||||||
|
// Parse exponents.
|
||||||
|
symbol = source.charAt(Index);
|
||||||
|
if (symbol == "e" || symbol == "E") {
|
||||||
|
// Skip past the sign following the exponent, if one is
|
||||||
|
// specified.
|
||||||
|
symbol = source.charAt(++Index);
|
||||||
|
if (symbol == "+" || symbol == "-") {
|
||||||
|
Index++;
|
||||||
|
}
|
||||||
|
// Parse the exponential component.
|
||||||
|
for (position = Index; position < length && (symbol = source.charAt(position), symbol >= "0" && symbol <= "9"); position++);
|
||||||
|
if (position == Index) {
|
||||||
|
// Illegal empty exponent.
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
Index = position;
|
||||||
|
}
|
||||||
|
// Coerce the parsed value to a JavaScript number.
|
||||||
|
return +source.slice(begin, Index);
|
||||||
|
}
|
||||||
|
// A negative sign may only precede numbers.
|
||||||
|
if (sign) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
// `true`, `false`, and `null` literals.
|
||||||
|
if (source.slice(Index, Index + 4) == "true") {
|
||||||
|
Index += 4;
|
||||||
|
return true;
|
||||||
|
} else if (source.slice(Index, Index + 5) == "false") {
|
||||||
|
Index += 5;
|
||||||
|
return false;
|
||||||
|
} else if (source.slice(Index, Index + 4) == "null") {
|
||||||
|
Index += 4;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Unrecognized token.
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Return the sentinel `$` character if the parser has reached the end
|
||||||
|
// of the source string.
|
||||||
|
return "$";
|
||||||
|
};
|
||||||
|
|
||||||
|
// Internal: Parses a JSON `value` token.
|
||||||
|
get = function (value) {
|
||||||
|
var results, any, key;
|
||||||
|
if (value == "$") {
|
||||||
|
// Unexpected end of input.
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
if (typeof value == "string") {
|
||||||
|
if (value.charAt(0) == "@") {
|
||||||
|
// Remove the sentinel `@` character.
|
||||||
|
return value.slice(1);
|
||||||
|
}
|
||||||
|
// Parse object and array literals.
|
||||||
|
if (value == "[") {
|
||||||
|
// Parses a JSON array, returning a new JavaScript array.
|
||||||
|
results = [];
|
||||||
|
for (;; any || (any = true)) {
|
||||||
|
value = lex();
|
||||||
|
// A closing square bracket marks the end of the array literal.
|
||||||
|
if (value == "]") {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// If the array literal contains elements, the current token
|
||||||
|
// should be a comma separating the previous element from the
|
||||||
|
// next.
|
||||||
|
if (any) {
|
||||||
|
if (value == ",") {
|
||||||
|
value = lex();
|
||||||
|
if (value == "]") {
|
||||||
|
// Unexpected trailing `,` in array literal.
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// A `,` must separate each array element.
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Elisions and leading commas are not permitted.
|
||||||
|
if (value == ",") {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
results.push(get(value));
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
} else if (value == "{") {
|
||||||
|
// Parses a JSON object, returning a new JavaScript object.
|
||||||
|
results = {};
|
||||||
|
for (;; any || (any = true)) {
|
||||||
|
value = lex();
|
||||||
|
// A closing curly brace marks the end of the object literal.
|
||||||
|
if (value == "}") {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// If the object literal contains members, the current token
|
||||||
|
// should be a comma separator.
|
||||||
|
if (any) {
|
||||||
|
if (value == ",") {
|
||||||
|
value = lex();
|
||||||
|
if (value == "}") {
|
||||||
|
// Unexpected trailing `,` in object literal.
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// A `,` must separate each object member.
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Leading commas are not permitted, object property names must be
|
||||||
|
// double-quoted strings, and a `:` must separate each property
|
||||||
|
// name and value.
|
||||||
|
if (value == "," || typeof value != "string" || value.charAt(0) != "@" || lex() != ":") {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
results[value.slice(1)] = get(lex());
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
// Unexpected token encountered.
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Internal: Updates a traversed object member.
|
||||||
|
update = function(source, property, callback) {
|
||||||
|
var element = walk(source, property, callback);
|
||||||
|
if (element === undef) {
|
||||||
|
delete source[property];
|
||||||
|
} else {
|
||||||
|
source[property] = element;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Internal: Recursively traverses a parsed JSON object, invoking the
|
||||||
|
// `callback` function for each value. This is an implementation of the
|
||||||
|
// `Walk(holder, name)` operation defined in ES 5.1 section 15.12.2.
|
||||||
|
walk = function (source, property, callback) {
|
||||||
|
var value = source[property], length;
|
||||||
|
if (typeof value == "object" && value) {
|
||||||
|
if (getClass.call(value) == "[object Array]") {
|
||||||
|
for (length = value.length; length--;) {
|
||||||
|
update(value, length, callback);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// `forEach` can't be used to traverse an array in Opera <= 8.54,
|
||||||
|
// as `Object#hasOwnProperty` returns `false` for array indices
|
||||||
|
// (e.g., `![1, 2, 3].hasOwnProperty("0")`).
|
||||||
|
forEach(value, function (property) {
|
||||||
|
update(value, property, callback);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return callback.call(source, property, value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Public: `JSON.parse`. See ES 5.1 section 15.12.2.
|
||||||
|
JSON3.parse = function (source, callback) {
|
||||||
|
var result, value;
|
||||||
|
Index = 0;
|
||||||
|
Source = source;
|
||||||
|
result = get(lex());
|
||||||
|
// If a JSON string contains multiple tokens, it is invalid.
|
||||||
|
if (lex() != "$") {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
// Reset the parser state.
|
||||||
|
Index = Source = null;
|
||||||
|
return callback && getClass.call(callback) == "[object Function]" ? walk((value = {}, value[""] = result, value), "", callback) : result;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export for asynchronous module loaders.
|
||||||
|
if (isLoader) {
|
||||||
|
define(function () {
|
||||||
|
return JSON3;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).call(this);
|
||||||
20
vendor/platform.js/LICENSE.txt
vendored
Normal file
20
vendor/platform.js/LICENSE.txt
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
Copyright 2011-2012 John-David Dalton <http://allyoucanleet.com/>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
98
vendor/platform.js/README.md
vendored
Normal file
98
vendor/platform.js/README.md
vendored
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
# 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>.
|
||||||
|
|
||||||
|
## Disclaimer
|
||||||
|
|
||||||
|
Platform.js is for informational purposes only and **not** intended as a substitution for [feature detection/inference](http://allyoucanleet.com/post/18087210413/feature-testing-costs#screencast2) checks.
|
||||||
|
|
||||||
|
## BestieJS
|
||||||
|
|
||||||
|
Platform.js is part of the BestieJS *"Best in Class"* module collection. This means we promote solid browser/environment support, ES5 precedents, unit testing, and plenty of documentation.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
The documentation for Platform.js can be viewed here: [/doc/README.md](https://github.com/bestiejs/platform.js/blob/master/doc/README.md#readme)
|
||||||
|
|
||||||
|
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
|
||||||
|
<script src="platform.js"></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
Via [npm](http://npmjs.org/):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install platform
|
||||||
|
```
|
||||||
|
|
||||||
|
In [Node.js](http://nodejs.org/) and [RingoJS](http://ringojs.org/):
|
||||||
|
|
||||||
|
```js
|
||||||
|
var platform = require('platform');
|
||||||
|
```
|
||||||
|
|
||||||
|
In [Rhino](http://www.mozilla.org/rhino/):
|
||||||
|
|
||||||
|
```js
|
||||||
|
load('platform.js');
|
||||||
|
```
|
||||||
|
|
||||||
|
In an AMD loader like [RequireJS](http://requirejs.org/):
|
||||||
|
|
||||||
|
```js
|
||||||
|
require({
|
||||||
|
'paths': {
|
||||||
|
'platform': 'path/to/platform'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
['platform'], function(platform) {
|
||||||
|
console.log(platform.name);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Usage example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// on IE10 x86 platform preview running in IE7 compatibility mode on Windows 7 64 bit edition
|
||||||
|
platform.name; // 'IE'
|
||||||
|
platform.version; // '10.0'
|
||||||
|
platform.layout; // 'Trident'
|
||||||
|
platform.os; // 'Windows Server 2008 R2 / 7 x64'
|
||||||
|
platform.description; // 'IE 10.0 x86 (platform preview; running in IE 7 mode) on Windows Server 2008 R2 / 7 x64'
|
||||||
|
|
||||||
|
// or on an iPad
|
||||||
|
platform.name; // 'Safari'
|
||||||
|
platform.version; // '5.1'
|
||||||
|
platform.product; // 'iPad'
|
||||||
|
platform.manufacturer; // 'Apple'
|
||||||
|
platform.layout; // 'WebKit'
|
||||||
|
platform.os; // 'iOS 5.0'
|
||||||
|
platform.description; // 'Safari 5.1 on Apple iPad (iOS 5.0)'
|
||||||
|
|
||||||
|
// or parsing a given UA string
|
||||||
|
var info = platform.parse('Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7.2; en; rv:2.0) Gecko/20100101 Firefox/4.0 Opera 11.52');
|
||||||
|
info.name; // 'Opera'
|
||||||
|
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'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
* [John-David Dalton](http://allyoucanleet.com/)
|
||||||
|
[](https://twitter.com/jdalton "Follow @jdalton on Twitter")
|
||||||
|
|
||||||
|
## Contributors
|
||||||
|
|
||||||
|
* [Mathias Bynens](http://mathiasbynens.be/)
|
||||||
|
[](https://twitter.com/mathias "Follow @mathias on Twitter")
|
||||||
996
vendor/platform.js/platform.js
vendored
Normal file
996
vendor/platform.js/platform.js
vendored
Normal file
@@ -0,0 +1,996 @@
|
|||||||
|
/*!
|
||||||
|
* 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>
|
||||||
|
*/
|
||||||
|
;(function(window) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/** Backup possible window/global object */
|
||||||
|
var oldWin = window;
|
||||||
|
|
||||||
|
/** Detect free variable `exports` */
|
||||||
|
var freeExports = typeof exports == 'object' && exports;
|
||||||
|
|
||||||
|
/** Detect free variable `global` */
|
||||||
|
var freeGlobal = typeof global == 'object' && global &&
|
||||||
|
(global == global.global ? (window = global) : global);
|
||||||
|
|
||||||
|
/** Opera regexp */
|
||||||
|
var reOpera = /Opera/;
|
||||||
|
|
||||||
|
/** Used to resolve a value's internal [[Class]] */
|
||||||
|
var toString = {}.toString;
|
||||||
|
|
||||||
|
/** Detect Java environment */
|
||||||
|
var java = /Java/.test(getClassOf(window.java)) && window.java;
|
||||||
|
|
||||||
|
/** A character to represent alpha */
|
||||||
|
var alpha = java ? 'a' : '\u03b1';
|
||||||
|
|
||||||
|
/** A character to represent beta */
|
||||||
|
var beta = java ? 'b' : '\u03b2';
|
||||||
|
|
||||||
|
/** Browser document object */
|
||||||
|
var doc = window.document || {};
|
||||||
|
|
||||||
|
/** Used to check for own properties of an object */
|
||||||
|
var hasOwnProperty = {}.hasOwnProperty;
|
||||||
|
|
||||||
|
/** Browser navigator object */
|
||||||
|
var nav = window.navigator || {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect Opera browser
|
||||||
|
* http://www.howtocreate.co.uk/operaStuff/operaObject.html
|
||||||
|
* http://dev.opera.com/articles/view/opera-mini-web-content-authoring-guidelines/#operamini
|
||||||
|
*/
|
||||||
|
var opera = window.operamini || window.opera;
|
||||||
|
|
||||||
|
/** Opera [[Class]] */
|
||||||
|
var operaClass = reOpera.test(operaClass = getClassOf(opera)) ? operaClass : (opera = null);
|
||||||
|
|
||||||
|
/** Possible global object */
|
||||||
|
var thisBinding = this;
|
||||||
|
|
||||||
|
/** Browser user agent string */
|
||||||
|
var userAgent = nav.userAgent || '';
|
||||||
|
|
||||||
|
/*--------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Capitalizes a string value.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {String} string The string to capitalize.
|
||||||
|
* @returns {String} The capitalized string.
|
||||||
|
*/
|
||||||
|
function capitalize(string) {
|
||||||
|
string = String(string);
|
||||||
|
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An iteration utility for arrays and objects.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Array|Object} object The object to iterate over.
|
||||||
|
* @param {Function} callback The function called per iteration.
|
||||||
|
*/
|
||||||
|
function each(object, callback) {
|
||||||
|
var index = -1,
|
||||||
|
length = object.length;
|
||||||
|
|
||||||
|
if (length == length >>> 0) {
|
||||||
|
while (++index < length) {
|
||||||
|
callback(object[index], index, object);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
forOwn(object, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trim and conditionally capitalize string values.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {String} string The string to format.
|
||||||
|
* @returns {String} The formatted string.
|
||||||
|
*/
|
||||||
|
function format(string) {
|
||||||
|
string = trim(string);
|
||||||
|
return /^(?:webOS|i(?:OS|P))/.test(string)
|
||||||
|
? string
|
||||||
|
: capitalize(string);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterates over an object's own properties, executing the `callback` for each.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Object} object The object to iterate over.
|
||||||
|
* @param {Function} callback The function executed per own property.
|
||||||
|
*/
|
||||||
|
function forOwn(object, callback) {
|
||||||
|
for (var key in object) {
|
||||||
|
hasKey(object, key) && callback(object[key], key, object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the internal [[Class]] of a value.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Mixed} value The value.
|
||||||
|
* @returns {String} The [[Class]].
|
||||||
|
*/
|
||||||
|
function getClassOf(value) {
|
||||||
|
return value == null
|
||||||
|
? capitalize(value)
|
||||||
|
: toString.call(value).slice(8, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if an object has the specified key as a direct property.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Object} object The object to check.
|
||||||
|
* @param {String} key The key to check for.
|
||||||
|
* @returns {Boolean} Returns `true` if key is a direct property, else `false`.
|
||||||
|
*/
|
||||||
|
function hasKey() {
|
||||||
|
// lazy define for others (not as accurate)
|
||||||
|
hasKey = function(object, key) {
|
||||||
|
var parent = object != null && (object.constructor || Object).prototype;
|
||||||
|
return !!parent && key in Object(object) && !(key in parent && object[key] === parent[key]);
|
||||||
|
};
|
||||||
|
// for modern browsers
|
||||||
|
if (getClassOf(hasOwnProperty) == 'Function') {
|
||||||
|
hasKey = function(object, key) {
|
||||||
|
return object != null && hasOwnProperty.call(object, key);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// for Safari 2
|
||||||
|
else if ({}.__proto__ == Object.prototype) {
|
||||||
|
hasKey = function(object, key) {
|
||||||
|
var result = false;
|
||||||
|
if (object != null) {
|
||||||
|
object = Object(object);
|
||||||
|
object.__proto__ = [object.__proto__, object.__proto__ = null, result = key in object][0];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return hasKey.apply(this, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Host objects can return type values that are different from their actual
|
||||||
|
* data type. The objects we are concerned with usually return non-primitive
|
||||||
|
* types of object, function, or unknown.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Mixed} object The owner of the property.
|
||||||
|
* @param {String} property The property to check.
|
||||||
|
* @returns {Boolean} Returns `true` if the property value is a non-primitive, else `false`.
|
||||||
|
*/
|
||||||
|
function isHostType(object, property) {
|
||||||
|
var type = object != null ? typeof object[property] : 'number';
|
||||||
|
return !/^(?:boolean|number|string|undefined)$/.test(type) &&
|
||||||
|
(type == 'object' ? !!object[property] : true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares a string for use in a RegExp constructor by making hyphens and
|
||||||
|
* spaces optional.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {String} string The string to qualify.
|
||||||
|
* @returns {String} The qualified string.
|
||||||
|
*/
|
||||||
|
function qualify(string) {
|
||||||
|
return String(string).replace(/([ -])(?!$)/g, '$1?');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A bare-bones` Array#reduce` like utility function.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Array} array The array to iterate over.
|
||||||
|
* @param {Function} callback The function called per iteration.
|
||||||
|
* @param {Mixed} accumulator Initial value of the accumulator.
|
||||||
|
* @returns {Mixed} The accumulator.
|
||||||
|
*/
|
||||||
|
function reduce(array, callback) {
|
||||||
|
var accumulator = null;
|
||||||
|
each(array, function(value, index) {
|
||||||
|
accumulator = callback(accumulator, value, index, array);
|
||||||
|
});
|
||||||
|
return accumulator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes leading and trailing whitespace from a string.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {String} string The string to trim.
|
||||||
|
* @returns {String} The trimmed string.
|
||||||
|
*/
|
||||||
|
function trim(string) {
|
||||||
|
return String(string).replace(/^ +| +$/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
/*--------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new platform object.
|
||||||
|
*
|
||||||
|
* @memberOf platform
|
||||||
|
* @param {String} [ua = navigator.userAgent] The user agent string.
|
||||||
|
* @returns {Object} A platform object.
|
||||||
|
*/
|
||||||
|
function parse(ua) {
|
||||||
|
|
||||||
|
ua || (ua = userAgent);
|
||||||
|
|
||||||
|
/** Temporary variable used over the script's lifetime */
|
||||||
|
var data;
|
||||||
|
|
||||||
|
/** The CPU architecture */
|
||||||
|
var arch = ua;
|
||||||
|
|
||||||
|
/** Platform description array */
|
||||||
|
var description = [];
|
||||||
|
|
||||||
|
/** Platform alpha/beta indicator */
|
||||||
|
var prerelease = null;
|
||||||
|
|
||||||
|
/** A flag to indicate that environment features should be used to resolve the platform */
|
||||||
|
var useFeatures = ua == userAgent;
|
||||||
|
|
||||||
|
/** The browser/environment version */
|
||||||
|
var version = useFeatures && opera && typeof opera.version == 'function' && opera.version();
|
||||||
|
|
||||||
|
/* Detectable layout engines (order is important) */
|
||||||
|
var layout = getLayout([
|
||||||
|
{ 'label': 'WebKit', 'pattern': 'AppleWebKit' },
|
||||||
|
'iCab',
|
||||||
|
'Presto',
|
||||||
|
'NetFront',
|
||||||
|
'Tasman',
|
||||||
|
'Trident',
|
||||||
|
'KHTML',
|
||||||
|
'Gecko'
|
||||||
|
]);
|
||||||
|
|
||||||
|
/* Detectable browser names (order is important) */
|
||||||
|
var name = getName([
|
||||||
|
'Adobe AIR',
|
||||||
|
'Arora',
|
||||||
|
'Avant Browser',
|
||||||
|
'Camino',
|
||||||
|
'Epiphany',
|
||||||
|
'Fennec',
|
||||||
|
'Flock',
|
||||||
|
'Galeon',
|
||||||
|
'GreenBrowser',
|
||||||
|
'iCab',
|
||||||
|
'Iceweasel',
|
||||||
|
'Iron',
|
||||||
|
'K-Meleon',
|
||||||
|
'Konqueror',
|
||||||
|
'Lunascape',
|
||||||
|
'Maxthon',
|
||||||
|
'Midori',
|
||||||
|
'Nook Browser',
|
||||||
|
'PhantomJS',
|
||||||
|
'Raven',
|
||||||
|
'Rekonq',
|
||||||
|
'RockMelt',
|
||||||
|
'SeaMonkey',
|
||||||
|
{ 'label': 'Silk', 'pattern': '(?:Cloud9|Silk-Accelerated)' },
|
||||||
|
'Sleipnir',
|
||||||
|
'SlimBrowser',
|
||||||
|
'Sunrise',
|
||||||
|
'Swiftfox',
|
||||||
|
'WebPositive',
|
||||||
|
'Opera Mini',
|
||||||
|
'Opera',
|
||||||
|
'Chrome',
|
||||||
|
{ 'label': 'Chrome Mobile', 'pattern': '(?:CriOS|CrMo)' },
|
||||||
|
{ 'label': 'Firefox', 'pattern': '(?:Firefox|Minefield)' },
|
||||||
|
{ 'label': 'IE', 'pattern': 'MSIE' },
|
||||||
|
'Safari'
|
||||||
|
]);
|
||||||
|
|
||||||
|
/* Detectable products (order is important) */
|
||||||
|
var product = getProduct([
|
||||||
|
'BlackBerry',
|
||||||
|
{ 'label': 'Galaxy S', 'pattern': 'GT-I9000' },
|
||||||
|
{ 'label': 'Galaxy S2', 'pattern': 'GT-I9100' },
|
||||||
|
'Google TV',
|
||||||
|
'iPad',
|
||||||
|
'iPod',
|
||||||
|
'iPhone',
|
||||||
|
'Kindle',
|
||||||
|
{ 'label': 'Kindle Fire', 'pattern': '(?:Cloud9|Silk-Accelerated)' },
|
||||||
|
'Nook',
|
||||||
|
'PlayBook',
|
||||||
|
'PlayStation Vita',
|
||||||
|
'TouchPad',
|
||||||
|
'Transformer',
|
||||||
|
'Xoom'
|
||||||
|
]);
|
||||||
|
|
||||||
|
/* Detectable manufacturers */
|
||||||
|
var manufacturer = getManufacturer({
|
||||||
|
'Apple': { 'iPad': 1, 'iPhone': 1, 'iPod': 1 },
|
||||||
|
'Amazon': { 'Kindle': 1, 'Kindle Fire': 1 },
|
||||||
|
'Asus': { 'Transformer': 1 },
|
||||||
|
'Barnes & Noble': { 'Nook': 1 },
|
||||||
|
'BlackBerry': { 'PlayBook': 1 },
|
||||||
|
'Google': { 'Google TV': 1 },
|
||||||
|
'HP': { 'TouchPad': 1 },
|
||||||
|
'LG': { },
|
||||||
|
'Motorola': { 'Xoom': 1 },
|
||||||
|
'Nokia': { },
|
||||||
|
'Samsung': { 'Galaxy S': 1, 'Galaxy S2': 1 },
|
||||||
|
'Sony': { 'PlayStation Vita': 1 }
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Detectable OSes (order is important) */
|
||||||
|
var os = getOS([
|
||||||
|
'Android',
|
||||||
|
'CentOS',
|
||||||
|
'Debian',
|
||||||
|
'Fedora',
|
||||||
|
'FreeBSD',
|
||||||
|
'Gentoo',
|
||||||
|
'Haiku',
|
||||||
|
'Kubuntu',
|
||||||
|
'Linux Mint',
|
||||||
|
'Red Hat',
|
||||||
|
'SuSE',
|
||||||
|
'Ubuntu',
|
||||||
|
'Xubuntu',
|
||||||
|
'Cygwin',
|
||||||
|
'Symbian OS',
|
||||||
|
'hpwOS',
|
||||||
|
'webOS ',
|
||||||
|
'webOS',
|
||||||
|
'Tablet OS',
|
||||||
|
'Linux',
|
||||||
|
'Mac OS X',
|
||||||
|
'Macintosh',
|
||||||
|
'Mac',
|
||||||
|
'Windows 98;',
|
||||||
|
'Windows '
|
||||||
|
]);
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Picks the layout engine from an array of guesses.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Array} guesses An array of guesses.
|
||||||
|
* @returns {String|Null} The detected layout engine.
|
||||||
|
*/
|
||||||
|
function getLayout(guesses) {
|
||||||
|
return reduce(guesses, function(result, guess) {
|
||||||
|
return result || RegExp('\\b' + (
|
||||||
|
guess.pattern || qualify(guess)
|
||||||
|
) + '\\b', 'i').exec(ua) && (guess.label || guess);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Picks the manufacturer from an array of guesses.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Array} guesses An array of guesses.
|
||||||
|
* @returns {String|Null} The detected manufacturer.
|
||||||
|
*/
|
||||||
|
function getManufacturer(guesses) {
|
||||||
|
return reduce(guesses, function(result, value, key) {
|
||||||
|
// lookup the manufacturer by product or scan the UA for the manufacturer
|
||||||
|
return result || (
|
||||||
|
value[product] ||
|
||||||
|
value[0/*Opera 9.25 fix*/, /^[a-z]+(?: +[a-z]+\b)*/i.exec(product)] ||
|
||||||
|
RegExp('\\b' + (key.pattern || qualify(key)) + '(?:\\b|\\w*\\d)', 'i').exec(ua)
|
||||||
|
) && (key.label || key);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Picks the browser name from an array of guesses.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Array} guesses An array of guesses.
|
||||||
|
* @returns {String|Null} The detected browser name.
|
||||||
|
*/
|
||||||
|
function getName(guesses) {
|
||||||
|
return reduce(guesses, function(result, guess) {
|
||||||
|
return result || RegExp('\\b' + (
|
||||||
|
guess.pattern || qualify(guess)
|
||||||
|
) + '\\b', 'i').exec(ua) && (guess.label || guess);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Picks the OS name from an array of guesses.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Array} guesses An array of guesses.
|
||||||
|
* @returns {String|Null} The detected OS name.
|
||||||
|
*/
|
||||||
|
function getOS(guesses) {
|
||||||
|
return reduce(guesses, function(result, guess) {
|
||||||
|
var pattern = guess.pattern || qualify(guess);
|
||||||
|
if (!result && (result =
|
||||||
|
RegExp('\\b' + pattern + '(?:/[\\d.]+|[ \\w.]*)', 'i').exec(ua))) {
|
||||||
|
// platform tokens defined at
|
||||||
|
// http://msdn.microsoft.com/en-us/library/ms537503(VS.85).aspx
|
||||||
|
// http://web.archive.org/web/20081122053950/http://msdn.microsoft.com/en-us/library/ms537503(VS.85).aspx
|
||||||
|
data = {
|
||||||
|
'6.2': '8',
|
||||||
|
'6.1': 'Server 2008 R2 / 7',
|
||||||
|
'6.0': 'Server 2008 / Vista',
|
||||||
|
'5.2': 'Server 2003 / XP 64-bit',
|
||||||
|
'5.1': 'XP',
|
||||||
|
'5.01': '2000 SP1',
|
||||||
|
'5.0': '2000',
|
||||||
|
'4.0': 'NT',
|
||||||
|
'4.90': 'ME'
|
||||||
|
};
|
||||||
|
// detect Windows version from platform tokens
|
||||||
|
if (/^Win/i.test(result) &&
|
||||||
|
(data = data[0/*Opera 9.25 fix*/, /[\d.]+$/.exec(result)])) {
|
||||||
|
result = 'Windows ' + data;
|
||||||
|
}
|
||||||
|
// correct character case and cleanup
|
||||||
|
result = format(String(result)
|
||||||
|
.replace(RegExp(pattern, 'i'), guess.label || guess)
|
||||||
|
.replace(/ ce$/i, ' CE')
|
||||||
|
.replace(/hpw/i, 'web')
|
||||||
|
.replace(/Macintosh/, 'Mac OS')
|
||||||
|
.replace(/_PowerPC/i, ' OS')
|
||||||
|
.replace(/(OS X) [^ \d]+/i, '$1')
|
||||||
|
.replace(/\/(\d)/, ' $1')
|
||||||
|
.replace(/_/g, '.')
|
||||||
|
.replace(/(?: BePC|[ .]*fc[ \d.]+)$/i, '')
|
||||||
|
.replace(/x86\.64/gi, 'x86_64')
|
||||||
|
.split(' on ')[0]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Picks the product name from an array of guesses.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Array} guesses An array of guesses.
|
||||||
|
* @returns {String|Null} The detected product name.
|
||||||
|
*/
|
||||||
|
function getProduct(guesses) {
|
||||||
|
return reduce(guesses, function(result, guess) {
|
||||||
|
var pattern = guess.pattern || qualify(guess);
|
||||||
|
if (!result && (result =
|
||||||
|
RegExp('\\b' + pattern + ' *\\d+[.\\w_]*', 'i').exec(ua) ||
|
||||||
|
RegExp('\\b' + pattern + '(?:; *(?:[a-z]+[_-])?[a-z]+\\d+|[^ ();-]*)', 'i').exec(ua)
|
||||||
|
)) {
|
||||||
|
// split by forward slash and append product version if needed
|
||||||
|
if ((result = String(guess.label || result).split('/'))[1] && !/[\d.]+/.test(result[0])) {
|
||||||
|
result[0] += ' ' + result[1];
|
||||||
|
}
|
||||||
|
// correct character case and cleanup
|
||||||
|
guess = guess.label || guess;
|
||||||
|
result = format(result[0]
|
||||||
|
.replace(RegExp(pattern, 'i'), guess)
|
||||||
|
.replace(RegExp('; *(?:' + guess + '[_-])?', 'i'), ' ')
|
||||||
|
.replace(RegExp('(' + guess + ')(\\w)', 'i'), '$1 $2'));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the version using an array of UA patterns.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Array} patterns An array of UA patterns.
|
||||||
|
* @returns {String|Null} The detected version.
|
||||||
|
*/
|
||||||
|
function getVersion(patterns) {
|
||||||
|
return reduce(patterns, function(result, pattern) {
|
||||||
|
return result || (RegExp(pattern +
|
||||||
|
'(?:-[\\d.]+/|(?: for [\\w-]+)?[ /-])([\\d.]+[^ ();/_-]*)', 'i').exec(ua) || 0)[1] || null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns `platform.description` when the platform object is coerced to a string.
|
||||||
|
*
|
||||||
|
* @name toString
|
||||||
|
* @memberOf platform
|
||||||
|
* @returns {String} Returns `platform.description` if available, else an empty string.
|
||||||
|
*/
|
||||||
|
function toStringPlatform() {
|
||||||
|
return this.description || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
// convert layout to an array so we can add extra details
|
||||||
|
layout && (layout = [layout]);
|
||||||
|
|
||||||
|
// detect product names that contain their manufacturer's name
|
||||||
|
if (manufacturer && !product) {
|
||||||
|
product = getProduct([manufacturer]);
|
||||||
|
}
|
||||||
|
// clean up Google TV
|
||||||
|
if ((data = /Google TV/.exec(product))) {
|
||||||
|
product = data[0];
|
||||||
|
}
|
||||||
|
// detect simulators
|
||||||
|
if (/\bSimulator\b/i.test(ua)) {
|
||||||
|
product = (product ? product + ' ' : '') + 'Simulator';
|
||||||
|
}
|
||||||
|
// detect iOS
|
||||||
|
if (/^iP/.test(product)) {
|
||||||
|
name || (name = 'Safari');
|
||||||
|
os = 'iOS' + ((data = / OS ([\d_]+)/i.exec(ua))
|
||||||
|
? ' ' + data[1].replace(/_/g, '.')
|
||||||
|
: '');
|
||||||
|
}
|
||||||
|
// detect Kubuntu
|
||||||
|
else if (name == 'Konqueror' && !/buntu/i.test(os)) {
|
||||||
|
os = 'Kubuntu';
|
||||||
|
}
|
||||||
|
// detect Android browsers
|
||||||
|
else if (manufacturer && manufacturer != 'Google' &&
|
||||||
|
/Chrome|Vita/.test(name + ';' + product)) {
|
||||||
|
name = 'Android Browser';
|
||||||
|
os = /Android/.test(os) ? os : 'Android';
|
||||||
|
}
|
||||||
|
// detect false positives for Firefox/Safari
|
||||||
|
else if (!name || (data = !/\bMinefield\b/i.test(ua) && /Firefox|Safari/.exec(name))) {
|
||||||
|
// escape the `/` for Firefox 1
|
||||||
|
if (name && !product && /[\/,]|^[^(]+?\)/.test(ua.slice(ua.indexOf(data + '/') + 8))) {
|
||||||
|
// clear name of false positives
|
||||||
|
name = null;
|
||||||
|
}
|
||||||
|
// reassign a generic name
|
||||||
|
if ((data = product || manufacturer || os) &&
|
||||||
|
(product || manufacturer || /Android|Symbian OS|Tablet OS|webOS/.test(os))) {
|
||||||
|
name = /[a-z]+(?: Hat)?/i.exec(/Android/.test(os) ? os : data) + ' Browser';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// detect non-Opera versions (order is important)
|
||||||
|
if (!version) {
|
||||||
|
version = getVersion([
|
||||||
|
'(?:Cloud9|CriOS|CrMo|Opera ?Mini|Raven|Silk(?!/[\\d.]+$))',
|
||||||
|
'Version',
|
||||||
|
qualify(name),
|
||||||
|
'(?:Firefox|Minefield|NetFront)'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
// detect stubborn layout engines
|
||||||
|
if (layout == 'iCab' && parseFloat(version) > 3) {
|
||||||
|
layout = ['WebKit'];
|
||||||
|
} else if (data =
|
||||||
|
/Opera/.test(name) && 'Presto' ||
|
||||||
|
/\b(?:Midori|Nook|Safari)\b/i.test(ua) && 'WebKit' ||
|
||||||
|
!layout && /\bMSIE\b/i.test(ua) && (/^Mac/.test(os) ? 'Tasman' : 'Trident')) {
|
||||||
|
layout = [data];
|
||||||
|
}
|
||||||
|
// leverage environment features
|
||||||
|
if (useFeatures) {
|
||||||
|
// detect server-side environments
|
||||||
|
// Rhino has a global function while others have a global object
|
||||||
|
if (isHostType(window, 'global')) {
|
||||||
|
if (java) {
|
||||||
|
data = java.lang.System;
|
||||||
|
arch = data.getProperty('os.arch');
|
||||||
|
os = os || data.getProperty('os.name') + ' ' + data.getProperty('os.version');
|
||||||
|
}
|
||||||
|
if (typeof exports == 'object' && exports) {
|
||||||
|
// if `thisBinding` is the [ModuleScope]
|
||||||
|
if (thisBinding == oldWin && typeof system == 'object' && (data = [system])[0]) {
|
||||||
|
os || (os = data[0].os || null);
|
||||||
|
try {
|
||||||
|
data[1] = require('ringo/engine').version;
|
||||||
|
version = data[1].join('.');
|
||||||
|
name = 'RingoJS';
|
||||||
|
} catch(e) {
|
||||||
|
if (data[0].global == freeGlobal) {
|
||||||
|
name = 'Narwhal';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (typeof process == 'object' && (data = process)) {
|
||||||
|
name = 'Node.js';
|
||||||
|
arch = data.arch;
|
||||||
|
os = data.platform;
|
||||||
|
version = /[\d.]+/.exec(data.version)[0];
|
||||||
|
}
|
||||||
|
} else if (getClassOf(window.environment) == 'Environment') {
|
||||||
|
name = 'Rhino';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// detect Adobe AIR
|
||||||
|
else if (getClassOf(data = window.runtime) == 'ScriptBridgingProxyObject') {
|
||||||
|
name = 'Adobe AIR';
|
||||||
|
os = data.flash.system.Capabilities.os;
|
||||||
|
}
|
||||||
|
// detect PhantomJS
|
||||||
|
else if (getClassOf(data = window.phantom) == 'RuntimeObject') {
|
||||||
|
name = 'PhantomJS';
|
||||||
|
version = (data = data.version || null) && (data.major + '.' + data.minor + '.' + data.patch);
|
||||||
|
}
|
||||||
|
// detect IE compatibility modes
|
||||||
|
else if (typeof doc.documentMode == 'number' && (data = /\bTrident\/(\d+)/i.exec(ua))) {
|
||||||
|
// we're in compatibility mode when the Trident version + 4 doesn't
|
||||||
|
// equal the document mode
|
||||||
|
version = [version, doc.documentMode];
|
||||||
|
if ((data = +data[1] + 4) != version[1]) {
|
||||||
|
description.push('IE ' + version[1] + ' mode');
|
||||||
|
layout[1] = '';
|
||||||
|
version[1] = data;
|
||||||
|
}
|
||||||
|
version = name == 'IE' ? String(version[1].toFixed(1)) : version[0];
|
||||||
|
}
|
||||||
|
os = os && format(os);
|
||||||
|
}
|
||||||
|
// detect prerelease phases
|
||||||
|
if (version && (data =
|
||||||
|
/(?:[ab]|dp|pre|[ab]\d+pre)(?:\d+\+?)?$/i.exec(version) ||
|
||||||
|
/(?:alpha|beta)(?: ?\d)?/i.exec(ua + ';' + (useFeatures && nav.appMinorVersion)) ||
|
||||||
|
/\bMinefield\b/i.test(ua) && 'a')) {
|
||||||
|
prerelease = /b/i.test(data) ? 'beta' : 'alpha';
|
||||||
|
version = version.replace(RegExp(data + '\\+?$'), '') +
|
||||||
|
(prerelease == 'beta' ? beta : alpha) + (/\d+\+?/.exec(data) || '');
|
||||||
|
}
|
||||||
|
// rename code name "Fennec"
|
||||||
|
if (name == 'Fennec') {
|
||||||
|
name = 'Firefox Mobile';
|
||||||
|
}
|
||||||
|
// obscure Maxthon's unreliable version
|
||||||
|
else if (name == 'Maxthon' && version) {
|
||||||
|
version = version.replace(/\.[\d.]+/, '.x');
|
||||||
|
}
|
||||||
|
// detect Silk desktop/accelerated modes
|
||||||
|
else if (name == 'Silk') {
|
||||||
|
if (!/Mobi/i.test(ua)) {
|
||||||
|
os = 'Android';
|
||||||
|
description.unshift('desktop mode');
|
||||||
|
}
|
||||||
|
if (/Accelerated *= *true/i.test(ua)) {
|
||||||
|
description.unshift('accelerated');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// detect Windows Phone desktop mode
|
||||||
|
else if (name == 'IE' && (data = (/; *(?:XBLWP|ZuneWP)(\d+)/i.exec(ua) || 0)[1])) {
|
||||||
|
name += ' Mobile';
|
||||||
|
os = 'Windows Phone OS ' + data + '.x';
|
||||||
|
description.unshift('desktop mode');
|
||||||
|
}
|
||||||
|
// add mobile postfix
|
||||||
|
else if ((name == 'IE' || name && !product && !/Browser|Mobi/.test(name)) &&
|
||||||
|
(os == 'Windows CE' || /Mobi/i.test(ua))) {
|
||||||
|
name += ' Mobile';
|
||||||
|
}
|
||||||
|
// detect IE platform preview
|
||||||
|
else if (name == 'IE' && useFeatures && typeof external == 'object' && !external) {
|
||||||
|
description.unshift('platform preview');
|
||||||
|
}
|
||||||
|
// detect BlackBerry OS version
|
||||||
|
// http://docs.blackberry.com/en/developers/deliverables/18169/HTTP_headers_sent_by_BB_Browser_1234911_11.jsp
|
||||||
|
else if (/BlackBerry/.test(product) && (data =
|
||||||
|
(RegExp(product.replace(/ +/g, ' *') + '/([.\\d]+)', 'i').exec(ua) || 0)[1] ||
|
||||||
|
version)) {
|
||||||
|
os = 'Device Software ' + data;
|
||||||
|
version = null;
|
||||||
|
}
|
||||||
|
// detect Opera identifying/masking itself as another browser
|
||||||
|
// http://www.opera.com/support/kb/view/843/
|
||||||
|
else if (this != forOwn && (
|
||||||
|
(useFeatures && opera) ||
|
||||||
|
(/Opera/.test(name) && /\b(?:MSIE|Firefox)\b/i.test(ua)) ||
|
||||||
|
(name == 'Firefox' && /OS X (?:\d+\.){2,}/.test(os)) ||
|
||||||
|
(name == 'IE' && (
|
||||||
|
(os && !/^Win/.test(os) && version > 5.5) ||
|
||||||
|
/Windows XP/.test(os) && version > 8 ||
|
||||||
|
version == 8 && !/Trident/.test(ua)
|
||||||
|
))
|
||||||
|
) && !reOpera.test(data = parse.call(forOwn, ua.replace(reOpera, '') + ';')) && data.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') {
|
||||||
|
os = null;
|
||||||
|
}
|
||||||
|
data = 'identify' + data;
|
||||||
|
}
|
||||||
|
// when "masking", the UA contains only the other browser's name
|
||||||
|
else {
|
||||||
|
data = 'mask' + data;
|
||||||
|
if (operaClass) {
|
||||||
|
name = format(operaClass.replace(/([a-z])([A-Z])/g, '$1 $2'));
|
||||||
|
} else {
|
||||||
|
name = 'Opera';
|
||||||
|
}
|
||||||
|
if (/IE/.test(data)) {
|
||||||
|
os = null;
|
||||||
|
}
|
||||||
|
if (!useFeatures) {
|
||||||
|
version = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layout = ['Presto'];
|
||||||
|
description.push(data);
|
||||||
|
}
|
||||||
|
// detect WebKit Nightly and approximate Chrome/Safari versions
|
||||||
|
if ((data = (/\bAppleWebKit\/([\d.]+\+?)/i.exec(ua) || 0)[1])) {
|
||||||
|
// correct build for numeric comparison
|
||||||
|
// (e.g. "532.5" becomes "532.05")
|
||||||
|
data = [parseFloat(data.replace(/\.(\d)$/, '.0$1')), data];
|
||||||
|
// nightly builds are postfixed with a `+`
|
||||||
|
if (name == 'Safari' && data[1].slice(-1) == '+') {
|
||||||
|
name = 'WebKit Nightly';
|
||||||
|
prerelease = 'alpha';
|
||||||
|
version = data[1].slice(0, -1);
|
||||||
|
}
|
||||||
|
// clear incorrect browser versions
|
||||||
|
else if (version == data[1] ||
|
||||||
|
version == (/\bSafari\/([\d.]+\+?)/i.exec(ua) || 0)[1]) {
|
||||||
|
version = null;
|
||||||
|
}
|
||||||
|
// use the full Chrome version when available
|
||||||
|
data = [data[0], (/\bChrome\/([\d.]+)/i.exec(ua) || 0)[1]];
|
||||||
|
|
||||||
|
// detect JavaScriptCore
|
||||||
|
// http://stackoverflow.com/questions/6768474/how-can-i-detect-which-javascript-engine-v8-or-jsc-is-used-at-runtime-in-androi
|
||||||
|
if (!useFeatures || (/internal|\n/i.test(toString.toString()) && !data[1])) {
|
||||||
|
layout[1] = 'like Safari';
|
||||||
|
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 < 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) ? '' : '+');
|
||||||
|
// obscure version for some Safari 1-2 releases
|
||||||
|
if (name == 'Safari' && (!version || parseInt(version) > 45)) {
|
||||||
|
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) {
|
||||||
|
os = trim(os.replace(data, ''));
|
||||||
|
}
|
||||||
|
// add layout engine
|
||||||
|
if (layout && !/Avant|Nook/.test(name) && (
|
||||||
|
/Browser|Lunascape|Maxthon/.test(name) ||
|
||||||
|
/^(?:Adobe|Arora|Midori|Phantom|Rekonq|Rock|Sleipnir|Web)/.test(name) && layout[1])) {
|
||||||
|
// don't add layout details to description if they are falsey
|
||||||
|
(data = layout[layout.length - 1]) && description.push(data);
|
||||||
|
}
|
||||||
|
// combine contextual information
|
||||||
|
if (description.length) {
|
||||||
|
description = ['(' + description.join('; ') + ')'];
|
||||||
|
}
|
||||||
|
// append manufacturer
|
||||||
|
if (manufacturer && product && product.indexOf(manufacturer) < 0) {
|
||||||
|
description.push('on ' + manufacturer);
|
||||||
|
}
|
||||||
|
// append product
|
||||||
|
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.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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ua || (ua = null);
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The platform object.
|
||||||
|
*
|
||||||
|
* @name platform
|
||||||
|
* @type Object
|
||||||
|
*/
|
||||||
|
return {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The browser/environment version.
|
||||||
|
*
|
||||||
|
* @memberOf platform
|
||||||
|
* @type String|Null
|
||||||
|
*/
|
||||||
|
'version': name && version && (description.unshift(version), version),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the browser/environment.
|
||||||
|
*
|
||||||
|
* @memberOf platform
|
||||||
|
* @type String|Null
|
||||||
|
*/
|
||||||
|
'name': name && (description.unshift(name), name),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the operating system.
|
||||||
|
*
|
||||||
|
* @memberOf platform
|
||||||
|
* @type Object
|
||||||
|
*/
|
||||||
|
'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.
|
||||||
|
*
|
||||||
|
* @memberOf platform
|
||||||
|
* @type String|Null
|
||||||
|
*/
|
||||||
|
'description': description.length ? description.join(' ') : ua,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the browser layout engine.
|
||||||
|
*
|
||||||
|
* @memberOf platform
|
||||||
|
* @type String|Null
|
||||||
|
*/
|
||||||
|
'layout': layout && layout[0],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the product's manufacturer.
|
||||||
|
*
|
||||||
|
* @memberOf platform
|
||||||
|
* @type String|Null
|
||||||
|
*/
|
||||||
|
'manufacturer': manufacturer,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The alpha/beta release indicator.
|
||||||
|
*
|
||||||
|
* @memberOf platform
|
||||||
|
* @type String|Null
|
||||||
|
*/
|
||||||
|
'prerelease': prerelease,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the product hosting the browser.
|
||||||
|
*
|
||||||
|
* @memberOf platform
|
||||||
|
* @type String|Null
|
||||||
|
*/
|
||||||
|
'product': product,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The browser's user agent string.
|
||||||
|
*
|
||||||
|
* @memberOf platform
|
||||||
|
* @type String|Null
|
||||||
|
*/
|
||||||
|
'ua': ua,
|
||||||
|
|
||||||
|
// parses a user agent string into a platform object
|
||||||
|
'parse': parse,
|
||||||
|
|
||||||
|
// returns the platform description
|
||||||
|
'toString': toStringPlatform
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*--------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
// expose platform
|
||||||
|
// some AMD build optimizers, like r.js, check for specific condition patterns like the following:
|
||||||
|
if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
|
||||||
|
// define as an anonymous module so, through path mapping, it can be aliased
|
||||||
|
define(function() {
|
||||||
|
return parse();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// check for `exports` after `define` in case a build optimizer adds an `exports` object
|
||||||
|
else if (freeExports) {
|
||||||
|
// in Narwhal, Node.js, or RingoJS
|
||||||
|
forOwn(parse(), function(value, key) {
|
||||||
|
freeExports[key] = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// in a browser or Rhino
|
||||||
|
else {
|
||||||
|
// use square bracket notation so Closure Compiler won't munge `platform`
|
||||||
|
// http://code.google.com/closure/compiler/docs/api-tutorial3.html#export
|
||||||
|
window['platform'] = parse();
|
||||||
|
}
|
||||||
|
}(this));
|
||||||
28
vendor/qunit-clib/README.md
vendored
28
vendor/qunit-clib/README.md
vendored
@@ -1,15 +1,19 @@
|
|||||||
# QUnit CLIB <sup>v1.0.0-pre</sup>
|
# QUnit CLIB <sup>v1.0.0</sup>
|
||||||
## command-line interface boilerplate
|
## 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
|
## Screenshot
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
## 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
|
## Usage
|
||||||
|
|
||||||
~~~ js
|
```js
|
||||||
(function(window) {
|
(function(window) {
|
||||||
|
|
||||||
// use a single load function
|
// 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();
|
QUnit.start();
|
||||||
}
|
}
|
||||||
}(typeof global == 'object' && global || this));
|
}(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
|
## 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.
|
1. QUnit v1.3.0 does not work with Narwhal or Ringo < v0.8.0
|
||||||
<a name="fn1" title="Jump back to footnote 1 in the text." href="#fnref1">↩</a>
|
|
||||||
|
|
||||||
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
|
## Author
|
||||||
|
|
||||||
|
|||||||
87
vendor/qunit-clib/qunit-clib.js
vendored
87
vendor/qunit-clib/qunit-clib.js
vendored
@@ -1,10 +1,11 @@
|
|||||||
/*!
|
/*!
|
||||||
* QUnit CLI Boilerplate v1.0.0-pre
|
* QUnit CLI Boilerplate v1.0.0
|
||||||
* Copyright 2011-2012 John-David Dalton <http://allyoucanleet.com/>
|
* Copyright 2011-2012 John-David Dalton <http://allyoucanleet.com/>
|
||||||
* Based on a gist by Jörn Zaefferer <https://gist.github.com/722381>
|
* Based on a gist by Jörn Zaefferer <https://gist.github.com/722381>
|
||||||
* Available under MIT license <http://mths.be/mit>
|
* Available under MIT license <http://mths.be/mit>
|
||||||
*/
|
*/
|
||||||
;(function(global) {
|
;(function(global) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
/** Add `console.log()` support for Narwhal, Rhino, and RingoJS */
|
/** Add `console.log()` support for Narwhal, Rhino, and RingoJS */
|
||||||
global.console || (global.console = { 'log': global.print });
|
global.console || (global.console = { 'log': global.print });
|
||||||
@@ -24,7 +25,8 @@
|
|||||||
var toString = {}.toString;
|
var toString = {}.toString;
|
||||||
|
|
||||||
/** Used by timer methods */
|
/** Used by timer methods */
|
||||||
var timer,
|
var doneCalled,
|
||||||
|
timer,
|
||||||
counter = 0,
|
counter = 0,
|
||||||
ids = {};
|
ids = {};
|
||||||
|
|
||||||
@@ -90,6 +92,8 @@
|
|||||||
* @returns {Number} The the ID of the timeout.
|
* @returns {Number} The the ID of the timeout.
|
||||||
*/
|
*/
|
||||||
function schedule(fn, delay, args, repeated) {
|
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, {
|
var task = ids[++counter] = new JavaAdapter(java.util.TimerTask, {
|
||||||
'run': function() {
|
'run': function() {
|
||||||
fn.apply(global, args);
|
fn.apply(global, args);
|
||||||
@@ -149,26 +153,31 @@
|
|||||||
* `runtime`, and `total`.
|
* `runtime`, and `total`.
|
||||||
*/
|
*/
|
||||||
function done(details) {
|
function done(details) {
|
||||||
// stop `asyncTest()` from erroneously calling `done()` twice in environments w/o timeouts
|
// stop `asyncTest()` from erroneously calling `done()` twice in
|
||||||
if (!QUnit.doneCalled) {
|
// environments w/o timeouts
|
||||||
console.log(hr);
|
if (doneCalled) {
|
||||||
console.log(' PASS: ' + details.passed + ' FAIL: ' + details.failed + ' TOTAL: ' + details.total);
|
return;
|
||||||
console.log(' Finished in ' + details.runtime + ' milliseconds.');
|
|
||||||
console.log(hr);
|
|
||||||
|
|
||||||
// exit out of Rhino
|
|
||||||
try {
|
|
||||||
quit();
|
|
||||||
} catch(e) { }
|
|
||||||
|
|
||||||
// exit out of Node.js
|
|
||||||
try {
|
|
||||||
process.exit();
|
|
||||||
} catch(e) { }
|
|
||||||
|
|
||||||
// prevent multiple calls to `done()`
|
|
||||||
QUnit.doneCalled = true;
|
|
||||||
}
|
}
|
||||||
|
doneCalled = true;
|
||||||
|
console.log(hr);
|
||||||
|
console.log(' PASS: ' + details.passed + ' FAIL: ' + details.failed + ' TOTAL: ' + details.total);
|
||||||
|
console.log(' Finished in ' + details.runtime + ' milliseconds.');
|
||||||
|
console.log(hr);
|
||||||
|
|
||||||
|
// exit out of Rhino
|
||||||
|
try {
|
||||||
|
quit();
|
||||||
|
} catch(e) { }
|
||||||
|
|
||||||
|
// exit out of Node.js
|
||||||
|
try {
|
||||||
|
if (details.failed) {
|
||||||
|
console.error('Error: ' + details.failed + ' of ' + details.total + ' tests failed.');
|
||||||
|
process.exit(1);
|
||||||
|
} else {
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
} catch(e) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -181,12 +190,13 @@
|
|||||||
function log(details) {
|
function log(details) {
|
||||||
var expected = details.expected,
|
var expected = details.expected,
|
||||||
result = details.result,
|
result = details.result,
|
||||||
type = typeof expected != 'undefined' ? 'EQ' : 'OK',
|
type = typeof expected != 'undefined' ? 'EQ' : 'OK';
|
||||||
assertion = [
|
|
||||||
result ? 'PASS' : 'FAIL',
|
var assertion = [
|
||||||
type,
|
result ? 'PASS' : 'FAIL',
|
||||||
details.message || 'ok'
|
type,
|
||||||
];
|
details.message || 'ok'
|
||||||
|
];
|
||||||
|
|
||||||
if (!result && type == 'EQ') {
|
if (!result && type == 'EQ') {
|
||||||
assertion.push('Expected: ' + expected + ', Actual: ' + details.actual);
|
assertion.push('Expected: ' + expected + ', Actual: ' + details.actual);
|
||||||
@@ -215,7 +225,7 @@
|
|||||||
* @returns {String} The result string.
|
* @returns {String} The result string.
|
||||||
*/
|
*/
|
||||||
var parseObject = (function() {
|
var parseObject = (function() {
|
||||||
var _parseObject = QUnit.jsDump.parsers.object;
|
var func = QUnit.jsDump.parsers.object;
|
||||||
return function(object) {
|
return function(object) {
|
||||||
// fork to support Rhino's error objects
|
// fork to support Rhino's error objects
|
||||||
if (typeof object.rhinoException == 'object') {
|
if (typeof object.rhinoException == 'object') {
|
||||||
@@ -224,7 +234,7 @@
|
|||||||
'", fileName: "' + object.fileName +
|
'", fileName: "' + object.fileName +
|
||||||
'", lineNumber: ' + object.lineNumber + ' }';
|
'", lineNumber: ' + object.lineNumber + ' }';
|
||||||
}
|
}
|
||||||
return _parseObject(object);
|
return func(object);
|
||||||
};
|
};
|
||||||
}());
|
}());
|
||||||
|
|
||||||
@@ -237,16 +247,16 @@
|
|||||||
*/
|
*/
|
||||||
function testDone(details) {
|
function testDone(details) {
|
||||||
var assertions = QUnit.config.testStats.assertions,
|
var assertions = QUnit.config.testStats.assertions,
|
||||||
name = details.name;
|
testName = details.name;
|
||||||
|
|
||||||
if (details.failed > 0) {
|
if (details.failed > 0) {
|
||||||
console.log(' FAIL - '+ name);
|
console.log(' FAIL - '+ testName);
|
||||||
each(assertions, function(value) {
|
each(assertions, function(value) {
|
||||||
console.log(' ' + value);
|
console.log(' ' + value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
console.log(' PASS - ' + name);
|
console.log(' PASS - ' + testName);
|
||||||
}
|
}
|
||||||
assertions.length = 0;
|
assertions.length = 0;
|
||||||
}
|
}
|
||||||
@@ -274,8 +284,11 @@
|
|||||||
// exclude `module` because some environments have it as a built-in object
|
// exclude `module` because some environments have it as a built-in object
|
||||||
each(['asyncTest', 'deepEqual', 'equal', 'equals', 'expect', 'notDeepEqual',
|
each(['asyncTest', 'deepEqual', 'equal', 'equals', 'expect', 'notDeepEqual',
|
||||||
'notEqual', 'notStrictEqual', 'ok', 'raises', 'same', 'start', 'stop',
|
'notEqual', 'notStrictEqual', 'ok', 'raises', 'same', 'start', 'stop',
|
||||||
'strictEqual', 'test'], function(name) {
|
'strictEqual', 'test', 'throws'], function(funcName) {
|
||||||
global[name] = QUnit[name];
|
var func = QUnit[funcName];
|
||||||
|
if (func) {
|
||||||
|
global[funcName] = func;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// expose timer methods to global
|
// expose timer methods to global
|
||||||
@@ -301,11 +314,11 @@
|
|||||||
QUnit.moduleStart(moduleStart);
|
QUnit.moduleStart(moduleStart);
|
||||||
QUnit.testDone(testDone);
|
QUnit.testDone(testDone);
|
||||||
|
|
||||||
// wrap `parseObject`
|
// add wrapped function
|
||||||
QUnit.jsDump.parsers.object = parseObject;
|
QUnit.jsDump.parsers.object = parseObject;
|
||||||
|
|
||||||
// must call `QUnit.start()` in the test file if using QUnit < 1.3.0 with Node.js
|
// must call `QUnit.start()` in the test file if using QUnit < 1.3.0 with
|
||||||
// or any version of QUnit with Narwhal, Rhino, or RingoJS
|
// Node.js or any version of QUnit with Narwhal, Rhino, or RingoJS
|
||||||
QUnit.init();
|
QUnit.init();
|
||||||
|
|
||||||
}(typeof global == 'object' && global || this));
|
}(typeof global == 'object' && global || this));
|
||||||
|
|||||||
23
vendor/qunit/README.md
vendored
23
vendor/qunit/README.md
vendored
@@ -1,4 +1,4 @@
|
|||||||
[QUnit](http://docs.jquery.com/QUnit) - A JavaScript Unit Testing framework.
|
[QUnit](http://qunitjs.com) - A JavaScript Unit Testing framework.
|
||||||
================================
|
================================
|
||||||
|
|
||||||
QUnit is a powerful, easy-to-use, JavaScript test suite. It's used by the jQuery
|
QUnit is a powerful, easy-to-use, JavaScript test suite. It's used by the jQuery
|
||||||
@@ -35,14 +35,25 @@ the change, run `grunt` to lint and test it, then commit, push and create a pull
|
|||||||
Include some background for the change in the commit message and `Fixes #nnn`, referring
|
Include some background for the change in the commit message and `Fixes #nnn`, referring
|
||||||
to the issue number you're addressing.
|
to the issue number you're addressing.
|
||||||
|
|
||||||
To run `grunt`, you need `node` and `npm`, then `npm install grunt -g`.
|
To run `grunt`, you need `node` and `npm`, then `npm install grunt -g`. That gives you a global
|
||||||
|
grunt binary. For additional grunt tasks, also run `npm install`.
|
||||||
|
|
||||||
Releases
|
Releases
|
||||||
--------
|
--------
|
||||||
|
|
||||||
Install git-extras and run `git changelog` to update History.md.
|
Install git-extras and run `git changelog` to update History.md.
|
||||||
Update qunit/qunit.js|css to the release version, commit and tag, update them
|
Update qunit/qunit.js|css and package.json to the release version, commit and
|
||||||
again to the next version, commit and push commits and tags.
|
tag, update them again to the next version, commit and push commits and tags
|
||||||
|
(`git push --tags origin master`).
|
||||||
|
|
||||||
Put the 'v' in front of the tag (unlike the 1.1.0 release). Clean up the changelog,
|
Put the 'v' in front of the tag, e.g. `v1.8.0`. Clean up the changelog, removing merge commits
|
||||||
removing merge commits or whitespace cleanups.
|
or whitespace cleanups.
|
||||||
|
|
||||||
|
To upload to code.jquery.com (replace $version accordingly):
|
||||||
|
|
||||||
|
scp -q qunit/qunit.js jqadmin@code.origin.jquery.com:/var/www/html/code.jquery.com/qunit/qunit-$version.js
|
||||||
|
scp -q qunit/qunit.css jqadmin@code.origin.jquery.com:/var/www/html/code.jquery.com/qunit/qunit-$version.css
|
||||||
|
|
||||||
|
Then update /var/www/html/code.jquery.com/index.html and purge it with:
|
||||||
|
|
||||||
|
curl -s http://code.origin.jquery.com/?reload
|
||||||
0
vendor/backbone/test/vendor/qunit.js → vendor/qunit/qunit/qunit-1.8.0.js
vendored
Executable file → Normal file
0
vendor/backbone/test/vendor/qunit.js → vendor/qunit/qunit/qunit-1.8.0.js
vendored
Executable file → Normal file
55
vendor/qunit/qunit/qunit.css
vendored
55
vendor/qunit/qunit/qunit.css
vendored
@@ -1,11 +1,11 @@
|
|||||||
/**
|
/**
|
||||||
* QUnit v1.8.0 - A JavaScript Unit Testing Framework
|
* QUnit v1.10.0 - A JavaScript Unit Testing Framework
|
||||||
*
|
*
|
||||||
* http://docs.jquery.com/QUnit
|
* http://qunitjs.com
|
||||||
*
|
*
|
||||||
* Copyright (c) 2012 John Resig, Jörn Zaefferer
|
* Copyright 2012 jQuery Foundation and other contributors
|
||||||
* Dual licensed under the MIT (MIT-LICENSE.txt)
|
* Released under the MIT license.
|
||||||
* or GPL (GPL-LICENSE.txt) licenses.
|
* http://jquery.org/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** Font Family and Sizes */
|
/** Font Family and Sizes */
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
/** Resets */
|
/** Resets */
|
||||||
|
|
||||||
#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult {
|
#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
@@ -38,10 +38,10 @@
|
|||||||
line-height: 1em;
|
line-height: 1em;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
|
|
||||||
border-radius: 15px 15px 0 0;
|
border-radius: 5px 5px 0 0;
|
||||||
-moz-border-radius: 15px 15px 0 0;
|
-moz-border-radius: 5px 5px 0 0;
|
||||||
-webkit-border-top-right-radius: 15px;
|
-webkit-border-top-right-radius: 5px;
|
||||||
-webkit-border-top-left-radius: 15px;
|
-webkit-border-top-left-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#qunit-header a {
|
#qunit-header a {
|
||||||
@@ -54,9 +54,9 @@
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
#qunit-header label {
|
#qunit-testrunner-toolbar label {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding-left: 0.5em;
|
padding: 0 .5em 0 .1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#qunit-banner {
|
#qunit-banner {
|
||||||
@@ -67,6 +67,7 @@
|
|||||||
padding: 0.5em 0 0.5em 2em;
|
padding: 0.5em 0 0.5em 2em;
|
||||||
color: #5E740B;
|
color: #5E740B;
|
||||||
background-color: #eee;
|
background-color: #eee;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
#qunit-userAgent {
|
#qunit-userAgent {
|
||||||
@@ -76,6 +77,9 @@
|
|||||||
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
|
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#qunit-modulefilter-container {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
/** Tests: Pass/Fail */
|
/** Tests: Pass/Fail */
|
||||||
|
|
||||||
@@ -113,13 +117,9 @@
|
|||||||
|
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
|
|
||||||
border-radius: 15px;
|
border-radius: 5px;
|
||||||
-moz-border-radius: 15px;
|
-moz-border-radius: 5px;
|
||||||
-webkit-border-radius: 15px;
|
-webkit-border-radius: 5px;
|
||||||
|
|
||||||
box-shadow: inset 0px 2px 13px #999;
|
|
||||||
-moz-box-shadow: inset 0px 2px 13px #999;
|
|
||||||
-webkit-box-shadow: inset 0px 2px 13px #999;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#qunit-tests table {
|
#qunit-tests table {
|
||||||
@@ -162,8 +162,7 @@
|
|||||||
#qunit-tests b.failed { color: #710909; }
|
#qunit-tests b.failed { color: #710909; }
|
||||||
|
|
||||||
#qunit-tests li li {
|
#qunit-tests li li {
|
||||||
margin: 0.5em;
|
padding: 5px;
|
||||||
padding: 0.4em 0.5em 0.4em 0.5em;
|
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
list-style-position: inside;
|
list-style-position: inside;
|
||||||
@@ -172,9 +171,9 @@
|
|||||||
/*** Passing Styles */
|
/*** Passing Styles */
|
||||||
|
|
||||||
#qunit-tests li li.pass {
|
#qunit-tests li li.pass {
|
||||||
color: #5E740B;
|
color: #3c510c;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-left: 26px solid #C6E746;
|
border-left: 10px solid #C6E746;
|
||||||
}
|
}
|
||||||
|
|
||||||
#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
|
#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
|
||||||
@@ -190,15 +189,15 @@
|
|||||||
#qunit-tests li li.fail {
|
#qunit-tests li li.fail {
|
||||||
color: #710909;
|
color: #710909;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-left: 26px solid #EE5757;
|
border-left: 10px solid #EE5757;
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
}
|
}
|
||||||
|
|
||||||
#qunit-tests > li:last-child {
|
#qunit-tests > li:last-child {
|
||||||
border-radius: 0 0 15px 15px;
|
border-radius: 0 0 5px 5px;
|
||||||
-moz-border-radius: 0 0 15px 15px;
|
-moz-border-radius: 0 0 5px 5px;
|
||||||
-webkit-border-bottom-right-radius: 15px;
|
-webkit-border-bottom-right-radius: 5px;
|
||||||
-webkit-border-bottom-left-radius: 15px;
|
-webkit-border-bottom-left-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#qunit-tests .fail { color: #000000; background-color: #EE5757; }
|
#qunit-tests .fail { color: #000000; background-color: #EE5757; }
|
||||||
|
|||||||
198
vendor/qunit/qunit/qunit.js
vendored
198
vendor/qunit/qunit/qunit.js
vendored
@@ -1,11 +1,11 @@
|
|||||||
/**
|
/**
|
||||||
* QUnit v1.8.0 - A JavaScript Unit Testing Framework
|
* QUnit v1.10.0 - A JavaScript Unit Testing Framework
|
||||||
*
|
*
|
||||||
* http://docs.jquery.com/QUnit
|
* http://qunitjs.com
|
||||||
*
|
*
|
||||||
* Copyright (c) 2012 John Resig, Jörn Zaefferer
|
* Copyright 2012 jQuery Foundation and other contributors
|
||||||
* Dual licensed under the MIT (MIT-LICENSE.txt)
|
* Released under the MIT license.
|
||||||
* or GPL (GPL-LICENSE.txt) licenses.
|
* http://jquery.org/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function( window ) {
|
(function( window ) {
|
||||||
@@ -17,6 +17,8 @@ var QUnit,
|
|||||||
fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""),
|
fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""),
|
||||||
toString = Object.prototype.toString,
|
toString = Object.prototype.toString,
|
||||||
hasOwn = Object.prototype.hasOwnProperty,
|
hasOwn = Object.prototype.hasOwnProperty,
|
||||||
|
// Keep a local reference to Date (GH-283)
|
||||||
|
Date = window.Date,
|
||||||
defined = {
|
defined = {
|
||||||
setTimeout: typeof window.setTimeout !== "undefined",
|
setTimeout: typeof window.setTimeout !== "undefined",
|
||||||
sessionStorage: (function() {
|
sessionStorage: (function() {
|
||||||
@@ -304,7 +306,8 @@ QUnit = {
|
|||||||
// call on start of module test to prepend name to all tests
|
// call on start of module test to prepend name to all tests
|
||||||
module: function( name, testEnvironment ) {
|
module: function( name, testEnvironment ) {
|
||||||
config.currentModule = name;
|
config.currentModule = name;
|
||||||
config.currentModuleTestEnviroment = testEnvironment;
|
config.currentModuleTestEnvironment = testEnvironment;
|
||||||
|
config.modules[name] = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
asyncTest: function( testName, expected, callback ) {
|
asyncTest: function( testName, expected, callback ) {
|
||||||
@@ -336,7 +339,7 @@ QUnit = {
|
|||||||
async: async,
|
async: async,
|
||||||
callback: callback,
|
callback: callback,
|
||||||
module: config.currentModule,
|
module: config.currentModule,
|
||||||
moduleTestEnvironment: config.currentModuleTestEnviroment,
|
moduleTestEnvironment: config.currentModuleTestEnvironment,
|
||||||
stack: sourceFromStacktrace( 2 )
|
stack: sourceFromStacktrace( 2 )
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -349,7 +352,11 @@ QUnit = {
|
|||||||
|
|
||||||
// Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
|
// Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
|
||||||
expect: function( asserts ) {
|
expect: function( asserts ) {
|
||||||
config.current.expected = asserts;
|
if (arguments.length === 1) {
|
||||||
|
config.current.expected = asserts;
|
||||||
|
} else {
|
||||||
|
return config.current.expected;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
start: function( count ) {
|
start: function( count ) {
|
||||||
@@ -403,6 +410,8 @@ QUnit = {
|
|||||||
QUnit.assert = {
|
QUnit.assert = {
|
||||||
/**
|
/**
|
||||||
* Asserts rough true-ish result.
|
* Asserts rough true-ish result.
|
||||||
|
* @name ok
|
||||||
|
* @function
|
||||||
* @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
|
* @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
|
||||||
*/
|
*/
|
||||||
ok: function( result, msg ) {
|
ok: function( result, msg ) {
|
||||||
@@ -413,6 +422,8 @@ QUnit.assert = {
|
|||||||
|
|
||||||
var source,
|
var source,
|
||||||
details = {
|
details = {
|
||||||
|
module: config.current.module,
|
||||||
|
name: config.current.testName,
|
||||||
result: result,
|
result: result,
|
||||||
message: msg
|
message: msg
|
||||||
};
|
};
|
||||||
@@ -437,36 +448,59 @@ QUnit.assert = {
|
|||||||
/**
|
/**
|
||||||
* Assert that the first two arguments are equal, with an optional message.
|
* Assert that the first two arguments are equal, with an optional message.
|
||||||
* Prints out both actual and expected values.
|
* Prints out both actual and expected values.
|
||||||
|
* @name equal
|
||||||
|
* @function
|
||||||
* @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" );
|
* @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" );
|
||||||
*/
|
*/
|
||||||
equal: function( actual, expected, message ) {
|
equal: function( actual, expected, message ) {
|
||||||
QUnit.push( expected == actual, actual, expected, message );
|
QUnit.push( expected == actual, actual, expected, message );
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name notEqual
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
notEqual: function( actual, expected, message ) {
|
notEqual: function( actual, expected, message ) {
|
||||||
QUnit.push( expected != actual, actual, expected, message );
|
QUnit.push( expected != actual, actual, expected, message );
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name deepEqual
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
deepEqual: function( actual, expected, message ) {
|
deepEqual: function( actual, expected, message ) {
|
||||||
QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
|
QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name notDeepEqual
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
notDeepEqual: function( actual, expected, message ) {
|
notDeepEqual: function( actual, expected, message ) {
|
||||||
QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message );
|
QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message );
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name strictEqual
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
strictEqual: function( actual, expected, message ) {
|
strictEqual: function( actual, expected, message ) {
|
||||||
QUnit.push( expected === actual, actual, expected, message );
|
QUnit.push( expected === actual, actual, expected, message );
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name notStrictEqual
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
notStrictEqual: function( actual, expected, message ) {
|
notStrictEqual: function( actual, expected, message ) {
|
||||||
QUnit.push( expected !== actual, actual, expected, message );
|
QUnit.push( expected !== actual, actual, expected, message );
|
||||||
},
|
},
|
||||||
|
|
||||||
raises: function( block, expected, message ) {
|
throws: function( block, expected, message ) {
|
||||||
var actual,
|
var actual,
|
||||||
ok = false;
|
ok = false;
|
||||||
|
|
||||||
|
// 'expected' is optional
|
||||||
if ( typeof expected === "string" ) {
|
if ( typeof expected === "string" ) {
|
||||||
message = expected;
|
message = expected;
|
||||||
expected = null;
|
expected = null;
|
||||||
@@ -494,18 +528,29 @@ QUnit.assert = {
|
|||||||
} else if ( expected.call( {}, actual ) === true ) {
|
} else if ( expected.call( {}, actual ) === true ) {
|
||||||
ok = true;
|
ok = true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
QUnit.push( ok, actual, null, message );
|
QUnit.push( ok, actual, null, message );
|
||||||
|
} else {
|
||||||
|
QUnit.pushFailure( message, null, 'No exception was thrown.' );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// @deprecated: Kept assertion helpers in root for backwards compatibility
|
/**
|
||||||
|
* @deprecate since 1.8.0
|
||||||
|
* Kept assertion helpers in root for backwards compatibility
|
||||||
|
*/
|
||||||
extend( QUnit, QUnit.assert );
|
extend( QUnit, QUnit.assert );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated: Kept for backwards compatibility
|
* @deprecated since 1.9.0
|
||||||
* next step: remove entirely
|
* Kept global "raises()" for backwards compatibility
|
||||||
|
*/
|
||||||
|
QUnit.raises = QUnit.assert.throws;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated since 1.0.0, replaced with error pushes since 1.3.0
|
||||||
|
* Kept to avoid TypeErrors for undefined methods.
|
||||||
*/
|
*/
|
||||||
QUnit.equals = function() {
|
QUnit.equals = function() {
|
||||||
QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" );
|
QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" );
|
||||||
@@ -549,7 +594,23 @@ config = {
|
|||||||
// when enabled, all tests must call expect()
|
// when enabled, all tests must call expect()
|
||||||
requireExpects: false,
|
requireExpects: false,
|
||||||
|
|
||||||
urlConfig: [ "noglobals", "notrycatch" ],
|
// add checkboxes that are persisted in the query-string
|
||||||
|
// when enabled, the id is set to `true` as a `QUnit.config` property
|
||||||
|
urlConfig: [
|
||||||
|
{
|
||||||
|
id: "noglobals",
|
||||||
|
label: "Check for Globals",
|
||||||
|
tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "notrycatch",
|
||||||
|
label: "No try-catch",
|
||||||
|
tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
// Set of all modules.
|
||||||
|
modules: {},
|
||||||
|
|
||||||
// logging callback queues
|
// logging callback queues
|
||||||
begin: [],
|
begin: [],
|
||||||
@@ -661,17 +722,10 @@ extend( QUnit, {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Resets the test setup. Useful for tests that modify the DOM.
|
// Resets the test setup. Useful for tests that modify the DOM.
|
||||||
// If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
|
|
||||||
reset: function() {
|
reset: function() {
|
||||||
var fixture;
|
var fixture = id( "qunit-fixture" );
|
||||||
|
if ( fixture ) {
|
||||||
if ( window.jQuery ) {
|
fixture.innerHTML = config.fixture;
|
||||||
jQuery( "#qunit-fixture" ).html( config.fixture );
|
|
||||||
} else {
|
|
||||||
fixture = id( "qunit-fixture" );
|
|
||||||
if ( fixture ) {
|
|
||||||
fixture.innerHTML = config.fixture;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -732,6 +786,8 @@ extend( QUnit, {
|
|||||||
|
|
||||||
var output, source,
|
var output, source,
|
||||||
details = {
|
details = {
|
||||||
|
module: config.current.module,
|
||||||
|
name: config.current.testName,
|
||||||
result: result,
|
result: result,
|
||||||
message: message,
|
message: message,
|
||||||
actual: actual,
|
actual: actual,
|
||||||
@@ -770,26 +826,36 @@ extend( QUnit, {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
pushFailure: function( message, source ) {
|
pushFailure: function( message, source, actual ) {
|
||||||
if ( !config.current ) {
|
if ( !config.current ) {
|
||||||
throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) );
|
throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) );
|
||||||
}
|
}
|
||||||
|
|
||||||
var output,
|
var output,
|
||||||
details = {
|
details = {
|
||||||
|
module: config.current.module,
|
||||||
|
name: config.current.testName,
|
||||||
result: false,
|
result: false,
|
||||||
message: message
|
message: message
|
||||||
};
|
};
|
||||||
|
|
||||||
message = escapeInnerText(message ) || "error";
|
message = escapeInnerText( message ) || "error";
|
||||||
message = "<span class='test-message'>" + message + "</span>";
|
message = "<span class='test-message'>" + message + "</span>";
|
||||||
output = message;
|
output = message;
|
||||||
|
|
||||||
|
output += "<table>";
|
||||||
|
|
||||||
|
if ( actual ) {
|
||||||
|
output += "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeInnerText( actual ) + "</pre></td></tr>";
|
||||||
|
}
|
||||||
|
|
||||||
if ( source ) {
|
if ( source ) {
|
||||||
details.source = source;
|
details.source = source;
|
||||||
output += "<table><tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr></table>";
|
output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr>";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
output += "</table>";
|
||||||
|
|
||||||
runLoggingCallbacks( "log", QUnit, details );
|
runLoggingCallbacks( "log", QUnit, details );
|
||||||
|
|
||||||
config.current.assertions.push({
|
config.current.assertions.push({
|
||||||
@@ -859,7 +925,9 @@ QUnit.load = function() {
|
|||||||
runLoggingCallbacks( "begin", QUnit, {} );
|
runLoggingCallbacks( "begin", QUnit, {} );
|
||||||
|
|
||||||
// Initialize the config, saving the execution queue
|
// Initialize the config, saving the execution queue
|
||||||
var banner, filter, i, label, len, main, ol, toolbar, userAgent, val,
|
var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, urlConfigCheckboxes, moduleFilter,
|
||||||
|
numModules = 0,
|
||||||
|
moduleFilterHtml = "",
|
||||||
urlConfigHtml = "",
|
urlConfigHtml = "",
|
||||||
oldconfig = extend( {}, config );
|
oldconfig = extend( {}, config );
|
||||||
|
|
||||||
@@ -872,10 +940,26 @@ QUnit.load = function() {
|
|||||||
|
|
||||||
for ( i = 0; i < len; i++ ) {
|
for ( i = 0; i < len; i++ ) {
|
||||||
val = config.urlConfig[i];
|
val = config.urlConfig[i];
|
||||||
config[val] = QUnit.urlParams[val];
|
if ( typeof val === "string" ) {
|
||||||
urlConfigHtml += "<label><input name='" + val + "' type='checkbox'" + ( config[val] ? " checked='checked'" : "" ) + ">" + val + "</label>";
|
val = {
|
||||||
|
id: val,
|
||||||
|
label: val,
|
||||||
|
tooltip: "[no tooltip available]"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
config[ val.id ] = QUnit.urlParams[ val.id ];
|
||||||
|
urlConfigHtml += "<input id='qunit-urlconfig-" + val.id + "' name='" + val.id + "' type='checkbox'" + ( config[ val.id ] ? " checked='checked'" : "" ) + " title='" + val.tooltip + "'><label for='qunit-urlconfig-" + val.id + "' title='" + val.tooltip + "'>" + val.label + "</label>";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label><select id='qunit-modulefilter' name='modulefilter'><option value='' " + ( config.module === undefined ? "selected" : "" ) + ">< All Modules ></option>";
|
||||||
|
for ( i in config.modules ) {
|
||||||
|
if ( config.modules.hasOwnProperty( i ) ) {
|
||||||
|
numModules += 1;
|
||||||
|
moduleFilterHtml += "<option value='" + encodeURIComponent(i) + "' " + ( config.module === i ? "selected" : "" ) + ">" + i + "</option>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
moduleFilterHtml += "</select>";
|
||||||
|
|
||||||
// `userAgent` initialized at top of scope
|
// `userAgent` initialized at top of scope
|
||||||
userAgent = id( "qunit-userAgent" );
|
userAgent = id( "qunit-userAgent" );
|
||||||
if ( userAgent ) {
|
if ( userAgent ) {
|
||||||
@@ -885,12 +969,7 @@ QUnit.load = function() {
|
|||||||
// `banner` initialized at top of scope
|
// `banner` initialized at top of scope
|
||||||
banner = id( "qunit-header" );
|
banner = id( "qunit-header" );
|
||||||
if ( banner ) {
|
if ( banner ) {
|
||||||
banner.innerHTML = "<a href='" + QUnit.url({ filter: undefined }) + "'>" + banner.innerHTML + "</a> " + urlConfigHtml;
|
banner.innerHTML = "<a href='" + QUnit.url({ filter: undefined, module: undefined, testNumber: undefined }) + "'>" + banner.innerHTML + "</a> ";
|
||||||
addEvent( banner, "change", function( event ) {
|
|
||||||
var params = {};
|
|
||||||
params[ event.target.name ] = event.target.checked ? true : undefined;
|
|
||||||
window.location = QUnit.url( params );
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// `toolbar` initialized at top of scope
|
// `toolbar` initialized at top of scope
|
||||||
@@ -931,8 +1010,31 @@ QUnit.load = function() {
|
|||||||
// `label` initialized at top of scope
|
// `label` initialized at top of scope
|
||||||
label = document.createElement( "label" );
|
label = document.createElement( "label" );
|
||||||
label.setAttribute( "for", "qunit-filter-pass" );
|
label.setAttribute( "for", "qunit-filter-pass" );
|
||||||
|
label.setAttribute( "title", "Only show tests and assertons that fail. Stored in sessionStorage." );
|
||||||
label.innerHTML = "Hide passed tests";
|
label.innerHTML = "Hide passed tests";
|
||||||
toolbar.appendChild( label );
|
toolbar.appendChild( label );
|
||||||
|
|
||||||
|
urlConfigCheckboxes = document.createElement( 'span' );
|
||||||
|
urlConfigCheckboxes.innerHTML = urlConfigHtml;
|
||||||
|
addEvent( urlConfigCheckboxes, "change", function( event ) {
|
||||||
|
var params = {};
|
||||||
|
params[ event.target.name ] = event.target.checked ? true : undefined;
|
||||||
|
window.location = QUnit.url( params );
|
||||||
|
});
|
||||||
|
toolbar.appendChild( urlConfigCheckboxes );
|
||||||
|
|
||||||
|
if (numModules > 1) {
|
||||||
|
moduleFilter = document.createElement( 'span' );
|
||||||
|
moduleFilter.setAttribute( 'id', 'qunit-modulefilter-container' );
|
||||||
|
moduleFilter.innerHTML = moduleFilterHtml;
|
||||||
|
addEvent( moduleFilter, "change", function() {
|
||||||
|
var selectBox = moduleFilter.getElementsByTagName("select")[0],
|
||||||
|
selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value);
|
||||||
|
|
||||||
|
window.location = QUnit.url( { module: ( selectedModule === "" ) ? undefined : selectedModule } );
|
||||||
|
});
|
||||||
|
toolbar.appendChild(moduleFilter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// `main` initialized at top of scope
|
// `main` initialized at top of scope
|
||||||
@@ -970,9 +1072,9 @@ window.onerror = function ( error, filePath, linerNr ) {
|
|||||||
}
|
}
|
||||||
QUnit.pushFailure( error, filePath + ":" + linerNr );
|
QUnit.pushFailure( error, filePath + ":" + linerNr );
|
||||||
} else {
|
} else {
|
||||||
QUnit.test( "global failure", function() {
|
QUnit.test( "global failure", extend( function() {
|
||||||
QUnit.pushFailure( error, filePath + ":" + linerNr );
|
QUnit.pushFailure( error, filePath + ":" + linerNr );
|
||||||
});
|
}, { validTest: validTest } ) );
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1039,6 +1141,11 @@ function done() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// scroll back to top to show results
|
||||||
|
if ( window.scrollTo ) {
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
runLoggingCallbacks( "done", QUnit, {
|
runLoggingCallbacks( "done", QUnit, {
|
||||||
failed: config.stats.bad,
|
failed: config.stats.bad,
|
||||||
passed: passed,
|
passed: passed,
|
||||||
@@ -1051,14 +1158,20 @@ function done() {
|
|||||||
function validTest( test ) {
|
function validTest( test ) {
|
||||||
var include,
|
var include,
|
||||||
filter = config.filter && config.filter.toLowerCase(),
|
filter = config.filter && config.filter.toLowerCase(),
|
||||||
module = config.module,
|
module = config.module && config.module.toLowerCase(),
|
||||||
fullName = (test.module + ": " + test.testName).toLowerCase();
|
fullName = (test.module + ": " + test.testName).toLowerCase();
|
||||||
|
|
||||||
|
// Internally-generated tests are always valid
|
||||||
|
if ( test.callback && test.callback.validTest === validTest ) {
|
||||||
|
delete test.callback.validTest;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if ( config.testNumber ) {
|
if ( config.testNumber ) {
|
||||||
return test.testNumber === config.testNumber;
|
return test.testNumber === config.testNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( module && test.module !== module ) {
|
if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1335,7 +1448,8 @@ QUnit.equiv = (function() {
|
|||||||
a.global === b.global &&
|
a.global === b.global &&
|
||||||
// (gmi) ...
|
// (gmi) ...
|
||||||
a.ignoreCase === b.ignoreCase &&
|
a.ignoreCase === b.ignoreCase &&
|
||||||
a.multiline === b.multiline;
|
a.multiline === b.multiline &&
|
||||||
|
a.sticky === b.sticky;
|
||||||
},
|
},
|
||||||
|
|
||||||
// - skip when the property is a method of an instance (OOP)
|
// - skip when the property is a method of an instance (OOP)
|
||||||
|
|||||||
899
vendor/requirejs/require.js
vendored
899
vendor/requirejs/require.js
vendored
File diff suppressed because it is too large
Load Diff
64
vendor/underscore/test/arrays.js
vendored
64
vendor/underscore/test/arrays.js
vendored
@@ -2,7 +2,7 @@ $(document).ready(function() {
|
|||||||
|
|
||||||
module("Arrays");
|
module("Arrays");
|
||||||
|
|
||||||
test("arrays: first", function() {
|
test("first", function() {
|
||||||
equal(_.first([1,2,3]), 1, 'can pull out the first element of an array');
|
equal(_.first([1,2,3]), 1, 'can pull out the first element of an array');
|
||||||
equal(_([1, 2, 3]).first(), 1, 'can perform OO-style "first()"');
|
equal(_([1, 2, 3]).first(), 1, 'can perform OO-style "first()"');
|
||||||
equal(_.first([1,2,3], 0).join(', '), "", 'can pass an index to first');
|
equal(_.first([1,2,3], 0).join(', '), "", 'can pass an index to first');
|
||||||
@@ -14,9 +14,11 @@ $(document).ready(function() {
|
|||||||
equal(result.join(','), '1,1', 'works well with _.map');
|
equal(result.join(','), '1,1', 'works well with _.map');
|
||||||
result = (function() { return _.take([1,2,3], 2); })();
|
result = (function() { return _.take([1,2,3], 2); })();
|
||||||
equal(result.join(','), '1,2', 'aliased as take');
|
equal(result.join(','), '1,2', 'aliased as take');
|
||||||
|
|
||||||
|
equal(_.first(null), undefined, 'handles nulls');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("arrays: rest", function() {
|
test("rest", function() {
|
||||||
var numbers = [1, 2, 3, 4];
|
var numbers = [1, 2, 3, 4];
|
||||||
equal(_.rest(numbers).join(", "), "2, 3, 4", 'working rest()');
|
equal(_.rest(numbers).join(", "), "2, 3, 4", 'working rest()');
|
||||||
equal(_.rest(numbers, 0).join(", "), "1, 2, 3, 4", 'working rest(0)');
|
equal(_.rest(numbers, 0).join(", "), "1, 2, 3, 4", 'working rest(0)');
|
||||||
@@ -25,9 +27,11 @@ $(document).ready(function() {
|
|||||||
equal(result.join(', '), '2, 3, 4', 'aliased as tail and works on arguments object');
|
equal(result.join(', '), '2, 3, 4', 'aliased as tail and works on arguments object');
|
||||||
result = _.map([[1,2,3],[1,2,3]], _.rest);
|
result = _.map([[1,2,3],[1,2,3]], _.rest);
|
||||||
equal(_.flatten(result).join(','), '2,3,2,3', 'works well with _.map');
|
equal(_.flatten(result).join(','), '2,3,2,3', 'works well with _.map');
|
||||||
|
result = (function(){ return _(arguments).drop(); })(1, 2, 3, 4);
|
||||||
|
equal(result.join(', '), '2, 3, 4', 'aliased as drop and works on arguments object');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("arrays: initial", function() {
|
test("initial", function() {
|
||||||
equal(_.initial([1,2,3,4,5]).join(", "), "1, 2, 3, 4", 'working initial()');
|
equal(_.initial([1,2,3,4,5]).join(", "), "1, 2, 3, 4", 'working initial()');
|
||||||
equal(_.initial([1,2,3,4],2).join(", "), "1, 2", 'initial can take an index');
|
equal(_.initial([1,2,3,4],2).join(", "), "1, 2", 'initial can take an index');
|
||||||
var result = (function(){ return _(arguments).initial(); })(1, 2, 3, 4);
|
var result = (function(){ return _(arguments).initial(); })(1, 2, 3, 4);
|
||||||
@@ -36,7 +40,7 @@ $(document).ready(function() {
|
|||||||
equal(_.flatten(result).join(','), '1,2,1,2', 'initial works with _.map');
|
equal(_.flatten(result).join(','), '1,2,1,2', 'initial works with _.map');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("arrays: last", function() {
|
test("last", function() {
|
||||||
equal(_.last([1,2,3]), 3, 'can pull out the last element of an array');
|
equal(_.last([1,2,3]), 3, 'can pull out the last element of an array');
|
||||||
equal(_.last([1,2,3], 0).join(', '), "", 'can pass an index to last');
|
equal(_.last([1,2,3], 0).join(', '), "", 'can pass an index to last');
|
||||||
equal(_.last([1,2,3], 2).join(', '), '2, 3', 'can pass an index to last');
|
equal(_.last([1,2,3], 2).join(', '), '2, 3', 'can pass an index to last');
|
||||||
@@ -45,15 +49,17 @@ $(document).ready(function() {
|
|||||||
equal(result, 4, 'works on an arguments object');
|
equal(result, 4, 'works on an arguments object');
|
||||||
result = _.map([[1,2,3],[1,2,3]], _.last);
|
result = _.map([[1,2,3],[1,2,3]], _.last);
|
||||||
equal(result.join(','), '3,3', 'works well with _.map');
|
equal(result.join(','), '3,3', 'works well with _.map');
|
||||||
|
|
||||||
|
equal(_.last(null), undefined, 'handles nulls');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("arrays: compact", function() {
|
test("compact", function() {
|
||||||
equal(_.compact([0, 1, false, 2, false, 3]).length, 3, 'can trim out all falsy values');
|
equal(_.compact([0, 1, false, 2, false, 3]).length, 3, 'can trim out all falsy values');
|
||||||
var result = (function(){ return _(arguments).compact().length; })(0, 1, false, 2, false, 3);
|
var result = (function(){ return _(arguments).compact().length; })(0, 1, false, 2, false, 3);
|
||||||
equal(result, 3, 'works on an arguments object');
|
equal(result, 3, 'works on an arguments object');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("arrays: flatten", function() {
|
test("flatten", function() {
|
||||||
if (window.JSON) {
|
if (window.JSON) {
|
||||||
var list = [1, [2], [3, [[[4]]]]];
|
var list = [1, [2], [3, [[[4]]]]];
|
||||||
equal(JSON.stringify(_.flatten(list)), '[1,2,3,4]', 'can flatten nested arrays');
|
equal(JSON.stringify(_.flatten(list)), '[1,2,3,4]', 'can flatten nested arrays');
|
||||||
@@ -63,7 +69,7 @@ $(document).ready(function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("arrays: without", function() {
|
test("without", function() {
|
||||||
var list = [1, 2, 1, 0, 3, 1, 4];
|
var list = [1, 2, 1, 0, 3, 1, 4];
|
||||||
equal(_.without(list, 0, 1).join(', '), '2, 3, 4', 'can remove all instances of an object');
|
equal(_.without(list, 0, 1).join(', '), '2, 3, 4', 'can remove all instances of an object');
|
||||||
var result = (function(){ return _.without(arguments, 0, 1); })(1, 2, 1, 0, 3, 1, 4);
|
var result = (function(){ return _.without(arguments, 0, 1); })(1, 2, 1, 0, 3, 1, 4);
|
||||||
@@ -74,7 +80,7 @@ $(document).ready(function() {
|
|||||||
ok(_.without(list, list[0]).length == 1, 'ditto.');
|
ok(_.without(list, list[0]).length == 1, 'ditto.');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("arrays: uniq", function() {
|
test("uniq", function() {
|
||||||
var list = [1, 2, 1, 3, 1, 4];
|
var list = [1, 2, 1, 3, 1, 4];
|
||||||
equal(_.uniq(list).join(', '), '1, 2, 3, 4', 'can find the unique values of an unsorted array');
|
equal(_.uniq(list).join(', '), '1, 2, 3, 4', 'can find the unique values of an unsorted array');
|
||||||
|
|
||||||
@@ -85,6 +91,8 @@ $(document).ready(function() {
|
|||||||
var iterator = function(value) { return value.name; };
|
var iterator = function(value) { return value.name; };
|
||||||
equal(_.map(_.uniq(list, false, iterator), iterator).join(', '), 'moe, curly, larry', 'can find the unique values of an array using a custom iterator');
|
equal(_.map(_.uniq(list, false, iterator), iterator).join(', '), 'moe, curly, larry', 'can find the unique values of an array using a custom iterator');
|
||||||
|
|
||||||
|
equal(_.map(_.uniq(list, iterator), iterator).join(', '), 'moe, curly, larry', 'can find the unique values of an array using a custom iterator without specifying whether array is sorted');
|
||||||
|
|
||||||
var iterator = function(value) { return value +1; };
|
var iterator = function(value) { return value +1; };
|
||||||
var list = [1, 2, 2, 3, 4, 4];
|
var list = [1, 2, 2, 3, 4, 4];
|
||||||
equal(_.uniq(list, true, iterator).join(', '), '1, 2, 3, 4', 'iterator works with sorted array');
|
equal(_.uniq(list, true, iterator).join(', '), '1, 2, 3, 4', 'iterator works with sorted array');
|
||||||
@@ -93,7 +101,7 @@ $(document).ready(function() {
|
|||||||
equal(result.join(', '), '1, 2, 3, 4', 'works on an arguments object');
|
equal(result.join(', '), '1, 2, 3, 4', 'works on an arguments object');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("arrays: intersection", function() {
|
test("intersection", function() {
|
||||||
var stooges = ['moe', 'curly', 'larry'], leaders = ['moe', 'groucho'];
|
var stooges = ['moe', 'curly', 'larry'], leaders = ['moe', 'groucho'];
|
||||||
equal(_.intersection(stooges, leaders).join(''), 'moe', 'can take the set intersection of two arrays');
|
equal(_.intersection(stooges, leaders).join(''), 'moe', 'can take the set intersection of two arrays');
|
||||||
equal(_(stooges).intersection(leaders).join(''), 'moe', 'can perform an OO-style intersection');
|
equal(_(stooges).intersection(leaders).join(''), 'moe', 'can perform an OO-style intersection');
|
||||||
@@ -101,7 +109,7 @@ $(document).ready(function() {
|
|||||||
equal(result.join(''), 'moe', 'works on an arguments object');
|
equal(result.join(''), 'moe', 'works on an arguments object');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("arrays: union", function() {
|
test("union", function() {
|
||||||
var result = _.union([1, 2, 3], [2, 30, 1], [1, 40]);
|
var result = _.union([1, 2, 3], [2, 30, 1], [1, 40]);
|
||||||
equal(result.join(' '), '1 2 3 30 40', 'takes the union of a list of arrays');
|
equal(result.join(' '), '1 2 3 30 40', 'takes the union of a list of arrays');
|
||||||
|
|
||||||
@@ -109,7 +117,7 @@ $(document).ready(function() {
|
|||||||
equal(result.join(' '), '1 2 3 30 40 1', 'takes the union of a list of nested arrays');
|
equal(result.join(' '), '1 2 3 30 40 1', 'takes the union of a list of nested arrays');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("arrays: difference", function() {
|
test("difference", function() {
|
||||||
var result = _.difference([1, 2, 3], [2, 30, 40]);
|
var result = _.difference([1, 2, 3], [2, 30, 40]);
|
||||||
equal(result.join(' '), '1 3', 'takes the difference of two arrays');
|
equal(result.join(' '), '1 3', 'takes the difference of two arrays');
|
||||||
|
|
||||||
@@ -117,19 +125,28 @@ $(document).ready(function() {
|
|||||||
equal(result.join(' '), '3 4', 'takes the difference of three arrays');
|
equal(result.join(' '), '3 4', 'takes the difference of three arrays');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('arrays: zip', function() {
|
test('zip', function() {
|
||||||
var names = ['moe', 'larry', 'curly'], ages = [30, 40, 50], leaders = [true];
|
var names = ['moe', 'larry', 'curly'], ages = [30, 40, 50], leaders = [true];
|
||||||
var stooges = _.zip(names, ages, leaders);
|
var stooges = _.zip(names, ages, leaders);
|
||||||
equal(String(stooges), 'moe,30,true,larry,40,,curly,50,', 'zipped together arrays of different lengths');
|
equal(String(stooges), 'moe,30,true,larry,40,,curly,50,', 'zipped together arrays of different lengths');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('arrays: zipObject', function() {
|
test('object', function() {
|
||||||
var result = _.zipObject(['moe', 'larry', 'curly'], [30, 40, 50]);
|
var result = _.object(['moe', 'larry', 'curly'], [30, 40, 50]);
|
||||||
var shouldBe = {moe: 30, larry: 40, curly: 50};
|
var shouldBe = {moe: 30, larry: 40, curly: 50};
|
||||||
ok(_.isEqual(result, shouldBe), 'two arrays zipped together into an object');
|
ok(_.isEqual(result, shouldBe), 'two arrays zipped together into an object');
|
||||||
|
|
||||||
|
result = _.object([['one', 1], ['two', 2], ['three', 3]]);
|
||||||
|
shouldBe = {one: 1, two: 2, three: 3};
|
||||||
|
ok(_.isEqual(result, shouldBe), 'an array of pairs zipped together into an object');
|
||||||
|
|
||||||
|
var stooges = {moe: 30, larry: 40, curly: 50};
|
||||||
|
ok(_.isEqual(_.object(_.pairs(stooges)), stooges), 'an object converted to pairs and back to an object');
|
||||||
|
|
||||||
|
ok(_.isEqual(_.object(null), {}), 'handles nulls');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("arrays: indexOf", function() {
|
test("indexOf", function() {
|
||||||
var numbers = [1, 2, 3];
|
var numbers = [1, 2, 3];
|
||||||
numbers.indexOf = null;
|
numbers.indexOf = null;
|
||||||
equal(_.indexOf(numbers, 2), 1, 'can compute indexOf, even without the native function');
|
equal(_.indexOf(numbers, 2), 1, 'can compute indexOf, even without the native function');
|
||||||
@@ -148,19 +165,30 @@ $(document).ready(function() {
|
|||||||
numbers = [1, 40, 40, 40, 40, 40, 40, 40, 50, 60, 70]; num = 40;
|
numbers = [1, 40, 40, 40, 40, 40, 40, 40, 50, 60, 70]; num = 40;
|
||||||
index = _.indexOf(numbers, num, true);
|
index = _.indexOf(numbers, num, true);
|
||||||
equal(index, 1, '40 is in the list');
|
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() {
|
test("lastIndexOf", function() {
|
||||||
var numbers = [1, 0, 1, 0, 0, 1, 0, 0, 0];
|
var numbers = [1, 0, 1];
|
||||||
|
equal(_.lastIndexOf(numbers, 1), 2);
|
||||||
|
|
||||||
|
numbers = [1, 0, 1, 0, 0, 1, 0, 0, 0];
|
||||||
numbers.lastIndexOf = null;
|
numbers.lastIndexOf = null;
|
||||||
equal(_.lastIndexOf(numbers, 1), 5, 'can compute lastIndexOf, even without the native function');
|
equal(_.lastIndexOf(numbers, 1), 5, 'can compute lastIndexOf, even without the native function');
|
||||||
equal(_.lastIndexOf(numbers, 0), 8, 'lastIndexOf the other element');
|
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);
|
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(result, 5, 'works on an arguments object');
|
||||||
equal(_.indexOf(null, 2), -1, 'handles nulls properly');
|
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(0).join(''), '', 'range with 0 as a first argument generates an empty array');
|
||||||
equal(_.range(4).join(' '), '0 1 2 3', 'range with a single positive argument generates an array of elements 0,1,2,...,n-1');
|
equal(_.range(4).join(' '), '0 1 2 3', 'range with a single positive argument generates an array of elements 0,1,2,...,n-1');
|
||||||
equal(_.range(5, 8).join(' '), '5 6 7', 'range with two arguments a & b, a<b generates an array of elements a,a+1,a+2,...,b-2,b-1');
|
equal(_.range(5, 8).join(' '), '5 6 7', 'range with two arguments a & b, a<b generates an array of elements a,a+1,a+2,...,b-2,b-1');
|
||||||
|
|||||||
8
vendor/underscore/test/chaining.js
vendored
8
vendor/underscore/test/chaining.js
vendored
@@ -2,7 +2,7 @@ $(document).ready(function() {
|
|||||||
|
|
||||||
module("Chaining");
|
module("Chaining");
|
||||||
|
|
||||||
test("chaining: map/flatten/reduce", function() {
|
test("map/flatten/reduce", function() {
|
||||||
var lyrics = [
|
var lyrics = [
|
||||||
"I'm a lumberjack and I'm okay",
|
"I'm a lumberjack and I'm okay",
|
||||||
"I sleep all night and I work all day",
|
"I sleep all night and I work all day",
|
||||||
@@ -20,7 +20,7 @@ $(document).ready(function() {
|
|||||||
ok(counts['a'] == 16 && counts['e'] == 10, 'counted all the letters in the song');
|
ok(counts['a'] == 16 && counts['e'] == 10, 'counted all the letters in the song');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("chaining: select/reject/sortBy", function() {
|
test("select/reject/sortBy", function() {
|
||||||
var numbers = [1,2,3,4,5,6,7,8,9,10];
|
var numbers = [1,2,3,4,5,6,7,8,9,10];
|
||||||
numbers = _(numbers).chain().select(function(n) {
|
numbers = _(numbers).chain().select(function(n) {
|
||||||
return n % 2 == 0;
|
return n % 2 == 0;
|
||||||
@@ -32,7 +32,7 @@ $(document).ready(function() {
|
|||||||
equal(numbers.join(', '), "10, 6, 2", "filtered and reversed the numbers");
|
equal(numbers.join(', '), "10, 6, 2", "filtered and reversed the numbers");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("chaining: select/reject/sortBy in functional style", function() {
|
test("select/reject/sortBy in functional style", function() {
|
||||||
var numbers = [1,2,3,4,5,6,7,8,9,10];
|
var numbers = [1,2,3,4,5,6,7,8,9,10];
|
||||||
numbers = _.chain(numbers).select(function(n) {
|
numbers = _.chain(numbers).select(function(n) {
|
||||||
return n % 2 == 0;
|
return n % 2 == 0;
|
||||||
@@ -44,7 +44,7 @@ $(document).ready(function() {
|
|||||||
equal(numbers.join(', '), "10, 6, 2", "filtered and reversed the numbers");
|
equal(numbers.join(', '), "10, 6, 2", "filtered and reversed the numbers");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("chaining: reverse/concat/unshift/pop/map", function() {
|
test("reverse/concat/unshift/pop/map", function() {
|
||||||
var numbers = [1,2,3,4,5];
|
var numbers = [1,2,3,4,5];
|
||||||
numbers = _(numbers).chain()
|
numbers = _(numbers).chain()
|
||||||
.reverse()
|
.reverse()
|
||||||
|
|||||||
194
vendor/underscore/test/collections.js
vendored
194
vendor/underscore/test/collections.js
vendored
@@ -2,7 +2,7 @@ $(document).ready(function() {
|
|||||||
|
|
||||||
module("Collections");
|
module("Collections");
|
||||||
|
|
||||||
test("collections: each", function() {
|
test("each", function() {
|
||||||
_.each([1, 2, 3], function(num, i) {
|
_.each([1, 2, 3], function(num, i) {
|
||||||
equal(num, i + 1, 'each iterators provide value and iteration count');
|
equal(num, i + 1, 'each iterators provide value and iteration count');
|
||||||
});
|
});
|
||||||
@@ -31,7 +31,7 @@ $(document).ready(function() {
|
|||||||
equal(answers, 0, 'handles a null properly');
|
equal(answers, 0, 'handles a null properly');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('collections: map', function() {
|
test('map', function() {
|
||||||
var doubled = _.map([1, 2, 3], function(num){ return num * 2; });
|
var doubled = _.map([1, 2, 3], function(num){ return num * 2; });
|
||||||
equal(doubled.join(', '), '2, 4, 6', 'doubled numbers');
|
equal(doubled.join(', '), '2, 4, 6', 'doubled numbers');
|
||||||
|
|
||||||
@@ -44,8 +44,13 @@ $(document).ready(function() {
|
|||||||
var doubled = _([1, 2, 3]).map(function(num){ return num * 2; });
|
var doubled = _([1, 2, 3]).map(function(num){ return num * 2; });
|
||||||
equal(doubled.join(', '), '2, 4, 6', 'OO-style doubled numbers');
|
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; });
|
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; });
|
var ids = _.map(document.images, function(n){ return n.id; });
|
||||||
ok(ids[0] == 'chart_image', 'can use collection methods on HTMLCollections');
|
ok(ids[0] == 'chart_image', 'can use collection methods on HTMLCollections');
|
||||||
@@ -54,7 +59,7 @@ $(document).ready(function() {
|
|||||||
ok(_.isArray(ifnull) && ifnull.length === 0, 'handles a null properly');
|
ok(_.isArray(ifnull) && ifnull.length === 0, 'handles a null properly');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('collections: reduce', function() {
|
test('reduce', function() {
|
||||||
var sum = _.reduce([1, 2, 3], function(sum, num){ return sum + num; }, 0);
|
var sum = _.reduce([1, 2, 3], function(sum, num){ return sum + num; }, 0);
|
||||||
equal(sum, 6, 'can sum up an array');
|
equal(sum, 6, 'can sum up an array');
|
||||||
|
|
||||||
@@ -84,7 +89,7 @@ $(document).ready(function() {
|
|||||||
raises(function() { _.reduce([], function(){}); }, TypeError, 'throws an error for empty arrays with no initial value');
|
raises(function() { _.reduce([], function(){}); }, TypeError, 'throws an error for empty arrays with no initial value');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('collections: reduceRight', function() {
|
test('reduceRight', function() {
|
||||||
var list = _.reduceRight(["foo", "bar", "baz"], function(memo, str){ return memo + str; }, '');
|
var list = _.reduceRight(["foo", "bar", "baz"], function(memo, str){ return memo + str; }, '');
|
||||||
equal(list, 'bazbarfoo', 'can perform right folds');
|
equal(list, 'bazbarfoo', 'can perform right folds');
|
||||||
|
|
||||||
@@ -102,24 +107,60 @@ $(document).ready(function() {
|
|||||||
}
|
}
|
||||||
ok(ifnull instanceof TypeError, 'handles a null (without inital value) properly');
|
ok(ifnull instanceof TypeError, 'handles a null (without inital 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');
|
||||||
|
|
||||||
ok(_.reduceRight(null, function(){}, 138) === 138, 'handles a null (with initial value) properly');
|
ok(_.reduceRight(null, function(){}, 138) === 138, 'handles a null (with initial value) properly');
|
||||||
|
|
||||||
equal(_.reduceRight([], function(){}, undefined), undefined, 'undefined can be passed as a special case');
|
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');
|
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];
|
var array = [1, 2, 3, 4];
|
||||||
strictEqual(_.find(array, function(n) { return n > 2; }), 3, 'should return first found `value`');
|
strictEqual(_.find(array, function(n) { return n > 2; }), 3, 'should return first found `value`');
|
||||||
strictEqual(_.find(array, function() { return false; }), void 0, 'should return `undefined` if `value` is not found');
|
strictEqual(_.find(array, function() { return false; }), void 0, 'should return `undefined` if `value` is not found');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('collections: detect', function() {
|
test('detect', function() {
|
||||||
var result = _.detect([1, 2, 3], function(num){ return num * 2 == 4; });
|
var result = _.detect([1, 2, 3], function(num){ return num * 2 == 4; });
|
||||||
equal(result, 2, 'found the first "2" and broke the loop');
|
equal(result, 2, 'found the first "2" and broke the loop');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('collections: select', function() {
|
test('select', function() {
|
||||||
var evens = _.select([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
|
var evens = _.select([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
|
||||||
equal(evens.join(', '), '2, 4, 6', 'selected each even number');
|
equal(evens.join(', '), '2, 4, 6', 'selected each even number');
|
||||||
|
|
||||||
@@ -127,12 +168,20 @@ $(document).ready(function() {
|
|||||||
equal(evens.join(', '), '2, 4, 6', 'aliased as "filter"');
|
equal(evens.join(', '), '2, 4, 6', 'aliased as "filter"');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('collections: reject', function() {
|
test('reject', function() {
|
||||||
var odds = _.reject([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
|
var odds = _.reject([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
|
||||||
equal(odds.join(', '), '1, 3, 5', 'rejected each even number');
|
equal(odds.join(', '), '1, 3, 5', 'rejected each even number');
|
||||||
|
|
||||||
|
var context = "obj";
|
||||||
|
|
||||||
|
var evens = _.reject([1, 2, 3, 4, 5, 6], function(num){
|
||||||
|
equal(context, "obj");
|
||||||
|
return num % 2 != 0;
|
||||||
|
}, context);
|
||||||
|
equal(evens.join(', '), '2, 4, 6', 'rejected each odd number');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('collections: all', function() {
|
test('all', function() {
|
||||||
ok(_.all([], _.identity), 'the empty set');
|
ok(_.all([], _.identity), 'the empty set');
|
||||||
ok(_.all([true, true, true], _.identity), 'all true values');
|
ok(_.all([true, true, true], _.identity), 'all true values');
|
||||||
ok(!_.all([true, false, true], _.identity), 'one false value');
|
ok(!_.all([true, false, true], _.identity), 'one false value');
|
||||||
@@ -141,9 +190,10 @@ $(document).ready(function() {
|
|||||||
ok(_.all([1], _.identity) === true, 'cast to boolean - true');
|
ok(_.all([1], _.identity) === true, 'cast to boolean - true');
|
||||||
ok(_.all([0], _.identity) === false, 'cast to boolean - false');
|
ok(_.all([0], _.identity) === false, 'cast to boolean - false');
|
||||||
ok(_.every([true, true, true], _.identity), 'aliased as "every"');
|
ok(_.every([true, true, true], _.identity), 'aliased as "every"');
|
||||||
|
ok(!_.all([undefined, undefined, undefined], _.identity), 'works with arrays of undefined');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('collections: any', function() {
|
test('any', function() {
|
||||||
var nativeSome = Array.prototype.some;
|
var nativeSome = Array.prototype.some;
|
||||||
Array.prototype.some = null;
|
Array.prototype.some = null;
|
||||||
ok(!_.any([]), 'the empty set');
|
ok(!_.any([]), 'the empty set');
|
||||||
@@ -159,21 +209,21 @@ $(document).ready(function() {
|
|||||||
Array.prototype.some = nativeSome;
|
Array.prototype.some = nativeSome;
|
||||||
});
|
});
|
||||||
|
|
||||||
test('collections: include', function() {
|
test('include', function() {
|
||||||
ok(_.include([1,2,3], 2), 'two is in the array');
|
ok(_.include([1,2,3], 2), 'two is in the array');
|
||||||
ok(!_.include([1,3,9], 2), 'two is not in the array');
|
ok(!_.include([1,3,9], 2), 'two is not in the array');
|
||||||
ok(_.contains({moe:1, larry:3, curly:9}, 3) === true, '_.include on objects checks their values');
|
ok(_.contains({moe:1, larry:3, curly:9}, 3) === true, '_.include on objects checks their values');
|
||||||
ok(_([1,2,3]).include(2), 'OO-style include');
|
ok(_([1,2,3]).include(2), 'OO-style include');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('collections: invoke', function() {
|
test('invoke', function() {
|
||||||
var list = [[5, 1, 7], [3, 2, 1]];
|
var list = [[5, 1, 7], [3, 2, 1]];
|
||||||
var result = _.invoke(list, 'sort');
|
var result = _.invoke(list, 'sort');
|
||||||
equal(result[0].join(', '), '1, 5, 7', 'first array sorted');
|
equal(result[0].join(', '), '1, 5, 7', 'first array sorted');
|
||||||
equal(result[1].join(', '), '1, 2, 3', 'second array sorted');
|
equal(result[1].join(', '), '1, 2, 3', 'second array sorted');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('collections: invoke w/ function reference', function() {
|
test('invoke w/ function reference', function() {
|
||||||
var list = [[5, 1, 7], [3, 2, 1]];
|
var list = [[5, 1, 7], [3, 2, 1]];
|
||||||
var result = _.invoke(list, Array.prototype.sort);
|
var result = _.invoke(list, Array.prototype.sort);
|
||||||
equal(result[0].join(', '), '1, 5, 7', 'first array sorted');
|
equal(result[0].join(', '), '1, 5, 7', 'first array sorted');
|
||||||
@@ -181,7 +231,7 @@ $(document).ready(function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Relevant when using ClojureScript
|
// Relevant when using ClojureScript
|
||||||
test('collections: invoke when strings have a call method', function() {
|
test('invoke when strings have a call method', function() {
|
||||||
String.prototype.call = function() {
|
String.prototype.call = function() {
|
||||||
return 42;
|
return 42;
|
||||||
};
|
};
|
||||||
@@ -195,12 +245,22 @@ $(document).ready(function() {
|
|||||||
equal(s.call, undefined, "call function removed");
|
equal(s.call, undefined, "call function removed");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('collections: pluck', function() {
|
test('pluck', function() {
|
||||||
var people = [{name : 'moe', age : 30}, {name : 'curly', age : 50}];
|
var people = [{name : 'moe', age : 30}, {name : 'curly', age : 50}];
|
||||||
equal(_.pluck(people, 'name').join(', '), 'moe, curly', 'pulls names out of objects');
|
equal(_.pluck(people, 'name').join(', '), 'moe, curly', 'pulls names out of objects');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('collections: max', function() {
|
test('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');
|
equal(3, _.max([1, 2, 3]), 'can perform a regular Math.max');
|
||||||
|
|
||||||
var neg = _.max([1, 2, 3], function(num){ return -num; });
|
var neg = _.max([1, 2, 3], function(num){ return -num; });
|
||||||
@@ -212,7 +272,7 @@ $(document).ready(function() {
|
|||||||
equal(299999, _.max(_.range(1,300000)), "Maximum value of a too-big array");
|
equal(299999, _.max(_.range(1,300000)), "Maximum value of a too-big array");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('collections: min', function() {
|
test('min', function() {
|
||||||
equal(1, _.min([1, 2, 3]), 'can perform a regular Math.min');
|
equal(1, _.min([1, 2, 3]), 'can perform a regular Math.min');
|
||||||
|
|
||||||
var neg = _.min([1, 2, 3], function(num){ return -num; });
|
var neg = _.min([1, 2, 3], function(num){ return -num; });
|
||||||
@@ -228,7 +288,7 @@ $(document).ready(function() {
|
|||||||
equal(1, _.min(_.range(1,300000)), "Minimum value of a too-big array");
|
equal(1, _.min(_.range(1,300000)), "Minimum value of a too-big array");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('collections: sortBy', function() {
|
test('sortBy', function() {
|
||||||
var people = [{name : 'curly', age : 50}, {name : 'moe', age : 30}];
|
var people = [{name : 'curly', age : 50}, {name : 'moe', age : 30}];
|
||||||
people = _.sortBy(people, function(person){ return person.age; });
|
people = _.sortBy(people, function(person){ return person.age; });
|
||||||
equal(_.pluck(people, 'name').join(', '), 'moe, curly', 'stooges sorted by age');
|
equal(_.pluck(people, 'name').join(', '), 'moe, curly', 'stooges sorted by age');
|
||||||
@@ -239,9 +299,32 @@ $(document).ready(function() {
|
|||||||
var list = ["one", "two", "three", "four", "five"];
|
var list = ["one", "two", "three", "four", "five"];
|
||||||
var sorted = _.sortBy(list, 'length');
|
var sorted = _.sortBy(list, 'length');
|
||||||
equal(sorted.join(' '), 'one two four five three', 'sorted by 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; });
|
var parity = _.groupBy([1, 2, 3, 4, 5, 6], function(num){ return num % 2; });
|
||||||
ok('0' in parity && '1' in parity, 'created a group for each value');
|
ok('0' in parity && '1' in parity, 'created a group for each value');
|
||||||
equal(parity[0].join(', '), '2, 4, 6', 'put each even number in the right group');
|
equal(parity[0].join(', '), '2, 4, 6', 'put each even number in the right group');
|
||||||
@@ -251,25 +334,70 @@ $(document).ready(function() {
|
|||||||
equal(grouped['3'].join(' '), 'one two six ten');
|
equal(grouped['3'].join(' '), 'one two six ten');
|
||||||
equal(grouped['4'].join(' '), 'four five nine');
|
equal(grouped['4'].join(' '), 'four five nine');
|
||||||
equal(grouped['5'].join(' '), 'three seven eight');
|
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 numbers = [10, 20, 30, 40, 50], num = 35;
|
||||||
var indexForNum = _.sortedIndex(numbers, num);
|
var indexForNum = _.sortedIndex(numbers, num);
|
||||||
equal(indexForNum, 3, '35 should be inserted at index 3');
|
equal(indexForNum, 3, '35 should be inserted at index 3');
|
||||||
|
|
||||||
var indexFor30 = _.sortedIndex(numbers, 30);
|
var indexFor30 = _.sortedIndex(numbers, 30);
|
||||||
equal(indexFor30, 2, '30 should be inserted at index 2');
|
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 numbers = _.range(10);
|
||||||
var shuffled = _.shuffle(numbers).sort();
|
var shuffled = _.shuffle(numbers).sort();
|
||||||
notStrictEqual(numbers, shuffled, 'original object is unmodified');
|
notStrictEqual(numbers, shuffled, 'original object is unmodified');
|
||||||
equal(shuffled.join(','), numbers.join(','), 'contains the same members before and after shuffle');
|
equal(shuffled.join(','), numbers.join(','), 'contains the same members before and after shuffle');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('collections: toArray', function() {
|
test('toArray', function() {
|
||||||
ok(!_.isArray(arguments), 'arguments object is not an array');
|
ok(!_.isArray(arguments), 'arguments object is not an array');
|
||||||
ok(_.isArray(_.toArray(arguments)), 'arguments object converted into array');
|
ok(_.isArray(_.toArray(arguments)), 'arguments object converted into array');
|
||||||
var a = [1,2,3];
|
var a = [1,2,3];
|
||||||
@@ -278,19 +406,21 @@ $(document).ready(function() {
|
|||||||
|
|
||||||
var numbers = _.toArray({one : 1, two : 2, three : 3});
|
var numbers = _.toArray({one : 1, two : 2, three : 3});
|
||||||
equal(numbers.join(', '), '1, 2, 3', 'object flattened into array');
|
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({one : 1, two : 2, three : 3}), 3, 'can compute the size of an object');
|
||||||
equal(_.size([1, 2, 3]), 3, 'can compute the size of an array');
|
equal(_.size([1, 2, 3]), 3, 'can compute the size of an array');
|
||||||
|
|
||||||
|
var func = function() {
|
||||||
|
return _.size(arguments);
|
||||||
|
};
|
||||||
|
|
||||||
|
equal(func(1, 2, 3, 4), 4, 'can test the size of the arguments object');
|
||||||
|
|
||||||
|
equal(_.size('hello'), 5, 'can compute the size of a string');
|
||||||
|
|
||||||
|
equal(_.size(null), 0, 'handles nulls');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
69
vendor/underscore/test/functions.js
vendored
69
vendor/underscore/test/functions.js
vendored
@@ -2,7 +2,7 @@ $(document).ready(function() {
|
|||||||
|
|
||||||
module("Functions");
|
module("Functions");
|
||||||
|
|
||||||
test("functions: bind", function() {
|
test("bind", function() {
|
||||||
var context = {name : 'moe'};
|
var context = {name : 'moe'};
|
||||||
var func = function(arg) { return "name: " + (this.name || arg); };
|
var func = function(arg) { return "name: " + (this.name || arg); };
|
||||||
var bound = _.bind(func, context);
|
var bound = _.bind(func, context);
|
||||||
@@ -38,7 +38,7 @@ $(document).ready(function() {
|
|||||||
equal(Boundf().hello, "moe curly", "When called without the new operator, it's OK to be bound to the context");
|
equal(Boundf().hello, "moe curly", "When called without the new operator, it's OK to be bound to the context");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("functions: bindAll", function() {
|
test("bindAll", function() {
|
||||||
var curly = {name : 'curly'}, moe = {
|
var curly = {name : 'curly'}, moe = {
|
||||||
name : 'moe',
|
name : 'moe',
|
||||||
getName : function() { return 'name: ' + this.name; },
|
getName : function() { return 'name: ' + this.name; },
|
||||||
@@ -61,7 +61,7 @@ $(document).ready(function() {
|
|||||||
equal(curly.sayHi(), 'hi: moe', 'calling bindAll with no arguments binds all functions to the object');
|
equal(curly.sayHi(), 'hi: moe', 'calling bindAll with no arguments binds all functions to the object');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("functions: memoize", function() {
|
test("memoize", function() {
|
||||||
var fib = function(n) {
|
var fib = function(n) {
|
||||||
return n < 2 ? n : fib(n - 1) + fib(n - 2);
|
return n < 2 ? n : fib(n - 1) + fib(n - 2);
|
||||||
};
|
};
|
||||||
@@ -77,20 +77,20 @@ $(document).ready(function() {
|
|||||||
equal(fastO('toString'), 'toString', 'checks hasOwnProperty');
|
equal(fastO('toString'), 'toString', 'checks hasOwnProperty');
|
||||||
});
|
});
|
||||||
|
|
||||||
asyncTest("functions: delay", 2, function() {
|
asyncTest("delay", 2, function() {
|
||||||
var delayed = false;
|
var delayed = false;
|
||||||
_.delay(function(){ delayed = true; }, 100);
|
_.delay(function(){ delayed = true; }, 100);
|
||||||
setTimeout(function(){ ok(!delayed, "didn't delay the function quite yet"); }, 50);
|
setTimeout(function(){ ok(!delayed, "didn't delay the function quite yet"); }, 50);
|
||||||
setTimeout(function(){ ok(delayed, 'delayed the function'); start(); }, 150);
|
setTimeout(function(){ ok(delayed, 'delayed the function'); start(); }, 150);
|
||||||
});
|
});
|
||||||
|
|
||||||
asyncTest("functions: defer", 1, function() {
|
asyncTest("defer", 1, function() {
|
||||||
var deferred = false;
|
var deferred = false;
|
||||||
_.defer(function(bool){ deferred = bool; }, true);
|
_.defer(function(bool){ deferred = bool; }, true);
|
||||||
_.delay(function(){ ok(deferred, "deferred the function"); start(); }, 50);
|
_.delay(function(){ ok(deferred, "deferred the function"); start(); }, 50);
|
||||||
});
|
});
|
||||||
|
|
||||||
asyncTest("functions: throttle", 2, function() {
|
asyncTest("throttle", 2, function() {
|
||||||
var counter = 0;
|
var counter = 0;
|
||||||
var incr = function(){ counter++; };
|
var incr = function(){ counter++; };
|
||||||
var throttledIncr = _.throttle(incr, 100);
|
var throttledIncr = _.throttle(incr, 100);
|
||||||
@@ -105,7 +105,7 @@ $(document).ready(function() {
|
|||||||
_.delay(function(){ equal(counter, 4, "incr was throttled"); start(); }, 400);
|
_.delay(function(){ equal(counter, 4, "incr was throttled"); start(); }, 400);
|
||||||
});
|
});
|
||||||
|
|
||||||
asyncTest("functions: throttle arguments", 2, function() {
|
asyncTest("throttle arguments", 2, function() {
|
||||||
var value = 0;
|
var value = 0;
|
||||||
var update = function(val){ value = val; };
|
var update = function(val){ value = val; };
|
||||||
var throttledUpdate = _.throttle(update, 100);
|
var throttledUpdate = _.throttle(update, 100);
|
||||||
@@ -117,7 +117,7 @@ $(document).ready(function() {
|
|||||||
_.delay(function(){ equal(value, 6, "updated to latest value"); start(); }, 400);
|
_.delay(function(){ equal(value, 6, "updated to latest value"); start(); }, 400);
|
||||||
});
|
});
|
||||||
|
|
||||||
asyncTest("functions: throttle once", 2, function() {
|
asyncTest("throttle once", 2, function() {
|
||||||
var counter = 0;
|
var counter = 0;
|
||||||
var incr = function(){ return ++counter; };
|
var incr = function(){ return ++counter; };
|
||||||
var throttledIncr = _.throttle(incr, 100);
|
var throttledIncr = _.throttle(incr, 100);
|
||||||
@@ -128,7 +128,7 @@ $(document).ready(function() {
|
|||||||
}, 220);
|
}, 220);
|
||||||
});
|
});
|
||||||
|
|
||||||
asyncTest("functions: throttle twice", 1, function() {
|
asyncTest("throttle twice", 1, function() {
|
||||||
var counter = 0;
|
var counter = 0;
|
||||||
var incr = function(){ counter++; };
|
var incr = function(){ counter++; };
|
||||||
var throttledIncr = _.throttle(incr, 100);
|
var throttledIncr = _.throttle(incr, 100);
|
||||||
@@ -136,7 +136,34 @@ $(document).ready(function() {
|
|||||||
_.delay(function(){ equal(counter, 2, "incr was called twice"); start(); }, 220);
|
_.delay(function(){ equal(counter, 2, "incr was called twice"); start(); }, 220);
|
||||||
});
|
});
|
||||||
|
|
||||||
asyncTest("functions: debounce", 1, function() {
|
asyncTest("throttle repeatedly with results", 9, function() {
|
||||||
|
var counter = 0;
|
||||||
|
var incr = function(){ return ++counter; };
|
||||||
|
var throttledIncr = _.throttle(incr, 100);
|
||||||
|
var results = [];
|
||||||
|
var saveResult = function() { results.push(throttledIncr()); };
|
||||||
|
saveResult(); saveResult(); saveResult();
|
||||||
|
setTimeout(saveResult, 70);
|
||||||
|
setTimeout(saveResult, 120);
|
||||||
|
setTimeout(saveResult, 140);
|
||||||
|
setTimeout(saveResult, 190);
|
||||||
|
setTimeout(saveResult, 240);
|
||||||
|
setTimeout(saveResult, 260);
|
||||||
|
_.delay(function() {
|
||||||
|
equal(results[0], 1, "incr was called once");
|
||||||
|
equal(results[1], 1, "incr was throttled");
|
||||||
|
equal(results[2], 1, "incr was throttled");
|
||||||
|
equal(results[3], 1, "incr was throttled");
|
||||||
|
equal(results[4], 2, "incr was called twice");
|
||||||
|
equal(results[5], 2, "incr was throttled");
|
||||||
|
equal(results[6], 2, "incr was throttled");
|
||||||
|
equal(results[7], 3, "incr was called thrice");
|
||||||
|
equal(results[8], 3, "incr was throttled");
|
||||||
|
start();
|
||||||
|
}, 400);
|
||||||
|
});
|
||||||
|
|
||||||
|
asyncTest("debounce", 1, function() {
|
||||||
var counter = 0;
|
var counter = 0;
|
||||||
var incr = function(){ counter++; };
|
var incr = function(){ counter++; };
|
||||||
var debouncedIncr = _.debounce(incr, 50);
|
var debouncedIncr = _.debounce(incr, 50);
|
||||||
@@ -149,11 +176,17 @@ $(document).ready(function() {
|
|||||||
_.delay(function(){ equal(counter, 1, "incr was debounced"); start(); }, 220);
|
_.delay(function(){ equal(counter, 1, "incr was debounced"); start(); }, 220);
|
||||||
});
|
});
|
||||||
|
|
||||||
asyncTest("functions: debounce asap", 2, function() {
|
asyncTest("debounce asap", 5, function() {
|
||||||
|
var a, b, c;
|
||||||
var counter = 0;
|
var counter = 0;
|
||||||
var incr = function(){ counter++; };
|
var incr = function(){ return ++counter; };
|
||||||
var debouncedIncr = _.debounce(incr, 50, true);
|
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');
|
equal(counter, 1, 'incr was called immediately');
|
||||||
setTimeout(debouncedIncr, 30);
|
setTimeout(debouncedIncr, 30);
|
||||||
setTimeout(debouncedIncr, 60);
|
setTimeout(debouncedIncr, 60);
|
||||||
@@ -163,7 +196,7 @@ $(document).ready(function() {
|
|||||||
_.delay(function(){ equal(counter, 1, "incr was debounced"); start(); }, 220);
|
_.delay(function(){ equal(counter, 1, "incr was debounced"); start(); }, 220);
|
||||||
});
|
});
|
||||||
|
|
||||||
asyncTest("functions: debounce asap recursively", 2, function() {
|
asyncTest("debounce asap recursively", 2, function() {
|
||||||
var counter = 0;
|
var counter = 0;
|
||||||
var debouncedIncr = _.debounce(function(){
|
var debouncedIncr = _.debounce(function(){
|
||||||
counter++;
|
counter++;
|
||||||
@@ -174,7 +207,7 @@ $(document).ready(function() {
|
|||||||
_.delay(function(){ equal(counter, 1, "incr was debounced"); start(); }, 70);
|
_.delay(function(){ equal(counter, 1, "incr was debounced"); start(); }, 70);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("functions: once", function() {
|
test("once", function() {
|
||||||
var num = 0;
|
var num = 0;
|
||||||
var increment = _.once(function(){ num++; });
|
var increment = _.once(function(){ num++; });
|
||||||
increment();
|
increment();
|
||||||
@@ -182,7 +215,7 @@ $(document).ready(function() {
|
|||||||
equal(num, 1);
|
equal(num, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("functions: wrap", function() {
|
test("wrap", function() {
|
||||||
var greet = function(name){ return "hi: " + name; };
|
var greet = function(name){ return "hi: " + name; };
|
||||||
var backwards = _.wrap(greet, function(func, name){ return func(name) + ' ' + name.split('').reverse().join(''); });
|
var backwards = _.wrap(greet, function(func, name){ return func(name) + ' ' + name.split('').reverse().join(''); });
|
||||||
equal(backwards('moe'), 'hi: moe eom', 'wrapped the saluation function');
|
equal(backwards('moe'), 'hi: moe eom', 'wrapped the saluation function');
|
||||||
@@ -198,7 +231,7 @@ $(document).ready(function() {
|
|||||||
deepEqual(ret, [noop, ['whats', 'your'], 'vector', 'victor']);
|
deepEqual(ret, [noop, ['whats', 'your'], 'vector', 'victor']);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("functions: compose", function() {
|
test("compose", function() {
|
||||||
var greet = function(name){ return "hi: " + name; };
|
var greet = function(name){ return "hi: " + name; };
|
||||||
var exclaim = function(sentence){ return sentence + '!'; };
|
var exclaim = function(sentence){ return sentence + '!'; };
|
||||||
var composed = _.compose(exclaim, greet);
|
var composed = _.compose(exclaim, greet);
|
||||||
@@ -208,7 +241,7 @@ $(document).ready(function() {
|
|||||||
equal(composed('moe'), 'hi: moe!', 'in this case, the functions are also commutative');
|
equal(composed('moe'), 'hi: moe!', 'in this case, the functions are also commutative');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("functions: after", function() {
|
test("after", function() {
|
||||||
var testAfter = function(afterAmount, timesCalled) {
|
var testAfter = function(afterAmount, timesCalled) {
|
||||||
var afterCalled = 0;
|
var afterCalled = 0;
|
||||||
var after = _.after(afterAmount, function() {
|
var after = _.after(afterAmount, function() {
|
||||||
|
|||||||
164
vendor/underscore/test/objects.js
vendored
164
vendor/underscore/test/objects.js
vendored
@@ -2,7 +2,7 @@ $(document).ready(function() {
|
|||||||
|
|
||||||
module("Objects");
|
module("Objects");
|
||||||
|
|
||||||
test("objects: keys", function() {
|
test("keys", function() {
|
||||||
equal(_.keys({one : 1, two : 2}).join(', '), 'one, two', 'can extract the keys from an object');
|
equal(_.keys({one : 1, two : 2}).join(', '), 'one, two', 'can extract the keys from an object');
|
||||||
// the test above is not safe because it relies on for-in enumeration order
|
// the test above is not safe because it relies on for-in enumeration order
|
||||||
var a = []; a[1] = 0;
|
var a = []; a[1] = 0;
|
||||||
@@ -14,11 +14,26 @@ $(document).ready(function() {
|
|||||||
raises(function() { _.keys(true); }, TypeError, 'throws an error for boolean primitives');
|
raises(function() { _.keys(true); }, TypeError, 'throws an error for boolean primitives');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("objects: values", function() {
|
test("values", function() {
|
||||||
equal(_.values({one : 1, two : 2}).join(', '), '1, 2', 'can extract the values from an object');
|
equal(_.values({one: 1, two: 2}).join(', '), '1, 2', 'can extract the values from an object');
|
||||||
|
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};
|
var obj = {a : 'dash', b : _.map, c : (/yo/), d : _.reduce};
|
||||||
ok(_.isEqual(['b', 'd'], _.functions(obj)), 'can grab the function names of any passed-in object');
|
ok(_.isEqual(['b', 'd'], _.functions(obj)), 'can grab the function names of any passed-in object');
|
||||||
|
|
||||||
@@ -27,7 +42,7 @@ $(document).ready(function() {
|
|||||||
equal(_.functions(new Animal).join(''), 'run', 'also looks up functions on the prototype');
|
equal(_.functions(new Animal).join(''), 'run', 'also looks up functions on the prototype');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("objects: extend", function() {
|
test("extend", function() {
|
||||||
var result;
|
var result;
|
||||||
equal(_.extend({}, {a:'b'}).a, 'b', 'can extend an object with the attributes of another');
|
equal(_.extend({}, {a:'b'}).a, 'b', 'can extend an object with the attributes of another');
|
||||||
equal(_.extend({a:'x'}, {a:'b'}).a, 'b', 'properties in source override destination');
|
equal(_.extend({a:'x'}, {a:'b'}).a, 'b', 'properties in source override destination');
|
||||||
@@ -40,7 +55,7 @@ $(document).ready(function() {
|
|||||||
equal(_.keys(result).join(''), 'ab', 'extend does not copy undefined values');
|
equal(_.keys(result).join(''), 'ab', 'extend does not copy undefined values');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("objects: pick", function() {
|
test("pick", function() {
|
||||||
var result;
|
var result;
|
||||||
result = _.pick({a:1, b:2, c:3}, 'a', 'c');
|
result = _.pick({a:1, b:2, c:3}, 'a', 'c');
|
||||||
ok(_.isEqual(result, {a:1, c:3}), 'can restrict properties to those named');
|
ok(_.isEqual(result, {a:1, c:3}), 'can restrict properties to those named');
|
||||||
@@ -48,9 +63,27 @@ $(document).ready(function() {
|
|||||||
ok(_.isEqual(result, {b:2, c:3}), 'can restrict properties to those named in an array');
|
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');
|
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');
|
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 result;
|
||||||
var options = {zero: 0, one: 1, empty: "", nan: NaN, string: "string"};
|
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');
|
equal(options.word, "word", 'new value is added, first one wins');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("objects: clone", function() {
|
test("clone", function() {
|
||||||
var moe = {name : 'moe', lucky : [13, 27, 34]};
|
var moe = {name : 'moe', lucky : [13, 27, 34]};
|
||||||
var clone = _.clone(moe);
|
var clone = _.clone(moe);
|
||||||
equal(clone.name, 'moe', 'the clone as the attributes of the original');
|
equal(clone.name, 'moe', 'the clone as the attributes of the original');
|
||||||
@@ -81,7 +114,7 @@ $(document).ready(function() {
|
|||||||
equal(_.clone(null), null, 'non objects should not be changed by clone');
|
equal(_.clone(null), null, 'non objects should not be changed by clone');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("objects: isEqual", function() {
|
test("isEqual", function() {
|
||||||
function First() {
|
function First() {
|
||||||
this.value = 1;
|
this.value = 1;
|
||||||
}
|
}
|
||||||
@@ -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(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");
|
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.
|
// 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", b: 1, c: true}, {a: "Curly", b: 1, c: true}), "Objects containing identical primitives are equal");
|
||||||
ok(_.isEqual({a: /Curly/g, b: new Date(2009, 11, 13)}, {a: /Curly/g, b: new Date(2009, 11, 13)}), "Objects containing equivalent members are equal");
|
ok(_.isEqual({a: /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");
|
b.push("Curly");
|
||||||
ok(!_.isEqual(a, b), "Arrays containing circular references and different properties are not equal");
|
ok(!_.isEqual(a, b), "Arrays containing circular references and different properties are not equal");
|
||||||
|
|
||||||
|
// More circular arrays #767.
|
||||||
|
a = ["everything is checked but", "this", "is not"];
|
||||||
|
a[1] = a;
|
||||||
|
b = ["everything is checked but", ["this", "array"], "is not"];
|
||||||
|
ok(!_.isEqual(a, b), "Comparison of circular references with non-circular references are not equal");
|
||||||
|
|
||||||
// Circular Objects.
|
// Circular Objects.
|
||||||
a = {abc: null};
|
a = {abc: null};
|
||||||
b = {abc: null};
|
b = {abc: null};
|
||||||
@@ -278,6 +309,12 @@ $(document).ready(function() {
|
|||||||
b.def = new Number(63);
|
b.def = new Number(63);
|
||||||
ok(!_.isEqual(a, b), "Objects containing circular references and different properties are not equal");
|
ok(!_.isEqual(a, b), "Objects containing circular references and different properties are not equal");
|
||||||
|
|
||||||
|
// More circular objects #767.
|
||||||
|
a = {everything: "is checked", but: "this", is: "not"};
|
||||||
|
a.but = a;
|
||||||
|
b = {everything: "is checked", but: {that:"object"}, is: "not"};
|
||||||
|
ok(!_.isEqual(a, b), "Comparison of circular references with non-circular object references are not equal");
|
||||||
|
|
||||||
// Cyclic Structures.
|
// Cyclic Structures.
|
||||||
a = [{abc: null}];
|
a = [{abc: null}];
|
||||||
b = [{abc: null}];
|
b = [{abc: null}];
|
||||||
@@ -311,60 +348,11 @@ $(document).ready(function() {
|
|||||||
ok(!_.isEqual(isEqualObj, {}), 'Objects that do not implement equivalent `isEqual` methods are not equal');
|
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');
|
ok(!_.isEqual({}, isEqualObj), 'Commutative equality is implemented for objects with different `isEqual` methods');
|
||||||
|
|
||||||
// Custom `isEqual` methods - comparing different types
|
// Objects from another frame.
|
||||||
LocalizedString = (function() {
|
ok(_.isEqual({}, iObject));
|
||||||
function LocalizedString(id) { this.id = id; this.string = (this.id===10)? 'Bonjour': ''; }
|
|
||||||
LocalizedString.prototype.isEqual = function(that) {
|
|
||||||
if (_.isString(that)) return this.string == that;
|
|
||||||
else if (that instanceof LocalizedString) return this.id == that.id;
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
return LocalizedString;
|
|
||||||
})();
|
|
||||||
var localized_string1 = new LocalizedString(10), localized_string2 = new LocalizedString(10), localized_string3 = new LocalizedString(11);
|
|
||||||
ok(_.isEqual(localized_string1, localized_string2), 'comparing same typed instances with same ids');
|
|
||||||
ok(!_.isEqual(localized_string1, localized_string3), 'comparing same typed instances with different ids');
|
|
||||||
ok(_.isEqual(localized_string1, 'Bonjour'), 'comparing different typed instances with same values');
|
|
||||||
ok(_.isEqual('Bonjour', localized_string1), 'comparing different typed instances with same values');
|
|
||||||
ok(!_.isEqual('Bonjour', localized_string3), 'comparing two localized strings with different ids');
|
|
||||||
ok(!_.isEqual(localized_string1, 'Au revoir'), 'comparing different typed instances with different values');
|
|
||||||
ok(!_.isEqual('Au revoir', localized_string1), 'comparing different typed instances with different values');
|
|
||||||
|
|
||||||
// Custom `isEqual` methods - comparing with serialized data
|
|
||||||
Date.prototype.toJSON = function() {
|
|
||||||
return {
|
|
||||||
_type:'Date',
|
|
||||||
year:this.getUTCFullYear(),
|
|
||||||
month:this.getUTCMonth(),
|
|
||||||
day:this.getUTCDate(),
|
|
||||||
hours:this.getUTCHours(),
|
|
||||||
minutes:this.getUTCMinutes(),
|
|
||||||
seconds:this.getUTCSeconds()
|
|
||||||
};
|
|
||||||
};
|
|
||||||
Date.prototype.isEqual = function(that) {
|
|
||||||
var this_date_components = this.toJSON();
|
|
||||||
var that_date_components = (that instanceof Date) ? that.toJSON() : that;
|
|
||||||
delete this_date_components['_type']; delete that_date_components['_type'];
|
|
||||||
return _.isEqual(this_date_components, that_date_components);
|
|
||||||
};
|
|
||||||
|
|
||||||
var date = new Date();
|
|
||||||
var date_json = {
|
|
||||||
_type:'Date',
|
|
||||||
year:date.getUTCFullYear(),
|
|
||||||
month:date.getUTCMonth(),
|
|
||||||
day:date.getUTCDate(),
|
|
||||||
hours:date.getUTCHours(),
|
|
||||||
minutes:date.getUTCMinutes(),
|
|
||||||
seconds:date.getUTCSeconds()
|
|
||||||
};
|
|
||||||
|
|
||||||
ok(_.isEqual(date_json, date), 'serialized date matches date');
|
|
||||||
ok(_.isEqual(date, date_json), 'date matches serialized date');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("objects: isEmpty", function() {
|
test("isEmpty", function() {
|
||||||
ok(!_([1]).isEmpty(), '[1] is not empty');
|
ok(!_([1]).isEmpty(), '[1] is not empty');
|
||||||
ok(_.isEmpty([]), '[] is empty');
|
ok(_.isEmpty([]), '[] is empty');
|
||||||
ok(!_.isEmpty({one : 1}), '{one : 1} is not empty');
|
ok(!_.isEmpty({one : 1}), '{one : 1} is not empty');
|
||||||
@@ -398,17 +386,18 @@ $(document).ready(function() {
|
|||||||
parent.iNull = null;\
|
parent.iNull = null;\
|
||||||
parent.iBoolean = new Boolean(false);\
|
parent.iBoolean = new Boolean(false);\
|
||||||
parent.iUndefined = undefined;\
|
parent.iUndefined = undefined;\
|
||||||
|
parent.iObject = {};\
|
||||||
</script>"
|
</script>"
|
||||||
);
|
);
|
||||||
iDoc.close();
|
iDoc.close();
|
||||||
|
|
||||||
test("objects: isElement", function() {
|
test("isElement", function() {
|
||||||
ok(!_.isElement('div'), 'strings are not dom elements');
|
ok(!_.isElement('div'), 'strings are not dom elements');
|
||||||
ok(_.isElement($('html')[0]), 'the html tag is a DOM element');
|
ok(_.isElement($('html')[0]), 'the html tag is a DOM element');
|
||||||
ok(_.isElement(iElement), 'even from another frame');
|
ok(_.isElement(iElement), 'even from another frame');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("objects: isArguments", function() {
|
test("isArguments", function() {
|
||||||
var args = (function(){ return arguments; })(1, 2, 3);
|
var args = (function(){ return arguments; })(1, 2, 3);
|
||||||
ok(!_.isArguments('string'), 'a string is not an arguments object');
|
ok(!_.isArguments('string'), 'a string is not an arguments object');
|
||||||
ok(!_.isArguments(_.isArguments), 'a function is not an arguments object');
|
ok(!_.isArguments(_.isArguments), 'a function is not an arguments object');
|
||||||
@@ -418,7 +407,7 @@ $(document).ready(function() {
|
|||||||
ok(_.isArguments(iArguments), 'even from another frame');
|
ok(_.isArguments(iArguments), 'even from another frame');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("objects: isObject", function() {
|
test("isObject", function() {
|
||||||
ok(_.isObject(arguments), 'the arguments object is object');
|
ok(_.isObject(arguments), 'the arguments object is object');
|
||||||
ok(_.isObject([1, 2, 3]), 'and arrays');
|
ok(_.isObject([1, 2, 3]), 'and arrays');
|
||||||
ok(_.isObject($('html')[0]), 'and DOM element');
|
ok(_.isObject($('html')[0]), 'and DOM element');
|
||||||
@@ -433,19 +422,19 @@ $(document).ready(function() {
|
|||||||
ok(_.isObject(new String('string')), 'but new String()');
|
ok(_.isObject(new String('string')), 'but new String()');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("objects: isArray", function() {
|
test("isArray", function() {
|
||||||
ok(!_.isArray(arguments), 'the arguments object is not an array');
|
ok(!_.isArray(arguments), 'the arguments object is not an array');
|
||||||
ok(_.isArray([1, 2, 3]), 'but arrays are');
|
ok(_.isArray([1, 2, 3]), 'but arrays are');
|
||||||
ok(_.isArray(iArray), 'even from another frame');
|
ok(_.isArray(iArray), 'even from another frame');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("objects: isString", function() {
|
test("isString", function() {
|
||||||
ok(!_.isString(document.body), 'the document body is not a string');
|
ok(!_.isString(document.body), 'the document body is not a string');
|
||||||
ok(_.isString([1, 2, 3].join(', ')), 'but strings are');
|
ok(_.isString([1, 2, 3].join(', ')), 'but strings are');
|
||||||
ok(_.isString(iString), 'even from another frame');
|
ok(_.isString(iString), 'even from another frame');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("objects: isNumber", function() {
|
test("isNumber", function() {
|
||||||
ok(!_.isNumber('string'), 'a string is not a number');
|
ok(!_.isNumber('string'), 'a string is not a number');
|
||||||
ok(!_.isNumber(arguments), 'the arguments object is not a number');
|
ok(!_.isNumber(arguments), 'the arguments object is not a number');
|
||||||
ok(!_.isNumber(undefined), 'undefined is not a number');
|
ok(!_.isNumber(undefined), 'undefined is not a number');
|
||||||
@@ -456,7 +445,7 @@ $(document).ready(function() {
|
|||||||
ok(!_.isNumber('1'), 'numeric strings are not numbers');
|
ok(!_.isNumber('1'), 'numeric strings are not numbers');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("objects: isBoolean", function() {
|
test("isBoolean", function() {
|
||||||
ok(!_.isBoolean(2), 'a number is not a boolean');
|
ok(!_.isBoolean(2), 'a number is not a boolean');
|
||||||
ok(!_.isBoolean("string"), 'a string is not a boolean');
|
ok(!_.isBoolean("string"), 'a string is not a boolean');
|
||||||
ok(!_.isBoolean("false"), 'the string "false" is not a boolean');
|
ok(!_.isBoolean("false"), 'the string "false" is not a boolean');
|
||||||
@@ -470,33 +459,35 @@ $(document).ready(function() {
|
|||||||
ok(_.isBoolean(iBoolean), 'even from another frame');
|
ok(_.isBoolean(iBoolean), 'even from another frame');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("objects: isFunction", function() {
|
test("isFunction", function() {
|
||||||
ok(!_.isFunction([1, 2, 3]), 'arrays are not functions');
|
ok(!_.isFunction([1, 2, 3]), 'arrays are not functions');
|
||||||
ok(!_.isFunction('moe'), 'strings are not functions');
|
ok(!_.isFunction('moe'), 'strings are not functions');
|
||||||
ok(_.isFunction(_.isFunction), 'but functions are');
|
ok(_.isFunction(_.isFunction), 'but functions are');
|
||||||
ok(_.isFunction(iFunction), 'even from another frame');
|
ok(_.isFunction(iFunction), 'even from another frame');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("objects: isDate", function() {
|
test("isDate", function() {
|
||||||
ok(!_.isDate(100), 'numbers are not dates');
|
ok(!_.isDate(100), 'numbers are not dates');
|
||||||
ok(!_.isDate({}), 'objects are not dates');
|
ok(!_.isDate({}), 'objects are not dates');
|
||||||
ok(_.isDate(new Date()), 'but dates are');
|
ok(_.isDate(new Date()), 'but dates are');
|
||||||
ok(_.isDate(iDate), 'even from another frame');
|
ok(_.isDate(iDate), 'even from another frame');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("objects: isRegExp", function() {
|
test("isRegExp", function() {
|
||||||
ok(!_.isRegExp(_.identity), 'functions are not RegExps');
|
ok(!_.isRegExp(_.identity), 'functions are not RegExps');
|
||||||
ok(_.isRegExp(/identity/), 'but RegExps are');
|
ok(_.isRegExp(/identity/), 'but RegExps are');
|
||||||
ok(_.isRegExp(iRegExp), 'even from another frame');
|
ok(_.isRegExp(iRegExp), 'even from another frame');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("objects: isFinite", function() {
|
test("isFinite", function() {
|
||||||
ok(!_.isFinite(undefined), 'undefined is not Finite');
|
ok(!_.isFinite(undefined), 'undefined is not Finite');
|
||||||
ok(!_.isFinite(null), 'null is not Finite');
|
ok(!_.isFinite(null), 'null is not Finite');
|
||||||
ok(!_.isFinite(NaN), 'NaN is not Finite');
|
ok(!_.isFinite(NaN), 'NaN is not Finite');
|
||||||
ok(!_.isFinite(Infinity), 'Infinity is not Finite');
|
ok(!_.isFinite(Infinity), 'Infinity is not Finite');
|
||||||
ok(!_.isFinite(-Infinity), '-Infinity is not Finite');
|
ok(!_.isFinite(-Infinity), '-Infinity is not Finite');
|
||||||
ok(!_.isFinite('12'), 'Strings are not numbers');
|
ok(_.isFinite('12'), 'Numeric strings are numbers');
|
||||||
|
ok(!_.isFinite('1a'), 'Non numeric strings are not numbers');
|
||||||
|
ok(!_.isFinite(''), 'Empty strings are not numbers');
|
||||||
var obj = new Number(5);
|
var obj = new Number(5);
|
||||||
ok(_.isFinite(obj), 'Number instances can be finite');
|
ok(_.isFinite(obj), 'Number instances can be finite');
|
||||||
ok(_.isFinite(0), '0 is Finite');
|
ok(_.isFinite(0), '0 is Finite');
|
||||||
@@ -504,22 +495,23 @@ $(document).ready(function() {
|
|||||||
ok(_.isFinite(-12.44), 'Floats are Finite');
|
ok(_.isFinite(-12.44), 'Floats are Finite');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("objects: isNaN", function() {
|
test("isNaN", function() {
|
||||||
ok(!_.isNaN(undefined), 'undefined is not NaN');
|
ok(!_.isNaN(undefined), 'undefined is not NaN');
|
||||||
ok(!_.isNaN(null), 'null is not NaN');
|
ok(!_.isNaN(null), 'null is not NaN');
|
||||||
ok(!_.isNaN(0), '0 is not NaN');
|
ok(!_.isNaN(0), '0 is not NaN');
|
||||||
ok(_.isNaN(NaN), 'but NaN is');
|
ok(_.isNaN(NaN), 'but NaN is');
|
||||||
ok(_.isNaN(iNaN), 'even from another frame');
|
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(undefined), 'undefined is not null');
|
||||||
ok(!_.isNull(NaN), 'NaN is not null');
|
ok(!_.isNull(NaN), 'NaN is not null');
|
||||||
ok(_.isNull(null), 'but null is');
|
ok(_.isNull(null), 'but null is');
|
||||||
ok(_.isNull(iNull), 'even from another frame');
|
ok(_.isNull(iNull), 'even from another frame');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("objects: isUndefined", function() {
|
test("isUndefined", function() {
|
||||||
ok(!_.isUndefined(1), 'numbers are defined');
|
ok(!_.isUndefined(1), 'numbers are defined');
|
||||||
ok(!_.isUndefined(null), 'null is defined');
|
ok(!_.isUndefined(null), 'null is defined');
|
||||||
ok(!_.isUndefined(false), 'false is defined');
|
ok(!_.isUndefined(false), 'false is defined');
|
||||||
@@ -530,7 +522,7 @@ $(document).ready(function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (window.ActiveXObject) {
|
if (window.ActiveXObject) {
|
||||||
test("objects: IE host objects", function() {
|
test("IE host objects", function() {
|
||||||
var xml = new ActiveXObject("Msxml2.DOMDocument.3.0");
|
var xml = new ActiveXObject("Msxml2.DOMDocument.3.0");
|
||||||
ok(!_.isNumber(xml));
|
ok(!_.isNumber(xml));
|
||||||
ok(!_.isBoolean(xml));
|
ok(!_.isBoolean(xml));
|
||||||
@@ -541,7 +533,7 @@ $(document).ready(function() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
test("objects: tap", function() {
|
test("tap", function() {
|
||||||
var intercepted = null;
|
var intercepted = null;
|
||||||
var interceptor = function(obj) { intercepted = obj; };
|
var interceptor = function(obj) { intercepted = obj; };
|
||||||
var returned = _.tap(1, interceptor);
|
var returned = _.tap(1, interceptor);
|
||||||
|
|||||||
46
vendor/underscore/test/utility.js
vendored
46
vendor/underscore/test/utility.js
vendored
@@ -14,18 +14,24 @@ $(document).ready(function() {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("utility: identity", function() {
|
test("#750 - Return _ instance.", 2, function() {
|
||||||
|
var instance = _([]);
|
||||||
|
ok(_(instance) === instance);
|
||||||
|
ok(new _(instance) === instance);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("identity", function() {
|
||||||
var moe = {name : 'moe'};
|
var moe = {name : 'moe'};
|
||||||
equal(_.identity(moe), moe, 'moe is the same as his identity');
|
equal(_.identity(moe), moe, 'moe is the same as his identity');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("utility: uniqueId", function() {
|
test("uniqueId", function() {
|
||||||
var ids = [], i = 0;
|
var ids = [], i = 0;
|
||||||
while(i++ < 100) ids.push(_.uniqueId());
|
while(i++ < 100) ids.push(_.uniqueId());
|
||||||
equal(_.uniq(ids).length, ids.length, 'can generate a globally-unique stream of ids');
|
equal(_.uniq(ids).length, ids.length, 'can generate a globally-unique stream of ids');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("utility: times", function() {
|
test("times", function() {
|
||||||
var vals = [];
|
var vals = [];
|
||||||
_.times(3, function (i) { vals.push(i); });
|
_.times(3, function (i) { vals.push(i); });
|
||||||
ok(_.isEqual(vals, [0,1,2]), "is 0 indexed");
|
ok(_.isEqual(vals, [0,1,2]), "is 0 indexed");
|
||||||
@@ -35,7 +41,7 @@ $(document).ready(function() {
|
|||||||
ok(_.isEqual(vals, [0,1,2]), "works as a wrapper");
|
ok(_.isEqual(vals, [0,1,2]), "works as a wrapper");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("utility: mixin", function() {
|
test("mixin", function() {
|
||||||
_.mixin({
|
_.mixin({
|
||||||
myReverse: function(string) {
|
myReverse: function(string) {
|
||||||
return string.split('').reverse().join('');
|
return string.split('').reverse().join('');
|
||||||
@@ -45,12 +51,21 @@ $(document).ready(function() {
|
|||||||
equal(_('champ').myReverse(), 'pmahc', 'mixed in a function to the OOP wrapper');
|
equal(_('champ').myReverse(), 'pmahc', 'mixed in a function to the OOP wrapper');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("utility: _.escape", function() {
|
test("_.escape", function() {
|
||||||
equal(_.escape("Curly & Moe"), "Curly & Moe");
|
equal(_.escape("Curly & Moe"), "Curly & Moe");
|
||||||
equal(_.escape("Curly & Moe"), "Curly &amp; Moe");
|
equal(_.escape("Curly & Moe"), "Curly &amp; Moe");
|
||||||
|
equal(_.escape(null), '');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("utility: template", function() {
|
test("_.unescape", function() {
|
||||||
|
var string = "Curly & Moe";
|
||||||
|
equal(_.unescape("Curly & Moe"), string);
|
||||||
|
equal(_.unescape("Curly &amp; Moe"), "Curly & Moe");
|
||||||
|
equal(_.unescape(null), '');
|
||||||
|
equal(_.unescape(_.escape(string)), string);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("template", function() {
|
||||||
var basicTemplate = _.template("<%= thing %> is gettin' on my noives!");
|
var basicTemplate = _.template("<%= thing %> is gettin' on my noives!");
|
||||||
var result = basicTemplate({thing : 'This'});
|
var result = basicTemplate({thing : 'This'});
|
||||||
equal(result, "This is gettin' on my noives!", 'can do basic attribute interpolation');
|
equal(result, "This is gettin' on my noives!", 'can do basic attribute interpolation');
|
||||||
@@ -156,6 +171,14 @@ $(document).ready(function() {
|
|||||||
equal(templateWithNull({planet : "world"}), "a null undefined world", "can handle missing escape and evaluate settings");
|
equal(templateWithNull({planet : "world"}), "a null undefined world", "can handle missing escape and evaluate settings");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('_.template provides the generated function source, when a SyntaxError occurs', function() {
|
||||||
|
try {
|
||||||
|
_.template('<b><%= if %></b>');
|
||||||
|
} catch (e) {
|
||||||
|
ok(e.source.indexOf('( if )') > 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('_.template handles \\u2028 & \\u2029', function() {
|
test('_.template handles \\u2028 & \\u2029', function() {
|
||||||
var tmpl = _.template('<p>\u2028<%= "\\u2028\\u2029" %>\u2029</p>');
|
var tmpl = _.template('<p>\u2028<%= "\\u2028\\u2029" %>\u2029</p>');
|
||||||
strictEqual(tmpl(), '<p>\u2028\u2028\u2029\u2029</p>');
|
strictEqual(tmpl(), '<p>\u2028\u2028\u2029\u2029</p>');
|
||||||
@@ -212,4 +235,15 @@ $(document).ready(function() {
|
|||||||
templateEscaped({f: function(){ ok(!(countEscaped++)); }});
|
templateEscaped({f: function(){ ok(!(countEscaped++)); }});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('#746 - _.template settings are not modified.', 1, function() {
|
||||||
|
var settings = {};
|
||||||
|
_.template('', null, settings);
|
||||||
|
deepEqual(settings, {});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('#779 - delimeters are applied to unescaped text.', 1, function() {
|
||||||
|
var template = _.template('<<\nx\n>>', null, {evaluate: /<<(.*?)>>/g});
|
||||||
|
strictEqual(template(), '<<\nx\n>>');
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
670
vendor/underscore/test/vendor/jslitmus.js
vendored
670
vendor/underscore/test/vendor/jslitmus.js
vendored
@@ -1,670 +0,0 @@
|
|||||||
// JSLitmus.js
|
|
||||||
//
|
|
||||||
// History:
|
|
||||||
// 2008-10-27: Initial release
|
|
||||||
// 2008-11-09: Account for iteration loop overhead
|
|
||||||
// 2008-11-13: Added OS detection
|
|
||||||
// 2009-02-25: Create tinyURL automatically, shift-click runs tests in reverse
|
|
||||||
//
|
|
||||||
// Copyright (c) 2008-2009, Robert Kieffer
|
|
||||||
// All Rights Reserved
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the
|
|
||||||
// Software), to deal in the Software without restriction, including
|
|
||||||
// without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
|
||||||
// persons to whom the Software is furnished to do so, subject to the
|
|
||||||
// following conditions:
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
|
||||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
||||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
||||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
||||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
(function() {
|
|
||||||
// Private methods and state
|
|
||||||
|
|
||||||
// Get platform info but don't go crazy trying to recognize everything
|
|
||||||
// that's out there. This is just for the major platforms and OSes.
|
|
||||||
var platform = 'unknown platform', ua = navigator.userAgent;
|
|
||||||
|
|
||||||
// Detect OS
|
|
||||||
var oses = ['Windows','iPhone OS','(Intel |PPC )?Mac OS X','Linux'].join('|');
|
|
||||||
var pOS = new RegExp('((' + oses + ') [^ \);]*)').test(ua) ? RegExp.$1 : null;
|
|
||||||
if (!pOS) pOS = new RegExp('((' + oses + ')[^ \);]*)').test(ua) ? RegExp.$1 : null;
|
|
||||||
|
|
||||||
// Detect browser
|
|
||||||
var pName = /(Chrome|MSIE|Safari|Opera|Firefox)/.test(ua) ? RegExp.$1 : null;
|
|
||||||
|
|
||||||
// Detect version
|
|
||||||
var vre = new RegExp('(Version|' + pName + ')[ \/]([^ ;]*)');
|
|
||||||
var pVersion = (pName && vre.test(ua)) ? RegExp.$2 : null;
|
|
||||||
var platform = (pOS && pName && pVersion) ? pName + ' ' + pVersion + ' on ' + pOS : 'unknown platform';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A smattering of methods that are needed to implement the JSLitmus testbed.
|
|
||||||
*/
|
|
||||||
var jsl = {
|
|
||||||
/**
|
|
||||||
* Enhanced version of escape()
|
|
||||||
*/
|
|
||||||
escape: function(s) {
|
|
||||||
s = s.replace(/,/g, '\\,');
|
|
||||||
s = escape(s);
|
|
||||||
s = s.replace(/\+/g, '%2b');
|
|
||||||
s = s.replace(/ /g, '+');
|
|
||||||
return s;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get an element by ID.
|
|
||||||
*/
|
|
||||||
$: function(id) {
|
|
||||||
return document.getElementById(id);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Null function
|
|
||||||
*/
|
|
||||||
F: function() {},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the status shown in the UI
|
|
||||||
*/
|
|
||||||
status: function(msg) {
|
|
||||||
var el = jsl.$('jsl_status');
|
|
||||||
if (el) el.innerHTML = msg || '';
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a number to an abbreviated string like, "15K" or "10M"
|
|
||||||
*/
|
|
||||||
toLabel: function(n) {
|
|
||||||
if (n == Infinity) {
|
|
||||||
return 'Infinity';
|
|
||||||
} else if (n > 1e9) {
|
|
||||||
n = Math.round(n/1e8);
|
|
||||||
return n/10 + 'B';
|
|
||||||
} else if (n > 1e6) {
|
|
||||||
n = Math.round(n/1e5);
|
|
||||||
return n/10 + 'M';
|
|
||||||
} else if (n > 1e3) {
|
|
||||||
n = Math.round(n/1e2);
|
|
||||||
return n/10 + 'K';
|
|
||||||
}
|
|
||||||
return n;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy properties from src to dst
|
|
||||||
*/
|
|
||||||
extend: function(dst, src) {
|
|
||||||
for (var k in src) dst[k] = src[k]; return dst;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Like Array.join(), but for the key-value pairs in an object
|
|
||||||
*/
|
|
||||||
join: function(o, delimit1, delimit2) {
|
|
||||||
if (o.join) return o.join(delimit1); // If it's an array
|
|
||||||
var pairs = [];
|
|
||||||
for (var k in o) pairs.push(k + delimit1 + o[k]);
|
|
||||||
return pairs.join(delimit2);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Array#indexOf isn't supported in IE, so we use this as a cross-browser solution
|
|
||||||
*/
|
|
||||||
indexOf: function(arr, o) {
|
|
||||||
if (arr.indexOf) return arr.indexOf(o);
|
|
||||||
for (var i = 0; i < this.length; i++) if (arr[i] === o) return i;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test manages a single test (created with
|
|
||||||
* JSLitmus.test())
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var Test = function (name, f) {
|
|
||||||
if (!f) throw new Error('Undefined test function');
|
|
||||||
if (!(/function[^\(]*\(([^,\)]*)/).test(f.toString())) {
|
|
||||||
throw new Error('"' + name + '" test: Test is not a valid Function object');
|
|
||||||
}
|
|
||||||
this.loopArg = RegExp.$1;
|
|
||||||
this.name = name;
|
|
||||||
this.f = f;
|
|
||||||
};
|
|
||||||
|
|
||||||
jsl.extend(Test, /** @lends Test */ {
|
|
||||||
/** Calibration tests for establishing iteration loop overhead */
|
|
||||||
CALIBRATIONS: [
|
|
||||||
new Test('calibrating loop', function(count) {while (count--);}),
|
|
||||||
new Test('calibrating function', jsl.F)
|
|
||||||
],
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Run calibration tests. Returns true if calibrations are not yet
|
|
||||||
* complete (in which case calling code should run the tests yet again).
|
|
||||||
* onCalibrated - Callback to invoke when calibrations have finished
|
|
||||||
*/
|
|
||||||
calibrate: function(onCalibrated) {
|
|
||||||
for (var i = 0; i < Test.CALIBRATIONS.length; i++) {
|
|
||||||
var cal = Test.CALIBRATIONS[i];
|
|
||||||
if (cal.running) return true;
|
|
||||||
if (!cal.count) {
|
|
||||||
cal.isCalibration = true;
|
|
||||||
cal.onStop = onCalibrated;
|
|
||||||
//cal.MIN_TIME = .1; // Do calibrations quickly
|
|
||||||
cal.run(2e4);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
jsl.extend(Test.prototype, {/** @lends Test.prototype */
|
|
||||||
/** Initial number of iterations */
|
|
||||||
INIT_COUNT: 10,
|
|
||||||
/** Max iterations allowed (i.e. used to detect bad looping functions) */
|
|
||||||
MAX_COUNT: 1e9,
|
|
||||||
/** Minimum time a test should take to get valid results (secs) */
|
|
||||||
MIN_TIME: .5,
|
|
||||||
|
|
||||||
/** Callback invoked when test state changes */
|
|
||||||
onChange: jsl.F,
|
|
||||||
|
|
||||||
/** Callback invoked when test is finished */
|
|
||||||
onStop: jsl.F,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset test state
|
|
||||||
*/
|
|
||||||
reset: function() {
|
|
||||||
delete this.count;
|
|
||||||
delete this.time;
|
|
||||||
delete this.running;
|
|
||||||
delete this.error;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Run the test (in a timeout). We use a timeout to make sure the browser
|
|
||||||
* has a chance to finish rendering any UI changes we've made, like
|
|
||||||
* updating the status message.
|
|
||||||
*/
|
|
||||||
run: function(count) {
|
|
||||||
count = count || this.INIT_COUNT;
|
|
||||||
jsl.status(this.name + ' x ' + count);
|
|
||||||
this.running = true;
|
|
||||||
var me = this;
|
|
||||||
setTimeout(function() {me._run(count);}, 200);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The nuts and bolts code that actually runs a test
|
|
||||||
*/
|
|
||||||
_run: function(count) {
|
|
||||||
var me = this;
|
|
||||||
|
|
||||||
// Make sure calibration tests have run
|
|
||||||
if (!me.isCalibration && Test.calibrate(function() {me.run(count);})) return;
|
|
||||||
this.error = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
var start, f = this.f, now, i = count;
|
|
||||||
|
|
||||||
// Start the timer
|
|
||||||
start = new Date();
|
|
||||||
|
|
||||||
// Now for the money shot. If this is a looping function ...
|
|
||||||
if (this.loopArg) {
|
|
||||||
// ... let it do the iteration itself
|
|
||||||
f(count);
|
|
||||||
} else {
|
|
||||||
// ... otherwise do the iteration for it
|
|
||||||
while (i--) f();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get time test took (in secs)
|
|
||||||
this.time = Math.max(1,new Date() - start)/1000;
|
|
||||||
|
|
||||||
// Store iteration count and per-operation time taken
|
|
||||||
this.count = count;
|
|
||||||
this.period = this.time/count;
|
|
||||||
|
|
||||||
// Do we need to do another run?
|
|
||||||
this.running = this.time <= this.MIN_TIME;
|
|
||||||
|
|
||||||
// ... if so, compute how many times we should iterate
|
|
||||||
if (this.running) {
|
|
||||||
// Bump the count to the nearest power of 2
|
|
||||||
var x = this.MIN_TIME/this.time;
|
|
||||||
var pow = Math.pow(2, Math.max(1, Math.ceil(Math.log(x)/Math.log(2))));
|
|
||||||
count *= pow;
|
|
||||||
if (count > this.MAX_COUNT) {
|
|
||||||
throw new Error('Max count exceeded. If this test uses a looping function, make sure the iteration loop is working properly.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// Exceptions are caught and displayed in the test UI
|
|
||||||
this.reset();
|
|
||||||
this.error = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Figure out what to do next
|
|
||||||
if (this.running) {
|
|
||||||
me.run(count);
|
|
||||||
} else {
|
|
||||||
jsl.status('');
|
|
||||||
me.onStop(me);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finish up
|
|
||||||
this.onChange(this);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the number of operations per second for this test.
|
|
||||||
*
|
|
||||||
* @param normalize if true, iteration loop overhead taken into account
|
|
||||||
*/
|
|
||||||
getHz: function(/**Boolean*/ normalize) {
|
|
||||||
var p = this.period;
|
|
||||||
|
|
||||||
// Adjust period based on the calibration test time
|
|
||||||
if (normalize && !this.isCalibration) {
|
|
||||||
var cal = Test.CALIBRATIONS[this.loopArg ? 0 : 1];
|
|
||||||
|
|
||||||
// If the period is within 20% of the calibration time, then zero the
|
|
||||||
// it out
|
|
||||||
p = p < cal.period*1.2 ? 0 : p - cal.period;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Math.round(1/p);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a friendly string describing the test
|
|
||||||
*/
|
|
||||||
toString: function() {
|
|
||||||
return this.name + ' - ' + this.time/this.count + ' secs';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// CSS we need for the UI
|
|
||||||
var STYLESHEET = '<style> \
|
|
||||||
#jslitmus {font-family:sans-serif; font-size: 12px;} \
|
|
||||||
#jslitmus a {text-decoration: none;} \
|
|
||||||
#jslitmus a:hover {text-decoration: underline;} \
|
|
||||||
#jsl_status { \
|
|
||||||
margin-top: 10px; \
|
|
||||||
font-size: 10px; \
|
|
||||||
color: #888; \
|
|
||||||
} \
|
|
||||||
A IMG {border:none} \
|
|
||||||
#test_results { \
|
|
||||||
margin-top: 10px; \
|
|
||||||
font-size: 12px; \
|
|
||||||
font-family: sans-serif; \
|
|
||||||
border-collapse: collapse; \
|
|
||||||
border-spacing: 0px; \
|
|
||||||
} \
|
|
||||||
#test_results th, #test_results td { \
|
|
||||||
border: solid 1px #ccc; \
|
|
||||||
vertical-align: top; \
|
|
||||||
padding: 3px; \
|
|
||||||
} \
|
|
||||||
#test_results th { \
|
|
||||||
vertical-align: bottom; \
|
|
||||||
background-color: #ccc; \
|
|
||||||
padding: 1px; \
|
|
||||||
font-size: 10px; \
|
|
||||||
} \
|
|
||||||
#test_results #test_platform { \
|
|
||||||
color: #444; \
|
|
||||||
text-align:center; \
|
|
||||||
} \
|
|
||||||
#test_results .test_row { \
|
|
||||||
color: #006; \
|
|
||||||
cursor: pointer; \
|
|
||||||
} \
|
|
||||||
#test_results .test_nonlooping { \
|
|
||||||
border-left-style: dotted; \
|
|
||||||
border-left-width: 2px; \
|
|
||||||
} \
|
|
||||||
#test_results .test_looping { \
|
|
||||||
border-left-style: solid; \
|
|
||||||
border-left-width: 2px; \
|
|
||||||
} \
|
|
||||||
#test_results .test_name {white-space: nowrap;} \
|
|
||||||
#test_results .test_pending { \
|
|
||||||
} \
|
|
||||||
#test_results .test_running { \
|
|
||||||
font-style: italic; \
|
|
||||||
} \
|
|
||||||
#test_results .test_done {} \
|
|
||||||
#test_results .test_done { \
|
|
||||||
text-align: right; \
|
|
||||||
font-family: monospace; \
|
|
||||||
} \
|
|
||||||
#test_results .test_error {color: #600;} \
|
|
||||||
#test_results .test_error .error_head {font-weight:bold;} \
|
|
||||||
#test_results .test_error .error_body {font-size:85%;} \
|
|
||||||
#test_results .test_row:hover td { \
|
|
||||||
background-color: #ffc; \
|
|
||||||
text-decoration: underline; \
|
|
||||||
} \
|
|
||||||
#chart { \
|
|
||||||
margin: 10px 0px; \
|
|
||||||
width: 250px; \
|
|
||||||
} \
|
|
||||||
#chart img { \
|
|
||||||
border: solid 1px #ccc; \
|
|
||||||
margin-bottom: 5px; \
|
|
||||||
} \
|
|
||||||
#chart #tiny_url { \
|
|
||||||
height: 40px; \
|
|
||||||
width: 250px; \
|
|
||||||
} \
|
|
||||||
#jslitmus_credit { \
|
|
||||||
font-size: 10px; \
|
|
||||||
color: #888; \
|
|
||||||
margin-top: 8px; \
|
|
||||||
} \
|
|
||||||
</style>';
|
|
||||||
|
|
||||||
// HTML markup for the UI
|
|
||||||
var MARKUP = '<div id="jslitmus"> \
|
|
||||||
<button onclick="JSLitmus.runAll(event)">Run Tests</button> \
|
|
||||||
<button id="stop_button" disabled="disabled" onclick="JSLitmus.stop()">Stop Tests</button> \
|
|
||||||
<br \> \
|
|
||||||
<br \> \
|
|
||||||
<input type="checkbox" style="vertical-align: middle" id="test_normalize" checked="checked" onchange="JSLitmus.renderAll()""> Normalize results \
|
|
||||||
<table id="test_results"> \
|
|
||||||
<colgroup> \
|
|
||||||
<col /> \
|
|
||||||
<col width="100" /> \
|
|
||||||
</colgroup> \
|
|
||||||
<tr><th id="test_platform" colspan="2">' + platform + '</th></tr> \
|
|
||||||
<tr><th>Test</th><th>Ops/sec</th></tr> \
|
|
||||||
<tr id="test_row_template" class="test_row" style="display:none"> \
|
|
||||||
<td class="test_name"></td> \
|
|
||||||
<td class="test_result">Ready</td> \
|
|
||||||
</tr> \
|
|
||||||
</table> \
|
|
||||||
<div id="jsl_status"></div> \
|
|
||||||
<div id="chart" style="display:none"> \
|
|
||||||
<a id="chart_link" target="_blank"><img id="chart_image"></a> \
|
|
||||||
TinyURL (for chart): \
|
|
||||||
<iframe id="tiny_url" frameBorder="0" scrolling="no" src=""></iframe> \
|
|
||||||
</div> \
|
|
||||||
<a id="jslitmus_credit" title="JSLitmus home page" href="http://code.google.com/p/jslitmus" target="_blank">Powered by JSLitmus</a> \
|
|
||||||
</div>';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The public API for creating and running tests
|
|
||||||
*/
|
|
||||||
window.JSLitmus = {
|
|
||||||
/** The list of all tests that have been registered with JSLitmus.test */
|
|
||||||
_tests: [],
|
|
||||||
/** The queue of tests that need to be run */
|
|
||||||
_queue: [],
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The parsed query parameters the current page URL. This is provided as a
|
|
||||||
* convenience for test functions - it's not used by JSLitmus proper
|
|
||||||
*/
|
|
||||||
params: {},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize
|
|
||||||
*/
|
|
||||||
_init: function() {
|
|
||||||
// Parse query params into JSLitmus.params[] hash
|
|
||||||
var match = (location + '').match(/([^?#]*)(#.*)?$/);
|
|
||||||
if (match) {
|
|
||||||
var pairs = match[1].split('&');
|
|
||||||
for (var i = 0; i < pairs.length; i++) {
|
|
||||||
var pair = pairs[i].split('=');
|
|
||||||
if (pair.length > 1) {
|
|
||||||
var key = pair.shift();
|
|
||||||
var value = pair.length > 1 ? pair.join('=') : pair[0];
|
|
||||||
this.params[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write out the stylesheet. We have to do this here because IE
|
|
||||||
// doesn't honor sheets written after the document has loaded.
|
|
||||||
document.write(STYLESHEET);
|
|
||||||
|
|
||||||
// Setup the rest of the UI once the document is loaded
|
|
||||||
if (window.addEventListener) {
|
|
||||||
window.addEventListener('load', this._setup, false);
|
|
||||||
} else if (document.addEventListener) {
|
|
||||||
document.addEventListener('load', this._setup, false);
|
|
||||||
} else if (window.attachEvent) {
|
|
||||||
window.attachEvent('onload', this._setup);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set up the UI
|
|
||||||
*/
|
|
||||||
_setup: function() {
|
|
||||||
var el = jsl.$('jslitmus_container');
|
|
||||||
if (!el) document.body.appendChild(el = document.createElement('div'));
|
|
||||||
|
|
||||||
el.innerHTML = MARKUP;
|
|
||||||
|
|
||||||
// Render the UI for all our tests
|
|
||||||
for (var i=0; i < JSLitmus._tests.length; i++)
|
|
||||||
JSLitmus.renderTest(JSLitmus._tests[i]);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* (Re)render all the test results
|
|
||||||
*/
|
|
||||||
renderAll: function() {
|
|
||||||
for (var i = 0; i < JSLitmus._tests.length; i++)
|
|
||||||
JSLitmus.renderTest(JSLitmus._tests[i]);
|
|
||||||
JSLitmus.renderChart();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* (Re)render the chart graphics
|
|
||||||
*/
|
|
||||||
renderChart: function() {
|
|
||||||
var url = JSLitmus.chartUrl();
|
|
||||||
jsl.$('chart_link').href = url;
|
|
||||||
jsl.$('chart_image').src = url;
|
|
||||||
jsl.$('chart').style.display = '';
|
|
||||||
|
|
||||||
// Update the tiny URL
|
|
||||||
jsl.$('tiny_url').src = 'http://tinyurl.com/api-create.php?url='+escape(url);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* (Re)render the results for a specific test
|
|
||||||
*/
|
|
||||||
renderTest: function(test) {
|
|
||||||
// Make a new row if needed
|
|
||||||
if (!test._row) {
|
|
||||||
var trow = jsl.$('test_row_template');
|
|
||||||
if (!trow) return;
|
|
||||||
|
|
||||||
test._row = trow.cloneNode(true);
|
|
||||||
test._row.style.display = '';
|
|
||||||
test._row.id = '';
|
|
||||||
test._row.onclick = function() {JSLitmus._queueTest(test);};
|
|
||||||
test._row.title = 'Run ' + test.name + ' test';
|
|
||||||
trow.parentNode.appendChild(test._row);
|
|
||||||
test._row.cells[0].innerHTML = test.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
var cell = test._row.cells[1];
|
|
||||||
var cns = [test.loopArg ? 'test_looping' : 'test_nonlooping'];
|
|
||||||
|
|
||||||
if (test.error) {
|
|
||||||
cns.push('test_error');
|
|
||||||
cell.innerHTML =
|
|
||||||
'<div class="error_head">' + test.error + '</div>' +
|
|
||||||
'<ul class="error_body"><li>' +
|
|
||||||
jsl.join(test.error, ': ', '</li><li>') +
|
|
||||||
'</li></ul>';
|
|
||||||
} else {
|
|
||||||
if (test.running) {
|
|
||||||
cns.push('test_running');
|
|
||||||
cell.innerHTML = 'running';
|
|
||||||
} else if (jsl.indexOf(JSLitmus._queue, test) >= 0) {
|
|
||||||
cns.push('test_pending');
|
|
||||||
cell.innerHTML = 'pending';
|
|
||||||
} else if (test.count) {
|
|
||||||
cns.push('test_done');
|
|
||||||
var hz = test.getHz(jsl.$('test_normalize').checked);
|
|
||||||
cell.innerHTML = hz != Infinity ? hz : '∞';
|
|
||||||
} else {
|
|
||||||
cell.innerHTML = 'ready';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cell.className = cns.join(' ');
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new test
|
|
||||||
*/
|
|
||||||
test: function(name, f) {
|
|
||||||
// Create the Test object
|
|
||||||
var test = new Test(name, f);
|
|
||||||
JSLitmus._tests.push(test);
|
|
||||||
|
|
||||||
// Re-render if the test state changes
|
|
||||||
test.onChange = JSLitmus.renderTest;
|
|
||||||
|
|
||||||
// Run the next test if this one finished
|
|
||||||
test.onStop = function(test) {
|
|
||||||
if (JSLitmus.onTestFinish) JSLitmus.onTestFinish(test);
|
|
||||||
JSLitmus.currentTest = null;
|
|
||||||
JSLitmus._nextTest();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Render the new test
|
|
||||||
this.renderTest(test);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add all tests to the run queue
|
|
||||||
*/
|
|
||||||
runAll: function(e) {
|
|
||||||
e = e || window.event;
|
|
||||||
var reverse = e && e.shiftKey, len = JSLitmus._tests.length;
|
|
||||||
for (var i = 0; i < len; i++) {
|
|
||||||
JSLitmus._queueTest(JSLitmus._tests[!reverse ? i : (len - i - 1)]);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove all tests from the run queue. The current test has to finish on
|
|
||||||
* it's own though
|
|
||||||
*/
|
|
||||||
stop: function() {
|
|
||||||
while (JSLitmus._queue.length) {
|
|
||||||
var test = JSLitmus._queue.shift();
|
|
||||||
JSLitmus.renderTest(test);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Run the next test in the run queue
|
|
||||||
*/
|
|
||||||
_nextTest: function() {
|
|
||||||
if (!JSLitmus.currentTest) {
|
|
||||||
var test = JSLitmus._queue.shift();
|
|
||||||
if (test) {
|
|
||||||
jsl.$('stop_button').disabled = false;
|
|
||||||
JSLitmus.currentTest = test;
|
|
||||||
test.run();
|
|
||||||
JSLitmus.renderTest(test);
|
|
||||||
if (JSLitmus.onTestStart) JSLitmus.onTestStart(test);
|
|
||||||
} else {
|
|
||||||
jsl.$('stop_button').disabled = true;
|
|
||||||
JSLitmus.renderChart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a test to the run queue
|
|
||||||
*/
|
|
||||||
_queueTest: function(test) {
|
|
||||||
if (jsl.indexOf(JSLitmus._queue, test) >= 0) return;
|
|
||||||
JSLitmus._queue.push(test);
|
|
||||||
JSLitmus.renderTest(test);
|
|
||||||
JSLitmus._nextTest();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a Google Chart URL that shows the data for all tests
|
|
||||||
*/
|
|
||||||
chartUrl: function() {
|
|
||||||
var n = JSLitmus._tests.length, markers = [], data = [];
|
|
||||||
var d, min = 0, max = -1e10;
|
|
||||||
var normalize = jsl.$('test_normalize').checked;
|
|
||||||
|
|
||||||
// Gather test data
|
|
||||||
for (var i=0; i < JSLitmus._tests.length; i++) {
|
|
||||||
var test = JSLitmus._tests[i];
|
|
||||||
if (test.count) {
|
|
||||||
var hz = test.getHz(normalize);
|
|
||||||
var v = hz != Infinity ? hz : 0;
|
|
||||||
data.push(v);
|
|
||||||
markers.push('t' + jsl.escape(test.name + '(' + jsl.toLabel(hz)+ ')') + ',000000,0,' +
|
|
||||||
markers.length + ',10');
|
|
||||||
max = Math.max(v, max);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (markers.length <= 0) return null;
|
|
||||||
|
|
||||||
// Build chart title
|
|
||||||
var title = document.getElementsByTagName('title');
|
|
||||||
title = (title && title.length) ? title[0].innerHTML : null;
|
|
||||||
var chart_title = [];
|
|
||||||
if (title) chart_title.push(title);
|
|
||||||
chart_title.push('Ops/sec (' + platform + ')');
|
|
||||||
|
|
||||||
// Build labels
|
|
||||||
var labels = [jsl.toLabel(min), jsl.toLabel(max)];
|
|
||||||
|
|
||||||
var w = 250, bw = 15;
|
|
||||||
var bs = 5;
|
|
||||||
var h = markers.length*(bw + bs) + 30 + chart_title.length*20;
|
|
||||||
|
|
||||||
var params = {
|
|
||||||
chtt: escape(chart_title.join('|')),
|
|
||||||
chts: '000000,10',
|
|
||||||
cht: 'bhg', // chart type
|
|
||||||
chd: 't:' + data.join(','), // data set
|
|
||||||
chds: min + ',' + max, // max/min of data
|
|
||||||
chxt: 'x', // label axes
|
|
||||||
chxl: '0:|' + labels.join('|'), // labels
|
|
||||||
chsp: '0,1',
|
|
||||||
chm: markers.join('|'), // test names
|
|
||||||
chbh: [bw, 0, bs].join(','), // bar widths
|
|
||||||
// chf: 'bg,lg,0,eeeeee,0,eeeeee,.5,ffffff,1', // gradient
|
|
||||||
chs: w + 'x' + h
|
|
||||||
};
|
|
||||||
return 'http://chart.apis.google.com/chart?' + jsl.join(params, '=', '&');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
JSLitmus._init();
|
|
||||||
})();
|
|
||||||
236
vendor/underscore/test/vendor/qunit.css
vendored
236
vendor/underscore/test/vendor/qunit.css
vendored
@@ -1,236 +0,0 @@
|
|||||||
/**
|
|
||||||
* QUnit v1.8.0 - A JavaScript Unit Testing Framework
|
|
||||||
*
|
|
||||||
* http://docs.jquery.com/QUnit
|
|
||||||
*
|
|
||||||
* Copyright (c) 2012 John Resig, Jörn Zaefferer
|
|
||||||
* Dual licensed under the MIT (MIT-LICENSE.txt)
|
|
||||||
* or GPL (GPL-LICENSE.txt) licenses.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/** Font Family and Sizes */
|
|
||||||
|
|
||||||
#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
|
|
||||||
font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
|
|
||||||
#qunit-tests { font-size: smaller; }
|
|
||||||
|
|
||||||
|
|
||||||
/** Resets */
|
|
||||||
|
|
||||||
#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/** Header */
|
|
||||||
|
|
||||||
#qunit-header {
|
|
||||||
padding: 0.5em 0 0.5em 1em;
|
|
||||||
|
|
||||||
color: #8699a4;
|
|
||||||
background-color: #0d3349;
|
|
||||||
|
|
||||||
font-size: 1.5em;
|
|
||||||
line-height: 1em;
|
|
||||||
font-weight: normal;
|
|
||||||
|
|
||||||
border-radius: 15px 15px 0 0;
|
|
||||||
-moz-border-radius: 15px 15px 0 0;
|
|
||||||
-webkit-border-top-right-radius: 15px;
|
|
||||||
-webkit-border-top-left-radius: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-header a {
|
|
||||||
text-decoration: none;
|
|
||||||
color: #c2ccd1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-header a:hover,
|
|
||||||
#qunit-header a:focus {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-header label {
|
|
||||||
display: inline-block;
|
|
||||||
padding-left: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-banner {
|
|
||||||
height: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-testrunner-toolbar {
|
|
||||||
padding: 0.5em 0 0.5em 2em;
|
|
||||||
color: #5E740B;
|
|
||||||
background-color: #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-userAgent {
|
|
||||||
padding: 0.5em 0 0.5em 2.5em;
|
|
||||||
background-color: #2b81af;
|
|
||||||
color: #fff;
|
|
||||||
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/** Tests: Pass/Fail */
|
|
||||||
|
|
||||||
#qunit-tests {
|
|
||||||
list-style-position: inside;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-tests li {
|
|
||||||
padding: 0.4em 0.5em 0.4em 2.5em;
|
|
||||||
border-bottom: 1px solid #fff;
|
|
||||||
list-style-position: inside;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-tests li strong {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-tests li a {
|
|
||||||
padding: 0.5em;
|
|
||||||
color: #c2ccd1;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
#qunit-tests li a:hover,
|
|
||||||
#qunit-tests li a:focus {
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-tests ol {
|
|
||||||
margin-top: 0.5em;
|
|
||||||
padding: 0.5em;
|
|
||||||
|
|
||||||
background-color: #fff;
|
|
||||||
|
|
||||||
border-radius: 15px;
|
|
||||||
-moz-border-radius: 15px;
|
|
||||||
-webkit-border-radius: 15px;
|
|
||||||
|
|
||||||
box-shadow: inset 0px 2px 13px #999;
|
|
||||||
-moz-box-shadow: inset 0px 2px 13px #999;
|
|
||||||
-webkit-box-shadow: inset 0px 2px 13px #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-tests table {
|
|
||||||
border-collapse: collapse;
|
|
||||||
margin-top: .2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-tests th {
|
|
||||||
text-align: right;
|
|
||||||
vertical-align: top;
|
|
||||||
padding: 0 .5em 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-tests td {
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-tests pre {
|
|
||||||
margin: 0;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-tests del {
|
|
||||||
background-color: #e0f2be;
|
|
||||||
color: #374e0c;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-tests ins {
|
|
||||||
background-color: #ffcaca;
|
|
||||||
color: #500;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*** Test Counts */
|
|
||||||
|
|
||||||
#qunit-tests b.counts { color: black; }
|
|
||||||
#qunit-tests b.passed { color: #5E740B; }
|
|
||||||
#qunit-tests b.failed { color: #710909; }
|
|
||||||
|
|
||||||
#qunit-tests li li {
|
|
||||||
margin: 0.5em;
|
|
||||||
padding: 0.4em 0.5em 0.4em 0.5em;
|
|
||||||
background-color: #fff;
|
|
||||||
border-bottom: none;
|
|
||||||
list-style-position: inside;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*** Passing Styles */
|
|
||||||
|
|
||||||
#qunit-tests li li.pass {
|
|
||||||
color: #5E740B;
|
|
||||||
background-color: #fff;
|
|
||||||
border-left: 26px solid #C6E746;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
|
|
||||||
#qunit-tests .pass .test-name { color: #366097; }
|
|
||||||
|
|
||||||
#qunit-tests .pass .test-actual,
|
|
||||||
#qunit-tests .pass .test-expected { color: #999999; }
|
|
||||||
|
|
||||||
#qunit-banner.qunit-pass { background-color: #C6E746; }
|
|
||||||
|
|
||||||
/*** Failing Styles */
|
|
||||||
|
|
||||||
#qunit-tests li li.fail {
|
|
||||||
color: #710909;
|
|
||||||
background-color: #fff;
|
|
||||||
border-left: 26px solid #EE5757;
|
|
||||||
white-space: pre;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-tests > li:last-child {
|
|
||||||
border-radius: 0 0 15px 15px;
|
|
||||||
-moz-border-radius: 0 0 15px 15px;
|
|
||||||
-webkit-border-bottom-right-radius: 15px;
|
|
||||||
-webkit-border-bottom-left-radius: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#qunit-tests .fail { color: #000000; background-color: #EE5757; }
|
|
||||||
#qunit-tests .fail .test-name,
|
|
||||||
#qunit-tests .fail .module-name { color: #000000; }
|
|
||||||
|
|
||||||
#qunit-tests .fail .test-actual { color: #EE5757; }
|
|
||||||
#qunit-tests .fail .test-expected { color: green; }
|
|
||||||
|
|
||||||
#qunit-banner.qunit-fail { background-color: #EE5757; }
|
|
||||||
|
|
||||||
|
|
||||||
/** Result */
|
|
||||||
|
|
||||||
#qunit-testresult {
|
|
||||||
padding: 0.5em 0.5em 0.5em 2.5em;
|
|
||||||
|
|
||||||
color: #2b81af;
|
|
||||||
background-color: #D2E0E6;
|
|
||||||
|
|
||||||
border-bottom: 1px solid white;
|
|
||||||
}
|
|
||||||
#qunit-testresult .module-name {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Fixture */
|
|
||||||
|
|
||||||
#qunit-fixture {
|
|
||||||
position: absolute;
|
|
||||||
top: -10000px;
|
|
||||||
left: -10000px;
|
|
||||||
width: 1000px;
|
|
||||||
height: 1000px;
|
|
||||||
}
|
|
||||||
1863
vendor/underscore/test/vendor/qunit.js
vendored
1863
vendor/underscore/test/vendor/qunit.js
vendored
File diff suppressed because it is too large
Load Diff
5
vendor/underscore/underscore-min.js
vendored
Normal file
5
vendor/underscore/underscore-min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
540
vendor/underscore/underscore.js
vendored
540
vendor/underscore/underscore.js
vendored
@@ -1,10 +1,7 @@
|
|||||||
// Underscore.js 1.3.3
|
// Underscore.js 1.4.2
|
||||||
|
// http://underscorejs.org
|
||||||
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
|
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
|
||||||
// Underscore is freely distributable under the MIT license.
|
// Underscore may be freely distributed 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
|
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
|
|
||||||
@@ -26,6 +23,7 @@
|
|||||||
// Create quick reference variables for speed access to core prototypes.
|
// Create quick reference variables for speed access to core prototypes.
|
||||||
var push = ArrayProto.push,
|
var push = ArrayProto.push,
|
||||||
slice = ArrayProto.slice,
|
slice = ArrayProto.slice,
|
||||||
|
concat = ArrayProto.concat,
|
||||||
unshift = ArrayProto.unshift,
|
unshift = ArrayProto.unshift,
|
||||||
toString = ObjProto.toString,
|
toString = ObjProto.toString,
|
||||||
hasOwnProperty = ObjProto.hasOwnProperty;
|
hasOwnProperty = ObjProto.hasOwnProperty;
|
||||||
@@ -47,7 +45,11 @@
|
|||||||
nativeBind = FuncProto.bind;
|
nativeBind = FuncProto.bind;
|
||||||
|
|
||||||
// Create a safe reference to the Underscore object for use below.
|
// Create a safe reference to the Underscore object for use below.
|
||||||
var _ = function(obj) { return new wrapper(obj); };
|
var _ = function(obj) {
|
||||||
|
if (obj instanceof _) return obj;
|
||||||
|
if (!(this instanceof _)) return new _(obj);
|
||||||
|
this._wrapped = obj;
|
||||||
|
};
|
||||||
|
|
||||||
// Export the Underscore object for **Node.js**, with
|
// Export the Underscore object for **Node.js**, with
|
||||||
// backwards-compatibility for the old `require()` API. If we're in
|
// backwards-compatibility for the old `require()` API. If we're in
|
||||||
@@ -63,7 +65,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Current version.
|
// Current version.
|
||||||
_.VERSION = '1.3.3';
|
_.VERSION = '1.4.2';
|
||||||
|
|
||||||
// Collection Functions
|
// Collection Functions
|
||||||
// --------------------
|
// --------------------
|
||||||
@@ -128,11 +130,24 @@
|
|||||||
if (obj == null) obj = [];
|
if (obj == null) obj = [];
|
||||||
if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
|
if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
|
||||||
if (context) iterator = _.bind(iterator, context);
|
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();
|
var length = obj.length;
|
||||||
if (context && !initial) iterator = _.bind(iterator, context);
|
if (length !== +length) {
|
||||||
return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator);
|
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`.
|
// Return the first value which passes a truth test. Aliased as `detect`.
|
||||||
@@ -162,18 +177,16 @@
|
|||||||
|
|
||||||
// Return all the elements for which a truth test fails.
|
// Return all the elements for which a truth test fails.
|
||||||
_.reject = function(obj, iterator, context) {
|
_.reject = function(obj, iterator, context) {
|
||||||
var results = [];
|
return _.filter(obj, function(value, index, list) {
|
||||||
if (obj == null) return results;
|
return !iterator.call(context, value, index, list);
|
||||||
each(obj, function(value, index, list) {
|
}, context);
|
||||||
if (!iterator.call(context, value, index, list)) results[results.length] = value;
|
|
||||||
});
|
|
||||||
return results;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Determine whether all of the elements match a truth test.
|
// Determine whether all of the elements match a truth test.
|
||||||
// Delegates to **ECMAScript 5**'s native `every` if available.
|
// Delegates to **ECMAScript 5**'s native `every` if available.
|
||||||
// Aliased as `all`.
|
// Aliased as `all`.
|
||||||
_.every = _.all = function(obj, iterator, context) {
|
_.every = _.all = function(obj, iterator, context) {
|
||||||
|
iterator || (iterator = _.identity);
|
||||||
var result = true;
|
var result = true;
|
||||||
if (obj == null) return result;
|
if (obj == null) return result;
|
||||||
if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
|
if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
|
||||||
@@ -197,16 +210,14 @@
|
|||||||
return !!result;
|
return !!result;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Determine if a given value is included in the array or object using `===`.
|
// Determine if the array or object contains a given value (using `===`).
|
||||||
// Aliased as `contains`.
|
// Aliased as `include`.
|
||||||
_.include = _.contains = function(obj, target) {
|
_.contains = _.include = function(obj, target) {
|
||||||
var found = false;
|
if (obj == null) return false;
|
||||||
if (obj == null) return found;
|
|
||||||
if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
|
if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
|
||||||
found = any(obj, function(value) {
|
return any(obj, function(value) {
|
||||||
return value === target;
|
return value === target;
|
||||||
});
|
});
|
||||||
return found;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Invoke a method (with arguments) on every item in a collection.
|
// Invoke a method (with arguments) on every item in a collection.
|
||||||
@@ -222,6 +233,18 @@
|
|||||||
return _.map(obj, function(value){ return value[key]; });
|
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).
|
// Return the maximum element or (element-based computation).
|
||||||
// Can't optimize arrays of integers longer than 65,535 elements.
|
// Can't optimize arrays of integers longer than 65,535 elements.
|
||||||
// See: https://bugs.webkit.org/show_bug.cgi?id=80797
|
// See: https://bugs.webkit.org/show_bug.cgi?id=80797
|
||||||
@@ -258,66 +281,91 @@
|
|||||||
var index = 0;
|
var index = 0;
|
||||||
var shuffled = [];
|
var shuffled = [];
|
||||||
each(obj, function(value) {
|
each(obj, function(value) {
|
||||||
rand = Math.floor(Math.random() * ++index);
|
rand = _.random(index++);
|
||||||
shuffled[index - 1] = shuffled[rand];
|
shuffled[index - 1] = shuffled[rand];
|
||||||
shuffled[rand] = value;
|
shuffled[rand] = value;
|
||||||
});
|
});
|
||||||
return shuffled;
|
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.
|
// Sort the object's values by a criterion produced by an iterator.
|
||||||
_.sortBy = function(obj, val, context) {
|
_.sortBy = function(obj, value, context) {
|
||||||
var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; };
|
var iterator = lookupIterator(value);
|
||||||
return _.pluck(_.map(obj, function(value, index, list) {
|
return _.pluck(_.map(obj, function(value, index, list) {
|
||||||
return {
|
return {
|
||||||
value : value,
|
value : value,
|
||||||
|
index : index,
|
||||||
criteria : iterator.call(context, value, index, list)
|
criteria : iterator.call(context, value, index, list)
|
||||||
};
|
};
|
||||||
}).sort(function(left, right) {
|
}).sort(function(left, right) {
|
||||||
var a = left.criteria, b = right.criteria;
|
var a = left.criteria;
|
||||||
if (a === void 0) return 1;
|
var b = right.criteria;
|
||||||
if (b === void 0) return -1;
|
if (a !== b) {
|
||||||
return a < b ? -1 : a > b ? 1 : 0;
|
if (a > b || a === void 0) return 1;
|
||||||
|
if (a < b || b === void 0) return -1;
|
||||||
|
}
|
||||||
|
return left.index < right.index ? -1 : 1;
|
||||||
}), 'value');
|
}), 'value');
|
||||||
};
|
};
|
||||||
|
|
||||||
// Groups the object's values by a criterion. Pass either a string attribute
|
// An internal function used for aggregate "group by" operations.
|
||||||
// to group by, or a function that returns the criterion.
|
var group = function(obj, value, context, behavior) {
|
||||||
_.groupBy = function(obj, val) {
|
|
||||||
var result = {};
|
var result = {};
|
||||||
var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; };
|
var iterator = lookupIterator(value);
|
||||||
each(obj, function(value, index) {
|
each(obj, function(value, index) {
|
||||||
var key = iterator(value, index);
|
var key = iterator.call(context, value, index, obj);
|
||||||
(result[key] || (result[key] = [])).push(value);
|
behavior(result, key, value);
|
||||||
});
|
});
|
||||||
return result;
|
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
|
// 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.
|
// an object should be inserted so as to maintain order. Uses binary search.
|
||||||
_.sortedIndex = function(array, obj, iterator) {
|
_.sortedIndex = function(array, obj, iterator, context) {
|
||||||
iterator || (iterator = _.identity);
|
iterator = iterator == null ? _.identity : lookupIterator(iterator);
|
||||||
var value = iterator(obj);
|
var value = iterator.call(context, obj);
|
||||||
var low = 0, high = array.length;
|
var low = 0, high = array.length;
|
||||||
while (low < high) {
|
while (low < high) {
|
||||||
var mid = (low + high) >> 1;
|
var mid = (low + high) >>> 1;
|
||||||
iterator(array[mid]) < value ? low = mid + 1 : high = mid;
|
iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid;
|
||||||
}
|
}
|
||||||
return low;
|
return low;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Safely convert anything iterable into a real, live array.
|
// Safely convert anything iterable into a real, live array.
|
||||||
_.toArray = function(obj) {
|
_.toArray = function(obj) {
|
||||||
if (!obj) return [];
|
if (!obj) return [];
|
||||||
if (_.isArray(obj)) return slice.call(obj);
|
if (obj.length === +obj.length) return slice.call(obj);
|
||||||
if (_.isArguments(obj)) return slice.call(obj);
|
|
||||||
if (obj.toArray && _.isFunction(obj.toArray)) return obj.toArray();
|
|
||||||
return _.values(obj);
|
return _.values(obj);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Return the number of elements in an object.
|
// Return the number of elements in an object.
|
||||||
_.size = function(obj) {
|
_.size = function(obj) {
|
||||||
return _.isArray(obj) ? obj.length : _.keys(obj).length;
|
if (obj == null) return 0;
|
||||||
|
return (obj.length === +obj.length) ? obj.length : _.keys(obj).length;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Array Functions
|
// Array Functions
|
||||||
@@ -327,6 +375,7 @@
|
|||||||
// values in the array. Aliased as `head` and `take`. The **guard** check
|
// values in the array. Aliased as `head` and `take`. The **guard** check
|
||||||
// allows it to work with `_.map`.
|
// allows it to work with `_.map`.
|
||||||
_.first = _.head = _.take = function(array, n, guard) {
|
_.first = _.head = _.take = function(array, n, guard) {
|
||||||
|
if (array == null) return void 0;
|
||||||
return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
|
return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -341,6 +390,7 @@
|
|||||||
// Get the last element of an array. Passing **n** will return the last N
|
// Get the last element of an array. Passing **n** will return the last N
|
||||||
// values in the array. The **guard** check allows it to work with `_.map`.
|
// values in the array. The **guard** check allows it to work with `_.map`.
|
||||||
_.last = function(array, n, guard) {
|
_.last = function(array, n, guard) {
|
||||||
|
if (array == null) return void 0;
|
||||||
if ((n != null) && !guard) {
|
if ((n != null) && !guard) {
|
||||||
return slice.call(array, Math.max(array.length - n, 0));
|
return slice.call(array, Math.max(array.length - n, 0));
|
||||||
} else {
|
} else {
|
||||||
@@ -348,12 +398,12 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Returns everything but the first entry of the array. Aliased as `tail`.
|
// Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
|
||||||
// Especially useful on the arguments object. Passing an **index** will return
|
// Especially useful on the arguments object. Passing an **n** will return
|
||||||
// the rest of the values in the array from that index onward. The **guard**
|
// the rest N values in the array. The **guard**
|
||||||
// check allows it to work with `_.map`.
|
// check allows it to work with `_.map`.
|
||||||
_.rest = _.tail = function(array, index, guard) {
|
_.rest = _.tail = _.drop = function(array, n, guard) {
|
||||||
return slice.call(array, (index == null) || guard ? 1 : index);
|
return slice.call(array, (n == null) || guard ? 1 : n);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Trim out all falsy values from an array.
|
// Trim out all falsy values from an array.
|
||||||
@@ -386,23 +436,28 @@
|
|||||||
// Produce a duplicate-free version of the array. If the array has already
|
// Produce a duplicate-free version of the array. If the array has already
|
||||||
// been sorted, you have the option of using a faster algorithm.
|
// been sorted, you have the option of using a faster algorithm.
|
||||||
// Aliased as `unique`.
|
// Aliased as `unique`.
|
||||||
_.uniq = _.unique = function(array, isSorted, iterator) {
|
_.uniq = _.unique = function(array, isSorted, iterator, context) {
|
||||||
var initial = iterator ? _.map(array, iterator) : array;
|
if (_.isFunction(isSorted)) {
|
||||||
|
context = iterator;
|
||||||
|
iterator = isSorted;
|
||||||
|
isSorted = false;
|
||||||
|
}
|
||||||
|
var initial = iterator ? _.map(array, iterator, context) : array;
|
||||||
var results = [];
|
var results = [];
|
||||||
_.reduce(initial, function(memo, value, index) {
|
var seen = [];
|
||||||
if (isSorted ? (_.last(memo) !== value || !memo.length) : !_.include(memo, value)) {
|
each(initial, function(value, index) {
|
||||||
memo.push(value);
|
if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) {
|
||||||
|
seen.push(value);
|
||||||
results.push(array[index]);
|
results.push(array[index]);
|
||||||
}
|
}
|
||||||
return memo;
|
});
|
||||||
}, []);
|
|
||||||
return results;
|
return results;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Produce an array that contains the union: each distinct element from all of
|
// Produce an array that contains the union: each distinct element from all of
|
||||||
// the passed-in arrays.
|
// the passed-in arrays.
|
||||||
_.union = function() {
|
_.union = function() {
|
||||||
return _.uniq(flatten(arguments, true, []));
|
return _.uniq(concat.apply(ArrayProto, arguments));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Produce an array that contains every item shared between all the
|
// Produce an array that contains every item shared between all the
|
||||||
@@ -419,8 +474,8 @@
|
|||||||
// Take the difference between one array and a number of other arrays.
|
// Take the difference between one array and a number of other arrays.
|
||||||
// Only the elements present in just the first array will remain.
|
// Only the elements present in just the first array will remain.
|
||||||
_.difference = function(array) {
|
_.difference = function(array) {
|
||||||
var rest = flatten(slice.call(arguments, 1), true, []);
|
var rest = concat.apply(ArrayProto, slice.call(arguments, 1));
|
||||||
return _.filter(array, function(value){ return !_.include(rest, value); });
|
return _.filter(array, function(value){ return !_.contains(rest, value); });
|
||||||
};
|
};
|
||||||
|
|
||||||
// Zip together multiple lists into a single array -- elements that share
|
// Zip together multiple lists into a single array -- elements that share
|
||||||
@@ -435,12 +490,18 @@
|
|||||||
return results;
|
return results;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Zip together two arrays -- an array of keys and an array of values -- into
|
// Converts lists into objects. Pass either a single array of `[key, value]`
|
||||||
// a single object.
|
// pairs, or two parallel arrays of the same length -- one of keys, and one of
|
||||||
_.zipObject = function(keys, values) {
|
// the corresponding values.
|
||||||
|
_.object = function(list, values) {
|
||||||
|
if (list == null) return {};
|
||||||
var result = {};
|
var result = {};
|
||||||
for (var i = 0, l = keys.length; i < l; i++) {
|
for (var i = 0, l = list.length; i < l; i++) {
|
||||||
result[keys[i]] = values[i];
|
if (values) {
|
||||||
|
result[list[i]] = values[i];
|
||||||
|
} else {
|
||||||
|
result[list[i][0]] = list[i][1];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
@@ -453,21 +514,28 @@
|
|||||||
// for **isSorted** to use binary search.
|
// for **isSorted** to use binary search.
|
||||||
_.indexOf = function(array, item, isSorted) {
|
_.indexOf = function(array, item, isSorted) {
|
||||||
if (array == null) return -1;
|
if (array == null) return -1;
|
||||||
var i, l;
|
var i = 0, l = array.length;
|
||||||
if (isSorted) {
|
if (isSorted) {
|
||||||
i = _.sortedIndex(array, item);
|
if (typeof isSorted == 'number') {
|
||||||
return array[i] === item ? i : -1;
|
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);
|
if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted);
|
||||||
for (i = 0, l = array.length; i < l; i++) if (array[i] === item) return i;
|
for (; i < l; i++) if (array[i] === item) return i;
|
||||||
return -1;
|
return -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
|
// Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
|
||||||
_.lastIndexOf = function(array, item) {
|
_.lastIndexOf = function(array, item, from) {
|
||||||
if (array == null) return -1;
|
if (array == null) return -1;
|
||||||
if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item);
|
var hasIndex = from != null;
|
||||||
var i = array.length;
|
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;
|
while (i--) if (array[i] === item) return i;
|
||||||
return -1;
|
return -1;
|
||||||
};
|
};
|
||||||
@@ -554,23 +622,25 @@
|
|||||||
// Returns a function, that, when invoked, will only be triggered at most once
|
// Returns a function, that, when invoked, will only be triggered at most once
|
||||||
// during a given window of time.
|
// during a given window of time.
|
||||||
_.throttle = function(func, wait) {
|
_.throttle = function(func, wait) {
|
||||||
var context, args, timeout, throttling, more, result;
|
var context, args, timeout, result;
|
||||||
var whenDone = _.debounce(function(){ more = throttling = false; }, wait);
|
var previous = 0;
|
||||||
|
var later = function() {
|
||||||
|
previous = new Date;
|
||||||
|
timeout = null;
|
||||||
|
result = func.apply(context, args);
|
||||||
|
};
|
||||||
return function() {
|
return function() {
|
||||||
context = this; args = arguments;
|
var now = new Date;
|
||||||
var later = function() {
|
var remaining = wait - (now - previous);
|
||||||
timeout = null;
|
context = this;
|
||||||
if (more) func.apply(context, args);
|
args = arguments;
|
||||||
whenDone();
|
if (remaining <= 0) {
|
||||||
};
|
clearTimeout(timeout);
|
||||||
if (!timeout) timeout = setTimeout(later, wait);
|
previous = now;
|
||||||
if (throttling) {
|
|
||||||
more = true;
|
|
||||||
} else {
|
|
||||||
throttling = true;
|
|
||||||
result = func.apply(context, args);
|
result = func.apply(context, args);
|
||||||
|
} else if (!timeout) {
|
||||||
|
timeout = setTimeout(later, remaining);
|
||||||
}
|
}
|
||||||
whenDone();
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -580,17 +650,18 @@
|
|||||||
// N milliseconds. If `immediate` is passed, trigger the function on the
|
// N milliseconds. If `immediate` is passed, trigger the function on the
|
||||||
// leading edge, instead of the trailing.
|
// leading edge, instead of the trailing.
|
||||||
_.debounce = function(func, wait, immediate) {
|
_.debounce = function(func, wait, immediate) {
|
||||||
var timeout;
|
var timeout, result;
|
||||||
return function() {
|
return function() {
|
||||||
var context = this, args = arguments;
|
var context = this, args = arguments;
|
||||||
var later = function() {
|
var later = function() {
|
||||||
timeout = null;
|
timeout = null;
|
||||||
if (!immediate) func.apply(context, args);
|
if (!immediate) result = func.apply(context, args);
|
||||||
};
|
};
|
||||||
var callNow = immediate && !timeout;
|
var callNow = immediate && !timeout;
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
timeout = setTimeout(later, wait);
|
timeout = setTimeout(later, wait);
|
||||||
if (callNow) func.apply(context, args);
|
if (callNow) result = func.apply(context, args);
|
||||||
|
return result;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -601,7 +672,9 @@
|
|||||||
return function() {
|
return function() {
|
||||||
if (ran) return memo;
|
if (ran) return memo;
|
||||||
ran = true;
|
ran = true;
|
||||||
return memo = func.apply(this, arguments);
|
memo = func.apply(this, arguments);
|
||||||
|
func = null;
|
||||||
|
return memo;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -610,7 +683,8 @@
|
|||||||
// conditionally execute the original function.
|
// conditionally execute the original function.
|
||||||
_.wrap = function(func, wrapper) {
|
_.wrap = function(func, wrapper) {
|
||||||
return function() {
|
return function() {
|
||||||
var args = [func].concat(slice.call(arguments, 0));
|
var args = [func];
|
||||||
|
push.apply(args, arguments);
|
||||||
return wrapper.apply(this, args);
|
return wrapper.apply(this, args);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -652,7 +726,23 @@
|
|||||||
|
|
||||||
// Retrieve the values of an object's properties.
|
// Retrieve the values of an object's properties.
|
||||||
_.values = function(obj) {
|
_.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.
|
// Return a sorted list of the function names available on the object.
|
||||||
@@ -677,11 +767,22 @@
|
|||||||
|
|
||||||
// Return a copy of the object only containing the whitelisted properties.
|
// Return a copy of the object only containing the whitelisted properties.
|
||||||
_.pick = function(obj) {
|
_.pick = function(obj) {
|
||||||
var result = {};
|
var copy = {};
|
||||||
each(flatten(slice.call(arguments, 1), true, []), function(key) {
|
var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
|
||||||
if (key in obj) result[key] = obj[key];
|
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.
|
// Fill in a given object with default properties.
|
||||||
@@ -709,18 +810,15 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Internal recursive comparison function for `isEqual`.
|
// 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.
|
// Identical objects are equal. `0 === -0`, but they aren't identical.
|
||||||
// See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
|
// See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
|
||||||
if (a === b) return a !== 0 || 1 / a == 1 / b;
|
if (a === b) return a !== 0 || 1 / a == 1 / b;
|
||||||
// A strict comparison is necessary because `null == undefined`.
|
// A strict comparison is necessary because `null == undefined`.
|
||||||
if (a == null || b == null) return a === b;
|
if (a == null || b == null) return a === b;
|
||||||
// Unwrap any wrapped objects.
|
// Unwrap any wrapped objects.
|
||||||
if (a._chain) a = a._wrapped;
|
if (a instanceof _) a = a._wrapped;
|
||||||
if (b._chain) b = b._wrapped;
|
if (b instanceof _) b = b._wrapped;
|
||||||
// Invoke a custom `isEqual` method if one is provided.
|
|
||||||
if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b);
|
|
||||||
if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a);
|
|
||||||
// Compare `[[Class]]` names.
|
// Compare `[[Class]]` names.
|
||||||
var className = toString.call(a);
|
var className = toString.call(a);
|
||||||
if (className != toString.call(b)) return false;
|
if (className != toString.call(b)) return false;
|
||||||
@@ -750,14 +848,15 @@
|
|||||||
if (typeof a != 'object' || typeof b != 'object') return false;
|
if (typeof a != 'object' || typeof b != 'object') return false;
|
||||||
// Assume equality for cyclic structures. The algorithm for detecting cyclic
|
// Assume equality for cyclic structures. The algorithm for detecting cyclic
|
||||||
// structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
|
// structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
|
||||||
var length = stack.length;
|
var length = aStack.length;
|
||||||
while (length--) {
|
while (length--) {
|
||||||
// Linear search. Performance is inversely proportional to the number of
|
// Linear search. Performance is inversely proportional to the number of
|
||||||
// unique nested structures.
|
// unique nested structures.
|
||||||
if (stack[length] == a) return true;
|
if (aStack[length] == a) return bStack[length] == b;
|
||||||
}
|
}
|
||||||
// Add the first object to the stack of traversed objects.
|
// Add the first object to the stack of traversed objects.
|
||||||
stack.push(a);
|
aStack.push(a);
|
||||||
|
bStack.push(b);
|
||||||
var size = 0, result = true;
|
var size = 0, result = true;
|
||||||
// Recursively compare objects and arrays.
|
// Recursively compare objects and arrays.
|
||||||
if (className == '[object Array]') {
|
if (className == '[object Array]') {
|
||||||
@@ -767,20 +866,24 @@
|
|||||||
if (result) {
|
if (result) {
|
||||||
// Deep compare the contents, ignoring non-numeric properties.
|
// Deep compare the contents, ignoring non-numeric properties.
|
||||||
while (size--) {
|
while (size--) {
|
||||||
// Ensure commutative equality for sparse arrays.
|
if (!(result = eq(a[size], b[size], aStack, bStack))) break;
|
||||||
if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Objects with different constructors are not equivalent.
|
// Objects with different constructors are not equivalent, but `Object`s
|
||||||
if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false;
|
// 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.
|
// Deep compare objects.
|
||||||
for (var key in a) {
|
for (var key in a) {
|
||||||
if (_.has(a, key)) {
|
if (_.has(a, key)) {
|
||||||
// Count the expected number of properties.
|
// Count the expected number of properties.
|
||||||
size++;
|
size++;
|
||||||
// Deep compare each member.
|
// Deep compare each member.
|
||||||
if (!(result = _.has(b, key) && eq(a[key], b[key], stack))) break;
|
if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Ensure that both objects contain the same number of properties.
|
// Ensure that both objects contain the same number of properties.
|
||||||
@@ -792,13 +895,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Remove the first object from the stack of traversed objects.
|
// Remove the first object from the stack of traversed objects.
|
||||||
stack.pop();
|
aStack.pop();
|
||||||
|
bStack.pop();
|
||||||
return result;
|
return result;
|
||||||
}
|
};
|
||||||
|
|
||||||
// Perform a deep comparison to check if two objects are equal.
|
// Perform a deep comparison to check if two objects are equal.
|
||||||
_.isEqual = function(a, b) {
|
_.isEqual = function(a, b) {
|
||||||
return eq(a, b, []);
|
return eq(a, b, [], []);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Is a given array, string, or object empty?
|
// Is a given array, string, or object empty?
|
||||||
@@ -812,7 +916,7 @@
|
|||||||
|
|
||||||
// Is a given value a DOM element?
|
// Is a given value a DOM element?
|
||||||
_.isElement = function(obj) {
|
_.isElement = function(obj) {
|
||||||
return !!(obj && obj.nodeType == 1);
|
return !!(obj && obj.nodeType === 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Is a given value an array?
|
// Is a given value an array?
|
||||||
@@ -832,7 +936,7 @@
|
|||||||
return toString.call(obj) == '[object ' + name + ']';
|
return toString.call(obj) == '[object ' + name + ']';
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// Define a fallback version of the method in browsers (ahem, IE), where
|
// Define a fallback version of the method in browsers (ahem, IE), where
|
||||||
// there isn't any inspectable "Arguments" type.
|
// there isn't any inspectable "Arguments" type.
|
||||||
if (!_.isArguments(arguments)) {
|
if (!_.isArguments(arguments)) {
|
||||||
@@ -841,15 +945,21 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Optimize `isFunction` if appropriate.
|
||||||
|
if (typeof (/./) !== 'function') {
|
||||||
|
_.isFunction = function(obj) {
|
||||||
|
return typeof obj === 'function';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Is a given object a finite number?
|
// Is a given object a finite number?
|
||||||
_.isFinite = function(obj) {
|
_.isFinite = function(obj) {
|
||||||
return _.isNumber(obj) && isFinite(obj);
|
return isFinite( obj ) && !isNaN( parseFloat(obj) );
|
||||||
};
|
};
|
||||||
|
|
||||||
// Is the given value `NaN`?
|
// Is the given value `NaN`? (NaN is the only number which does not equal itself).
|
||||||
_.isNaN = function(obj) {
|
_.isNaN = function(obj) {
|
||||||
// `NaN` is the only value for which `===` is not reflexive.
|
return _.isNumber(obj) && obj != +obj;
|
||||||
return obj !== obj;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Is a given value a boolean?
|
// Is a given value a boolean?
|
||||||
@@ -893,25 +1003,43 @@
|
|||||||
for (var i = 0; i < n; i++) iterator.call(context, i);
|
for (var i = 0; i < n; i++) iterator.call(context, i);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Return a random integer between min and max (inclusive).
|
||||||
|
_.random = function(min, max) {
|
||||||
|
if (max == null) {
|
||||||
|
max = min;
|
||||||
|
min = 0;
|
||||||
|
}
|
||||||
|
return min + (0 | Math.random() * (max - min + 1));
|
||||||
|
};
|
||||||
|
|
||||||
// List of HTML entities for escaping.
|
// List of HTML entities for escaping.
|
||||||
var htmlEscapes = {
|
var entityMap = {
|
||||||
'&': '&',
|
escape: {
|
||||||
'<': '<',
|
'&': '&',
|
||||||
'>': '>',
|
'<': '<',
|
||||||
'"': '"',
|
'>': '>',
|
||||||
"'": ''',
|
'"': '"',
|
||||||
'/': '/'
|
"'": ''',
|
||||||
|
'/': '/'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
entityMap.unescape = _.invert(entityMap.escape);
|
||||||
|
|
||||||
|
// Regexes containing the keys and values listed immediately above.
|
||||||
|
var entityRegexes = {
|
||||||
|
escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'),
|
||||||
|
unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g')
|
||||||
};
|
};
|
||||||
|
|
||||||
// Regex containing the keys listed immediately above.
|
// Functions for escaping and unescaping strings to/from HTML interpolation.
|
||||||
var htmlEscaper = /[&<>"'\/]/g;
|
_.each(['escape', 'unescape'], function(method) {
|
||||||
|
_[method] = function(string) {
|
||||||
// Escape a string for HTML interpolation.
|
if (string == null) return '';
|
||||||
_.escape = function(string) {
|
return ('' + string).replace(entityRegexes[method], function(match) {
|
||||||
return ('' + string).replace(htmlEscaper, function(match) {
|
return entityMap[method][match];
|
||||||
return htmlEscapes[match];
|
});
|
||||||
});
|
};
|
||||||
};
|
});
|
||||||
|
|
||||||
// If the value of the named property is a function then invoke it;
|
// If the value of the named property is a function then invoke it;
|
||||||
// otherwise, return it.
|
// otherwise, return it.
|
||||||
@@ -921,11 +1049,15 @@
|
|||||||
return _.isFunction(value) ? value.call(object) : value;
|
return _.isFunction(value) ? value.call(object) : value;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add your own custom functions to the Underscore object, ensuring that
|
// Add your own custom functions to the Underscore object.
|
||||||
// they're correctly added to the OOP wrapper as well.
|
|
||||||
_.mixin = function(obj) {
|
_.mixin = function(obj) {
|
||||||
each(_.functions(obj), function(name){
|
each(_.functions(obj), function(name){
|
||||||
addToWrapper(name, _[name] = obj[name]);
|
var func = _[name] = obj[name];
|
||||||
|
_.prototype[name] = function() {
|
||||||
|
var args = [this._wrapped];
|
||||||
|
push.apply(args, arguments);
|
||||||
|
return result.call(this, func.apply(_, args));
|
||||||
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -948,63 +1080,63 @@
|
|||||||
// When customizing `templateSettings`, if you don't want to define an
|
// When customizing `templateSettings`, if you don't want to define an
|
||||||
// interpolation, evaluation or escaping regex, we need one that is
|
// interpolation, evaluation or escaping regex, we need one that is
|
||||||
// guaranteed not to match.
|
// guaranteed not to match.
|
||||||
var noMatch = /.^/;
|
var noMatch = /(.)^/;
|
||||||
|
|
||||||
// Certain characters need to be escaped so that they can be put into a
|
// Certain characters need to be escaped so that they can be put into a
|
||||||
// string literal.
|
// string literal.
|
||||||
var escapes = {
|
var escapes = {
|
||||||
'\\': '\\',
|
"'": "'",
|
||||||
"'": "'",
|
'\\': '\\',
|
||||||
r: '\r',
|
'\r': 'r',
|
||||||
n: '\n',
|
'\n': 'n',
|
||||||
t: '\t',
|
'\t': 't',
|
||||||
u2028: '\u2028',
|
'\u2028': 'u2028',
|
||||||
u2029: '\u2029'
|
'\u2029': 'u2029'
|
||||||
};
|
};
|
||||||
|
|
||||||
for (var key in escapes) escapes[escapes[key]] = key;
|
|
||||||
var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
|
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.
|
// JavaScript micro-templating, similar to John Resig's implementation.
|
||||||
// Underscore templating handles arbitrary delimiters, preserves whitespace,
|
// Underscore templating handles arbitrary delimiters, preserves whitespace,
|
||||||
// and correctly escapes quotes within interpolated code.
|
// and correctly escapes quotes within interpolated code.
|
||||||
_.template = function(text, data, settings) {
|
_.template = function(text, data, settings) {
|
||||||
settings = _.defaults(settings || {}, _.templateSettings);
|
settings = _.defaults({}, settings, _.templateSettings);
|
||||||
|
|
||||||
// Compile the template source, taking care to escape characters that
|
// Combine delimiters into one regular expression via alternation.
|
||||||
// cannot be included in a string literal and then unescape them in code
|
var matcher = new RegExp([
|
||||||
// blocks.
|
(settings.escape || noMatch).source,
|
||||||
var source = "__p+='" + text
|
(settings.interpolate || noMatch).source,
|
||||||
.replace(escaper, function(match) {
|
(settings.evaluate || noMatch).source
|
||||||
return '\\' + escapes[match];
|
].join('|') + '|$', 'g');
|
||||||
})
|
|
||||||
.replace(settings.escape || noMatch, function(match, code) {
|
// Compile the template source, escaping string literals appropriately.
|
||||||
return "'+\n((__t=(" + unescape(code) + "))==null?'':_.escape(__t))+\n'";
|
var index = 0;
|
||||||
})
|
var source = "__p+='";
|
||||||
.replace(settings.interpolate || noMatch, function(match, code) {
|
text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
|
||||||
return "'+\n((__t=(" + unescape(code) + "))==null?'':__t)+\n'";
|
source += text.slice(index, offset)
|
||||||
})
|
.replace(escaper, function(match) { return '\\' + escapes[match]; });
|
||||||
.replace(settings.evaluate || noMatch, function(match, code) {
|
source +=
|
||||||
return "';\n" + unescape(code) + "\n__p+='";
|
escape ? "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'" :
|
||||||
}) + "';\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 a variable is not specified, place data values in local scope.
|
||||||
if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
|
if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
|
||||||
|
|
||||||
source = "var __t,__p='',__j=Array.prototype.join," +
|
source = "var __t,__p='',__j=Array.prototype.join," +
|
||||||
"print=function(){__p+=__j.call(arguments,'')};\n" +
|
"print=function(){__p+=__j.call(arguments,'');};\n" +
|
||||||
source + "return __p;\n";
|
source + "return __p;\n";
|
||||||
|
|
||||||
var render = new Function(settings.variable || 'obj', '_', source);
|
try {
|
||||||
|
var render = new Function(settings.variable || 'obj', '_', source);
|
||||||
|
} catch (e) {
|
||||||
|
e.source = source;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
if (data) return render(data, _);
|
if (data) return render(data, _);
|
||||||
var template = function(data) {
|
var template = function(data) {
|
||||||
return render.call(this, data, _);
|
return render.call(this, data, _);
|
||||||
@@ -1021,29 +1153,15 @@
|
|||||||
return _(obj).chain();
|
return _(obj).chain();
|
||||||
};
|
};
|
||||||
|
|
||||||
// The OOP Wrapper
|
// OOP
|
||||||
// ---------------
|
// ---------------
|
||||||
|
|
||||||
// If Underscore is called as a function, it returns a wrapped object that
|
// If Underscore is called as a function, it returns a wrapped object that
|
||||||
// can be used OO-style. This wrapper holds altered versions of all the
|
// can be used OO-style. This wrapper holds altered versions of all the
|
||||||
// underscore functions. Wrapped objects may be chained.
|
// underscore functions. Wrapped objects may be chained.
|
||||||
var wrapper = function(obj) { this._wrapped = obj; };
|
|
||||||
|
|
||||||
// Expose `wrapper.prototype` as `_.prototype`
|
|
||||||
_.prototype = wrapper.prototype;
|
|
||||||
|
|
||||||
// Helper function to continue chaining intermediate results.
|
// Helper function to continue chaining intermediate results.
|
||||||
var result = function(obj, chain) {
|
var result = function(obj) {
|
||||||
return chain ? _(obj).chain() : obj;
|
return this._chain ? _(obj).chain() : obj;
|
||||||
};
|
|
||||||
|
|
||||||
// A method to easily add functions to the OOP wrapper.
|
|
||||||
var addToWrapper = function(name, func) {
|
|
||||||
wrapper.prototype[name] = function() {
|
|
||||||
var args = slice.call(arguments);
|
|
||||||
unshift.call(args, this._wrapped);
|
|
||||||
return result(func.apply(_, args), this._chain);
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add all of the Underscore functions to the wrapper object.
|
// Add all of the Underscore functions to the wrapper object.
|
||||||
@@ -1052,31 +1170,35 @@
|
|||||||
// Add all mutator Array functions to the wrapper.
|
// Add all mutator Array functions to the wrapper.
|
||||||
each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
|
each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
|
||||||
var method = ArrayProto[name];
|
var method = ArrayProto[name];
|
||||||
wrapper.prototype[name] = function() {
|
_.prototype[name] = function() {
|
||||||
var obj = this._wrapped;
|
var obj = this._wrapped;
|
||||||
method.apply(obj, arguments);
|
method.apply(obj, arguments);
|
||||||
if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0];
|
if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0];
|
||||||
return result(obj, this._chain);
|
return result.call(this, obj);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add all accessor Array functions to the wrapper.
|
// Add all accessor Array functions to the wrapper.
|
||||||
each(['concat', 'join', 'slice'], function(name) {
|
each(['concat', 'join', 'slice'], function(name) {
|
||||||
var method = ArrayProto[name];
|
var method = ArrayProto[name];
|
||||||
wrapper.prototype[name] = function() {
|
_.prototype[name] = function() {
|
||||||
return result(method.apply(this._wrapped, arguments), this._chain);
|
return result.call(this, method.apply(this._wrapped, arguments));
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// Start chaining a wrapped Underscore object.
|
_.extend(_.prototype, {
|
||||||
wrapper.prototype.chain = function() {
|
|
||||||
this._chain = true;
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Extracts the result from a wrapped and chained object.
|
// Start chaining a wrapped Underscore object.
|
||||||
wrapper.prototype.value = function() {
|
chain: function() {
|
||||||
return this._wrapped;
|
this._chain = true;
|
||||||
};
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Extracts the result from a wrapped and chained object.
|
||||||
|
value: function() {
|
||||||
|
return this._wrapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
}).call(this);
|
}).call(this);
|
||||||
|
|||||||
Reference in New Issue
Block a user