Added node-modules

This commit is contained in:
Dobie Wollert
2014-09-14 07:04:16 -04:00
parent 663941bf57
commit 6a92348cf5
4870 changed files with 670395 additions and 0 deletions

View File

@ -0,0 +1,8 @@
language: ruby
rvm:
- 1.9.3
before_script:
- "export DISPLAY=:99.0"
- "sh -e /etc/init.d/xvfb start"
- sleep 2

View File

@ -0,0 +1,5 @@
source :rubygems
gem 'serve'
gem 'uglifier'
gem 'rake'

View File

@ -0,0 +1,34 @@
GEM
remote: http://rubygems.org/
specs:
activesupport (3.2.3)
i18n (~> 0.6)
multi_json (~> 1.0)
execjs (1.3.0)
multi_json (~> 1.0)
i18n (0.6.0)
multi_json (1.2.0)
rack (1.4.1)
rack-test (0.6.1)
rack (>= 1.0)
rake (0.9.2.2)
serve (1.5.1)
activesupport (~> 3.0)
i18n
rack (~> 1.2)
rack-test (~> 0.5)
tilt (~> 1.3)
tzinfo
tilt (1.3.3)
tzinfo (0.3.33)
uglifier (1.2.4)
execjs (>= 0.3.0)
multi_json (>= 1.0.2)
PLATFORMS
ruby
DEPENDENCIES
rake
serve
uglifier

View File

