├── .gitmodules ├── CHANGES ├── MIT-LICENSE ├── README.md ├── Rakefile ├── lib └── collections.js └── specs ├── array-specs.js ├── collection-specs.html ├── hash-specs.js └── string-specs.js /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "jsspec"] 2 | path = jsspec 3 | url = git://github.com/osteele/jsspec.git 4 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | = Collections JS Changes 2 | 3 | == 2008-04-18 4 | * Add top-level +pluck+ function. 5 | * Add +Array.select+ 6 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2007-2008 Oliver Steele 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Status 2 | ====== 3 | This library predates [Underscore], [Underscore.string], and [Lowdash], 4 | which I now recommend (and personally use) instead. 5 | 6 | Additionally, modern versions of JavaScript (as of ECMAScript 5) include a number of Array methods 7 | (`forEach`, `every`, `some`, `reduce`, `filter`, `indexOf`, `lastIndexOf`, and `reduceRight`), 8 | that provide a substantial subset of what this library provides. 9 | [Modern browsers](http://kangax.github.io/es5-compat-table/) support these methods. 10 | 11 | Finally, [es5-shim] is an alternative to Underscore etc. that extends pre-ECMAScript 5 JavaScript implementations with the ECMAScript 5 array methods. 12 | 13 | [Underscore]: http://underscorejs.org 14 | [Underscore.string]: http://epeli.github.io/underscore.string/ 15 | [Lowdash]: http://lodash.com 16 | [es5-shim]: https://github.com/kriskowal/es5-shim 17 | 18 | Collections JS 19 | ============== 20 | 21 | Framework-independent JavaScript collection methods, for use in 22 | browser JavaScript and in ActionScript. 23 | 24 | The Array and String methods use prototype extension; Hash methods use 25 | a proxying wrapper to avoid prototype pollution. The methods with the 26 | same names as the ECMAScript 1.6+ extensions have the same spec as 27 | those; the ones with the same name as prototype extensions have the 28 | same spec as those in the Prototype library; and there's a few odds 29 | and ends such as String#capitalize. 30 | 31 | The methods are documented in lib/collections.js, and speced (for 32 | further documentation, as well as testing) in spec/*.js. 33 | 34 | License 35 | ======= 36 | Copyright 2007-2008 by Oliver Steele. Available under the MIT License. 37 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake/rdoctask' 2 | 3 | Rake::RDocTask.new do |rd| 4 | rd.main = "README" 5 | rd.rdoc_files.include("README", "CHANGES") 6 | end 7 | -------------------------------------------------------------------------------- /lib/collections.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2007-2008 by Oliver Steele. All rights reserved. */ 2 | 3 | /* 4 | * JavaScript 1.6 Array extensions 5 | * 6 | * http://developer.mozilla.org/en/docs/New_in_JavaScript_1.6 7 | */ 8 | 9 | (function() { 10 | function define(klass, methodName, value) { 11 | klass.prototype[methodName] || (klass.prototype[methodName] = value); 12 | } 13 | 14 | /** This method returns true if each call to `fn` returns true. 15 | * `fn` is called with each item and its index. 16 | */ 17 | define(Array, 'every', function(fn, thisObject) { 18 | var len = this.length; 19 | for (var i = 0 ; i < len; i++) 20 | if (!fn.call(thisObject, this[i], i, this)) 21 | return false; 22 | return true; 23 | }); 24 | 25 | /** This method returns true if some call to `fn` returns true. 26 | * `fn` is called with each item and its index. 27 | */ 28 | define(Array, 'some', function(fn, thisObject) { 29 | var len = this.length; 30 | for (var i = 0 ; i < len; i++) 31 | if (fn.call(thisObject, this[i], i, this)) 32 | return true; 33 | return false; 34 | }); 35 | 36 | /** Returns a subarray for which `fn` returns true. 37 | * `fn` is called with each item and its index. 38 | */ 39 | define(Array, 'filter', function(fn, thisObject) { 40 | var len = this.length, 41 | results = []; 42 | for (var i = 0 ; i < len; i++) 43 | if (fn.call(thisObject, this[i], i, this)) 44 | results.push(this[i]); 45 | return results; 46 | }); 47 | 48 | /** Runs `fn` for each item in the array. 49 | * `fn` is called with each item and its index. 50 | */ 51 | define(Array, 'forEach', function(fn, thisObject) { 52 | var len = this.length; 53 | for (var i = 0 ; i < len; i++) 54 | if (typeof this[i] != 'undefined') 55 | fn.call(thisObject, this[i], i, this); 56 | }); 57 | 58 | /** Returns the index such that this[index] === searchElement, 59 | * or -1. 60 | * 61 | * The `fromIndex` parameter is not implemented. 62 | */ 63 | define(Array, 'indexOf', function(searchElement/*, fromIndex*/) { 64 | var len = this.length; 65 | for (var i = 0; i < len; i++) 66 | if (this[i] == searchElement) 67 | return i; 68 | return -1; 69 | }); 70 | 71 | /** Runs a function on every item in the array and returns the results 72 | * in an array. 73 | */ 74 | define(Array, 'map', function(fn, thisObject) { 75 | var len = this.length, 76 | result = new Array(len); 77 | for (var i = 0; i < len; i++) 78 | if (typeof this[i] != 'undefined') 79 | result[i] = fn.call(thisObject, this[i], i, this); 80 | return result; 81 | }); 82 | 83 | 84 | /* 85 | * Prototype Array extensions 86 | * 87 | * These have the same spec as in the Prototype library. 88 | */ 89 | 90 | /** Returns a copy of this array without null and undefined elements. */ 91 | define(Array, 'compact', function() { 92 | var results = []; 93 | this.forEach(function(item) { 94 | item == null || item == undefined || results.push(item); 95 | }); 96 | return results; 97 | }); 98 | 99 | /** Returns the first element e of this array such that fn(e, ix) 100 | * is true, where ix is the array index. Returns null if no 101 | * such element exists. 102 | */ 103 | define(Array, 'detect', function(fn, thisObject) { 104 | for (var i = 0; i < this.length; i++) 105 | if (fn.call(thisObject, this[i], i, this)) 106 | return this[i]; 107 | return null; 108 | }); 109 | 110 | /** A synonym for Array#forEach. */ 111 | define(Array, 'each', Array.prototype.forEach); 112 | 113 | /** Returns true iff an element of this array is == `item`. */ 114 | define(Array, 'find', function(item) { 115 | for (var i = 0; i < this.length; i++) 116 | if (this[i] == item) 117 | return true; 118 | return false; 119 | }); 120 | 121 | /** A synonym for Array#find. */ 122 | define(Array, 'contains', Array.prototype.find); 123 | 124 | /** Invokes the `name` method of each element of this array, 125 | * and returns a new array of the results. Additional arguments 126 | * to this method are supplied as arguments to the `name` method. 127 | */ 128 | define(Array, 'invoke', function(name) { 129 | var result = new Array(this.length), 130 | args = [].slice.call(arguments, 1); 131 | this.forEach(function(item, ix) { 132 | result[ix] = item[name].apply(item, args); 133 | }); 134 | return result; 135 | }); 136 | 137 | /** Returns an array of the values of the `name` property of each 138 | * element of this array. 139 | */ 140 | define(Array, 'pluck', function(name) { 141 | var result = new Array(this.length); 142 | this.forEach(function(item, ix) { 143 | result[ix] = item[name]; 144 | }); 145 | return result; 146 | }); 147 | 148 | /** A synonym for Array#filter. */ 149 | define(Array, 'select', Array.prototype.filter); 150 | 151 | /** Returns the sum of the items of this array, which should be numeric. */ 152 | define(Array, 'sum', function() { 153 | var sum = 0; 154 | this.forEach(function(n) {sum += n}); 155 | return sum; 156 | }); 157 | 158 | /** Returns a copy of this array not including any items == item. */ 159 | define(Array, 'without', function(item) { 160 | return this.filter(function(it) { 161 | return it != item; 162 | }); 163 | }); 164 | 165 | 166 | /* 167 | * Other array extensions 168 | */ 169 | 170 | /** Returns a string: the string representations of the elements, 171 | * interpolated by ','. 172 | */ 173 | define(Array, 'commas', function() { 174 | return this.join(','); 175 | }); 176 | 177 | /** Returns the minimum of the elements of this array, which should 178 | * be numeric. 179 | */ 180 | define(Array, 'min', function() { 181 | var n = Infinity; 182 | for (var i = 0, len = this.length; i < len; i++) 183 | n = Math.min(n, this[i]); 184 | return n; 185 | }); 186 | 187 | /** Returns the maximum of the elements of this array, which should 188 | * be numeric. 189 | */ 190 | define(Array, 'max', function() { 191 | var n = -Infinity; 192 | for (var i = 0, len = this.length; i < len; i++) 193 | n = Math.max(n, this[i]); 194 | return n; 195 | }); 196 | 197 | /** Returns the last element of this array, or null if it is empty. 198 | */ 199 | define(Array, 'last', function() { 200 | var length = this.length; 201 | return length ? this[length-1] : null; 202 | }); 203 | 204 | /** Returns a partition of the elements of this array by `fn`. The 205 | * return value is a hash, where the keys are the values returned by 206 | * `fn` and the values are lists of elements with those keys. 207 | */ 208 | define(Array, 'partitionBy', function(fn, thisObject) { 209 | var partitions = {}; 210 | for (var i = 0, len = this.length; i < len; i++) { 211 | var item = this[i], 212 | key = fn.call(thisObject, item, i), 213 | partition = partitions[key]; 214 | if (!partition) 215 | partition = partitions[key] = []; 216 | partition.push(item); 217 | } 218 | return partitions; 219 | }); 220 | 221 | })(); 222 | 223 | 224 | /* 225 | * Global array functions 226 | */ 227 | 228 | if (!Array.slice) { // mozilla already defines this 229 | Array.slice = (function() { 230 | var slice = Array.prototype.slice; 231 | return function(array) { 232 | return slice.apply(array, slice.call(arguments, 1)); 233 | }; 234 | })(); 235 | } 236 | 237 | /* Return a function that returns the value of the `propertyName` 238 | * property of its first argument. */ 239 | function pluck(propertyName) { 240 | return function(object) { 241 | return object[propertyName]; 242 | } 243 | } 244 | 245 | /** Return an array. Identity for arrays; same as monadic `return` 246 | * for other values. 247 | */ 248 | Array.toList = function(ar) { 249 | return ar instanceof Array ? ar : [ar]; 250 | } 251 | 252 | /** Return a non-array. Returns the first element of an array; 253 | * identity for other values. 254 | */ 255 | Array.fromList = function(ar) { 256 | return ar instanceof Array ? ar[0] : ar; 257 | } 258 | 259 | 260 | /* 261 | * Prototype-style Hash utilities 262 | * 263 | * An framework-neutral implementation of the Prototype Hash 264 | * API. See http://www.prototypejs.org/api/hash 265 | */ 266 | 267 | function $H(object) { 268 | return object instanceof Hash ? object : new Hash(object); 269 | } 270 | 271 | function Hash(object) { 272 | this.hash = object; 273 | } 274 | 275 | Hash.prototype.each = function(fn) { 276 | var hash = this.hash, 277 | ix = 0; 278 | for (var key in hash) 279 | fn({key:key, value:hash[key]}, ix++); 280 | } 281 | 282 | Hash.prototype.keys = function() { 283 | var hash = this.hash, 284 | keys = []; 285 | for (var key in hash) 286 | keys.push(key); 287 | return keys; 288 | } 289 | 290 | Hash.prototype.merge = function(source) { 291 | var hash = this.hash; 292 | for (var key in source) 293 | hash[key] = source[key]; 294 | return hash; 295 | } 296 | 297 | Hash.prototype.toQueryString = function() { 298 | var hash = this.hash, 299 | words = []; 300 | for (name in hash) { 301 | var value = hash[name]; 302 | typeof value == 'function' || 303 | words.push([name, '=', value].join('')); 304 | } 305 | words.sort(); 306 | return words.join('&'); 307 | } 308 | 309 | Hash.prototype.values = function() { 310 | var hash = this.hash, 311 | values = []; 312 | for (var key in hash) 313 | values.push(hash[key]); 314 | return values; 315 | } 316 | 317 | 318 | /* 319 | * Other Hash extensions 320 | */ 321 | 322 | Hash.prototype.compact = function() { 323 | var hash = this.hash, 324 | result = {}; 325 | for (var name in hash) { 326 | var value = hash[name]; 327 | if (value != null && value != undefined) 328 | result[name] = value; 329 | } 330 | return result; 331 | } 332 | 333 | Hash.prototype.items = function() { 334 | var hash = this.hash, 335 | result = []; 336 | for (var key in hash) 337 | result.push({key:key, value:hash[key]}); 338 | return result; 339 | } 340 | 341 | Hash.prototype.map = function(fn) { 342 | var hash = this.hash, 343 | result = []; 344 | for (var key in hash) 345 | result.push(fn({key:key, value:hash[key]})); 346 | return result; 347 | }; 348 | 349 | 350 | /* 351 | * String utilities 352 | */ 353 | 354 | (function() { 355 | function define(klass, methodName, value) { 356 | klass.prototype[methodName] || (klass.prototype[methodName] = value); 357 | } 358 | 359 | /** Return this string with its first letter uppercase. */ 360 | define(String, 'capitalize', function() { 361 | return this.slice(0,1).toUpperCase() + this.slice(1); 362 | }); 363 | 364 | /** Replace the four XML characters by entities. */ 365 | define(String, 'escapeHTML', function() { 366 | return (this.replace('&', '&') 367 | .replace('<', '<') 368 | .replace('>', '>') 369 | .replace('"', '"')); 370 | }); 371 | 372 | /** An interesting and complicated function that I will document later. */ 373 | define(String, 'inflect', function(suffix) { 374 | var index = this.indexOf(' '); 375 | if (index >= 0) 376 | return this.slice(0, index).inflect(suffix) + this.slice(index); 377 | // pos == 'v', or vp has single word 378 | var vowels = "aeiou", 379 | inflections = {'ed': {'set': 'set'}}, 380 | key = this.toLowerCase(), 381 | value = (inflections[suffix]||{})[key]; 382 | if (!value) { 383 | value = key; 384 | var lastChar = key.charAt(key.length-1); 385 | info(0, key); 386 | switch (lastChar) { 387 | case 'y': 388 | if (suffix == 'ed') 389 | value = value.slice(0, value.length-1) + 'i'; 390 | break; 391 | case 'e': 392 | value = value.slice(0, value.length-1); 393 | break; 394 | } 395 | if (key == value && 396 | // CVC -> VCVV 397 | vowels.indexOf(value.charAt(value.length-1)) < 0 && 398 | vowels.indexOf(value.charAt(value.length-2)) >= 0 && 399 | vowels.indexOf(value.charAt(value.length-3)) < 0) 400 | value += value.charAt(value.length-1); 401 | value += suffix; 402 | } 403 | // TODO: capitalize 404 | return value; 405 | }); 406 | 407 | /** Dirt-cheap pluralization: adds 's' if count != 1 */ 408 | define(String, 'pluralize', function(count) { 409 | if (arguments.length && count == 1) 410 | return this; 411 | return this+'s'; 412 | }); 413 | 414 | /** Return a string without start and trailing whitespace. */ 415 | define(String, 'strip', function() { 416 | // uses ws instead of regular expressions, so that this will 417 | // compile with OpenLaszlo 418 | var ws = " \t\n\r"; 419 | for (j = this.length; --j >= 0 && ws.indexOf(this.charAt(j)) >= 0; ) 420 | ; 421 | for (i = 0; i < j && ws.indexOf(this.charAt(i)) >= 0; i++) 422 | ; 423 | return 0 == i && j == this.length-1 ? this : this.slice(i, j+1); 424 | }); 425 | 426 | /** If this string is longer than `length`, trim it to `length` 427 | * and add `ellipsis`. 428 | */ 429 | define(String, 'truncate', function(length, ellipsis) { 430 | return (this.length <= length 431 | ? string 432 | : string.slice(0, length) + ellipsis); 433 | }); 434 | 435 | })(); -------------------------------------------------------------------------------- /specs/array-specs.js: -------------------------------------------------------------------------------- 1 | describe('Array.every', { 2 | 'should return true': function() { 3 | value_of([1, 2].every(function(x){return x>0})).should_be(true); 4 | }, 5 | 'should return false': function() { 6 | value_of([1, 2].every(function(x){return x>1})).should_be(false); 7 | }, 8 | 'should return true for an empty list': function() { 9 | value_of([].every(function(x){return x>1})).should_be(true); 10 | } 11 | }); 12 | 13 | describe('Array.some', { 14 | 'should return true': function() { 15 | value_of([1, 2].some(function(x){return x>1})).should_be(true); 16 | }, 17 | 'should return false': function() { 18 | value_of([1, 2].some(function(x){return x>2})).should_be(false); 19 | }, 20 | 'should return false for an empty list': function() { 21 | value_of([].some(function(x){return x>1})).should_be(false); 22 | } 23 | }); 24 | 25 | describe('Array.filter', { 26 | 'should remove some elements': function() { 27 | value_of([1, 2].filter(function(x){return x>1})).should_be([2]); 28 | } 29 | }); 30 | 31 | describe('Array.forEach', { 32 | 'should apply fn to each element': function() { 33 | var visited = []; 34 | [1, 2].forEach(function(x){visited.push(x)}); 35 | value_of(visited).should_be([1,2]); 36 | } 37 | }); 38 | 39 | describe('Array.indexOf', { 40 | 'should return the least index': function() { 41 | var ix = [1, 2, 3, 2].indexOf(2); 42 | value_of(ix).should_be(1); 43 | }, 44 | 45 | 'should return -1 when no item matches': function() { 46 | var ix = [1, 2, 3, 2].indexOf(5); 47 | value_of(ix).should_be(-1); 48 | }, 49 | 50 | 'should return -1 when the array is empty': function() { 51 | var ix = [].indexOf(2); 52 | value_of(ix).should_be(-1); 53 | } 54 | }); 55 | 56 | describe('Array.map', { 57 | 'should apply fn to each item': function() { 58 | var value = [1, 2, 3].map(function(x){return x + 1}); 59 | value_of(value).should_be([2, 3, 4]); 60 | } 61 | }); 62 | 63 | describe('Array.compact', { 64 | 'should remove null': function() { 65 | value_of([1,null,2].compact()).should_be([1,2]); 66 | }, 67 | 'should remove undefined': function() { 68 | value_of([1,undefined,2].compact()).should_be([1,2]); 69 | }, 70 | 'should remove multiple null values': function() { 71 | value_of([1,null,2,null].compact()).should_be([1,2]); 72 | }, 73 | 'should leave 0 alone': function() { 74 | value_of([1,0,2].compact()).should_be([1,0,2]); 75 | }, 76 | }); 77 | 78 | describe('Array.detect', { 79 | 'should return the first matching item': function() { 80 | var value = [1, 2, 3, 2].detect(function(x){return x > 1}); 81 | value_of(value).should_be(2); 82 | }, 83 | 84 | 'should return null when no item matches': function() { 85 | var value = [1, 2, 3, 2].detect(function(x){return x == 5}); 86 | value_of(value).should_be(null); 87 | }, 88 | 89 | 'should return null when the array is empty': function() { 90 | var value = [].detect(function(x){return x == 5}); 91 | value_of(value).should_be(null); 92 | } 93 | }); 94 | 95 | describe('Array.find', { 96 | 'should find the item': function() { 97 | var value = [1, 2, 3, 2].find(2); 98 | value_of(value).should_be(true); 99 | }, 100 | 101 | 'should return false when no item matches': function() { 102 | var value = [1, 2, 3, 2].find(5); 103 | value_of(value).should_be(false); 104 | }, 105 | 106 | 'should return false when the array is empty': function() { 107 | var value = [].find(2); 108 | value_of(value).should_be(false); 109 | } 110 | }); 111 | 112 | describe('Array.invoke', { 113 | 'should call the method': function() { 114 | function C(x) { this.x = x } 115 | C.prototype.m = function() {return this.x}; 116 | var ar = [new C(2), new C(3)]; 117 | value_of(ar.invoke('m')).should_be([2,3]); 118 | } 119 | }); 120 | 121 | describe('Array.pluck', { 122 | 'should retrieve the property': function() { 123 | function C(x) { this.x = x } 124 | var ar = [new C(2), new C(3)]; 125 | value_of(ar.pluck('x')).should_be([2,3]); 126 | } 127 | }); 128 | 129 | describe('Array.sum', { 130 | 'should sum the values': function() { 131 | value_of([1,2,3].sum()).should_be(6); 132 | }, 133 | 'should return 0 when the list is empty': function() { 134 | value_of([].sum()).should_be(0); 135 | } 136 | }); 137 | 138 | describe('Array.without', { 139 | 'should remove all copies of the value': function() { 140 | value_of([1,2,3,2,4].without(2)).should_be([1,3,4]); 141 | } 142 | }); 143 | 144 | 145 | describe('Array.min', { 146 | 'should return the min': function() { 147 | value_of([1,2,3].min()).should_be(1); 148 | }, 149 | 'should return Infinity when the list is empty': function() { 150 | value_of([].min()).should_be(Infinity); 151 | } 152 | }); 153 | 154 | 155 | describe('Array.max', { 156 | 'should return the max': function() { 157 | value_of([1,2,3].max()).should_be(3); 158 | }, 159 | 'should return -Infinity when the list is empty': function() { 160 | value_of([].max()).should_be(-Infinity); 161 | } 162 | }); 163 | 164 | describe('Array.commas', { 165 | 'should join the values': function() { 166 | value_of([1,2,3].commas()).should_be('1,2,3'); 167 | }, 168 | 'should return the item string when the array is unary': function() { 169 | value_of(['x'].commas()).should_be('x'); 170 | }, 171 | 'should return the empty string when the array is empty': function() { 172 | value_of([].commas()).should_be(''); 173 | } 174 | }); 175 | 176 | describe('Array.last', { 177 | 'should return the last value': function() { 178 | value_of([1,2,3].last()).should_be(3); 179 | }, 180 | 'should return the null string when the array is empty': function() { 181 | value_of([].last()).should_be(null); 182 | } 183 | }); 184 | 185 | describe('Array.partitionBy', { 186 | 'should partition': function() { 187 | var part = [1,2,10,11,22].partitionBy(function(x) {return x % 10}); 188 | value_of(part[0]).should_be([10]); 189 | value_of(part[1]).should_be([1,11]); 190 | value_of(part[2]).should_be([2,22]); 191 | } 192 | }); 193 | 194 | describe('Array.slice', { 195 | 'should slice': function() { 196 | var array = [1,2,10,11,22]; 197 | value_of(Array.slice(array, 0).join(',')).should_be([1,2,10,11,22].join(',')); 198 | value_of(Array.slice(array, 1).join(',')).should_be([2,10,11,22].join(',')); 199 | value_of(Array.slice(array, 1, 3).join(',')).should_be([2,10].join(',')); 200 | } 201 | }); 202 | 203 | describe('pluck', { 204 | 'should work with map': function() { 205 | value_of([{x:1,y:2}, {x:3,y:4}].map(pluck('x'))).should_be([1,3]); 206 | }, 207 | 'should work with select': function() { 208 | var array = [{x:1,y:2}, {z:3,w:4}]; 209 | var result = array.select(pluck('x')); 210 | value_of(result.length).should_be(1); 211 | value_of(result[0]).should_be(array[0]); 212 | } 213 | }); 214 | 215 | describe('Array.toList', { 216 | 'should be identity on arrays': function() { 217 | value_of(Array.toList([1,2])).should_be([1,2]); 218 | }, 219 | 'should inject non-arrays': function() { 220 | value_of(Array.toList(1)).should_be([1]); 221 | } 222 | }); 223 | 224 | describe('Array.fromList', { 225 | 'should be extract the first element from a list': function() { 226 | value_of(Array.fromList([1,2])).should_be(1); 227 | }, 228 | 'should be identity on non-arrays': function() { 229 | value_of(Array.fromList(1)).should_be(1); 230 | } 231 | }); 232 | -------------------------------------------------------------------------------- /specs/collection-specs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSSpec results 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

