├── .gitignore ├── .travis.yml ├── CNAME ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── Rakefile ├── docs ├── docco.css ├── favicon.ico ├── images │ ├── background.png │ └── underscore.png ├── public │ ├── fonts │ │ ├── aller-bold.eot │ │ ├── aller-bold.ttf │ │ ├── aller-bold.woff │ │ ├── aller-light.eot │ │ ├── aller-light.ttf │ │ ├── aller-light.woff │ │ ├── novecento-bold.eot │ │ ├── novecento-bold.ttf │ │ └── novecento-bold.woff │ └── stylesheets │ │ └── normalize.css └── underscore.html ├── favicon.ico ├── index.html ├── package.json ├── test ├── arrays.js ├── chaining.js ├── collections.js ├── functions.js ├── index.html ├── objects.js ├── speed.js ├── utility.js └── vendor │ ├── jquery.js │ ├── jslitmus.js │ ├── qunit.css │ ├── qunit.js │ └── runner.js ├── underscore-min.js ├── underscore-min.map └── underscore.js /.gitignore: -------------------------------------------------------------------------------- 1 | raw 2 | node_modules 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.8 4 | notifications: 5 | email: false 6 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | underscorejs.org 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## How to contribute to Underscore.js 2 | 3 | * Before you open a ticket or send a pull request, [search](https://github.com/jashkenas/underscore/issues) for previous discussions about the same feature or issue. Add to the earlier ticket if you find one. 4 | 5 | * Before sending a pull request for a feature, be sure to have [tests](http://underscorejs.org/test/). 6 | 7 | * Use the same coding style as the rest of the [codebase](https://github.com/jashkenas/underscore/blob/master/underscore.js). 8 | 9 | * In your pull request, do not add documentation or re-build the minified `underscore-min.js` file. We'll do those things before cutting a new release. 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative 2 | Reporters & Editors 3 | 4 | Permission is hereby granted, free of charge, to any person 5 | obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without 7 | restriction, including without limitation the rights to use, 8 | copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | OTHER DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | __ 2 | /\ \ __ 3 | __ __ ___ \_\ \ __ _ __ ____ ___ ___ _ __ __ /\_\ ____ 4 | /\ \/\ \ /' _ `\ /'_ \ /'__`\/\ __\/ ,__\ / ___\ / __`\/\ __\/'__`\ \/\ \ /',__\ 5 | \ \ \_\ \/\ \/\ \/\ \ \ \/\ __/\ \ \//\__, `\/\ \__//\ \ \ \ \ \//\ __/ __ \ \ \/\__, `\ 6 | \ \____/\ \_\ \_\ \___,_\ \____\\ \_\\/\____/\ \____\ \____/\ \_\\ \____\/\_\ _\ \ \/\____/ 7 | \/___/ \/_/\/_/\/__,_ /\/____/ \/_/ \/___/ \/____/\/___/ \/_/ \/____/\/_//\ \_\ \/___/ 8 | \ \____/ 9 | \/___/ 10 | 11 | Underscore.js is a utility-belt library for JavaScript that provides 12 | support for the usual functional suspects (each, map, reduce, filter...) 13 | without extending any core JavaScript objects. 14 | 15 | For Docs, License, Tests, and pre-packed downloads, see: 16 | http://underscorejs.org 17 | 18 | Underscore is an open-sourced component of DocumentCloud: 19 | https://github.com/documentcloud 20 | 21 | Many thanks to our contributors: 22 | https://github.com/jashkenas/underscore/contributors 23 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | desc "Use Uglify JS to compress Underscore.js" 2 | task :build do 3 | sh "uglifyjs underscore.js -c \"evaluate=false\" --comments \"/ .*/\" -m --source-map underscore-min.map -o underscore-min.js" 4 | end 5 | 6 | desc "Build the docco documentation" 7 | task :doc do 8 | sh "docco underscore.js" 9 | end 10 | 11 | -------------------------------------------------------------------------------- /docs/docco.css: -------------------------------------------------------------------------------- 1 | /*--------------------- Typography ----------------------------*/ 2 | 3 | @font-face { 4 | font-family: 'aller-light'; 5 | src: url('public/fonts/aller-light.eot'); 6 | src: url('public/fonts/aller-light.eot?#iefix') format('embedded-opentype'), 7 | url('public/fonts/aller-light.woff') format('woff'), 8 | url('public/fonts/aller-light.ttf') format('truetype'); 9 | font-weight: normal; 10 | font-style: normal; 11 | } 12 | 13 | @font-face { 14 | font-family: 'aller-bold'; 15 | src: url('public/fonts/aller-bold.eot'); 16 | src: url('public/fonts/aller-bold.eot?#iefix') format('embedded-opentype'), 17 | url('public/fonts/aller-bold.woff') format('woff'), 18 | url('public/fonts/aller-bold.ttf') format('truetype'); 19 | font-weight: normal; 20 | font-style: normal; 21 | } 22 | 23 | @font-face { 24 | font-family: 'novecento-bold'; 25 | src: url('public/fonts/novecento-bold.eot'); 26 | src: url('public/fonts/novecento-bold.eot?#iefix') format('embedded-opentype'), 27 | url('public/fonts/novecento-bold.woff') format('woff'), 28 | url('public/fonts/novecento-bold.ttf') format('truetype'); 29 | font-weight: normal; 30 | font-style: normal; 31 | } 32 | 33 | /*--------------------- Layout ----------------------------*/ 34 | html { height: 100%; } 35 | body { 36 | font-family: "aller-light"; 37 | font-size: 14px; 38 | line-height: 18px; 39 | color: #30404f; 40 | margin: 0; padding: 0; 41 | height:100%; 42 | } 43 | #container { min-height: 100%; } 44 | 45 | a { 46 | color: #000; 47 | } 48 | 49 | b, strong { 50 | font-weight: normal; 51 | font-family: "aller-bold"; 52 | } 53 | 54 | p, ul, ol { 55 | margin: 15px 0 0px; 56 | } 57 | 58 | h1, h2, h3, h4, h5, h6 { 59 | color: #112233; 60 | line-height: 1em; 61 | font-weight: normal; 62 | font-family: "novecento-bold"; 63 | text-transform: uppercase; 64 | margin: 30px 0 15px 0; 65 | } 66 | 67 | h1 { 68 | margin-top: 40px; 69 | } 70 | 71 | hr { 72 | border: 0; 73 | background: 1px solid #ddd; 74 | height: 1px; 75 | margin: 20px 0; 76 | } 77 | 78 | pre, tt, code { 79 | font-size: 12px; line-height: 16px; 80 | font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace; 81 | margin: 0; padding: 0; 82 | } 83 | .annotation pre { 84 | display: block; 85 | margin: 0; 86 | padding: 7px 10px; 87 | background: #fcfcfc; 88 | -moz-box-shadow: inset 0 0 10px rgba(0,0,0,0.1); 89 | -webkit-box-shadow: inset 0 0 10px rgba(0,0,0,0.1); 90 | box-shadow: inset 0 0 10px rgba(0,0,0,0.1); 91 | overflow-x: auto; 92 | } 93 | .annotation pre code { 94 | border: 0; 95 | padding: 0; 96 | background: transparent; 97 | } 98 | 99 | 100 | blockquote { 101 | border-left: 5px solid #ccc; 102 | margin: 0; 103 | padding: 1px 0 1px 1em; 104 | } 105 | .sections blockquote p { 106 | font-family: Menlo, Consolas, Monaco, monospace; 107 | font-size: 12px; line-height: 16px; 108 | color: #999; 109 | margin: 10px 0 0; 110 | white-space: pre-wrap; 111 | } 112 | 113 | ul.sections { 114 | list-style: none; 115 | padding:0 0 5px 0;; 116 | margin:0; 117 | } 118 | 119 | /* 120 | Force border-box so that % widths fit the parent 121 | container without overlap because of margin/padding. 122 | 123 | More Info : http://www.quirksmode.org/css/box.html 124 | */ 125 | ul.sections > li > div { 126 | -moz-box-sizing: border-box; /* firefox */ 127 | -ms-box-sizing: border-box; /* ie */ 128 | -webkit-box-sizing: border-box; /* webkit */ 129 | -khtml-box-sizing: border-box; /* konqueror */ 130 | box-sizing: border-box; /* css3 */ 131 | } 132 | 133 | 134 | /*---------------------- Jump Page -----------------------------*/ 135 | #jump_to, #jump_page { 136 | margin: 0; 137 | background: white; 138 | -webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777; 139 | -webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px; 140 | font: 16px Arial; 141 | cursor: pointer; 142 | text-align: right; 143 | list-style: none; 144 | } 145 | 146 | #jump_to a { 147 | text-decoration: none; 148 | } 149 | 150 | #jump_to a.large { 151 | display: none; 152 | } 153 | #jump_to a.small { 154 | font-size: 22px; 155 | font-weight: bold; 156 | color: #676767; 157 | } 158 | 159 | #jump_to, #jump_wrapper { 160 | position: fixed; 161 | right: 0; top: 0; 162 | padding: 10px 15px; 163 | margin:0; 164 | } 165 | 166 | #jump_wrapper { 167 | display: none; 168 | padding:0; 169 | } 170 | 171 | #jump_to:hover #jump_wrapper { 172 | display: block; 173 | } 174 | 175 | #jump_page { 176 | padding: 5px 0 3px; 177 | margin: 0 0 25px 25px; 178 | } 179 | 180 | #jump_page .source { 181 | display: block; 182 | padding: 15px; 183 | text-decoration: none; 184 | border-top: 1px solid #eee; 185 | } 186 | 187 | #jump_page .source:hover { 188 | background: #f5f5ff; 189 | } 190 | 191 | #jump_page .source:first-child { 192 | } 193 | 194 | /*---------------------- Low resolutions (> 320px) ---------------------*/ 195 | @media only screen and (min-width: 320px) { 196 | .pilwrap { display: none; } 197 | 198 | ul.sections > li > div { 199 | display: block; 200 | padding:5px 10px 0 10px; 201 | } 202 | 203 | ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol { 204 | padding-left: 30px; 205 | } 206 | 207 | ul.sections > li > div.content { 208 | background: #f5f5ff; 209 | overflow-x:auto; 210 | -webkit-box-shadow: inset 0 0 5px #e5e5ee; 211 | box-shadow: inset 0 0 5px #e5e5ee; 212 | border: 1px solid #dedede; 213 | margin:5px 10px 5px 10px; 214 | padding-bottom: 5px; 215 | } 216 | 217 | ul.sections > li > div.annotation pre { 218 | margin: 7px 0 7px; 219 | padding-left: 15px; 220 | } 221 | 222 | ul.sections > li > div.annotation p tt, .annotation code { 223 | background: #f8f8ff; 224 | border: 1px solid #dedede; 225 | font-size: 12px; 226 | padding: 0 0.2em; 227 | } 228 | } 229 | 230 | /*---------------------- (> 481px) ---------------------*/ 231 | @media only screen and (min-width: 481px) { 232 | #container { 233 | position: relative; 234 | } 235 | body { 236 | background-color: #F5F5FF; 237 | font-size: 15px; 238 | line-height: 21px; 239 | } 240 | pre, tt, code { 241 | line-height: 18px; 242 | } 243 | p, ul, ol { 244 | margin: 0 0 15px; 245 | } 246 | 247 | 248 | #jump_to { 249 | padding: 5px 10px; 250 | } 251 | #jump_wrapper { 252 | padding: 0; 253 | } 254 | #jump_to, #jump_page { 255 | font: 10px Arial; 256 | text-transform: uppercase; 257 | } 258 | #jump_page .source { 259 | padding: 5px 10px; 260 | } 261 | #jump_to a.large { 262 | display: inline-block; 263 | } 264 | #jump_to a.small { 265 | display: none; 266 | } 267 | 268 | 269 | 270 | #background { 271 | position: absolute; 272 | top: 0; bottom: 0; 273 | width: 350px; 274 | background: #fff; 275 | border-right: 1px solid #e5e5ee; 276 | z-index: -1; 277 | } 278 | 279 | ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol { 280 | padding-left: 40px; 281 | } 282 | 283 | ul.sections > li { 284 | white-space: nowrap; 285 | } 286 | 287 | ul.sections > li > div { 288 | display: inline-block; 289 | } 290 | 291 | ul.sections > li > div.annotation { 292 | max-width: 350px; 293 | min-width: 350px; 294 | min-height: 5px; 295 | padding: 13px; 296 | overflow-x: hidden; 297 | white-space: normal; 298 | vertical-align: top; 299 | text-align: left; 300 | } 301 | ul.sections > li > div.annotation pre { 302 | margin: 15px 0 15px; 303 | padding-left: 15px; 304 | } 305 | 306 | ul.sections > li > div.content { 307 | padding: 13px; 308 | vertical-align: top; 309 | background: #f5f5ff; 310 | border: none; 311 | -webkit-box-shadow: none; 312 | box-shadow: none; 313 | } 314 | 315 | .pilwrap { 316 | position: relative; 317 | display: inline; 318 | } 319 | 320 | .pilcrow { 321 | font: 12px Arial; 322 | text-decoration: none; 323 | color: #454545; 324 | position: absolute; 325 | top: 3px; left: -20px; 326 | padding: 1px 2px; 327 | opacity: 0; 328 | -webkit-transition: opacity 0.2s linear; 329 | } 330 | .for-h1 .pilcrow { 331 | top: 47px; 332 | } 333 | .for-h2 .pilcrow, .for-h3 .pilcrow, .for-h4 .pilcrow { 334 | top: 35px; 335 | } 336 | 337 | ul.sections > li > div.annotation:hover .pilcrow { 338 | opacity: 1; 339 | } 340 | } 341 | 342 | /*---------------------- (> 1025px) ---------------------*/ 343 | @media only screen and (min-width: 1025px) { 344 | 345 | body { 346 | font-size: 16px; 347 | line-height: 24px; 348 | } 349 | 350 | #background { 351 | width: 525px; 352 | } 353 | ul.sections > li > div.annotation { 354 | max-width: 525px; 355 | min-width: 525px; 356 | padding: 10px 25px 1px 50px; 357 | } 358 | ul.sections > li > div.content { 359 | padding: 9px 15px 16px 25px; 360 | } 361 | } 362 | 363 | /*---------------------- Syntax Highlighting -----------------------------*/ 364 | 365 | td.linenos { background-color: #f0f0f0; padding-right: 10px; } 366 | span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; } 367 | /* 368 | 369 | github.com style (c) Vasily Polovnyov 370 | 371 | */ 372 | 373 | pre code { 374 | display: block; padding: 0.5em; 375 | color: #000; 376 | background: #f8f8ff 377 | } 378 | 379 | pre .comment, 380 | pre .template_comment, 381 | pre .diff .header, 382 | pre .javadoc { 383 | color: #408080; 384 | font-style: italic 385 | } 386 | 387 | pre .keyword, 388 | pre .assignment, 389 | pre .literal, 390 | pre .css .rule .keyword, 391 | pre .winutils, 392 | pre .javascript .title, 393 | pre .lisp .title, 394 | pre .subst { 395 | color: #954121; 396 | /*font-weight: bold*/ 397 | } 398 | 399 | pre .number, 400 | pre .hexcolor { 401 | color: #40a070 402 | } 403 | 404 | pre .string, 405 | pre .tag .value, 406 | pre .phpdoc, 407 | pre .tex .formula { 408 | color: #219161; 409 | } 410 | 411 | pre .title, 412 | pre .id { 413 | color: #19469D; 414 | } 415 | pre .params { 416 | color: #00F; 417 | } 418 | 419 | pre .javascript .title, 420 | pre .lisp .title, 421 | pre .subst { 422 | font-weight: normal 423 | } 424 | 425 | pre .class .title, 426 | pre .haskell .label, 427 | pre .tex .command { 428 | color: #458; 429 | font-weight: bold 430 | } 431 | 432 | pre .tag, 433 | pre .tag .title, 434 | pre .rules .property, 435 | pre .django .tag .keyword { 436 | color: #000080; 437 | font-weight: normal 438 | } 439 | 440 | pre .attribute, 441 | pre .variable, 442 | pre .instancevar, 443 | pre .lisp .body { 444 | color: #008080 445 | } 446 | 447 | pre .regexp { 448 | color: #B68 449 | } 450 | 451 | pre .class { 452 | color: #458; 453 | font-weight: bold 454 | } 455 | 456 | pre .symbol, 457 | pre .ruby .symbol .string, 458 | pre .ruby .symbol .keyword, 459 | pre .ruby .symbol .keymethods, 460 | pre .lisp .keyword, 461 | pre .tex .special, 462 | pre .input_number { 463 | color: #990073 464 | } 465 | 466 | pre .builtin, 467 | pre .constructor, 468 | pre .built_in, 469 | pre .lisp .title { 470 | color: #0086b3 471 | } 472 | 473 | pre .preprocessor, 474 | pre .pi, 475 | pre .doctype, 476 | pre .shebang, 477 | pre .cdata { 478 | color: #999; 479 | font-weight: bold 480 | } 481 | 482 | pre .deletion { 483 | background: #fdd 484 | } 485 | 486 | pre .addition { 487 | background: #dfd 488 | } 489 | 490 | pre .diff .change { 491 | background: #0086b3 492 | } 493 | 494 | pre .chunk { 495 | color: #aaa 496 | } 497 | 498 | pre .tex .formula { 499 | opacity: 0.5; 500 | } 501 | -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amdjs/underscore/37a9fb361fe1667c395d6133de40ab29f1d9e461/docs/favicon.ico -------------------------------------------------------------------------------- /docs/images/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amdjs/underscore/37a9fb361fe1667c395d6133de40ab29f1d9e461/docs/images/background.png -------------------------------------------------------------------------------- /docs/images/underscore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amdjs/underscore/37a9fb361fe1667c395d6133de40ab29f1d9e461/docs/images/underscore.png -------------------------------------------------------------------------------- /docs/public/fonts/aller-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amdjs/underscore/37a9fb361fe1667c395d6133de40ab29f1d9e461/docs/public/fonts/aller-bold.eot -------------------------------------------------------------------------------- /docs/public/fonts/aller-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amdjs/underscore/37a9fb361fe1667c395d6133de40ab29f1d9e461/docs/public/fonts/aller-bold.ttf -------------------------------------------------------------------------------- /docs/public/fonts/aller-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amdjs/underscore/37a9fb361fe1667c395d6133de40ab29f1d9e461/docs/public/fonts/aller-bold.woff -------------------------------------------------------------------------------- /docs/public/fonts/aller-light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amdjs/underscore/37a9fb361fe1667c395d6133de40ab29f1d9e461/docs/public/fonts/aller-light.eot -------------------------------------------------------------------------------- /docs/public/fonts/aller-light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amdjs/underscore/37a9fb361fe1667c395d6133de40ab29f1d9e461/docs/public/fonts/aller-light.ttf -------------------------------------------------------------------------------- /docs/public/fonts/aller-light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amdjs/underscore/37a9fb361fe1667c395d6133de40ab29f1d9e461/docs/public/fonts/aller-light.woff -------------------------------------------------------------------------------- /docs/public/fonts/novecento-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amdjs/underscore/37a9fb361fe1667c395d6133de40ab29f1d9e461/docs/public/fonts/novecento-bold.eot -------------------------------------------------------------------------------- /docs/public/fonts/novecento-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amdjs/underscore/37a9fb361fe1667c395d6133de40ab29f1d9e461/docs/public/fonts/novecento-bold.ttf -------------------------------------------------------------------------------- /docs/public/fonts/novecento-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amdjs/underscore/37a9fb361fe1667c395d6133de40ab29f1d9e461/docs/public/fonts/novecento-bold.woff -------------------------------------------------------------------------------- /docs/public/stylesheets/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v2.0.1 | MIT License | git.io/normalize */ 2 | 3 | /* ========================================================================== 4 | HTML5 display definitions 5 | ========================================================================== */ 6 | 7 | /* 8 | * Corrects `block` display not defined in IE 8/9. 9 | */ 10 | 11 | article, 12 | aside, 13 | details, 14 | figcaption, 15 | figure, 16 | footer, 17 | header, 18 | hgroup, 19 | nav, 20 | section, 21 | summary { 22 | display: block; 23 | } 24 | 25 | /* 26 | * Corrects `inline-block` display not defined in IE 8/9. 27 | */ 28 | 29 | audio, 30 | canvas, 31 | video { 32 | display: inline-block; 33 | } 34 | 35 | /* 36 | * Prevents modern browsers from displaying `audio` without controls. 37 | * Remove excess height in iOS 5 devices. 38 | */ 39 | 40 | audio:not([controls]) { 41 | display: none; 42 | height: 0; 43 | } 44 | 45 | /* 46 | * Addresses styling for `hidden` attribute not present in IE 8/9. 47 | */ 48 | 49 | [hidden] { 50 | display: none; 51 | } 52 | 53 | /* ========================================================================== 54 | Base 55 | ========================================================================== */ 56 | 57 | /* 58 | * 1. Sets default font family to sans-serif. 59 | * 2. Prevents iOS text size adjust after orientation change, without disabling 60 | * user zoom. 61 | */ 62 | 63 | html { 64 | font-family: sans-serif; /* 1 */ 65 | -webkit-text-size-adjust: 100%; /* 2 */ 66 | -ms-text-size-adjust: 100%; /* 2 */ 67 | } 68 | 69 | /* 70 | * Removes default margin. 71 | */ 72 | 73 | body { 74 | margin: 0; 75 | } 76 | 77 | /* ========================================================================== 78 | Links 79 | ========================================================================== */ 80 | 81 | /* 82 | * Addresses `outline` inconsistency between Chrome and other browsers. 83 | */ 84 | 85 | a:focus { 86 | outline: thin dotted; 87 | } 88 | 89 | /* 90 | * Improves readability when focused and also mouse hovered in all browsers. 91 | */ 92 | 93 | a:active, 94 | a:hover { 95 | outline: 0; 96 | } 97 | 98 | /* ========================================================================== 99 | Typography 100 | ========================================================================== */ 101 | 102 | /* 103 | * Addresses `h1` font sizes within `section` and `article` in Firefox 4+, 104 | * Safari 5, and Chrome. 105 | */ 106 | 107 | h1 { 108 | font-size: 2em; 109 | } 110 | 111 | /* 112 | * Addresses styling not present in IE 8/9, Safari 5, and Chrome. 113 | */ 114 | 115 | abbr[title] { 116 | border-bottom: 1px dotted; 117 | } 118 | 119 | /* 120 | * Addresses style set to `bolder` in Firefox 4+, Safari 5, and Chrome. 121 | */ 122 | 123 | b, 124 | strong { 125 | font-weight: bold; 126 | } 127 | 128 | /* 129 | * Addresses styling not present in Safari 5 and Chrome. 130 | */ 131 | 132 | dfn { 133 | font-style: italic; 134 | } 135 | 136 | /* 137 | * Addresses styling not present in IE 8/9. 138 | */ 139 | 140 | mark { 141 | background: #ff0; 142 | color: #000; 143 | } 144 | 145 | 146 | /* 147 | * Corrects font family set oddly in Safari 5 and Chrome. 148 | */ 149 | 150 | code, 151 | kbd, 152 | pre, 153 | samp { 154 | font-family: monospace, serif; 155 | font-size: 1em; 156 | } 157 | 158 | /* 159 | * Improves readability of pre-formatted text in all browsers. 160 | */ 161 | 162 | pre { 163 | white-space: pre; 164 | white-space: pre-wrap; 165 | word-wrap: break-word; 166 | } 167 | 168 | /* 169 | * Sets consistent quote types. 170 | */ 171 | 172 | q { 173 | quotes: "\201C" "\201D" "\2018" "\2019"; 174 | } 175 | 176 | /* 177 | * Addresses inconsistent and variable font size in all browsers. 178 | */ 179 | 180 | small { 181 | font-size: 80%; 182 | } 183 | 184 | /* 185 | * Prevents `sub` and `sup` affecting `line-height` in all browsers. 186 | */ 187 | 188 | sub, 189 | sup { 190 | font-size: 75%; 191 | line-height: 0; 192 | position: relative; 193 | vertical-align: baseline; 194 | } 195 | 196 | sup { 197 | top: -0.5em; 198 | } 199 | 200 | sub { 201 | bottom: -0.25em; 202 | } 203 | 204 | /* ========================================================================== 205 | Embedded content 206 | ========================================================================== */ 207 | 208 | /* 209 | * Removes border when inside `a` element in IE 8/9. 210 | */ 211 | 212 | img { 213 | border: 0; 214 | } 215 | 216 | /* 217 | * Corrects overflow displayed oddly in IE 9. 218 | */ 219 | 220 | svg:not(:root) { 221 | overflow: hidden; 222 | } 223 | 224 | /* ========================================================================== 225 | Figures 226 | ========================================================================== */ 227 | 228 | /* 229 | * Addresses margin not present in IE 8/9 and Safari 5. 230 | */ 231 | 232 | figure { 233 | margin: 0; 234 | } 235 | 236 | /* ========================================================================== 237 | Forms 238 | ========================================================================== */ 239 | 240 | /* 241 | * Define consistent border, margin, and padding. 242 | */ 243 | 244 | fieldset { 245 | border: 1px solid #c0c0c0; 246 | margin: 0 2px; 247 | padding: 0.35em 0.625em 0.75em; 248 | } 249 | 250 | /* 251 | * 1. Corrects color not being inherited in IE 8/9. 252 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 253 | */ 254 | 255 | legend { 256 | border: 0; /* 1 */ 257 | padding: 0; /* 2 */ 258 | } 259 | 260 | /* 261 | * 1. Corrects font family not being inherited in all browsers. 262 | * 2. Corrects font size not being inherited in all browsers. 263 | * 3. Addresses margins set differently in Firefox 4+, Safari 5, and Chrome 264 | */ 265 | 266 | button, 267 | input, 268 | select, 269 | textarea { 270 | font-family: inherit; /* 1 */ 271 | font-size: 100%; /* 2 */ 272 | margin: 0; /* 3 */ 273 | } 274 | 275 | /* 276 | * Addresses Firefox 4+ setting `line-height` on `input` using `!important` in 277 | * the UA stylesheet. 278 | */ 279 | 280 | button, 281 | input { 282 | line-height: normal; 283 | } 284 | 285 | /* 286 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 287 | * and `video` controls. 288 | * 2. Corrects inability to style clickable `input` types in iOS. 289 | * 3. Improves usability and consistency of cursor style between image-type 290 | * `input` and others. 291 | */ 292 | 293 | button, 294 | html input[type="button"], /* 1 */ 295 | input[type="reset"], 296 | input[type="submit"] { 297 | -webkit-appearance: button; /* 2 */ 298 | cursor: pointer; /* 3 */ 299 | } 300 | 301 | /* 302 | * Re-set default cursor for disabled elements. 303 | */ 304 | 305 | button[disabled], 306 | input[disabled] { 307 | cursor: default; 308 | } 309 | 310 | /* 311 | * 1. Addresses box sizing set to `content-box` in IE 8/9. 312 | * 2. Removes excess padding in IE 8/9. 313 | */ 314 | 315 | input[type="checkbox"], 316 | input[type="radio"] { 317 | box-sizing: border-box; /* 1 */ 318 | padding: 0; /* 2 */ 319 | } 320 | 321 | /* 322 | * 1. Addresses `appearance` set to `searchfield` in Safari 5 and Chrome. 323 | * 2. Addresses `box-sizing` set to `border-box` in Safari 5 and Chrome 324 | * (include `-moz` to future-proof). 325 | */ 326 | 327 | input[type="search"] { 328 | -webkit-appearance: textfield; /* 1 */ 329 | -moz-box-sizing: content-box; 330 | -webkit-box-sizing: content-box; /* 2 */ 331 | box-sizing: content-box; 332 | } 333 | 334 | /* 335 | * Removes inner padding and search cancel button in Safari 5 and Chrome 336 | * on OS X. 337 | */ 338 | 339 | input[type="search"]::-webkit-search-cancel-button, 340 | input[type="search"]::-webkit-search-decoration { 341 | -webkit-appearance: none; 342 | } 343 | 344 | /* 345 | * Removes inner padding and border in Firefox 4+. 346 | */ 347 | 348 | button::-moz-focus-inner, 349 | input::-moz-focus-inner { 350 | border: 0; 351 | padding: 0; 352 | } 353 | 354 | /* 355 | * 1. Removes default vertical scrollbar in IE 8/9. 356 | * 2. Improves readability and alignment in all browsers. 357 | */ 358 | 359 | textarea { 360 | overflow: auto; /* 1 */ 361 | vertical-align: top; /* 2 */ 362 | } 363 | 364 | /* ========================================================================== 365 | Tables 366 | ========================================================================== */ 367 | 368 | /* 369 | * Remove most spacing between table cells. 370 | */ 371 | 372 | table { 373 | border-collapse: collapse; 374 | border-spacing: 0; 375 | } -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amdjs/underscore/37a9fb361fe1667c395d6133de40ab29f1d9e461/favicon.ico -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "underscore", 3 | "description" : "JavaScript's functional programming helper library.", 4 | "homepage" : "http://underscorejs.org", 5 | "keywords" : ["util", "functional", "server", "client", "browser"], 6 | "author" : "Jeremy Ashkenas ", 7 | "repository" : {"type": "git", "url": "git://github.com/jashkenas/underscore.git"}, 8 | "main" : "underscore.js", 9 | "version" : "1.5.2", 10 | "devDependencies": { 11 | "phantomjs": "1.9.0-1" 12 | }, 13 | "scripts": { 14 | "test": "phantomjs test/vendor/runner.js test/index.html?noglobals=true" 15 | }, 16 | "licenses": [ 17 | { 18 | "type": "MIT", 19 | "url": "https://raw.github.com/jashkenas/underscore/master/LICENSE" 20 | } 21 | ], 22 | "files" : ["underscore.js", "underscore-min.js", "LICENSE"] 23 | } 24 | -------------------------------------------------------------------------------- /test/arrays.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | 3 | module("Arrays"); 4 | 5 | test("first", function() { 6 | equal(_.first([1,2,3]), 1, 'can pull out the first element of an array'); 7 | equal(_([1, 2, 3]).first(), 1, 'can perform OO-style "first()"'); 8 | equal(_.first([1,2,3], 0).join(', '), "", 'can pass an index to first'); 9 | equal(_.first([1,2,3], 2).join(', '), '1, 2', 'can pass an index to first'); 10 | equal(_.first([1,2,3], 5).join(', '), '1, 2, 3', 'can pass an index to first'); 11 | var result = (function(){ return _.first(arguments); })(4, 3, 2, 1); 12 | equal(result, 4, 'works on an arguments object.'); 13 | result = _.map([[1,2,3],[1,2,3]], _.first); 14 | equal(result.join(','), '1,1', 'works well with _.map'); 15 | result = (function() { return _.take([1,2,3], 2); })(); 16 | equal(result.join(','), '1,2', 'aliased as take'); 17 | 18 | equal(_.first(null), undefined, 'handles nulls'); 19 | }); 20 | 21 | test("rest", function() { 22 | var numbers = [1, 2, 3, 4]; 23 | equal(_.rest(numbers).join(", "), "2, 3, 4", 'working rest()'); 24 | equal(_.rest(numbers, 0).join(", "), "1, 2, 3, 4", 'working rest(0)'); 25 | equal(_.rest(numbers, 2).join(', '), '3, 4', 'rest can take an index'); 26 | var result = (function(){ return _(arguments).tail(); })(1, 2, 3, 4); 27 | equal(result.join(', '), '2, 3, 4', 'aliased as tail and works on arguments object'); 28 | result = _.map([[1,2,3],[1,2,3]], _.rest); 29 | equal(_.flatten(result).join(','), '2,3,2,3', 'works well with _.map'); 30 | result = (function(){ return _(arguments).drop(); })(1, 2, 3, 4); 31 | equal(result.join(', '), '2, 3, 4', 'aliased as drop and works on arguments object'); 32 | }); 33 | 34 | test("initial", function() { 35 | equal(_.initial([1,2,3,4,5]).join(", "), "1, 2, 3, 4", 'working initial()'); 36 | equal(_.initial([1,2,3,4],2).join(", "), "1, 2", 'initial can take an index'); 37 | var result = (function(){ return _(arguments).initial(); })(1, 2, 3, 4); 38 | equal(result.join(", "), "1, 2, 3", 'initial works on arguments object'); 39 | result = _.map([[1,2,3],[1,2,3]], _.initial); 40 | equal(_.flatten(result).join(','), '1,2,1,2', 'initial works with _.map'); 41 | }); 42 | 43 | test("last", function() { 44 | equal(_.last([1,2,3]), 3, 'can pull out the last element of an array'); 45 | equal(_.last([1,2,3], 0).join(', '), "", 'can pass an index to last'); 46 | equal(_.last([1,2,3], 2).join(', '), '2, 3', 'can pass an index to last'); 47 | equal(_.last([1,2,3], 5).join(', '), '1, 2, 3', 'can pass an index to last'); 48 | var result = (function(){ return _(arguments).last(); })(1, 2, 3, 4); 49 | equal(result, 4, 'works on an arguments object'); 50 | result = _.map([[1,2,3],[1,2,3]], _.last); 51 | equal(result.join(','), '3,3', 'works well with _.map'); 52 | 53 | equal(_.last(null), undefined, 'handles nulls'); 54 | }); 55 | 56 | test("compact", function() { 57 | equal(_.compact([0, 1, false, 2, false, 3]).length, 3, 'can trim out all falsy values'); 58 | var result = (function(){ return _.compact(arguments).length; })(0, 1, false, 2, false, 3); 59 | equal(result, 3, 'works on an arguments object'); 60 | }); 61 | 62 | test("flatten", function() { 63 | var list = [1, [2], [3, [[[4]]]]]; 64 | deepEqual(_.flatten(list), [1,2,3,4], 'can flatten nested arrays'); 65 | deepEqual(_.flatten(list, true), [1,2,3,[[[4]]]], 'can shallowly flatten nested arrays'); 66 | var result = (function(){ return _.flatten(arguments); })(1, [2], [3, [[[4]]]]); 67 | deepEqual(result, [1,2,3,4], 'works on an arguments object'); 68 | list = [[1], [2], [3], [[4]]]; 69 | deepEqual(_.flatten(list, true), [1, 2, 3, [4]], 'can shallowly flatten arrays containing only other arrays'); 70 | }); 71 | 72 | test("without", function() { 73 | var list = [1, 2, 1, 0, 3, 1, 4]; 74 | equal(_.without(list, 0, 1).join(', '), '2, 3, 4', 'can remove all instances of an object'); 75 | var result = (function(){ return _.without(arguments, 0, 1); })(1, 2, 1, 0, 3, 1, 4); 76 | equal(result.join(', '), '2, 3, 4', 'works on an arguments object'); 77 | 78 | list = [{one : 1}, {two : 2}]; 79 | ok(_.without(list, {one : 1}).length == 2, 'uses real object identity for comparisons.'); 80 | ok(_.without(list, list[0]).length == 1, 'ditto.'); 81 | }); 82 | 83 | test("uniq", function() { 84 | var list = [1, 2, 1, 3, 1, 4]; 85 | equal(_.uniq(list).join(', '), '1, 2, 3, 4', 'can find the unique values of an unsorted array'); 86 | 87 | list = [1, 1, 1, 2, 2, 3]; 88 | equal(_.uniq(list, true).join(', '), '1, 2, 3', 'can find the unique values of a sorted array faster'); 89 | 90 | list = [{name:'moe'}, {name:'curly'}, {name:'larry'}, {name:'curly'}]; 91 | var iterator = function(value) { return value.name; }; 92 | equal(_.map(_.uniq(list, false, iterator), iterator).join(', '), 'moe, curly, larry', 'can find the unique values of an array using a custom iterator'); 93 | 94 | 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'); 95 | 96 | iterator = function(value) { return value +1; }; 97 | list = [1, 2, 2, 3, 4, 4]; 98 | equal(_.uniq(list, true, iterator).join(', '), '1, 2, 3, 4', 'iterator works with sorted array'); 99 | 100 | var result = (function(){ return _.uniq(arguments); })(1, 2, 1, 3, 1, 4); 101 | equal(result.join(', '), '1, 2, 3, 4', 'works on an arguments object'); 102 | }); 103 | 104 | test("intersection", function() { 105 | var stooges = ['moe', 'curly', 'larry'], leaders = ['moe', 'groucho']; 106 | equal(_.intersection(stooges, leaders).join(''), 'moe', 'can take the set intersection of two arrays'); 107 | equal(_(stooges).intersection(leaders).join(''), 'moe', 'can perform an OO-style intersection'); 108 | var result = (function(){ return _.intersection(arguments, leaders); })('moe', 'curly', 'larry'); 109 | equal(result.join(''), 'moe', 'works on an arguments object'); 110 | var theSixStooges = ['moe', 'moe', 'curly', 'curly', 'larry', 'larry']; 111 | equal(_.intersection(theSixStooges, leaders).join(''), 'moe', 'returns a duplicate-free array'); 112 | }); 113 | 114 | test("union", function() { 115 | var result = _.union([1, 2, 3], [2, 30, 1], [1, 40]); 116 | equal(result.join(' '), '1 2 3 30 40', 'takes the union of a list of arrays'); 117 | 118 | result = _.union([1, 2, 3], [2, 30, 1], [1, 40, [1]]); 119 | equal(result.join(' '), '1 2 3 30 40 1', 'takes the union of a list of nested arrays'); 120 | 121 | var args = null; 122 | (function(){ args = arguments; })(1, 2, 3); 123 | result = _.union(args, [2, 30, 1], [1, 40]); 124 | equal(result.join(' '), '1 2 3 30 40', 'takes the union of a list of arrays'); 125 | 126 | result = _.union(null, [1, 2, 3]); 127 | deepEqual(result, [null, 1, 2, 3]); 128 | }); 129 | 130 | test("difference", function() { 131 | var result = _.difference([1, 2, 3], [2, 30, 40]); 132 | equal(result.join(' '), '1 3', 'takes the difference of two arrays'); 133 | 134 | result = _.difference([1, 2, 3, 4], [2, 30, 40], [1, 11, 111]); 135 | equal(result.join(' '), '3 4', 'takes the difference of three arrays'); 136 | }); 137 | 138 | test('zip', function() { 139 | var names = ['moe', 'larry', 'curly'], ages = [30, 40, 50], leaders = [true]; 140 | var stooges = _.zip(names, ages, leaders); 141 | equal(String(stooges), 'moe,30,true,larry,40,,curly,50,', 'zipped together arrays of different lengths'); 142 | 143 | stooges = _.zip(['moe',30, 'stooge 1'],['larry',40, 'stooge 2'],['curly',50, 'stooge 3']); 144 | deepEqual(stooges, [['moe','larry','curly'],[30,40,50], ['stooge 1', 'stooge 2', 'stooge 3']], 'zipped pairs'); 145 | 146 | // In the case of difference lengths of the tuples undefineds 147 | // should be used as placeholder 148 | stooges = _.zip(['moe',30],['larry',40],['curly',50, 'extra data']); 149 | deepEqual(stooges, [['moe','larry','curly'],[30,40,50], [undefined, undefined, 'extra data']], 'zipped pairs with empties'); 150 | 151 | var empty = _.zip([]); 152 | deepEqual(empty, [], 'unzipped empty'); 153 | }); 154 | 155 | test('object', function() { 156 | var result = _.object(['moe', 'larry', 'curly'], [30, 40, 50]); 157 | var shouldBe = {moe: 30, larry: 40, curly: 50}; 158 | ok(_.isEqual(result, shouldBe), 'two arrays zipped together into an object'); 159 | 160 | result = _.object([['one', 1], ['two', 2], ['three', 3]]); 161 | shouldBe = {one: 1, two: 2, three: 3}; 162 | ok(_.isEqual(result, shouldBe), 'an array of pairs zipped together into an object'); 163 | 164 | var stooges = {moe: 30, larry: 40, curly: 50}; 165 | ok(_.isEqual(_.object(_.pairs(stooges)), stooges), 'an object converted to pairs and back to an object'); 166 | 167 | ok(_.isEqual(_.object(null), {}), 'handles nulls'); 168 | }); 169 | 170 | test("indexOf", function() { 171 | var numbers = [1, 2, 3]; 172 | numbers.indexOf = null; 173 | equal(_.indexOf(numbers, 2), 1, 'can compute indexOf, even without the native function'); 174 | var result = (function(){ return _.indexOf(arguments, 2); })(1, 2, 3); 175 | equal(result, 1, 'works on an arguments object'); 176 | equal(_.indexOf(null, 2), -1, 'handles nulls properly'); 177 | 178 | var num = 35; 179 | numbers = [10, 20, 30, 40, 50]; 180 | var index = _.indexOf(numbers, num, true); 181 | equal(index, -1, '35 is not in the list'); 182 | 183 | numbers = [10, 20, 30, 40, 50]; num = 40; 184 | index = _.indexOf(numbers, num, true); 185 | equal(index, 3, '40 is in the list'); 186 | 187 | numbers = [1, 40, 40, 40, 40, 40, 40, 40, 50, 60, 70]; num = 40; 188 | index = _.indexOf(numbers, num, true); 189 | equal(index, 1, '40 is in the list'); 190 | 191 | numbers = [1, 2, 3, 1, 2, 3, 1, 2, 3]; 192 | index = _.indexOf(numbers, 2, 5); 193 | equal(index, 7, 'supports the fromIndex argument'); 194 | }); 195 | 196 | test("lastIndexOf", function() { 197 | var numbers = [1, 0, 1]; 198 | equal(_.lastIndexOf(numbers, 1), 2); 199 | 200 | numbers = [1, 0, 1, 0, 0, 1, 0, 0, 0]; 201 | numbers.lastIndexOf = null; 202 | equal(_.lastIndexOf(numbers, 1), 5, 'can compute lastIndexOf, even without the native function'); 203 | equal(_.lastIndexOf(numbers, 0), 8, 'lastIndexOf the other element'); 204 | var result = (function(){ return _.lastIndexOf(arguments, 1); })(1, 0, 1, 0, 0, 1, 0, 0, 0); 205 | equal(result, 5, 'works on an arguments object'); 206 | equal(_.lastIndexOf(null, 2), -1, 'handles nulls properly'); 207 | 208 | numbers = [1, 2, 3, 1, 2, 3, 1, 2, 3]; 209 | var index = _.lastIndexOf(numbers, 2, 2); 210 | equal(index, 1, 'supports the fromIndex argument'); 211 | }); 212 | 213 | test("range", function() { 214 | equal(_.range(0).join(''), '', 'range with 0 as a first argument generates an empty array'); 215 | equal(_.range(4).join(' '), '0 1 2 3', 'range with a single positive argument generates an array of elements 0,1,2,...,n-1'); 216 | 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'); 217 | equal(_.range(8, 5).join(''), '', 'range with two arguments a & b, b<a generates an empty array'); 218 | equal(_.range(3, 10, 3).join(' '), '3 6 9', 'range with three arguments a & b & c, c < b-a, a < b generates an array of elements a,a+c,a+2c,...,b - (multiplier of a) < c'); 219 | equal(_.range(3, 10, 15).join(''), '3', 'range with three arguments a & b & c, c > b-a, a < b generates an array with a single element, equal to a'); 220 | equal(_.range(12, 7, -2).join(' '), '12 10 8', 'range with three arguments a & b & c, a > b, c < 0 generates an array of elements a,a-c,a-2c and ends with the number not less than b'); 221 | equal(_.range(0, -10, -1).join(' '), '0 -1 -2 -3 -4 -5 -6 -7 -8 -9', 'final example in the Python docs'); 222 | }); 223 | 224 | }); 225 | -------------------------------------------------------------------------------- /test/chaining.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | 3 | module("Chaining"); 4 | 5 | test("map/flatten/reduce", function() { 6 | var lyrics = [ 7 | "I'm a lumberjack and I'm okay", 8 | "I sleep all night and I work all day", 9 | "He's a lumberjack and he's okay", 10 | "He sleeps all night and he works all day" 11 | ]; 12 | var counts = _(lyrics).chain() 13 | .map(function(line) { return line.split(''); }) 14 | .flatten() 15 | .reduce(function(hash, l) { 16 | hash[l] = hash[l] || 0; 17 | hash[l]++; 18 | return hash; 19 | }, {}).value(); 20 | ok(counts.a == 16 && counts.e == 10, 'counted all the letters in the song'); 21 | }); 22 | 23 | test("select/reject/sortBy", function() { 24 | var numbers = [1,2,3,4,5,6,7,8,9,10]; 25 | numbers = _(numbers).chain().select(function(n) { 26 | return n % 2 === 0; 27 | }).reject(function(n) { 28 | return n % 4 === 0; 29 | }).sortBy(function(n) { 30 | return -n; 31 | }).value(); 32 | equal(numbers.join(', '), "10, 6, 2", "filtered and reversed the numbers"); 33 | }); 34 | 35 | test("select/reject/sortBy in functional style", function() { 36 | var numbers = [1,2,3,4,5,6,7,8,9,10]; 37 | numbers = _.chain(numbers).select(function(n) { 38 | return n % 2 === 0; 39 | }).reject(function(n) { 40 | return n % 4 === 0; 41 | }).sortBy(function(n) { 42 | return -n; 43 | }).value(); 44 | equal(numbers.join(', '), "10, 6, 2", "filtered and reversed the numbers"); 45 | }); 46 | 47 | test("reverse/concat/unshift/pop/map", function() { 48 | var numbers = [1,2,3,4,5]; 49 | numbers = _(numbers).chain() 50 | .reverse() 51 | .concat([5, 5, 5]) 52 | .unshift(17) 53 | .pop() 54 | .map(function(n){ return n * 2; }) 55 | .value(); 56 | equal(numbers.join(', '), "34, 10, 8, 6, 4, 2, 10, 10", 'can chain together array functions.'); 57 | }); 58 | 59 | test("chaining works in small stages", function() { 60 | var o = _([1, 2, 3, 4]).chain(); 61 | deepEqual(o.filter(function(i) { return i < 3; }).value(), [1, 2]); 62 | deepEqual(o.filter(function(i) { return i > 2; }).value(), [3, 4]); 63 | }); 64 | 65 | }); 66 | -------------------------------------------------------------------------------- /test/collections.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | 3 | module("Collections"); 4 | 5 | test("each", function() { 6 | _.each([1, 2, 3], function(num, i) { 7 | equal(num, i + 1, 'each iterators provide value and iteration count'); 8 | }); 9 | 10 | var answers = []; 11 | _.each([1, 2, 3], function(num){ answers.push(num * this.multiplier);}, {multiplier : 5}); 12 | equal(answers.join(', '), '5, 10, 15', 'context object property accessed'); 13 | 14 | answers = []; 15 | _.forEach([1, 2, 3], function(num){ answers.push(num); }); 16 | equal(answers.join(', '), '1, 2, 3', 'aliased as "forEach"'); 17 | 18 | answers = []; 19 | var obj = {one : 1, two : 2, three : 3}; 20 | obj.constructor.prototype.four = 4; 21 | _.each(obj, function(value, key){ answers.push(key); }); 22 | equal(answers.join(", "), 'one, two, three', 'iterating over objects works, and ignores the object prototype.'); 23 | delete obj.constructor.prototype.four; 24 | 25 | var answer = null; 26 | _.each([1, 2, 3], function(num, index, arr){ if (_.include(arr, num)) answer = true; }); 27 | ok(answer, 'can reference the original collection from inside the iterator'); 28 | 29 | answers = 0; 30 | _.each(null, function(){ ++answers; }); 31 | equal(answers, 0, 'handles a null properly'); 32 | }); 33 | 34 | test('map', function() { 35 | var doubled = _.map([1, 2, 3], function(num){ return num * 2; }); 36 | equal(doubled.join(', '), '2, 4, 6', 'doubled numbers'); 37 | 38 | doubled = _.collect([1, 2, 3], function(num){ return num * 2; }); 39 | equal(doubled.join(', '), '2, 4, 6', 'aliased as "collect"'); 40 | 41 | var tripled = _.map([1, 2, 3], function(num){ return num * this.multiplier; }, {multiplier : 3}); 42 | equal(tripled.join(', '), '3, 6, 9', 'tripled numbers with context'); 43 | 44 | var doubled = _([1, 2, 3]).map(function(num){ return num * 2; }); 45 | equal(doubled.join(', '), '2, 4, 6', 'OO-style doubled numbers'); 46 | 47 | if (document.querySelectorAll) { 48 | var ids = _.map(document.querySelectorAll('#map-test *'), function(n){ return n.id; }); 49 | deepEqual(ids, ['id1', 'id2'], 'Can use collection methods on NodeLists.'); 50 | } 51 | 52 | var ids = _.map($('#map-test').children(), function(n){ return n.id; }); 53 | deepEqual(ids, ['id1', 'id2'], 'Can use collection methods on jQuery Array-likes.'); 54 | 55 | var ids = _.map(document.images, function(n){ return n.id; }); 56 | ok(ids[0] == 'chart_image', 'can use collection methods on HTMLCollections'); 57 | 58 | var ifnull = _.map(null, function(){}); 59 | ok(_.isArray(ifnull) && ifnull.length === 0, 'handles a null properly'); 60 | }); 61 | 62 | test('reduce', function() { 63 | var sum = _.reduce([1, 2, 3], function(sum, num){ return sum + num; }, 0); 64 | equal(sum, 6, 'can sum up an array'); 65 | 66 | var context = {multiplier : 3}; 67 | sum = _.reduce([1, 2, 3], function(sum, num){ return sum + num * this.multiplier; }, 0, context); 68 | equal(sum, 18, 'can reduce with a context object'); 69 | 70 | sum = _.inject([1, 2, 3], function(sum, num){ return sum + num; }, 0); 71 | equal(sum, 6, 'aliased as "inject"'); 72 | 73 | sum = _([1, 2, 3]).reduce(function(sum, num){ return sum + num; }, 0); 74 | equal(sum, 6, 'OO-style reduce'); 75 | 76 | var sum = _.reduce([1, 2, 3], function(sum, num){ return sum + num; }); 77 | equal(sum, 6, 'default initial value'); 78 | 79 | var prod = _.reduce([1, 2, 3, 4], function(prod, num){ return prod * num; }); 80 | equal(prod, 24, 'can reduce via multiplication'); 81 | 82 | var ifnull; 83 | try { 84 | _.reduce(null, function(){}); 85 | } catch (ex) { 86 | ifnull = ex; 87 | } 88 | ok(ifnull instanceof TypeError, 'handles a null (without initial value) properly'); 89 | 90 | ok(_.reduce(null, function(){}, 138) === 138, 'handles a null (with initial value) properly'); 91 | equal(_.reduce([], function(){}, undefined), undefined, 'undefined can be passed as a special case'); 92 | raises(function() { _.reduce([], function(){}); }, TypeError, 'throws an error for empty arrays with no initial value'); 93 | }); 94 | 95 | test('reduceRight', function() { 96 | var list = _.reduceRight(["foo", "bar", "baz"], function(memo, str){ return memo + str; }, ''); 97 | equal(list, 'bazbarfoo', 'can perform right folds'); 98 | 99 | var list = _.foldr(["foo", "bar", "baz"], function(memo, str){ return memo + str; }, ''); 100 | equal(list, 'bazbarfoo', 'aliased as "foldr"'); 101 | 102 | var list = _.foldr(["foo", "bar", "baz"], function(memo, str){ return memo + str; }); 103 | equal(list, 'bazbarfoo', 'default initial value'); 104 | 105 | var ifnull; 106 | try { 107 | _.reduceRight(null, function(){}); 108 | } catch (ex) { 109 | ifnull = ex; 110 | } 111 | ok(ifnull instanceof TypeError, 'handles a null (without initial value) properly'); 112 | 113 | var sum = _.reduceRight({a: 1, b: 2, c: 3}, function(sum, num){ return sum + num; }); 114 | equal(sum, 6, 'default initial value on object'); 115 | 116 | ok(_.reduceRight(null, function(){}, 138) === 138, 'handles a null (with initial value) properly'); 117 | 118 | equal(_.reduceRight([], function(){}, undefined), undefined, 'undefined can be passed as a special case'); 119 | raises(function() { _.reduceRight([], function(){}); }, TypeError, 'throws an error for empty arrays with no initial value'); 120 | 121 | // Assert that the correct arguments are being passed. 122 | 123 | var args, 124 | memo = {}, 125 | object = {a: 1, b: 2}, 126 | lastKey = _.keys(object).pop(); 127 | 128 | var expected = lastKey == 'a' 129 | ? [memo, 1, 'a', object] 130 | : [memo, 2, 'b', object]; 131 | 132 | _.reduceRight(object, function() { 133 | args || (args = _.toArray(arguments)); 134 | }, memo); 135 | 136 | deepEqual(args, expected); 137 | 138 | // And again, with numeric keys. 139 | 140 | object = {'2': 'a', '1': 'b'}; 141 | lastKey = _.keys(object).pop(); 142 | args = null; 143 | 144 | expected = lastKey == '2' 145 | ? [memo, 'a', '2', object] 146 | : [memo, 'b', '1', object]; 147 | 148 | _.reduceRight(object, function() { 149 | args || (args = _.toArray(arguments)); 150 | }, memo); 151 | 152 | deepEqual(args, expected); 153 | }); 154 | 155 | test('find', function() { 156 | var array = [1, 2, 3, 4]; 157 | strictEqual(_.find(array, function(n) { return n > 2; }), 3, 'should return first found `value`'); 158 | strictEqual(_.find(array, function() { return false; }), void 0, 'should return `undefined` if `value` is not found'); 159 | }); 160 | 161 | test('detect', function() { 162 | var result = _.detect([1, 2, 3], function(num){ return num * 2 == 4; }); 163 | equal(result, 2, 'found the first "2" and broke the loop'); 164 | }); 165 | 166 | test('select', function() { 167 | var evens = _.select([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; }); 168 | equal(evens.join(', '), '2, 4, 6', 'selected each even number'); 169 | 170 | evens = _.filter([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; }); 171 | equal(evens.join(', '), '2, 4, 6', 'aliased as "filter"'); 172 | }); 173 | 174 | test('reject', function() { 175 | var odds = _.reject([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; }); 176 | equal(odds.join(', '), '1, 3, 5', 'rejected each even number'); 177 | 178 | var context = "obj"; 179 | 180 | var evens = _.reject([1, 2, 3, 4, 5, 6], function(num){ 181 | equal(context, "obj"); 182 | return num % 2 != 0; 183 | }, context); 184 | equal(evens.join(', '), '2, 4, 6', 'rejected each odd number'); 185 | }); 186 | 187 | test('all', function() { 188 | ok(_.all([], _.identity), 'the empty set'); 189 | ok(_.all([true, true, true], _.identity), 'all true values'); 190 | ok(!_.all([true, false, true], _.identity), 'one false value'); 191 | ok(_.all([0, 10, 28], function(num){ return num % 2 == 0; }), 'even numbers'); 192 | ok(!_.all([0, 11, 28], function(num){ return num % 2 == 0; }), 'an odd number'); 193 | ok(_.all([1], _.identity) === true, 'cast to boolean - true'); 194 | ok(_.all([0], _.identity) === false, 'cast to boolean - false'); 195 | ok(_.every([true, true, true], _.identity), 'aliased as "every"'); 196 | ok(!_.all([undefined, undefined, undefined], _.identity), 'works with arrays of undefined'); 197 | }); 198 | 199 | test('any', function() { 200 | var nativeSome = Array.prototype.some; 201 | Array.prototype.some = null; 202 | ok(!_.any([]), 'the empty set'); 203 | ok(!_.any([false, false, false]), 'all false values'); 204 | ok(_.any([false, false, true]), 'one true value'); 205 | ok(_.any([null, 0, 'yes', false]), 'a string'); 206 | ok(!_.any([null, 0, '', false]), 'falsy values'); 207 | ok(!_.any([1, 11, 29], function(num){ return num % 2 == 0; }), 'all odd numbers'); 208 | ok(_.any([1, 10, 29], function(num){ return num % 2 == 0; }), 'an even number'); 209 | ok(_.any([1], _.identity) === true, 'cast to boolean - true'); 210 | ok(_.any([0], _.identity) === false, 'cast to boolean - false'); 211 | ok(_.some([false, false, true]), 'aliased as "some"'); 212 | Array.prototype.some = nativeSome; 213 | }); 214 | 215 | test('include', function() { 216 | ok(_.include([1,2,3], 2), 'two is in the array'); 217 | ok(!_.include([1,3,9], 2), 'two is not in the array'); 218 | ok(_.contains({moe:1, larry:3, curly:9}, 3) === true, '_.include on objects checks their values'); 219 | ok(_([1,2,3]).include(2), 'OO-style include'); 220 | }); 221 | 222 | test('invoke', function() { 223 | var list = [[5, 1, 7], [3, 2, 1]]; 224 | var result = _.invoke(list, 'sort'); 225 | equal(result[0].join(', '), '1, 5, 7', 'first array sorted'); 226 | equal(result[1].join(', '), '1, 2, 3', 'second array sorted'); 227 | }); 228 | 229 | test('invoke w/ function reference', function() { 230 | var list = [[5, 1, 7], [3, 2, 1]]; 231 | var result = _.invoke(list, Array.prototype.sort); 232 | equal(result[0].join(', '), '1, 5, 7', 'first array sorted'); 233 | equal(result[1].join(', '), '1, 2, 3', 'second array sorted'); 234 | }); 235 | 236 | // Relevant when using ClojureScript 237 | test('invoke when strings have a call method', function() { 238 | String.prototype.call = function() { 239 | return 42; 240 | }; 241 | var list = [[5, 1, 7], [3, 2, 1]]; 242 | var s = "foo"; 243 | equal(s.call(), 42, "call function exists"); 244 | var result = _.invoke(list, 'sort'); 245 | equal(result[0].join(', '), '1, 5, 7', 'first array sorted'); 246 | equal(result[1].join(', '), '1, 2, 3', 'second array sorted'); 247 | delete String.prototype.call; 248 | equal(s.call, undefined, "call function removed"); 249 | }); 250 | 251 | test('pluck', function() { 252 | var people = [{name : 'moe', age : 30}, {name : 'curly', age : 50}]; 253 | equal(_.pluck(people, 'name').join(', '), 'moe, curly', 'pulls names out of objects'); 254 | }); 255 | 256 | test('where', function() { 257 | var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}]; 258 | var result = _.where(list, {a: 1}); 259 | equal(result.length, 3); 260 | equal(result[result.length - 1].b, 4); 261 | result = _.where(list, {b: 2}); 262 | equal(result.length, 2); 263 | equal(result[0].a, 1); 264 | 265 | result = _.where(list, {a: 1}, true); 266 | equal(result.b, 2, "Only get the first object matched.") 267 | result = _.where(list, {a: 1}, false); 268 | equal(result.length, 3); 269 | }); 270 | 271 | test('findWhere', function() { 272 | var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}, {a: 2, b: 4}]; 273 | var result = _.findWhere(list, {a: 1}); 274 | deepEqual(result, {a: 1, b: 2}); 275 | result = _.findWhere(list, {b: 4}); 276 | deepEqual(result, {a: 1, b: 4}); 277 | 278 | result = _.findWhere(list, {c:1}) 279 | ok(_.isUndefined(result), "undefined when not found"); 280 | 281 | result = _.findWhere([], {c:1}); 282 | ok(_.isUndefined(result), "undefined when searching empty list"); 283 | }); 284 | 285 | test('max', function() { 286 | equal(3, _.max([1, 2, 3]), 'can perform a regular Math.max'); 287 | 288 | var neg = _.max([1, 2, 3], function(num){ return -num; }); 289 | equal(neg, 1, 'can perform a computation-based max'); 290 | 291 | equal(-Infinity, _.max({}), 'Maximum value of an empty object'); 292 | equal(-Infinity, _.max([]), 'Maximum value of an empty array'); 293 | equal(_.max({'a': 'a'}), -Infinity, 'Maximum value of a non-numeric collection'); 294 | 295 | equal(299999, _.max(_.range(1,300000)), "Maximum value of a too-big array"); 296 | }); 297 | 298 | test('min', function() { 299 | equal(1, _.min([1, 2, 3]), 'can perform a regular Math.min'); 300 | 301 | var neg = _.min([1, 2, 3], function(num){ return -num; }); 302 | equal(neg, 3, 'can perform a computation-based min'); 303 | 304 | equal(Infinity, _.min({}), 'Minimum value of an empty object'); 305 | equal(Infinity, _.min([]), 'Minimum value of an empty array'); 306 | equal(_.min({'a': 'a'}), Infinity, 'Minimum value of a non-numeric collection'); 307 | 308 | var now = new Date(9999999999); 309 | var then = new Date(0); 310 | equal(_.min([now, then]), then); 311 | 312 | equal(1, _.min(_.range(1,300000)), "Minimum value of a too-big array"); 313 | }); 314 | 315 | test('sortBy', function() { 316 | var people = [{name : 'curly', age : 50}, {name : 'moe', age : 30}]; 317 | people = _.sortBy(people, function(person){ return person.age; }); 318 | equal(_.pluck(people, 'name').join(', '), 'moe, curly', 'stooges sorted by age'); 319 | 320 | var list = [undefined, 4, 1, undefined, 3, 2]; 321 | equal(_.sortBy(list, _.identity).join(','), '1,2,3,4,,', 'sortBy with undefined values'); 322 | 323 | var list = ["one", "two", "three", "four", "five"]; 324 | var sorted = _.sortBy(list, 'length'); 325 | equal(sorted.join(' '), 'one two four five three', 'sorted by length'); 326 | 327 | function Pair(x, y) { 328 | this.x = x; 329 | this.y = y; 330 | } 331 | 332 | var collection = [ 333 | new Pair(1, 1), new Pair(1, 2), 334 | new Pair(1, 3), new Pair(1, 4), 335 | new Pair(1, 5), new Pair(1, 6), 336 | new Pair(2, 1), new Pair(2, 2), 337 | new Pair(2, 3), new Pair(2, 4), 338 | new Pair(2, 5), new Pair(2, 6), 339 | new Pair(undefined, 1), new Pair(undefined, 2), 340 | new Pair(undefined, 3), new Pair(undefined, 4), 341 | new Pair(undefined, 5), new Pair(undefined, 6) 342 | ]; 343 | 344 | var actual = _.sortBy(collection, function(pair) { 345 | return pair.x; 346 | }); 347 | 348 | deepEqual(actual, collection, 'sortBy should be stable'); 349 | }); 350 | 351 | test('groupBy', function() { 352 | var parity = _.groupBy([1, 2, 3, 4, 5, 6], function(num){ return num % 2; }); 353 | ok('0' in parity && '1' in parity, 'created a group for each value'); 354 | equal(parity[0].join(', '), '2, 4, 6', 'put each even number in the right group'); 355 | 356 | var list = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"]; 357 | var grouped = _.groupBy(list, 'length'); 358 | equal(grouped['3'].join(' '), 'one two six ten'); 359 | equal(grouped['4'].join(' '), 'four five nine'); 360 | equal(grouped['5'].join(' '), 'three seven eight'); 361 | 362 | var context = {}; 363 | _.groupBy([{}], function(){ ok(this === context); }, context); 364 | 365 | grouped = _.groupBy([4.2, 6.1, 6.4], function(num) { 366 | return Math.floor(num) > 4 ? 'hasOwnProperty' : 'constructor'; 367 | }); 368 | equal(grouped.constructor.length, 1); 369 | equal(grouped.hasOwnProperty.length, 2); 370 | 371 | var array = [{}]; 372 | _.groupBy(array, function(value, index, obj){ ok(obj === array); }); 373 | 374 | var array = [1, 2, 1, 2, 3]; 375 | var grouped = _.groupBy(array); 376 | equal(grouped['1'].length, 2); 377 | equal(grouped['3'].length, 1); 378 | 379 | var matrix = [ 380 | [1,2], 381 | [1,3], 382 | [2,3] 383 | ]; 384 | deepEqual(_.groupBy(matrix, 0), {1: [[1,2], [1,3]], 2: [[2,3]]}) 385 | deepEqual(_.groupBy(matrix, 1), {2: [[1,2]], 3: [[1,3], [2,3]]}) 386 | }); 387 | 388 | test('indexBy', function() { 389 | var parity = _.indexBy([1, 2, 3, 4, 5], function(num){ return num % 2 == 0; }); 390 | equal(parity['true'], 4); 391 | equal(parity['false'], 5); 392 | 393 | var list = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"]; 394 | var grouped = _.indexBy(list, 'length'); 395 | equal(grouped['3'], 'ten'); 396 | equal(grouped['4'], 'nine'); 397 | equal(grouped['5'], 'eight'); 398 | 399 | var array = [1, 2, 1, 2, 3]; 400 | var grouped = _.indexBy(array); 401 | equal(grouped['1'], 1); 402 | equal(grouped['2'], 2); 403 | equal(grouped['3'], 3); 404 | }); 405 | 406 | test('countBy', function() { 407 | var parity = _.countBy([1, 2, 3, 4, 5], function(num){ return num % 2 == 0; }); 408 | equal(parity['true'], 2); 409 | equal(parity['false'], 3); 410 | 411 | var list = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"]; 412 | var grouped = _.countBy(list, 'length'); 413 | equal(grouped['3'], 4); 414 | equal(grouped['4'], 3); 415 | equal(grouped['5'], 3); 416 | 417 | var context = {}; 418 | _.countBy([{}], function(){ ok(this === context); }, context); 419 | 420 | grouped = _.countBy([4.2, 6.1, 6.4], function(num) { 421 | return Math.floor(num) > 4 ? 'hasOwnProperty' : 'constructor'; 422 | }); 423 | equal(grouped.constructor, 1); 424 | equal(grouped.hasOwnProperty, 2); 425 | 426 | var array = [{}]; 427 | _.countBy(array, function(value, index, obj){ ok(obj === array); }); 428 | 429 | var array = [1, 2, 1, 2, 3]; 430 | var grouped = _.countBy(array); 431 | equal(grouped['1'], 2); 432 | equal(grouped['3'], 1); 433 | }); 434 | 435 | test('sortedIndex', function() { 436 | var numbers = [10, 20, 30, 40, 50], num = 35; 437 | var indexForNum = _.sortedIndex(numbers, num); 438 | equal(indexForNum, 3, '35 should be inserted at index 3'); 439 | 440 | var indexFor30 = _.sortedIndex(numbers, 30); 441 | equal(indexFor30, 2, '30 should be inserted at index 2'); 442 | 443 | var objects = [{x: 10}, {x: 20}, {x: 30}, {x: 40}]; 444 | var iterator = function(obj){ return obj.x; }; 445 | strictEqual(_.sortedIndex(objects, {x: 25}, iterator), 2); 446 | strictEqual(_.sortedIndex(objects, {x: 35}, 'x'), 3); 447 | 448 | var context = {1: 2, 2: 3, 3: 4}; 449 | iterator = function(obj){ return this[obj]; }; 450 | strictEqual(_.sortedIndex([1, 3], 2, iterator, context), 1); 451 | }); 452 | 453 | test('shuffle', function() { 454 | var numbers = _.range(10); 455 | var shuffled = _.shuffle(numbers).sort(); 456 | notStrictEqual(numbers, shuffled, 'original object is unmodified'); 457 | equal(shuffled.join(','), numbers.join(','), 'contains the same members before and after shuffle'); 458 | }); 459 | 460 | test('sample', function() { 461 | var numbers = _.range(10); 462 | var all_sampled = _.sample(numbers, 10).sort(); 463 | equal(all_sampled.join(','), numbers.join(','), 'contains the same members before and after sample'); 464 | all_sampled = _.sample(numbers, 20).sort(); 465 | equal(all_sampled.join(','), numbers.join(','), 'also works when sampling more objects than are present'); 466 | ok(_.contains(numbers, _.sample(numbers)), 'sampling a single element returns something from the array'); 467 | strictEqual(_.sample([]), undefined, 'sampling empty array with no number returns undefined'); 468 | notStrictEqual(_.sample([], 5), [], 'sampling empty array with a number returns an empty array'); 469 | notStrictEqual(_.sample([1, 2, 3], 0), [], 'sampling an array with 0 picks returns an empty array'); 470 | deepEqual(_.sample([1, 2], -1), [], 'sampling a negative number of picks returns an empty array'); 471 | }); 472 | 473 | test('toArray', function() { 474 | ok(!_.isArray(arguments), 'arguments object is not an array'); 475 | ok(_.isArray(_.toArray(arguments)), 'arguments object converted into array'); 476 | var a = [1,2,3]; 477 | ok(_.toArray(a) !== a, 'array is cloned'); 478 | equal(_.toArray(a).join(', '), '1, 2, 3', 'cloned array contains same elements'); 479 | 480 | var numbers = _.toArray({one : 1, two : 2, three : 3}); 481 | equal(numbers.join(', '), '1, 2, 3', 'object flattened into array'); 482 | 483 | // test in IE < 9 484 | try { 485 | var actual = _.toArray(document.childNodes); 486 | } catch(ex) { } 487 | 488 | ok(_.isArray(actual), 'should not throw converting a node list'); 489 | }); 490 | 491 | test('size', function() { 492 | equal(_.size({one : 1, two : 2, three : 3}), 3, 'can compute the size of an object'); 493 | equal(_.size([1, 2, 3]), 3, 'can compute the size of an array'); 494 | equal(_.size($('
').add('').add('')), 3, 'can compute the size of jQuery objects'); 495 | 496 | var func = function() { 497 | return _.size(arguments); 498 | }; 499 | 500 | equal(func(1, 2, 3, 4), 4, 'can test the size of the arguments object'); 501 | 502 | equal(_.size('hello'), 5, 'can compute the size of a string literal'); 503 | equal(_.size(new String('hello')), 5, 'can compute the size of string object'); 504 | 505 | equal(_.size(null), 0, 'handles nulls'); 506 | }); 507 | 508 | }); 509 | -------------------------------------------------------------------------------- /test/functions.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | 3 | module("Functions"); 4 | 5 | test("bind", function() { 6 | var context = {name : 'moe'}; 7 | var func = function(arg) { return "name: " + (this.name || arg); }; 8 | var bound = _.bind(func, context); 9 | equal(bound(), 'name: moe', 'can bind a function to a context'); 10 | 11 | bound = _(func).bind(context); 12 | equal(bound(), 'name: moe', 'can do OO-style binding'); 13 | 14 | bound = _.bind(func, null, 'curly'); 15 | equal(bound(), 'name: curly', 'can bind without specifying a context'); 16 | 17 | func = function(salutation, name) { return salutation + ': ' + name; }; 18 | func = _.bind(func, this, 'hello'); 19 | equal(func('moe'), 'hello: moe', 'the function was partially applied in advance'); 20 | 21 | func = _.bind(func, this, 'curly'); 22 | equal(func(), 'hello: curly', 'the function was completely applied in advance'); 23 | 24 | func = function(salutation, firstname, lastname) { return salutation + ': ' + firstname + ' ' + lastname; }; 25 | func = _.bind(func, this, 'hello', 'moe', 'curly'); 26 | equal(func(), 'hello: moe curly', 'the function was partially applied in advance and can accept multiple arguments'); 27 | 28 | func = function(context, message) { equal(this, context, message); }; 29 | _.bind(func, 0, 0, 'can bind a function to `0`')(); 30 | _.bind(func, '', '', 'can bind a function to an empty string')(); 31 | _.bind(func, false, false, 'can bind a function to `false`')(); 32 | 33 | // These tests are only meaningful when using a browser without a native bind function 34 | // To test this with a modern browser, set underscore's nativeBind to undefined 35 | var F = function () { return this; }; 36 | var Boundf = _.bind(F, {hello: "moe curly"}); 37 | var newBoundf = new Boundf(); 38 | equal(newBoundf.hello, undefined, "function should not be bound to the context, to comply with ECMAScript 5"); 39 | equal(Boundf().hello, "moe curly", "When called without the new operator, it's OK to be bound to the context"); 40 | ok(newBoundf instanceof F, "a bound instance is an instance of the original function"); 41 | }); 42 | 43 | test("partial", function() { 44 | var obj = {name: 'moe'}; 45 | var func = function() { return this.name + ' ' + _.toArray(arguments).join(' '); }; 46 | 47 | obj.func = _.partial(func, 'a', 'b'); 48 | equal(obj.func('c', 'd'), 'moe a b c d', 'can partially apply'); 49 | }); 50 | 51 | test("bindAll", function() { 52 | var curly = {name : 'curly'}, moe = { 53 | name : 'moe', 54 | getName : function() { return 'name: ' + this.name; }, 55 | sayHi : function() { return 'hi: ' + this.name; } 56 | }; 57 | curly.getName = moe.getName; 58 | _.bindAll(moe, 'getName', 'sayHi'); 59 | curly.sayHi = moe.sayHi; 60 | equal(curly.getName(), 'name: curly', 'unbound function is bound to current object'); 61 | equal(curly.sayHi(), 'hi: moe', 'bound function is still bound to original object'); 62 | 63 | curly = {name : 'curly'}; 64 | moe = { 65 | name : 'moe', 66 | getName : function() { return 'name: ' + this.name; }, 67 | sayHi : function() { return 'hi: ' + this.name; } 68 | }; 69 | 70 | raises(function() { _.bindAll(moe); }, Error, 'throws an error for bindAll with no functions named'); 71 | 72 | _.bindAll(moe, 'sayHi'); 73 | curly.sayHi = moe.sayHi; 74 | equal(curly.sayHi(), 'hi: moe'); 75 | }); 76 | 77 | test("memoize", function() { 78 | var fib = function(n) { 79 | return n < 2 ? n : fib(n - 1) + fib(n - 2); 80 | }; 81 | equal(fib(10), 55, 'a memoized version of fibonacci produces identical results'); 82 | fib = _.memoize(fib); // Redefine `fib` for memoization 83 | equal(fib(10), 55, 'a memoized version of fibonacci produces identical results'); 84 | 85 | var o = function(str) { 86 | return str; 87 | }; 88 | var fastO = _.memoize(o); 89 | equal(o('toString'), 'toString', 'checks hasOwnProperty'); 90 | equal(fastO('toString'), 'toString', 'checks hasOwnProperty'); 91 | }); 92 | 93 | asyncTest("delay", 2, function() { 94 | var delayed = false; 95 | _.delay(function(){ delayed = true; }, 100); 96 | setTimeout(function(){ ok(!delayed, "didn't delay the function quite yet"); }, 50); 97 | setTimeout(function(){ ok(delayed, 'delayed the function'); start(); }, 150); 98 | }); 99 | 100 | asyncTest("defer", 1, function() { 101 | var deferred = false; 102 | _.defer(function(bool){ deferred = bool; }, true); 103 | _.delay(function(){ ok(deferred, "deferred the function"); start(); }, 50); 104 | }); 105 | 106 | asyncTest("throttle", 2, function() { 107 | var counter = 0; 108 | var incr = function(){ counter++; }; 109 | var throttledIncr = _.throttle(incr, 32); 110 | throttledIncr(); throttledIncr(); 111 | 112 | equal(counter, 1, "incr was called immediately"); 113 | _.delay(function(){ equal(counter, 2, "incr was throttled"); start(); }, 64); 114 | }); 115 | 116 | asyncTest("throttle arguments", 2, function() { 117 | var value = 0; 118 | var update = function(val){ value = val; }; 119 | var throttledUpdate = _.throttle(update, 32); 120 | throttledUpdate(1); throttledUpdate(2); 121 | _.delay(function(){ throttledUpdate(3); }, 64); 122 | equal(value, 1, "updated to latest value"); 123 | _.delay(function(){ equal(value, 3, "updated to latest value"); start(); }, 96); 124 | }); 125 | 126 | asyncTest("throttle once", 2, function() { 127 | var counter = 0; 128 | var incr = function(){ return ++counter; }; 129 | var throttledIncr = _.throttle(incr, 32); 130 | var result = throttledIncr(); 131 | _.delay(function(){ 132 | equal(result, 1, "throttled functions return their value"); 133 | equal(counter, 1, "incr was called once"); start(); 134 | }, 64); 135 | }); 136 | 137 | asyncTest("throttle twice", 1, function() { 138 | var counter = 0; 139 | var incr = function(){ counter++; }; 140 | var throttledIncr = _.throttle(incr, 32); 141 | throttledIncr(); throttledIncr(); 142 | _.delay(function(){ equal(counter, 2, "incr was called twice"); start(); }, 64); 143 | }); 144 | 145 | asyncTest("more throttling", 3, function() { 146 | var counter = 0; 147 | var incr = function(){ counter++; }; 148 | var throttledIncr = _.throttle(incr, 30); 149 | throttledIncr(); throttledIncr(); 150 | ok(counter == 1); 151 | _.delay(function(){ 152 | ok(counter == 2); 153 | throttledIncr(); 154 | ok(counter == 3); 155 | start(); 156 | }, 85); 157 | }); 158 | 159 | asyncTest("throttle repeatedly with results", 6, function() { 160 | var counter = 0; 161 | var incr = function(){ return ++counter; }; 162 | var throttledIncr = _.throttle(incr, 100); 163 | var results = []; 164 | var saveResult = function() { results.push(throttledIncr()); }; 165 | saveResult(); saveResult(); 166 | _.delay(saveResult, 50); 167 | _.delay(saveResult, 150); 168 | _.delay(saveResult, 160); 169 | _.delay(saveResult, 230); 170 | _.delay(function() { 171 | equal(results[0], 1, "incr was called once"); 172 | equal(results[1], 1, "incr was throttled"); 173 | equal(results[2], 1, "incr was throttled"); 174 | equal(results[3], 2, "incr was called twice"); 175 | equal(results[4], 2, "incr was throttled"); 176 | equal(results[5], 3, "incr was called trailing"); 177 | start(); 178 | }, 300); 179 | }); 180 | 181 | asyncTest("throttle triggers trailing call when invoked repeatedly", 2, function() { 182 | var counter = 0; 183 | var limit = 48; 184 | var incr = function(){ counter++; }; 185 | var throttledIncr = _.throttle(incr, 32); 186 | 187 | var stamp = new Date; 188 | while ((new Date - stamp) < limit) { 189 | throttledIncr(); 190 | } 191 | var lastCount = counter; 192 | ok(counter > 1); 193 | 194 | _.delay(function() { 195 | ok(counter > lastCount); 196 | start(); 197 | }, 96); 198 | }); 199 | 200 | asyncTest("throttle does not trigger leading call when leading is set to false", 2, function() { 201 | var counter = 0; 202 | var incr = function(){ counter++; }; 203 | var throttledIncr = _.throttle(incr, 60, {leading: false}); 204 | 205 | throttledIncr(); throttledIncr(); 206 | ok(counter === 0); 207 | 208 | _.delay(function() { 209 | ok(counter == 1); 210 | start(); 211 | }, 96); 212 | }); 213 | 214 | asyncTest("more throttle does not trigger leading call when leading is set to false", 3, function() { 215 | var counter = 0; 216 | var incr = function(){ counter++; }; 217 | var throttledIncr = _.throttle(incr, 100, {leading: false}); 218 | 219 | throttledIncr(); 220 | _.delay(throttledIncr, 50); 221 | _.delay(throttledIncr, 60); 222 | _.delay(throttledIncr, 200); 223 | ok(counter === 0); 224 | 225 | _.delay(function() { 226 | ok(counter == 1); 227 | }, 250); 228 | 229 | _.delay(function() { 230 | ok(counter == 2); 231 | start(); 232 | }, 350); 233 | }); 234 | 235 | asyncTest("one more throttle with leading: false test", 2, function() { 236 | var counter = 0; 237 | var incr = function(){ counter++; }; 238 | var throttledIncr = _.throttle(incr, 100, {leading: false}); 239 | 240 | var time = new Date; 241 | while (new Date - time < 350) throttledIncr(); 242 | ok(counter <= 3); 243 | 244 | _.delay(function() { 245 | ok(counter <= 4); 246 | start(); 247 | }, 200); 248 | }); 249 | 250 | asyncTest("throttle does not trigger trailing call when trailing is set to false", 4, function() { 251 | var counter = 0; 252 | var incr = function(){ counter++; }; 253 | var throttledIncr = _.throttle(incr, 60, {trailing: false}); 254 | 255 | throttledIncr(); throttledIncr(); throttledIncr(); 256 | ok(counter === 1); 257 | 258 | _.delay(function() { 259 | ok(counter == 1); 260 | 261 | throttledIncr(); throttledIncr(); 262 | ok(counter == 2); 263 | 264 | _.delay(function() { 265 | ok(counter == 2); 266 | start(); 267 | }, 96); 268 | }, 96); 269 | }); 270 | 271 | asyncTest("debounce", 1, function() { 272 | var counter = 0; 273 | var incr = function(){ counter++; }; 274 | var debouncedIncr = _.debounce(incr, 32); 275 | debouncedIncr(); debouncedIncr(); 276 | _.delay(debouncedIncr, 16); 277 | _.delay(function(){ equal(counter, 1, "incr was debounced"); start(); }, 96); 278 | }); 279 | 280 | asyncTest("debounce asap", 4, function() { 281 | var a, b; 282 | var counter = 0; 283 | var incr = function(){ return ++counter; }; 284 | var debouncedIncr = _.debounce(incr, 64, true); 285 | a = debouncedIncr(); 286 | b = debouncedIncr(); 287 | equal(a, 1); 288 | equal(b, 1); 289 | equal(counter, 1, 'incr was called immediately'); 290 | _.delay(debouncedIncr, 16); 291 | _.delay(debouncedIncr, 32); 292 | _.delay(debouncedIncr, 48); 293 | _.delay(function(){ equal(counter, 1, "incr was debounced"); start(); }, 128); 294 | }); 295 | 296 | asyncTest("debounce asap recursively", 2, function() { 297 | var counter = 0; 298 | var debouncedIncr = _.debounce(function(){ 299 | counter++; 300 | if (counter < 10) debouncedIncr(); 301 | }, 32, true); 302 | debouncedIncr(); 303 | equal(counter, 1, "incr was called immediately"); 304 | _.delay(function(){ equal(counter, 1, "incr was debounced"); start(); }, 96); 305 | }); 306 | 307 | test("once", function() { 308 | var num = 0; 309 | var increment = _.once(function(){ num++; }); 310 | increment(); 311 | increment(); 312 | equal(num, 1); 313 | }); 314 | 315 | test("Recursive onced function.", 1, function() { 316 | var f = _.once(function(){ 317 | ok(true); 318 | f(); 319 | }); 320 | f(); 321 | }); 322 | 323 | test("wrap", function() { 324 | var greet = function(name){ return "hi: " + name; }; 325 | var backwards = _.wrap(greet, function(func, name){ return func(name) + ' ' + name.split('').reverse().join(''); }); 326 | equal(backwards('moe'), 'hi: moe eom', 'wrapped the salutation function'); 327 | 328 | var inner = function(){ return "Hello "; }; 329 | var obj = {name : "Moe"}; 330 | obj.hi = _.wrap(inner, function(fn){ return fn() + this.name; }); 331 | equal(obj.hi(), "Hello Moe"); 332 | 333 | var noop = function(){}; 334 | var wrapped = _.wrap(noop, function(fn){ return Array.prototype.slice.call(arguments, 0); }); 335 | var ret = wrapped(['whats', 'your'], 'vector', 'victor'); 336 | deepEqual(ret, [noop, ['whats', 'your'], 'vector', 'victor']); 337 | }); 338 | 339 | test("compose", function() { 340 | var greet = function(name){ return "hi: " + name; }; 341 | var exclaim = function(sentence){ return sentence + '!'; }; 342 | var composed = _.compose(exclaim, greet); 343 | equal(composed('moe'), 'hi: moe!', 'can compose a function that takes another'); 344 | 345 | composed = _.compose(greet, exclaim); 346 | equal(composed('moe'), 'hi: moe!', 'in this case, the functions are also commutative'); 347 | }); 348 | 349 | test("after", function() { 350 | var testAfter = function(afterAmount, timesCalled) { 351 | var afterCalled = 0; 352 | var after = _.after(afterAmount, function() { 353 | afterCalled++; 354 | }); 355 | while (timesCalled--) after(); 356 | return afterCalled; 357 | }; 358 | 359 | equal(testAfter(5, 5), 1, "after(N) should fire after being called N times"); 360 | equal(testAfter(5, 4), 0, "after(N) should not fire unless called N times"); 361 | equal(testAfter(0, 0), 0, "after(0) should not fire immediately"); 362 | equal(testAfter(0, 1), 1, "after(0) should fire when first invoked"); 363 | }); 364 | 365 | }); 366 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Underscore Test Suite 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |

Underscore Speed Suite

29 |

30 | A representative sample of the functions are benchmarked here, to provide 31 | a sense of how fast they might run in different browsers. 32 | Each iteration runs on an array of 1000 elements.

33 | For example, the 'intersection' test measures the number of times you can 34 | find the intersection of two thousand-element arrays in one second. 35 |

36 |
37 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /test/objects.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | 3 | module("Objects"); 4 | 5 | test("keys", function() { 6 | equal(_.keys({one : 1, two : 2}).join(', '), 'one, two', 'can extract the keys from an object'); 7 | // the test above is not safe because it relies on for-in enumeration order 8 | var a = []; a[1] = 0; 9 | equal(_.keys(a).join(', '), '1', 'is not fooled by sparse arrays; see issue #95'); 10 | raises(function() { _.keys(null); }, TypeError, 'throws an error for `null` values'); 11 | raises(function() { _.keys(void 0); }, TypeError, 'throws an error for `undefined` values'); 12 | raises(function() { _.keys(1); }, TypeError, 'throws an error for number primitives'); 13 | raises(function() { _.keys('a'); }, TypeError, 'throws an error for string primitives'); 14 | raises(function() { _.keys(true); }, TypeError, 'throws an error for boolean primitives'); 15 | }); 16 | 17 | test("values", function() { 18 | equal(_.values({one: 1, two: 2}).join(', '), '1, 2', 'can extract the values from an object'); 19 | equal(_.values({one: 1, two: 2, length: 3}).join(', '), '1, 2, 3', '... even when one of them is "length"'); 20 | }); 21 | 22 | test("pairs", function() { 23 | deepEqual(_.pairs({one: 1, two: 2}), [['one', 1], ['two', 2]], 'can convert an object into pairs'); 24 | deepEqual(_.pairs({one: 1, two: 2, length: 3}), [['one', 1], ['two', 2], ['length', 3]], '... even when one of them is "length"'); 25 | }); 26 | 27 | test("invert", function() { 28 | var obj = {first: 'Moe', second: 'Larry', third: 'Curly'}; 29 | equal(_.keys(_.invert(obj)).join(' '), 'Moe Larry Curly', 'can invert an object'); 30 | ok(_.isEqual(_.invert(_.invert(obj)), obj), 'two inverts gets you back where you started'); 31 | 32 | var obj = {length: 3}; 33 | ok(_.invert(obj)['3'] == 'length', 'can invert an object with "length"') 34 | }); 35 | 36 | test("functions", function() { 37 | var obj = {a : 'dash', b : _.map, c : (/yo/), d : _.reduce}; 38 | ok(_.isEqual(['b', 'd'], _.functions(obj)), 'can grab the function names of any passed-in object'); 39 | 40 | var Animal = function(){}; 41 | Animal.prototype.run = function(){}; 42 | equal(_.functions(new Animal).join(''), 'run', 'also looks up functions on the prototype'); 43 | }); 44 | 45 | test("extend", function() { 46 | var result; 47 | equal(_.extend({}, {a:'b'}).a, 'b', 'can extend an object with the attributes of another'); 48 | equal(_.extend({a:'x'}, {a:'b'}).a, 'b', 'properties in source override destination'); 49 | equal(_.extend({x:'x'}, {a:'b'}).x, 'x', "properties not in source don't get overriden"); 50 | result = _.extend({x:'x'}, {a:'a'}, {b:'b'}); 51 | ok(_.isEqual(result, {x:'x', a:'a', b:'b'}), 'can extend from multiple source objects'); 52 | result = _.extend({x:'x'}, {a:'a', x:2}, {a:'b'}); 53 | ok(_.isEqual(result, {x:2, a:'b'}), 'extending from multiple source objects last property trumps'); 54 | result = _.extend({}, {a: void 0, b: null}); 55 | equal(_.keys(result).join(''), 'ab', 'extend copies undefined values'); 56 | 57 | try { 58 | result = {}; 59 | _.extend(result, null, undefined, {a:1}); 60 | } catch(ex) {} 61 | 62 | equal(result.a, 1, 'should not error on `null` or `undefined` sources'); 63 | }); 64 | 65 | test("pick", function() { 66 | var result; 67 | result = _.pick({a:1, b:2, c:3}, 'a', 'c'); 68 | ok(_.isEqual(result, {a:1, c:3}), 'can restrict properties to those named'); 69 | result = _.pick({a:1, b:2, c:3}, ['b', 'c']); 70 | ok(_.isEqual(result, {b:2, c:3}), 'can restrict properties to those named in an array'); 71 | result = _.pick({a:1, b:2, c:3}, ['a'], 'b'); 72 | ok(_.isEqual(result, {a:1, b:2}), 'can restrict properties to those named in mixed args'); 73 | 74 | var Obj = function(){}; 75 | Obj.prototype = {a: 1, b: 2, c: 3}; 76 | ok(_.isEqual(_.pick(new Obj, 'a', 'c'), {a:1, c: 3}), 'include prototype props'); 77 | }); 78 | 79 | test("omit", function() { 80 | var result; 81 | result = _.omit({a:1, b:2, c:3}, 'b'); 82 | ok(_.isEqual(result, {a:1, c:3}), 'can omit a single named property'); 83 | result = _.omit({a:1, b:2, c:3}, 'a', 'c'); 84 | ok(_.isEqual(result, {b:2}), 'can omit several named properties'); 85 | result = _.omit({a:1, b:2, c:3}, ['b', 'c']); 86 | ok(_.isEqual(result, {a:1}), 'can omit properties named in an array'); 87 | 88 | var Obj = function(){}; 89 | Obj.prototype = {a: 1, b: 2, c: 3}; 90 | ok(_.isEqual(_.omit(new Obj, 'b'), {a:1, c: 3}), 'include prototype props'); 91 | }); 92 | 93 | test("defaults", function() { 94 | var result; 95 | var options = {zero: 0, one: 1, empty: "", nan: NaN, nothing: null}; 96 | 97 | _.defaults(options, {zero: 1, one: 10, twenty: 20, nothing: 'str'}); 98 | equal(options.zero, 0, 'value exists'); 99 | equal(options.one, 1, 'value exists'); 100 | equal(options.twenty, 20, 'default applied'); 101 | equal(options.nothing, null, "null isn't overridden"); 102 | 103 | _.defaults(options, {empty: "full"}, {nan: "nan"}, {word: "word"}, {word: "dog"}); 104 | equal(options.empty, "", 'value exists'); 105 | ok(_.isNaN(options.nan), "NaN isn't overridden"); 106 | equal(options.word, "word", 'new value is added, first one wins'); 107 | 108 | try { 109 | options = {}; 110 | _.defaults(options, null, undefined, {a:1}); 111 | } catch(ex) {} 112 | 113 | equal(options.a, 1, 'should not error on `null` or `undefined` sources'); 114 | }); 115 | 116 | test("clone", function() { 117 | var moe = {name : 'moe', lucky : [13, 27, 34]}; 118 | var clone = _.clone(moe); 119 | equal(clone.name, 'moe', 'the clone as the attributes of the original'); 120 | 121 | clone.name = 'curly'; 122 | ok(clone.name == 'curly' && moe.name == 'moe', 'clones can change shallow attributes without affecting the original'); 123 | 124 | clone.lucky.push(101); 125 | equal(_.last(moe.lucky), 101, 'changes to deep attributes are shared with the original'); 126 | 127 | equal(_.clone(undefined), void 0, 'non objects should not be changed by clone'); 128 | equal(_.clone(1), 1, 'non objects should not be changed by clone'); 129 | equal(_.clone(null), null, 'non objects should not be changed by clone'); 130 | }); 131 | 132 | test("isEqual", function() { 133 | function First() { 134 | this.value = 1; 135 | } 136 | First.prototype.value = 1; 137 | function Second() { 138 | this.value = 1; 139 | } 140 | Second.prototype.value = 2; 141 | 142 | // Basic equality and identity comparisons. 143 | ok(_.isEqual(null, null), "`null` is equal to `null`"); 144 | ok(_.isEqual(), "`undefined` is equal to `undefined`"); 145 | 146 | ok(!_.isEqual(0, -0), "`0` is not equal to `-0`"); 147 | ok(!_.isEqual(-0, 0), "Commutative equality is implemented for `0` and `-0`"); 148 | ok(!_.isEqual(null, undefined), "`null` is not equal to `undefined`"); 149 | ok(!_.isEqual(undefined, null), "Commutative equality is implemented for `null` and `undefined`"); 150 | 151 | // String object and primitive comparisons. 152 | ok(_.isEqual("Curly", "Curly"), "Identical string primitives are equal"); 153 | ok(_.isEqual(new String("Curly"), new String("Curly")), "String objects with identical primitive values are equal"); 154 | ok(_.isEqual(new String("Curly"), "Curly"), "String primitives and their corresponding object wrappers are equal"); 155 | ok(_.isEqual("Curly", new String("Curly")), "Commutative equality is implemented for string objects and primitives"); 156 | 157 | ok(!_.isEqual("Curly", "Larry"), "String primitives with different values are not equal"); 158 | ok(!_.isEqual(new String("Curly"), new String("Larry")), "String objects with different primitive values are not equal"); 159 | ok(!_.isEqual(new String("Curly"), {toString: function(){ return "Curly"; }}), "String objects and objects with a custom `toString` method are not equal"); 160 | 161 | // Number object and primitive comparisons. 162 | ok(_.isEqual(75, 75), "Identical number primitives are equal"); 163 | ok(_.isEqual(new Number(75), new Number(75)), "Number objects with identical primitive values are equal"); 164 | ok(_.isEqual(75, new Number(75)), "Number primitives and their corresponding object wrappers are equal"); 165 | ok(_.isEqual(new Number(75), 75), "Commutative equality is implemented for number objects and primitives"); 166 | ok(!_.isEqual(new Number(0), -0), "`new Number(0)` and `-0` are not equal"); 167 | ok(!_.isEqual(0, new Number(-0)), "Commutative equality is implemented for `new Number(0)` and `-0`"); 168 | 169 | ok(!_.isEqual(new Number(75), new Number(63)), "Number objects with different primitive values are not equal"); 170 | ok(!_.isEqual(new Number(63), {valueOf: function(){ return 63; }}), "Number objects and objects with a `valueOf` method are not equal"); 171 | 172 | // Comparisons involving `NaN`. 173 | ok(_.isEqual(NaN, NaN), "`NaN` is equal to `NaN`"); 174 | ok(!_.isEqual(61, NaN), "A number primitive is not equal to `NaN`"); 175 | ok(!_.isEqual(new Number(79), NaN), "A number object is not equal to `NaN`"); 176 | ok(!_.isEqual(Infinity, NaN), "`Infinity` is not equal to `NaN`"); 177 | 178 | // Boolean object and primitive comparisons. 179 | ok(_.isEqual(true, true), "Identical boolean primitives are equal"); 180 | ok(_.isEqual(new Boolean, new Boolean), "Boolean objects with identical primitive values are equal"); 181 | ok(_.isEqual(true, new Boolean(true)), "Boolean primitives and their corresponding object wrappers are equal"); 182 | ok(_.isEqual(new Boolean(true), true), "Commutative equality is implemented for booleans"); 183 | ok(!_.isEqual(new Boolean(true), new Boolean), "Boolean objects with different primitive values are not equal"); 184 | 185 | // Common type coercions. 186 | ok(!_.isEqual(true, new Boolean(false)), "Boolean objects are not equal to the boolean primitive `true`"); 187 | ok(!_.isEqual("75", 75), "String and number primitives with like values are not equal"); 188 | ok(!_.isEqual(new Number(63), new String(63)), "String and number objects with like values are not equal"); 189 | ok(!_.isEqual(75, "75"), "Commutative equality is implemented for like string and number values"); 190 | ok(!_.isEqual(0, ""), "Number and string primitives with like values are not equal"); 191 | ok(!_.isEqual(1, true), "Number and boolean primitives with like values are not equal"); 192 | ok(!_.isEqual(new Boolean(false), new Number(0)), "Boolean and number objects with like values are not equal"); 193 | ok(!_.isEqual(false, new String("")), "Boolean primitives and string objects with like values are not equal"); 194 | ok(!_.isEqual(12564504e5, new Date(2009, 9, 25)), "Dates and their corresponding numeric primitive values are not equal"); 195 | 196 | // Dates. 197 | ok(_.isEqual(new Date(2009, 9, 25), new Date(2009, 9, 25)), "Date objects referencing identical times are equal"); 198 | ok(!_.isEqual(new Date(2009, 9, 25), new Date(2009, 11, 13)), "Date objects referencing different times are not equal"); 199 | ok(!_.isEqual(new Date(2009, 11, 13), { 200 | getTime: function(){ 201 | return 12606876e5; 202 | } 203 | }), "Date objects and objects with a `getTime` method are not equal"); 204 | ok(!_.isEqual(new Date("Curly"), new Date("Curly")), "Invalid dates are not equal"); 205 | 206 | // Functions. 207 | ok(!_.isEqual(First, Second), "Different functions with identical bodies and source code representations are not equal"); 208 | 209 | // RegExps. 210 | ok(_.isEqual(/(?:)/gim, /(?:)/gim), "RegExps with equivalent patterns and flags are equal"); 211 | ok(!_.isEqual(/(?:)/g, /(?:)/gi), "RegExps with equivalent patterns and different flags are not equal"); 212 | ok(!_.isEqual(/Moe/gim, /Curly/gim), "RegExps with different patterns and equivalent flags are not equal"); 213 | ok(!_.isEqual(/(?:)/gi, /(?:)/g), "Commutative equality is implemented for RegExps"); 214 | ok(!_.isEqual(/Curly/g, {source: "Larry", global: true, ignoreCase: false, multiline: false}), "RegExps and RegExp-like objects are not equal"); 215 | 216 | // Empty arrays, array-like objects, and object literals. 217 | ok(_.isEqual({}, {}), "Empty object literals are equal"); 218 | ok(_.isEqual([], []), "Empty array literals are equal"); 219 | ok(_.isEqual([{}], [{}]), "Empty nested arrays and objects are equal"); 220 | ok(!_.isEqual({length: 0}, []), "Array-like objects and arrays are not equal."); 221 | ok(!_.isEqual([], {length: 0}), "Commutative equality is implemented for array-like objects"); 222 | 223 | ok(!_.isEqual({}, []), "Object literals and array literals are not equal"); 224 | ok(!_.isEqual([], {}), "Commutative equality is implemented for objects and arrays"); 225 | 226 | // Arrays with primitive and object values. 227 | ok(_.isEqual([1, "Larry", true], [1, "Larry", true]), "Arrays containing identical primitives are equal"); 228 | ok(_.isEqual([(/Moe/g), new Date(2009, 9, 25)], [(/Moe/g), new Date(2009, 9, 25)]), "Arrays containing equivalent elements are equal"); 229 | 230 | // Multi-dimensional arrays. 231 | var a = [new Number(47), false, "Larry", /Moe/, new Date(2009, 11, 13), ['running', 'biking', new String('programming')], {a: 47}]; 232 | var b = [new Number(47), false, "Larry", /Moe/, new Date(2009, 11, 13), ['running', 'biking', new String('programming')], {a: 47}]; 233 | ok(_.isEqual(a, b), "Arrays containing nested arrays and objects are recursively compared"); 234 | 235 | // Overwrite the methods defined in ES 5.1 section 15.4.4. 236 | a.forEach = a.map = a.filter = a.every = a.indexOf = a.lastIndexOf = a.some = a.reduce = a.reduceRight = null; 237 | b.join = b.pop = b.reverse = b.shift = b.slice = b.splice = b.concat = b.sort = b.unshift = null; 238 | 239 | // Array elements and properties. 240 | ok(_.isEqual(a, b), "Arrays containing equivalent elements and different non-numeric properties are equal"); 241 | a.push("White Rocks"); 242 | ok(!_.isEqual(a, b), "Arrays of different lengths are not equal"); 243 | a.push("East Boulder"); 244 | b.push("Gunbarrel Ranch", "Teller Farm"); 245 | ok(!_.isEqual(a, b), "Arrays of identical lengths containing different elements are not equal"); 246 | 247 | // Sparse arrays. 248 | ok(_.isEqual(Array(3), Array(3)), "Sparse arrays of identical lengths are equal"); 249 | ok(!_.isEqual(Array(3), Array(6)), "Sparse arrays of different lengths are not equal when both are empty"); 250 | 251 | // Simple objects. 252 | ok(_.isEqual({a: "Curly", b: 1, c: true}, {a: "Curly", b: 1, c: true}), "Objects containing identical primitives are equal"); 253 | 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"); 254 | ok(!_.isEqual({a: 63, b: 75}, {a: 61, b: 55}), "Objects of identical sizes with different values are not equal"); 255 | ok(!_.isEqual({a: 63, b: 75}, {a: 61, c: 55}), "Objects of identical sizes with different property names are not equal"); 256 | ok(!_.isEqual({a: 1, b: 2}, {a: 1}), "Objects of different sizes are not equal"); 257 | ok(!_.isEqual({a: 1}, {a: 1, b: 2}), "Commutative equality is implemented for objects"); 258 | ok(!_.isEqual({x: 1, y: undefined}, {x: 1, z: 2}), "Objects with identical keys and different values are not equivalent"); 259 | 260 | // `A` contains nested objects and arrays. 261 | a = { 262 | name: new String("Moe Howard"), 263 | age: new Number(77), 264 | stooge: true, 265 | hobbies: ["acting"], 266 | film: { 267 | name: "Sing a Song of Six Pants", 268 | release: new Date(1947, 9, 30), 269 | stars: [new String("Larry Fine"), "Shemp Howard"], 270 | minutes: new Number(16), 271 | seconds: 54 272 | } 273 | }; 274 | 275 | // `B` contains equivalent nested objects and arrays. 276 | b = { 277 | name: new String("Moe Howard"), 278 | age: new Number(77), 279 | stooge: true, 280 | hobbies: ["acting"], 281 | film: { 282 | name: "Sing a Song of Six Pants", 283 | release: new Date(1947, 9, 30), 284 | stars: [new String("Larry Fine"), "Shemp Howard"], 285 | minutes: new Number(16), 286 | seconds: 54 287 | } 288 | }; 289 | ok(_.isEqual(a, b), "Objects with nested equivalent members are recursively compared"); 290 | 291 | // Instances. 292 | ok(_.isEqual(new First, new First), "Object instances are equal"); 293 | ok(!_.isEqual(new First, new Second), "Objects with different constructors and identical own properties are not equal"); 294 | ok(!_.isEqual({value: 1}, new First), "Object instances and objects sharing equivalent properties are not equal"); 295 | ok(!_.isEqual({value: 2}, new Second), "The prototype chain of objects should not be examined"); 296 | 297 | // Circular Arrays. 298 | (a = []).push(a); 299 | (b = []).push(b); 300 | ok(_.isEqual(a, b), "Arrays containing circular references are equal"); 301 | a.push(new String("Larry")); 302 | b.push(new String("Larry")); 303 | ok(_.isEqual(a, b), "Arrays containing circular references and equivalent properties are equal"); 304 | a.push("Shemp"); 305 | b.push("Curly"); 306 | ok(!_.isEqual(a, b), "Arrays containing circular references and different properties are not equal"); 307 | 308 | // More circular arrays #767. 309 | a = ["everything is checked but", "this", "is not"]; 310 | a[1] = a; 311 | b = ["everything is checked but", ["this", "array"], "is not"]; 312 | ok(!_.isEqual(a, b), "Comparison of circular references with non-circular references are not equal"); 313 | 314 | // Circular Objects. 315 | a = {abc: null}; 316 | b = {abc: null}; 317 | a.abc = a; 318 | b.abc = b; 319 | ok(_.isEqual(a, b), "Objects containing circular references are equal"); 320 | a.def = 75; 321 | b.def = 75; 322 | ok(_.isEqual(a, b), "Objects containing circular references and equivalent properties are equal"); 323 | a.def = new Number(75); 324 | b.def = new Number(63); 325 | ok(!_.isEqual(a, b), "Objects containing circular references and different properties are not equal"); 326 | 327 | // More circular objects #767. 328 | a = {everything: "is checked", but: "this", is: "not"}; 329 | a.but = a; 330 | b = {everything: "is checked", but: {that:"object"}, is: "not"}; 331 | ok(!_.isEqual(a, b), "Comparison of circular references with non-circular object references are not equal"); 332 | 333 | // Cyclic Structures. 334 | a = [{abc: null}]; 335 | b = [{abc: null}]; 336 | (a[0].abc = a).push(a); 337 | (b[0].abc = b).push(b); 338 | ok(_.isEqual(a, b), "Cyclic structures are equal"); 339 | a[0].def = "Larry"; 340 | b[0].def = "Larry"; 341 | ok(_.isEqual(a, b), "Cyclic structures containing equivalent properties are equal"); 342 | a[0].def = new String("Larry"); 343 | b[0].def = new String("Curly"); 344 | ok(!_.isEqual(a, b), "Cyclic structures containing different properties are not equal"); 345 | 346 | // Complex Circular References. 347 | a = {foo: {b: {foo: {c: {foo: null}}}}}; 348 | b = {foo: {b: {foo: {c: {foo: null}}}}}; 349 | a.foo.b.foo.c.foo = a; 350 | b.foo.b.foo.c.foo = b; 351 | ok(_.isEqual(a, b), "Cyclic structures with nested and identically-named properties are equal"); 352 | 353 | // Chaining. 354 | ok(!_.isEqual(_({x: 1, y: undefined}).chain(), _({x: 1, z: 2}).chain()), 'Chained objects containing different values are not equal'); 355 | 356 | a = _({x: 1, y: 2}).chain(); 357 | b = _({x: 1, y: 2}).chain(); 358 | equal(_.isEqual(a.isEqual(b), _(true)), true, '`isEqual` can be chained'); 359 | 360 | // Objects from another frame. 361 | ok(_.isEqual({}, iObject)); 362 | }); 363 | 364 | test("isEmpty", function() { 365 | ok(!_([1]).isEmpty(), '[1] is not empty'); 366 | ok(_.isEmpty([]), '[] is empty'); 367 | ok(!_.isEmpty({one : 1}), '{one : 1} is not empty'); 368 | ok(_.isEmpty({}), '{} is empty'); 369 | ok(_.isEmpty(new RegExp('')), 'objects with prototype properties are empty'); 370 | ok(_.isEmpty(null), 'null is empty'); 371 | ok(_.isEmpty(), 'undefined is empty'); 372 | ok(_.isEmpty(''), 'the empty string is empty'); 373 | ok(!_.isEmpty('moe'), 'but other strings are not'); 374 | 375 | var obj = {one : 1}; 376 | delete obj.one; 377 | ok(_.isEmpty(obj), 'deleting all the keys from an object empties it'); 378 | }); 379 | 380 | // Setup remote variables for iFrame tests. 381 | var iframe = document.createElement('iframe'); 382 | jQuery(iframe).appendTo(document.body); 383 | var iDoc = iframe.contentDocument || iframe.contentWindow.document; 384 | iDoc.write( 385 | "" 400 | ); 401 | iDoc.close(); 402 | 403 | test("isElement", function() { 404 | ok(!_.isElement('div'), 'strings are not dom elements'); 405 | ok(_.isElement($('html')[0]), 'the html tag is a DOM element'); 406 | ok(_.isElement(iElement), 'even from another frame'); 407 | }); 408 | 409 | test("isArguments", function() { 410 | var args = (function(){ return arguments; })(1, 2, 3); 411 | ok(!_.isArguments('string'), 'a string is not an arguments object'); 412 | ok(!_.isArguments(_.isArguments), 'a function is not an arguments object'); 413 | ok(_.isArguments(args), 'but the arguments object is an arguments object'); 414 | ok(!_.isArguments(_.toArray(args)), 'but not when it\'s converted into an array'); 415 | ok(!_.isArguments([1,2,3]), 'and not vanilla arrays.'); 416 | ok(_.isArguments(iArguments), 'even from another frame'); 417 | }); 418 | 419 | test("isObject", function() { 420 | ok(_.isObject(arguments), 'the arguments object is object'); 421 | ok(_.isObject([1, 2, 3]), 'and arrays'); 422 | ok(_.isObject($('html')[0]), 'and DOM element'); 423 | ok(_.isObject(iElement), 'even from another frame'); 424 | ok(_.isObject(function () {}), 'and functions'); 425 | ok(_.isObject(iFunction), 'even from another frame'); 426 | ok(!_.isObject(null), 'but not null'); 427 | ok(!_.isObject(undefined), 'and not undefined'); 428 | ok(!_.isObject('string'), 'and not string'); 429 | ok(!_.isObject(12), 'and not number'); 430 | ok(!_.isObject(true), 'and not boolean'); 431 | ok(_.isObject(new String('string')), 'but new String()'); 432 | }); 433 | 434 | test("isArray", function() { 435 | ok(!_.isArray(undefined), 'undefined vars are not arrays'); 436 | ok(!_.isArray(arguments), 'the arguments object is not an array'); 437 | ok(_.isArray([1, 2, 3]), 'but arrays are'); 438 | ok(_.isArray(iArray), 'even from another frame'); 439 | }); 440 | 441 | test("isString", function() { 442 | var obj = new String("I am a string object"); 443 | ok(!_.isString(document.body), 'the document body is not a string'); 444 | ok(_.isString([1, 2, 3].join(', ')), 'but strings are'); 445 | ok(_.isString(iString), 'even from another frame'); 446 | ok(_.isString("I am a string literal"), 'string literals are'); 447 | ok(_.isString(obj), 'so are String objects'); 448 | }); 449 | 450 | test("isNumber", function() { 451 | ok(!_.isNumber('string'), 'a string is not a number'); 452 | ok(!_.isNumber(arguments), 'the arguments object is not a number'); 453 | ok(!_.isNumber(undefined), 'undefined is not a number'); 454 | ok(_.isNumber(3 * 4 - 7 / 10), 'but numbers are'); 455 | ok(_.isNumber(NaN), 'NaN *is* a number'); 456 | ok(_.isNumber(Infinity), 'Infinity is a number'); 457 | ok(_.isNumber(iNumber), 'even from another frame'); 458 | ok(!_.isNumber('1'), 'numeric strings are not numbers'); 459 | }); 460 | 461 | test("isBoolean", function() { 462 | ok(!_.isBoolean(2), 'a number is not a boolean'); 463 | ok(!_.isBoolean("string"), 'a string is not a boolean'); 464 | ok(!_.isBoolean("false"), 'the string "false" is not a boolean'); 465 | ok(!_.isBoolean("true"), 'the string "true" is not a boolean'); 466 | ok(!_.isBoolean(arguments), 'the arguments object is not a boolean'); 467 | ok(!_.isBoolean(undefined), 'undefined is not a boolean'); 468 | ok(!_.isBoolean(NaN), 'NaN is not a boolean'); 469 | ok(!_.isBoolean(null), 'null is not a boolean'); 470 | ok(_.isBoolean(true), 'but true is'); 471 | ok(_.isBoolean(false), 'and so is false'); 472 | ok(_.isBoolean(iBoolean), 'even from another frame'); 473 | }); 474 | 475 | test("isFunction", function() { 476 | ok(!_.isFunction(undefined), 'undefined vars are not functions'); 477 | ok(!_.isFunction([1, 2, 3]), 'arrays are not functions'); 478 | ok(!_.isFunction('moe'), 'strings are not functions'); 479 | ok(_.isFunction(_.isFunction), 'but functions are'); 480 | ok(_.isFunction(iFunction), 'even from another frame'); 481 | ok(_.isFunction(function(){}), 'even anonymous ones'); 482 | }); 483 | 484 | test("isDate", function() { 485 | ok(!_.isDate(100), 'numbers are not dates'); 486 | ok(!_.isDate({}), 'objects are not dates'); 487 | ok(_.isDate(new Date()), 'but dates are'); 488 | ok(_.isDate(iDate), 'even from another frame'); 489 | }); 490 | 491 | test("isRegExp", function() { 492 | ok(!_.isRegExp(_.identity), 'functions are not RegExps'); 493 | ok(_.isRegExp(/identity/), 'but RegExps are'); 494 | ok(_.isRegExp(iRegExp), 'even from another frame'); 495 | }); 496 | 497 | test("isFinite", function() { 498 | ok(!_.isFinite(undefined), 'undefined is not Finite'); 499 | ok(!_.isFinite(null), 'null is not Finite'); 500 | ok(!_.isFinite(NaN), 'NaN is not Finite'); 501 | ok(!_.isFinite(Infinity), 'Infinity is not Finite'); 502 | ok(!_.isFinite(-Infinity), '-Infinity is not Finite'); 503 | ok(_.isFinite('12'), 'Numeric strings are numbers'); 504 | ok(!_.isFinite('1a'), 'Non numeric strings are not numbers'); 505 | ok(!_.isFinite(''), 'Empty strings are not numbers'); 506 | var obj = new Number(5); 507 | ok(_.isFinite(obj), 'Number instances can be finite'); 508 | ok(_.isFinite(0), '0 is Finite'); 509 | ok(_.isFinite(123), 'Ints are Finite'); 510 | ok(_.isFinite(-12.44), 'Floats are Finite'); 511 | }); 512 | 513 | test("isNaN", function() { 514 | ok(!_.isNaN(undefined), 'undefined is not NaN'); 515 | ok(!_.isNaN(null), 'null is not NaN'); 516 | ok(!_.isNaN(0), '0 is not NaN'); 517 | ok(_.isNaN(NaN), 'but NaN is'); 518 | ok(_.isNaN(iNaN), 'even from another frame'); 519 | ok(_.isNaN(new Number(NaN)), 'wrapped NaN is still NaN'); 520 | }); 521 | 522 | test("isNull", function() { 523 | ok(!_.isNull(undefined), 'undefined is not null'); 524 | ok(!_.isNull(NaN), 'NaN is not null'); 525 | ok(_.isNull(null), 'but null is'); 526 | ok(_.isNull(iNull), 'even from another frame'); 527 | }); 528 | 529 | test("isUndefined", function() { 530 | ok(!_.isUndefined(1), 'numbers are defined'); 531 | ok(!_.isUndefined(null), 'null is defined'); 532 | ok(!_.isUndefined(false), 'false is defined'); 533 | ok(!_.isUndefined(NaN), 'NaN is defined'); 534 | ok(_.isUndefined(), 'nothing is undefined'); 535 | ok(_.isUndefined(undefined), 'undefined is undefined'); 536 | ok(_.isUndefined(iUndefined), 'even from another frame'); 537 | }); 538 | 539 | if (window.ActiveXObject) { 540 | test("IE host objects", function() { 541 | var xml = new ActiveXObject("Msxml2.DOMDocument.3.0"); 542 | ok(!_.isNumber(xml)); 543 | ok(!_.isBoolean(xml)); 544 | ok(!_.isNaN(xml)); 545 | ok(!_.isFunction(xml)); 546 | ok(!_.isNull(xml)); 547 | ok(!_.isUndefined(xml)); 548 | }); 549 | } 550 | 551 | test("tap", function() { 552 | var intercepted = null; 553 | var interceptor = function(obj) { intercepted = obj; }; 554 | var returned = _.tap(1, interceptor); 555 | equal(intercepted, 1, "passes tapped object to interceptor"); 556 | equal(returned, 1, "returns tapped object"); 557 | 558 | returned = _([1,2,3]).chain(). 559 | map(function(n){ return n * 2; }). 560 | max(). 561 | tap(interceptor). 562 | value(); 563 | ok(returned == 6 && intercepted == 6, 'can use tapped objects in a chain'); 564 | }); 565 | 566 | test("has", function () { 567 | var obj = {foo: "bar", func: function () {} }; 568 | ok (_.has(obj, "foo"), "has() checks that the object has a property."); 569 | ok (_.has(obj, "baz") == false, "has() returns false if the object doesn't have the property."); 570 | ok (_.has(obj, "func"), "has() works for functions too."); 571 | obj.hasOwnProperty = null; 572 | ok (_.has(obj, "foo"), "has() works even when the hasOwnProperty method is deleted."); 573 | var child = {}; 574 | child.prototype = obj; 575 | ok (_.has(child, "foo") == false, "has() does not check the prototype chain for a property.") 576 | }); 577 | }); 578 | -------------------------------------------------------------------------------- /test/speed.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | var numbers = []; 4 | for (var i=0; i<1000; i++) numbers.push(i); 5 | var objArray = _.map(numbers, function(n){ return {num : n}; }); 6 | var bigObj = {}; 7 | _.times(1000, function(n){ bigObj['key' + n] = n; }); 8 | var randomized = _.sortBy(numbers, function(){ return Math.random(); }); 9 | var deep = _.map(_.range(100), function() { return _.range(1000); }); 10 | 11 | JSLitmus.test('_.each()', function() { 12 | var timesTwo = []; 13 | _.each(numbers, function(num){ timesTwo.push(num * 2); }); 14 | return timesTwo; 15 | }); 16 | 17 | JSLitmus.test('_(list).each()', function() { 18 | var timesTwo = []; 19 | _(numbers).each(function(num){ timesTwo.push(num * 2); }); 20 | return timesTwo; 21 | }); 22 | 23 | JSLitmus.test('jQuery.each()', function() { 24 | var timesTwo = []; 25 | jQuery.each(numbers, function(){ timesTwo.push(this * 2); }); 26 | return timesTwo; 27 | }); 28 | 29 | JSLitmus.test('_.map()', function() { 30 | return _.map(objArray, function(obj){ return obj.num; }); 31 | }); 32 | 33 | JSLitmus.test('jQuery.map()', function() { 34 | return jQuery.map(objArray, function(obj){ return obj.num; }); 35 | }); 36 | 37 | JSLitmus.test('_.pluck()', function() { 38 | return _.pluck(objArray, 'num'); 39 | }); 40 | 41 | JSLitmus.test('_.uniq()', function() { 42 | return _.uniq(randomized); 43 | }); 44 | 45 | JSLitmus.test('_.uniq() (sorted)', function() { 46 | return _.uniq(numbers, true); 47 | }); 48 | 49 | JSLitmus.test('_.sortBy()', function() { 50 | return _.sortBy(numbers, function(num){ return -num; }); 51 | }); 52 | 53 | JSLitmus.test('_.isEqual()', function() { 54 | return _.isEqual(numbers, randomized); 55 | }); 56 | 57 | JSLitmus.test('_.keys()', function() { 58 | return _.keys(bigObj); 59 | }); 60 | 61 | JSLitmus.test('_.values()', function() { 62 | return _.values(bigObj); 63 | }); 64 | 65 | JSLitmus.test('_.intersection()', function() { 66 | return _.intersection(numbers, randomized); 67 | }); 68 | 69 | JSLitmus.test('_.range()', function() { 70 | return _.range(1000); 71 | }); 72 | 73 | JSLitmus.test('_.flatten()', function() { 74 | return _.flatten(deep); 75 | }); 76 | 77 | JSLitmus.test('_.flatten() (shallow)', function() { 78 | return _.flatten(deep, true); 79 | }); 80 | })(); 81 | -------------------------------------------------------------------------------- /test/utility.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | 3 | var templateSettings; 4 | 5 | module("Utility", { 6 | 7 | setup: function() { 8 | templateSettings = _.clone(_.templateSettings); 9 | }, 10 | 11 | teardown: function() { 12 | _.templateSettings = templateSettings; 13 | } 14 | 15 | }); 16 | 17 | test("#750 - Return _ instance.", 2, function() { 18 | var instance = _([]); 19 | ok(_(instance) === instance); 20 | ok(new _(instance) === instance); 21 | }); 22 | 23 | test("identity", function() { 24 | var moe = {name : 'moe'}; 25 | equal(_.identity(moe), moe, 'moe is the same as his identity'); 26 | }); 27 | 28 | test("random", function() { 29 | var array = _.range(1000); 30 | var min = Math.pow(2, 31); 31 | var max = Math.pow(2, 62); 32 | 33 | ok(_.every(array, function() { 34 | return _.random(min, max) >= min; 35 | }), "should produce a random number greater than or equal to the minimum number"); 36 | 37 | ok(_.some(array, function() { 38 | return _.random(Number.MAX_VALUE) > 0; 39 | }), "should produce a random number when passed `Number.MAX_VALUE`"); 40 | }); 41 | 42 | test("uniqueId", function() { 43 | var ids = [], i = 0; 44 | while(i++ < 100) ids.push(_.uniqueId()); 45 | equal(_.uniq(ids).length, ids.length, 'can generate a globally-unique stream of ids'); 46 | }); 47 | 48 | test("times", function() { 49 | var vals = []; 50 | _.times(3, function (i) { vals.push(i); }); 51 | ok(_.isEqual(vals, [0,1,2]), "is 0 indexed"); 52 | // 53 | vals = []; 54 | _(3).times(function(i) { vals.push(i); }); 55 | ok(_.isEqual(vals, [0,1,2]), "works as a wrapper"); 56 | // collects return values 57 | ok(_.isEqual([0, 1, 2], _.times(3, function(i) { return i; })), "collects return values"); 58 | 59 | deepEqual(_.times(0, _.identity), []); 60 | deepEqual(_.times(-1, _.identity), []); 61 | deepEqual(_.times(parseFloat('-Infinity'), _.identity), []); 62 | }); 63 | 64 | test("mixin", function() { 65 | _.mixin({ 66 | myReverse: function(string) { 67 | return string.split('').reverse().join(''); 68 | } 69 | }); 70 | equal(_.myReverse('panacea'), 'aecanap', 'mixed in a function to _'); 71 | equal(_('champ').myReverse(), 'pmahc', 'mixed in a function to the OOP wrapper'); 72 | }); 73 | 74 | test("_.escape", function() { 75 | equal(_.escape("Curly & Moe"), "Curly & Moe"); 76 | equal(_.escape('Curly & Moe\'s'), '<a href="http://moe.com">Curly & Moe's</a>'); 77 | equal(_.escape("Curly & Moe"), "Curly &amp; Moe"); 78 | equal(_.escape(null), ''); 79 | }); 80 | 81 | test("_.unescape", function() { 82 | var string = "Curly & Moe"; 83 | equal(_.unescape("Curly & Moe"), string); 84 | equal(_.unescape('<a href="http://moe.com">Curly & Moe's</a>'), 'Curly & Moe\'s'); 85 | equal(_.unescape("Curly &amp; Moe"), "Curly & Moe"); 86 | equal(_.unescape(null), ''); 87 | equal(_.unescape(_.escape(string)), string); 88 | }); 89 | 90 | test("template", function() { 91 | var basicTemplate = _.template("<%= thing %> is gettin' on my noives!"); 92 | var result = basicTemplate({thing : 'This'}); 93 | equal(result, "This is gettin' on my noives!", 'can do basic attribute interpolation'); 94 | 95 | var sansSemicolonTemplate = _.template("A <% this %> B"); 96 | equal(sansSemicolonTemplate(), "A B"); 97 | 98 | var backslashTemplate = _.template("<%= thing %> is \\ridanculous"); 99 | equal(backslashTemplate({thing: 'This'}), "This is \\ridanculous"); 100 | 101 | var escapeTemplate = _.template('<%= a ? "checked=\\"checked\\"" : "" %>'); 102 | equal(escapeTemplate({a: true}), 'checked="checked"', 'can handle slash escapes in interpolations.'); 103 | 104 | var fancyTemplate = _.template("
    <% \ 105 | for (var key in people) { \ 106 | %>
  • <%= people[key] %>
  • <% } %>
"); 107 | result = fancyTemplate({people : {moe : "Moe", larry : "Larry", curly : "Curly"}}); 108 | equal(result, "
  • Moe
  • Larry
  • Curly
", 'can run arbitrary javascript in templates'); 109 | 110 | var escapedCharsInJavascriptTemplate = _.template("
    <% _.each(numbers.split('\\n'), function(item) { %>
  • <%= item %>
  • <% }) %>
"); 111 | result = escapedCharsInJavascriptTemplate({numbers: "one\ntwo\nthree\nfour"}); 112 | equal(result, "
  • one
  • two
  • three
  • four
", 'Can use escaped characters (e.g. \\n) in Javascript'); 113 | 114 | var namespaceCollisionTemplate = _.template("<%= pageCount %> <%= thumbnails[pageCount] %> <% _.each(thumbnails, function(p) { %>
\">
<% }); %>"); 115 | result = namespaceCollisionTemplate({ 116 | pageCount: 3, 117 | thumbnails: { 118 | 1: "p1-thumbnail.gif", 119 | 2: "p2-thumbnail.gif", 120 | 3: "p3-thumbnail.gif" 121 | } 122 | }); 123 | equal(result, "3 p3-thumbnail.gif
"); 124 | 125 | var noInterpolateTemplate = _.template("

Just some text. Hey, I know this is silly but it aids consistency.

"); 126 | result = noInterpolateTemplate(); 127 | equal(result, "

Just some text. Hey, I know this is silly but it aids consistency.

"); 128 | 129 | var quoteTemplate = _.template("It's its, not it's"); 130 | equal(quoteTemplate({}), "It's its, not it's"); 131 | 132 | var quoteInStatementAndBody = _.template("<%\ 133 | if(foo == 'bar'){ \ 134 | %>Statement quotes and 'quotes'.<% } %>"); 135 | equal(quoteInStatementAndBody({foo: "bar"}), "Statement quotes and 'quotes'."); 136 | 137 | var withNewlinesAndTabs = _.template('This\n\t\tis: <%= x %>.\n\tok.\nend.'); 138 | equal(withNewlinesAndTabs({x: 'that'}), 'This\n\t\tis: that.\n\tok.\nend.'); 139 | 140 | var template = _.template("<%- value %>"); 141 | var result = template({value: "