@ -0,0 +1,668 @@
# Underscore.string [![Build Status](https://secure.travis-ci.org/epeli/underscore.string.png?branch=master)](http://travis-ci.org/epeli/underscore.string) #
Javascript lacks complete string manipulation operations.
This an attempt to fill that gap. List of build-in methods can be found
for example from [Dive Into JavaScript][d].
[d]: http://www.diveintojavascript.com/core-javascript-reference/the-string-object
As name states this an extension for [Underscore.js][u], but it can be used
independently from **_s**-global variable. But with Underscore.js you can
use Object-Oriented style and chaining:
[u]: http://documentcloud.github.com/underscore/
```javascript
_(" epeli ").chain().trim().capitalize().value()
=> "Epeli"
```
## Download ##
* [Development version](https://raw.github.com/epeli/underscore.string/master/lib/underscore.string.js) *Uncompressed with Comments 18kb*
* [Production version](https://github.com/epeli/underscore.string/raw/master/dist/underscore.string.min.js) *Minified 7kb*
## Node.js installation ##
**npm package**
npm install underscore.string
**Standalone usage**:
```javascript
var _s = require('underscore.string');
```
**Integrate with Underscore.js**:
```javascript
var _ = require('underscore');
// Import Underscore.string to separate object, because there are conflict functions (include, reverse, contains)
_.str = require('underscore.string');
// Mix in non-conflict functions to Underscore namespace if you want
_.mixin(_.str.exports());
// All functions, include conflict, will be available through _.str object
_.str.include('Underscore.string', 'string'); // => true
```
## String Functions ##
For availability of functions in this way you need to mix in Underscore.string functions:
```javascript
_.mixin(_.string.exports());
```
otherwise functions from examples will be available through _.string or _.str objects:
```javascript
_.str.capitalize('epeli')
=> "Epeli"
```
**capitalize** _.capitalize(string)
Converts first letter of the string to uppercase.
```javascript
_.capitalize("foo Bar")
=> "Foo Bar"
```
**chop** _.chop(string, step)
```javascript
_.chop('whitespace', 3)
=> ['whi','tes','pac','e']
```
**clean** _.clean(str)
Compress some whitespaces to one.
```javascript
_.clean(" foo bar ")
=> 'foo bar'
```
**chars** _.chars(str)
```javascript
_.chars('Hello')
=> ['H','e','l','l','o']
```
**includes** _.includes(string, substring)
Tests if string contains a substring.
```javascript
_.includes("foobar", "ob")
=> true
```
**include** available only through _.str object, because Underscore has function with the same name.
```javascript
_.str.include("foobar", "ob")
=> true
```
**includes** function was removed
But you can create it in this way, for compatibility with previous versions:
```javascript
_.includes = _.str.include
```
**count** _.count(string, substring)
```javascript
_('Hello world').count('l')
=> 3
```
**escapeHTML** _.escapeHTML(string)
Converts HTML special characters to their entity equivalents.
```javascript
_('<div>Blah blah blah</div>').escapeHTML();
=> '&lt;div&gt;Blah blah blah&lt;/div&gt;'
```
**unescapeHTML** _.unescapeHTML(string)
Converts entity characters to HTML equivalents.
```javascript
_('&lt;div&gt;Blah blah blah&lt;/div&gt;').unescapeHTML();
=> '<div>Blah blah blah</div>'
```
**insert** _.insert(string, index, substing)
```javascript
_('Hello ').insert(6, 'world')
=> 'Hello world'
```
**isBlank** _.isBlank(string)
```javascript
_('').isBlank(); // => true
_('\n').isBlank(); // => true
_(' ').isBlank(); // => true
_('a').isBlank(); // => false
```
**join** _.join(separator, *strings)
Joins strings together with given separator
```javascript
_.join(" ", "foo", "bar")
=> "foo bar"
```
**lines** _.lines(str)
```javascript
_.lines("Hello\nWorld")
=> ["Hello", "World"]
```
**reverse** available only through _.str object, because Underscore has function with the same name.
Return reversed string:
```javascript
_.str.reverse("foobar")
=> 'raboof'
```
**splice** _.splice(string, index, howmany, substring)
Like a array splice.
```javascript
_('https://edtsech@bitbucket.org/edtsech/underscore.strings').splice(30, 7, 'epeli')
=> 'https://edtsech@bitbucket.org/epeli/underscore.strings'
```
**startsWith** _.startsWith(string, starts)
This method checks whether string starts with starts.
```javascript
_("image.gif").startsWith("image")
=> true
```
**endsWith** _.endsWith(string, ends)
This method checks whether string ends with ends.
```javascript
_("image.gif").endsWith("gif")
=> true
```
**succ** _.succ(str)
Returns the successor to str.
```javascript
_('a').succ()
=> 'b'
_('A').succ()
=> 'B'
```
**supplant**
Supplant function was removed, use Underscore.js [template function][p].
[p]: http://documentcloud.github.com/underscore/#template
**strip** alias for *trim*
**lstrip** alias for *ltrim*
**rstrip** alias for *rtrim*
**titleize** _.titleize(string)
```javascript
_('my name is epeli').titleize()
=> 'My Name Is Epeli'
```
**camelize** _.camelize(string)
Converts underscored or dasherized string to a camelized one
```javascript
_('-moz-transform').camelize()
=> 'MozTransform'
```
**classify** _.classify(string)
Converts string to camelized class name
```javascript
_('some_class_name').classify()
=> 'SomeClassName'
```
**underscored** _.underscored(string)
Converts a camelized or dasherized string into an underscored one
```javascript
_('MozTransform').underscored()
=> 'moz_transform'
```
**dasherize** _.dasherize(string)
Converts a underscored or camelized string into an dasherized one
```javascript
_('MozTransform').dasherize()
=> '-moz-transform'
```
**humanize** _.humanize(string)
Converts an underscored, camelized, or dasherized string into a humanized one.
Also removes beginning and ending whitespace, and removes the postfix '_id'.
```javascript
_(' capitalize dash-CamelCase_underscore trim ').humanize()
=> 'Capitalize dash camel case underscore trim'
```
**trim** _.trim(string, [characters])
trims defined characters from begining and ending of the string.
Defaults to whitespace characters.
```javascript
_.trim(" foobar ")
=> "foobar"
_.trim("_-foobar-_", "_-")
=> "foobar"
```
**ltrim** _.ltrim(string, [characters])
Left trim. Similar to trim, but only for left side.
**rtrim** _.rtrim(string, [characters])
Right trim. Similar to trim, but only for right side.
**truncate** _.truncate(string, length, truncateString)
```javascript
_('Hello world').truncate(5)
=> 'Hello...'
_('Hello').truncate(10)
=> 'Hello'
```
**prune** _.prune(string, length, pruneString)
Elegant version of truncate.
Makes sure the pruned string does not exceed the original length.
Avoid half-chopped words when truncating.
```javascript
_('Hello, world').prune(5)
=> 'Hello...'
_('Hello, world').prune(8)
=> 'Hello...'
_('Hello, world').prune(5, ' (read a lot more)')
=> 'Hello, world' (as adding "(read a lot more)" would be longer than the original string)
_('Hello, cruel world').prune(15)
=> 'Hello, cruel...'
_('Hello').prune(10)
=> 'Hello'
```
**words** _.words(str, delimiter=" ")
Split string by delimiter (String or RegExp), ' ' by default.
```javascript
_.words("I love you")
=> ["I","love","you"]
_.words("I_love_you", "_")
=> ["I","love","you"]
_.words("I-love-you", /-/)
=> ["I","love","you"]
```
**sprintf** _.sprintf(string format, *arguments)
C like string formatting.
Credits goes to [Alexandru Marasteanu][o].
For more detailed documentation, see the [original page][o].
[o]: http://www.diveintojavascript.com/projects/sprintf-for-javascript
```javascript
_.sprintf("%.1f", 1.17)
"1.2"
```
**pad** _.pad(str, length, [padStr, type])
pads the `str` with characters until the total string length is equal to the passed `length` parameter. By default, pads on the **left** with the space char (`" "`). `padStr` is truncated to a single character if necessary.
```javascript
_.pad("1", 8)
-> " 1";
_.pad("1", 8, '0')
-> "00000001";
_.pad("1", 8, '0', 'right')
-> "10000000";
_.pad("1", 8, '0', 'both')
-> "00001000";
_.pad("1", 8, 'bleepblorp', 'both')
-> "bbbb1bbb";
```
**lpad** _.lpad(str, length, [padStr])
left-pad a string. Alias for `pad(str, length, padStr, 'left')`
```javascript
_.lpad("1", 8, '0')
-> "00000001";
```
**rpad** _.rpad(str, length, [padStr])
right-pad a string. Alias for `pad(str, length, padStr, 'right')`
```javascript
_.rpad("1", 8, '0')
-> "10000000";
```
**lrpad** _.lrpad(str, length, [padStr])
left/right-pad a string. Alias for `pad(str, length, padStr, 'both')`
```javascript
_.lrpad("1", 8, '0')
-> "00001000";
```
**center** alias for **lrpad**
**ljust** alias for *rpad*
**rjust** alias for *lpad*
**toNumber** _.toNumber(string, [decimals])
Parse string to number. Returns NaN if string can't be parsed to number.
```javascript
_('2.556').toNumber()
=> 3
_('2.556').toNumber(1)
=> 2.6
```
**strRight** _.strRight(string, pattern)
Searches a string from left to right for a pattern and returns a substring consisting of the characters in the string that are to the right of the pattern or all string if no match found.
```javascript
_('This_is_a_test_string').strRight('_')
=> "is_a_test_string";
```
**strRightBack** _.strRightBack(string, pattern)
Searches a string from right to left for a pattern and returns a substring consisting of the characters in the string that are to the right of the pattern or all string if no match found.
```javascript
_('This_is_a_test_string').strRightBack('_')
=> "string";
```
**strLeft** _.strLeft(string, pattern)
Searches a string from left to right for a pattern and returns a substring consisting of the characters in the string that are to the left of the pattern or all string if no match found.
```javascript
_('This_is_a_test_string').strLeft('_')
=> "This";
```
**strLeftBack** _.strLeftBack(string, pattern)
Searches a string from right to left for a pattern and returns a substring consisting of the characters in the string that are to the left of the pattern or all string if no match found.
```javascript
_('This_is_a_test_string').strLeftBack('_')
=> "This_is_a_test";
```
**stripTags**
Removes all html tags from string.
```javascript
_('a <a href="#">link</a>').stripTags()
=> 'a link'
_('a <a href="#">link</a><script>alert("hello world!")</script>').stripTags()
=> 'a linkalert("hello world!")'
```
**toSentence** _.toSentence(array, [delimiter, lastDelimiter])
Join an array into a human readable sentence.
```javascript
_.toSentence(['jQuery', 'Mootools', 'Prototype'])
=> 'jQuery, Mootools and Prototype';
_.toSentence(['jQuery', 'Mootools', 'Prototype'], ', ', ' unt ')
=> 'jQuery, Mootools unt Prototype';
```
**repeat** _.repeat(string, count, [separator])
Repeats a string count times.
```javascript
_.repeat("foo", 3)
=> 'foofoofoo';
_.repeat("foo", 3, "bar")
=> 'foobarfoobarfoo'
```
**slugify** _.slugify(string)
Transform text into a URL slug. Replaces whitespaces, accentuated, and special characters with a dash.
```javascript
_.slugify("Un éléphant à l'orée du bois")
=> 'un-elephant-a-loree-du-bois';
```
***Caution: this function is charset dependent***
## Roadmap ##
Any suggestions or bug reports are welcome. Just email me or more preferably open an issue.
## Changelog ##
### 2.0.0 ###
* Added prune, humanize functions
* Added _.string (_.str) namespace for Underscore.string library
* Removed includes function
#### Problems
We lose two things for `include` and `reverse` methods from `_.string`:
* Calls like `_('foobar').include('bar')` aren't available;
* Chaining isn't available too.
But if you need this functionality you can create aliases for conflict functions which will be convenient for you:
```javascript
_.mixin({
includeString: _.str.include,
reverseString: _.str.reverse
})
// Now wrapper calls and chaining are available.
_('foobar').chain().reverseString().includeString('rab').value()
```
#### Standalone Usage
If you are using Underscore.string without Underscore. You also have `_.string` namespace for it and `_.str` alias
But of course you can just reassign `_` variable with `_.string`
```javascript
_ = _.string
```
### 2.2.0 ###
* Capitalize method behavior changed
* Various perfomance tweaks
### 2.1.1###
* Fixed words method bug
* Added classify method
### 2.1.0 ###
* AMD support
* Added toSentence method
* Added slugify method
* Lots of speed optimizations
### 2.0.0 ###
For upgrading to this version you need to mix in Underscore.string library to Underscore object:
```javascript
_.mixin(_.string.exports());
```
and all non-conflict Underscore.string functions will be available through Underscore object.
Also function `includes` has been removed, you should replace this function by `_.str.include`
or create alias `_.includes = _.str.include` and all your code will work fine.
### 1.1.6 ###
* Fixed reverse and truncate
* Added isBlank, stripTags, inlude(alias for includes)
* Added uglifier compression
### 1.1.5 ###
* Added strRight, strRightBack, strLeft, strLeftBack
### 1.1.4 ###
* Added pad, lpad, rpad, lrpad methods and aliases center, ljust, rjust
* Integration with Underscore 1.1.6
### 1.1.3 ###
* Added methods: underscored, camelize, dasherize
* Support newer version of npm
### 1.1.2 ###
* Created functions: lines, chars, words functions
### 1.0.2 ###
* Created integration test suite with underscore.js 1.1.4 (now it's absolutely compatible)
* Removed 'reverse' function, because this function override underscore.js 'reverse'
## Contribute ##
* Fork & pull request. Don't forget about tests.
* If you planning add some feature please create issue before.
Otherwise changes will be rejected.
## Contributors list ##
* Esa-Matti Suuronen <esa-matti@suuronen.org> (<http://esa-matti.suuronen.org/>),
* Edward Tsech <edtsech@gmail.com>,
* Sasha Koss <kossnocorp@gmail.com> (<http://koss.nocorp.me/>),
* Vladimir Dronnikov <dronnikov@gmail.com>,
* Pete Kruckenberg (<https://github.com/kruckenb>),
* Paul Chavard <paul@chavard.net> (<http://tchak.net>),
* Ed Finkler <coj@funkatron.com> (<http://funkatron.com>)
* Pavel Pravosud <rwz@duckroll.ru>
* Anton Lindqvist <anton@qvister.se> (<http://qvister.se>)
## Licence ##
The MIT License
Copyright (c) 2011 Esa-Matti Suuronen esa-matti@suuronen.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,28 @@
# encoding: utf-8
task default: :test
desc 'Use UglifyJS to compress Underscore.string'
task :build do
require 'uglifier'
source = File.read('lib/underscore.string.js')
compressed = Uglifier.compile(source, copyright: false)
File.open('dist/underscore.string.min.js', 'w'){ |f| f.write compressed }
compression_rate = compressed.length.to_f/source.length
puts "compressed dist/underscore.string.min.js: #{compressed.length}/#{source.length} #{(compression_rate * 100).round}%"
end
desc 'Run tests'
task :test do
pid = spawn('bundle exec serve', err: '/dev/null')
sleep 2
puts "Running underscore.string test suite."
result1 = system %{phantomjs ./test/run-qunit.js "http://localhost:4000/test/test.html"}
puts "Running Underscore test suite."
result2 = system %{phantomjs ./test/run-qunit.js "http://localhost:4000/test/test_underscore/test.html"}
Process.kill 'INT', pid
exit(result1 && result2 ? 0 : 1)
end

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,10 @@
function boolMatch(s, matchers) {
var i, matcher, down = s.toLowerCase();
matchers = [].concat(matchers);
for (i = 0; i < matchers.length; i += 1) {
matcher = matchers[i];
if (matcher.test && matcher.test(s)) return true;
if (matcher && matcher.toLowerCase() === down) return true;
}
}

View File

@ -0,0 +1,525 @@
// Underscore.string
// (c) 2010 Esa-Matti Suuronen <esa-matti aet suuronen dot org>
// Underscore.strings is freely distributable under the terms of the MIT license.
// Documentation: https://github.com/epeli/underscore.string
// Some code is borrowed from MooTools and Alexandru Marasteanu.
// Version 2.2.0rc
(function(root){
'use strict';
// Defining helper functions.
var nativeTrim = String.prototype.trim;
var nativeTrimRight = String.prototype.trimRight;
var nativeTrimLeft = String.prototype.trimLeft;
var parseNumber = function(source) { return source * 1 || 0; };
var strRepeat = function(str, qty, separator){
// ~~var — is the fastest available way to convert anything to Integer in javascript.
// We'll use it extensively in this lib.
str += ''; qty = ~~qty;
for (var repeat = []; qty > 0; repeat[--qty] = str) {}
return repeat.join(separator == null ? '' : separator);
};
var slice = function(a){
return Array.prototype.slice.call(a);
};
var defaultToWhiteSpace = function(characters){
if (characters != null) {
return '[' + _s.escapeRegExp(''+characters) + ']';
}
return '\\s';
};
var escapeChars = {
lt: '<',
gt: '>',
quot: '"',
apos: "'",
amp: '&'
};
var reversedEscapeChars = {};
for(var key in escapeChars){ reversedEscapeChars[escapeChars[key]] = key; }
// sprintf() for JavaScript 0.7-beta1
// http://www.diveintojavascript.com/projects/javascript-sprintf
//
// Copyright (c) Alexandru Marasteanu <alexaholic [at) gmail (dot] com>
// All rights reserved.
var sprintf = (function() {
function get_type(variable) {
return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase();
}
var str_repeat = strRepeat;
var str_format = function() {
if (!str_format.cache.hasOwnProperty(arguments[0])) {
str_format.cache[arguments[0]] = str_format.parse(arguments[0]);
}
return str_format.format.call(null, str_format.cache[arguments[0]], arguments);
};
str_format.format = function(parse_tree, argv) {
var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length;
for (i = 0; i < tree_length; i++) {
node_type = get_type(parse_tree[i]);
if (node_type === 'string') {
output.push(parse_tree[i]);
}
else if (node_type === 'array') {
match = parse_tree[i]; // convenience purposes only
if (match[2]) { // keyword argument
arg = argv[cursor];
for (k = 0; k < match[2].length; k++) {
if (!arg.hasOwnProperty(match[2][k])) {
throw new Error(sprintf('[_.sprintf] property "%s" does not exist', match[2][k]));
}
arg = arg[match[2][k]];
}
} else if (match[1]) { // positional argument (explicit)
arg = argv[match[1]];
}
else { // positional argument (implicit)
arg = argv[cursor++];
}
if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) {
throw new Error(sprintf('[_.sprintf] expecting number but found %s', get_type(arg)));
}
switch (match[8]) {
case 'b': arg = arg.toString(2); break;
case 'c': arg = String.fromCharCode(arg); break;
case 'd': arg = parseInt(arg, 10); break;
case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break;
case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break;
case 'o': arg = arg.toString(8); break;
case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break;
case 'u': arg = Math.abs(arg); break;
case 'x': arg = arg.toString(16); break;
case 'X': arg = arg.toString(16).toUpperCase(); break;
}
arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg);
pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' ';
pad_length = match[6] - String(arg).length;
pad = match[6] ? str_repeat(pad_character, pad_length) : '';
output.push(match[5] ? arg + pad : pad + arg);
}
}
return output.join('');
};
str_format.cache = {};
str_format.parse = function(fmt) {
var _fmt = fmt, match = [], parse_tree = [], arg_names = 0;
while (_fmt) {
if ((match = /^[^\x25]+/.exec(_fmt)) !== null) {
parse_tree.push(match[0]);
}
else if ((match = /^\x25{2}/.exec(_fmt)) !== null) {
parse_tree.push('%');
}
else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) {
if (match[2]) {
arg_names |= 1;
var field_list = [], replacement_field = match[2], field_match = [];
if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
field_list.push(field_match[1]);
while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
field_list.push(field_match[1]);
}
else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) {
field_list.push(field_match[1]);
}
else {
throw new Error('[_.sprintf] huh?');
}
}
}
else {
throw new Error('[_.sprintf] huh?');
}
match[2] = field_list;
}
else {
arg_names |= 2;
}
if (arg_names === 3) {
throw new Error('[_.sprintf] mixing positional and named placeholders is not (yet) supported');
}
parse_tree.push(match);
}
else {
throw new Error('[_.sprintf] huh?');
}
_fmt = _fmt.substring(match[0].length);
}
return parse_tree;
};
return str_format;
})();
// Defining underscore.string
var _s = {
VERSION: '2.2.0rc',
isBlank: function(str){
return (/^\s*$/).test(str);
},
stripTags: function(str){
return (''+str).replace(/<\/?[^>]+>/g, '');
},
capitalize : function(str) {
str += '';
return str.charAt(0).toUpperCase() + str.substring(1);
},
chop: function(str, step){
str = str+'';
step = ~~step || str.length;
var arr = [];
for (var i = 0; i < str.length; i += step)
arr.push(str.slice(i,i + step));
return arr;
},
clean: function(str){
return _s.strip(str).replace(/\s+/g, ' ');
},
count: function(str, substr){
str += ''; substr += '';
return str.split(substr).length - 1;
},
chars: function(str) {
return (''+str).split('');
},
escapeHTML: function(str) {
return (''+str).replace(/[&<>"']/g, function(match){ return '&' + reversedEscapeChars[match] + ';'; });
},
unescapeHTML: function(str) {
return (''+str).replace(/\&([^;]+);/g, function(entity, entityCode){
var match;
if (entityCode in escapeChars) {
return escapeChars[entityCode];
} else if (match = entityCode.match(/^#x([\da-fA-F]+)$/)) {
return String.fromCharCode(parseInt(match[1], 16));
} else if (match = entityCode.match(/^#(\d+)$/)) {
return String.fromCharCode(~~match[1]);
} else {
return entity;
}
});
},
escapeRegExp: function(str){
// From MooTools core 1.2.4
return str.replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1');
},
insert: function(str, i, substr){
var arr = _s.chars(str);
arr.splice(~~i, 0, ''+substr);
return arr.join('');
},
include: function(str, needle){
return !!~(''+str).indexOf(needle);
},
join: function() {
var args = slice(arguments);
return args.join(args.shift());
},
lines: function(str) {
return (''+str).split("\n");
},
reverse: function(str){
return _s.chars(str).reverse().join('');
},
splice: function(str, i, howmany, substr){
var arr = _s.chars(str);
arr.splice(~~i, ~~howmany, substr);
return arr.join('');
},
startsWith: function(str, starts){
str += ''; starts += '';
return str.length >= starts.length && str.substring(0, starts.length) === starts;
},
endsWith: function(str, ends){
str += ''; ends += '';
return str.length >= ends.length && str.substring(str.length - ends.length) === ends;
},
succ: function(str){
str += '';
var arr = _s.chars(str);
arr.splice(str.length-1, 1, String.fromCharCode(str.charCodeAt(str.length-1) + 1));
return arr.join('');
},
titleize: function(str){
return (''+str).replace(/\b./g, function(ch){ return ch.toUpperCase(); });
},
camelize: function(str){
return _s.trim(str).replace(/[-_\s]+(.)?/g, function(match, chr){
return chr && chr.toUpperCase();
});
},
underscored: function(str){
return _s.trim(str).replace(/([a-z\d])([A-Z]+)/g, '$1_$2').replace(/[-\s]+/g, '_').toLowerCase();
},
dasherize: function(str){
return _s.trim(str).replace(/[_\s]+/g, '-').replace(/([A-Z])/g, '-$1').replace(/-+/g, '-').toLowerCase();
},
classify: function(str){
str += '';
return _s.titleize(str.replace(/_/g, ' ')).replace(/\s/g, '')
},
humanize: function(str){
return _s.capitalize(this.underscored(str).replace(/_id$/,'').replace(/_/g, ' '));
},
trim: function(str, characters){
str += '';
if (!characters && nativeTrim) { return nativeTrim.call(str); }
characters = defaultToWhiteSpace(characters);
return str.replace(new RegExp('\^' + characters + '+|' + characters + '+$', 'g'), '');
},
ltrim: function(str, characters){
str+='';
if (!characters && nativeTrimLeft) {
return nativeTrimLeft.call(str);
}
characters = defaultToWhiteSpace(characters);
return str.replace(new RegExp('^' + characters + '+'), '');
},
rtrim: function(str, characters){
str+='';
if (!characters && nativeTrimRight) {
return nativeTrimRight.call(str);
}
characters = defaultToWhiteSpace(characters);
return str.replace(new RegExp(characters + '+$'), '');
},
truncate: function(str, length, truncateStr){
str += ''; truncateStr = truncateStr || '...';
length = ~~length;
return str.length > length ? str.slice(0, length) + truncateStr : str;
},
/**
* _s.prune: a more elegant version of truncate
* prune extra chars, never leaving a half-chopped word.
* @author github.com/sergiokas
*/
prune: function(str, length, pruneStr){
str += ''; length = ~~length;
pruneStr = pruneStr != null ? ''+pruneStr : '...';
var pruned, borderChar, template = str.replace(/\W/g, function(ch){
return (ch.toUpperCase() !== ch.toLowerCase()) ? 'A' : ' ';
});
borderChar = template.charAt(length);
pruned = template.slice(0, length);
// Check if we're in the middle of a word
if (borderChar && borderChar.match(/\S/))
pruned = pruned.replace(/\s\S+$/, '');
pruned = _s.rtrim(pruned);
return (pruned+pruneStr).length > str.length ? str : str.substring(0, pruned.length)+pruneStr;
},
words: function(str, delimiter) {
return _s.trim(str, delimiter).split(delimiter || /\s+/);
},
pad: function(str, length, padStr, type) {
str += '';
var padlen = 0;
length = ~~length;
if (!padStr) {
padStr = ' ';
} else if (padStr.length > 1) {
padStr = padStr.charAt(0);
}
switch(type) {
case 'right':
padlen = (length - str.length);
return str + strRepeat(padStr, padlen);
case 'both':
padlen = (length - str.length);
return strRepeat(padStr, Math.ceil(padlen/2)) +
str +
strRepeat(padStr, Math.floor(padlen/2));
default: // 'left'
padlen = (length - str.length);
return strRepeat(padStr, padlen) + str;
}
},
lpad: function(str, length, padStr) {
return _s.pad(str, length, padStr);
},
rpad: function(str, length, padStr) {
return _s.pad(str, length, padStr, 'right');
},
lrpad: function(str, length, padStr) {
return _s.pad(str, length, padStr, 'both');
},
sprintf: sprintf,
vsprintf: function(fmt, argv){
argv.unshift(fmt);
return sprintf.apply(null, argv);
},
toNumber: function(str, decimals) {
str += '';
var num = parseNumber(parseNumber(str).toFixed(~~decimals));
return num === 0 && !str.match(/^0+$/) ? Number.NaN : num;
},
strRight: function(str, sep){
str += ''; sep = sep != null ? ''+sep : sep;
var pos = !sep ? -1 : str.indexOf(sep);
return ~pos ? str.slice(pos+sep.length, str.length) : str;
},
strRightBack: function(str, sep){
str += ''; sep = sep != null ? ''+sep : sep;
var pos = !sep ? -1 : str.lastIndexOf(sep);
return ~pos ? str.slice(pos+sep.length, str.length) : str;
},
strLeft: function(str, sep){
str += ''; sep = sep != null ? ''+sep : sep;
var pos = !sep ? -1 : str.indexOf(sep);
return ~pos ? str.slice(0, pos) : str;
},
strLeftBack: function(str, sep){
str += ''; sep = sep != null ? ''+sep : sep;
var pos = str.lastIndexOf(sep);
return ~pos ? str.slice(0, pos) : str;
},
toSentence: function(array, separator, lastSeparator) {
separator || (separator = ', ');
lastSeparator || (lastSeparator = ' and ');
var length = array.length, str = '';
for (var i = 0; i < length; i++) {
str += array[i];
if (i === (length - 2)) { str += lastSeparator; }
else if (i < (length - 1)) { str += separator; }
}
return str;
},
slugify: function(str) {
var from = "ąàáäâãćęèéëêìíïîłńòóöôõùúüûñçżź",
to = "aaaaaaceeeeeiiiilnooooouuuunczz",
regex = new RegExp(defaultToWhiteSpace(from), 'g');
str = (''+str).toLowerCase();
str = str.replace(regex, function(ch){
var index = from.indexOf(ch);
return to.charAt(index) || '-';
});
return _s.trim(str.replace(/[^\w\s-]/g, '').replace(/[-\s]+/g, '-'), '-');
},
exports: function() {
var result = {};
for (var prop in this) {
if (!this.hasOwnProperty(prop) || ~_s.words('include contains reverse').indexOf(prop)) continue;
result[prop] = this[prop];
}
return result;
},
repeat: strRepeat
};
// Aliases
_s.strip = _s.trim;
_s.lstrip = _s.ltrim;
_s.rstrip = _s.rtrim;
_s.center = _s.lrpad;
_s.rjust = _s.lpad;
_s.ljust = _s.rpad;
_s.contains = _s.include;
// CommonJS module is defined
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
// Export module
module.exports = _s;
}
exports._s = _s;
} else if (typeof define === 'function' && define.amd) {
// Register as a named module with AMD.
define('underscore.string', function() {
return _s;
});
} else {
// Integrate with Underscore.js if defined
// or create our own underscore object.
root._ = root._ || {};
root._.string = root._.str = _s;
}
}(this || window));

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,44 @@
function waitFor(test, complete, timeout) {
var result, start = new Date().getTime()
setInterval(function interval() {
if ((new Date().getTime() - start < timeout) && !result) {
result = test()
} else {
if (!result) {
phantom.exit(1)
} else {
complete()
clearInterval(interval)
}
}
}, 100)
}
var page = new WebPage()
page.onConsoleMessage = function(msg) {
console.log(msg)
}
page.open(phantom.args[0], function(status) {
waitFor(function() {
return page.evaluate(function(){
var el = document.getElementById('qunit-testresult')
return el && el.innerText.match('completed')
})
}, function() {
var failures = page.evaluate(function() {
var el = document.getElementById('qunit-testresult'),
fails = document.getElementsByClassName('fail')
for (var i = 0; i < fails.length; i++)
console.log(fails[i].innerText)
console.log(el.innerText)
return parseInt(el.getElementsByClassName('failed')[0].innerHTML)
})
phantom.exit(failures > 0 ? 1 : 0)
}, 10000)
})

View File

@ -0,0 +1,138 @@
(function() {
JSLitmus.test('trimNoNative', function() {
return _.trim(" foobar ", " ");
});
JSLitmus.test('trim', function() {
return _.trim(" foobar ");
});
JSLitmus.test('trim object-oriented', function() {
return _(" foobar ").trim();
});
JSLitmus.test('trim jQuery', function() {
return jQuery.trim(" foobar ");
});
JSLitmus.test('ltrimp', function() {
return _.ltrim(" foobar ", " ");
});
JSLitmus.test('rtrimp', function() {
return _.rtrim(" foobar ", " ");
});
JSLitmus.test('startsWith', function() {
return _.startsWith("foobar", "foo");
});
JSLitmus.test('endsWith', function() {
return _.endsWith("foobar", "xx");
});
JSLitmus.test('chop', function(){
return _('whitespace').chop(2);
});
JSLitmus.test('count', function(){
return _('Hello worls').count('l');
});
JSLitmus.test('insert', function() {
return _('Hello ').insert(6, 'world');
});
JSLitmus.test('splice', function() {
return _('https://edtsech@bitbucket.org/edtsech/underscore.strings').splice(30, 7, 'epeli');
});
JSLitmus.test('succ', function(){
var let = 'a', alphabet = [];
for (var i=0; i < 26; i++) {
alphabet.push(let);
let = _(let).succ();
}
return alphabet;
});
JSLitmus.test('titleize', function(){
return _('the titleize string method').titleize();
});
JSLitmus.test('truncate', function(){
return _('Hello world').truncate(5);
});
JSLitmus.test('prune', function(){
return _('Hello world').prune(5);
});
JSLitmus.test('isBlank', function(){
return _('').isBlank();
});
JSLitmus.test('escapeHTML', function(){
_('<div>Blah blah blah</div>').escapeHTML();
});
JSLitmus.test('unescapeHTML', function(){
_('&lt;div&gt;Blah blah blah&lt;/div&gt;').unescapeHTML();
});
JSLitmus.test('reverse', function(){
_('Hello World').reverse();
});
JSLitmus.test('pad default', function(){
_('foo').pad(12);
});
JSLitmus.test('pad hash left', function(){
_('foo').pad(12, '#');
});
JSLitmus.test('pad hash right', function(){
_('foo').pad(12, '#', 'right');
});
JSLitmus.test('pad hash both', function(){
_('foo').pad(12, '#', 'both');
});
JSLitmus.test('pad hash both longPad', function(){
_('foo').pad(12, 'f00f00f00', 'both');
});
JSLitmus.test('toNumber', function(){
_('10.232323').toNumber(2);
});
JSLitmus.test('strRight', function(){
_('aaa_bbb_ccc').strRight('_');
});
JSLitmus.test('strRightBack', function(){
_('aaa_bbb_ccc').strRightBack('_');
});
JSLitmus.test('strLeft', function(){
_('aaa_bbb_ccc').strLeft('_');
});
JSLitmus.test('strLeftBack', function(){
_('aaa_bbb_ccc').strLeftBack('_');
});
JSLitmus.test('join', function(){
_('separator').join(1, 2, 3, 4, 5, 6, 7, 8, 'foo', 'bar', 'lol', 'wut');
});
JSLitmus.test('slugify', function(){
_("Un éléphant à l'orée du bois").slugify();
});
})();

View File

@ -0,0 +1,438 @@
$(document).ready(function() {
// Include Underscore.string methods to Underscore namespace
_.mixin(_.str.exports());
module("String extensions");
test("Strings: trim", function() {
equals(_.trim(123), "123", "Non string");
equals(_(" foo").trim(), "foo");
equals(_("foo ").trim(), "foo");
equals(_(" foo ").trim(), "foo");
equals(_(" foo ").trim(), "foo");
equals(_(" foo ", " ").trim(), "foo", "Manually set whitespace");
equals(_("ffoo").trim("f"), "oo");
equals(_("ooff").trim("f"), "oo");
equals(_("ffooff").trim("f"), "oo");
equals(_("_-foobar-_").trim("_-"), "foobar");
equals(_("http://foo/").trim("/"), "http://foo");
equals(_("c:\\").trim('\\'), "c:");
equals(_(123).trim(), '123');
equals(_(123).trim(3), '12');
});
test("Strings: ltrim", function() {
equals(_(" foo").ltrim(), "foo");
equals(_(" foo").ltrim(), "foo");
equals(_("foo ").ltrim(), "foo ");
equals(_(" foo ").ltrim(), "foo ");
equals(_("ffoo").ltrim("f"), "oo");
equals(_("ooff").ltrim("f"), "ooff");
equals(_("ffooff").ltrim("f"), "ooff");
equals(_("_-foobar-_").ltrim("_-"), "foobar-_");
equals(_(123).ltrim(1), '23');
});
test("Strings: rtrim", function() {
equals(_("http://foo/").rtrim("/"), "http://foo", 'clean trailing slash');
equals(_(" foo").rtrim(), " foo");
equals(_("foo ").rtrim(), "foo");
equals(_("foo ").rtrim(), "foo");
equals(_("foo bar ").rtrim(), "foo bar");
equals(_(" foo ").rtrim(), " foo");
equals(_("ffoo").rtrim("f"), "ffoo");
equals(_("ooff").rtrim("f"), "oo");
equals(_("ffooff").rtrim("f"), "ffoo");
equals(_("_-foobar-_").rtrim("_-"), "_-foobar");
equals(_(123).rtrim(3), '12');
});
test("Strings: capitalize", function() {
equals(_("fabio").capitalize(), "Fabio", 'First letter is upper case');
equals(_.capitalize("fabio"), "Fabio", 'First letter is upper case');
equals(_.capitalize('FOO'), 'FOO', 'Other letters unchanged');
equals(_(123).capitalize(), "123", "Non string");
});
test("Strings: join", function() {
equals(_.join("", "foo", "bar"), "foobar", 'basic join');
equals(_.join("", 1, "foo", 2), "1foo2", 'join numbers and strings');
equals(_.join(" ","foo", "bar"), "foo bar", 'join with spaces');
equals(_.join("1", "2", "2"), "212", 'join number strings');
equals(_.join(1, 2, 2), "212", 'join numbers');
equals(_(" ").join("foo", "bar"), "foo bar", 'join object oriented');
});
test("Strings: reverse", function() {
equals(_.str.reverse("foo"), "oof" );
equals(_.str.reverse("foobar"), "raboof" );
equals(_.str.reverse("foo bar"), "rab oof" );
equals(_.str.reverse("saippuakauppias"), "saippuakauppias" );
equals(_.str.reverse(123), "321", "Non string");
equals(_.str.reverse(123.45), "54.321", "Non string");
});
test("Strings: clean", function() {
equals(_(" foo bar ").clean(), "foo bar");
equals(_(123).clean(), "123");
});
test("Strings: sprintf", function() {
// Should be very tested function already. Thanks to
// http://www.diveintojavascript.com/projects/sprintf-for-javascript
equals(_.sprintf("Hello %s", "me"), "Hello me", 'basic');
equals(_("Hello %s").sprintf("me"), "Hello me", 'object');
equals(_("hello %s").chain().sprintf("me").capitalize().value(), "Hello me", 'Chaining works');
equals(_.sprintf("%.1f", 1.22222), "1.2", 'round');
equals(_.sprintf("%.1f", 1.17), "1.2", 'round 2');
equals(_.sprintf("%(id)d - %(name)s", {id: 824, name: "Hello World"}), "824 - Hello World", 'Named replacements work');
equals(_.sprintf("%(args[0].id)d - %(args[1].name)s", {args: [{id: 824}, {name: "Hello World"}]}), "824 - Hello World", 'Named replacements with arrays work');
});
test("Strings: vsprintf", function() {
equals(_.vsprintf("Hello %s", ["me"]), "Hello me", 'basic');
equals(_("Hello %s").vsprintf(["me"]), "Hello me", 'object');
equals(_("hello %s").chain().vsprintf(["me"]).capitalize().value(), "Hello me", 'Chaining works');
equals(_.vsprintf("%.1f", [1.22222]), "1.2", 'round');
equals(_.vsprintf("%.1f", [1.17]), "1.2", 'round 2');
equals(_.vsprintf("%(id)d - %(name)s", [{id: 824, name: "Hello World"}]), "824 - Hello World", 'Named replacement works');
equals(_.vsprintf("%(args[0].id)d - %(args[1].name)s", [{args: [{id: 824}, {name: "Hello World"}]}]), "824 - Hello World", 'Named replacement with arrays works');
});
test("Strings: startsWith", function() {
ok(_("foobar").startsWith("foo"), 'foobar starts with foo');
ok(!_("oobar").startsWith("foo"), 'oobar does not start with foo');
ok(_(12345).startsWith(123), '12345 starts with 123');
ok(!_(2345).startsWith(123), '2345 does not start with 123');
});
test("Strings: endsWith", function() {
ok(_("foobar").endsWith("bar"), 'foobar ends with bar');
ok(_.endsWith("foobar", "bar"), 'foobar ends with bar');
ok(_.endsWith("00018-0000062.Plone.sdh264.1a7264e6912a91aa4a81b64dc5517df7b8875994.mp4", "mp4"), 'endsWith .mp4');
ok(!_("fooba").endsWith("bar"), 'fooba does not end with bar');
ok(_.endsWith(12345, 45), '12345 ends with 45');
ok(!_.endsWith(12345, 6), '12345 does not end with 6');
});
test("Strings: include", function() {
ok(_.str.include("foobar", "bar"), 'foobar includes bar');
ok(!_.str.include("foobar", "buzz"), 'foobar does not includes buzz');
ok(_.str.include(12345, 34), '12345 includes 34');
ok(!_.str.contains(12345, 6), '12345 does not includes 6');
});
test('String: chop', function(){
ok(_('whitespace').chop(2).length === 5, "output ['wh','it','es','pa','ce']");
ok(_('whitespace').chop(3).length === 4, "output ['whi','tes','pac','e']");
ok(_('whitespace').chop()[0].length === 10, "output ['whitespace']");
ok(_(12345).chop(1).length === 5, "output ['1','2','3','4','5']");
});
test('String: clean', function(){
equals(_.clean(' foo bar '), 'foo bar');
equals(_.clean(1), '1');
});
test('String: count', function(){
equals(_('Hello world').count('l'), 3);
equals(_('Hello world').count('Hello'), 1);
equals(_('Hello world').count('foo'), 0);
equals(_('x.xx....x.x').count('x'), 5);
equals(_(12345).count(1), 1);
equals(_(11345).count(1), 2);
});
test('String: insert', function(){
equals(_('Hello ').insert(6, 'Jessy'), 'Hello Jessy');
equals(_('Hello ').insert(100, 'Jessy'), 'Hello Jessy');
equals(_(12345).insert(6, 'Jessy'), '12345Jessy');
});
test('String: splice', function(){
equals(_('https://edtsech@bitbucket.org/edtsech/underscore.strings').splice(30, 7, 'epeli'),
'https://edtsech@bitbucket.org/epeli/underscore.strings');
equals(_.splice(12345, 1, 2, 321), '132145', 'Non strings');
});
test('String: succ', function(){
equals(_('a').succ(), 'b');
equals(_('A').succ(), 'B');
equals(_('+').succ(), ',');
equals(_(1).succ(), '2');
});
test('String: titleize', function(){
equals(_('the titleize string method').titleize(), 'The Titleize String Method');
equals(_('the titleize string method').titleize(), 'The Titleize String Method');
equals(_(123).titleize(), '123');
});
test('String: camelize', function(){
equals(_('the_camelize_string_method').camelize(), 'theCamelizeStringMethod');
equals(_('-the-camelize-string-method').camelize(), 'TheCamelizeStringMethod');
equals(_('the camelize string method').camelize(), 'theCamelizeStringMethod');
equals(_(' the camelize string method').camelize(), 'theCamelizeStringMethod');
equals(_('the camelize string method').camelize(), 'theCamelizeStringMethod');
equals(_(123).camelize(), '123');
});
test('String: underscored', function(){
equals(_('the-underscored-string-method').underscored(), 'the_underscored_string_method');
equals(_('theUnderscoredStringMethod').underscored(), 'the_underscored_string_method');
equals(_('TheUnderscoredStringMethod').underscored(), 'the_underscored_string_method');
equals(_(' the underscored string method').underscored(), 'the_underscored_string_method');
equals(_(123).underscored(), '123');
});
test('String: dasherize', function(){
equals(_('the_dasherize_string_method').dasherize(), 'the-dasherize-string-method');
equals(_('TheDasherizeStringMethod').dasherize(), '-the-dasherize-string-method');
equals(_('thisIsATest').dasherize(), 'this-is-a-test');
equals(_('this Is A Test').dasherize(), 'this-is-a-test');
equals(_('thisIsATest123').dasherize(), 'this-is-a-test123');
equals(_('123thisIsATest').dasherize(), '123this-is-a-test');
equals(_('the dasherize string method').dasherize(), 'the-dasherize-string-method');
equals(_('the dasherize string method ').dasherize(), 'the-dasherize-string-method');
equals(_('téléphone').dasherize(), 'téléphone');
equals(_('foo$bar').dasherize(), 'foo$bar');
equals(_(123).dasherize(), '123');
});
test('String: camelize', function(){
equals(_.camelize('-moz-transform'), 'MozTransform');
equals(_.camelize('webkit-transform'), 'webkitTransform');
equals(_.camelize('under_scored'), 'underScored');
equals(_.camelize(' with spaces'), 'withSpaces');
});
test('String: join', function(){
equals(_.join(1, 2, 3, 4), '21314');
equals(_.join('|', 'foo', 'bar', 'baz'), 'foo|bar|baz');
});
test('String: classify', function(){
equals(_.classify(1), '1');
equals(_('some_class_name').classify(), 'SomeClassName');
});
test('String: humanize', function(){
equals(_('the_humanize_string_method').humanize(), 'The humanize string method');
equals(_('ThehumanizeStringMethod').humanize(), 'Thehumanize string method');
equals(_('the humanize string method').humanize(), 'The humanize string method');
equals(_('the humanize_id string method_id').humanize(), 'The humanize id string method');
equals(_('the humanize string method ').humanize(), 'The humanize string method');
equals(_(' capitalize dash-CamelCase_underscore trim ').humanize(), 'Capitalize dash camel case underscore trim');
equals(_(123).humanize(), '123');
});
test('String: truncate', function(){
equals(_('Hello world').truncate(6, 'read more'), 'Hello read more');
equals(_('Hello world').truncate(5), 'Hello...');
equals(_('Hello').truncate(10), 'Hello');
equals(_(1234567890).truncate(5), '12345...');
});
test('String: prune', function(){
equals(_('Hello, cruel world').prune(6, ' read more'), 'Hello read more');
equals(_('Hello, world').prune(5, 'read a lot more'), 'Hello, world');
equals(_('Hello, world').prune(5), 'Hello...');
equals(_('Hello, world').prune(8), 'Hello...');
equals(_('Hello, cruel world').prune(15), 'Hello, cruel...');
equals(_('Hello world').prune(22), 'Hello world');
equals(_('Привет, жестокий мир').prune(6, ' read more'), 'Привет read more');
equals(_('Привет, мир').prune(6, 'read a lot more'), 'Привет, мир');
equals(_('Привет, мир').prune(6), 'Привет...');
equals(_('Привет, мир').prune(8), 'Привет...');
equals(_('Привет, жестокий мир').prune(16), 'Привет, жестокий...');
equals(_('Привет, мир').prune(22), 'Привет, мир');
equals(_(123).prune(10), '123');
equals(_(123).prune(1,1), '11');
});
test('String: isBlank', function(){
ok(_('').isBlank());
ok(_(' ').isBlank());
ok(_('\n').isBlank());
ok(!_('a').isBlank());
ok(!_('0').isBlank());
ok(!_(0).isBlank());
});
test('String: escapeHTML', function(){
equals(_('<div>Blah & "blah" & \'blah\'</div>').escapeHTML(),
'&lt;div&gt;Blah &amp; &quot;blah&quot; &amp; &apos;blah&apos;&lt;/div&gt;');
equals(_('&lt;').escapeHTML(), '&amp;lt;');
equals(_(5).escapeHTML(), '5');
// equals(_(undefined).escapeHTML(), '');
});
test('String: unescapeHTML', function(){
equals(_('&lt;div&gt;Blah &amp; &quot;blah&quot; &amp; &apos;blah&apos;&lt;/div&gt;').unescapeHTML(),
'<div>Blah & "blah" & \'blah\'</div>');
equals(_('&amp;lt;').unescapeHTML(), '&lt;');
equals(_('&#39;').unescapeHTML(), "'");
equals(_('&#0039;').unescapeHTML(), "'");
equals(_('&#x4a;').unescapeHTML(), "J");
equals(_('&#x04A;').unescapeHTML(), "J");
equals(_('&#X4A;').unescapeHTML(), "&#X4A;");
equals(_('&_#39;').unescapeHTML(), "&_#39;");
equals(_('&#39_;').unescapeHTML(), "&#39_;");
equals(_('&amp;#38;').unescapeHTML(), "&#38;");
equals(_('&#38;amp;').unescapeHTML(), "&amp;");
equals(_(5).unescapeHTML(), '5');
// equals(_(undefined).unescapeHTML(), '');
});
test('String: words', function() {
equals(_("I love you!").words().length, 3);
equals(_(" I love you! ").words().length, 3);
equals(_("I_love_you!").words('_').length, 3);
equals(_("I-love-you!").words(/-/).length, 3);
equals(_(123).words().length, 1);
});
test('String: chars', function() {
equals(_("Hello").chars().length, 5);
equals(_(123).chars().length, 3);
});
test('String: lines', function() {
equals(_("Hello\nWorld").lines().length, 2);
equals(_("Hello World").lines().length, 1);
equals(_(123).lines().length, 1);
});
test('String: pad', function() {
equals(_("1").pad(8), ' 1');
equals(_(1).pad(8), ' 1');
equals(_("1").pad(8, '0'), '00000001');
equals(_("1").pad(8, '0', 'left'), '00000001');
equals(_("1").pad(8, '0', 'right'), '10000000');
equals(_("1").pad(8, '0', 'both'), '00001000');
equals(_("foo").pad(8, '0', 'both'), '000foo00');
equals(_("foo").pad(7, '0', 'both'), '00foo00');
equals(_("foo").pad(7, '!@$%dofjrofj', 'both'), '!!foo!!');
});
test('String: lpad', function() {
equals(_("1").lpad(8), ' 1');
equals(_(1).lpad(8), ' 1');
equals(_("1").lpad(8, '0'), '00000001');
equals(_("1").lpad(8, '0', 'left'), '00000001');
});
test('String: rpad', function() {
equals(_("1").rpad(8), '1 ');
equals(_(1).lpad(8), ' 1');
equals(_("1").rpad(8, '0'), '10000000');
equals(_("foo").rpad(8, '0'), 'foo00000');
equals(_("foo").rpad(7, '0'), 'foo0000');
});
test('String: lrpad', function() {
equals(_("1").lrpad(8), ' 1 ');
equals(_(1).lrpad(8), ' 1 ');
equals(_("1").lrpad(8, '0'), '00001000');
equals(_("foo").lrpad(8, '0'), '000foo00');
equals(_("foo").lrpad(7, '0'), '00foo00');
equals(_("foo").lrpad(7, '!@$%dofjrofj'), '!!foo!!');
});
test('String: toNumber', function() {
deepEqual(_("not a number").toNumber(), Number.NaN);
equals(_(0).toNumber(), 0);
equals(_("0").toNumber(), 0);
equals(_("0000").toNumber(), 0);
equals(_("2.345").toNumber(), 2);
equals(_("2.345").toNumber(NaN), 2);
equals(_("2.345").toNumber(2), 2.35);
equals(_("2.344").toNumber(2), 2.34);
equals(_("2").toNumber(2), 2.00);
equals(_(2).toNumber(2), 2.00);
equals(_(-2).toNumber(), -2);
equals(_("-2").toNumber(), -2);
});
test('String: strRight', function() {
equals(_("This_is_a_test_string").strRight("_"), "is_a_test_string");
equals(_("This_is_a_test_string").strRight("string"), "");
equals(_("This_is_a_test_string").strRight(), "This_is_a_test_string");
equals(_("This_is_a_test_string").strRight(""), "This_is_a_test_string");
equals(_("This_is_a_test_string").strRight("-"), "This_is_a_test_string");
equals(_(12345).strRight(2), "345");
});
test('String: strRightBack', function() {
equals(_("This_is_a_test_string").strRightBack("_"), "string");
equals(_("This_is_a_test_string").strRightBack("string"), "");
equals(_("This_is_a_test_string").strRightBack(), "This_is_a_test_string");
equals(_("This_is_a_test_string").strRightBack(""), "This_is_a_test_string");
equals(_("This_is_a_test_string").strRightBack("-"), "This_is_a_test_string");
equals(_(12345).strRightBack(2), "345");
});
test('String: strLeft', function() {
equals(_("This_is_a_test_string").strLeft("_"), "This");
equals(_("This_is_a_test_string").strLeft("This"), "");
equals(_("This_is_a_test_string").strLeft(), "This_is_a_test_string");
equals(_("This_is_a_test_string").strLeft(""), "This_is_a_test_string");
equals(_("This_is_a_test_string").strLeft("-"), "This_is_a_test_string");
equals(_(123454321).strLeft(3), "12");
});
test('String: strLeftBack', function() {
equals(_("This_is_a_test_string").strLeftBack("_"), "This_is_a_test");
equals(_("This_is_a_test_string").strLeftBack("This"), "");
equals(_("This_is_a_test_string").strLeftBack(), "This_is_a_test_string");
equals(_("This_is_a_test_string").strLeftBack(""), "This_is_a_test_string");
equals(_("This_is_a_test_string").strLeftBack("-"), "This_is_a_test_string");
equals(_(123454321).strLeftBack(3), "123454");
});
test('Strings: stripTags', function() {
equals(_('a <a href="#">link</a>').stripTags(), 'a link');
equals(_('a <a href="#">link</a><script>alert("hello world!")</scr'+'ipt>').stripTags(), 'a linkalert("hello world!")');
equals(_('<html><body>hello world</body></html>').stripTags(), 'hello world');
equals(_(123).stripTags(), '123');
});
test('Strings: toSentence', function() {
equals(_.toSentence(['jQuery']), 'jQuery', 'array with a single element');
equals(_.toSentence(['jQuery', 'MooTools']), 'jQuery and MooTools', 'array with two elements');
equals(_.toSentence(['jQuery', 'MooTools', 'Prototype']), 'jQuery, MooTools and Prototype', 'array with three elements');
equals(_.toSentence(['jQuery', 'MooTools', 'Prototype', 'YUI']), 'jQuery, MooTools, Prototype and YUI', 'array with multiple elements');
equals(_.toSentence(['jQuery', 'MooTools', 'Prototype'], ',', ' or '), 'jQuery,MooTools or Prototype', 'handles custom separators');
});
test('Strings: slugify', function() {
equals(_("Jack & Jill like numbers 1,2,3 and 4 and silly characters ?%.$!/").slugify(), "jack-jill-like-numbers-123-and-4-and-silly-characters");
equals(_("Un éléphant à l'orée du bois").slugify(), "un-elephant-a-loree-du-bois");
equals(_("I know latin characters: á í ó ú ç ã õ ñ ü").slugify(), "i-know-latin-characters-a-i-o-u-c-a-o-n-u");
equals(_("I am a word too, even though I am but a single letter: i!").slugify(), "i-am-a-word-too-even-though-i-am-but-a-single-letter-i");
});
test('Strings: repeat', function() {
equals(_.repeat('foo'), '');
equals(_.repeat('foo', 3), 'foofoofoo');
equals(_.repeat('foo', '3'), 'foofoofoo');
equals(_.repeat(123, 2), '123123');
equals(_.repeat(1234, 2, '*'), '1234*1234');
equals(_.repeat(1234, 2, 5), '123451234');
});
});

View File

@ -0,0 +1,12 @@
$(document).ready(function() {
module("String extensions");
test("underscore not included", function() {
raises(function() { _("foo") }, /TypeError/);
});
test("provides standalone functions", function() {
equals(typeof _.str.trim, "function");
});
});

View File

@ -0,0 +1,31 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Underscore.strings Test Suite</title>
<link rel="stylesheet" href="test_underscore/vendor/qunit.css" type="text/css" media="screen" />
<script type="text/javascript" src="test_underscore/vendor/jquery.js"></script>
<script type="text/javascript" src="test_underscore/vendor/qunit.js"></script>
<script type="text/javascript" src="test_underscore/vendor/jslitmus.js"></script>
<script type="text/javascript" src="underscore.js"></script>
<script type="text/javascript" src="../lib/underscore.string.js"></script>
<script type="text/javascript" src="strings.js"></script>
<script type="text/javascript" src="speed.js"></script>
</head>
<body>
<h1 id="qunit-header">Underscore.string Test Suite</h1>
<h2 id="qunit-banner"></h2>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>
<br />
<h1 class="qunit-header">Underscore.string Speed Suite</h1>
<!-- <h2 class="qunit-userAgent">
A representative sample of the functions are benchmarked here, to provide
a sense of how fast they might run in different browsers.
Each iteration runs on an array of 1000 elements.<br /><br />
For example, the 'intersect' test measures the number of times you can
find the intersection of two thousand-element arrays in one second.
</h2> -->
<br />
</body>
</html>

View File

@ -0,0 +1,18 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Underscore.strings Test Suite</title>
<link rel="stylesheet" href="test_underscore/vendor/qunit.css" type="text/css" media="screen" />
<script type="text/javascript" src="test_underscore/vendor/jquery.js"></script>
<script type="text/javascript" src="test_underscore/vendor/qunit.js"></script>
<script type="text/javascript" src="../lib/underscore.string.js"></script>
<script type="text/javascript" src="strings_standalone.js"></script>
</head>
<body>
<h1 id="qunit-header">Underscore.string Test Suite</h1>
<h2 id="qunit-banner"></h2>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>
</body>
</html>

View File

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

View File

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

View File

@ -0,0 +1,270 @@
$(document).ready(function() {
module("Collections");
test("collections: each", function() {
_.each([1, 2, 3], function(num, i) {
equals(num, i + 1, 'each iterators provide value and iteration count');
});
var answers = [];
_.each([1, 2, 3], function(num){ answers.push(num * this.multiplier);}, {multiplier : 5});
equals(answers.join(', '), '5, 10, 15', 'context object property accessed');
answers = [];
_.forEach([1, 2, 3], function(num){ answers.push(num); });
equals(answers.join(', '), '1, 2, 3', 'aliased as "forEach"');
answers = [];
var obj = {one : 1, two : 2, three : 3};
obj.constructor.prototype.four = 4;
_.each(obj, function(value, key){ answers.push(key); });
equals(answers.join(", "), 'one, two, three', 'iterating over objects works, and ignores the object prototype.');
delete obj.constructor.prototype.four;
answer = null;
_.each([1, 2, 3], function(num, index, arr){ if (_.include(arr, num)) answer = true; });
ok(answer, 'can reference the original collection from inside the iterator');
answers = 0;
_.each(null, function(){ ++answers; });
equals(answers, 0, 'handles a null properly');
});
test('collections: map', function() {
var doubled = _.map([1, 2, 3], function(num){ return num * 2; });
equals(doubled.join(', '), '2, 4, 6', 'doubled numbers');
doubled = _.collect([1, 2, 3], function(num){ return num * 2; });
equals(doubled.join(', '), '2, 4, 6', 'aliased as "collect"');
var tripled = _.map([1, 2, 3], function(num){ return num * this.multiplier; }, {multiplier : 3});
equals(tripled.join(', '), '3, 6, 9', 'tripled numbers with context');
var doubled = _([1, 2, 3]).map(function(num){ return num * 2; });
equals(doubled.join(', '), '2, 4, 6', 'OO-style doubled numbers');
var ids = _.map($('div.underscore-test').children(), function(n){ return n.id; });
ok(_.include(ids, 'qunit-header'), 'can use collection methods on NodeLists');
var ids = _.map(document.images, function(n){ return n.id; });
ok(ids[0] == 'chart_image', 'can use collection methods on HTMLCollections');
var ifnull = _.map(null, function(){});
ok(_.isArray(ifnull) && ifnull.length === 0, 'handles a null properly');
var length = _.map(Array(2), function(v) { return v; }).length;
equals(length, 2, "can preserve a sparse array's length");
});
test('collections: reduce', function() {
var sum = _.reduce([1, 2, 3], function(sum, num){ return sum + num; }, 0);
equals(sum, 6, 'can sum up an array');
var context = {multiplier : 3};
sum = _.reduce([1, 2, 3], function(sum, num){ return sum + num * this.multiplier; }, 0, context);
equals(sum, 18, 'can reduce with a context object');
sum = _.inject([1, 2, 3], function(sum, num){ return sum + num; }, 0);
equals(sum, 6, 'aliased as "inject"');
sum = _([1, 2, 3]).reduce(function(sum, num){ return sum + num; }, 0);
equals(sum, 6, 'OO-style reduce');
var sum = _.reduce([1, 2, 3], function(sum, num){ return sum + num; });
equals(sum, 6, 'default initial value');
var ifnull;
try {
_.reduce(null, function(){});
} catch (ex) {
ifnull = ex;
}
ok(ifnull instanceof TypeError, 'handles a null (without inital value) properly');
ok(_.reduce(null, function(){}, 138) === 138, 'handles a null (with initial value) properly');
equals(_.reduce([], function(){}, undefined), undefined, 'undefined can be passed as a special case');
raises(function() { _.reduce([], function(){}); }, TypeError, 'throws an error for empty arrays with no initial value');
var sparseArray = [];
sparseArray[0] = 20;
sparseArray[2] = -5;
equals(_.reduce(sparseArray, function(a, b){ return a - b; }), 25, 'initially-sparse arrays with no memo');
});
test('collections: reduceRight', function() {
var list = _.reduceRight(["foo", "bar", "baz"], function(memo, str){ return memo + str; }, '');
equals(list, 'bazbarfoo', 'can perform right folds');
var list = _.foldr(["foo", "bar", "baz"], function(memo, str){ return memo + str; }, '');
equals(list, 'bazbarfoo', 'aliased as "foldr"');
var list = _.foldr(["foo", "bar", "baz"], function(memo, str){ return memo + str; });
equals(list, 'bazbarfoo', 'default initial value');
var ifnull;
try {
_.reduceRight(null, function(){});
} catch (ex) {
ifnull = ex;
}
ok(ifnull instanceof TypeError, 'handles a null (without inital value) properly');
ok(_.reduceRight(null, function(){}, 138) === 138, 'handles a null (with initial value) properly');
equals(_.reduceRight([], function(){}, undefined), undefined, 'undefined can be passed as a special case');
raises(function() { _.reduceRight([], function(){}); }, TypeError, 'throws an error for empty arrays with no initial value');
var sparseArray = [];
sparseArray[0] = 20;
sparseArray[2] = -5;
equals(_.reduceRight(sparseArray, function(a, b){ return a - b; }), -25, 'initially-sparse arrays with no memo');
});
test('collections: detect', function() {
var result = _.detect([1, 2, 3], function(num){ return num * 2 == 4; });
equals(result, 2, 'found the first "2" and broke the loop');
});
test('collections: select', function() {
var evens = _.select([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
equals(evens.join(', '), '2, 4, 6', 'selected each even number');
evens = _.filter([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
equals(evens.join(', '), '2, 4, 6', 'aliased as "filter"');
});
test('collections: reject', function() {
var odds = _.reject([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
equals(odds.join(', '), '1, 3, 5', 'rejected each even number');
});
test('collections: all', function() {
ok(_.all([], _.identity), 'the empty set');
ok(_.all([true, true, true], _.identity), 'all true values');
ok(!_.all([true, false, true], _.identity), 'one false value');
ok(_.all([0, 10, 28], function(num){ return num % 2 == 0; }), 'even numbers');
ok(!_.all([0, 11, 28], function(num){ return num % 2 == 0; }), 'an odd number');
ok(_.every([true, true, true], _.identity), 'aliased as "every"');
});
test('collections: any', function() {
var nativeSome = Array.prototype.some;
Array.prototype.some = null;
ok(!_.any([]), 'the empty set');
ok(!_.any([false, false, false]), 'all false values');
ok(_.any([false, false, true]), 'one true value');
ok(_.any([null, 0, 'yes', false]), 'a string');
ok(!_.any([null, 0, '', false]), 'falsy values');
ok(!_.any([1, 11, 29], function(num){ return num % 2 == 0; }), 'all odd numbers');
ok(_.any([1, 10, 29], function(num){ return num % 2 == 0; }), 'an even number');
ok(_.some([false, false, true]), 'aliased as "some"');
Array.prototype.some = nativeSome;
});
test('collections: include', function() {
ok(_.include([1,2,3], 2), 'two is in the array');
ok(!_.include([1,3,9], 2), 'two is not in the array');
ok(_.contains({moe:1, larry:3, curly:9}, 3) === true, '_.include on objects checks their values');
ok(_([1,2,3]).include(2), 'OO-style include');
});
test('collections: invoke', function() {
var list = [[5, 1, 7], [3, 2, 1]];
var result = _.invoke(list, 'sort');
equals(result[0].join(', '), '1, 5, 7', 'first array sorted');
equals(result[1].join(', '), '1, 2, 3', 'second array sorted');
});
test('collections: invoke w/ function reference', function() {
var list = [[5, 1, 7], [3, 2, 1]];
var result = _.invoke(list, Array.prototype.sort);
equals(result[0].join(', '), '1, 5, 7', 'first array sorted');
equals(result[1].join(', '), '1, 2, 3', 'second array sorted');
});
// Relevant when using ClojureScript
test('collections: invoke when strings have a call method', function() {
String.prototype.call = function(){return 42;}
var list = [[5, 1, 7], [3, 2, 1]];
var s = "foo";
equals(s.call(), 42, "call function exists");
var result = _.invoke(list, 'sort');
equals(result[0].join(', '), '1, 5, 7', 'first array sorted');
equals(result[1].join(', '), '1, 2, 3', 'second array sorted');
delete String.prototype.call;
equals(s.call, undefined, "call function removed");
});
test('collections: pluck', function() {
var people = [{name : 'moe', age : 30}, {name : 'curly', age : 50}];
equals(_.pluck(people, 'name').join(', '), 'moe, curly', 'pulls names out of objects');
});
test('collections: max', function() {
equals(3, _.max([1, 2, 3]), 'can perform a regular Math.max');
var neg = _.max([1, 2, 3], function(num){ return -num; });
equals(neg, 1, 'can perform a computation-based max');
equals(-Infinity, _.max({}), 'Maximum value of an empty object');
equals(-Infinity, _.max([]), 'Maximum value of an empty array');
});
test('collections: min', function() {
equals(1, _.min([1, 2, 3]), 'can perform a regular Math.min');
var neg = _.min([1, 2, 3], function(num){ return -num; });
equals(neg, 3, 'can perform a computation-based min');
equals(Infinity, _.min({}), 'Minimum value of an empty object');
equals(Infinity, _.min([]), 'Minimum value of an empty array');
});
test('collections: sortBy', function() {
var people = [{name : 'curly', age : 50}, {name : 'moe', age : 30}];
people = _.sortBy(people, function(person){ return person.age; });
equals(_.pluck(people, 'name').join(', '), 'moe, curly', 'stooges sorted by age');
});
test('collections: groupBy', function() {
var parity = _.groupBy([1, 2, 3, 4, 5, 6], function(num){ return num % 2; });
ok('0' in parity && '1' in parity, 'created a group for each value');
equals(parity[0].join(', '), '2, 4, 6', 'put each even number in the right group');
var list = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"];
var grouped = _.groupBy(list, 'length');
equals(grouped['3'].join(' '), 'one two six ten');
equals(grouped['4'].join(' '), 'four five nine');
equals(grouped['5'].join(' '), 'three seven eight');
});
test('collections: sortedIndex', function() {
var numbers = [10, 20, 30, 40, 50], num = 35;
var index = _.sortedIndex(numbers, num);
equals(index, 3, '35 should be inserted at index 3');
});
test('collections: shuffle', function() {
var numbers = _.range(10);
var shuffled = _.shuffle(numbers).sort();
notStrictEqual(numbers, shuffled, 'original object is unmodified');
equals(shuffled.join(','), numbers.join(','), 'contains the same members before and after shuffle');
});
test('collections: toArray', function() {
ok(!_.isArray(arguments), 'arguments object is not an array');
ok(_.isArray(_.toArray(arguments)), 'arguments object converted into array');
var a = [1,2,3];
ok(_.toArray(a) !== a, 'array is cloned');
equals(_.toArray(a).join(', '), '1, 2, 3', 'cloned array contains same elements');
var numbers = _.toArray({one : 1, two : 2, three : 3});
equals(numbers.join(', '), '1, 2, 3', 'object flattened into array');
});
test('collections: size', function() {
equals(_.size({one : 1, two : 2, three : 3}), 3, 'can compute the size of an object');
});
});

View File

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

View File

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

View File

@ -0,0 +1,70 @@
(function() {
var numbers = [];
for (var i=0; i<1000; i++) numbers.push(i);
var objects = _.map(numbers, function(n){ return {num : n}; });
var randomized = _.sortBy(numbers, function(){ return Math.random(); });
JSLitmus.test('_.each()', function() {
var timesTwo = [];
_.each(numbers, function(num){ timesTwo.push(num * 2); });
return timesTwo;
});
JSLitmus.test('_(list).each()', function() {
var timesTwo = [];
_(numbers).each(function(num){ timesTwo.push(num * 2); });
return timesTwo;
});
JSLitmus.test('jQuery.each()', function() {
var timesTwo = [];
jQuery.each(numbers, function(){ timesTwo.push(this * 2); });
return timesTwo;
});
JSLitmus.test('_.map()', function() {
return _.map(objects, function(obj){ return obj.num; });
});
JSLitmus.test('jQuery.map()', function() {
return jQuery.map(objects, function(obj){ return obj.num; });
});
JSLitmus.test('_.pluck()', function() {
return _.pluck(objects, 'num');
});
JSLitmus.test('_.uniq()', function() {
return _.uniq(randomized);
});
JSLitmus.test('_.uniq() (sorted)', function() {
return _.uniq(numbers, true);
});
JSLitmus.test('_.sortBy()', function() {
return _.sortBy(numbers, function(num){ return -num; });
});
JSLitmus.test('_.isEqual()', function() {
return _.isEqual(numbers, randomized);
});
JSLitmus.test('_.keys()', function() {
return _.keys(objects);
});
JSLitmus.test('_.values()', function() {
return _.values(objects);
});
JSLitmus.test('_.intersect()', function() {
return _.intersect(numbers, randomized);
});
JSLitmus.test('_.range()', function() {
return _.range(1000);
});
})();

View File

@ -0,0 +1,27 @@
(function() {
var func = function(){};
var date = new Date();
var str = "a string";
var numbers = [];
for (var i=0; i<1000; i++) numbers.push(i);
var objects = _.map(numbers, function(n){ return {num : n}; });
var randomized = _.sortBy(numbers, function(){ return Math.random(); });
JSLitmus.test('_.isNumber', function() {
return _.isNumber(1000)
});
JSLitmus.test('_.newIsNumber', function() {
return _.newIsNumber(1000)
});
JSLitmus.test('_.isNumber(NaN)', function() {
return _.isNumber(NaN)
});
JSLitmus.test('_.newIsNumber(NaN)', function() {
return _.newIsNumber(NaN)
});
})();

View File

@ -0,0 +1,19 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Underscore Temporary Tests</title>
<link rel="stylesheet" href="vendor/qunit.css" type="text/css" media="screen" />
<script type="text/javascript" src="vendor/jquery.js"></script>
<script type="text/javascript" src="vendor/jslitmus.js"></script>
<script type="text/javascript" src="../underscore.js"></script>
<script type="text/javascript" src="temp.js"></script>
</head>
<body>
<h1 class="qunit-header">Underscore Temporary Tests</h1>
<h2 class="qunit-userAgent">
A page for temporary speed tests, used for developing faster implementations
of existing Underscore methods.
</h2>
<br />
</body>
</html>

View File

@ -0,0 +1,43 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Underscore Test Suite</title>
<link rel="stylesheet" href="vendor/qunit.css" type="text/css" media="screen" />
<script type="text/javascript" src="vendor/jquery.js"></script>
<script type="text/javascript" src="vendor/qunit.js"></script>
<script type="text/javascript" src="vendor/jslitmus.js"></script>
<script type="text/javascript" src="../underscore.js"></script>
<script type="text/javascript" src="../../lib/underscore.string.js"></script>
<script type="text/javascript" src="collections.js"></script>
<script type="text/javascript" src="arrays.js"></script>
<script type="text/javascript" src="functions.js"></script>
<script type="text/javascript" src="objects.js"></script>
<script type="text/javascript" src="utility.js"></script>
<script type="text/javascript" src="chaining.js"></script>
<script type="text/javascript" src="speed.js"></script>
</head>
<body>
<div class="underscore-test">
<h1 id="qunit-header">Underscore Test Suite</h1>
<h2 id="qunit-banner"></h2>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>
<br />
<h1 class="qunit-header">Underscore Speed Suite</h1>
<p>
A representative sample of the functions are benchmarked here, to provide
a sense of how fast they might run in different browsers.
Each iteration runs on an array of 1000 elements.<br /><br />
For example, the 'intersect' test measures the number of times you can
find the intersection of two thousand-element arrays in one second.
</p>
<br />
<script type="text/html" id="template">
<%
if (data) { data += 12345; }; %>
<li><%= data %></li>
</script>
</div>
</body>
</html>

View File

@ -0,0 +1,155 @@
$(document).ready(function() {
module("Utility");
test("utility: noConflict", function() {
var underscore = _.noConflict();
ok(underscore.isUndefined(_), "The '_' variable has been returned to its previous state.");
var intersection = underscore.intersect([-1, 0, 1, 2], [1, 2, 3, 4]);
equals(intersection.join(', '), '1, 2', 'but the intersection function still works');
window._ = underscore;
});
test("utility: identity", function() {
var moe = {name : 'moe'};
equals(_.identity(moe), moe, 'moe is the same as his identity');
});
test("utility: uniqueId", function() {
var ids = [], i = 0;
while(i++ < 100) ids.push(_.uniqueId());
equals(_.uniq(ids).length, ids.length, 'can generate a globally-unique stream of ids');
});
test("utility: times", function() {
var vals = [];
_.times(3, function (i) { vals.push(i); });
ok(_.isEqual(vals, [0,1,2]), "is 0 indexed");
//
vals = [];
_(3).times(function (i) { vals.push(i); });
ok(_.isEqual(vals, [0,1,2]), "works as a wrapper");
});
test("utility: mixin", function() {
_.mixin({
myReverse: function(string) {
return string.split('').reverse().join('');
}
});
equals(_.myReverse('panacea'), 'aecanap', 'mixed in a function to _');
equals(_('champ').myReverse(), 'pmahc', 'mixed in a function to the OOP wrapper');
});
test("utility: _.escape", function() {
equals(_.escape("Curly & Moe"), "Curly &amp; Moe");
equals(_.escape("Curly &amp; Moe"), "Curly &amp;amp; Moe");
});
test("utility: template", function() {
var basicTemplate = _.template("<%= thing %> is gettin' on my noives!");
var result = basicTemplate({thing : 'This'});
equals(result, "This is gettin' on my noives!", 'can do basic attribute interpolation');
var sansSemicolonTemplate = _.template("A <% this %> B");
equals(sansSemicolonTemplate(), "A B");
var backslashTemplate = _.template("<%= thing %> is \\ridanculous");
equals(backslashTemplate({thing: 'This'}), "This is \\ridanculous");
var escapeTemplate = _.template('<%= a ? "checked=\\"checked\\"" : "" %>');
equals(escapeTemplate({a: true}), 'checked="checked"', 'can handle slash escapes in interpolations.');
var fancyTemplate = _.template("<ul><% \
for (key in people) { \
%><li><%= people[key] %></li><% } %></ul>");
result = fancyTemplate({people : {moe : "Moe", larry : "Larry", curly : "Curly"}});
equals(result, "<ul><li>Moe</li><li>Larry</li><li>Curly</li></ul>", 'can run arbitrary javascript in templates');
var escapedCharsInJavascriptTemplate = _.template("<ul><% _.each(numbers.split('\\n'), function(item) { %><li><%= item %></li><% }) %></ul>");
result = escapedCharsInJavascriptTemplate({numbers: "one\ntwo\nthree\nfour"});
equals(result, "<ul><li>one</li><li>two</li><li>three</li><li>four</li></ul>", 'Can use escaped characters (e.g. \\n) in Javascript');
var namespaceCollisionTemplate = _.template("<%= pageCount %> <%= thumbnails[pageCount] %> <% _.each(thumbnails, function(p) { %><div class=\"thumbnail\" rel=\"<%= p %>\"></div><% }); %>");
result = namespaceCollisionTemplate({
pageCount: 3,
thumbnails: {
1: "p1-thumbnail.gif",
2: "p2-thumbnail.gif",
3: "p3-thumbnail.gif"
}
});
equals(result, "3 p3-thumbnail.gif <div class=\"thumbnail\" rel=\"p1-thumbnail.gif\"></div><div class=\"thumbnail\" rel=\"p2-thumbnail.gif\"></div><div class=\"thumbnail\" rel=\"p3-thumbnail.gif\"></div>");
var noInterpolateTemplate = _.template("<div><p>Just some text. Hey, I know this is silly but it aids consistency.</p></div>");
result = noInterpolateTemplate();
equals(result, "<div><p>Just some text. Hey, I know this is silly but it aids consistency.</p></div>");
var quoteTemplate = _.template("It's its, not it's");
equals(quoteTemplate({}), "It's its, not it's");
var quoteInStatementAndBody = _.template("<%\
if(foo == 'bar'){ \
%>Statement quotes and 'quotes'.<% } %>");
equals(quoteInStatementAndBody({foo: "bar"}), "Statement quotes and 'quotes'.");
var withNewlinesAndTabs = _.template('This\n\t\tis: <%= x %>.\n\tok.\nend.');
equals(withNewlinesAndTabs({x: 'that'}), 'This\n\t\tis: that.\n\tok.\nend.');
var template = _.template("<i><%- value %></i>");
var result = template({value: "<script>"});
equals(result, '<i>&lt;script&gt;</i>');
var stooge = {
name: "Moe",
template: _.template("I'm <%= this.name %>")
};
equals(stooge.template(), "I'm Moe");
if (!$.browser.msie) {
var fromHTML = _.template($('#template').html());
equals(fromHTML({data : 12345}).replace(/\s/g, ''), '<li>24690</li>');
}
_.templateSettings = {
evaluate : /\{\{([\s\S]+?)\}\}/g,
interpolate : /\{\{=([\s\S]+?)\}\}/g
};
var custom = _.template("<ul>{{ for (key in people) { }}<li>{{= people[key] }}</li>{{ } }}</ul>");
result = custom({people : {moe : "Moe", larry : "Larry", curly : "Curly"}});
equals(result, "<ul><li>Moe</li><li>Larry</li><li>Curly</li></ul>", 'can run arbitrary javascript in templates');
var customQuote = _.template("It's its, not it's");
equals(customQuote({}), "It's its, not it's");
var quoteInStatementAndBody = _.template("{{ if(foo == 'bar'){ }}Statement quotes and 'quotes'.{{ } }}");
equals(quoteInStatementAndBody({foo: "bar"}), "Statement quotes and 'quotes'.");
_.templateSettings = {
evaluate : /<\?([\s\S]+?)\?>/g,
interpolate : /<\?=([\s\S]+?)\?>/g
};
var customWithSpecialChars = _.template("<ul><? for (key in people) { ?><li><?= people[key] ?></li><? } ?></ul>");
result = customWithSpecialChars({people : {moe : "Moe", larry : "Larry", curly : "Curly"}});
equals(result, "<ul><li>Moe</li><li>Larry</li><li>Curly</li></ul>", 'can run arbitrary javascript in templates');
var customWithSpecialCharsQuote = _.template("It's its, not it's");
equals(customWithSpecialCharsQuote({}), "It's its, not it's");
var quoteInStatementAndBody = _.template("<? if(foo == 'bar'){ ?>Statement quotes and 'quotes'.<? } ?>");
equals(quoteInStatementAndBody({foo: "bar"}), "Statement quotes and 'quotes'.");
_.templateSettings = {
interpolate : /\{\{(.+?)\}\}/g
};
var mustache = _.template("Hello {{planet}}!");
equals(mustache({planet : "World"}), "Hello World!", "can mimic mustache.js");
var templateWithNull = _.template("a null undefined {{planet}}");
equals(templateWithNull({planet : "world"}), "a null undefined world", "can handle missing escape and evaluate settings");
});
});

File diff suppressed because one or more lines are too long

View File

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

View File

@ -0,0 +1,196 @@
/** Font Family and Sizes */
#qunit-tests, #qunit-header, .qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
}
#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
#qunit-tests { font-size: smaller; }
/** Resets */
#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult {
margin: 0;
padding: 0;
}
/** Header */
#qunit-header, .qunit-header {
padding: 0.5em 0 0.5em 1em;
color: #8699a4;
background-color: #0d3349;
font-size: 1.5em;
line-height: 1em;
font-weight: normal;
border-radius: 15px 15px 0 0;
-moz-border-radius: 15px 15px 0 0;
-webkit-border-top-right-radius: 15px;
-webkit-border-top-left-radius: 15px;
}
#qunit-header a {
text-decoration: none;
color: #c2ccd1;
}
#qunit-header a:hover,
#qunit-header a:focus {
color: #fff;
}
#qunit-banner {
height: 5px;
}
#qunit-testrunner-toolbar {
padding: 0em 0 0.5em 2em;
}
#qunit-userAgent {
padding: 0.5em 0 0.5em 2.5em;
background-color: #2b81af;
color: #fff;
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
}
/** Tests: Pass/Fail */
#qunit-tests {
list-style-position: inside;
}
#qunit-tests li {
padding: 0.4em 0.5em 0.4em 2.5em;
border-bottom: 1px solid #fff;
list-style-position: inside;
}
#qunit-tests li strong {
cursor: pointer;
}
#qunit-tests ol {
margin-top: 0.5em;
padding: 0.5em;
background-color: #fff;
border-radius: 15px;
-moz-border-radius: 15px;
-webkit-border-radius: 15px;
box-shadow: inset 0px 2px 13px #999;
-moz-box-shadow: inset 0px 2px 13px #999;
-webkit-box-shadow: inset 0px 2px 13px #999;
}
#qunit-tests table {
border-collapse: collapse;
margin-top: .2em;
}
#qunit-tests th {
text-align: right;
vertical-align: top;
padding: 0 .5em 0 0;
}
#qunit-tests td {
vertical-align: top;
}
#qunit-tests pre {
margin: 0;
white-space: pre-wrap;
word-wrap: break-word;
}
#qunit-tests del {
background-color: #e0f2be;
color: #374e0c;
text-decoration: none;
}
#qunit-tests ins {
background-color: #ffcaca;
color: #500;
text-decoration: none;
}
/*** Test Counts */
#qunit-tests b.counts { color: black; }
#qunit-tests b.passed { color: #5E740B; }
#qunit-tests b.failed { color: #710909; }
#qunit-tests li li {
margin: 0.5em;
padding: 0.4em 0.5em 0.4em 0.5em;
background-color: #fff;
border-bottom: none;
list-style-position: inside;
}
/*** Passing Styles */
#qunit-tests li li.pass {
color: #5E740B;
background-color: #fff;
border-left: 26px solid #C6E746;
}
#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
#qunit-tests .pass .test-name { color: #366097; }
#qunit-tests .pass .test-actual,
#qunit-tests .pass .test-expected { color: #999999; }
#qunit-banner.qunit-pass { background-color: #C6E746; }
/*** Failing Styles */
#qunit-tests li li.fail {
color: #710909;
background-color: #fff;
border-left: 26px solid #EE5757;
}
#qunit-tests .fail { color: #000000; background-color: #EE5757; }
#qunit-tests .fail .test-name,
#qunit-tests .fail .module-name { color: #000000; }
#qunit-tests .fail .test-actual { color: #EE5757; }
#qunit-tests .fail .test-expected { color: green; }
#qunit-banner.qunit-fail,
#qunit-testrunner-toolbar { background-color: #EE5757; }
/** Footer */
#qunit-testresult {
padding: 0.5em 0.5em 0.5em 2.5em;
color: #2b81af;
background-color: #D2E0E6;
border-radius: 0 0 15px 15px;
-moz-border-radius: 0 0 15px 15px;
-webkit-border-bottom-right-radius: 15px;
-webkit-border-bottom-left-radius: 15px;
}
/** Fixture */
#qunit-fixture {
position: absolute;
top: -10000px;
left: -10000px;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,999 @@
// Underscore.js 1.3.1
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Underscore is freely distributable under the MIT license.
// Portions of Underscore are inspired or borrowed from Prototype,
// Oliver Steele's Functional, and John Resig's Micro-Templating.
// For all details and documentation:
// http://documentcloud.github.com/underscore
(function() {
// Baseline setup
// --------------
// Establish the root object, `window` in the browser, or `global` on the server.
var root = this;
// Save the previous value of the `_` variable.
var previousUnderscore = root._;
// Establish the object that gets returned to break out of a loop iteration.
var breaker = {};
// Save bytes in the minified (but not gzipped) version:
var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
// Create quick reference variables for speed access to core prototypes.
var slice = ArrayProto.slice,
unshift = ArrayProto.unshift,
toString = ObjProto.toString,
hasOwnProperty = ObjProto.hasOwnProperty;
// All **ECMAScript 5** native function implementations that we hope to use
// are declared here.
var
nativeForEach = ArrayProto.forEach,
nativeMap = ArrayProto.map,
nativeReduce = ArrayProto.reduce,
nativeReduceRight = ArrayProto.reduceRight,
nativeFilter = ArrayProto.filter,
nativeEvery = ArrayProto.every,
nativeSome = ArrayProto.some,
nativeIndexOf = ArrayProto.indexOf,
nativeLastIndexOf = ArrayProto.lastIndexOf,
nativeIsArray = Array.isArray,
nativeKeys = Object.keys,
nativeBind = FuncProto.bind;
// Create a safe reference to the Underscore object for use below.
var _ = function(obj) { return new wrapper(obj); };
// Export the Underscore object for **Node.js**, with
// backwards-compatibility for the old `require()` API. If we're in
// the browser, add `_` as a global object via a string identifier,
// for Closure Compiler "advanced" mode.
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = _;
}
exports._ = _;
} else {
root['_'] = _;
}
// Current version.
_.VERSION = '1.3.1';
// Collection Functions
// --------------------
// The cornerstone, an `each` implementation, aka `forEach`.
// Handles objects with the built-in `forEach`, arrays, and raw objects.
// Delegates to **ECMAScript 5**'s native `forEach` if available.
var each = _.each = _.forEach = function(obj, iterator, context) {
if (obj == null) return;
if (nativeForEach && obj.forEach === nativeForEach) {
obj.forEach(iterator, context);
} else if (obj.length === +obj.length) {
for (var i = 0, l = obj.length; i < l; i++) {
if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return;
}
} else {
for (var key in obj) {
if (_.has(obj, key)) {
if (iterator.call(context, obj[key], key, obj) === breaker) return;
}
}
}
};
// Return the results of applying the iterator to each element.
// Delegates to **ECMAScript 5**'s native `map` if available.
_.map = _.collect = function(obj, iterator, context) {
var results = [];
if (obj == null) return results;
if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
each(obj, function(value, index, list) {
results[results.length] = iterator.call(context, value, index, list);
});
if (obj.length === +obj.length) results.length = obj.length;
return results;
};
// **Reduce** builds up a single result from a list of values, aka `inject`,
// or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
_.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
var initial = arguments.length > 2;
if (obj == null) obj = [];
if (nativeReduce && obj.reduce === nativeReduce) {
if (context) iterator = _.bind(iterator, context);
return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
}
each(obj, function(value, index, list) {
if (!initial) {
memo = value;
initial = true;
} else {
memo = iterator.call(context, memo, value, index, list);
}
});
if (!initial) throw new TypeError('Reduce of empty array with no initial value');
return memo;
};
// The right-associative version of reduce, also known as `foldr`.
// Delegates to **ECMAScript 5**'s native `reduceRight` if available.
_.reduceRight = _.foldr = function(obj, iterator, memo, context) {
var initial = arguments.length > 2;
if (obj == null) obj = [];
if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
if (context) iterator = _.bind(iterator, context);
return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
}
var reversed = _.toArray(obj).reverse();
if (context && !initial) iterator = _.bind(iterator, context);
return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator);
};
// Return the first value which passes a truth test. Aliased as `detect`.
_.find = _.detect = function(obj, iterator, context) {
var result;
any(obj, function(value, index, list) {
if (iterator.call(context, value, index, list)) {
result = value;
return true;
}
});
return result;
};
// Return all the elements that pass a truth test.
// Delegates to **ECMAScript 5**'s native `filter` if available.
// Aliased as `select`.
_.filter = _.select = function(obj, iterator, context) {
var results = [];
if (obj == null) return results;
if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
each(obj, function(value, index, list) {
if (iterator.call(context, value, index, list)) results[results.length] = value;
});
return results;
};
// Return all the elements for which a truth test fails.
_.reject = function(obj, iterator, context) {
var results = [];
if (obj == null) return results;
each(obj, function(value, index, list) {
if (!iterator.call(context, value, index, list)) results[results.length] = value;
});
return results;
};
// Determine whether all of the elements match a truth test.
// Delegates to **ECMAScript 5**'s native `every` if available.
// Aliased as `all`.
_.every = _.all = function(obj, iterator, context) {
var result = true;
if (obj == null) return result;
if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
each(obj, function(value, index, list) {
if (!(result = result && iterator.call(context, value, index, list))) return breaker;
});
return result;
};
// Determine if at least one element in the object matches a truth test.
// Delegates to **ECMAScript 5**'s native `some` if available.
// Aliased as `any`.
var any = _.some = _.any = function(obj, iterator, context) {
iterator || (iterator = _.identity);
var result = false;
if (obj == null) return result;
if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
each(obj, function(value, index, list) {
if (result || (result = iterator.call(context, value, index, list))) return breaker;
});
return !!result;
};
// Determine if a given value is included in the array or object using `===`.
// Aliased as `contains`.
_.include = _.contains = function(obj, target) {
var found = false;
if (obj == null) return found;
if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
found = any(obj, function(value) {
return value === target;
});
return found;
};
// Invoke a method (with arguments) on every item in a collection.
_.invoke = function(obj, method) {
var args = slice.call(arguments, 2);
return _.map(obj, function(value) {
return (_.isFunction(method) ? method || value : value[method]).apply(value, args);
});
};
// Convenience version of a common use case of `map`: fetching a property.
_.pluck = function(obj, key) {
return _.map(obj, function(value){ return value[key]; });
};
// Return the maximum element or (element-based computation).
_.max = function(obj, iterator, context) {
if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj);
if (!iterator && _.isEmpty(obj)) return -Infinity;
var result = {computed : -Infinity};
each(obj, function(value, index, list) {
var computed = iterator ? iterator.call(context, value, index, list) : value;
computed >= result.computed && (result = {value : value, computed : computed});
});
return result.value;
};
// Return the minimum element (or element-based computation).
_.min = function(obj, iterator, context) {
if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj);
if (!iterator && _.isEmpty(obj)) return Infinity;
var result = {computed : Infinity};
each(obj, function(value, index, list) {
var computed = iterator ? iterator.call(context, value, index, list) : value;
computed < result.computed && (result = {value : value, computed : computed});
});
return result.value;
};
// Shuffle an array.
_.shuffle = function(obj) {
var shuffled = [], rand;
each(obj, function(value, index, list) {
if (index == 0) {
shuffled[0] = value;
} else {
rand = Math.floor(Math.random() * (index + 1));
shuffled[index] = shuffled[rand];
shuffled[rand] = value;
}
});
return shuffled;
};
// Sort the object's values by a criterion produced by an iterator.
_.sortBy = function(obj, iterator, context) {
return _.pluck(_.map(obj, function(value, index, list) {
return {
value : value,
criteria : iterator.call(context, value, index, list)
};
}).sort(function(left, right) {
var a = left.criteria, b = right.criteria;
return a < b ? -1 : a > b ? 1 : 0;
}), 'value');
};
// Groups the object's values by a criterion. Pass either a string attribute
// to group by, or a function that returns the criterion.
_.groupBy = function(obj, val) {
var result = {};
var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; };
each(obj, function(value, index) {
var key = iterator(value, index);
(result[key] || (result[key] = [])).push(value);
});
return result;
};
// Use a comparator function to figure out at what index an object should
// be inserted so as to maintain order. Uses binary search.
_.sortedIndex = function(array, obj, iterator) {
iterator || (iterator = _.identity);
var low = 0, high = array.length;
while (low < high) {
var mid = (low + high) >> 1;
iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;
}
return low;
};
// Safely convert anything iterable into a real, live array.
_.toArray = function(iterable) {
if (!iterable) return [];
if (iterable.toArray) return iterable.toArray();
if (_.isArray(iterable)) return slice.call(iterable);
if (_.isArguments(iterable)) return slice.call(iterable);
return _.values(iterable);
};
// Return the number of elements in an object.
_.size = function(obj) {
return _.toArray(obj).length;
};
// Array Functions
// ---------------
// Get the first element of an array. Passing **n** will return the first N
// values in the array. Aliased as `head`. The **guard** check allows it to work
// with `_.map`.
_.first = _.head = function(array, n, guard) {
return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
};
// Returns everything but the last entry of the array. Especcialy useful on
// the arguments object. Passing **n** will return all the values in
// the array, excluding the last N. The **guard** check allows it to work with
// `_.map`.
_.initial = function(array, n, guard) {
return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
};
// Get the last element of an array. Passing **n** will return the last N
// values in the array. The **guard** check allows it to work with `_.map`.
_.last = function(array, n, guard) {
if ((n != null) && !guard) {
return slice.call(array, Math.max(array.length - n, 0));
} else {
return array[array.length - 1];
}
};
// Returns everything but the first entry of the array. Aliased as `tail`.
// Especially useful on the arguments object. Passing an **index** will return
// the rest of the values in the array from that index onward. The **guard**
// check allows it to work with `_.map`.
_.rest = _.tail = function(array, index, guard) {
return slice.call(array, (index == null) || guard ? 1 : index);
};
// Trim out all falsy values from an array.
_.compact = function(array) {
return _.filter(array, function(value){ return !!value; });
};
// Return a completely flattened version of an array.
_.flatten = function(array, shallow) {
return _.reduce(array, function(memo, value) {
if (_.isArray(value)) return memo.concat(shallow ? value : _.flatten(value));
memo[memo.length] = value;
return memo;
}, []);
};
// Return a version of the array that does not contain the specified value(s).
_.without = function(array) {
return _.difference(array, slice.call(arguments, 1));
};
// Produce a duplicate-free version of the array. If the array has already
// been sorted, you have the option of using a faster algorithm.
// Aliased as `unique`.
_.uniq = _.unique = function(array, isSorted, iterator) {
var initial = iterator ? _.map(array, iterator) : array;
var result = [];
_.reduce(initial, function(memo, el, i) {
if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) {
memo[memo.length] = el;
result[result.length] = array[i];
}
return memo;
}, []);
return result;
};
// Produce an array that contains the union: each distinct element from all of
// the passed-in arrays.
_.union = function() {
return _.uniq(_.flatten(arguments, true));
};
// Produce an array that contains every item shared between all the
// passed-in arrays. (Aliased as "intersect" for back-compat.)
_.intersection = _.intersect = function(array) {
var rest = slice.call(arguments, 1);
return _.filter(_.uniq(array), function(item) {
return _.every(rest, function(other) {
return _.indexOf(other, item) >= 0;
});
});
};
// Take the difference between one array and a number of other arrays.
// Only the elements present in just the first array will remain.
_.difference = function(array) {
var rest = _.flatten(slice.call(arguments, 1));
return _.filter(array, function(value){ return !_.include(rest, value); });
};
// Zip together multiple lists into a single array -- elements that share
// an index go together.
_.zip = function() {
var args = slice.call(arguments);
var length = _.max(_.pluck(args, 'length'));
var results = new Array(length);
for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i);
return results;
};
// If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
// we need this function. Return the position of the first occurrence of an
// item in an array, or -1 if the item is not included in the array.
// Delegates to **ECMAScript 5**'s native `indexOf` if available.
// If the array is large and already in sort order, pass `true`
// for **isSorted** to use binary search.
_.indexOf = function(array, item, isSorted) {
if (array == null) return -1;
var i, l;
if (isSorted) {
i = _.sortedIndex(array, item);
return array[i] === item ? i : -1;
}
if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
for (i = 0, l = array.length; i < l; i++) if (i in array && array[i] === item) return i;
return -1;
};
// Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
_.lastIndexOf = function(array, item) {
if (array == null) return -1;
if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item);
var i = array.length;
while (i--) if (i in array && array[i] === item) return i;
return -1;
};
// Generate an integer Array containing an arithmetic progression. A port of
// the native Python `range()` function. See
// [the Python documentation](http://docs.python.org/library/functions.html#range).
_.range = function(start, stop, step) {
if (arguments.length <= 1) {
stop = start || 0;
start = 0;
}
step = arguments[2] || 1;
var len = Math.max(Math.ceil((stop - start) / step), 0);
var idx = 0;
var range = new Array(len);
while(idx < len) {
range[idx++] = start;
start += step;
}
return range;
};
// Function (ahem) Functions
// ------------------
// Reusable constructor function for prototype setting.
var ctor = function(){};
// Create a function bound to a given object (assigning `this`, and arguments,
// optionally). Binding with arguments is also known as `curry`.
// Delegates to **ECMAScript 5**'s native `Function.bind` if available.
// We check for `func.bind` first, to fail fast when `func` is undefined.
_.bind = function bind(func, context) {
var bound, args;
if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
if (!_.isFunction(func)) throw new TypeError;
args = slice.call(arguments, 2);
return bound = function() {
if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
ctor.prototype = func.prototype;
var self = new ctor;
var result = func.apply(self, args.concat(slice.call(arguments)));
if (Object(result) === result) return result;
return self;
};
};
// Bind all of an object's methods to that object. Useful for ensuring that
// all callbacks defined on an object belong to it.
_.bindAll = function(obj) {
var funcs = slice.call(arguments, 1);
if (funcs.length == 0) funcs = _.functions(obj);
each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
return obj;
};
// Memoize an expensive function by storing its results.
_.memoize = function(func, hasher) {
var memo = {};
hasher || (hasher = _.identity);
return function() {
var key = hasher.apply(this, arguments);
return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
};
};
// Delays a function for the given number of milliseconds, and then calls
// it with the arguments supplied.
_.delay = function(func, wait) {
var args = slice.call(arguments, 2);
return setTimeout(function(){ return func.apply(func, args); }, wait);
};
// Defers a function, scheduling it to run after the current call stack has
// cleared.
_.defer = function(func) {
return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
};
// Returns a function, that, when invoked, will only be triggered at most once
// during a given window of time.
_.throttle = function(func, wait) {
var context, args, timeout, throttling, more;
var whenDone = _.debounce(function(){ more = throttling = false; }, wait);
return function() {
context = this; args = arguments;
var later = function() {
timeout = null;
if (more) func.apply(context, args);
whenDone();
};
if (!timeout) timeout = setTimeout(later, wait);
if (throttling) {
more = true;
} else {
func.apply(context, args);
}
whenDone();
throttling = true;
};
};
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds.
_.debounce = function(func, wait) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
func.apply(context, args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
};
// Returns a function that will be executed at most one time, no matter how
// often you call it. Useful for lazy initialization.
_.once = function(func) {
var ran = false, memo;
return function() {
if (ran) return memo;
ran = true;
return memo = func.apply(this, arguments);
};
};
// Returns the first function passed as an argument to the second,
// allowing you to adjust arguments, run code before and after, and
// conditionally execute the original function.
_.wrap = function(func, wrapper) {
return function() {
var args = [func].concat(slice.call(arguments, 0));
return wrapper.apply(this, args);
};
};
// Returns a function that is the composition of a list of functions, each
// consuming the return value of the function that follows.
_.compose = function() {
var funcs = arguments;
return function() {
var args = arguments;
for (var i = funcs.length - 1; i >= 0; i--) {
args = [funcs[i].apply(this, args)];
}
return args[0];
};
};
// Returns a function that will only be executed after being called N times.
_.after = function(times, func) {
if (times <= 0) return func();
return function() {
if (--times < 1) { return func.apply(this, arguments); }
};
};
// Object Functions
// ----------------
// Retrieve the names of an object's properties.
// Delegates to **ECMAScript 5**'s native `Object.keys`
_.keys = nativeKeys || function(obj) {
if (obj !== Object(obj)) throw new TypeError('Invalid object');
var keys = [];
for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key;
return keys;
};
// Retrieve the values of an object's properties.
_.values = function(obj) {
return _.map(obj, _.identity);
};
// Return a sorted list of the function names available on the object.
// Aliased as `methods`
_.functions = _.methods = function(obj) {
var names = [];
for (var key in obj) {
if (_.isFunction(obj[key])) names.push(key);
}
return names.sort();
};
// Extend a given object with all the properties in passed-in object(s).
_.extend = function(obj) {
each(slice.call(arguments, 1), function(source) {
for (var prop in source) {
obj[prop] = source[prop];
}
});
return obj;
};
// Fill in a given object with default properties.
_.defaults = function(obj) {
each(slice.call(arguments, 1), function(source) {
for (var prop in source) {
if (obj[prop] == null) obj[prop] = source[prop];
}
});
return obj;
};
// Create a (shallow-cloned) duplicate of an object.
_.clone = function(obj) {
if (!_.isObject(obj)) return obj;
return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
};
// Invokes interceptor with the obj, and then returns obj.
// The primary purpose of this method is to "tap into" a method chain, in
// order to perform operations on intermediate results within the chain.
_.tap = function(obj, interceptor) {
interceptor(obj);
return obj;
};
// Internal recursive comparison function.
function eq(a, b, stack) {
// Identical objects are equal. `0 === -0`, but they aren't identical.
// See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
if (a === b) return a !== 0 || 1 / a == 1 / b;
// A strict comparison is necessary because `null == undefined`.
if (a == null || b == null) return a === b;
// Unwrap any wrapped objects.
if (a._chain) a = a._wrapped;
if (b._chain) b = b._wrapped;
// Invoke a custom `isEqual` method if one is provided.
if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b);
if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a);
// Compare `[[Class]]` names.
var className = toString.call(a);
if (className != toString.call(b)) return false;
switch (className) {
// Strings, numbers, dates, and booleans are compared by value.
case '[object String]':
// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
// equivalent to `new String("5")`.
return a == String(b);
case '[object Number]':
// `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
// other numeric values.
return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
case '[object Date]':
case '[object Boolean]':
// Coerce dates and booleans to numeric primitive values. Dates are compared by their
// millisecond representations. Note that invalid dates with millisecond representations
// of `NaN` are not equivalent.
return +a == +b;
// RegExps are compared by their source patterns and flags.
case '[object RegExp]':
return a.source == b.source &&
a.global == b.global &&
a.multiline == b.multiline &&
a.ignoreCase == b.ignoreCase;
}
if (typeof a != 'object' || typeof b != 'object') return false;
// Assume equality for cyclic structures. The algorithm for detecting cyclic
// structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
var length = stack.length;
while (length--) {
// Linear search. Performance is inversely proportional to the number of
// unique nested structures.
if (stack[length] == a) return true;
}
// Add the first object to the stack of traversed objects.
stack.push(a);
var size = 0, result = true;
// Recursively compare objects and arrays.
if (className == '[object Array]') {
// Compare array lengths to determine if a deep comparison is necessary.
size = a.length;
result = size == b.length;
if (result) {
// Deep compare the contents, ignoring non-numeric properties.
while (size--) {
// Ensure commutative equality for sparse arrays.
if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break;
}
}
} else {
// Objects with different constructors are not equivalent.
if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false;
// Deep compare objects.
for (var key in a) {
if (_.has(a, key)) {
// Count the expected number of properties.
size++;
// Deep compare each member.
if (!(result = _.has(b, key) && eq(a[key], b[key], stack))) break;
}
}
// Ensure that both objects contain the same number of properties.
if (result) {
for (key in b) {
if (_.has(b, key) && !(size--)) break;
}
result = !size;
}
}
// Remove the first object from the stack of traversed objects.
stack.pop();
return result;
}
// Perform a deep comparison to check if two objects are equal.
_.isEqual = function(a, b) {
return eq(a, b, []);
};
// Is a given array, string, or object empty?
// An "empty" object has no enumerable own-properties.
_.isEmpty = function(obj) {
if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
for (var key in obj) if (_.has(obj, key)) return false;
return true;
};
// Is a given value a DOM element?
_.isElement = function(obj) {
return !!(obj && obj.nodeType == 1);
};
// Is a given value an array?
// Delegates to ECMA5's native Array.isArray
_.isArray = nativeIsArray || function(obj) {
return toString.call(obj) == '[object Array]';
};
// Is a given variable an object?
_.isObject = function(obj) {
return obj === Object(obj);
};
// Is a given variable an arguments object?
_.isArguments = function(obj) {
return toString.call(obj) == '[object Arguments]';
};
if (!_.isArguments(arguments)) {
_.isArguments = function(obj) {
return !!(obj && _.has(obj, 'callee'));
};
}
// Is a given value a function?
_.isFunction = function(obj) {
return toString.call(obj) == '[object Function]';
};
// Is a given value a string?
_.isString = function(obj) {
return toString.call(obj) == '[object String]';
};
// Is a given value a number?
_.isNumber = function(obj) {
return toString.call(obj) == '[object Number]';
};
// Is the given value `NaN`?
_.isNaN = function(obj) {
// `NaN` is the only value for which `===` is not reflexive.
return obj !== obj;
};
// Is a given value a boolean?
_.isBoolean = function(obj) {
return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
};
// Is a given value a date?
_.isDate = function(obj) {
return toString.call(obj) == '[object Date]';
};
// Is the given value a regular expression?
_.isRegExp = function(obj) {
return toString.call(obj) == '[object RegExp]';
};
// Is a given value equal to null?
_.isNull = function(obj) {
return obj === null;
};
// Is a given variable undefined?
_.isUndefined = function(obj) {
return obj === void 0;
};
// Has own property?
_.has = function(obj, key) {
return hasOwnProperty.call(obj, key);
};
// Utility Functions
// -----------------
// Run Underscore.js in *noConflict* mode, returning the `_` variable to its
// previous owner. Returns a reference to the Underscore object.
_.noConflict = function() {
root._ = previousUnderscore;
return this;
};
// Keep the identity function around for default iterators.
_.identity = function(value) {
return value;
};
// Run a function **n** times.
_.times = function (n, iterator, context) {
for (var i = 0; i < n; i++) iterator.call(context, i);
};
// Escape a string for HTML interpolation.
_.escape = function(string) {
return (''+string).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#x27;').replace(/\//g,'&#x2F;');
};
// Add your own custom functions to the Underscore object, ensuring that
// they're correctly added to the OOP wrapper as well.
_.mixin = function(obj) {
each(_.functions(obj), function(name){
addToWrapper(name, _[name] = obj[name]);
});
};
// Generate a unique integer id (unique within the entire client session).
// Useful for temporary DOM ids.
var idCounter = 0;
_.uniqueId = function(prefix) {
var id = idCounter++;
return prefix ? prefix + id : id;
};
// By default, Underscore uses ERB-style template delimiters, change the
// following template settings to use alternative delimiters.
_.templateSettings = {
evaluate : /<%([\s\S]+?)%>/g,
interpolate : /<%=([\s\S]+?)%>/g,
escape : /<%-([\s\S]+?)%>/g
};
// When customizing `templateSettings`, if you don't want to define an
// interpolation, evaluation or escaping regex, we need one that is
// guaranteed not to match.
var noMatch = /.^/;
// Within an interpolation, evaluation, or escaping, remove HTML escaping
// that had been previously added.
var unescape = function(code) {
return code.replace(/\\\\/g, '\\').replace(/\\'/g, "'");
};
// JavaScript micro-templating, similar to John Resig's implementation.
// Underscore templating handles arbitrary delimiters, preserves whitespace,
// and correctly escapes quotes within interpolated code.
_.template = function(str, data) {
var c = _.templateSettings;
var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' +
'with(obj||{}){__p.push(\'' +
str.replace(/\\/g, '\\\\')
.replace(/'/g, "\\'")
.replace(c.escape || noMatch, function(match, code) {
return "',_.escape(" + unescape(code) + "),'";
})
.replace(c.interpolate || noMatch, function(match, code) {
return "'," + unescape(code) + ",'";
})
.replace(c.evaluate || noMatch, function(match, code) {
return "');" + unescape(code).replace(/[\r\n\t]/g, ' ') + ";__p.push('";
})
.replace(/\r/g, '\\r')
.replace(/\n/g, '\\n')
.replace(/\t/g, '\\t')
+ "');}return __p.join('');";
var func = new Function('obj', '_', tmpl);
if (data) return func(data, _);
return function(data) {
return func.call(this, data, _);
};
};
// Add a "chain" function, which will delegate to the wrapper.
_.chain = function(obj) {
return _(obj).chain();
};
// The OOP Wrapper
// ---------------
// If Underscore is called as a function, it returns a wrapped object that
// can be used OO-style. This wrapper holds altered versions of all the
// underscore functions. Wrapped objects may be chained.
var wrapper = function(obj) { this._wrapped = obj; };
// Expose `wrapper.prototype` as `_.prototype`
_.prototype = wrapper.prototype;
// Helper function to continue chaining intermediate results.
var result = function(obj, chain) {
return chain ? _(obj).chain() : obj;
};
// A method to easily add functions to the OOP wrapper.
var addToWrapper = function(name, func) {
wrapper.prototype[name] = function() {
var args = slice.call(arguments);
unshift.call(args, this._wrapped);
return result(func.apply(_, args), this._chain);
};
};
// Add all of the Underscore functions to the wrapper object.
_.mixin(_);
// Add all mutator Array functions to the wrapper.
each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
var method = ArrayProto[name];
wrapper.prototype[name] = function() {
var wrapped = this._wrapped;
method.apply(wrapped, arguments);
var length = wrapped.length;
if ((name == 'shift' || name == 'splice') && length === 0) delete wrapped[0];
return result(wrapped, this._chain);
};
});
// Add all accessor Array functions to the wrapper.
each(['concat', 'join', 'slice'], function(name) {
var method = ArrayProto[name];
wrapper.prototype[name] = function() {
return result(method.apply(this._wrapped, arguments), this._chain);
};
});
// Start chaining a wrapped Underscore object.
wrapper.prototype.chain = function() {
this._chain = true;
return this;
};
// Extracts the result from a wrapped and chained object.
wrapper.prototype.value = function() {
return this._wrapped;
};
}).call(this);