A

B

15 | 16 | -------------------------------------------------------------------------------- /specs/hash-specs.js: -------------------------------------------------------------------------------- 1 | describe('$H', { 2 | 'should return a Hash': function() { 3 | value_of($H({}) instanceof Hash).should_be(true); 4 | }, 5 | 'should be idempotent': function() { 6 | var h = $H({}); 7 | value_of($H(h)).should_be(h); 8 | } 9 | }); 10 | 11 | describe('Hash.each', { 12 | 'should visit each item': function() { 13 | var visits = 0; 14 | $H({a:1, b:2}).each(function(item) { visits++ }); 15 | value_of(visits).should_be(2); 16 | }, 17 | 'should supply the keys': function() { 18 | var visits = []; 19 | $H({a:1, b:2}).each(function(item) { visits.push(item.key) }); 20 | value_of(visits).should_be(['a', 'b']); 21 | }, 22 | 'should visit the values': function() { 23 | var visits = []; 24 | $H({a:1, b:2}).each(function(item) { visits.push(item.value) }); 25 | value_of(visits).should_be([1,2]); 26 | } 27 | }); 28 | 29 | describe('Hash.keys', { 30 | 'should return a list of keys': function() { 31 | value_of($H({a:1, b:2}).keys()).should_be(['a','b']); 32 | } 33 | }); 34 | 35 | describe('Hash.merge', { 36 | 'should fill in unspecified values': function() { 37 | var h = $H({a:1, b:2}).merge({b:3, c:4}); 38 | value_of(h.c).should_be(4); 39 | }, 40 | 'should replace overridden values': function() { 41 | var h = $H({a:1, b:2}).merge({b:3, c:4}); 42 | value_of(h.b).should_be(3); 43 | }, 44 | 'should leave values non-overridden values intact': function() { 45 | var h = $H({a:1, b:2}).merge({b:3, c:4}); 46 | value_of(h.a).should_be(1); 47 | } 48 | }); 49 | 50 | describe('Hash.toQueryString', { 51 | 'should join values with &': function() { 52 | value_of($H({a:1, b:2}).toQueryString()).should_be('a=1&b=2'); 53 | }, 54 | 'should return null when there are no values': function() { 55 | value_of($H({}).toQueryString()).should_be(''); 56 | } 57 | }); 58 | 59 | describe('Hash.values', { 60 | 'should return a list of values': function() { 61 | value_of($H({a:1, b:2}).values()).should_be([1, 2]); 62 | } 63 | }); 64 | 65 | describe('Hash.compact', { 66 | 'should remove null-valued items': function() { 67 | var h = $H({a:null, b:2}).compact(); 68 | value_of('a' in h).should_be(false); 69 | }, 70 | 'should remove undefined-valued items': function() { 71 | var h = $H({a:undefined, b:2}).compact(); 72 | value_of('a' in h).should_be(false); 73 | }, 74 | 'should remove multiple null-valued items': function() { 75 | var h = $H({a:null, b:2, c:null}).compact(); 76 | value_of('a' in h).should_be(false); 77 | value_of('c' in h).should_be(false); 78 | }, 79 | 'should leave 0-valued items': function() { 80 | var h = $H({a:0, b:2}).compact(); 81 | value_of('a' in h).should_be(true); 82 | }, 83 | }); 84 | 85 | describe('Hash.items', { 86 | 'should return a list of items': function() { 87 | var items = $H({a:1, b:2}).items(); 88 | value_of(items.length).should_be(2); 89 | value_of(items[0].key).should_be('a'); 90 | value_of(items[0].value).should_be(1); 91 | value_of(items[1].key).should_be('b'); 92 | value_of(items[1].value).should_be(2); 93 | } 94 | }); 95 | 96 | describe('Hash.map', { 97 | 'should apply the function to each item': function() { 98 | var results = $H({a:1, b:2}).map(function(item) { 99 | return [item.key, '->', item.value].join(''); 100 | }); 101 | value_of(results).should_be(['a->1', 'b->2']); 102 | } 103 | }); 104 | -------------------------------------------------------------------------------- /specs/string-specs.js: -------------------------------------------------------------------------------- 1 | describe('String.capitalize', { 2 | 'should capitalize the first letter': function() { 3 | value_of('some Text'.capitalize()).should_be('Some Text'); 4 | }, 5 | 'should be identity on an empty string': function() { 6 | value_of(''.capitalize()).should_be(''); 7 | } 8 | }); 9 | 10 | describe('String.escapeHTML', { 11 | 'should quote &': function() { 12 | value_of('a & b'.escapeHTML()).should_be('a & b'); 13 | } 14 | }); 15 | 16 | describe('String.pluralize', { 17 | 'should add s': function() { 18 | value_of('dog'.pluralize()).should_be('dogs'); 19 | }, 20 | 'should pluralize when count == 0': function() { 21 | value_of('dog'.pluralize(0)).should_be('dogs'); 22 | }, 23 | 'should pluralize when count > 1': function() { 24 | value_of('dog'.pluralize(2)).should_be('dogs'); 25 | }, 26 | 'should not pluralize when count == 1': function() { 27 | value_of('dog'.pluralize(1)).should_be('dog'); 28 | } 29 | }); 30 | 31 | describe('String.strip', { 32 | 'should remove initial and final spaces': function() { 33 | value_of(' a b '.strip()).should_be('a b'); 34 | }, 35 | 'should remove \\n': function() { 36 | value_of('\na b \n'.strip()).should_be('a b'); 37 | }, 38 | 'should apply to the left side': function() { 39 | value_of('\na b'.strip()).should_be('a b'); 40 | }, 41 | 'should apply to the right side': function() { 42 | value_of('a b \n'.strip()).should_be('a b'); 43 | }, 44 | "should be identity when there is no terminal ws": function() { 45 | value_of('a b \n'.strip()).should_be('a b'); 46 | }, 47 | 'should be identity on the empty string': function() { 48 | value_of(''.strip()).should_be(''); 49 | } 50 | }); 51 | 52 | describe('String.truncate', { 53 | 'should return a list of values': function() { 54 | value_of($H({a:1, b:2}).values()).should_be([1, 2]); 55 | } 56 | }); 57 | --------------------------------------------------------------------------------