├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── package.json ├── spell.js └── test ├── css └── mocha.css ├── index.html ├── lib ├── JSONStream │ ├── .gitignore │ ├── bench.js │ ├── examples │ │ └── all_docs.js │ ├── index.js │ ├── package.json │ ├── readme.markdown │ └── test │ │ ├── fixtures │ │ ├── all_docs_include.json │ │ └── all_npm.json │ │ ├── parsejson.js │ │ ├── stringify.js │ │ ├── test.js │ │ ├── test2.js │ │ └── two-ways.js ├── jquery-1.7.1.min.js ├── json2.js ├── mocha-0.3.6.js └── request │ ├── LICENSE │ ├── README.md │ ├── main.js │ ├── mimetypes.js │ ├── oauth.js │ ├── package.json │ ├── tests │ ├── googledoodle.png │ ├── run.sh │ ├── server.js │ ├── test-body.js │ ├── test-cookie.js │ ├── test-cookiejar.js │ ├── test-errors.js │ ├── test-oauth.js │ ├── test-pipes.js │ ├── test-proxy.js │ └── test-timeout.js │ ├── uuid.js │ └── vendor │ └── cookie │ ├── index.js │ └── jar.js ├── perf.js ├── resources ├── big.js ├── big.json ├── big.txt ├── big_browser.js ├── npm.js ├── npm.json ├── perf1.js └── perf2.js └── spell.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | *tmp.* 4 | *.log -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: "node_js" 2 | node_js: 3 | - 0.6 4 | branches: 5 | only: 6 | - master 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | copyright 2011 nuno job (oO)--',-- 2 | & pedro teixeira 3 | 4 | licensed under the apache license, version 2.0 (the "license"); 5 | you may not use this file except in compliance with the license. 6 | you may obtain a copy of the license at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | unless required by applicable law or agreed to in writing, software 11 | distributed under the license is distributed on an "as is" basis, 12 | without warranties or conditions of any kind, either express or implied. 13 | see the license for the specific language governing permissions and 14 | limitations under the license. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # spell 2 | 3 | `spell` is a dictionary module for `node.js`. for an explanation of the algorithm, performance, expectations, and techniques used please read [this article][norvig] 4 | 5 | # installation 6 | 7 | ## node.js 8 | 9 | 1. install [npm] 10 | 2. `npm install spell` 11 | 3. `var spell = require('spell');` 12 | 13 | ## browser 14 | 15 | 1. minimize spell.js 16 | 2. load it into your webpage 17 | 18 | # usage 19 | 20 | ## basics 21 | 22 | ``` js 23 | // instantiate a new dictionary 24 | var dict = spell(); 25 | // load text into dictionary so we can train the dictionary to know 26 | // which words exists and which ones are more frequent than others 27 | dict.load("I am going to the park with Theo today. It's going to be the bomb"); 28 | console.log(dict.suggest('thew')); 29 | ``` 30 | 31 | normally you would generate the dictionary once and then re-use it so this code is unlikely and serves for demonstration purposes only. this should log: 32 | 33 | ``` js 34 | [{"word": "the", "score": 2}, {"word": "theo", "score": 1}] 35 | ``` 36 | 37 | as there are two occurrences of the word `the` and one of the word `theo` 38 | 39 | feeling lucky? 40 | 41 | ``` js 42 | dict.lucky('thew'); 43 | ``` 44 | 45 | returns 46 | 47 | ``` js 48 | "the" 49 | ``` 50 | 51 | you can also `add` and `remove` words from the dictionary: 52 | 53 | ``` js 54 | dict.remove_word('park'); 55 | dict.add_word('nuno'); 56 | ``` 57 | 58 | and you can `reset` the dictionary, making it empty: 59 | 60 | ``` js 61 | dict.reset(); 62 | ``` 63 | 64 | if you want to export the dictionary: 65 | 66 | ``` js 67 | dict.export(); 68 | ``` 69 | 70 | ## advanced 71 | 72 | when loading you can provide a compiled dictionary instead of free form text 73 | 74 | ``` js 75 | dict.load( 76 | { corpus: 77 | { "I" : 1 78 | , "am" : 1 79 | , "going" : 1 80 | , "to" : 2 81 | , "the" : 1 82 | , "park" : 1 83 | } 84 | } 85 | ); 86 | ``` 87 | 88 | you can also provide options: 89 | 90 | * `reset`, defaults to true, meaning it will reset the dictionary before running load 91 | * `store`, defaults to true, meaning it will store the dictionary after running load 92 | * `after_store`, defaults to empty function, the callback function to run after `store` is done 93 | 94 | e.g. to add text to an existing `dict` you could do: 95 | 96 | ``` js 97 | dict.load("Better yet, chocolate", { reset: false } ); 98 | ``` 99 | 100 | finally when adding words you can optionally give it a score: 101 | 102 | ``` js 103 | dict.add_word('beer', {score: 100}); 104 | ``` 105 | 106 | if you are working with words that include punctuation in your dictionary you might need to override the alphabet that is being used. e.g. you might want to add `.` & `@` if you have a dictionary of email address. you can do that by: 107 | 108 | ``` js 109 | // instantiate a new dictionary 110 | var dict = spell(); 111 | // load text into dictionary so we can train the dictionary to know 112 | // which words exists and which ones are more frequent than others 113 | dict.load("nuno@dino.saur pedro@space.ship"); 114 | console.log(dict.suggest('nuno@dino.sau', 115 | "abcdefghijklmnopqrstuvwxyz.@".split("") 116 | )); 117 | ``` 118 | 119 | ## storage 120 | 121 | by default `dict` is stored in process (memory) for each dictionary instance you create. however if you feel like storing the dictionary externally, or use a shared dictionary, you can: 122 | 123 | an example using `localStorage`: 124 | 125 | ``` js 126 | var dict = spell( 127 | { "get" : function () { 128 | JSON.parse(window.localStorage.getItem('dict')); 129 | } 130 | , "store" : function (dict,after_store) { 131 | window.localStorage.setItem('dict', JSON.stringify(dict)); 132 | } 133 | } 134 | ); 135 | ``` 136 | 137 | # roadmap 138 | 139 | check [issues] 140 | 141 | # contribute 142 | 143 | everyone is welcome to contribute. patches, bug-fixes, new features 144 | 145 | 1. create an [issue][issues] so the community can comment on your idea 146 | 2. fork `spell` 147 | 3. create a new branch `git checkout -b my_branch` 148 | 4. create tests for the changes you made 149 | 5. make sure you pass both existing and newly inserted tests 150 | 6. commit your changes 151 | 7. push to your branch `git push origin my_branch` 152 | 8. create an pull request 153 | 154 | # meta 155 | 156 | * code: `git clone git://github.com/dscape/spell.git` 157 | * home: 158 | * bugs: 159 | * build: [![build status](https://secure.travis-ci.org/dscape/spell.png)](http://travis-ci.org/dscape/spell) 160 | 161 | `(oO)--',-` in [caos] 162 | 163 | 164 | # license 165 | 166 | copyright 2012 nuno job `(oO)--',--` 167 | 168 | licensed under the apache license, version 2.0 (the "license"); 169 | you may not use this file except in compliance with the license. 170 | you may obtain a copy of the license at 171 | 172 | http://www.apache.org/licenses/LICENSE-2.0 173 | 174 | unless required by applicable law or agreed to in writing, software 175 | distributed under the license is distributed on an "as is" basis, 176 | without warranties or conditions of any kind, either express or implied. 177 | see the license for the specific language governing permissions and 178 | limitations under the license 179 | 180 | [npm]: http://npmjs.org 181 | [issues]: http://github.com/dscape/spell/issues 182 | [caos]: http://caos.di.uminho.pt/ 183 | [norvig]: http://norvig.com/spell-correct.html 184 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { "name" : "spell" 2 | , "description" : "javascript dictionary module for node.js, and the browser" 3 | , "homepage" : "http://github.com/dscape/spell" 4 | , "repository" : "git://github.com/dscape/spell" 5 | , "version" : "0.3.0" 6 | , "author" : "Nuno Job (http://nunojob.com)" 7 | , "contributors" : 8 | [ "Pedro Teixeira (http://metaduck.com)" ] 9 | , "keywords" : 10 | ["spell", "speller", "dictionary", "nlp", "dict", "spell check"] 11 | , "devDependencies" : { "mocha": "0.3.6", "should": "0.3.2" } 12 | , "main" : "./spell.js" 13 | , "scripts" : 14 | { "test" : 15 | "./node_modules/mocha/bin/mocha --globals _test_spell -r should test/spell.js" 16 | , "perf1" : 17 | "node --prof --trace-opt --trace-bailout --trace-deopt test/perf.js 1" 18 | , "perf2" : 19 | "node --prof --trace-opt --trace-bailout --trace-deopt test/perf.js 2" 20 | } 21 | , "engines" : { "node": ">=0.3.6" } 22 | } -------------------------------------------------------------------------------- /spell.js: -------------------------------------------------------------------------------- 1 | /* javascript spell checker based on 2 | * http://norvig.com/spell-correct.html 3 | * 4 | * copyright 2011 nuno job (oO)--',-- 5 | * pedro teixeira 6 | * 7 | * licensed under the apache license, version 2.0 (the "license"); 8 | * you may not use this file except in compliance with the license. 9 | * you may obtain a copy of the license at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * unless required by applicable law or agreed to in writing, software 14 | * distributed under the license is distributed on an "as is" basis, 15 | * without warranties or conditions of any kind, either express or implied. 16 | * see the license for the specific language governing permissions and 17 | * limitations under the license. 18 | */ 19 | (function () { 20 | var root = this 21 | , previous_spell = root.spell 22 | ; 23 | 24 | /* 25 | * dictionary 26 | * 27 | * creates a dictionary given a place to store 28 | * 29 | * @param {dict_store:object:required} 30 | * object that implements two functions 31 | * `get` to retrieve a stored dictionary from disk/memory 32 | * `store` to store a dictionary from disk/memory 33 | * 34 | * @return {object} a dictionary module 35 | */ 36 | var spell = function dictionary(dict_store) { 37 | var dict = 38 | dict_store && typeof dict_store.get === 'function' ? dict_store.get() : {} 39 | , noop = function(){} 40 | , alphabet = "abcdefghijklmnopqrstuvwxyz".split("") 41 | ; 42 | 43 | function spell_store(cb) { 44 | if (dict_store && typeof dict_store.store === 'function') { 45 | dict_store.store(dict, cb); 46 | } 47 | } 48 | 49 | function spell_train(corpus,regex) { 50 | var match, word; 51 | regex = regex || /[a-z]+/g; 52 | corpus = corpus.toLowerCase(); 53 | while ((match = regex.exec(corpus))) { 54 | word = match[0]; 55 | spell_add_word(word, 1); 56 | } 57 | } 58 | 59 | function spell_edits(word, alphabetOverride) { 60 | var edits = [] 61 | , thisAlphabet = alphabetOverride ? alphabetOverride : alphabet 62 | , i 63 | , j 64 | ; 65 | for (i=0; i < word.length; i++) { // deletion 66 | edits.push(word.slice(0, i) + word.slice(i+1)); 67 | } 68 | for (i=0; i < word.length-1; i++) { // transposition 69 | edits.push( word.slice(0, i) + word.slice(i+1, i+2) + 70 | word.slice(i, i+1) + word.slice(i+2)); 71 | } 72 | for (i=0; i < word.length; i++) { // alteration 73 | for(j in thisAlphabet) { 74 | edits.push(word.slice(0, i) + thisAlphabet[j] + word.slice(i+1)); 75 | } 76 | } 77 | for (i=0; i <= word.length; i++) { // insertion 78 | for(j in thisAlphabet) { 79 | edits.push(word.slice(0, i) + thisAlphabet[j] + word.slice(i)); 80 | } 81 | } 82 | return edits; 83 | } 84 | 85 | function is_empty(obj) { 86 | for (var key in obj) { if (obj.hasOwnProperty(key)) return false; } 87 | return true; 88 | } 89 | 90 | function spell_order(candidates, min, max) { 91 | var ordered_candidates = [] 92 | , current 93 | , i 94 | , w 95 | ; 96 | for(i=max; i>=min; i--) { 97 | if(candidates.hasOwnProperty(i)) { 98 | current = candidates[i]; 99 | for (w in current) { 100 | if(current.hasOwnProperty(w)) { 101 | ordered_candidates.push({"word": w, "score": i}); 102 | } 103 | } 104 | } 105 | } 106 | return ordered_candidates; 107 | } 108 | 109 | /* 110 | * reset 111 | * 112 | * resets the dictionary. 113 | * 114 | * e.g. 115 | * spell.reset(); 116 | * 117 | * @return void 118 | */ 119 | function spell_reset() { return spell_load({reset: true}); } 120 | 121 | /* 122 | * load 123 | * 124 | * loads a free form corpus dictionary. 125 | * 126 | * e.g. 127 | * spell.load({'dog': 1, 'cat': 2}); 128 | * spell.load('dog cat cat'); 129 | * 130 | * @param {opts.corpus:string|object:optional} 131 | * corpus string to initialize to 132 | * @param {opts.reset:boolean:optional} 133 | * whether you want to reset the existing dictionary or just append 134 | * to what already exists 135 | * @param {opts.store:boolean:optional} 136 | * decide if you want to use storage 137 | * @param {opts.after_store:function:optional} 138 | * function to call back when store is done 139 | * 140 | * @return void 141 | */ 142 | function spell_load(corpus, opts) { 143 | if ('object' === typeof corpus) { opts = corpus; } 144 | if ('string' === typeof corpus) { 145 | if('object' === typeof opts) { 146 | opts.corpus = corpus; 147 | } else { 148 | opts = {corpus: corpus }; 149 | } 150 | } 151 | if ('string' === typeof opts) { opts = {corpus: opts }; } 152 | opts = 'object' === typeof opts ? opts : {}; 153 | opts.reset = (opts.reset !== false); 154 | opts.store = (opts.store !== false); 155 | opts.after_store = opts.after_store || noop; 156 | opts.corpus = opts.corpus || ''; 157 | if(opts.reset) { dict = {}; } 158 | if('object' === typeof opts.corpus) { 159 | for(var key in opts.corpus) { 160 | spell_add_word(key, {score: opts.corpus[key]}); 161 | } 162 | } else { spell_train(opts.corpus); } 163 | if(opts.store) { spell_store(opts.after_store); } 164 | } 165 | 166 | /* 167 | * add word 168 | * 169 | * loads a word into the dictionary 170 | * 171 | * e.g. 172 | * spell.insert_word('dog', 5); 173 | * 174 | * @param {word:string:required} 175 | * the word you want to add 176 | * @param {opts.count:int:optional} 177 | * the number of times the word appears in a text, defaults to one 178 | * @param {opts.store:boolean:optional} 179 | * decide if you want to use storage 180 | * @param {opts.done:function:optional} 181 | * function to call back when store is done 182 | * 183 | * @return void 184 | */ 185 | function spell_add_word(word, opts) { 186 | if ('string' === typeof opts || 'number' === typeof opts) { 187 | opts = { score: parseInt(opts, 10) }; 188 | } 189 | opts = 'object' === typeof opts ? opts : {}; 190 | opts.score = opts.score || 1; 191 | opts.store = opts.store || true; 192 | opts.done = opts.done || noop; 193 | word = word.toLowerCase(); 194 | dict[word] = 195 | dict.hasOwnProperty(word) ? dict[word] + opts.score : opts.score; 196 | if(opts.store) { spell_store(opts.done); } 197 | } 198 | 199 | /* 200 | * remove word 201 | * 202 | * removes word from the dictionary 203 | * 204 | * e.g. 205 | * spell.remove_word('dog'); 206 | * 207 | * @param {word:string:required} 208 | * the word you want to add 209 | * @param {opts.store:boolean:optional} 210 | * decide if you want to use storage 211 | * @param {opts.done:function:optional} 212 | * function to call back when store is done 213 | * 214 | * @return void 215 | */ 216 | function spell_remove_word(word,opts) { 217 | opts = 'object' === typeof opts ? opts : {}; 218 | opts.store = (opts.store !== false); 219 | opts.done = opts.done || noop; 220 | if (dict.hasOwnProperty(word)) { delete dict[word]; } 221 | if(opts.store) { spell_store(opts.done); } 222 | } 223 | 224 | /* 225 | * suggest 226 | * 227 | * returns spelling sugestions for a given word 228 | * 229 | * e.g. 230 | * spell.suggest('speling'); 231 | * 232 | * @param {word:string:required} 233 | * the word you want to spell check 234 | * @param {alphabet:array:optional} 235 | * if you need to override checking for just words you can set this 236 | * and it will enable you to make suggestions that include punctiation 237 | * etc 238 | * 239 | * @return {array} ordered array containing json objects such as 240 | * [{"word": "spelling", "score": 10}] 241 | */ 242 | function spell_suggest(word, alphabet) { 243 | if (dict.hasOwnProperty(word)) { 244 | return [{"word":word, "score": dict[word]}]; 245 | } 246 | var edits1 = spell_edits(word, alphabet) 247 | , candidates = {} 248 | , min 249 | , max 250 | , current_count 251 | ; 252 | function get_candidates(word) { 253 | if(dict.hasOwnProperty(word)) { 254 | current_count = dict[word]; 255 | if (candidates.hasOwnProperty(current_count)) { 256 | candidates[current_count][word] = true; 257 | } else { 258 | candidates[current_count] = {}; 259 | candidates[current_count][word] = true; 260 | } 261 | max = max ? (max < current_count ? current_count : max) : current_count; 262 | min = min ? (min > current_count ? current_count : min) : current_count; 263 | } 264 | } 265 | edits1.forEach(get_candidates); 266 | if(!is_empty(candidates)) { return spell_order(candidates,min,max); } 267 | edits1.forEach(function(edit1){ 268 | spell_edits(edit1, alphabet).forEach(get_candidates); 269 | }); 270 | if(!is_empty(candidates)) { return spell_order(candidates,min,max); } 271 | return []; // no suggestions 272 | } 273 | 274 | /* 275 | * feeling lucky 276 | * 277 | * returns the first spelling correction for a word 278 | * 279 | * e.g. 280 | * spell.lucky('speling'); 281 | * 282 | * @param {word:string:required} 283 | * the word you want to spell check 284 | * @param {alphabet:array:optional} 285 | * if you need to override checking for just words you can set this 286 | * and it will enable you to make suggestions that include punctiation 287 | * etc 288 | * 289 | * @return {string} the most likely match 290 | */ 291 | function spell_lucky(word, alphabet) { 292 | var suggest = spell_suggest(word, alphabet)[0]; 293 | if(suggest && suggest.hasOwnProperty("word")) { 294 | return suggest.word; 295 | } 296 | return; 297 | } 298 | 299 | /* 300 | * export 301 | * 302 | * exports the dictionary 303 | * 304 | * e.g. 305 | * spell.export(); 306 | * 307 | * @return {json} dictionary 308 | */ 309 | function spell_export(word) { 310 | return {corpus: dict}; 311 | } 312 | 313 | return { reset : spell_reset 314 | , load : spell_load 315 | , "export" : spell_export 316 | , save : spell_export // alias 317 | , add_word : spell_add_word 318 | , addWord : spell_add_word // alias 319 | , remove_word : spell_remove_word 320 | , removeWord : spell_remove_word // alias 321 | , suggest : spell_suggest 322 | , lucky : spell_lucky 323 | }; 324 | }; 325 | 326 | spell._previous = previous_spell; 327 | if (typeof exports !== 'undefined') { // nodejs 328 | spell.platform = { name: "node.js", version: process.version }; 329 | spell.version = JSON.parse( 330 | require('fs').readFileSync(__dirname + "/package.json")).version; 331 | spell.path = __dirname; 332 | if (typeof module !== 'undefined' && module.exports) { 333 | exports = module.exports = spell; 334 | } 335 | exports.spell = spell; 336 | } else { // browser 337 | // browser detection is possible in the future 338 | spell.platform = { name: "browser" }; 339 | spell.version = "0.0.3"; 340 | if (typeof define === 'function' && define.amd) { 341 | define('spell', function() { return spell; }); 342 | } 343 | else { 344 | root.spell = spell; 345 | } 346 | } 347 | })(); 348 | -------------------------------------------------------------------------------- /test/css/mocha.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | font: 20px/1.5 "Helvetica Neue", Helvetica, Aria;, sans-serif; 4 | padding: 60px 50px; 5 | } 6 | 7 | #mocha h1, h2 { 8 | margin: 0; 9 | } 10 | 11 | #mocha h1 { 12 | font-size: 1em; 13 | font-weight: 200; 14 | } 15 | 16 | #mocha .suite .suite h1 { 17 | font-size: .8em; 18 | } 19 | 20 | #mocha h2 { 21 | font-size: 12px; 22 | font-weight: normal; 23 | cursor: pointer; 24 | } 25 | 26 | #mocha .suite { 27 | margin-left: 15px; 28 | } 29 | 30 | #mocha .test { 31 | margin-left: 15px; 32 | } 33 | 34 | #mocha .test.pass::before { 35 | content: '✓'; 36 | font-size: 12px; 37 | display: block; 38 | float: left; 39 | margin-right: 5px; 40 | color: #00c41c; 41 | } 42 | 43 | #mocha .test.pending { 44 | color: #0b97c4; 45 | } 46 | 47 | #mocha .test.pending::before { 48 | content: '◦'; 49 | color: #0b97c4; 50 | } 51 | 52 | #mocha .test.fail { 53 | color: #c00; 54 | } 55 | 56 | #mocha .test.fail pre { 57 | color: black; 58 | } 59 | 60 | #mocha .test.fail::before { 61 | content: '✖'; 62 | font-size: 12px; 63 | display: block; 64 | float: left; 65 | margin-right: 5px; 66 | color: #c00; 67 | } 68 | 69 | #mocha .test pre.error { 70 | color: #c00; 71 | } 72 | 73 | #mocha .test pre { 74 | display: inline-block; 75 | font: 12px/1.5 monaco, monospace; 76 | margin: 5px; 77 | padding: 15px; 78 | border: 1px solid #eee; 79 | border-bottom-color: #ddd; 80 | -webkit-border-radius: 3px; 81 | -webkit-box-shadow: 0 1px 3px #eee; 82 | } 83 | 84 | #error { 85 | color: #c00; 86 | font-size: 1.5 em; 87 | font-weight: 100; 88 | letter-spacing: 1px; 89 | } 90 | 91 | #stats { 92 | position: fixed; 93 | top: 30px; 94 | right: 30px; 95 | font-size: 12px; 96 | margin: 0; 97 | color: #888; 98 | } 99 | 100 | #stats .progress { 101 | margin-bottom: 10px; 102 | } 103 | 104 | #stats em { 105 | color: black; 106 | } 107 | 108 | #stats li { 109 | list-style: none; 110 | } -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | spell.js 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 19 | 20 | 30 | 31 | 32 |
33 | 34 | -------------------------------------------------------------------------------- /test/lib/JSONStream/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | node_modules/* 3 | npm_debug.log 4 | -------------------------------------------------------------------------------- /test/lib/JSONStream/bench.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var fs = require('fs') 4 | , join = require('path').join 5 | , JSONStream = require('./') 6 | , es = require('event-stream') 7 | , request = require('request') 8 | , floby = require('json-streams').createParseStream() 9 | var file = join(__dirname, 'test/fixtures/all_docs_include.json') 10 | , stat = fs.statSync(file) 11 | , parser = JSONStream.parse(['rows',/./]) 12 | 13 | 14 | function checkStream() { 15 | var start = Date.now() 16 | fs.createReadStream(file).pipe(floby) 17 | floby.on('end', function (err, array) { 18 | var time = Date.now() - start 19 | 20 | console.error({type: 'JSONStream.parse', size: stat.size, time: time}) 21 | }) 22 | } 23 | 24 | function checkParse() { 25 | var start = Date.now() 26 | fs.readFile(file, function (err, json) { 27 | var obj = JSON.parse(json) 28 | var time = Date.now() - start 29 | 30 | console.error({type: 'JSON.parse', size: stat.size, time: time}) 31 | checkStream() 32 | }) 33 | } 34 | 35 | checkParse() -------------------------------------------------------------------------------- /test/lib/JSONStream/examples/all_docs.js: -------------------------------------------------------------------------------- 1 | var request = require('request') 2 | , JSONStream = require('JSONStream') 3 | , es = require('event-stream') 4 | 5 | var parser = JSONStream.parse(['rows', /./]) //emit parts that match this path (any element of the rows array) 6 | , req = request({url: 'http://isaacs.couchone.com/registry/_all_docs'}) 7 | , logger = es.mapSync(function (data) { //create a stream that logs to stderr, 8 | console.error(data) 9 | return data 10 | }) 11 | 12 | req.pipe(parser) 13 | parser.pipe(logger) 14 | -------------------------------------------------------------------------------- /test/lib/JSONStream/index.js: -------------------------------------------------------------------------------- 1 | 2 | var Parser = require('jsonparse') 3 | , Stream = require('stream').Stream 4 | 5 | /* 6 | 7 | the value of this.stack that creationix's jsonparse has is weird. 8 | 9 | it makes this code ugly, but his problem is way harder that mine, 10 | so i'll forgive him. 11 | 12 | */ 13 | 14 | exports.parse = function (path) { 15 | 16 | var stream = new Stream() 17 | var parser = new Parser() 18 | var count = 0 19 | if(!path.length) 20 | path = null 21 | parser.onValue = function () { 22 | if(!this.root && this.stack.length == 1){ 23 | stream.root = this.value 24 | } 25 | if(!path || this.stack.length !== path.length) 26 | return 27 | var _path = [] 28 | for( var i = 0; i < (path.length - 1); i++) { 29 | var key = path[i] 30 | var c = this.stack[1 + (+i)] 31 | 32 | if(!c) { 33 | return 34 | } 35 | var m = 36 | ( 'string' === typeof key 37 | ? c.key == key 38 | : key.exec(c.key)) 39 | _path.push(c.key) 40 | 41 | if(!m) 42 | return 43 | 44 | } 45 | var c = this 46 | 47 | var key = path[path.length - 1] 48 | var m = 49 | ( 'string' === typeof key 50 | ? c.key == key 51 | : key.exec(c.key)) 52 | if(!m) 53 | return 54 | _path.push(c.key) 55 | 56 | count ++ 57 | stream.emit('data', this.value[this.key]) 58 | } 59 | 60 | 61 | parser.onError = function (err) { 62 | stream.emit('error', err) 63 | } 64 | stream.readable = true 65 | stream.writable = true 66 | stream.write = function (chunk) { 67 | if('string' === typeof chunk) 68 | chunk = new Buffer(chunk) 69 | parser.write(chunk) 70 | } 71 | stream.end = function (data) { 72 | if(data) 73 | stream.write(data) 74 | if(!count) 75 | stream.emit('data', stream.root) 76 | stream.emit('end') 77 | } 78 | return stream 79 | } 80 | 81 | exports.stringify = function (op, sep, cl) { 82 | if (op === false){ 83 | op = '' 84 | sep = '\n' 85 | cl = '' 86 | } else if (op == null) { 87 | 88 | op = '[\n' 89 | sep = '\n,\n' 90 | cl = '\n]\n' 91 | 92 | } 93 | 94 | //else, what ever you like 95 | 96 | var stream = new Stream () 97 | , first = true 98 | , ended = false 99 | stream.write = function (data) { 100 | var json = JSON.stringify(data) 101 | if(first) { first = false ; stream.emit('data', op + json)} 102 | else stream.emit('data', sep + json) 103 | } 104 | stream.end = function (data) { 105 | if(ended) 106 | return 107 | ended = true 108 | if(data) 109 | stream.write(data) 110 | stream.emit('data', cl) 111 | 112 | stream.emit('end') 113 | } 114 | stream.writable = true 115 | stream.readable = true 116 | 117 | return stream 118 | } -------------------------------------------------------------------------------- /test/lib/JSONStream/package.json: -------------------------------------------------------------------------------- 1 | { "name": "JSONStream" 2 | , "version": "0.1.2" 3 | , "description": "rawStream.pipe(JSONStream.parse()).pipe(streamOfObjects)" 4 | , "homepage": "http://github.com/dominictarr/JSONStream" 5 | , "repository": 6 | { "type": "git" 7 | , "url": "https://github.com/dominictarr/JSONStream.git" } 8 | , "dependencies": { 9 | "jsonparse": "0.0.1" 10 | } 11 | , "devDependencies": {} 12 | , "author": "Dominic Tarr (http://bit.ly/dominictarr)" 13 | , "scripts": { "test": "meta-test test/*.js" } } -------------------------------------------------------------------------------- /test/lib/JSONStream/readme.markdown: -------------------------------------------------------------------------------- 1 | # JSONStream 2 | 3 | streaming JSON.parse and stringify 4 | 5 | ## example 6 | 7 | ```javascript 8 | 9 | var request = require('request') 10 | , JSONStream = require('JSONStream') 11 | , es = require('event-stream') 12 | 13 | var parser = JSONStream.parse(['rows', /./]) 14 | , req = request({url: 'http://isaacs.couchone.com/registry/_all_docs'}) 15 | , logger = es.mapSync(function (data) { 16 | console.error(data) 17 | return data 18 | }) 19 | ``` 20 | 21 | in node 0.4.x 22 | 23 | ``` javascript 24 | 25 | req.pipe(parser) 26 | parser.pipe(logger) 27 | 28 | ``` 29 | 30 | in node v0.5.x 31 | 32 | ``` javascript 33 | req.pipe(parser).pipe(logger) 34 | 35 | ``` 36 | 37 | ## JSONStream.parse(path) 38 | 39 | usally, a json API will return a list of objects. 40 | 41 | `path` should be an array of property names and/or `RedExp`s. 42 | any object that matches the path will be emitted as 'data' (and `pipe`d down stream) 43 | 44 | if `path` is empty or null, or if no matches are made: 45 | JSONStream.parse will only one 'data': the root object. 46 | 47 | (this is useful when there is an error, because the error will probably not match your path) 48 | 49 | ### example 50 | 51 | query a couchdb view: 52 | 53 | ``` bash 54 | curl -sS localhost:5984/tests/_all_docs&include_docs=true 55 | ``` 56 | you will get something like this: 57 | 58 | ``` js 59 | {"total_rows":129,"offset":0,"rows":[ 60 | { "id":"change1_0.6995461115147918" 61 | , "key":"change1_0.6995461115147918" 62 | , "value":{"rev":"1-e240bae28c7bb3667f02760f6398d508"} 63 | , "doc":{ 64 | "_id": "change1_0.6995461115147918" 65 | , "_rev": "1-e240bae28c7bb3667f02760f6398d508","hello":1} 66 | }, 67 | { "id":"change2_0.6995461115147918" 68 | , "key":"change2_0.6995461115147918" 69 | , "value":{"rev":"1-13677d36b98c0c075145bb8975105153"} 70 | , "doc":{ 71 | "_id":"change2_0.6995461115147918" 72 | , "_rev":"1-13677d36b98c0c075145bb8975105153" 73 | , "hello":2 74 | } 75 | }, 76 | ]} 77 | 78 | ``` 79 | 80 | we are probably most interested in the `rows.*.docs` 81 | 82 | create a `Stream` that parses the documents from the feed like this: 83 | 84 | ``` js 85 | JSONStream.parse(['rows', /./, 'doc']) //rows, ANYTHING, doc 86 | ``` 87 | awesome! 88 | 89 | ## JSONStream.stringify(open, sep, close) 90 | 91 | Create a writable stream. 92 | 93 | you may pass in custom `open`, `close`, and `seperator` strings. 94 | But, by default, `JSONStream.stringify()` will create an array, 95 | (with default options `open='[\n', sep='\n,\n', cl='\n]\n'`) 96 | 97 | If you call `JSONStream.stringify(false)` 98 | the elements will only be seperated by a newline. 99 | 100 | If you only write one item this will be valid JSON. 101 | 102 | If you write many items, 103 | you can use a `RegExp` to split it into valid chunks. 104 | 105 | ## numbers 106 | 107 | There are occasional problems parsing and unparsing very precise numbers. 108 | 109 | I have opened an issue here: 110 | 111 | https://github.com/creationix/jsonparse/issues/2 112 | 113 | +1 114 | 115 | ## Acknowlegements 116 | 117 | this module depends on https://github.com/creationix/jsonparse 118 | by Tim Caswell 119 | and also thanks to Florent Jaby for teaching me about parsing with: 120 | https://github.com/Floby/node-json-streams 121 | -------------------------------------------------------------------------------- /test/lib/JSONStream/test/parsejson.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | /* 4 | sometimes jsonparse changes numbers slightly. 5 | */ 6 | 7 | var r = Math.random() 8 | , Parser = require('jsonparse') 9 | , p = new Parser() 10 | , assert = require('assert') 11 | , times = 20 12 | while (times --) { 13 | 14 | assert.equal(JSON.parse(JSON.stringify(r)), r, 'core JSON') 15 | 16 | p.onValue = function (v) { 17 | console.error('parsed', v) 18 | assert.equal(v, r) 19 | } 20 | console.error('correct', r) 21 | p.write (new Buffer(JSON.stringify([r]))) 22 | 23 | 24 | 25 | } -------------------------------------------------------------------------------- /test/lib/JSONStream/test/stringify.js: -------------------------------------------------------------------------------- 1 | 2 | var fs = require ('fs') 3 | , join = require('path').join 4 | , file = join(__dirname, 'fixtures','all_npm.json') 5 | , JSONStream = require('../') 6 | , it = require('it-is').style('colour') 7 | 8 | function randomObj () { 9 | return ( 10 | Math.random () < 0.4 11 | ? {hello: 'eonuhckmqjk', 12 | whatever: 236515, 13 | lies: true, 14 | nothing: [null], 15 | stuff: [Math.random(),Math.random(),Math.random()] 16 | } 17 | : ['AOREC', 'reoubaor', {ouec: 62642}, [[[], {}, 53]]] 18 | ) 19 | } 20 | 21 | var expected = [] 22 | , stringify = JSONStream.stringify() 23 | , es = require('event-stream') 24 | , stringified = '' 25 | , called = 0 26 | , count = 10 27 | , ended = false 28 | 29 | while (count --) 30 | expected.push(randomObj()) 31 | 32 | es.connect( 33 | es.readArray(expected), 34 | stringify, 35 | //JSONStream.parse([/./]), 36 | es.writeArray(function (err, lines) { 37 | 38 | it(JSON.parse(lines.join(''))).deepEqual(expected) 39 | console.error('PASSED') 40 | }) 41 | ) 42 | -------------------------------------------------------------------------------- /test/lib/JSONStream/test/test.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var fs = require ('fs') 4 | , join = require('path').join 5 | , file = join(__dirname, 'fixtures','all_npm.json') 6 | , JSONStream = require('../') 7 | , it = require('it-is') 8 | 9 | var expected = JSON.parse(fs.readFileSync(file)) 10 | , parser = JSONStream.parse(['rows', /\d+/ /*, 'value'*/]) 11 | , called = 0 12 | , ended = false 13 | , parsed = [] 14 | 15 | fs.createReadStream(file).pipe(parser) 16 | 17 | parser.on('data', function (data) { 18 | called ++ 19 | it.has({ 20 | id: it.isString(), 21 | value: {rev: it.isString()}, 22 | key:it.isString() 23 | }) 24 | parsed.push(data) 25 | }) 26 | 27 | parser.on('end', function () { 28 | ended = true 29 | }) 30 | 31 | process.on('exit', function () { 32 | it(called).equal(expected.rows.length) 33 | it(parsed).deepEqual(expected.rows) 34 | console.error('PASSED') 35 | }) -------------------------------------------------------------------------------- /test/lib/JSONStream/test/test2.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var fs = require ('fs') 4 | , join = require('path').join 5 | , file = join(__dirname, '..','package.json') 6 | , JSONStream = require('../') 7 | , it = require('it-is') 8 | 9 | var expected = JSON.parse(fs.readFileSync(file)) 10 | , parser = JSONStream.parse([]) 11 | , called = 0 12 | , ended = false 13 | , parsed = [] 14 | 15 | fs.createReadStream(file).pipe(parser) 16 | 17 | parser.on('data', function (data) { 18 | called ++ 19 | it(data).deepEqual(expected) 20 | }) 21 | 22 | parser.on('end', function () { 23 | ended = true 24 | }) 25 | 26 | process.on('exit', function () { 27 | it(called).equal(1) 28 | console.error('PASSED') 29 | }) -------------------------------------------------------------------------------- /test/lib/JSONStream/test/two-ways.js: -------------------------------------------------------------------------------- 1 | 2 | var fs = require ('fs') 3 | , join = require('path').join 4 | , file = join(__dirname, 'fixtures','all_npm.json') 5 | , JSONStream = require('../') 6 | , it = require('it-is').style('colour') 7 | 8 | function randomObj () { 9 | return ( 10 | Math.random () < 0.4 11 | ? {hello: 'eonuhckmqjk', 12 | whatever: 236515, 13 | lies: true, 14 | nothing: [null], 15 | // stuff: [Math.random(),Math.random(),Math.random()] 16 | } 17 | : ['AOREC', 'reoubaor', {ouec: 62642}, [[[], {}, 53]]] 18 | ) 19 | } 20 | 21 | var expected = [] 22 | , stringify = JSONStream.stringify() 23 | , es = require('event-stream') 24 | , stringified = '' 25 | , called = 0 26 | , count = 10 27 | , ended = false 28 | 29 | while (count --) 30 | expected.push(randomObj()) 31 | 32 | es.connect( 33 | es.readArray(expected), 34 | stringify, 35 | JSONStream.parse([/./]), 36 | es.writeArray(function (err, lines) { 37 | 38 | it(lines).has(expected) 39 | console.error('PASSED') 40 | }) 41 | ) 42 | -------------------------------------------------------------------------------- /test/lib/json2.js: -------------------------------------------------------------------------------- 1 | /* 2 | http://www.JSON.org/json2.js 3 | 2011-10-19 4 | 5 | Public Domain. 6 | 7 | NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. 8 | 9 | See http://www.JSON.org/js.html 10 | 11 | 12 | This code should be minified before deployment. 13 | See http://javascript.crockford.com/jsmin.html 14 | 15 | USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO 16 | NOT CONTROL. 17 | 18 | 19 | This file creates a global JSON object containing two methods: stringify 20 | and parse. 21 | 22 | JSON.stringify(value, replacer, space) 23 | value any JavaScript value, usually an object or array. 24 | 25 | replacer an optional parameter that determines how object 26 | values are stringified for objects. It can be a 27 | function or an array of strings. 28 | 29 | space an optional parameter that specifies the indentation 30 | of nested structures. If it is omitted, the text will 31 | be packed without extra whitespace. If it is a number, 32 | it will specify the number of spaces to indent at each 33 | level. If it is a string (such as '\t' or ' '), 34 | it contains the characters used to indent at each level. 35 | 36 | This method produces a JSON text from a JavaScript value. 37 | 38 | When an object value is found, if the object contains a toJSON 39 | method, its toJSON method will be called and the result will be 40 | stringified. A toJSON method does not serialize: it returns the 41 | value represented by the name/value pair that should be serialized, 42 | or undefined if nothing should be serialized. The toJSON method 43 | will be passed the key associated with the value, and this will be 44 | bound to the value 45 | 46 | For example, this would serialize Dates as ISO strings. 47 | 48 | Date.prototype.toJSON = function (key) { 49 | function f(n) { 50 | // Format integers to have at least two digits. 51 | return n < 10 ? '0' + n : n; 52 | } 53 | 54 | return this.getUTCFullYear() + '-' + 55 | f(this.getUTCMonth() + 1) + '-' + 56 | f(this.getUTCDate()) + 'T' + 57 | f(this.getUTCHours()) + ':' + 58 | f(this.getUTCMinutes()) + ':' + 59 | f(this.getUTCSeconds()) + 'Z'; 60 | }; 61 | 62 | You can provide an optional replacer method. It will be passed the 63 | key and value of each member, with this bound to the containing 64 | object. The value that is returned from your method will be 65 | serialized. If your method returns undefined, then the member will 66 | be excluded from the serialization. 67 | 68 | If the replacer parameter is an array of strings, then it will be 69 | used to select the members to be serialized. It filters the results 70 | such that only members with keys listed in the replacer array are 71 | stringified. 72 | 73 | Values that do not have JSON representations, such as undefined or 74 | functions, will not be serialized. Such values in objects will be 75 | dropped; in arrays they will be replaced with null. You can use 76 | a replacer function to replace those with JSON values. 77 | JSON.stringify(undefined) returns undefined. 78 | 79 | The optional space parameter produces a stringification of the 80 | value that is filled with line breaks and indentation to make it 81 | easier to read. 82 | 83 | If the space parameter is a non-empty string, then that string will 84 | be used for indentation. If the space parameter is a number, then 85 | the indentation will be that many spaces. 86 | 87 | Example: 88 | 89 | text = JSON.stringify(['e', {pluribus: 'unum'}]); 90 | // text is '["e",{"pluribus":"unum"}]' 91 | 92 | 93 | text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); 94 | // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' 95 | 96 | text = JSON.stringify([new Date()], function (key, value) { 97 | return this[key] instanceof Date ? 98 | 'Date(' + this[key] + ')' : value; 99 | }); 100 | // text is '["Date(---current time---)"]' 101 | 102 | 103 | JSON.parse(text, reviver) 104 | This method parses a JSON text to produce an object or array. 105 | It can throw a SyntaxError exception. 106 | 107 | The optional reviver parameter is a function that can filter and 108 | transform the results. It receives each of the keys and values, 109 | and its return value is used instead of the original value. 110 | If it returns what it received, then the structure is not modified. 111 | If it returns undefined then the member is deleted. 112 | 113 | Example: 114 | 115 | // Parse the text. Values that look like ISO date strings will 116 | // be converted to Date objects. 117 | 118 | myData = JSON.parse(text, function (key, value) { 119 | var a; 120 | if (typeof value === 'string') { 121 | a = 122 | /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); 123 | if (a) { 124 | return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], 125 | +a[5], +a[6])); 126 | } 127 | } 128 | return value; 129 | }); 130 | 131 | myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { 132 | var d; 133 | if (typeof value === 'string' && 134 | value.slice(0, 5) === 'Date(' && 135 | value.slice(-1) === ')') { 136 | d = new Date(value.slice(5, -1)); 137 | if (d) { 138 | return d; 139 | } 140 | } 141 | return value; 142 | }); 143 | 144 | 145 | This is a reference implementation. You are free to copy, modify, or 146 | redistribute. 147 | */ 148 | 149 | /*jslint evil: true, regexp: true */ 150 | 151 | /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, 152 | call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, 153 | getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, 154 | lastIndex, length, parse, prototype, push, replace, slice, stringify, 155 | test, toJSON, toString, valueOf 156 | */ 157 | 158 | 159 | // Create a JSON object only if one does not already exist. We create the 160 | // methods in a closure to avoid creating global variables. 161 | 162 | var JSON; 163 | if (!JSON) { 164 | JSON = {}; 165 | } 166 | 167 | (function () { 168 | 'use strict'; 169 | 170 | function f(n) { 171 | // Format integers to have at least two digits. 172 | return n < 10 ? '0' + n : n; 173 | } 174 | 175 | if (typeof Date.prototype.toJSON !== 'function') { 176 | 177 | Date.prototype.toJSON = function (key) { 178 | 179 | return isFinite(this.valueOf()) 180 | ? this.getUTCFullYear() + '-' + 181 | f(this.getUTCMonth() + 1) + '-' + 182 | f(this.getUTCDate()) + 'T' + 183 | f(this.getUTCHours()) + ':' + 184 | f(this.getUTCMinutes()) + ':' + 185 | f(this.getUTCSeconds()) + 'Z' 186 | : null; 187 | }; 188 | 189 | String.prototype.toJSON = 190 | Number.prototype.toJSON = 191 | Boolean.prototype.toJSON = function (key) { 192 | return this.valueOf(); 193 | }; 194 | } 195 | 196 | var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 197 | escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 198 | gap, 199 | indent, 200 | meta = { // table of character substitutions 201 | '\b': '\\b', 202 | '\t': '\\t', 203 | '\n': '\\n', 204 | '\f': '\\f', 205 | '\r': '\\r', 206 | '"' : '\\"', 207 | '\\': '\\\\' 208 | }, 209 | rep; 210 | 211 | 212 | function quote(string) { 213 | 214 | // If the string contains no control characters, no quote characters, and no 215 | // backslash characters, then we can safely slap some quotes around it. 216 | // Otherwise we must also replace the offending characters with safe escape 217 | // sequences. 218 | 219 | escapable.lastIndex = 0; 220 | return escapable.test(string) ? '"' + string.replace(escapable, function (a) { 221 | var c = meta[a]; 222 | return typeof c === 'string' 223 | ? c 224 | : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 225 | }) + '"' : '"' + string + '"'; 226 | } 227 | 228 | 229 | function str(key, holder) { 230 | 231 | // Produce a string from holder[key]. 232 | 233 | var i, // The loop counter. 234 | k, // The member key. 235 | v, // The member value. 236 | length, 237 | mind = gap, 238 | partial, 239 | value = holder[key]; 240 | 241 | // If the value has a toJSON method, call it to obtain a replacement value. 242 | 243 | if (value && typeof value === 'object' && 244 | typeof value.toJSON === 'function') { 245 | value = value.toJSON(key); 246 | } 247 | 248 | // If we were called with a replacer function, then call the replacer to 249 | // obtain a replacement value. 250 | 251 | if (typeof rep === 'function') { 252 | value = rep.call(holder, key, value); 253 | } 254 | 255 | // What happens next depends on the value's type. 256 | 257 | switch (typeof value) { 258 | case 'string': 259 | return quote(value); 260 | 261 | case 'number': 262 | 263 | // JSON numbers must be finite. Encode non-finite numbers as null. 264 | 265 | return isFinite(value) ? String(value) : 'null'; 266 | 267 | case 'boolean': 268 | case 'null': 269 | 270 | // If the value is a boolean or null, convert it to a string. Note: 271 | // typeof null does not produce 'null'. The case is included here in 272 | // the remote chance that this gets fixed someday. 273 | 274 | return String(value); 275 | 276 | // If the type is 'object', we might be dealing with an object or an array or 277 | // null. 278 | 279 | case 'object': 280 | 281 | // Due to a specification blunder in ECMAScript, typeof null is 'object', 282 | // so watch out for that case. 283 | 284 | if (!value) { 285 | return 'null'; 286 | } 287 | 288 | // Make an array to hold the partial results of stringifying this object value. 289 | 290 | gap += indent; 291 | partial = []; 292 | 293 | // Is the value an array? 294 | 295 | if (Object.prototype.toString.apply(value) === '[object Array]') { 296 | 297 | // The value is an array. Stringify every element. Use null as a placeholder 298 | // for non-JSON values. 299 | 300 | length = value.length; 301 | for (i = 0; i < length; i += 1) { 302 | partial[i] = str(i, value) || 'null'; 303 | } 304 | 305 | // Join all of the elements together, separated with commas, and wrap them in 306 | // brackets. 307 | 308 | v = partial.length === 0 309 | ? '[]' 310 | : gap 311 | ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' 312 | : '[' + partial.join(',') + ']'; 313 | gap = mind; 314 | return v; 315 | } 316 | 317 | // If the replacer is an array, use it to select the members to be stringified. 318 | 319 | if (rep && typeof rep === 'object') { 320 | length = rep.length; 321 | for (i = 0; i < length; i += 1) { 322 | if (typeof rep[i] === 'string') { 323 | k = rep[i]; 324 | v = str(k, value); 325 | if (v) { 326 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 327 | } 328 | } 329 | } 330 | } else { 331 | 332 | // Otherwise, iterate through all of the keys in the object. 333 | 334 | for (k in value) { 335 | if (Object.prototype.hasOwnProperty.call(value, k)) { 336 | v = str(k, value); 337 | if (v) { 338 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 339 | } 340 | } 341 | } 342 | } 343 | 344 | // Join all of the member texts together, separated with commas, 345 | // and wrap them in braces. 346 | 347 | v = partial.length === 0 348 | ? '{}' 349 | : gap 350 | ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' 351 | : '{' + partial.join(',') + '}'; 352 | gap = mind; 353 | return v; 354 | } 355 | } 356 | 357 | // If the JSON object does not yet have a stringify method, give it one. 358 | 359 | if (typeof JSON.stringify !== 'function') { 360 | JSON.stringify = function (value, replacer, space) { 361 | 362 | // The stringify method takes a value and an optional replacer, and an optional 363 | // space parameter, and returns a JSON text. The replacer can be a function 364 | // that can replace values, or an array of strings that will select the keys. 365 | // A default replacer method can be provided. Use of the space parameter can 366 | // produce text that is more easily readable. 367 | 368 | var i; 369 | gap = ''; 370 | indent = ''; 371 | 372 | // If the space parameter is a number, make an indent string containing that 373 | // many spaces. 374 | 375 | if (typeof space === 'number') { 376 | for (i = 0; i < space; i += 1) { 377 | indent += ' '; 378 | } 379 | 380 | // If the space parameter is a string, it will be used as the indent string. 381 | 382 | } else if (typeof space === 'string') { 383 | indent = space; 384 | } 385 | 386 | // If there is a replacer, it must be a function or an array. 387 | // Otherwise, throw an error. 388 | 389 | rep = replacer; 390 | if (replacer && typeof replacer !== 'function' && 391 | (typeof replacer !== 'object' || 392 | typeof replacer.length !== 'number')) { 393 | throw new Error('JSON.stringify'); 394 | } 395 | 396 | // Make a fake root object containing our value under the key of ''. 397 | // Return the result of stringifying the value. 398 | 399 | return str('', {'': value}); 400 | }; 401 | } 402 | 403 | 404 | // If the JSON object does not yet have a parse method, give it one. 405 | 406 | if (typeof JSON.parse !== 'function') { 407 | JSON.parse = function (text, reviver) { 408 | 409 | // The parse method takes a text and an optional reviver function, and returns 410 | // a JavaScript value if the text is a valid JSON text. 411 | 412 | var j; 413 | 414 | function walk(holder, key) { 415 | 416 | // The walk method is used to recursively walk the resulting structure so 417 | // that modifications can be made. 418 | 419 | var k, v, value = holder[key]; 420 | if (value && typeof value === 'object') { 421 | for (k in value) { 422 | if (Object.prototype.hasOwnProperty.call(value, k)) { 423 | v = walk(value, k); 424 | if (v !== undefined) { 425 | value[k] = v; 426 | } else { 427 | delete value[k]; 428 | } 429 | } 430 | } 431 | } 432 | return reviver.call(holder, key, value); 433 | } 434 | 435 | 436 | // Parsing happens in four stages. In the first stage, we replace certain 437 | // Unicode characters with escape sequences. JavaScript handles many characters 438 | // incorrectly, either silently deleting them, or treating them as line endings. 439 | 440 | text = String(text); 441 | cx.lastIndex = 0; 442 | if (cx.test(text)) { 443 | text = text.replace(cx, function (a) { 444 | return '\\u' + 445 | ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 446 | }); 447 | } 448 | 449 | // In the second stage, we run the text against regular expressions that look 450 | // for non-JSON patterns. We are especially concerned with '()' and 'new' 451 | // because they can cause invocation, and '=' because it can cause mutation. 452 | // But just to be safe, we want to reject all unexpected forms. 453 | 454 | // We split the second stage into 4 regexp operations in order to work around 455 | // crippling inefficiencies in IE's and Safari's regexp engines. First we 456 | // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we 457 | // replace all simple value tokens with ']' characters. Third, we delete all 458 | // open brackets that follow a colon or comma or that begin the text. Finally, 459 | // we look to see that the remaining characters are only whitespace or ']' or 460 | // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. 461 | 462 | if (/^[\],:{}\s]*$/ 463 | .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') 464 | .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') 465 | .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { 466 | 467 | // In the third stage we use the eval function to compile the text into a 468 | // JavaScript structure. The '{' operator is subject to a syntactic ambiguity 469 | // in JavaScript: it can begin a block or an object literal. We wrap the text 470 | // in parens to eliminate the ambiguity. 471 | 472 | j = eval('(' + text + ')'); 473 | 474 | // In the optional fourth stage, we recursively walk the new structure, passing 475 | // each name/value pair to a reviver function for possible transformation. 476 | 477 | return typeof reviver === 'function' 478 | ? walk({'': j}, '') 479 | : j; 480 | } 481 | 482 | // If the text is not JSON parseable, then a SyntaxError is thrown. 483 | 484 | throw new SyntaxError('JSON.parse'); 485 | }; 486 | } 487 | }()); -------------------------------------------------------------------------------- /test/lib/mocha-0.3.6.js: -------------------------------------------------------------------------------- 1 | ;(function(){ 2 | 3 | 4 | // CommonJS require() 5 | 6 | function require(p){ 7 | var path = require.resolve(p) 8 | , mod = require.modules[path]; 9 | if (!mod) throw new Error('failed to require "' + p + '"'); 10 | if (!mod.exports) { 11 | mod.exports = {}; 12 | mod.call(mod.exports, mod, mod.exports, require.relative(path)); 13 | } 14 | return mod.exports; 15 | } 16 | 17 | require.modules = {}; 18 | 19 | require.resolve = function (path){ 20 | var orig = path 21 | , reg = path + '.js' 22 | , index = path + '/index.js'; 23 | return require.modules[reg] && reg 24 | || require.modules[index] && index 25 | || orig; 26 | }; 27 | 28 | require.register = function (path, fn){ 29 | require.modules[path] = fn; 30 | }; 31 | 32 | require.relative = function (parent) { 33 | return function(p){ 34 | if ('.' != p[0]) return require(p); 35 | 36 | var path = parent.split('/') 37 | , segs = p.split('/'); 38 | path.pop(); 39 | 40 | for (var i = 0; i < segs.length; i++) { 41 | var seg = segs[i]; 42 | if ('..' == seg) path.pop(); 43 | else if ('.' != seg) path.push(seg); 44 | } 45 | 46 | return require(path.join('/')); 47 | }; 48 | }; 49 | 50 | 51 | require.register("browser/debug.js", function(module, exports, require){ 52 | 53 | module.exports = function(type){ 54 | return function(){ 55 | 56 | } 57 | }; 58 | }); // module: browser/debug.js 59 | 60 | require.register("browser/events.js", function(module, exports, require){ 61 | 62 | /** 63 | * Module exports. 64 | */ 65 | 66 | exports.EventEmitter = EventEmitter; 67 | 68 | /** 69 | * Check if `obj` is an array. 70 | */ 71 | 72 | function isArray(obj) { 73 | return '[object Array]' == {}.toString.call(obj); 74 | } 75 | 76 | /** 77 | * Event emitter constructor. 78 | * 79 | * @api public. 80 | */ 81 | 82 | function EventEmitter(){}; 83 | 84 | /** 85 | * Adds a listener. 86 | * 87 | * @api public 88 | */ 89 | 90 | EventEmitter.prototype.on = function (name, fn) { 91 | if (!this.$events) { 92 | this.$events = {}; 93 | } 94 | 95 | if (!this.$events[name]) { 96 | this.$events[name] = fn; 97 | } else if (isArray(this.$events[name])) { 98 | this.$events[name].push(fn); 99 | } else { 100 | this.$events[name] = [this.$events[name], fn]; 101 | } 102 | 103 | return this; 104 | }; 105 | 106 | EventEmitter.prototype.addListener = EventEmitter.prototype.on; 107 | 108 | /** 109 | * Adds a volatile listener. 110 | * 111 | * @api public 112 | */ 113 | 114 | EventEmitter.prototype.once = function (name, fn) { 115 | var self = this; 116 | 117 | function on () { 118 | self.removeListener(name, on); 119 | fn.apply(this, arguments); 120 | }; 121 | 122 | on.listener = fn; 123 | this.on(name, on); 124 | 125 | return this; 126 | }; 127 | 128 | /** 129 | * Removes a listener. 130 | * 131 | * @api public 132 | */ 133 | 134 | EventEmitter.prototype.removeListener = function (name, fn) { 135 | if (this.$events && this.$events[name]) { 136 | var list = this.$events[name]; 137 | 138 | if (isArray(list)) { 139 | var pos = -1; 140 | 141 | for (var i = 0, l = list.length; i < l; i++) { 142 | if (list[i] === fn || (list[i].listener && list[i].listener === fn)) { 143 | pos = i; 144 | break; 145 | } 146 | } 147 | 148 | if (pos < 0) { 149 | return this; 150 | } 151 | 152 | list.splice(pos, 1); 153 | 154 | if (!list.length) { 155 | delete this.$events[name]; 156 | } 157 | } else if (list === fn || (list.listener && list.listener === fn)) { 158 | delete this.$events[name]; 159 | } 160 | } 161 | 162 | return this; 163 | }; 164 | 165 | /** 166 | * Removes all listeners for an event. 167 | * 168 | * @api public 169 | */ 170 | 171 | EventEmitter.prototype.removeAllListeners = function (name) { 172 | if (name === undefined) { 173 | this.$events = {}; 174 | return this; 175 | } 176 | 177 | if (this.$events && this.$events[name]) { 178 | this.$events[name] = null; 179 | } 180 | 181 | return this; 182 | }; 183 | 184 | /** 185 | * Gets all listeners for a certain event. 186 | * 187 | * @api publci 188 | */ 189 | 190 | EventEmitter.prototype.listeners = function (name) { 191 | if (!this.$events) { 192 | this.$events = {}; 193 | } 194 | 195 | if (!this.$events[name]) { 196 | this.$events[name] = []; 197 | } 198 | 199 | if (!isArray(this.$events[name])) { 200 | this.$events[name] = [this.$events[name]]; 201 | } 202 | 203 | return this.$events[name]; 204 | }; 205 | 206 | /** 207 | * Emits an event. 208 | * 209 | * @api public 210 | */ 211 | 212 | EventEmitter.prototype.emit = function (name) { 213 | if (!this.$events) { 214 | return false; 215 | } 216 | 217 | var handler = this.$events[name]; 218 | 219 | if (!handler) { 220 | return false; 221 | } 222 | 223 | var args = [].slice.call(arguments, 1); 224 | 225 | if ('function' == typeof handler) { 226 | handler.apply(this, args); 227 | } else if (isArray(handler)) { 228 | var listeners = handler.slice(); 229 | 230 | for (var i = 0, l = listeners.length; i < l; i++) { 231 | listeners[i].apply(this, args); 232 | } 233 | } else { 234 | return false; 235 | } 236 | 237 | return true; 238 | }; 239 | }); // module: browser/events.js 240 | 241 | require.register("browser/fs.js", function(module, exports, require){ 242 | 243 | }); // module: browser/fs.js 244 | 245 | require.register("browser/progress.js", function(module, exports, require){ 246 | 247 | /** 248 | * Expose `Progress`. 249 | */ 250 | 251 | module.exports = Progress; 252 | 253 | /** 254 | * Initialize a new `Progress` indicator. 255 | */ 256 | 257 | function Progress() { 258 | this.percent = 0; 259 | this.size(0); 260 | this.fontSize(12); 261 | this.font('helvetica, arial, sans-serif'); 262 | } 263 | 264 | /** 265 | * Set progress size to `n`. 266 | * 267 | * @param {Number} n 268 | * @return {Progress} for chaining 269 | * @api public 270 | */ 271 | 272 | Progress.prototype.size = function(n){ 273 | this._size = n; 274 | return this; 275 | }; 276 | 277 | /** 278 | * Set text to `str`. 279 | * 280 | * @param {String} str 281 | * @return {Progress} for chaining 282 | * @api public 283 | */ 284 | 285 | Progress.prototype.text = function(str){ 286 | this._text = str; 287 | return this; 288 | }; 289 | 290 | /** 291 | * Set font size to `n`. 292 | * 293 | * @param {Number} n 294 | * @return {Progress} for chaining 295 | * @api public 296 | */ 297 | 298 | Progress.prototype.fontSize = function(n){ 299 | this._fontSize = n; 300 | return this; 301 | }; 302 | 303 | /** 304 | * Set font `family`. 305 | * 306 | * @param {String} family 307 | * @return {Progress} for chaining 308 | */ 309 | 310 | Progress.prototype.font = function(family){ 311 | this._font = family; 312 | return this; 313 | }; 314 | 315 | /** 316 | * Update percentage to `n`. 317 | * 318 | * @param {Number} n 319 | * @return {Progress} for chaining 320 | */ 321 | 322 | Progress.prototype.update = function(n){ 323 | this.percent = n; 324 | return this; 325 | }; 326 | 327 | /** 328 | * Draw on `ctx`. 329 | * 330 | * @param {CanvasRenderingContext2d} ctx 331 | * @return {Progress} for chaining 332 | */ 333 | 334 | Progress.prototype.draw = function(ctx){ 335 | var percent = Math.min(this.percent, 100) 336 | , size = this._size 337 | , half = size / 2 338 | , x = half 339 | , y = half 340 | , rad = half - 1 341 | , fontSize = this._fontSize; 342 | 343 | ctx.font = fontSize + 'px ' + this._font; 344 | 345 | var angle = Math.PI * 2 * (percent / 100); 346 | ctx.clearRect(0, 0, size, size); 347 | 348 | // outer circle 349 | ctx.strokeStyle = '#9f9f9f'; 350 | ctx.beginPath(); 351 | ctx.arc(x, y, rad, 0, angle, false); 352 | ctx.stroke(); 353 | 354 | // inner circle 355 | ctx.strokeStyle = '#eee'; 356 | ctx.beginPath(); 357 | ctx.arc(x, y, rad - 1, 0, angle, true); 358 | ctx.stroke(); 359 | 360 | // text 361 | var text = this._text || (percent | 0) + '%' 362 | , w = ctx.measureText(text).width; 363 | 364 | ctx.fillText( 365 | text 366 | , x - w / 2 + 1 367 | , y + fontSize / 2 - 1); 368 | 369 | return this; 370 | }; 371 | 372 | }); // module: browser/progress.js 373 | 374 | require.register("browser/tty.js", function(module, exports, require){ 375 | 376 | exports.isatty = function(){ 377 | return true; 378 | }; 379 | 380 | exports.getWindowSize = function(){ 381 | return [window.innerHeight, window.innerWidth]; 382 | }; 383 | }); // module: browser/tty.js 384 | 385 | require.register("hook.js", function(module, exports, require){ 386 | 387 | /** 388 | * Module dependencies. 389 | */ 390 | 391 | var Runnable = require('./runnable'); 392 | 393 | /** 394 | * Expose `Hook`. 395 | */ 396 | 397 | module.exports = Hook; 398 | 399 | /** 400 | * Initialize a new `Hook` with the given `title` and callback `fn`. 401 | * 402 | * @param {String} title 403 | * @param {Function} fn 404 | * @api private 405 | */ 406 | 407 | function Hook(title, fn) { 408 | Runnable.call(this, title, fn); 409 | } 410 | 411 | /** 412 | * Inherit from `Runnable.prototype`. 413 | */ 414 | 415 | Hook.prototype = new Runnable; 416 | Hook.prototype.constructor = Hook; 417 | 418 | 419 | }); // module: hook.js 420 | 421 | require.register("interfaces/bdd.js", function(module, exports, require){ 422 | 423 | /** 424 | * Module dependencies. 425 | */ 426 | 427 | var Suite = require('../suite') 428 | , Test = require('../test'); 429 | 430 | /** 431 | * BDD-style interface: 432 | * 433 | * describe('Array', function(){ 434 | * describe('#indexOf()', function(){ 435 | * it('should return -1 when not present', function(){ 436 | * 437 | * }); 438 | * 439 | * it('should return the index when present', function(){ 440 | * 441 | * }); 442 | * }); 443 | * }); 444 | * 445 | */ 446 | 447 | module.exports = function(suite){ 448 | var suites = [suite]; 449 | 450 | suite.on('pre-require', function(context){ 451 | 452 | /** 453 | * Execute before running tests. 454 | */ 455 | 456 | context.before = function(fn){ 457 | suites[0].beforeAll(fn); 458 | }; 459 | 460 | /** 461 | * Execute after running tests. 462 | */ 463 | 464 | context.after = function(fn){ 465 | suites[0].afterAll(fn); 466 | }; 467 | 468 | /** 469 | * Execute before each test case. 470 | */ 471 | 472 | context.beforeEach = function(fn){ 473 | suites[0].beforeEach(fn); 474 | }; 475 | 476 | /** 477 | * Execute after each test case. 478 | */ 479 | 480 | context.afterEach = function(fn){ 481 | suites[0].afterEach(fn); 482 | }; 483 | 484 | /** 485 | * Describe a "suite" with the given `title` 486 | * and callback `fn` containing nested suites 487 | * and/or tests. 488 | */ 489 | 490 | context.describe = function(title, fn){ 491 | var suite = Suite.create(suites[0], title); 492 | suites.unshift(suite); 493 | fn(); 494 | suites.shift(); 495 | }; 496 | 497 | /** 498 | * Describe a specification or test-case 499 | * with the given `title` and callback `fn` 500 | * acting as a thunk. 501 | */ 502 | 503 | context.it = function(title, fn){ 504 | suites[0].addTest(new Test(title, fn)); 505 | }; 506 | }); 507 | }; 508 | 509 | }); // module: interfaces/bdd.js 510 | 511 | require.register("interfaces/exports.js", function(module, exports, require){ 512 | 513 | /** 514 | * Module dependencies. 515 | */ 516 | 517 | var Suite = require('../suite') 518 | , Test = require('../test'); 519 | 520 | /** 521 | * TDD-style interface: 522 | * 523 | * exports.Array = { 524 | * '#indexOf()': { 525 | * 'should return -1 when the value is not present': function(){ 526 | * 527 | * }, 528 | * 529 | * 'should return the correct index when the value is present': function(){ 530 | * 531 | * } 532 | * } 533 | * }; 534 | * 535 | */ 536 | 537 | module.exports = function(suite){ 538 | var suites = [suite]; 539 | 540 | suite.on('require', visit); 541 | 542 | function visit(obj) { 543 | var suite; 544 | for (var key in obj) { 545 | if ('function' == typeof obj[key]) { 546 | var fn = obj[key]; 547 | switch (key) { 548 | case 'before': 549 | suites[0].beforeAll(fn); 550 | break; 551 | case 'after': 552 | suites[0].afterAll(fn); 553 | break; 554 | case 'beforeEach': 555 | suites[0].beforeEach(fn); 556 | break; 557 | case 'afterEach': 558 | suites[0].afterEach(fn); 559 | break; 560 | default: 561 | suites[0].addTest(new Test(key, fn)); 562 | } 563 | } else { 564 | var suite = Suite.create(suites[0], key); 565 | suites.unshift(suite); 566 | visit(obj[key]); 567 | suites.shift(); 568 | } 569 | } 570 | } 571 | }; 572 | }); // module: interfaces/exports.js 573 | 574 | require.register("interfaces/index.js", function(module, exports, require){ 575 | 576 | exports.bdd = require('./bdd'); 577 | exports.tdd = require('./tdd'); 578 | exports.exports = require('./exports'); 579 | }); // module: interfaces/index.js 580 | 581 | require.register("interfaces/tdd.js", function(module, exports, require){ 582 | 583 | /** 584 | * Module dependencies. 585 | */ 586 | 587 | var Suite = require('../suite') 588 | , Test = require('../test'); 589 | 590 | /** 591 | * TDD-style interface: 592 | * 593 | * suite('Array', function(){ 594 | * suite('#indexOf()', function(){ 595 | * suiteSetup(function(){ 596 | * 597 | * }); 598 | * 599 | * test('should return -1 when not present', function(){ 600 | * 601 | * }); 602 | * 603 | * test('should return the index when present', function(){ 604 | * 605 | * }); 606 | * 607 | * suiteTeardown(function(){ 608 | * 609 | * }); 610 | * }); 611 | * }); 612 | * 613 | */ 614 | 615 | module.exports = function(suite){ 616 | var suites = [suite]; 617 | 618 | suite.on('pre-require', function(context){ 619 | 620 | /** 621 | * Execute before each test case. 622 | */ 623 | 624 | context.setup = function(fn){ 625 | suites[0].beforeEach(fn); 626 | }; 627 | 628 | /** 629 | * Execute after each test case. 630 | */ 631 | 632 | context.teardown = function(fn){ 633 | suites[0].afterEach(fn); 634 | }; 635 | 636 | /** 637 | * Execute before the suite. 638 | */ 639 | 640 | context.suiteSetup = function(fn){ 641 | suites[0].beforeAll(fn); 642 | }; 643 | 644 | /** 645 | * Execute after the suite. 646 | */ 647 | 648 | context.suiteTeardown = function(fn){ 649 | suites[0].afterAll(fn); 650 | }; 651 | 652 | /** 653 | * Describe a "suite" with the given `title` 654 | * and callback `fn` containing nested suites 655 | * and/or tests. 656 | */ 657 | 658 | context.suite = function(title, fn){ 659 | var suite = Suite.create(suites[0], title); 660 | suites.unshift(suite); 661 | fn(); 662 | suites.shift(); 663 | }; 664 | 665 | /** 666 | * Describe a specification or test-case 667 | * with the given `title` and callback `fn` 668 | * acting as a thunk. 669 | */ 670 | 671 | context.test = function(title, fn){ 672 | suites[0].addTest(new Test(title, fn)); 673 | }; 674 | }); 675 | }; 676 | 677 | }); // module: interfaces/tdd.js 678 | 679 | require.register("mocha.js", function(module, exports, require){ 680 | 681 | /*! 682 | * mocha 683 | * Copyright(c) 2011 TJ Holowaychuk 684 | * MIT Licensed 685 | */ 686 | 687 | /** 688 | * Library version. 689 | */ 690 | 691 | exports.version = '0.3.6'; 692 | 693 | exports.interfaces = require('./interfaces'); 694 | exports.reporters = require('./reporters'); 695 | exports.Runnable = require('./runnable'); 696 | exports.Runner = require('./runner'); 697 | exports.Suite = require('./suite'); 698 | exports.Hook = require('./hook'); 699 | exports.Test = require('./test'); 700 | exports.watch = require('./watch'); 701 | }); // module: mocha.js 702 | 703 | require.register("reporters/base.js", function(module, exports, require){ 704 | 705 | /** 706 | * Module dependencies. 707 | */ 708 | 709 | var tty = require('browser/tty'); 710 | 711 | /** 712 | * Check if both stdio streams are associated with a tty. 713 | */ 714 | 715 | var isatty = tty.isatty(1) && tty.isatty(2); 716 | 717 | /** 718 | * Expose `Base`. 719 | */ 720 | 721 | exports = module.exports = Base; 722 | 723 | /** 724 | * Enable coloring by default. 725 | */ 726 | 727 | exports.useColors = isatty; 728 | 729 | /** 730 | * Default color map. 731 | */ 732 | 733 | exports.colors = { 734 | 'pass': 90 735 | , 'fail': 31 736 | , 'bright pass': 92 737 | , 'bright fail': 91 738 | , 'bright yellow': 93 739 | , 'pending': 36 740 | , 'suite': 0 741 | , 'error title': 0 742 | , 'error message': 31 743 | , 'error stack': 90 744 | , 'checkmark': 32 745 | , 'fast': 90 746 | , 'medium': 33 747 | , 'slow': 31 748 | , 'green': 32 749 | , 'light': 90 750 | }; 751 | 752 | /** 753 | * Color `str` with the given `type`, 754 | * allowing colors to be disabled, 755 | * as well as user-defined color 756 | * schemes. 757 | * 758 | * @param {String} type 759 | * @param {String} str 760 | * @return {String} 761 | * @api private 762 | */ 763 | 764 | var color = exports.color = function(type, str) { 765 | if (!exports.useColors) return str; 766 | return '\033[' + exports.colors[type] + 'm' + str + '\033[0m'; 767 | }; 768 | 769 | /** 770 | * Expose term window size, with some 771 | * defaults for when stderr is not a tty. 772 | */ 773 | 774 | exports.window = { 775 | width: isatty 776 | ? process.stdout.getWindowSize 777 | ? process.stdout.getWindowSize(1)[0] 778 | : tty.getWindowSize()[1] 779 | : 75 780 | }; 781 | 782 | /** 783 | * Expose some basic cursor interactions 784 | * that are common among reporters. 785 | */ 786 | 787 | exports.cursor = { 788 | hide: function(){ 789 | process.stdout.write('\033[?25l'); 790 | }, 791 | 792 | show: function(){ 793 | process.stdout.write('\033[?25h'); 794 | } 795 | }; 796 | 797 | /** 798 | * A test is considered slow if it 799 | * exceeds the following value in milliseconds. 800 | */ 801 | 802 | exports.slow = 75; 803 | 804 | /** 805 | * Outut the given `failures` as a list. 806 | * 807 | * @param {Array} failures 808 | * @api public 809 | */ 810 | 811 | exports.list = function(failures){ 812 | console.error(); 813 | failures.forEach(function(test, i){ 814 | // format 815 | var fmt = color('error title', ' %s) %s:\n') 816 | + color('error message', ' %s') 817 | + color('error stack', '\n%s\n'); 818 | 819 | // msg 820 | var err = test.err 821 | , stack = err.stack 822 | , message = err.message || '' 823 | , index = stack.indexOf(message) + message.length 824 | , msg = stack.slice(0, index); 825 | 826 | // indent stack trace without msg 827 | stack = stack.slice(index + 1) 828 | .replace(/^/gm, ' '); 829 | 830 | console.error(fmt, i, test.fullTitle(), msg, stack); 831 | }); 832 | }; 833 | 834 | /** 835 | * Initialize a new `Base` reporter. 836 | * 837 | * All other reporters generally 838 | * inherit from this reporter, providing 839 | * stats such as test duration, number 840 | * of tests passed / failed etc. 841 | * 842 | * @param {Runner} runner 843 | * @api public 844 | */ 845 | 846 | function Base(runner) { 847 | var self = this 848 | , stats = this.stats = { suites: 0, tests: 0, passes: 0, failures: 0 } 849 | , failures = this.failures = []; 850 | 851 | if (!runner) return; 852 | this.runner = runner; 853 | 854 | runner.on('start', function(){ 855 | stats.start = new Date; 856 | }); 857 | 858 | runner.on('suite', function(suite){ 859 | stats.suites = stats.suites || 0; 860 | stats.suites++; 861 | }); 862 | 863 | runner.on('test end', function(test){ 864 | stats.tests = stats.tests || 0; 865 | stats.tests++; 866 | }); 867 | 868 | runner.on('pass', function(test){ 869 | stats.passes = stats.passes || 0; 870 | 871 | var medium = exports.slow / 2; 872 | test.speed = test.duration > exports.slow 873 | ? 'slow' 874 | : test.duration > medium 875 | ? 'medium' 876 | : 'fast'; 877 | 878 | stats.passes++; 879 | }); 880 | 881 | runner.on('fail', function(test, err){ 882 | stats.failures = stats.failures || 0; 883 | stats.failures++; 884 | test.err = err; 885 | failures.push(test); 886 | }); 887 | 888 | runner.on('end', function(){ 889 | stats.end = new Date; 890 | stats.duration = new Date - stats.start; 891 | }); 892 | } 893 | 894 | /** 895 | * Output common epilogue used by many of 896 | * the bundled reporters. 897 | * 898 | * @api public 899 | */ 900 | 901 | Base.prototype.epilogue = function(){ 902 | var stats = this.stats 903 | , fmt; 904 | 905 | console.log(); 906 | 907 | // failure 908 | if (stats.failures) { 909 | fmt = color('bright fail', ' ✖') 910 | + color('fail', ' %d of %d tests failed') 911 | + color('light', ':') 912 | 913 | console.error(fmt, stats.failures, this.runner.total); 914 | Base.list(this.failures); 915 | console.error(); 916 | return; 917 | } 918 | 919 | // pass 920 | fmt = color('bright pass', ' ✔') 921 | + color('green', ' %d tests complete') 922 | + color('light', ' (%dms)'); 923 | 924 | console.log(fmt, stats.tests || 0, stats.duration); 925 | console.log(); 926 | }; 927 | 928 | }); // module: reporters/base.js 929 | 930 | require.register("reporters/doc.js", function(module, exports, require){ 931 | 932 | /** 933 | * Module dependencies. 934 | */ 935 | 936 | var Base = require('./base') 937 | , utils = require('../utils'); 938 | 939 | /** 940 | * Expose `Doc`. 941 | */ 942 | 943 | exports = module.exports = Doc; 944 | 945 | /** 946 | * Initialize a new `Doc` reporter. 947 | * 948 | * @param {Runner} runner 949 | * @api public 950 | */ 951 | 952 | function Doc(runner) { 953 | Base.call(this, runner); 954 | 955 | var self = this 956 | , stats = this.stats 957 | , total = runner.total 958 | , indents = 2; 959 | 960 | function indent() { 961 | return Array(indents).join(' '); 962 | } 963 | 964 | runner.on('suite', function(suite){ 965 | if (suite.root) return; 966 | ++indents; 967 | console.log('%s
', indent()); 968 | ++indents; 969 | console.log('%s

%s

', indent(), suite.title); 970 | console.log('%s
', indent()); 971 | }); 972 | 973 | runner.on('suite end', function(suite){ 974 | if (suite.root) return; 975 | console.log('%s
', indent()); 976 | --indents; 977 | console.log('%s
', indent()); 978 | --indents; 979 | }); 980 | 981 | runner.on('pass', function(test){ 982 | console.log('%s
%s
', indent(), test.title); 983 | var code = utils.escape(clean(test.fn.toString())); 984 | console.log('%s
%s
', indent(), code); 985 | }); 986 | } 987 | 988 | /** 989 | * Strip the function definition from `str`, 990 | * and re-indent for pre whitespace. 991 | */ 992 | 993 | function clean(str) { 994 | str = str 995 | .replace(/^function *\(.*\) *{/, '') 996 | .replace(/\s+\}$/, ''); 997 | 998 | var spaces = str.match(/^\n?( *)/)[1].length 999 | , re = new RegExp('^ {' + spaces + '}', 'gm'); 1000 | 1001 | str = str.replace(re, ''); 1002 | 1003 | return str; 1004 | } 1005 | }); // module: reporters/doc.js 1006 | 1007 | require.register("reporters/dot.js", function(module, exports, require){ 1008 | 1009 | /** 1010 | * Module dependencies. 1011 | */ 1012 | 1013 | var Base = require('./base') 1014 | , color = Base.color; 1015 | 1016 | /** 1017 | * Expose `Dot`. 1018 | */ 1019 | 1020 | exports = module.exports = Dot; 1021 | 1022 | /** 1023 | * Initialize a new `Dot` matrix test reporter. 1024 | * 1025 | * @param {Runner} runner 1026 | * @api public 1027 | */ 1028 | 1029 | function Dot(runner) { 1030 | Base.call(this, runner); 1031 | 1032 | var self = this 1033 | , stats = this.stats 1034 | , width = Base.window.width * .75 | 0 1035 | , n = 0; 1036 | 1037 | runner.on('start', function(){ 1038 | process.stdout.write('\n '); 1039 | }); 1040 | 1041 | runner.on('pending', function(test){ 1042 | process.stdout.write(color('pending', '.')); 1043 | }); 1044 | 1045 | runner.on('pass', function(test){ 1046 | if (++n % width == 0) process.stdout.write('\n '); 1047 | if ('slow' == test.speed) { 1048 | process.stdout.write(color('bright yellow', '.')); 1049 | } else { 1050 | process.stdout.write(color(test.speed, '.')); 1051 | } 1052 | }); 1053 | 1054 | runner.on('fail', function(test, err){ 1055 | if (++n % width == 0) process.stdout.write('\n '); 1056 | process.stdout.write(color('fail', '.')); 1057 | }); 1058 | 1059 | runner.on('end', function(){ 1060 | console.log(); 1061 | self.epilogue(); 1062 | }); 1063 | } 1064 | 1065 | /** 1066 | * Inherit from `Base.prototype`. 1067 | */ 1068 | 1069 | Dot.prototype = new Base; 1070 | Dot.prototype.constructor = Dot; 1071 | 1072 | }); // module: reporters/dot.js 1073 | 1074 | require.register("reporters/html.js", function(module, exports, require){ 1075 | 1076 | /** 1077 | * Module dependencies. 1078 | */ 1079 | 1080 | var Base = require('./base') 1081 | , utils = require('../utils') 1082 | , Progress = require('../browser/progress'); 1083 | 1084 | /** 1085 | * Expose `Doc`. 1086 | */ 1087 | 1088 | exports = module.exports = HTML; 1089 | 1090 | /** 1091 | * Stats template. 1092 | */ 1093 | 1094 | var statsTemplate = '
    ' 1095 | + '
  • ' 1096 | + '
  • passes: 0
  • ' 1097 | + '
  • failures: 0
  • ' 1098 | + '
  • duration: 0s
  • ' 1099 | + '
'; 1100 | 1101 | /** 1102 | * Initialize a new `Doc` reporter. 1103 | * 1104 | * @param {Runner} runner 1105 | * @api public 1106 | */ 1107 | 1108 | function HTML(runner) { 1109 | Base.call(this, runner); 1110 | 1111 | // TODO: clean up 1112 | 1113 | var self = this 1114 | , stats = this.stats 1115 | , total = runner.total 1116 | , root = $('#mocha') 1117 | , stack = [root] 1118 | , stat = $(statsTemplate).appendTo(root) 1119 | , canvas = stat.find('canvas').get(0) 1120 | , ctx = canvas.getContext('2d') 1121 | , progress = new Progress; 1122 | 1123 | if (!root.length) return error('#mocha div missing, add it to your document'); 1124 | progress.size(50); 1125 | 1126 | runner.on('suite', function(suite){ 1127 | if (suite.root) return; 1128 | 1129 | // suite 1130 | var el = $('

' + suite.title + '

'); 1131 | 1132 | // container 1133 | stack[0].append(el); 1134 | stack.unshift($('
')); 1135 | el.append(stack[0]); 1136 | }); 1137 | 1138 | runner.on('suite end', function(suite){ 1139 | if (suite.root) return; 1140 | stack.shift(); 1141 | }); 1142 | 1143 | runner.on('test end', function(test){ 1144 | // TODO: add to stats 1145 | var percent = stats.tests / total * 100 | 0; 1146 | 1147 | // update progress bar 1148 | progress.update(percent).draw(ctx); 1149 | 1150 | // update stats 1151 | var ms = new Date - stats.start; 1152 | stat.find('.passes em').text(stats.passes); 1153 | stat.find('.failures em').text(stats.failures); 1154 | stat.find('.duration em').text((ms / 1000).toFixed(2)); 1155 | 1156 | // test 1157 | if (test.passed) { 1158 | var el = $('

' + test.title + '

') 1159 | } else if (test.pending) { 1160 | var el = $('

' + test.title + '

') 1161 | } else { 1162 | var el = $('

' + test.title + '

'); 1163 | var str = test.err.stack || test.err; 1164 | var err = $('
' + str + '
'); 1165 | el.append(err); 1166 | } 1167 | 1168 | // toggle code 1169 | el.find('h2').toggle(function(){ 1170 | pre.slideDown('fast'); 1171 | }, function(){ 1172 | pre.slideUp('fast'); 1173 | }); 1174 | 1175 | // code 1176 | // TODO: defer 1177 | if (!test.pending) { 1178 | var code = utils.escape(clean(test.fn.toString())); 1179 | var pre = $('
' + code + '
'); 1180 | pre.appendTo(el).hide(); 1181 | } 1182 | stack[0].append(el); 1183 | }); 1184 | } 1185 | 1186 | function error(msg) { 1187 | $('
' + msg + '
').appendTo('body'); 1188 | } 1189 | 1190 | /** 1191 | * Strip the function definition from `str`, 1192 | * and re-indent for pre whitespace. 1193 | */ 1194 | 1195 | function clean(str) { 1196 | str = str 1197 | .replace(/^function *\(.*\) *{/, '') 1198 | .replace(/\s+\}$/, ''); 1199 | 1200 | var spaces = str.match(/^\n?( *)/)[1].length 1201 | , re = new RegExp('^ {' + spaces + '}', 'gm'); 1202 | 1203 | str = str 1204 | .replace(re, '') 1205 | .replace(/^\s+/, ''); 1206 | 1207 | return str; 1208 | } 1209 | }); // module: reporters/html.js 1210 | 1211 | require.register("reporters/index.js", function(module, exports, require){ 1212 | 1213 | exports.Base = require('./base'); 1214 | exports.Dot = require('./dot'); 1215 | exports.Doc = require('./doc'); 1216 | exports.TAP = require('./tap'); 1217 | exports.JSON = require('./json'); 1218 | exports.HTML = require('./html'); 1219 | exports.List = require('./list'); 1220 | exports.Spec = require('./spec'); 1221 | exports.Progress = require('./progress'); 1222 | exports.Landing = require('./landing'); 1223 | exports.JSONStream = require('./json-stream'); 1224 | 1225 | }); // module: reporters/index.js 1226 | 1227 | require.register("reporters/json-stream.js", function(module, exports, require){ 1228 | 1229 | /** 1230 | * Module dependencies. 1231 | */ 1232 | 1233 | var Base = require('./base') 1234 | , color = Base.color; 1235 | 1236 | /** 1237 | * Expose `List`. 1238 | */ 1239 | 1240 | exports = module.exports = List; 1241 | 1242 | /** 1243 | * Initialize a new `List` test reporter. 1244 | * 1245 | * @param {Runner} runner 1246 | * @api public 1247 | */ 1248 | 1249 | function List(runner) { 1250 | Base.call(this, runner); 1251 | 1252 | var self = this 1253 | , stats = this.stats 1254 | , total = runner.total; 1255 | 1256 | runner.on('start', function(){ 1257 | console.log(JSON.stringify(['start', { total: total }])); 1258 | }); 1259 | 1260 | runner.on('pass', function(test){ 1261 | console.log(JSON.stringify(['pass', clean(test)])); 1262 | }); 1263 | 1264 | runner.on('fail', function(test, err){ 1265 | console.log(JSON.stringify(['fail', clean(test)])); 1266 | }); 1267 | 1268 | runner.on('end', function(){ 1269 | process.stdout.write(JSON.stringify(['end', self.stats])); 1270 | }); 1271 | } 1272 | 1273 | /** 1274 | * Return a plain-object representation of `test` 1275 | * free of cyclic properties etc. 1276 | * 1277 | * @param {Object} test 1278 | * @return {Object} 1279 | * @api private 1280 | */ 1281 | 1282 | function clean(test) { 1283 | return { 1284 | title: test.title 1285 | , fullTitle: test.fullTitle() 1286 | , duration: test.duration 1287 | } 1288 | } 1289 | }); // module: reporters/json-stream.js 1290 | 1291 | require.register("reporters/json.js", function(module, exports, require){ 1292 | 1293 | /** 1294 | * Module dependencies. 1295 | */ 1296 | 1297 | var Base = require('./base') 1298 | , cursor = Base.cursor 1299 | , color = Base.color; 1300 | 1301 | /** 1302 | * Expose `JSON`. 1303 | */ 1304 | 1305 | exports = module.exports = JSONReporter; 1306 | 1307 | /** 1308 | * Initialize a new `JSON` reporter. 1309 | * 1310 | * @param {Runner} runner 1311 | * @api public 1312 | */ 1313 | 1314 | function JSONReporter(runner) { 1315 | var self = this; 1316 | Base.call(this, runner); 1317 | 1318 | var tests = [] 1319 | , failures = [] 1320 | , passes = []; 1321 | 1322 | runner.on('test end', function(test){ 1323 | tests.push(test); 1324 | }); 1325 | 1326 | runner.on('pass', function(test){ 1327 | passes.push(test); 1328 | }); 1329 | 1330 | runner.on('fail', function(test){ 1331 | failures.push(test); 1332 | }); 1333 | 1334 | runner.on('end', function(){ 1335 | var obj = { 1336 | stats: self.stats 1337 | , tests: tests.map(clean) 1338 | , failures: failures.map(clean) 1339 | , passes: passes.map(clean) 1340 | }; 1341 | 1342 | process.stdout.write(JSON.stringify(obj)); 1343 | }); 1344 | } 1345 | 1346 | /** 1347 | * Return a plain-object representation of `test` 1348 | * free of cyclic properties etc. 1349 | * 1350 | * @param {Object} test 1351 | * @return {Object} 1352 | * @api private 1353 | */ 1354 | 1355 | function clean(test) { 1356 | return { 1357 | title: test.title 1358 | , fullTitle: test.fullTitle() 1359 | , duration: test.duration 1360 | } 1361 | } 1362 | }); // module: reporters/json.js 1363 | 1364 | require.register("reporters/landing.js", function(module, exports, require){ 1365 | 1366 | /** 1367 | * Module dependencies. 1368 | */ 1369 | 1370 | var Base = require('./base') 1371 | , cursor = Base.cursor 1372 | , color = Base.color; 1373 | 1374 | /** 1375 | * Expose `Landing`. 1376 | */ 1377 | 1378 | exports = module.exports = Landing; 1379 | 1380 | /** 1381 | * Airplane color. 1382 | */ 1383 | 1384 | Base.colors.plane = 0; 1385 | 1386 | /** 1387 | * Airplane crash color. 1388 | */ 1389 | 1390 | Base.colors['plane crash'] = 31; 1391 | 1392 | /** 1393 | * Runway color. 1394 | */ 1395 | 1396 | Base.colors.runway = 90; 1397 | 1398 | /** 1399 | * Initialize a new `Landing` reporter. 1400 | * 1401 | * @param {Runner} runner 1402 | * @api public 1403 | */ 1404 | 1405 | function Landing(runner) { 1406 | Base.call(this, runner); 1407 | 1408 | var self = this 1409 | , stats = this.stats 1410 | , width = Base.window.width * .75 | 0 1411 | , total = runner.total 1412 | , stream = process.stdout 1413 | , plane = color('plane', '✈') 1414 | , crashed = -1 1415 | , n = 0; 1416 | 1417 | function runway() { 1418 | var buf = Array(width).join('-'); 1419 | return ' ' + color('runway', buf); 1420 | } 1421 | 1422 | runner.on('start', function(){ 1423 | stream.write('\n '); 1424 | cursor.hide(); 1425 | }); 1426 | 1427 | runner.on('test end', function(test){ 1428 | // check if the plane crashed 1429 | var col = -1 == crashed 1430 | ? width * ++n / total | 0 1431 | : crashed; 1432 | 1433 | // show the crash 1434 | if (test.failed) { 1435 | plane = color('plane crash', '✈'); 1436 | crashed = col; 1437 | } 1438 | 1439 | // render landing strip 1440 | stream.write('\033[4F\n\n'); 1441 | stream.write(runway()); 1442 | stream.write('\n '); 1443 | stream.write(color('runway', Array(col).join('⋅'))); 1444 | stream.write(plane) 1445 | stream.write(color('runway', Array(width - col).join('⋅') + '\n')); 1446 | stream.write(runway()); 1447 | stream.write('\033[0m'); 1448 | }); 1449 | 1450 | runner.on('end', function(){ 1451 | cursor.show(); 1452 | console.log(); 1453 | self.epilogue(); 1454 | }); 1455 | } 1456 | 1457 | /** 1458 | * Inherit from `Base.prototype`. 1459 | */ 1460 | 1461 | Landing.prototype = new Base; 1462 | Landing.prototype.constructor = Landing; 1463 | 1464 | }); // module: reporters/landing.js 1465 | 1466 | require.register("reporters/list.js", function(module, exports, require){ 1467 | 1468 | /** 1469 | * Module dependencies. 1470 | */ 1471 | 1472 | var Base = require('./base') 1473 | , color = Base.color; 1474 | 1475 | /** 1476 | * Expose `List`. 1477 | */ 1478 | 1479 | exports = module.exports = List; 1480 | 1481 | /** 1482 | * Initialize a new `List` test reporter. 1483 | * 1484 | * @param {Runner} runner 1485 | * @api public 1486 | */ 1487 | 1488 | function List(runner) { 1489 | Base.call(this, runner); 1490 | 1491 | var self = this 1492 | , stats = this.stats 1493 | , n = 0; 1494 | 1495 | runner.on('start', function(){ 1496 | console.log(); 1497 | }); 1498 | 1499 | runner.on('test', function(test){ 1500 | process.stdout.write(color('pass', ' ' + test.fullTitle() + ': ')); 1501 | }); 1502 | 1503 | runner.on('pending', function(test){ 1504 | var fmt = color('checkmark', ' -') 1505 | + color('pending', ' %s'); 1506 | console.log(fmt, test.fullTitle()); 1507 | }); 1508 | 1509 | runner.on('pass', function(test){ 1510 | var fmt = color('checkmark', ' ✓') 1511 | + color('pass', ' %s: ') 1512 | + color(test.speed, '%dms'); 1513 | console.log('\r' + fmt, test.fullTitle(), test.duration); 1514 | }); 1515 | 1516 | runner.on('fail', function(test, err){ 1517 | console.log('\r' + color('fail', ' %d) %s'), n++, test.fullTitle()); 1518 | }); 1519 | 1520 | runner.on('end', self.epilogue.bind(self)); 1521 | } 1522 | 1523 | /** 1524 | * Inherit from `Base.prototype`. 1525 | */ 1526 | 1527 | List.prototype = new Base; 1528 | List.prototype.constructor = List; 1529 | 1530 | }); // module: reporters/list.js 1531 | 1532 | require.register("reporters/progress.js", function(module, exports, require){ 1533 | 1534 | /** 1535 | * Module dependencies. 1536 | */ 1537 | 1538 | var Base = require('./base') 1539 | , cursor = Base.cursor 1540 | , color = Base.color; 1541 | 1542 | /** 1543 | * Expose `Progress`. 1544 | */ 1545 | 1546 | exports = module.exports = Progress; 1547 | 1548 | /** 1549 | * General progress bar color. 1550 | */ 1551 | 1552 | Base.colors.progress = 90; 1553 | 1554 | /** 1555 | * Initialize a new `Progress` bar test reporter. 1556 | * 1557 | * @param {Runner} runner 1558 | * @param {Object} options 1559 | * @api public 1560 | */ 1561 | 1562 | function Progress(runner, options) { 1563 | Base.call(this, runner); 1564 | 1565 | var self = this 1566 | , options = options || {} 1567 | , stats = this.stats 1568 | , width = Base.window.width * .50 | 0 1569 | , total = runner.total 1570 | , complete = 0 1571 | , max = Math.max; 1572 | 1573 | // default chars 1574 | options.open = options.open || '['; 1575 | options.complete = options.complete || '▬'; 1576 | options.incomplete = options.incomplete || '⋅'; 1577 | options.close = options.close || ']'; 1578 | options.verbose = false; 1579 | 1580 | // tests started 1581 | runner.on('start', function(){ 1582 | console.log(); 1583 | cursor.hide(); 1584 | }); 1585 | 1586 | // tests complete 1587 | runner.on('test end', function(){ 1588 | var incomplete = total - complete 1589 | , percent = complete++ / total 1590 | , n = width * percent | 0 1591 | , i = width - n; 1592 | 1593 | process.stdout.write('\r\033[J'); 1594 | process.stdout.write(color('progress', ' ' + options.open)); 1595 | process.stdout.write(Array(n).join(options.complete)); 1596 | process.stdout.write(Array(i).join(options.incomplete)); 1597 | process.stdout.write(color('progress', options.close)); 1598 | if (options.verbose) { 1599 | process.stdout.write(color('progress', ' ' + complete + ' of ' + total)); 1600 | } 1601 | }); 1602 | 1603 | // tests are complete, output some stats 1604 | // and the failures if any 1605 | runner.on('end', function(){ 1606 | cursor.show(); 1607 | console.log(); 1608 | self.epilogue(); 1609 | }); 1610 | } 1611 | 1612 | /** 1613 | * Inherit from `Base.prototype`. 1614 | */ 1615 | 1616 | Progress.prototype = new Base; 1617 | Progress.prototype.constructor = Progress; 1618 | 1619 | }); // module: reporters/progress.js 1620 | 1621 | require.register("reporters/spec.js", function(module, exports, require){ 1622 | 1623 | /** 1624 | * Module dependencies. 1625 | */ 1626 | 1627 | var Base = require('./base') 1628 | , color = Base.color; 1629 | 1630 | /** 1631 | * Expose `Spec`. 1632 | */ 1633 | 1634 | exports = module.exports = Spec; 1635 | 1636 | /** 1637 | * Initialize a new `Spec` test reporter. 1638 | * 1639 | * @param {Runner} runner 1640 | * @api public 1641 | */ 1642 | 1643 | function Spec(runner) { 1644 | Base.call(this, runner); 1645 | 1646 | var self = this 1647 | , stats = this.stats 1648 | , indents = 0 1649 | , n = 0; 1650 | 1651 | function indent() { 1652 | return Array(indents).join(' ') 1653 | } 1654 | 1655 | runner.on('start', function(){ 1656 | console.log(); 1657 | }); 1658 | 1659 | runner.on('suite', function(suite){ 1660 | ++indents; 1661 | console.log(color('suite', '%s%s'), indent(), suite.title); 1662 | }); 1663 | 1664 | runner.on('suite end', function(suite){ 1665 | --indents; 1666 | if (1 == indents) console.log(); 1667 | }); 1668 | 1669 | runner.on('test', function(test){ 1670 | process.stdout.write(indent() + color('pass', ' ◦ ' + test.title + ': ')); 1671 | }); 1672 | 1673 | runner.on('pending', function(test){ 1674 | var fmt = indent() + color('pending', ' - %s'); 1675 | console.log(fmt, test.title); 1676 | }); 1677 | 1678 | runner.on('pass', function(test){ 1679 | if ('fast' == test.speed) { 1680 | var fmt = indent() 1681 | + color('checkmark', ' ✓') 1682 | + color('pass', ' %s '); 1683 | console.log('\r' + fmt, test.title); 1684 | } else { 1685 | var fmt = indent() 1686 | + color('checkmark', ' ✓') 1687 | + color('pass', ' %s ') 1688 | + color(test.speed, '(%dms)'); 1689 | console.log('\r' + fmt, test.title, test.duration); 1690 | } 1691 | }); 1692 | 1693 | runner.on('fail', function(test, err){ 1694 | console.log('\r' + indent() + color('fail', ' %d) %s'), n++, test.title); 1695 | }); 1696 | 1697 | runner.on('end', self.epilogue.bind(self)); 1698 | } 1699 | 1700 | /** 1701 | * Inherit from `Base.prototype`. 1702 | */ 1703 | 1704 | Spec.prototype = new Base; 1705 | Spec.prototype.constructor = Spec; 1706 | 1707 | }); // module: reporters/spec.js 1708 | 1709 | require.register("reporters/tap.js", function(module, exports, require){ 1710 | 1711 | /** 1712 | * Module dependencies. 1713 | */ 1714 | 1715 | var Base = require('./base') 1716 | , cursor = Base.cursor 1717 | , color = Base.color; 1718 | 1719 | /** 1720 | * Expose `TAP`. 1721 | */ 1722 | 1723 | exports = module.exports = TAP; 1724 | 1725 | /** 1726 | * Initialize a new `TAP` reporter. 1727 | * 1728 | * @param {Runner} runner 1729 | * @api public 1730 | */ 1731 | 1732 | function TAP(runner) { 1733 | Base.call(this, runner); 1734 | 1735 | var self = this 1736 | , stats = this.stats 1737 | , total = runner.total 1738 | , n = 1; 1739 | 1740 | runner.on('start', function(){ 1741 | console.log('%d..%d', 1, total); 1742 | }); 1743 | 1744 | runner.on('test end', function(){ 1745 | ++n; 1746 | }); 1747 | 1748 | runner.on('pass', function(test){ 1749 | console.log('ok %d %s', n, test.fullTitle()); 1750 | }); 1751 | 1752 | runner.on('fail', function(test, err){ 1753 | console.log('not ok %d %s', n, test.fullTitle()); 1754 | console.log(err.stack.replace(/^/gm, ' ')); 1755 | }); 1756 | } 1757 | }); // module: reporters/tap.js 1758 | 1759 | require.register("runnable.js", function(module, exports, require){ 1760 | 1761 | /** 1762 | * Module dependencies. 1763 | */ 1764 | 1765 | var EventEmitter = require('browser/events').EventEmitter 1766 | , debug = require('browser/debug')('runnable'); 1767 | 1768 | /** 1769 | * Expose `Runnable`. 1770 | */ 1771 | 1772 | module.exports = Runnable; 1773 | 1774 | /** 1775 | * Initialize a new `Runnable` with the given `title` and callback `fn`. 1776 | * 1777 | * @param {String} title 1778 | * @param {Function} fn 1779 | * @api private 1780 | */ 1781 | 1782 | function Runnable(title, fn) { 1783 | this.title = title; 1784 | this.fn = fn; 1785 | this.async = fn && fn.length; 1786 | this.sync = ! this.async; 1787 | this._timeout = 2000; 1788 | } 1789 | 1790 | /** 1791 | * Inherit from `EventEmitter.prototype`. 1792 | */ 1793 | 1794 | Runnable.prototype = new EventEmitter; 1795 | Runnable.prototype.constructor = Runnable; 1796 | 1797 | 1798 | /** 1799 | * Set & get timeout `ms`. 1800 | * 1801 | * @param {Number} ms 1802 | * @return {Runnable|Number} ms or self 1803 | * @api private 1804 | */ 1805 | 1806 | Runnable.prototype.timeout = function(ms){ 1807 | if (0 == arguments.length) return this._timeout; 1808 | debug('timeout %d', ms); 1809 | this._timeout = ms; 1810 | return this; 1811 | }; 1812 | 1813 | /** 1814 | * Return the full title generated by recursively 1815 | * concatenating the parent's full title. 1816 | * 1817 | * @return {String} 1818 | * @api public 1819 | */ 1820 | 1821 | Runnable.prototype.fullTitle = function(){ 1822 | return this.parent.fullTitle() + ' ' + this.title; 1823 | }; 1824 | 1825 | /** 1826 | * Clear the timeout. 1827 | * 1828 | * @api private 1829 | */ 1830 | 1831 | Runnable.prototype.clearTimeout = function(){ 1832 | clearTimeout(this.timer); 1833 | }; 1834 | 1835 | /** 1836 | * Run the test and invoke `fn(err)`. 1837 | * 1838 | * @param {Function} fn 1839 | * @api private 1840 | */ 1841 | 1842 | Runnable.prototype.run = function(fn){ 1843 | var self = this 1844 | , ms = this.timeout() 1845 | , start = new Date 1846 | , finished 1847 | , emitted; 1848 | 1849 | // timeout 1850 | if (this.async) { 1851 | this.timer = setTimeout(function(){ 1852 | done(new Error('timeout of ' + ms + 'ms exceeded')); 1853 | }, ms); 1854 | } 1855 | 1856 | // called multiple times 1857 | function multiple() { 1858 | if (emitted) return; 1859 | emitted = true; 1860 | self.emit('error', new Error('done() called multiple times')); 1861 | } 1862 | 1863 | // finished 1864 | function done(err) { 1865 | if (finished) return multiple(); 1866 | self.clearTimeout(); 1867 | self.duration = new Date - start; 1868 | finished = true; 1869 | fn(err); 1870 | } 1871 | 1872 | // async 1873 | if (this.async) { 1874 | try { 1875 | this.fn(function(err){ 1876 | if (err instanceof Error) return done(err); 1877 | done(); 1878 | }); 1879 | } catch (err) { 1880 | done(err); 1881 | } 1882 | return; 1883 | } 1884 | 1885 | // sync 1886 | try { 1887 | if (!this.pending) this.fn(); 1888 | this.duration = new Date - start; 1889 | fn(); 1890 | } catch (err) { 1891 | fn(err); 1892 | } 1893 | }; 1894 | 1895 | }); // module: runnable.js 1896 | 1897 | require.register("runner.js", function(module, exports, require){ 1898 | 1899 | /** 1900 | * Module dependencies. 1901 | */ 1902 | 1903 | var EventEmitter = require('browser/events').EventEmitter 1904 | , debug = require('browser/debug')('runner') 1905 | , Test = require('./test') 1906 | , noop = function(){}; 1907 | 1908 | /** 1909 | * Expose `Runner`. 1910 | */ 1911 | 1912 | module.exports = Runner; 1913 | 1914 | /** 1915 | * Initialize a `Runner` for the given `suite`. 1916 | * 1917 | * Events: 1918 | * 1919 | * - `start` execution started 1920 | * - `end` execution complete 1921 | * - `suite` (suite) test suite execution started 1922 | * - `suite end` (suite) all tests (and sub-suites) have finished 1923 | * - `test` (test) test execution started 1924 | * - `test end` (test) test completed 1925 | * - `hook` (hook) hook execution started 1926 | * - `hook end` (hook) hook complete 1927 | * - `pass` (test) test passed 1928 | * - `fail` (test, err) test failed 1929 | * 1930 | * @api public 1931 | */ 1932 | 1933 | function Runner(suite) { 1934 | var self = this; 1935 | this._globals = []; 1936 | this.suite = suite; 1937 | this.total = suite.total(); 1938 | this.failures = 0; 1939 | this.on('test end', function(test){ self.checkGlobals(test); }); 1940 | this.on('hook end', function(hook){ self.checkGlobals(hook); }); 1941 | this.grep(/.*/); 1942 | this.globals(Object.keys(global).concat(['errno'])); 1943 | } 1944 | 1945 | /** 1946 | * Inherit from `EventEmitter.prototype`. 1947 | */ 1948 | 1949 | Runner.prototype = new EventEmitter; 1950 | Runner.prototype.constructor = Runner; 1951 | 1952 | 1953 | /** 1954 | * Run tests with full titles matching `re`. 1955 | * 1956 | * @param {RegExp} re 1957 | * @return {Runner} for chaining 1958 | * @api public 1959 | */ 1960 | 1961 | Runner.prototype.grep = function(re){ 1962 | debug('grep %s', re); 1963 | this._grep = re; 1964 | return this; 1965 | }; 1966 | 1967 | /** 1968 | * Allow the given `arr` of globals. 1969 | * 1970 | * @param {Array} arr 1971 | * @return {Runner} for chaining 1972 | * @api public 1973 | */ 1974 | 1975 | Runner.prototype.globals = function(arr){ 1976 | debug('globals %j', arr); 1977 | arr.forEach(function(arr){ 1978 | this._globals.push(arr); 1979 | }, this); 1980 | return this; 1981 | }; 1982 | 1983 | /** 1984 | * Check for global variable leaks. 1985 | * 1986 | * @api private 1987 | */ 1988 | 1989 | Runner.prototype.checkGlobals = function(test){ 1990 | if (this.ignoreLeaks) return; 1991 | 1992 | var leaks = Object.keys(global).filter(function(key){ 1993 | return !~this._globals.indexOf(key); 1994 | }, this); 1995 | 1996 | this._globals = this._globals.concat(leaks); 1997 | 1998 | if (leaks.length > 1) { 1999 | this.fail(test, new Error('global leaks detected: ' + leaks.join(', ') + '')); 2000 | } else if (leaks.length) { 2001 | this.fail(test, new Error('global leak detected: ' + leaks[0])); 2002 | } 2003 | }; 2004 | 2005 | /** 2006 | * Fail the given `test`. 2007 | * 2008 | * @param {Test} test 2009 | * @param {Error} err 2010 | * @api private 2011 | */ 2012 | 2013 | Runner.prototype.fail = function(test, err){ 2014 | ++this.failures; 2015 | test.failed = true; 2016 | this.emit('fail', test, err); 2017 | }; 2018 | 2019 | /** 2020 | * Fail the given `hook` with `err`. 2021 | * 2022 | * Hook failures (currently) hard-end due 2023 | * to that fact that a failing hook will 2024 | * surely cause subsequent tests to fail, 2025 | * causing jumbled reporting. 2026 | * 2027 | * @param {Hook} hook 2028 | * @param {Error} err 2029 | * @api private 2030 | */ 2031 | 2032 | Runner.prototype.failHook = function(hook, err){ 2033 | ++this.failures; 2034 | this.fail(hook, err); 2035 | this.emit('end'); 2036 | }; 2037 | 2038 | /** 2039 | * Run hook `name` callbacks and then invoke `fn()`. 2040 | * 2041 | * @param {String} name 2042 | * @param {Function} function 2043 | * @api private 2044 | */ 2045 | 2046 | Runner.prototype.hook = function(name, fn){ 2047 | var suite = this.suite 2048 | , hooks = suite['_' + name] 2049 | , ms = suite._timeout 2050 | , self = this 2051 | , timer; 2052 | 2053 | function next(i) { 2054 | var hook = hooks[i]; 2055 | if (!hook) return fn(); 2056 | self.currentRunnable = hook; 2057 | 2058 | self.emit('hook', hook); 2059 | 2060 | hook.on('error', function(err){ 2061 | self.failHook(hook, err); 2062 | }); 2063 | 2064 | hook.run(function(err){ 2065 | hook.removeAllListeners('error'); 2066 | if (err) return self.failHook(hook, err); 2067 | self.emit('hook end', hook); 2068 | next(++i); 2069 | }); 2070 | } 2071 | 2072 | process.nextTick(function(){ 2073 | next(0); 2074 | }); 2075 | }; 2076 | 2077 | /** 2078 | * Run hook `name` for the given array of `suites` 2079 | * in order, and callback `fn(err)`. 2080 | * 2081 | * @param {String} name 2082 | * @param {Array} suites 2083 | * @param {Function} fn 2084 | * @api private 2085 | */ 2086 | 2087 | Runner.prototype.hooks = function(name, suites, fn){ 2088 | var self = this 2089 | , orig = this.suite; 2090 | 2091 | function next(suite) { 2092 | self.suite = suite; 2093 | 2094 | if (!suite) { 2095 | self.suite = orig; 2096 | return fn(); 2097 | } 2098 | 2099 | self.hook(name, function(err){ 2100 | if (err) { 2101 | self.suite = orig; 2102 | return fn(err); 2103 | } 2104 | 2105 | next(suites.pop()); 2106 | }); 2107 | } 2108 | 2109 | next(suites.pop()); 2110 | }; 2111 | 2112 | /** 2113 | * Run hooks from the top level down. 2114 | * 2115 | * @param {String} name 2116 | * @param {Function} fn 2117 | * @api private 2118 | */ 2119 | 2120 | Runner.prototype.hookUp = function(name, fn){ 2121 | var suites = [this.suite].concat(this.parents()).reverse(); 2122 | this.hooks(name, suites, fn); 2123 | }; 2124 | 2125 | /** 2126 | * Run hooks from the bottom up. 2127 | * 2128 | * @param {String} name 2129 | * @param {Function} fn 2130 | * @api private 2131 | */ 2132 | 2133 | Runner.prototype.hookDown = function(name, fn){ 2134 | var suites = [this.suite].concat(this.parents()); 2135 | this.hooks(name, suites, fn); 2136 | }; 2137 | 2138 | /** 2139 | * Return an array of parent Suites from 2140 | * closest to furthest. 2141 | * 2142 | * @return {Array} 2143 | * @api private 2144 | */ 2145 | 2146 | Runner.prototype.parents = function(){ 2147 | var suite = this.suite 2148 | , suites = []; 2149 | while (suite = suite.parent) suites.push(suite); 2150 | return suites; 2151 | }; 2152 | 2153 | /** 2154 | * Run the current test and callback `fn(err)`. 2155 | * 2156 | * @param {Function} fn 2157 | * @api private 2158 | */ 2159 | 2160 | Runner.prototype.runTest = function(fn){ 2161 | var test = this.test 2162 | , self = this; 2163 | 2164 | try { 2165 | test.on('error', function(err){ 2166 | self.fail(test, err); 2167 | }); 2168 | test.run(fn); 2169 | } catch (err) { 2170 | fn(err); 2171 | } 2172 | }; 2173 | 2174 | /** 2175 | * Run tests in the given `suite` and invoke 2176 | * the callback `fn()` when complete. 2177 | * 2178 | * @param {Suite} suite 2179 | * @param {Function} fn 2180 | * @api private 2181 | */ 2182 | 2183 | Runner.prototype.runTests = function(suite, fn){ 2184 | var self = this 2185 | , tests = suite.tests 2186 | , test; 2187 | 2188 | function next(err) { 2189 | // next test 2190 | test = tests.shift(); 2191 | 2192 | // all done 2193 | if (!test) return fn(); 2194 | 2195 | // grep 2196 | if (!self._grep.test(test.fullTitle())) return next(); 2197 | 2198 | // pending 2199 | if (test.pending) { 2200 | self.emit('pending', test); 2201 | self.emit('test end', test); 2202 | return next(); 2203 | } 2204 | 2205 | // execute test and hook(s) 2206 | self.emit('test', self.test = self.currentRunnable = test); 2207 | self.hookDown('beforeEach', function(){ 2208 | self.runTest(function(err){ 2209 | if (err) { 2210 | self.fail(test, err); 2211 | self.emit('test end', test); 2212 | return self.hookUp('afterEach', next); 2213 | } 2214 | 2215 | self.emit('pass', test); 2216 | test.passed = true; 2217 | self.emit('test end', test); 2218 | self.hookUp('afterEach', next); 2219 | }); 2220 | }); 2221 | } 2222 | 2223 | next(); 2224 | }; 2225 | 2226 | /** 2227 | * Run the given `suite` and invoke the 2228 | * callback `fn()` when complete. 2229 | * 2230 | * @param {Suite} suite 2231 | * @param {Function} fn 2232 | * @api private 2233 | */ 2234 | 2235 | Runner.prototype.runSuite = function(suite, fn){ 2236 | var self = this 2237 | , i = 0; 2238 | 2239 | debug('run suite %s', suite.fullTitle()); 2240 | this.emit('suite', this.suite = suite); 2241 | 2242 | function next() { 2243 | var curr = suite.suites[i++]; 2244 | if (!curr) return done(); 2245 | self.runSuite(curr, next); 2246 | } 2247 | 2248 | function done() { 2249 | self.suite = suite; 2250 | self.hook('afterAll', function(){ 2251 | self.emit('suite end', suite); 2252 | fn(); 2253 | }); 2254 | } 2255 | 2256 | this.hook('beforeAll', function(){ 2257 | self.runTests(suite, next); 2258 | }); 2259 | }; 2260 | 2261 | /** 2262 | * Run the root suite and invoke `fn(failures)` 2263 | * on completion. 2264 | * 2265 | * @param {Function} fn 2266 | * @return {Runner} for chaining 2267 | * @api public 2268 | */ 2269 | 2270 | Runner.prototype.run = function(fn){ 2271 | var self = this 2272 | , fn = fn || function(){}; 2273 | 2274 | debug('start'); 2275 | 2276 | // callback 2277 | self.on('end', function(){ 2278 | debug('end'); 2279 | process.removeListener('uncaughtException', uncaught); 2280 | fn(self.failures); 2281 | }); 2282 | 2283 | // run suites 2284 | this.emit('start'); 2285 | this.runSuite(this.suite, function(){ 2286 | debug('finished running'); 2287 | self.emit('end'); 2288 | }); 2289 | 2290 | // uncaught exception 2291 | function uncaught(err){ 2292 | debug('uncaught exception'); 2293 | self.currentRunnable.clearTimeout(); 2294 | self.fail(self.currentRunnable, err); 2295 | self.emit('test end', self.test); 2296 | self.emit('end'); 2297 | } 2298 | 2299 | process.on('uncaughtException', uncaught); 2300 | 2301 | return this; 2302 | }; 2303 | 2304 | }); // module: runner.js 2305 | 2306 | require.register("suite.js", function(module, exports, require){ 2307 | 2308 | /** 2309 | * Module dependencies. 2310 | */ 2311 | 2312 | var EventEmitter = require('browser/events').EventEmitter 2313 | , debug = require('browser/debug')('suite') 2314 | , Hook = require('./hook'); 2315 | 2316 | /** 2317 | * Expose `Suite`. 2318 | */ 2319 | 2320 | exports = module.exports = Suite; 2321 | 2322 | /** 2323 | * Create a new `Suite` with the given `title` 2324 | * and parent `Suite`. When a suite with the 2325 | * same title is already present, that suite 2326 | * is returned to provide nicer reporter 2327 | * and more flexible meta-testing. 2328 | * 2329 | * @param {Suite} parent 2330 | * @param {String} title 2331 | * @return {Suite} 2332 | * @api public 2333 | */ 2334 | 2335 | exports.create = function(parent, title){ 2336 | var suite = new Suite(title); 2337 | suite.parent = parent; 2338 | title = suite.fullTitle(); 2339 | parent.addSuite(suite); 2340 | return suite; 2341 | }; 2342 | 2343 | /** 2344 | * Initialize a new `Suite` with the given `title`. 2345 | * 2346 | * @param {String} title 2347 | * @api private 2348 | */ 2349 | 2350 | function Suite(title) { 2351 | this.title = title; 2352 | this.suites = []; 2353 | this.tests = []; 2354 | this._beforeEach = []; 2355 | this._beforeAll = []; 2356 | this._afterEach = []; 2357 | this._afterAll = []; 2358 | this.root = !title; 2359 | this._timeout = 2000; 2360 | } 2361 | 2362 | /** 2363 | * Inherit from `EventEmitter.prototype`. 2364 | */ 2365 | 2366 | Suite.prototype = new EventEmitter; 2367 | Suite.prototype.constructor = Suite; 2368 | 2369 | 2370 | /** 2371 | * Return a clone of this `Suite`. 2372 | * 2373 | * @return {Suite} 2374 | * @api private 2375 | */ 2376 | 2377 | Suite.prototype.clone = function(){ 2378 | var suite = new Suite(this.title); 2379 | debug('clone'); 2380 | suite.timeout(this.timeout()); 2381 | return suite; 2382 | }; 2383 | 2384 | /** 2385 | * Set timeout `ms` or short-hand such as "2s". 2386 | * 2387 | * @param {Number|String} ms 2388 | * @return {Suite|Number} for chaining 2389 | * @api private 2390 | */ 2391 | 2392 | Suite.prototype.timeout = function(ms){ 2393 | if (0 == arguments.length) return this._timeout; 2394 | if (String(ms).match(/s$/)) ms = parseFloat(ms) * 1000; 2395 | debug('timeout %d', ms); 2396 | this._timeout = parseInt(ms, 10); 2397 | return this; 2398 | }; 2399 | 2400 | /** 2401 | * Run `fn(test[, done])` before running tests. 2402 | * 2403 | * @param {Function} fn 2404 | * @return {Suite} for chaining 2405 | * @api private 2406 | */ 2407 | 2408 | Suite.prototype.beforeAll = function(fn){ 2409 | var hook = new Hook('"before all" hook', fn); 2410 | hook.parent = this; 2411 | hook.timeout(this.timeout()); 2412 | this._beforeAll.push(hook); 2413 | this.emit('beforeAll', hook); 2414 | return this; 2415 | }; 2416 | 2417 | /** 2418 | * Run `fn(test[, done])` after running tests. 2419 | * 2420 | * @param {Function} fn 2421 | * @return {Suite} for chaining 2422 | * @api private 2423 | */ 2424 | 2425 | Suite.prototype.afterAll = function(fn){ 2426 | var hook = new Hook('"after all" hook', fn); 2427 | hook.parent = this; 2428 | hook.timeout(this.timeout()); 2429 | this._afterAll.push(hook); 2430 | this.emit('afterAll', hook); 2431 | return this; 2432 | }; 2433 | 2434 | /** 2435 | * Run `fn(test[, done])` before each test case. 2436 | * 2437 | * @param {Function} fn 2438 | * @return {Suite} for chaining 2439 | * @api private 2440 | */ 2441 | 2442 | Suite.prototype.beforeEach = function(fn){ 2443 | var hook = new Hook('"before each" hook', fn); 2444 | hook.parent = this; 2445 | hook.timeout(this.timeout()); 2446 | this._beforeEach.push(hook); 2447 | this.emit('beforeEach', hook); 2448 | return this; 2449 | }; 2450 | 2451 | /** 2452 | * Run `fn(test[, done])` after each test case. 2453 | * 2454 | * @param {Function} fn 2455 | * @return {Suite} for chaining 2456 | * @api private 2457 | */ 2458 | 2459 | Suite.prototype.afterEach = function(fn){ 2460 | var hook = new Hook('"after each" hook', fn); 2461 | hook.parent = this; 2462 | hook.timeout(this.timeout()); 2463 | this._afterEach.push(hook); 2464 | this.emit('afterEach', hook); 2465 | return this; 2466 | }; 2467 | 2468 | /** 2469 | * Add a test `suite`. 2470 | * 2471 | * @param {Suite} suite 2472 | * @return {Suite} for chaining 2473 | * @api private 2474 | */ 2475 | 2476 | Suite.prototype.addSuite = function(suite){ 2477 | suite.parent = this; 2478 | suite.timeout(this.timeout()); 2479 | this.suites.push(suite); 2480 | this.emit('suite', suite); 2481 | return this; 2482 | }; 2483 | 2484 | /** 2485 | * Add a `test` to this suite. 2486 | * 2487 | * @param {Test} test 2488 | * @return {Suite} for chaining 2489 | * @api private 2490 | */ 2491 | 2492 | Suite.prototype.addTest = function(test){ 2493 | test.parent = this; 2494 | test.timeout(this.timeout()); 2495 | this.tests.push(test); 2496 | this.emit('test', test); 2497 | return this; 2498 | }; 2499 | 2500 | /** 2501 | * Return the full title generated by recursively 2502 | * concatenating the parent's full title. 2503 | * 2504 | * @return {String} 2505 | * @api public 2506 | */ 2507 | 2508 | Suite.prototype.fullTitle = function(){ 2509 | if (this.parent) { 2510 | var full = this.parent.fullTitle(); 2511 | if (full) return full + ' ' + this.title; 2512 | } 2513 | return this.title; 2514 | }; 2515 | 2516 | /** 2517 | * Return the total number of tests. 2518 | * 2519 | * @return {Number} 2520 | * @api public 2521 | */ 2522 | 2523 | Suite.prototype.total = function(){ 2524 | return this.suites.reduce(function(sum, suite){ 2525 | return sum + suite.total(); 2526 | }, 0) + this.tests.length; 2527 | }; 2528 | 2529 | }); // module: suite.js 2530 | 2531 | require.register("test.js", function(module, exports, require){ 2532 | 2533 | /** 2534 | * Module dependencies. 2535 | */ 2536 | 2537 | var Runnable = require('./runnable'); 2538 | 2539 | /** 2540 | * Expose `Test`. 2541 | */ 2542 | 2543 | module.exports = Test; 2544 | 2545 | /** 2546 | * Initialize a new `Test` with the given `title` and callback `fn`. 2547 | * 2548 | * @param {String} title 2549 | * @param {Function} fn 2550 | * @api private 2551 | */ 2552 | 2553 | function Test(title, fn) { 2554 | Runnable.call(this, title, fn); 2555 | this.pending = !fn; 2556 | } 2557 | 2558 | /** 2559 | * Inherit from `Runnable.prototype`. 2560 | */ 2561 | 2562 | Test.prototype = new Runnable; 2563 | Test.prototype.constructor = Test; 2564 | 2565 | 2566 | }); // module: test.js 2567 | 2568 | require.register("utils.js", function(module, exports, require){ 2569 | 2570 | /** 2571 | * Escape special characters in the given string of html. 2572 | * 2573 | * @param {String} html 2574 | * @return {String} 2575 | * @api private 2576 | */ 2577 | 2578 | exports.escape = function(html) { 2579 | return String(html) 2580 | .replace(/&/g, '&') 2581 | .replace(/"/g, '"') 2582 | .replace(//g, '>'); 2584 | }; 2585 | }); // module: utils.js 2586 | 2587 | require.register("watch.js", function(module, exports, require){ 2588 | 2589 | /** 2590 | * Module dependencies. 2591 | */ 2592 | 2593 | var fs = require('browser/fs') 2594 | , debug = require('browser/debug')('watch'); 2595 | 2596 | module.exports = function(paths, fn){ 2597 | var options = { interval: 100 }; 2598 | paths.forEach(function(path){ 2599 | debug('watch %s', path); 2600 | fs.watchFile(path, options, function(curr, prev){ 2601 | if (prev.mtime < curr.mtime) fn(path); 2602 | }); 2603 | }); 2604 | }; 2605 | }); // module: watch.js 2606 | 2607 | /** 2608 | * Node shims. 2609 | * 2610 | * These are meant only to allow 2611 | * mocha.js to run untouched, not 2612 | * to allow running node code in 2613 | * the browser. 2614 | */ 2615 | 2616 | process = {}; 2617 | process.exit = function(status){}; 2618 | process.stdout = {}; 2619 | global = this; 2620 | 2621 | process.nextTick = function(fn){ fn(); }; 2622 | 2623 | process.removeListener = function(ev){ 2624 | if ('uncaughtException' == ev) { 2625 | window.onerror = null; 2626 | } 2627 | }; 2628 | 2629 | process.on = function(ev, fn){ 2630 | if ('uncaughtException' == ev) { 2631 | window.onerror = fn; 2632 | } 2633 | }; 2634 | 2635 | mocha = require('mocha'); 2636 | 2637 | // boot 2638 | ;(function(){ 2639 | var suite = new mocha.Suite; 2640 | var Reporter = mocha.reporters.HTML; 2641 | 2642 | function parse(qs) { 2643 | return qs 2644 | .replace('?', '') 2645 | .split('&') 2646 | .reduce(function(obj, pair){ 2647 | var i = pair.indexOf('=') 2648 | , key = pair.slice(0, i) 2649 | , val = pair.slice(++i); 2650 | 2651 | obj[key] = decodeURIComponent(val); 2652 | return obj; 2653 | }, {}); 2654 | } 2655 | 2656 | mocha.setup = function(ui){ 2657 | ui = mocha.interfaces[ui]; 2658 | if (!ui) throw new Error('invalid mocha interface "' + ui + '"'); 2659 | ui(suite); 2660 | suite.emit('pre-require', global); 2661 | }; 2662 | 2663 | mocha.run = function(){ 2664 | suite.emit('run'); 2665 | var runner = new mocha.Runner(suite); 2666 | var reporter = new Reporter(runner); 2667 | var query = parse(window.location.search || ""); 2668 | if (query.grep) runner.grep(new RegExp(query.grep)); 2669 | return runner.run(); 2670 | }; 2671 | })(); 2672 | })(); -------------------------------------------------------------------------------- /test/lib/request/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | 3 | Version 2.0, January 2004 4 | 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 16 | 17 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. 18 | 19 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. 20 | 21 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. 22 | 23 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). 24 | 25 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. 26 | 27 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." 28 | 29 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 30 | 31 | 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 32 | 33 | 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 34 | 35 | 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: 36 | 37 | You must give any other recipients of the Work or Derivative Works a copy of this License; and 38 | 39 | You must cause any modified files to carry prominent notices stating that You changed the files; and 40 | 41 | You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 42 | 43 | If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 44 | 45 | 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 46 | 47 | 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 48 | 49 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 50 | 51 | 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 52 | 53 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. 54 | 55 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /test/lib/request/README.md: -------------------------------------------------------------------------------- 1 | # Request -- Simplified HTTP request method 2 | 3 | ## Install 4 | 5 |
  6 |   npm install request
  7 | 
8 | 9 | Or from source: 10 | 11 |
 12 |   git clone git://github.com/mikeal/request.git 
 13 |   cd request
 14 |   npm link
 15 | 
16 | 17 | ## Super simple to use 18 | 19 | Request is designed to be the simplest way possible to make http calls. It support HTTPS and follows redirects by default. 20 | 21 | ```javascript 22 | var request = require('request'); 23 | request('http://www.google.com', function (error, response, body) { 24 | if (!error && response.statusCode == 200) { 25 | console.log(body) // Print the google web page. 26 | } 27 | }) 28 | ``` 29 | 30 | ## Streaming 31 | 32 | You can stream any response to a file stream. 33 | 34 | ```javascript 35 | request('http://google.com/doodle.png').pipe(fs.createWriteStream('doodle.png')) 36 | ``` 37 | 38 | You can also stream a file to a PUT or POST request. This method will also check the file extension against a mapping of file extensions to content-types, in this case `application/json`, and use the proper content-type in the PUT request if one is not already provided in the headers. 39 | 40 | ```javascript 41 | fs.readStream('file.json').pipe(request.put('http://mysite.com/obj.json')) 42 | ``` 43 | 44 | Request can also pipe to itself. When doing so the content-type and content-length will be preserved in the PUT headers. 45 | 46 | ```javascript 47 | request.get('http://google.com/img.png').pipe(request.put('http://mysite.com/img.png')) 48 | ``` 49 | 50 | Now let's get fancy. 51 | 52 | ```javascript 53 | http.createServer(function (req, resp) { 54 | if (req.url === '/doodle.png') { 55 | if (req.method === 'PUT') { 56 | req.pipe(request.put('http://mysite.com/doodle.png')) 57 | } else if (req.method === 'GET' || req.method === 'HEAD') { 58 | request.get('http://mysite.com/doodle.png').pipe(resp) 59 | } 60 | } 61 | }) 62 | ``` 63 | 64 | You can also pipe() from a http.ServerRequest instance and to a http.ServerResponse instance. The HTTP method and headers will be sent as well as the entity-body data. Which means that, if you don't really care about security, you can do: 65 | 66 | ```javascript 67 | http.createServer(function (req, resp) { 68 | if (req.url === '/doodle.png') { 69 | var x = request('http://mysite.com/doodle.png') 70 | req.pipe(x) 71 | x.pipe(resp) 72 | } 73 | }) 74 | ``` 75 | 76 | And since pipe() returns the destination stream in node 0.5.x you can do one line proxying :) 77 | 78 | ```javascript 79 | req.pipe(request('http://mysite.com/doodle.png')).pipe(resp) 80 | ``` 81 | 82 | Also, none of this new functionality conflicts with requests previous features, it just expands them. 83 | 84 | ```javascript 85 | var r = request.defaults({'proxy':'http://localproxy.com'}) 86 | 87 | http.createServer(function (req, resp) { 88 | if (req.url === '/doodle.png') { 89 | r.get('http://google.com/doodle.png').pipe(resp) 90 | } 91 | }) 92 | ``` 93 | 94 | You can still use intermediate proxies, the requests will still follow HTTP forwards, etc. 95 | 96 | ## OAuth Signing 97 | 98 | ```javascript 99 | // Twitter OAuth 100 | var qs = require('querystring') 101 | , oauth = 102 | { callback: 'http://mysite.com/callback/' 103 | , consumer_key: CONSUMER_KEY 104 | , consumer_secret: CONSUMER_SECRET 105 | } 106 | , url = 'https://api.twitter.com/oauth/request_token' 107 | ; 108 | request.post({url:url, oauth:oauth}, function (e, r, body) { 109 | // Assume by some stretch of magic you aquired the verifier 110 | var access_token = qs.parse(body) 111 | , oauth = 112 | { consumer_key: CONSUMER_KEY 113 | , consumer_secret: CONSUMER_SECRET 114 | , token: access_token.oauth_token 115 | , verifier: VERIFIER 116 | , token_secret: access_token.oauth_token_secret 117 | } 118 | , url = 'https://api.twitter.com/oauth/access_token' 119 | ; 120 | request.post({url:url, oauth:oauth}, function (e, r, body) { 121 | var perm_token = qs.parse(body) 122 | , oauth = 123 | { consumer_key: CONSUMER_KEY 124 | , consumer_secret: CONSUMER_SECRET 125 | , token: perm_token.oauth_token 126 | , token_secret: perm_token.oauth_token_secret 127 | } 128 | , url = 'https://api.twitter.com/1/users/show.json?' 129 | , params = 130 | { screen_name: perm_token.screen_name 131 | , user_id: perm_token.user_id 132 | } 133 | ; 134 | url += qs.stringify(params) 135 | request.get({url:url, oauth:oauth, json:true}, function (e, r, user) { 136 | console.log(user) 137 | }) 138 | }) 139 | }) 140 | ``` 141 | 142 | 143 | 144 | ### request(options, callback) 145 | 146 | The first argument can be either a url or an options object. The only required option is uri, all others are optional. 147 | 148 | * `uri` || `url` - fully qualified uri or a parsed url object from url.parse() 149 | * `method` - http method, defaults to GET 150 | * `headers` - http headers, defaults to {} 151 | * `body` - entity body for POST and PUT requests. Must be buffer or string. 152 | * `json` - sets `body` but to JSON representation of value and adds `Content-type: application/json` header. 153 | * `multipart` - (experimental) array of objects which contains their own headers and `body` attribute. Sends `multipart/related` request. See example below. 154 | * `followRedirect` - follow HTTP 3xx responses as redirects. defaults to true. 155 | * `maxRedirects` - the maximum number of redirects to follow, defaults to 10. 156 | * `onResponse` - If true the callback will be fired on the "response" event instead of "end". If a function it will be called on "response" and not effect the regular semantics of the main callback on "end". 157 | * `encoding` - Encoding to be used on response.setEncoding when buffering the response data. 158 | * `pool` - A hash object containing the agents for these requests. If omitted this request will use the global pool which is set to node's default maxSockets. 159 | * `pool.maxSockets` - Integer containing the maximum amount of sockets in the pool. 160 | * `timeout` - Integer containing the number of milliseconds to wait for a request to respond before aborting the request 161 | * `proxy` - An HTTP proxy to be used. Support proxy Auth with Basic Auth the same way it's supported with the `url` parameter by embedding the auth info in the uri. 162 | * `oauth` - Options for OAuth HMAC-SHA1 signing, see documentation above. 163 | * `strictSSL` - Set to `true` to require that SSL certificates be valid. Note: to use your own certificate authority, you need to specify an agent that was created with that ca as an option. 164 | * `jar` - Set to `false` if you don't want cookies to be remembered for future use or define your custom cookie jar (see examples section) 165 | 166 | 167 | The callback argument gets 3 arguments. The first is an error when applicable (usually from the http.Client option not the http.ClientRequest object). The second in an http.ClientResponse object. The third is the response body buffer. 168 | 169 | ## Convenience methods 170 | 171 | There are also shorthand methods for different HTTP METHODs and some other conveniences. 172 | 173 | ### request.defaults(options) 174 | 175 | This method returns a wrapper around the normal request API that defaults to whatever options you pass in to it. 176 | 177 | ### request.put 178 | 179 | Same as request() but defaults to `method: "PUT"`. 180 | 181 | ```javascript 182 | request.put(url) 183 | ``` 184 | 185 | ### request.post 186 | 187 | Same as request() but defaults to `method: "POST"`. 188 | 189 | ```javascript 190 | request.post(url) 191 | ``` 192 | 193 | ### request.head 194 | 195 | Same as request() but defaults to `method: "HEAD"`. 196 | 197 | ```javascript 198 | request.head(url) 199 | ``` 200 | 201 | ### request.del 202 | 203 | Same as request() but defaults to `method: "DELETE"`. 204 | 205 | ```javascript 206 | request.del(url) 207 | ``` 208 | 209 | ### request.get 210 | 211 | Alias to normal request method for uniformity. 212 | 213 | ```javascript 214 | request.get(url) 215 | ``` 216 | ### request.cookie 217 | 218 | Function that creates a new cookie. 219 | 220 | ```javascript 221 | request.cookie('cookie_string_here') 222 | ``` 223 | ### request.jar 224 | 225 | Function that creates a new cookie jar. 226 | 227 | ```javascript 228 | request.jar() 229 | ``` 230 | 231 | 232 | ## Examples: 233 | 234 | ```javascript 235 | var request = require('request') 236 | , rand = Math.floor(Math.random()*100000000).toString() 237 | ; 238 | request( 239 | { method: 'PUT' 240 | , uri: 'http://mikeal.iriscouch.com/testjs/' + rand 241 | , multipart: 242 | [ { 'content-type': 'application/json' 243 | , body: JSON.stringify({foo: 'bar', _attachments: {'message.txt': {follows: true, length: 18, 'content_type': 'text/plain' }}}) 244 | } 245 | , { body: 'I am an attachment' } 246 | ] 247 | } 248 | , function (error, response, body) { 249 | if(response.statusCode == 201){ 250 | console.log('document saved as: http://mikeal.iriscouch.com/testjs/'+ rand) 251 | } else { 252 | console.log('error: '+ response.statusCode) 253 | console.log(body) 254 | } 255 | } 256 | ) 257 | ``` 258 | Cookies are enabled by default (so they can be used in subsequent requests). To disable cookies set jar to false (either in defaults or in the options sent). 259 | 260 | ```javascript 261 | var request = request.defaults({jar: false}) 262 | request('http://www.google.com', function () { 263 | request('http://images.google.com') 264 | }) 265 | ``` 266 | 267 | If you to use a custom cookie jar (instead of letting request use its own global cookie jar) you do so by setting the jar default or by specifying it as an option: 268 | 269 | ```javascript 270 | var j = request.jar() 271 | var request = request.defaults({jar:j}) 272 | request('http://www.google.com', function () { 273 | request('http://images.google.com') 274 | }) 275 | ``` 276 | OR 277 | 278 | ```javascript 279 | var j = request.jar() 280 | var cookie = request.cookie('your_cookie_here') 281 | j.add(cookie) 282 | request({url: 'http://www.google.com', jar: j}, function () { 283 | request('http://images.google.com') 284 | }) 285 | ``` 286 | -------------------------------------------------------------------------------- /test/lib/request/main.js: -------------------------------------------------------------------------------- 1 | // Copyright 2010-2011 Mikeal Rogers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | var http = require('http') 16 | , https = false 17 | , tls = false 18 | , url = require('url') 19 | , util = require('util') 20 | , stream = require('stream') 21 | , qs = require('querystring') 22 | , mimetypes = require('./mimetypes') 23 | , oauth = require('./oauth') 24 | , uuid = require('./uuid') 25 | , Cookie = require('./vendor/cookie') 26 | , CookieJar = require('./vendor/cookie/jar') 27 | , cookieJar = new CookieJar 28 | ; 29 | 30 | try { 31 | https = require('https') 32 | } catch (e) {} 33 | 34 | try { 35 | tls = require('tls') 36 | } catch (e) {} 37 | 38 | function toBase64 (str) { 39 | return (new Buffer(str || "", "ascii")).toString("base64") 40 | } 41 | 42 | // Hacky fix for pre-0.4.4 https 43 | if (https && !https.Agent) { 44 | https.Agent = function (options) { 45 | http.Agent.call(this, options) 46 | } 47 | util.inherits(https.Agent, http.Agent) 48 | https.Agent.prototype._getConnection = function(host, port, cb) { 49 | var s = tls.connect(port, host, this.options, function() { 50 | // do other checks here? 51 | if (cb) cb() 52 | }) 53 | return s 54 | } 55 | } 56 | 57 | function isReadStream (rs) { 58 | if (rs.readable && rs.path && rs.mode) { 59 | return true 60 | } 61 | } 62 | 63 | function copy (obj) { 64 | var o = {} 65 | for (var i in obj) o[i] = obj[i] 66 | return o 67 | } 68 | 69 | var isUrl = /^https?:/ 70 | 71 | var globalPool = {} 72 | 73 | function Request (options) { 74 | stream.Stream.call(this) 75 | this.readable = true 76 | this.writable = true 77 | 78 | if (typeof options === 'string') { 79 | options = {uri:options} 80 | } 81 | 82 | for (var i in options) { 83 | this[i] = options[i] 84 | } 85 | if (!this.pool) this.pool = globalPool 86 | this.dests = [] 87 | this.__isRequestRequest = true 88 | } 89 | util.inherits(Request, stream.Stream) 90 | Request.prototype.getAgent = function (host, port) { 91 | if (!this.pool[host+':'+port]) { 92 | this.pool[host+':'+port] = new this.httpModule.Agent({host:host, port:port}) 93 | } 94 | return this.pool[host+':'+port] 95 | } 96 | Request.prototype.request = function () { 97 | var self = this 98 | 99 | // Protect against double callback 100 | if (!self._callback && self.callback) { 101 | self._callback = self.callback 102 | self.callback = function () { 103 | if (self._callbackCalled) return // Print a warning maybe? 104 | self._callback.apply(self, arguments) 105 | self._callbackCalled = true 106 | } 107 | } 108 | 109 | if (self.url) { 110 | // People use this property instead all the time so why not just support it. 111 | self.uri = self.url 112 | delete self.url 113 | } 114 | 115 | if (!self.uri) { 116 | throw new Error("options.uri is a required argument") 117 | } else { 118 | if (typeof self.uri == "string") self.uri = url.parse(self.uri) 119 | } 120 | if (self.proxy) { 121 | if (typeof self.proxy == 'string') self.proxy = url.parse(self.proxy) 122 | } 123 | 124 | self._redirectsFollowed = self._redirectsFollowed || 0 125 | self.maxRedirects = (self.maxRedirects !== undefined) ? self.maxRedirects : 10 126 | self.followRedirect = (self.followRedirect !== undefined) ? self.followRedirect : true 127 | if (self.followRedirect) 128 | self.redirects = self.redirects || [] 129 | 130 | self.headers = self.headers ? copy(self.headers) : {} 131 | 132 | var setHost = false 133 | if (!self.headers.host) { 134 | self.headers.host = self.uri.hostname 135 | if (self.uri.port) { 136 | if ( !(self.uri.port === 80 && self.uri.protocol === 'http:') && 137 | !(self.uri.port === 443 && self.uri.protocol === 'https:') ) 138 | self.headers.host += (':'+self.uri.port) 139 | } 140 | setHost = true 141 | } 142 | 143 | if (self.jar === false) { 144 | // disable cookies 145 | var cookies = false; 146 | self._disableCookies = true; 147 | } else if (self.jar) { 148 | // fetch cookie from the user defined cookie jar 149 | var cookies = self.jar.get({ url: self.uri.href }) 150 | } else { 151 | // fetch cookie from the global cookie jar 152 | var cookies = cookieJar.get({ url: self.uri.href }) 153 | } 154 | if (cookies) { 155 | var cookieString = cookies.map(function (c) { 156 | return c.name + "=" + c.value; 157 | }).join("; "); 158 | 159 | self.headers.Cookie = cookieString; 160 | } 161 | 162 | if (!self.uri.pathname) {self.uri.pathname = '/'} 163 | if (!self.uri.port) { 164 | if (self.uri.protocol == 'http:') {self.uri.port = 80} 165 | else if (self.uri.protocol == 'https:') {self.uri.port = 443} 166 | } 167 | 168 | if (self.proxy) { 169 | self.port = self.proxy.port 170 | self.host = self.proxy.hostname 171 | } else { 172 | self.port = self.uri.port 173 | self.host = self.uri.hostname 174 | } 175 | 176 | if (self.onResponse === true) { 177 | self.onResponse = self.callback 178 | delete self.callback 179 | } 180 | 181 | var clientErrorHandler = function (error) { 182 | if (setHost) delete self.headers.host 183 | if (self.timeout && self.timeoutTimer) clearTimeout(self.timeoutTimer) 184 | self.emit('error', error) 185 | } 186 | if (self.onResponse) self.on('error', function (e) {self.onResponse(e)}) 187 | if (self.callback) self.on('error', function (e) {self.callback(e)}) 188 | 189 | if (self.form) { 190 | self.headers['content-type'] = 'application/x-www-form-urlencoded; charset=utf-8' 191 | self.body = qs.stringify(self.form).toString('utf8') 192 | } 193 | 194 | if (self.oauth) { 195 | var form 196 | if (self.headers['content-type'] && 197 | self.headers['content-type'].slice(0, 'application/x-www-form-urlencoded'.length) === 198 | 'application/x-www-form-urlencoded' 199 | ) { 200 | form = qs.parse(self.body) 201 | } 202 | if (self.uri.query) { 203 | form = qs.parse(self.uri.query) 204 | } 205 | if (!form) form = {} 206 | var oa = {} 207 | for (var i in form) oa[i] = form[i] 208 | for (var i in self.oauth) oa['oauth_'+i] = self.oauth[i] 209 | if (!oa.oauth_version) oa.oauth_version = '1.0' 210 | if (!oa.oauth_timestamp) oa.oauth_timestamp = Math.floor( (new Date()).getTime() / 1000 ).toString() 211 | if (!oa.oauth_nonce) oa.oauth_nonce = uuid().replace(/-/g, '') 212 | 213 | oa.oauth_signature_method = 'HMAC-SHA1' 214 | 215 | var consumer_secret = oa.oauth_consumer_secret 216 | delete oa.oauth_consumer_secret 217 | var token_secret = oa.oauth_token_secret 218 | delete oa.oauth_token_secret 219 | 220 | var baseurl = self.uri.protocol + '//' + self.uri.host + self.uri.pathname 221 | var signature = oauth.hmacsign(self.method, baseurl, oa, consumer_secret, token_secret) 222 | 223 | // oa.oauth_signature = signature 224 | for (var i in form) { 225 | if ( i.slice(0, 'oauth_') in self.oauth) { 226 | // skip 227 | } else { 228 | delete oa['oauth_'+i] 229 | } 230 | } 231 | self.headers.authorization = 232 | 'OAuth '+Object.keys(oa).sort().map(function (i) {return i+'="'+oauth.rfc3986(oa[i])+'"'}).join(',') 233 | self.headers.authorization += ',oauth_signature="'+oauth.rfc3986(signature)+'"' 234 | } 235 | 236 | if (self.uri.auth && !self.headers.authorization) { 237 | self.headers.authorization = "Basic " + toBase64(self.uri.auth.split(':').map(function(item){ return qs.unescape(item)}).join(':')) 238 | } 239 | if (self.proxy && self.proxy.auth && !self.headers['proxy-authorization']) { 240 | self.headers['proxy-authorization'] = "Basic " + toBase64(self.proxy.auth.split(':').map(function(item){ return qs.unescape(item)}).join(':')) 241 | } 242 | 243 | if (self.uri.path) { 244 | self.path = self.uri.path 245 | } else { 246 | self.path = self.uri.pathname + (self.uri.search || "") 247 | } 248 | 249 | if (self.path.length === 0) self.path = '/' 250 | 251 | if (self.proxy) self.path = (self.uri.protocol + '//' + self.uri.host + self.path) 252 | 253 | if (self.json) { 254 | self.headers['content-type'] = 'application/json' 255 | if (typeof self.json === 'boolean') { 256 | if (typeof self.body === 'object') self.body = JSON.stringify(self.body) 257 | } else { 258 | self.body = JSON.stringify(self.json) 259 | } 260 | 261 | } else if (self.multipart) { 262 | self.body = []; 263 | self.headers['content-type'] = 'multipart/related;boundary="frontier"' 264 | if (!self.multipart.forEach) throw new Error('Argument error, options.multipart.') 265 | 266 | self.multipart.forEach(function (part) { 267 | var body = part.body 268 | if(!body) throw Error('Body attribute missing in multipart.') 269 | delete part.body 270 | var preamble = '--frontier\r\n' 271 | Object.keys(part).forEach(function(key){ 272 | preamble += key + ': ' + part[key] + '\r\n' 273 | }) 274 | preamble += '\r\n'; 275 | self.body.push(new Buffer(preamble)); 276 | self.body.push(new Buffer(body)); 277 | self.body.push(new Buffer('\r\n')); 278 | }) 279 | self.body.push(new Buffer('--frontier--')); 280 | } 281 | 282 | if (self.body) { 283 | var length = 0; 284 | if (!Buffer.isBuffer(self.body)) { 285 | if (Array.isArray(self.body)) { 286 | for (var i = 0; i < self.body.length; i++) { 287 | length += self.body[i].length; 288 | } 289 | } else { 290 | self.body = new Buffer(self.body) 291 | length = self.body.length; 292 | } 293 | } else { 294 | length = self.body.length; 295 | } 296 | if (length) { 297 | self.headers['content-length'] = length; 298 | } else { 299 | throw new Error('Argument error, options.body.') 300 | } 301 | } 302 | 303 | self.httpModule = 304 | {"http:":http, "https:":https}[self.proxy ? self.proxy.protocol : self.uri.protocol] 305 | 306 | if (!self.httpModule) throw new Error("Invalid protocol") 307 | 308 | if (self.pool === false) { 309 | self.agent = false 310 | } else { 311 | if (self.maxSockets) { 312 | // Don't use our pooling if node has the refactored client 313 | self.agent = self.httpModule.globalAgent || self.getAgent(self.host, self.port) 314 | self.agent.maxSockets = self.maxSockets 315 | } 316 | if (self.pool.maxSockets) { 317 | // Don't use our pooling if node has the refactored client 318 | self.agent = self.httpModule.globalAgent || self.getAgent(self.host, self.port) 319 | self.agent.maxSockets = self.pool.maxSockets 320 | } 321 | } 322 | 323 | self.start = function () { 324 | self._started = true 325 | self.method = self.method || 'GET' 326 | 327 | self.req = self.httpModule.request(self, function (response) { 328 | self.response = response 329 | response.request = self 330 | 331 | if (self.httpModule === https && 332 | self.strictSSL && 333 | !response.client.authorized) { 334 | var sslErr = response.client.authorizationError 335 | self.emit('error', new Error('SSL Error: '+ sslErr)) 336 | return 337 | } 338 | 339 | if (setHost) delete self.headers.host 340 | if (self.timeout && self.timeoutTimer) clearTimeout(self.timeoutTimer) 341 | 342 | if (response.headers['set-cookie'] && (!self._disableCookies)) { 343 | response.headers['set-cookie'].forEach(function(cookie) { 344 | if (self.jar) { 345 | // custom defined jar 346 | self.jar.add(new Cookie(cookie)); 347 | } 348 | else { 349 | // add to the global cookie jar if user don't define his own 350 | cookieJar.add(new Cookie(cookie)); 351 | } 352 | }); 353 | } 354 | 355 | if (response.statusCode >= 300 && 356 | response.statusCode < 400 && 357 | self.followRedirect && 358 | self.method !== 'PUT' && 359 | self.method !== 'POST' && 360 | response.headers.location) { 361 | if (self._redirectsFollowed >= self.maxRedirects) { 362 | self.emit('error', new Error("Exceeded maxRedirects. Probably stuck in a redirect loop.")) 363 | return 364 | } 365 | self._redirectsFollowed += 1 366 | 367 | if (!isUrl.test(response.headers.location)) { 368 | response.headers.location = url.resolve(self.uri.href, response.headers.location) 369 | } 370 | self.uri = response.headers.location 371 | self.redirects.push( { statusCode : response.statusCode, 372 | redirectUri: response.headers.location }) 373 | delete self.req 374 | delete self.agent 375 | delete self._started 376 | if (self.headers) { 377 | delete self.headers.host 378 | } 379 | request(self, self.callback) 380 | return // Ignore the rest of the response 381 | } else { 382 | self._redirectsFollowed = self._redirectsFollowed || 0 383 | // Be a good stream and emit end when the response is finished. 384 | // Hack to emit end on close because of a core bug that never fires end 385 | response.on('close', function () { 386 | if (!self._ended) self.response.emit('end') 387 | }) 388 | 389 | if (self.encoding) { 390 | if (self.dests.length !== 0) { 391 | console.error("Ingoring encoding parameter as this stream is being piped to another stream which makes the encoding option invalid.") 392 | } else { 393 | response.setEncoding(self.encoding) 394 | } 395 | } 396 | 397 | self.pipeDest = function (dest) { 398 | if (dest.headers) { 399 | dest.headers['content-type'] = response.headers['content-type'] 400 | if (response.headers['content-length']) { 401 | dest.headers['content-length'] = response.headers['content-length'] 402 | } 403 | } 404 | if (dest.setHeader) { 405 | for (var i in response.headers) { 406 | dest.setHeader(i, response.headers[i]) 407 | } 408 | dest.statusCode = response.statusCode 409 | } 410 | if (self.pipefilter) self.pipefilter(response, dest) 411 | } 412 | 413 | self.dests.forEach(function (dest) { 414 | self.pipeDest(dest) 415 | }) 416 | 417 | response.on("data", function (chunk) { 418 | self._destdata = true 419 | self.emit("data", chunk) 420 | }) 421 | response.on("end", function (chunk) { 422 | self._ended = true 423 | self.emit("end", chunk) 424 | }) 425 | response.on("close", function () {self.emit("close")}) 426 | 427 | self.emit('response', response) 428 | 429 | if (self.onResponse) { 430 | self.onResponse(null, response) 431 | } 432 | if (self.callback) { 433 | var buffer = [] 434 | var bodyLen = 0 435 | self.on("data", function (chunk) { 436 | buffer.push(chunk) 437 | bodyLen += chunk.length 438 | }) 439 | self.on("end", function () { 440 | if (buffer.length && Buffer.isBuffer(buffer[0])) { 441 | var body = new Buffer(bodyLen) 442 | var i = 0 443 | buffer.forEach(function (chunk) { 444 | chunk.copy(body, i, 0, chunk.length) 445 | i += chunk.length 446 | }) 447 | response.body = body.toString() 448 | } else if (buffer.length) { 449 | response.body = buffer.join('') 450 | } 451 | 452 | if (self.json) { 453 | try { 454 | response.body = JSON.parse(response.body) 455 | } catch (e) {} 456 | } 457 | 458 | self.callback(null, response, response.body) 459 | }) 460 | } 461 | } 462 | }) 463 | 464 | if (self.timeout) { 465 | self.timeoutTimer = setTimeout(function() { 466 | self.req.abort() 467 | var e = new Error("ETIMEDOUT") 468 | e.code = "ETIMEDOUT" 469 | self.emit("error", e) 470 | }, self.timeout) 471 | } 472 | 473 | self.req.on('error', clientErrorHandler) 474 | } 475 | 476 | self.once('pipe', function (src) { 477 | if (self.ntick) throw new Error("You cannot pipe to this stream after the first nextTick() after creation of the request stream.") 478 | self.src = src 479 | if (isReadStream(src)) { 480 | if (!self.headers['content-type'] && !self.headers['Content-Type']) 481 | self.headers['content-type'] = mimetypes.lookup(src.path.slice(src.path.lastIndexOf('.')+1)) 482 | } else { 483 | if (src.headers) { 484 | for (var i in src.headers) { 485 | if (!self.headers[i]) { 486 | self.headers[i] = src.headers[i] 487 | } 488 | } 489 | } 490 | if (src.method && !self.method) { 491 | self.method = src.method 492 | } 493 | } 494 | 495 | self.on('pipe', function () { 496 | console.error("You have already piped to this stream. Pipeing twice is likely to break the request.") 497 | }) 498 | }) 499 | 500 | process.nextTick(function () { 501 | if (self.body) { 502 | if (Array.isArray(self.body)) { 503 | self.body.forEach(function(part) { 504 | self.write(part); 505 | }); 506 | } else { 507 | self.write(self.body) 508 | } 509 | self.end() 510 | } else if (self.requestBodyStream) { 511 | console.warn("options.requestBodyStream is deprecated, please pass the request object to stream.pipe.") 512 | self.requestBodyStream.pipe(self) 513 | } else if (!self.src) { 514 | self.headers['content-length'] = 0 515 | self.end() 516 | } 517 | self.ntick = true 518 | }) 519 | } 520 | Request.prototype.pipe = function (dest) { 521 | if (this.response) { 522 | if (this._destdata) { 523 | throw new Error("You cannot pipe after data has been emitted from the response.") 524 | } else if (this._ended) { 525 | throw new Error("You cannot pipe after the response has been ended.") 526 | } else { 527 | stream.Stream.prototype.pipe.call(this, dest) 528 | this.pipeDest(dest) 529 | return dest 530 | } 531 | } else { 532 | this.dests.push(dest) 533 | stream.Stream.prototype.pipe.call(this, dest) 534 | return dest 535 | } 536 | } 537 | Request.prototype.write = function () { 538 | if (!this._started) this.start() 539 | if (!this.req) throw new Error("This request has been piped before http.request() was called.") 540 | this.req.write.apply(this.req, arguments) 541 | } 542 | Request.prototype.end = function () { 543 | if (!this._started) this.start() 544 | if (!this.req) throw new Error("This request has been piped before http.request() was called.") 545 | this.req.end.apply(this.req, arguments) 546 | } 547 | Request.prototype.pause = function () { 548 | if (!this.response) throw new Error("This request has been piped before http.request() was called.") 549 | this.response.pause.apply(this.response, arguments) 550 | } 551 | Request.prototype.resume = function () { 552 | if (!this.response) throw new Error("This request has been piped before http.request() was called.") 553 | this.response.resume.apply(this.response, arguments) 554 | } 555 | 556 | function request (options, callback) { 557 | if (typeof options === 'string') options = {uri:options} 558 | if (callback) options.callback = callback 559 | var r = new Request(options) 560 | r.request() 561 | return r 562 | } 563 | 564 | module.exports = request 565 | 566 | request.defaults = function (options) { 567 | var def = function (method) { 568 | var d = function (opts, callback) { 569 | if (typeof opts === 'string') opts = {uri:opts} 570 | for (var i in options) { 571 | if (opts[i] === undefined) opts[i] = options[i] 572 | } 573 | return method(opts, callback) 574 | } 575 | return d 576 | } 577 | var de = def(request) 578 | de.get = def(request.get) 579 | de.post = def(request.post) 580 | de.put = def(request.put) 581 | de.head = def(request.head) 582 | de.del = def(request.del) 583 | de.cookie = def(request.cookie) 584 | de.jar = def(request.jar) 585 | return de 586 | } 587 | 588 | request.get = request 589 | request.post = function (options, callback) { 590 | if (typeof options === 'string') options = {uri:options} 591 | options.method = 'POST' 592 | return request(options, callback) 593 | } 594 | request.put = function (options, callback) { 595 | if (typeof options === 'string') options = {uri:options} 596 | options.method = 'PUT' 597 | return request(options, callback) 598 | } 599 | request.head = function (options, callback) { 600 | if (typeof options === 'string') options = {uri:options} 601 | options.method = 'HEAD' 602 | if (options.body || options.requestBodyStream || options.json || options.multipart) { 603 | throw new Error("HTTP HEAD requests MUST NOT include a request body.") 604 | } 605 | return request(options, callback) 606 | } 607 | request.del = function (options, callback) { 608 | if (typeof options === 'string') options = {uri:options} 609 | options.method = 'DELETE' 610 | return request(options, callback) 611 | } 612 | request.jar = function () { 613 | return new CookieJar 614 | } 615 | request.cookie = function (str) { 616 | if (typeof str !== 'string') throw new Error("The cookie function only accepts STRING as param") 617 | return new Cookie(str) 618 | } 619 | -------------------------------------------------------------------------------- /test/lib/request/mimetypes.js: -------------------------------------------------------------------------------- 1 | // from http://github.com/felixge/node-paperboy 2 | exports.types = { 3 | "aiff":"audio/x-aiff", 4 | "arj":"application/x-arj-compressed", 5 | "asf":"video/x-ms-asf", 6 | "asx":"video/x-ms-asx", 7 | "au":"audio/ulaw", 8 | "avi":"video/x-msvideo", 9 | "bcpio":"application/x-bcpio", 10 | "ccad":"application/clariscad", 11 | "cod":"application/vnd.rim.cod", 12 | "com":"application/x-msdos-program", 13 | "cpio":"application/x-cpio", 14 | "cpt":"application/mac-compactpro", 15 | "csh":"application/x-csh", 16 | "css":"text/css", 17 | "deb":"application/x-debian-package", 18 | "dl":"video/dl", 19 | "doc":"application/msword", 20 | "drw":"application/drafting", 21 | "dvi":"application/x-dvi", 22 | "dwg":"application/acad", 23 | "dxf":"application/dxf", 24 | "dxr":"application/x-director", 25 | "etx":"text/x-setext", 26 | "ez":"application/andrew-inset", 27 | "fli":"video/x-fli", 28 | "flv":"video/x-flv", 29 | "gif":"image/gif", 30 | "gl":"video/gl", 31 | "gtar":"application/x-gtar", 32 | "gz":"application/x-gzip", 33 | "hdf":"application/x-hdf", 34 | "hqx":"application/mac-binhex40", 35 | "html":"text/html", 36 | "ice":"x-conference/x-cooltalk", 37 | "ico":"image/x-icon", 38 | "ief":"image/ief", 39 | "igs":"model/iges", 40 | "ips":"application/x-ipscript", 41 | "ipx":"application/x-ipix", 42 | "jad":"text/vnd.sun.j2me.app-descriptor", 43 | "jar":"application/java-archive", 44 | "jpeg":"image/jpeg", 45 | "jpg":"image/jpeg", 46 | "js":"text/javascript", 47 | "json":"application/json", 48 | "latex":"application/x-latex", 49 | "lsp":"application/x-lisp", 50 | "lzh":"application/octet-stream", 51 | "m":"text/plain", 52 | "m3u":"audio/x-mpegurl", 53 | "man":"application/x-troff-man", 54 | "me":"application/x-troff-me", 55 | "midi":"audio/midi", 56 | "mif":"application/x-mif", 57 | "mime":"www/mime", 58 | "movie":"video/x-sgi-movie", 59 | "mustache":"text/plain", 60 | "mp4":"video/mp4", 61 | "mpg":"video/mpeg", 62 | "mpga":"audio/mpeg", 63 | "ms":"application/x-troff-ms", 64 | "nc":"application/x-netcdf", 65 | "oda":"application/oda", 66 | "ogm":"application/ogg", 67 | "pbm":"image/x-portable-bitmap", 68 | "pdf":"application/pdf", 69 | "pgm":"image/x-portable-graymap", 70 | "pgn":"application/x-chess-pgn", 71 | "pgp":"application/pgp", 72 | "pm":"application/x-perl", 73 | "png":"image/png", 74 | "pnm":"image/x-portable-anymap", 75 | "ppm":"image/x-portable-pixmap", 76 | "ppz":"application/vnd.ms-powerpoint", 77 | "pre":"application/x-freelance", 78 | "prt":"application/pro_eng", 79 | "ps":"application/postscript", 80 | "qt":"video/quicktime", 81 | "ra":"audio/x-realaudio", 82 | "rar":"application/x-rar-compressed", 83 | "ras":"image/x-cmu-raster", 84 | "rgb":"image/x-rgb", 85 | "rm":"audio/x-pn-realaudio", 86 | "rpm":"audio/x-pn-realaudio-plugin", 87 | "rtf":"text/rtf", 88 | "rtx":"text/richtext", 89 | "scm":"application/x-lotusscreencam", 90 | "set":"application/set", 91 | "sgml":"text/sgml", 92 | "sh":"application/x-sh", 93 | "shar":"application/x-shar", 94 | "silo":"model/mesh", 95 | "sit":"application/x-stuffit", 96 | "skt":"application/x-koan", 97 | "smil":"application/smil", 98 | "snd":"audio/basic", 99 | "sol":"application/solids", 100 | "spl":"application/x-futuresplash", 101 | "src":"application/x-wais-source", 102 | "stl":"application/SLA", 103 | "stp":"application/STEP", 104 | "sv4cpio":"application/x-sv4cpio", 105 | "sv4crc":"application/x-sv4crc", 106 | "svg":"image/svg+xml", 107 | "swf":"application/x-shockwave-flash", 108 | "tar":"application/x-tar", 109 | "tcl":"application/x-tcl", 110 | "tex":"application/x-tex", 111 | "texinfo":"application/x-texinfo", 112 | "tgz":"application/x-tar-gz", 113 | "tiff":"image/tiff", 114 | "tr":"application/x-troff", 115 | "tsi":"audio/TSP-audio", 116 | "tsp":"application/dsptype", 117 | "tsv":"text/tab-separated-values", 118 | "unv":"application/i-deas", 119 | "ustar":"application/x-ustar", 120 | "vcd":"application/x-cdlink", 121 | "vda":"application/vda", 122 | "vivo":"video/vnd.vivo", 123 | "vrm":"x-world/x-vrml", 124 | "wav":"audio/x-wav", 125 | "wax":"audio/x-ms-wax", 126 | "wma":"audio/x-ms-wma", 127 | "wmv":"video/x-ms-wmv", 128 | "wmx":"video/x-ms-wmx", 129 | "wrl":"model/vrml", 130 | "wvx":"video/x-ms-wvx", 131 | "xbm":"image/x-xbitmap", 132 | "xlw":"application/vnd.ms-excel", 133 | "xml":"text/xml", 134 | "xpm":"image/x-xpixmap", 135 | "xwd":"image/x-xwindowdump", 136 | "xyz":"chemical/x-pdb", 137 | "zip":"application/zip", 138 | }; 139 | 140 | exports.lookup = function(ext, defaultType) { 141 | defaultType = defaultType || 'application/octet-stream'; 142 | 143 | return (ext in exports.types) 144 | ? exports.types[ext] 145 | : defaultType; 146 | }; -------------------------------------------------------------------------------- /test/lib/request/oauth.js: -------------------------------------------------------------------------------- 1 | var crypto = require('crypto') 2 | , qs = require('querystring') 3 | ; 4 | 5 | function sha1 (key, body) { 6 | return crypto.createHmac('sha1', key).update(body).digest('base64') 7 | } 8 | 9 | function rfc3986 (str) { 10 | return encodeURIComponent(str) 11 | .replace('!','%21') 12 | .replace('*','%2A') 13 | .replace('(','%28') 14 | .replace(')','%29') 15 | .replace("'",'%27') 16 | ; 17 | } 18 | 19 | function hmacsign (httpMethod, base_uri, params, consumer_secret, token_secret, body) { 20 | // adapted from https://dev.twitter.com/docs/auth/oauth 21 | var base = 22 | httpMethod + "&" + 23 | encodeURIComponent( base_uri ) + "&" + 24 | Object.keys(params).sort().map(function (i) { 25 | // big WTF here with the escape + encoding but it's what twitter wants 26 | return escape(rfc3986(i)) + "%3D" + escape(rfc3986(params[i])) 27 | }).join("%26") 28 | var key = consumer_secret + '&' 29 | if (token_secret) key += token_secret 30 | return sha1(key, base) 31 | } 32 | 33 | exports.hmacsign = hmacsign 34 | exports.rfc3986 = rfc3986 -------------------------------------------------------------------------------- /test/lib/request/package.json: -------------------------------------------------------------------------------- 1 | { "name" : "request" 2 | , "description" : "Simplified HTTP request client." 3 | , "tags" : ["http", "simple", "util", "utility"] 4 | , "version" : "2.2.9" 5 | , "author" : "Mikeal Rogers " 6 | , "repository" : 7 | { "type" : "git" 8 | , "url" : "http://github.com/mikeal/request.git" 9 | } 10 | , "bugs" : 11 | { "url" : "http://github.com/mikeal/request/issues" } 12 | , "engines" : ["node >= 0.3.6"] 13 | , "main" : "./main" 14 | , "scripts": { "test": "bash tests/run.sh" } 15 | } 16 | -------------------------------------------------------------------------------- /test/lib/request/tests/googledoodle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dscape/spell/9a81d913cc8a1c9e14a27ad4315cffce39d9cb95/test/lib/request/tests/googledoodle.png -------------------------------------------------------------------------------- /test/lib/request/tests/run.sh: -------------------------------------------------------------------------------- 1 | FAILS=0 2 | for i in tests/test-*.js; do 3 | echo $i 4 | node $i || let FAILS++ 5 | done 6 | exit $FAILS 7 | -------------------------------------------------------------------------------- /test/lib/request/tests/server.js: -------------------------------------------------------------------------------- 1 | var http = require('http') 2 | , events = require('events') 3 | , stream = require('stream') 4 | , assert = require('assert') 5 | ; 6 | 7 | exports.createServer = function (port) { 8 | port = port || 6767 9 | var s = http.createServer(function (req, resp) { 10 | s.emit(req.url, req, resp); 11 | }) 12 | s.port = port 13 | s.url = 'http://localhost:'+port 14 | return s; 15 | } 16 | 17 | exports.createPostStream = function (text) { 18 | var postStream = new stream.Stream(); 19 | postStream.writeable = true; 20 | postStream.readable = true; 21 | setTimeout(function () {postStream.emit('data', new Buffer(text)); postStream.emit('end')}, 0); 22 | return postStream; 23 | } 24 | exports.createPostValidator = function (text) { 25 | var l = function (req, resp) { 26 | var r = ''; 27 | req.on('data', function (chunk) {r += chunk}) 28 | req.on('end', function () { 29 | if (r !== text) console.log(r, text); 30 | assert.equal(r, text) 31 | resp.writeHead(200, {'content-type':'text/plain'}) 32 | resp.write('OK') 33 | resp.end() 34 | }) 35 | } 36 | return l; 37 | } 38 | exports.createGetResponse = function (text, contentType) { 39 | var l = function (req, resp) { 40 | contentType = contentType || 'text/plain' 41 | resp.writeHead(200, {'content-type':contentType}) 42 | resp.write(text) 43 | resp.end() 44 | } 45 | return l; 46 | } 47 | exports.createChunkResponse = function (chunks, contentType) { 48 | var l = function (req, resp) { 49 | contentType = contentType || 'text/plain' 50 | resp.writeHead(200, {'content-type':contentType}) 51 | chunks.forEach(function (chunk) { 52 | resp.write(chunk) 53 | }) 54 | resp.end() 55 | } 56 | return l; 57 | } 58 | -------------------------------------------------------------------------------- /test/lib/request/tests/test-body.js: -------------------------------------------------------------------------------- 1 | var server = require('./server') 2 | , events = require('events') 3 | , stream = require('stream') 4 | , assert = require('assert') 5 | , request = require('../main.js') 6 | ; 7 | 8 | var s = server.createServer(); 9 | 10 | var tests = 11 | { testGet : 12 | { resp : server.createGetResponse("TESTING!") 13 | , expectBody: "TESTING!" 14 | } 15 | , testGetChunkBreak : 16 | { resp : server.createChunkResponse( 17 | [ new Buffer([239]) 18 | , new Buffer([163]) 19 | , new Buffer([191]) 20 | , new Buffer([206]) 21 | , new Buffer([169]) 22 | , new Buffer([226]) 23 | , new Buffer([152]) 24 | , new Buffer([131]) 25 | ]) 26 | , expectBody: "Ω☃" 27 | } 28 | , testGetJSON : 29 | { resp : server.createGetResponse('{"test":true}', 'application/json') 30 | , json : true 31 | , expectBody: {"test":true} 32 | } 33 | , testPutString : 34 | { resp : server.createPostValidator("PUTTINGDATA") 35 | , method : "PUT" 36 | , body : "PUTTINGDATA" 37 | } 38 | , testPutBuffer : 39 | { resp : server.createPostValidator("PUTTINGDATA") 40 | , method : "PUT" 41 | , body : new Buffer("PUTTINGDATA") 42 | } 43 | , testPutJSON : 44 | { resp : server.createPostValidator(JSON.stringify({foo: 'bar'})) 45 | , method: "PUT" 46 | , json: {foo: 'bar'} 47 | } 48 | , testPutMultipart : 49 | { resp: server.createPostValidator( 50 | '--frontier\r\n' + 51 | 'content-type: text/html\r\n' + 52 | '\r\n' + 53 | 'Oh hi.' + 54 | '\r\n--frontier\r\n\r\n' + 55 | 'Oh hi.' + 56 | '\r\n--frontier--' 57 | ) 58 | , method: "PUT" 59 | , multipart: 60 | [ {'content-type': 'text/html', 'body': 'Oh hi.'} 61 | , {'body': 'Oh hi.'} 62 | ] 63 | } 64 | } 65 | 66 | s.listen(s.port, function () { 67 | 68 | var counter = 0 69 | 70 | for (i in tests) { 71 | (function () { 72 | var test = tests[i] 73 | s.on('/'+i, test.resp) 74 | test.uri = s.url + '/' + i 75 | request(test, function (err, resp, body) { 76 | if (err) throw err 77 | if (test.expectBody) { 78 | assert.deepEqual(test.expectBody, body) 79 | } 80 | counter = counter - 1; 81 | if (counter === 0) { 82 | console.log(Object.keys(tests).length+" tests passed.") 83 | s.close() 84 | } 85 | }) 86 | counter++ 87 | })() 88 | } 89 | }) 90 | 91 | -------------------------------------------------------------------------------- /test/lib/request/tests/test-cookie.js: -------------------------------------------------------------------------------- 1 | var Cookie = require('../vendor/cookie') 2 | , assert = require('assert'); 3 | 4 | var str = 'sid=s543qactge.wKE61E01Bs%2BKhzmxrwrnug; path=/; httpOnly; expires=Sat, 04 Dec 2010 23:27:28 GMT'; 5 | var cookie = new Cookie(str); 6 | 7 | // test .toString() 8 | assert.equal(cookie.toString(), str); 9 | 10 | // test .path 11 | assert.equal(cookie.path, '/'); 12 | 13 | // test .httpOnly 14 | assert.equal(cookie.httpOnly, true); 15 | 16 | // test .name 17 | assert.equal(cookie.name, 'sid'); 18 | 19 | // test .value 20 | assert.equal(cookie.value, 's543qactge.wKE61E01Bs%2BKhzmxrwrnug'); 21 | 22 | // test .expires 23 | assert.equal(cookie.expires instanceof Date, true); 24 | 25 | // test .path default 26 | var cookie = new Cookie('foo=bar', { url: 'http://foo.com/bar' }); 27 | assert.equal(cookie.path, '/bar'); 28 | 29 | console.log('All tests passed'); 30 | -------------------------------------------------------------------------------- /test/lib/request/tests/test-cookiejar.js: -------------------------------------------------------------------------------- 1 | var Cookie = require('../vendor/cookie') 2 | , Jar = require('../vendor/cookie/jar') 3 | , assert = require('assert'); 4 | 5 | function expires(ms) { 6 | return new Date(Date.now() + ms).toUTCString(); 7 | } 8 | 9 | // test .get() expiration 10 | (function() { 11 | var jar = new Jar; 12 | var cookie = new Cookie('sid=1234; path=/; expires=' + expires(1000)); 13 | jar.add(cookie); 14 | setTimeout(function(){ 15 | var cookies = jar.get({ url: 'http://foo.com/foo' }); 16 | assert.equal(cookies.length, 1); 17 | assert.equal(cookies[0], cookie); 18 | setTimeout(function(){ 19 | var cookies = jar.get({ url: 'http://foo.com/foo' }); 20 | assert.equal(cookies.length, 0); 21 | }, 1000); 22 | }, 5); 23 | })(); 24 | 25 | // test .get() path support 26 | (function() { 27 | var jar = new Jar; 28 | var a = new Cookie('sid=1234; path=/'); 29 | var b = new Cookie('sid=1111; path=/foo/bar'); 30 | var c = new Cookie('sid=2222; path=/'); 31 | jar.add(a); 32 | jar.add(b); 33 | jar.add(c); 34 | 35 | // should remove the duplicates 36 | assert.equal(jar.cookies.length, 2); 37 | 38 | // same name, same path, latter prevails 39 | var cookies = jar.get({ url: 'http://foo.com/' }); 40 | assert.equal(cookies.length, 1); 41 | assert.equal(cookies[0], c); 42 | 43 | // same name, diff path, path specifity prevails, latter prevails 44 | var cookies = jar.get({ url: 'http://foo.com/foo/bar' }); 45 | assert.equal(cookies.length, 1); 46 | assert.equal(cookies[0], b); 47 | 48 | var jar = new Jar; 49 | var a = new Cookie('sid=1111; path=/foo/bar'); 50 | var b = new Cookie('sid=1234; path=/'); 51 | jar.add(a); 52 | jar.add(b); 53 | 54 | var cookies = jar.get({ url: 'http://foo.com/foo/bar' }); 55 | assert.equal(cookies.length, 1); 56 | assert.equal(cookies[0], a); 57 | 58 | var cookies = jar.get({ url: 'http://foo.com/' }); 59 | assert.equal(cookies.length, 1); 60 | assert.equal(cookies[0], b); 61 | 62 | var jar = new Jar; 63 | var a = new Cookie('sid=1111; path=/foo/bar'); 64 | var b = new Cookie('sid=3333; path=/foo/bar'); 65 | var c = new Cookie('pid=3333; path=/foo/bar'); 66 | var d = new Cookie('sid=2222; path=/foo/'); 67 | var e = new Cookie('sid=1234; path=/'); 68 | jar.add(a); 69 | jar.add(b); 70 | jar.add(c); 71 | jar.add(d); 72 | jar.add(e); 73 | 74 | var cookies = jar.get({ url: 'http://foo.com/foo/bar' }); 75 | assert.equal(cookies.length, 2); 76 | assert.equal(cookies[0], b); 77 | assert.equal(cookies[1], c); 78 | 79 | var cookies = jar.get({ url: 'http://foo.com/foo/' }); 80 | assert.equal(cookies.length, 1); 81 | assert.equal(cookies[0], d); 82 | 83 | var cookies = jar.get({ url: 'http://foo.com/' }); 84 | assert.equal(cookies.length, 1); 85 | assert.equal(cookies[0], e); 86 | })(); 87 | 88 | setTimeout(function() { 89 | console.log('All tests passed'); 90 | }, 1200); 91 | -------------------------------------------------------------------------------- /test/lib/request/tests/test-errors.js: -------------------------------------------------------------------------------- 1 | var server = require('./server') 2 | , events = require('events') 3 | , assert = require('assert') 4 | , request = require('../main.js') 5 | ; 6 | 7 | var local = 'http://localhost:8888/asdf' 8 | 9 | try { 10 | request({uri:local, body:{}}) 11 | assert.fail("Should have throw") 12 | } catch(e) { 13 | assert.equal(e.message, 'Argument error, options.body.') 14 | } 15 | 16 | try { 17 | request({uri:local, multipart: 'foo'}) 18 | assert.fail("Should have throw") 19 | } catch(e) { 20 | assert.equal(e.message, 'Argument error, options.multipart.') 21 | } 22 | 23 | try { 24 | request({uri:local, multipart: [{}]}) 25 | assert.fail("Should have throw") 26 | } catch(e) { 27 | assert.equal(e.message, 'Body attribute missing in multipart.') 28 | } 29 | 30 | console.log("All tests passed.") 31 | -------------------------------------------------------------------------------- /test/lib/request/tests/test-oauth.js: -------------------------------------------------------------------------------- 1 | var hmacsign = require('../oauth').hmacsign 2 | , assert = require('assert') 3 | , qs = require('querystring') 4 | , request = require('../main') 5 | ; 6 | 7 | function getsignature (r) { 8 | var sign 9 | r.headers.authorization.slice('OAuth '.length).replace(/,\ /g, ',').split(',').forEach(function (v) { 10 | if (v.slice(0, 'oauth_signature="'.length) === 'oauth_signature="') sign = v.slice('oauth_signature="'.length, -1) 11 | }) 12 | return decodeURIComponent(sign) 13 | } 14 | 15 | // Tests from Twitter documentation https://dev.twitter.com/docs/auth/oauth 16 | 17 | var reqsign = hmacsign('POST', 'https://api.twitter.com/oauth/request_token', 18 | { oauth_callback: 'http://localhost:3005/the_dance/process_callback?service_provider_id=11' 19 | , oauth_consumer_key: 'GDdmIQH6jhtmLUypg82g' 20 | , oauth_nonce: 'QP70eNmVz8jvdPevU3oJD2AfF7R7odC2XJcn4XlZJqk' 21 | , oauth_signature_method: 'HMAC-SHA1' 22 | , oauth_timestamp: '1272323042' 23 | , oauth_version: '1.0' 24 | }, "MCD8BKwGdgPHvAuvgvz4EQpqDAtx89grbuNMRd7Eh98") 25 | 26 | console.log(reqsign) 27 | console.log('8wUi7m5HFQy76nowoCThusfgB+Q=') 28 | assert.equal(reqsign, '8wUi7m5HFQy76nowoCThusfgB+Q=') 29 | 30 | var accsign = hmacsign('POST', 'https://api.twitter.com/oauth/access_token', 31 | { oauth_consumer_key: 'GDdmIQH6jhtmLUypg82g' 32 | , oauth_nonce: '9zWH6qe0qG7Lc1telCn7FhUbLyVdjEaL3MO5uHxn8' 33 | , oauth_signature_method: 'HMAC-SHA1' 34 | , oauth_token: '8ldIZyxQeVrFZXFOZH5tAwj6vzJYuLQpl0WUEYtWc' 35 | , oauth_timestamp: '1272323047' 36 | , oauth_verifier: 'pDNg57prOHapMbhv25RNf75lVRd6JDsni1AJJIDYoTY' 37 | , oauth_version: '1.0' 38 | }, "MCD8BKwGdgPHvAuvgvz4EQpqDAtx89grbuNMRd7Eh98", "x6qpRnlEmW9JbQn4PQVVeVG8ZLPEx6A0TOebgwcuA") 39 | 40 | console.log(accsign) 41 | console.log('PUw/dHA4fnlJYM6RhXk5IU/0fCc=') 42 | assert.equal(accsign, 'PUw/dHA4fnlJYM6RhXk5IU/0fCc=') 43 | 44 | var upsign = hmacsign('POST', 'http://api.twitter.com/1/statuses/update.json', 45 | { oauth_consumer_key: "GDdmIQH6jhtmLUypg82g" 46 | , oauth_nonce: "oElnnMTQIZvqvlfXM56aBLAf5noGD0AQR3Fmi7Q6Y" 47 | , oauth_signature_method: "HMAC-SHA1" 48 | , oauth_token: "819797-Jxq8aYUDRmykzVKrgoLhXSq67TEa5ruc4GJC2rWimw" 49 | , oauth_timestamp: "1272325550" 50 | , oauth_version: "1.0" 51 | , status: 'setting up my twitter 私のさえずりを設定する' 52 | }, "MCD8BKwGdgPHvAuvgvz4EQpqDAtx89grbuNMRd7Eh98", "J6zix3FfA9LofH0awS24M3HcBYXO5nI1iYe8EfBA") 53 | 54 | console.log(upsign) 55 | console.log('yOahq5m0YjDDjfjxHaXEsW9D+X0=') 56 | assert.equal(upsign, 'yOahq5m0YjDDjfjxHaXEsW9D+X0=') 57 | 58 | 59 | var r = request.post( 60 | { url: 'https://api.twitter.com/oauth/request_token' 61 | , oauth: 62 | { callback: 'http://localhost:3005/the_dance/process_callback?service_provider_id=11' 63 | , consumer_key: 'GDdmIQH6jhtmLUypg82g' 64 | , nonce: 'QP70eNmVz8jvdPevU3oJD2AfF7R7odC2XJcn4XlZJqk' 65 | , timestamp: '1272323042' 66 | , version: '1.0' 67 | , consumer_secret: "MCD8BKwGdgPHvAuvgvz4EQpqDAtx89grbuNMRd7Eh98" 68 | } 69 | }) 70 | 71 | console.log(getsignature(r)) 72 | assert.equal(reqsign, getsignature(r)) 73 | 74 | var r = request.post( 75 | { url: 'https://api.twitter.com/oauth/access_token' 76 | , oauth: 77 | { consumer_key: 'GDdmIQH6jhtmLUypg82g' 78 | , nonce: '9zWH6qe0qG7Lc1telCn7FhUbLyVdjEaL3MO5uHxn8' 79 | , signature_method: 'HMAC-SHA1' 80 | , token: '8ldIZyxQeVrFZXFOZH5tAwj6vzJYuLQpl0WUEYtWc' 81 | , timestamp: '1272323047' 82 | , verifier: 'pDNg57prOHapMbhv25RNf75lVRd6JDsni1AJJIDYoTY' 83 | , version: '1.0' 84 | , consumer_secret: "MCD8BKwGdgPHvAuvgvz4EQpqDAtx89grbuNMRd7Eh98" 85 | , token_secret: "x6qpRnlEmW9JbQn4PQVVeVG8ZLPEx6A0TOebgwcuA" 86 | } 87 | }) 88 | 89 | console.log(getsignature(r)) 90 | assert.equal(accsign, getsignature(r)) 91 | 92 | var r = request.post( 93 | { url: 'http://api.twitter.com/1/statuses/update.json' 94 | , oauth: 95 | { consumer_key: "GDdmIQH6jhtmLUypg82g" 96 | , nonce: "oElnnMTQIZvqvlfXM56aBLAf5noGD0AQR3Fmi7Q6Y" 97 | , signature_method: "HMAC-SHA1" 98 | , token: "819797-Jxq8aYUDRmykzVKrgoLhXSq67TEa5ruc4GJC2rWimw" 99 | , timestamp: "1272325550" 100 | , version: "1.0" 101 | , consumer_secret: "MCD8BKwGdgPHvAuvgvz4EQpqDAtx89grbuNMRd7Eh98" 102 | , token_secret: "J6zix3FfA9LofH0awS24M3HcBYXO5nI1iYe8EfBA" 103 | } 104 | , form: {status: 'setting up my twitter 私のさえずりを設定する'} 105 | }) 106 | 107 | console.log(getsignature(r)) 108 | assert.equal(upsign, getsignature(r)) 109 | 110 | -------------------------------------------------------------------------------- /test/lib/request/tests/test-pipes.js: -------------------------------------------------------------------------------- 1 | var server = require('./server') 2 | , events = require('events') 3 | , stream = require('stream') 4 | , assert = require('assert') 5 | , fs = require('fs') 6 | , request = require('../main.js') 7 | , path = require('path') 8 | , util = require('util') 9 | ; 10 | 11 | var s = server.createServer(3453); 12 | 13 | function ValidationStream(str) { 14 | this.str = str 15 | this.buf = '' 16 | this.on('data', function (data) { 17 | this.buf += data 18 | }) 19 | this.on('end', function () { 20 | assert.equal(this.str, this.buf) 21 | }) 22 | this.writable = true 23 | } 24 | util.inherits(ValidationStream, stream.Stream) 25 | ValidationStream.prototype.write = function (chunk) { 26 | this.emit('data', chunk) 27 | } 28 | ValidationStream.prototype.end = function (chunk) { 29 | if (chunk) emit('data', chunk) 30 | this.emit('end') 31 | } 32 | 33 | s.listen(s.port, function () { 34 | counter = 0; 35 | 36 | var check = function () { 37 | counter = counter - 1 38 | if (counter === 0) { 39 | console.log('All tests passed.') 40 | setTimeout(function () { 41 | process.exit(); 42 | }, 500) 43 | } 44 | } 45 | 46 | // Test pipeing to a request object 47 | s.once('/push', server.createPostValidator("mydata")); 48 | 49 | var mydata = new stream.Stream(); 50 | mydata.readable = true 51 | 52 | counter++ 53 | var r1 = request.put({url:'http://localhost:3453/push'}, function () { 54 | check(); 55 | }) 56 | mydata.pipe(r1) 57 | 58 | mydata.emit('data', 'mydata'); 59 | mydata.emit('end'); 60 | 61 | 62 | // Test pipeing from a request object. 63 | s.once('/pull', server.createGetResponse("mypulldata")); 64 | 65 | var mypulldata = new stream.Stream(); 66 | mypulldata.writable = true 67 | 68 | counter++ 69 | request({url:'http://localhost:3453/pull'}).pipe(mypulldata) 70 | 71 | var d = ''; 72 | 73 | mypulldata.write = function (chunk) { 74 | d += chunk; 75 | } 76 | mypulldata.end = function () { 77 | assert.equal(d, 'mypulldata'); 78 | check(); 79 | }; 80 | 81 | 82 | s.on('/cat', function (req, resp) { 83 | if (req.method === "GET") { 84 | resp.writeHead(200, {'content-type':'text/plain-test', 'content-length':4}); 85 | resp.end('asdf') 86 | } else if (req.method === "PUT") { 87 | assert.equal(req.headers['content-type'], 'text/plain-test'); 88 | assert.equal(req.headers['content-length'], 4) 89 | var validate = ''; 90 | 91 | req.on('data', function (chunk) {validate += chunk}) 92 | req.on('end', function () { 93 | resp.writeHead(201); 94 | resp.end(); 95 | assert.equal(validate, 'asdf'); 96 | check(); 97 | }) 98 | } 99 | }) 100 | s.on('/pushjs', function (req, resp) { 101 | if (req.method === "PUT") { 102 | assert.equal(req.headers['content-type'], 'text/javascript'); 103 | check(); 104 | } 105 | }) 106 | s.on('/catresp', function (req, resp) { 107 | request.get('http://localhost:3453/cat').pipe(resp) 108 | }) 109 | s.on('/doodle', function (req, resp) { 110 | if (req.headers['x-oneline-proxy']) { 111 | resp.setHeader('x-oneline-proxy', 'yup') 112 | } 113 | resp.writeHead('200', {'content-type':'image/png'}) 114 | fs.createReadStream(path.join(__dirname, 'googledoodle.png')).pipe(resp) 115 | }) 116 | s.on('/onelineproxy', function (req, resp) { 117 | var x = request('http://localhost:3453/doodle') 118 | req.pipe(x) 119 | x.pipe(resp) 120 | }) 121 | 122 | counter++ 123 | fs.createReadStream(__filename).pipe(request.put('http://localhost:3453/pushjs')) 124 | 125 | counter++ 126 | request.get('http://localhost:3453/cat').pipe(request.put('http://localhost:3453/cat')) 127 | 128 | counter++ 129 | request.get('http://localhost:3453/catresp', function (e, resp, body) { 130 | assert.equal(resp.headers['content-type'], 'text/plain-test'); 131 | assert.equal(resp.headers['content-length'], 4) 132 | check(); 133 | }) 134 | 135 | var doodleWrite = fs.createWriteStream(path.join(__dirname, 'test.png')) 136 | 137 | counter++ 138 | request.get('http://localhost:3453/doodle').pipe(doodleWrite) 139 | 140 | doodleWrite.on('close', function () { 141 | assert.deepEqual(fs.readFileSync(path.join(__dirname, 'googledoodle.png')), fs.readFileSync(path.join(__dirname, 'test.png'))) 142 | check() 143 | }) 144 | 145 | process.on('exit', function () { 146 | fs.unlinkSync(path.join(__dirname, 'test.png')) 147 | }) 148 | 149 | counter++ 150 | request.get({uri:'http://localhost:3453/onelineproxy', headers:{'x-oneline-proxy':'nope'}}, function (err, resp, body) { 151 | assert.equal(resp.headers['x-oneline-proxy'], 'yup') 152 | check() 153 | }) 154 | 155 | s.on('/afterresponse', function (req, resp) { 156 | resp.write('d') 157 | resp.end() 158 | }) 159 | 160 | counter++ 161 | var afterresp = request.post('http://localhost:3453/afterresponse').on('response', function () { 162 | var v = new ValidationStream('d') 163 | afterresp.pipe(v) 164 | v.on('end', check) 165 | }) 166 | 167 | }) 168 | -------------------------------------------------------------------------------- /test/lib/request/tests/test-proxy.js: -------------------------------------------------------------------------------- 1 | var server = require('./server') 2 | , events = require('events') 3 | , stream = require('stream') 4 | , assert = require('assert') 5 | , fs = require('fs') 6 | , request = require('../main.js') 7 | , path = require('path') 8 | , util = require('util') 9 | ; 10 | 11 | var port = 6768 12 | , called = false 13 | , proxiedHost = 'google.com' 14 | ; 15 | 16 | var s = server.createServer(port) 17 | s.listen(port, function () { 18 | s.on('http://google.com/', function (req, res) { 19 | called = true 20 | assert.equal(req.headers.host, proxiedHost) 21 | res.writeHeader(200) 22 | res.end() 23 | }) 24 | request ({ 25 | url: 'http://'+proxiedHost, 26 | proxy: 'http://localhost:'+port 27 | /* 28 | //should behave as if these arguments where passed: 29 | url: 'http://localhost:'+port, 30 | headers: {host: proxiedHost} 31 | //*/ 32 | }, function (err, res, body) { 33 | s.close() 34 | }) 35 | }) 36 | 37 | process.on('exit', function () { 38 | assert.ok(called, 'the request must be made to the proxy server') 39 | }) 40 | -------------------------------------------------------------------------------- /test/lib/request/tests/test-timeout.js: -------------------------------------------------------------------------------- 1 | var server = require('./server') 2 | , events = require('events') 3 | , stream = require('stream') 4 | , assert = require('assert') 5 | , request = require('../main.js') 6 | ; 7 | 8 | var s = server.createServer(); 9 | var expectedBody = "waited"; 10 | var remainingTests = 5; 11 | 12 | s.listen(s.port, function () { 13 | // Request that waits for 200ms 14 | s.on('/timeout', function (req, resp) { 15 | setTimeout(function(){ 16 | resp.writeHead(200, {'content-type':'text/plain'}) 17 | resp.write(expectedBody) 18 | resp.end() 19 | }, 200); 20 | }); 21 | 22 | // Scenario that should timeout 23 | var shouldTimeout = { 24 | url: s.url + "/timeout", 25 | timeout:100 26 | } 27 | 28 | 29 | request(shouldTimeout, function (err, resp, body) { 30 | assert.equal(err.code, "ETIMEDOUT"); 31 | checkDone(); 32 | }) 33 | 34 | 35 | // Scenario that shouldn't timeout 36 | var shouldntTimeout = { 37 | url: s.url + "/timeout", 38 | timeout:300 39 | } 40 | 41 | request(shouldntTimeout, function (err, resp, body) { 42 | assert.equal(err, null); 43 | assert.equal(expectedBody, body) 44 | checkDone(); 45 | }) 46 | 47 | // Scenario with no timeout set, so shouldn't timeout 48 | var noTimeout = { 49 | url: s.url + "/timeout" 50 | } 51 | 52 | request(noTimeout, function (err, resp, body) { 53 | assert.equal(err); 54 | assert.equal(expectedBody, body) 55 | checkDone(); 56 | }) 57 | 58 | // Scenario with a negative timeout value, should be treated a zero or the minimum delay 59 | var negativeTimeout = { 60 | url: s.url + "/timeout", 61 | timeout:-1000 62 | } 63 | 64 | request(negativeTimeout, function (err, resp, body) { 65 | assert.equal(err.code, "ETIMEDOUT"); 66 | checkDone(); 67 | }) 68 | 69 | // Scenario with a float timeout value, should be rounded by setTimeout anyway 70 | var floatTimeout = { 71 | url: s.url + "/timeout", 72 | timeout: 100.76 73 | } 74 | 75 | request(floatTimeout, function (err, resp, body) { 76 | assert.equal(err.code, "ETIMEDOUT"); 77 | checkDone(); 78 | }) 79 | 80 | function checkDone() { 81 | if(--remainingTests == 0) { 82 | s.close(); 83 | console.log("All tests passed."); 84 | } 85 | } 86 | }) 87 | 88 | -------------------------------------------------------------------------------- /test/lib/request/uuid.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | var s = [], itoh = '0123456789ABCDEF'; 3 | 4 | // Make array of random hex digits. The UUID only has 32 digits in it, but we 5 | // allocate an extra items to make room for the '-'s we'll be inserting. 6 | for (var i = 0; i <36; i++) s[i] = Math.floor(Math.random()*0x10); 7 | 8 | // Conform to RFC-4122, section 4.4 9 | s[14] = 4; // Set 4 high bits of time_high field to version 10 | s[19] = (s[19] & 0x3) | 0x8; // Specify 2 high bits of clock sequence 11 | 12 | // Convert to hex chars 13 | for (var i = 0; i <36; i++) s[i] = itoh[s[i]]; 14 | 15 | // Insert '-'s 16 | s[8] = s[13] = s[18] = s[23] = '-'; 17 | 18 | return s.join(''); 19 | } 20 | -------------------------------------------------------------------------------- /test/lib/request/vendor/cookie/index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Tobi - Cookie 3 | * Copyright(c) 2010 LearnBoost 4 | * MIT Licensed 5 | */ 6 | 7 | /** 8 | * Module dependencies. 9 | */ 10 | 11 | var url = require('url'); 12 | 13 | /** 14 | * Initialize a new `Cookie` with the given cookie `str` and `req`. 15 | * 16 | * @param {String} str 17 | * @param {IncomingRequest} req 18 | * @api private 19 | */ 20 | 21 | var Cookie = exports = module.exports = function Cookie(str, req) { 22 | this.str = str; 23 | 24 | // First key is the name 25 | this.name = str.substr(0, str.indexOf('=')); 26 | 27 | // Map the key/val pairs 28 | str.split(/ *; */).reduce(function(obj, pair){ 29 | pair = pair.split(/ *= */); 30 | obj[pair[0].toLowerCase()] = pair[1] || true; 31 | return obj; 32 | }, this); 33 | 34 | // Assign value 35 | this.value = this[this.name]; 36 | 37 | // Expires 38 | this.expires = this.expires 39 | ? new Date(this.expires) 40 | : Infinity; 41 | 42 | // Default or trim path 43 | this.path = this.path || '/'; 44 | }; 45 | 46 | /** 47 | * Return the original cookie string. 48 | * 49 | * @return {String} 50 | * @api public 51 | */ 52 | 53 | Cookie.prototype.toString = function(){ 54 | return this.str; 55 | }; 56 | -------------------------------------------------------------------------------- /test/lib/request/vendor/cookie/jar.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Tobi - CookieJar 3 | * Copyright(c) 2010 LearnBoost 4 | * MIT Licensed 5 | */ 6 | 7 | /** 8 | * Module dependencies. 9 | */ 10 | 11 | var url = require('url'); 12 | 13 | /** 14 | * Initialize a new `CookieJar`. 15 | * 16 | * @api private 17 | */ 18 | 19 | var CookieJar = exports = module.exports = function CookieJar() { 20 | this.cookies = []; 21 | }; 22 | 23 | /** 24 | * Add the given `cookie` to the jar. 25 | * 26 | * @param {Cookie} cookie 27 | * @api private 28 | */ 29 | 30 | CookieJar.prototype.add = function(cookie){ 31 | this.cookies = this.cookies.filter(function(c){ 32 | // Avoid duplication (same path, same name) 33 | return !(c.name == cookie.name && c.path == cookie.path); 34 | }); 35 | this.cookies.push(cookie); 36 | }; 37 | 38 | /** 39 | * Get cookies for the given `req`. 40 | * 41 | * @param {IncomingRequest} req 42 | * @return {Array} 43 | * @api private 44 | */ 45 | 46 | CookieJar.prototype.get = function(req){ 47 | var path = url.parse(req.url).pathname 48 | , now = new Date 49 | , specificity = {}; 50 | return this.cookies.filter(function(cookie){ 51 | if (0 == path.indexOf(cookie.path) && now < cookie.expires 52 | && cookie.path.length > (specificity[cookie.name] || 0)) 53 | return specificity[cookie.name] = cookie.path.length; 54 | }); 55 | }; 56 | 57 | /** 58 | * Return Cookie string for the given `req`. 59 | * 60 | * @param {IncomingRequest} req 61 | * @return {String} 62 | * @api private 63 | */ 64 | 65 | CookieJar.prototype.cookieString = function(req){ 66 | var cookies = this.get(req); 67 | if (cookies.length) { 68 | return cookies.map(function(cookie){ 69 | return cookie.name + '=' + cookie.value; 70 | }).join('; '); 71 | } 72 | }; 73 | -------------------------------------------------------------------------------- /test/perf.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var spell = require('../spell.js') 3 | , fs = require('fs') 4 | , dict = spell() 5 | , set1 = require('./resources/perf1') 6 | , set2 = require('./resources/perf2') 7 | , n = 0 8 | , bad = 0 9 | , unknown = 0 10 | , start 11 | , tests = process.argv[2] === '2' ? set2 : set1 12 | , exported 13 | ; 14 | 15 | dict.load( 16 | {corpus: JSON.parse(fs.readFileSync(__dirname + '/resources/big.json')) }); 17 | exported = dict.export(); 18 | start = new Date(); 19 | 20 | for (target in tests) { 21 | if (tests.hasOwnProperty(target)) { 22 | var wrongs = tests[target]; 23 | wrongs.split(/\s+/).forEach(function(wrong) { 24 | n++; 25 | var w = dict.lucky(wrong); 26 | if (w !== target) { 27 | bad++; 28 | if (!exported.hasOwnProperty(target)) { 29 | unknown++; 30 | } 31 | } 32 | }); 33 | } 34 | } 35 | 36 | console.log( 37 | { "bad" : bad 38 | , "n" : n 39 | , "bias" : 0 40 | , "pct" : ((100-100*bad/n)).toFixed(0) 41 | , "unknown" : unknown 42 | , "secs" : ((new Date()-start)/1000).toFixed(0) 43 | } 44 | ); -------------------------------------------------------------------------------- /test/resources/big.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var fs = require('fs') 3 | , spell = require('../../spell') 4 | , big = fs.readFileSync('big.txt').toString() 5 | , dict = spell() 6 | ; 7 | 8 | dict.load(big); 9 | fs.writeFileSync('big.json', JSON.stringify(dict.export(),null,2)); -------------------------------------------------------------------------------- /test/resources/npm.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var fs = require('fs') 3 | , spell = require('../../spell') 4 | , request = require('../lib/request') 5 | , JSONStream = require('../lib/JSONStream') 6 | , dict = spell() 7 | , get_stream = 8 | request('http://isaacs.couchone.com/registry/_all_docs?include_docs=true') 9 | , parse_stream = JSONStream.parse(['rows', /./, 'doc']) 10 | ; 11 | 12 | get_stream.pipe(parse_stream); 13 | parse_stream.on('data', function (module) { 14 | try { 15 | dict.load(JSON.stringify(module), {reset:false}); 16 | if(module.name) 17 | dict.add_word(module.name, 10000); // a name is like a 10thousand words 18 | } catch (e) { 19 | console.log('!!! ', e); 20 | console.log('----', JSON.stringify(module)); 21 | } 22 | }); 23 | parse_stream.on('end', function () { 24 | console.log('> npm.json'); 25 | fs.writeFileSync('npm.json', JSON.stringify(dict.export(),null,2)); 26 | }); 27 | -------------------------------------------------------------------------------- /test/resources/perf1.js: -------------------------------------------------------------------------------- 1 | var PERF1 = exports = 2 | { "access": "acess" 3 | , "accessing": "accesing" 4 | , "accommodation": "accomodation acommodation acomodation" 5 | , "account": "acount" 6 | , "address": "adress adres" 7 | , "addressable": "addresable" 8 | , "arranged": "aranged arrainged" 9 | , "arrangeing": "aranging" 10 | , "arrangement": "arragment" 11 | , "articles": "articals" 12 | , "aunt": "annt anut arnt" 13 | , "auxiliary": "auxillary" 14 | , "available": "avaible" 15 | , "awful": "awfall afful" 16 | , "basically": "basicaly" 17 | , "beginning": "begining" 18 | , "benefit": "benifit" 19 | , "benefits": "benifits" 20 | , "between": "beetween" 21 | , "bicycle": "bicycal bycicle bycycle" 22 | , "biscuits": "biscits biscutes biscuts bisquits buiscits buiscuts" 23 | , "built": "biult" 24 | , "cake": "cak" 25 | , "career": "carrer" 26 | , "cemetery": "cemetary semetary" 27 | , "centrally": "centraly" 28 | , "certain": "cirtain" 29 | , "challenges": "chalenges chalenges" 30 | , "chapter": "chaper chaphter chaptur" 31 | , "choice": "choise" 32 | , "choosing": "chosing" 33 | , "clerical": "clearical" 34 | , "committee": "comittee" 35 | , "compare": "compair" 36 | , "completely": "completly" 37 | , "consider": "concider" 38 | , "considerable": "conciderable" 39 | , "contented": "contenpted contende contended contentid" 40 | , "curtains": "cartains certans courtens cuaritains curtans curtians curtions" 41 | , "decide": "descide" 42 | , "decided": "descided" 43 | , "definitely": "definately difinately" 44 | , "definition": "defenition" 45 | , "definitions": "defenitions" 46 | , "description": "discription" 47 | , "desiccate": "desicate dessicate dessiccate" 48 | , "diagrammatically": "diagrammaticaally" 49 | , "different": "diffrent" 50 | , "driven": "dirven" 51 | , "ecstasy": "exstacy ecstacy" 52 | , "embarrass": "embaras embarass" 53 | , "establishing": "astablishing establising" 54 | , "experience": "experance experiance" 55 | , "experiences": "experances" 56 | , "extended": "extented" 57 | , "extremely": "extreamly" 58 | , "fails": "failes" 59 | , "families": "familes" 60 | , "february": "febuary" 61 | , "further": "futher" 62 | , "gallery": "galery gallary gallerry gallrey" 63 | , "hierarchal": "hierachial" 64 | , "hierarchy": "hierchy" 65 | , "inconvenient": "inconvienient inconvient inconvinient" 66 | , "independent": "independant independant" 67 | , "initial": "intial" 68 | , "initials": "inetials inistals initails initals intials" 69 | , "juice": "guic juce jucie juise juse" 70 | , "latest": "lates latets latiest latist" 71 | , "laugh": "lagh lauf laught lugh" 72 | , "level": "leval" 73 | , "levels": "levals" 74 | , "liaison": "liaision liason" 75 | , "lieu": "liew" 76 | , "literature": "litriture" 77 | , "loans": "lones" 78 | , "locally": "localy" 79 | , "magnificent": "magnificnet magificent magnifcent magnifecent magnifiscant magnifisent magnificant" 80 | , "management": "managment" 81 | , "meant": "ment" 82 | , "minuscule": "miniscule" 83 | , "minutes": "muinets" 84 | , "monitoring": "monitering" 85 | , "necessary": "neccesary necesary neccesary necassary necassery neccasary" 86 | , "occurrence": "occurence occurence" 87 | , "often": "ofen offen offten ofton" 88 | , "opposite": "opisite oppasite oppesite oppisit oppisite opposit oppossite oppossitte" 89 | , "parallel": "paralel paralell parrallel parralell parrallell" 90 | , "particular": "particulaur" 91 | , "perhaps": "perhapse" 92 | , "personnel": "personnell" 93 | , "planned": "planed" 94 | , "poem": "poame" 95 | , "poems": "poims pomes" 96 | , "poetry": "poartry poertry poetre poety powetry" 97 | , "position": "possition" 98 | , "possible": "possable" 99 | , "pretend": "pertend protend prtend pritend" 100 | , "problem": "problam proble promblem proplen" 101 | , "pronunciation": "pronounciation" 102 | , "purple": "perple perpul poarple" 103 | , "questionnaire": "questionaire" 104 | , "really": "realy relley relly" 105 | , "receipt": "receit receite reciet recipt" 106 | , "receive": "recieve" 107 | , "refreshment": "reafreshment refreshmant refresment refressmunt" 108 | , "remember": "rember remeber rememmer rermember" 109 | , "remind": "remine remined" 110 | , "scarcely": "scarcly scarecly scarely scarsely" 111 | , "scissors": "scisors sissors" 112 | , "separate": "seperate" 113 | , "singular": "singulaur" 114 | , "someone": "somone" 115 | , "sources": "sorces" 116 | , "southern": "southen" 117 | , "special": "speaical specail specal speical" 118 | , "splendid": "spledid splended splened splended" 119 | , "standardizing": "stanerdizing" 120 | , "stomach": "stomac stomache stomec stumache" 121 | , "supersede": "supercede superceed" 122 | , "there": "ther" 123 | , "totally": "totaly" 124 | , "transferred": "transfred" 125 | , "transportability": "transportibility" 126 | , "triangular": "triangulaur" 127 | , "understand": "undersand undistand" 128 | , "unexpected": "unexpcted unexpeted unexspected" 129 | , "unfortunately": "unfortunatly" 130 | , "unique": "uneque" 131 | , "useful": "usefull" 132 | , "valuable": "valubale valuble" 133 | , "variable": "varable" 134 | , "variant": "vairiant" 135 | , "various": "vairious" 136 | , "visited": "fisited viseted vistid vistied" 137 | , "visitors": "vistors" 138 | , "voluntary": "volantry" 139 | , "voting": "voteing" 140 | , "wanted": "wantid wonted" 141 | , "whether": "wether" 142 | , "wrote": "rote wote" 143 | }; 144 | if(module && module.exports) { module.exports = PERF1; } -------------------------------------------------------------------------------- /test/resources/perf2.js: -------------------------------------------------------------------------------- 1 | var PERF2 = exports = 2 | { "forbidden": "forbiden" 3 | , "decisions": "deciscions descisions" 4 | , "supposedly": "supposidly" 5 | , "embellishing": "embelishing" 6 | , "technique": "tecnique" 7 | , "permanently": "perminantly" 8 | , "confirmation": "confermation" 9 | , "appointment": "appoitment" 10 | , "progression": "progresion" 11 | , "accompanying": "acompaning" 12 | , "applicable": "aplicable" 13 | , "regained": "regined" 14 | , "guidelines": "guidlines" 15 | , "surrounding": "serounding" 16 | , "titles": "tittles" 17 | , "unavailable": "unavailble" 18 | , "advantageous": "advantageos" 19 | , "brief": "brif" 20 | , "appeal": "apeal" 21 | , "consisting": "consisiting" 22 | , "clerk": "cleark clerck" 23 | , "component": "componant" 24 | , "favourable": "faverable" 25 | , "separation": "seperation" 26 | , "search": "serch" 27 | , "receive": "recieve" 28 | , "employees": "emploies" 29 | , "prior": "piror" 30 | , "resulting": "reulting" 31 | , "suggestion": "sugestion" 32 | , "opinion": "oppinion" 33 | , "cancellation": "cancelation" 34 | , "criticism": "citisum" 35 | , "useful": "usful" 36 | , "humour": "humor" 37 | , "anomalies": "anomolies" 38 | , "would": "whould" 39 | , "doubt": "doupt" 40 | , "examination": "eximination" 41 | , "therefore": "therefoe" 42 | , "recommend": "recomend" 43 | , "separated": "seperated" 44 | , "successful": "sucssuful succesful" 45 | , "apparent": "apparant" 46 | , "occurred": "occureed" 47 | , "particular": "paerticulaur" 48 | , "pivoting": "pivting" 49 | , "announcing": "anouncing" 50 | , "challenge": "chalange" 51 | , "arrangements": "araingements" 52 | , "proportions": "proprtions" 53 | , "organized": "oranised" 54 | , "accept": "acept" 55 | , "dependence": "dependance" 56 | , "unequalled": "unequaled" 57 | , "numbers": "numbuers" 58 | , "sense": "sence" 59 | , "conversely": "conversly" 60 | , "provide": "provid" 61 | , "arrangement": "arrangment" 62 | , "responsibilities": "responsiblities" 63 | , "fourth": "forth" 64 | , "ordinary": "ordenary" 65 | , "description": "desription descvription desacription" 66 | , "inconceivable": "inconcievable" 67 | , "data": "dsata" 68 | , "register": "rgister" 69 | , "supervision": "supervison" 70 | , "encompassing": "encompasing" 71 | , "negligible": "negligable" 72 | , "allow": "alow" 73 | , "operations": "operatins" 74 | , "executed": "executted" 75 | , "interpretation": "interpritation" 76 | , "hierarchy": "heiarky" 77 | , "indeed": "indead" 78 | , "years": "yesars" 79 | , "through": "throut" 80 | , "committee": "committe" 81 | , "inquiries": "equiries" 82 | , "before": "befor" 83 | , "continued": "contuned" 84 | , "permanent": "perminant" 85 | , "choose": "chose" 86 | , "virtually": "vertually" 87 | , "correspondence": "correspondance" 88 | , "eventually": "eventully" 89 | , "lonely": "lonley" 90 | , "profession": "preffeson" 91 | , "they": "thay" 92 | , "now": "noe" 93 | , "desperately": "despratly" 94 | , "university": "unversity" 95 | , "adjournment": "adjurnment" 96 | , "possibilities": "possablities" 97 | , "stopped": "stoped" 98 | , "mean": "meen" 99 | , "weighted": "wagted" 100 | , "adequately": "adequattly" 101 | , "shown": "hown" 102 | , "matrix": "matriiix" 103 | , "profit": "proffit" 104 | , "encourage": "encorage" 105 | , "collate": "colate" 106 | , "disaggregate": "disaggreagte disaggreaget" 107 | , "receiving": "recieving reciving" 108 | , "proviso": "provisoe" 109 | , "umbrella": "umberalla" 110 | , "approached": "aproached" 111 | , "pleasant": "plesent" 112 | , "difficulty": "dificulty" 113 | , "appointments": "apointments" 114 | , "base": "basse" 115 | , "conditioning": "conditining" 116 | , "earliest": "earlyest" 117 | , "beginning": "begining" 118 | , "universally": "universaly" 119 | , "unresolved": "unresloved" 120 | , "length": "lengh" 121 | , "exponentially": "exponentualy" 122 | , "utilized": "utalised" 123 | , "set": "et" 124 | , "surveys": "servays" 125 | , "families": "familys" 126 | , "system": "sysem" 127 | , "approximately": "aproximatly" 128 | , "their": "ther" 129 | , "scheme": "scheem" 130 | , "speaking": "speeking" 131 | , "repetitive": "repetative" 132 | , "inefficient": "ineffiect" 133 | , "geneva": "geniva" 134 | , "exactly": "exsactly" 135 | , "immediate": "imediate" 136 | , "appreciation": "apreciation" 137 | , "luckily": "luckeley" 138 | , "eliminated": "elimiated" 139 | , "believe": "belive" 140 | , "appreciated": "apreciated" 141 | , "readjusted": "reajusted" 142 | , "were": "wer where" 143 | , "feeling": "fealing" 144 | , "and": "anf" 145 | , "false": "faulse" 146 | , "seen": "seeen" 147 | , "interrogating": "interogationg" 148 | , "academically": "academicly" 149 | , "relatively": "relativly relitivly" 150 | , "traditionally": "traditionaly" 151 | , "studying": "studing" 152 | , "majority": "majorty" 153 | , "build": "biuld" 154 | , "aggravating": "agravating" 155 | , "transactions": "trasactions" 156 | , "arguing": "aurguing" 157 | , "sheets": "sheertes" 158 | , "successive": "sucsesive sucessive" 159 | , "segment": "segemnt" 160 | , "especially": "especaily" 161 | , "later": "latter" 162 | , "senior": "sienior" 163 | , "dragged": "draged" 164 | , "atmosphere": "atmospher" 165 | , "drastically": "drasticaly" 166 | , "particularly": "particulary" 167 | , "visitor": "vistor" 168 | , "session": "sesion" 169 | , "continually": "contually" 170 | , "availability": "avaiblity" 171 | , "busy": "buisy" 172 | , "parameters": "perametres" 173 | , "surroundings": "suroundings seroundings" 174 | , "employed": "emploied" 175 | , "adequate": "adiquate" 176 | , "handle": "handel" 177 | , "means": "meens" 178 | , "familiar": "familer" 179 | , "between": "beeteen" 180 | , "overall": "overal" 181 | , "timing": "timeing" 182 | , "committees": "comittees commitees" 183 | , "queries": "quies" 184 | , "econometric": "economtric" 185 | , "erroneous": "errounous" 186 | , "decides": "descides" 187 | , "reference": "refereence refference" 188 | , "intelligence": "inteligence" 189 | , "edition": "ediion ediition" 190 | , "are": "arte" 191 | , "apologies": "appologies" 192 | , "thermawear": "thermawere thermawhere" 193 | , "techniques": "tecniques" 194 | , "voluntary": "volantary" 195 | , "subsequent": "subsequant subsiquent" 196 | , "currently": "curruntly" 197 | , "forecast": "forcast" 198 | , "weapons": "wepons" 199 | , "routine": "rouint" 200 | , "neither": "niether" 201 | , "approach": "aproach" 202 | , "available": "availble" 203 | , "recently": "reciently" 204 | , "ability": "ablity" 205 | , "nature": "natior" 206 | , "commercial": "comersial" 207 | , "agencies": "agences" 208 | , "however": "howeverr" 209 | , "suggested": "sugested" 210 | , "career": "carear" 211 | , "many": "mony" 212 | , "annual": "anual" 213 | , "according": "acording" 214 | , "receives": "recives recieves" 215 | , "interesting": "intresting" 216 | , "expense": "expence" 217 | , "relevant": "relavent relevaant" 218 | , "table": "tasble" 219 | , "throughout": "throuout" 220 | , "conference": "conferance" 221 | , "sensible": "sensable" 222 | , "described": "discribed describd" 223 | , "union": "unioun" 224 | , "interest": "intrest" 225 | , "flexible": "flexable" 226 | , "refered": "reffered" 227 | , "controlled": "controled" 228 | , "sufficient": "suficient" 229 | , "dissension": "desention" 230 | , "adaptable": "adabtable" 231 | , "representative": "representitive" 232 | , "irrelevant": "irrelavent" 233 | , "unnecessarily": "unessasarily" 234 | , "applied": "upplied" 235 | , "apologised": "appologised" 236 | , "these": "thees thess" 237 | , "choices": "choises" 238 | , "will": "wil" 239 | , "procedure": "proceduer" 240 | , "shortened": "shortend" 241 | , "manually": "manualy" 242 | , "disappointing": "dissapoiting" 243 | , "excessively": "exessively" 244 | , "comments": "coments" 245 | , "containing": "containg" 246 | , "develop": "develope" 247 | , "credit": "creadit" 248 | , "government": "goverment" 249 | , "acquaintances": "aquantences" 250 | , "orientated": "orentated" 251 | , "widely": "widly" 252 | , "advise": "advice" 253 | , "difficult": "dificult" 254 | , "investigated": "investegated" 255 | , "bonus": "bonas" 256 | , "conceived": "concieved" 257 | , "nationally": "nationaly" 258 | , "compared": "comppared compased" 259 | , "moving": "moveing" 260 | , "necessity": "nessesity" 261 | , "opportunity": "oppertunity oppotunity opperttunity" 262 | , "thoughts": "thorts" 263 | , "equalled": "equaled" 264 | , "variety": "variatry" 265 | , "analysis": "analiss analsis analisis" 266 | , "patterns": "pattarns" 267 | , "qualities": "quaties" 268 | , "easily": "easyly" 269 | , "organization": "oranisation oragnisation" 270 | , "the": "thw hte thi" 271 | , "corporate": "corparate" 272 | , "composed": "compossed" 273 | , "enormously": "enomosly" 274 | , "financially": "financialy" 275 | , "functionally": "functionaly" 276 | , "discipline": "disiplin" 277 | , "announcement": "anouncement" 278 | , "progresses": "progressess" 279 | , "except": "excxept" 280 | , "recommending": "recomending" 281 | , "mathematically": "mathematicaly" 282 | , "source": "sorce" 283 | , "combine": "comibine" 284 | , "input": "inut" 285 | , "careers": "currers carrers" 286 | , "resolved": "resoved" 287 | , "demands": "diemands" 288 | , "unequivocally": "unequivocaly" 289 | , "suffering": "suufering" 290 | , "immediately": "imidatly imediatly" 291 | , "accepted": "acepted" 292 | , "projects": "projeccts" 293 | , "necessary": "necasery nessasary nessisary neccassary" 294 | , "journalism": "journaism" 295 | , "unnecessary": "unessessay" 296 | , "night": "nite" 297 | , "output": "oputput" 298 | , "security": "seurity" 299 | , "essential": "esential" 300 | , "beneficial": "benificial benficial" 301 | , "explaining": "explaning" 302 | , "supplementary": "suplementary" 303 | , "questionnaire": "questionare" 304 | , "employment": "empolyment" 305 | , "proceeding": "proceding" 306 | , "decision": "descisions descision" 307 | , "per": "pere" 308 | , "discretion": "discresion" 309 | , "reaching": "reching" 310 | , "analysed": "analised" 311 | , "expansion": "expanion" 312 | , "although": "athough" 313 | , "subtract": "subtrcat" 314 | , "analysing": "aalysing" 315 | , "comparison": "comparrison" 316 | , "months": "monthes" 317 | , "hierarchal": "hierachial" 318 | , "misleading": "missleading" 319 | , "commit": "comit" 320 | , "auguments": "aurgument" 321 | , "within": "withing" 322 | , "obtaining": "optaning" 323 | , "accounts": "acounts" 324 | , "primarily": "pimarily" 325 | , "operator": "opertor" 326 | , "accumulated": "acumulated" 327 | , "extremely": "extreemly" 328 | , "there": "thear" 329 | , "summarys": "sumarys" 330 | , "analyse": "analiss" 331 | , "understandable": "understadable" 332 | , "safeguard": "safegaurd" 333 | , "consist": "consisit" 334 | , "declarations": "declaratrions" 335 | , "minutes": "muinutes muiuets" 336 | , "associated": "assosiated" 337 | , "accessibility": "accessability" 338 | , "examine": "examin" 339 | , "surveying": "servaying" 340 | , "politics": "polatics" 341 | , "annoying": "anoying" 342 | , "again": "agiin" 343 | , "assessing": "accesing" 344 | , "ideally": "idealy" 345 | , "scrutinized": "scrutiniesed" 346 | , "simular": "similar" 347 | , "personnel": "personel" 348 | , "whereas": "wheras" 349 | , "when": "whn" 350 | , "geographically": "goegraphicaly" 351 | , "gaining": "ganing" 352 | , "requested": "rquested" 353 | , "separate": "seporate" 354 | , "students": "studens" 355 | , "prepared": "prepaired" 356 | , "generated": "generataed" 357 | , "graphically": "graphicaly" 358 | , "suited": "suted" 359 | , "variable": "varible vaiable" 360 | , "building": "biulding" 361 | , "required": "reequired" 362 | , "necessitates": "nessisitates" 363 | , "together": "togehter" 364 | , "profits": "proffits" 365 | }; 366 | if(module && module.exports) { module.exports = PERF2; } -------------------------------------------------------------------------------- /test/spell.js: -------------------------------------------------------------------------------- 1 | if (!spell) { // node 2 | var spell = require('../spell.js') 3 | , assert = require('assert') 4 | , fs = require('fs') 5 | , big = JSON.parse(fs.readFileSync(__dirname + '/resources/big.json')) 6 | , PERF1 = require('./resources/perf1') 7 | , PERF2 = require('./resources/perf2') 8 | ; 9 | } else { 10 | var big = BIG; 11 | } 12 | 13 | function quality(name) { 14 | var dict = spell() 15 | , n = 0 16 | , bad = 0 17 | , unknown = 0 18 | , tests = (name === '2') ? PERF2 : PERF1 19 | , target 20 | , start 21 | , exported 22 | ; 23 | 24 | dict.load({ corpus: big }); 25 | exported = dict['export']().corpus; 26 | 27 | for (target in tests) { 28 | if (tests.hasOwnProperty(target)) { 29 | var wrongs = tests[target]; 30 | wrongs.split(/\s+/).forEach(function(wrong) { 31 | n++; 32 | var w = dict.lucky(wrong); 33 | if (w !== target) { 34 | bad++; 35 | if (!exported.hasOwnProperty(target)) { 36 | unknown++; 37 | } 38 | } 39 | }); 40 | } 41 | } 42 | 43 | return { "bad": bad, "n": n, "unknown" : unknown }; 44 | } 45 | 46 | describe('spell', function(){ 47 | var readme; 48 | 49 | readme = spell(); 50 | readme.load("I am going to the park with Theo today." + 51 | "It's going to be the bomb."); 52 | 53 | describe('#export()', function(){ 54 | it('[readme] should be export the load', function() { 55 | var exported = readme["export"]().corpus; 56 | assert(exported.i === 1); 57 | assert(exported.am === 1); 58 | assert(exported.going === 2); 59 | assert(exported.to === 2); 60 | assert(exported.the === 2); 61 | assert(exported.park === 1); 62 | assert(exported.be === 1); 63 | assert(exported.bomb === 1); 64 | assert(exported.theo === 1); 65 | assert(exported["with"] === 1); 66 | }); 67 | }); 68 | 69 | describe('#load()', function(){ 70 | it('[readme] should be able to read json', function() { 71 | var dict = spell() 72 | , exported 73 | ; 74 | dict.load( 75 | { corpus: 76 | { "I" : 1 77 | , "am" : 1 78 | , "going" : 1 79 | , "to" : 2 80 | , "the" : 1 81 | , "park" : 1 82 | } 83 | }); 84 | exported = dict["export"]().corpus; 85 | assert(exported.i === 1); 86 | assert(exported.am === 1); 87 | assert(exported.going === 1); 88 | assert(exported.to === 2); 89 | assert(exported.the === 1); 90 | assert(exported.park === 1); 91 | }); 92 | it('[readme] load without reseting', function() { 93 | var dict = spell() 94 | , exported 95 | ; 96 | dict.load("One Two Three."); 97 | dict.load({"corpus": "four", "reset": false }); 98 | exported = dict["export"]().corpus; 99 | assert(exported.one === 1); 100 | assert(exported.two === 1); 101 | assert(exported.three === 1); 102 | assert(exported.four === 1); 103 | }); 104 | it('[iso] load and export should work together', function () { 105 | var dict = spell() 106 | , another = spell() 107 | , exported 108 | ; 109 | dict.load({"corpus": "four"}); 110 | another.load(dict['export']()); 111 | assert(JSON.stringify(another['export']() === dict['export']())); 112 | }); 113 | it('[readme] load with reseting', function() { 114 | var dict = spell() 115 | , exported 116 | ; 117 | dict.load("One Two Three."); 118 | dict.load({"corpus": "four", "reset": true }); 119 | exported = dict["export"]().corpus; 120 | assert(exported.one !== 1); 121 | assert(exported.two !== 1); 122 | assert(exported.three !== 1); 123 | assert(exported.four === 1); 124 | }); 125 | it('[readme] load with first param string', function() { 126 | var dict = spell() 127 | , exported 128 | ; 129 | dict.load("One Two Three."); 130 | dict.load("four", { "reset": false }); 131 | exported = dict["export"]().corpus; 132 | assert(exported.one === 1); 133 | assert(exported.two === 1); 134 | assert(exported.three === 1); 135 | assert(exported.four === 1); 136 | }); 137 | }); 138 | 139 | describe('#add_word()', function(){ 140 | it('[readme] basic usage', function() { 141 | var dict = spell() 142 | , exported 143 | ; 144 | dict.load("One Two Three."); 145 | dict.add_word("Four"); 146 | exported = dict["export"]().corpus; 147 | assert(exported.one === 1); 148 | assert(exported.two === 1); 149 | assert(exported.three === 1); 150 | assert(exported.four === 1); 151 | }); 152 | it('[readme] setting score', function() { 153 | var dict = spell() 154 | , exported 155 | ; 156 | dict.add_word("Four", {score: 500}); 157 | exported = dict["export"]().corpus; 158 | assert(exported.four === 500); 159 | }); 160 | it('[readme] add word with integer', function() { 161 | var dict = spell() 162 | , exported 163 | ; 164 | dict.add_word("test", 500); 165 | exported = dict["export"]().corpus; 166 | assert(exported.test === 500); 167 | }); 168 | it('[readme] add word with string', function() { 169 | var dict = spell() 170 | , exported 171 | ; 172 | dict.add_word("test", "500"); 173 | exported = dict["export"]().corpus; 174 | assert(exported.test === 500); 175 | }); 176 | }); 177 | 178 | describe('#remove_word()', function(){ 179 | it('[readme] removing word', function() { 180 | var dict = spell() 181 | , exported 182 | ; 183 | dict.load('the game'); 184 | dict.remove_word("the"); 185 | exported = dict["export"]().corpus; 186 | assert(exported.game === 1); 187 | assert(exported.the === undefined); 188 | }); 189 | }); 190 | 191 | describe('#reset()', function(){ 192 | it('[readme] reset should clean contents of dict', function() { 193 | var dict = spell() 194 | , exported 195 | ; 196 | dict.load({corpus: readme["export"]()}); 197 | dict.reset(); 198 | exported = JSON.stringify(dict["export"]().corpus); 199 | assert(exported === '{}'); 200 | }); 201 | }); 202 | 203 | describe('#suggest()', function(){ 204 | it('[readme] `suggest` `the` for `thew`', function() { 205 | var suggest = readme.suggest("thew"); 206 | assert(suggest[0].word === "the"); 207 | assert(suggest[0].score === 2); 208 | assert(suggest[1].word === "theo"); 209 | assert(suggest[1].score === 1); 210 | }); 211 | it('[readme] correct word that exists should return the word', function(){ 212 | var suggest = readme.suggest("the"); 213 | assert(suggest[0].word === "the"); 214 | assert(suggest[0].score === 2); 215 | assert(suggest.length === 1); 216 | }); 217 | it('[readme] first `suggest` should match `lucky`', function(){ 218 | var suggest = readme.suggest("thew") 219 | , lucky = readme.lucky("the") 220 | ; 221 | assert(suggest[0].word === readme.lucky("the")); 222 | }); 223 | it('[npm] should be able to suggest w/ customer alphabets', function () { 224 | var npm = spell() 225 | , suggest 226 | ; 227 | npm.load({corpus: {"uglify": 1, "uglify-js": 1}}); 228 | suggest = npm.suggest('uglifyjs', 229 | "abcdefghijklmnopqrstuvwxyz-".split("")); 230 | assert(suggest[0]. word === 'uglify-js'); 231 | assert(suggest[0].score === 1); 232 | }); 233 | }); 234 | 235 | describe('#lucky()', function(){ 236 | it('[readme] with empty dict', function() { 237 | var dict = spell() 238 | , lucky = dict.lucky("testing") 239 | ; 240 | assert(lucky === undefined); 241 | }); 242 | }); 243 | 244 | describe('#storage', function () { 245 | it('[readme] should be able to store and load from storage', function(){ 246 | var err 247 | , dict 248 | , dict_clone 249 | , dict_not_clone 250 | , exported 251 | , exported_not_clone 252 | , node = (typeof exports !== 'undefined') 253 | , global_var = node ? global : window 254 | , storage = 255 | { "get" : function () { 256 | try { return JSON.parse(global_var._test_spell); } 257 | catch (err) { return {}; } 258 | } 259 | , "store" : function (dict,after_store) { 260 | try { global_var._test_spell = JSON.stringify(dict); } 261 | catch (err) { return {}; } 262 | } 263 | } 264 | ; 265 | dict = spell(storage); 266 | dict.load("tucano second skin", {store: true}); 267 | dict_clone = spell(storage); 268 | dict_not_clone = spell(); 269 | exported = dict_clone["export"]().corpus; 270 | exported_not_clone = dict_not_clone["export"]().corpus; 271 | assert(exported.tucano === 1); 272 | assert(exported.second === 1); 273 | assert(exported.skin === 1); 274 | assert(exported_not_clone.tucano !== 1); 275 | assert(exported_not_clone.second !== 1); 276 | assert(exported_not_clone.skin !== 1); 277 | delete global_var._test_spell; // clean up 278 | }); 279 | }); 280 | 281 | describe('#quality', function(){ 282 | it('[perf1] less than 68 bad, 15 unknown', function() { 283 | var results = quality('1'); 284 | assert(results.bad < 69); 285 | assert(results.unknown < 16); 286 | assert(results.n === 270); 287 | }); 288 | it('[perf2] less than 130 bad, 43 unknown', function() { 289 | var results = quality('2'); 290 | assert(results.bad < 131); 291 | assert(results.unknown < 44); 292 | assert(results.n === 400); 293 | }); 294 | }); 295 | }); --------------------------------------------------------------------------------