├── .gitignore ├── .travis.yml ├── bower.json ├── tests ├── browser │ ├── index.htm │ ├── mocha.css │ └── mocha.js └── main.js ├── gulpfile.js ├── package.json ├── karma.conf.js ├── dist ├── lightrouter.min.js └── lightrouter.js ├── README.md └── src └── lightrouter.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | before_script: 5 | - npm install -g karma-cli 6 | install: npm install -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lightrouter", 3 | "homepage": "https://github.com/garygreen/lightrouter", 4 | "authors": [ 5 | "Gary Green" 6 | ], 7 | "description": "Lightweight javascript routing", 8 | "main": "dist/lightrouter.js", 9 | "keywords": [ 10 | "javascript", 11 | "routing" 12 | ], 13 | "license": "Apache 2.0", 14 | "ignore": [ 15 | "**/.*", 16 | "node_modules", 17 | "bower_components", 18 | "test", 19 | "tests" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /tests/browser/index.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Mocha Tests 6 | 7 | 12 | 13 | 14 |
15 |
16 | 17 | 18 | 19 | 25 | 26 | 27 | 31 |
32 | 33 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | clean = require('gulp-clean'), 3 | uglify = require('gulp-uglify'), 4 | concat = require('gulp-concat'), 5 | header = require('gulp-header'); 6 | 7 | // Clean dist folder 8 | gulp.task('clean', function() { 9 | return gulp.src('dist').pipe(clean()); 10 | }); 11 | 12 | // Copy 13 | gulp.task('copy', function() { 14 | return gulp.src('src/lightrouter.js') 15 | .pipe(gulp.dest('dist')); 16 | }); 17 | 18 | // Uglify / Compress 19 | gulp.task('uglify', function() { 20 | return gulp.src('src/lightrouter.js') 21 | .pipe(uglify()) 22 | .pipe(concat('lightrouter.min.js')) 23 | .pipe(header('/* lightrouter.js - Copyright 2014 Gary Green. Licensed under the Apache License, Version 2.0 */')) 24 | .pipe(gulp.dest('dist')); 25 | }); 26 | 27 | gulp.task('default', ['clean', 'copy', 'uglify']); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lightrouter", 3 | "version": "0.3.3", 4 | "description": "Lighweight javascript router", 5 | "author": "Gary Green", 6 | "main": "./src/lightrouter.js", 7 | "keywords": [ 8 | "router", "routing" 9 | ], 10 | "scripts": { 11 | "test-browser": "karma start --single-run --browsers PhantomJS", 12 | "test-node": "mocha -b -R tap tests", 13 | "test": "npm run test-node && npm run test-browser" 14 | }, 15 | "dependencies": {}, 16 | "devDependencies": { 17 | "chai": "^3.0.0", 18 | "gulp": "^3.8.5", 19 | "gulp-clean": "^0.3.1", 20 | "gulp-concat": "^2.2.0", 21 | "gulp-header": "^1.0.5", 22 | "gulp-uglify": "^0.3.1", 23 | "karma": "^0.12.37", 24 | "karma-chai": "^0.1.0", 25 | "karma-mocha": "^0.2.0", 26 | "karma-phantomjs-launcher": "^0.2.0", 27 | "mocha": "^2.2.5", 28 | "phantomjs": "^1.9.17" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Sat Jul 12 2014 14:48:42 GMT-0700 (PDT) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['mocha', 'chai'], 14 | 15 | 16 | // list of files / patterns to load in the browser 17 | files: [ 18 | 'src/lightrouter.js', 19 | 'tests/main.js' 20 | ], 21 | 22 | 23 | // list of files to exclude 24 | exclude: [ 25 | 26 | ], 27 | 28 | 29 | // preprocess matching files before serving them to the browser 30 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 31 | preprocessors: { 32 | 33 | }, 34 | 35 | // test results reporter to use 36 | // possible values: 'dots', 'progress' 37 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 38 | reporters: ['progress'], 39 | 40 | 41 | // web server port 42 | port: 9876, 43 | 44 | 45 | // enable / disable colors in the output (reporters and logs) 46 | colors: true, 47 | 48 | 49 | // level of logging 50 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 51 | logLevel: config.LOG_INFO, 52 | 53 | 54 | // enable / disable watching file and executing tests whenever any file changes 55 | autoWatch: true, 56 | 57 | 58 | // start these browsers 59 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 60 | browsers: ['PhantomJS'], 61 | 62 | 63 | // Continuous Integration mode 64 | // if true, Karma captures browsers, runs the tests and exits 65 | singleRun: false 66 | }); 67 | }; -------------------------------------------------------------------------------- /dist/lightrouter.min.js: -------------------------------------------------------------------------------- 1 | /* lightrouter.js - Copyright 2014 Gary Green. Licensed under the Apache License, Version 2.0 */!function(t){"undefined"!=typeof exports?module.exports=t():window.LightRouter=t(window)}(function(t){function e(e){this.pathRoot="",this.routes=[],this.type="path",this.path=null,this.hash=null,this.context=this,this.handler=t;var h="([\\w-]+)";if(this.namedParam={match:new RegExp("{("+h+")}","g"),replace:h},e=e||{},e.type&&this.setType(e.type),e.path&&this.setPath(e.path),e.pathRoot&&this.setPathRoot(e.pathRoot),e.hash&&this.setHash(e.hash),e.context&&this.setContext(e.context),e.handler&&this.setHandler(e.handler),e.routes){var s;for(s in e.routes)this.add(s,e.routes[s])}}function h(t,e,h){this.path=t,this.callback=e,this.router=h,this.values=[]}return e.prototype={Route:h,add:function(t,e){return this.routes.push(new this.Route(t,e,this)),this},empty:function(){return this.routes=[],this},setType:function(t){return this.type=t,this},setPathRoot:function(t){return this.pathRoot=t,this},setPath:function(t){return this.path=t,this},setHash:function(t){return this.hash=t,this},setContext:function(t){return this.context=t,this},setHandler:function(t){return this.handler=t,this},getUrl:function(e){var h;if(e=e||this.type,"path"==e){var s=new RegExp("^"+this.pathRoot+"/?");h=this.path||t.location.pathname.substring(1),h=h.replace(s,"")}else"hash"==e&&(h=this.hash||t.location.hash.substring(1));return decodeURI(h)},match:function(t,e){var h=new this.Route(t,e,this);return h.test(this.getUrl())?h.run():void 0},run:function(){var t,e=this.getUrl();for(var h in this.routes)if(t=this.routes[h],t.test(e))return t.run(),t}},h.prototype={regex:function(){var t=this.path;return"string"==typeof t?new RegExp("^"+t.replace(/\//g,"\\/").replace(this.router.namedParam.match,this.router.namedParam.replace)+"$"):t},params:function(){var t,e,h={},s=this.values,r=s,i=0,n=this.path;"string"==typeof n&&(i=1,r=n.match(this.router.namedParam.match));for(e in r)t=i?r[e].replace(this.router.namedParam.match,"$1"):e,h[t]=s[e];return h},test:function(t){var e;return(e=t.match(this.regex()))?(this.values=e.slice(1),!0):!1},run:function(){return"string"==typeof this.callback?this.router.handler[this.callback](this.params()):this.callback.apply(this.router.context,[this.params()])}},e}); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Lightrouter 2 | =========== 3 | 4 | [![Build Status](https://api.travis-ci.org/garygreen/lightrouter.svg)](https://travis-ci.org/garygreen/lightrouter) 5 | 6 | Ultra lightweight javascript router for those that need the most basic simple javascript routing. 7 | 8 | 9 | ```javascript 10 | // Initialise the router 11 | var router = new LightRouter({ 12 | type: 'path', // Default routing type 13 | handler: handler, // Handler for string-based route callbacks 14 | pathRoot: 'my-app/path', // Base path for your app 15 | routes: { 16 | '': 'home', // Base Url 17 | 'users': 'userIndex', 18 | 'users/{id}': 'userShow', 19 | 'users/(.*)': function(params) { /* User: params[0] */ }, 20 | 'users/(?:create|{id}/edit)': function(params) { /* Create/Edit User: params.id */ } 21 | } 22 | }); 23 | 24 | // Route callback handlers 25 | var handler = { 26 | home: function() { }, 27 | userIndex: function() { }, 28 | userShow: function(params) { /* Show user: params.id */ } 29 | }; 30 | 31 | // Run the router 32 | router.run(); 33 | ``` 34 | 35 | ## Features 36 | 37 | * Super fast, regexes and objects are only initialized when they need to be. 38 | * Predictable and easy regex-based, no new syntax to learn. 39 | * Most routers use `:param` style syntax which interferes with regexes non-consuming match `(?:` 40 | * Node support 41 | * Support for matching from a base path (see `pathRoot`). 42 | * Traditional URL matching and support for hash based routing for single page apps. 43 | * Fully unit tested. 44 | 45 | #### Adding routes 46 | 47 | Routes can be added with the `add()` method 48 | 49 | ```javascript 50 | router.add(/anywhere-in-url-match\/(\w+)/, function(params) { }); 51 | router.add('articles/{id}', function(params) { console.log('loading article ' + params.id); }); 52 | router.add('user/{userId}/show', function(params) { console.log('showing user', params.userId); }); 53 | ``` 54 | 55 | #### Routing Types 56 | 57 | Type | Matches Against 58 | -------|---------------------------- 59 | path | window.location.pathname 60 | hash | window.location.hash 61 | 62 | ### Base/Path Root Support 63 | 64 | The given `pathRoot` will be essentially stripped out when matching the routes: 65 | 66 | ```javascript 67 | // Initialise the router 68 | var router = new LightRouter({ 69 | type: 'path', 70 | path: 'my-app-path/users/1', 71 | pathRoot: 'my-app-path', 72 | routes: { 73 | 'users/{id}': function(params) { console.log('showing user', params.id); } 74 | } 75 | }).run(); 76 | ``` 77 | 78 | 79 | API 80 | --- 81 | 82 | ### add([string|regex], callback) 83 | Adds a route and calls the callback function if the routing url matches. Callback can be either a string (with use of the `handler` option) or closure. 84 | 85 | ### empty() 86 | Empty's all the routes 87 | 88 | ### setType(['hash'|'path']) 89 | Set's the default routing type to either by hash or path 90 | 91 | ### setPathRoot([string]url) 92 | Set's the paths root url (the paths root url will be removed when matching against routes). 93 | 94 | ### setPath([string]path) 95 | Set's the path to match routes against (will default to window.location.pathname) 96 | 97 | ### setHash([string]hash) 98 | Set's the hash to match routes against (will default to window.location.hash) 99 | 100 | ### setContext([object]context) 101 | Set's the context to call matched routes under. 102 | 103 | ### setHandler([object]handler) 104 | Set the object handler for string-based route callbacks. 105 | 106 | ### getUrl([type]) 107 | Get's the url to match routes against (will default to get the url on the default routing type as set in the options or by `setType()` or for the type if supplied.) 108 | 109 | ### match(route, [string|closure]callback) 110 | Attempt to match a one-time route. 111 | 112 | ### run() 113 | Checks the routes against the url and calls the associated route function. Will also return the matched `Route` object. 114 | -------------------------------------------------------------------------------- /tests/browser/mocha.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | body { 4 | font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; 5 | padding: 60px 50px; 6 | } 7 | 8 | #mocha ul, #mocha li { 9 | margin: 0; 10 | padding: 0; 11 | } 12 | 13 | #mocha ul { 14 | list-style: none; 15 | } 16 | 17 | #mocha h1, #mocha h2 { 18 | margin: 0; 19 | } 20 | 21 | #mocha h1 { 22 | margin-top: 15px; 23 | font-size: 1em; 24 | font-weight: 200; 25 | } 26 | 27 | #mocha h1 a { 28 | text-decoration: none; 29 | color: inherit; 30 | } 31 | 32 | #mocha h1 a:hover { 33 | text-decoration: underline; 34 | } 35 | 36 | #mocha .suite .suite h1 { 37 | margin-top: 0; 38 | font-size: .8em; 39 | } 40 | 41 | .hidden { 42 | display: none; 43 | } 44 | 45 | #mocha h2 { 46 | font-size: 12px; 47 | font-weight: normal; 48 | cursor: pointer; 49 | } 50 | 51 | #mocha .suite { 52 | margin-left: 15px; 53 | } 54 | 55 | #mocha .test { 56 | margin-left: 15px; 57 | overflow: hidden; 58 | } 59 | 60 | #mocha .test.pending:hover h2::after { 61 | content: '(pending)'; 62 | font-family: arial; 63 | } 64 | 65 | #mocha .test.pass.medium .duration { 66 | background: #C09853; 67 | } 68 | 69 | #mocha .test.pass.slow .duration { 70 | background: #B94A48; 71 | } 72 | 73 | #mocha .test.pass::before { 74 | content: '✓'; 75 | font-size: 12px; 76 | display: block; 77 | float: left; 78 | margin-right: 5px; 79 | color: #00d6b2; 80 | } 81 | 82 | #mocha .test.pass .duration { 83 | font-size: 9px; 84 | margin-left: 5px; 85 | padding: 2px 5px; 86 | color: white; 87 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 88 | -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 89 | box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 90 | -webkit-border-radius: 5px; 91 | -moz-border-radius: 5px; 92 | -ms-border-radius: 5px; 93 | -o-border-radius: 5px; 94 | border-radius: 5px; 95 | } 96 | 97 | #mocha .test.pass.fast .duration { 98 | display: none; 99 | } 100 | 101 | #mocha .test.pending { 102 | color: #0b97c4; 103 | } 104 | 105 | #mocha .test.pending::before { 106 | content: '◦'; 107 | color: #0b97c4; 108 | } 109 | 110 | #mocha .test.fail { 111 | color: #c00; 112 | } 113 | 114 | #mocha .test.fail pre { 115 | color: black; 116 | } 117 | 118 | #mocha .test.fail::before { 119 | content: '✖'; 120 | font-size: 12px; 121 | display: block; 122 | float: left; 123 | margin-right: 5px; 124 | color: #c00; 125 | } 126 | 127 | #mocha .test pre.error { 128 | color: #c00; 129 | max-height: 300px; 130 | overflow: auto; 131 | } 132 | 133 | #mocha .test pre { 134 | display: block; 135 | float: left; 136 | clear: left; 137 | font: 12px/1.5 monaco, monospace; 138 | margin: 5px; 139 | padding: 15px; 140 | border: 1px solid #eee; 141 | border-bottom-color: #ddd; 142 | -webkit-border-radius: 3px; 143 | -webkit-box-shadow: 0 1px 3px #eee; 144 | -moz-border-radius: 3px; 145 | -moz-box-shadow: 0 1px 3px #eee; 146 | } 147 | 148 | #mocha .test h2 { 149 | position: relative; 150 | } 151 | 152 | #mocha .test a.replay { 153 | position: absolute; 154 | top: 3px; 155 | right: 0; 156 | text-decoration: none; 157 | vertical-align: middle; 158 | display: block; 159 | width: 15px; 160 | height: 15px; 161 | line-height: 15px; 162 | text-align: center; 163 | background: #eee; 164 | font-size: 15px; 165 | -moz-border-radius: 15px; 166 | border-radius: 15px; 167 | -webkit-transition: opacity 200ms; 168 | -moz-transition: opacity 200ms; 169 | transition: opacity 200ms; 170 | opacity: 0.3; 171 | color: #888; 172 | } 173 | 174 | #mocha .test:hover a.replay { 175 | opacity: 1; 176 | } 177 | 178 | #mocha-report.pass .test.fail { 179 | display: none; 180 | } 181 | 182 | #mocha-report.fail .test.pass { 183 | display: none; 184 | } 185 | 186 | #mocha-error { 187 | color: #c00; 188 | font-size: 1.5 em; 189 | font-weight: 100; 190 | letter-spacing: 1px; 191 | } 192 | 193 | #mocha-stats { 194 | position: fixed; 195 | top: 15px; 196 | right: 10px; 197 | font-size: 12px; 198 | margin: 0; 199 | color: #888; 200 | } 201 | 202 | #mocha-stats .progress { 203 | float: right; 204 | padding-top: 0; 205 | } 206 | 207 | #mocha-stats em { 208 | color: black; 209 | } 210 | 211 | #mocha-stats a { 212 | text-decoration: none; 213 | color: inherit; 214 | } 215 | 216 | #mocha-stats a:hover { 217 | border-bottom: 1px solid #eee; 218 | } 219 | 220 | #mocha-stats li { 221 | display: inline-block; 222 | margin: 0 5px; 223 | list-style: none; 224 | padding-top: 11px; 225 | } 226 | 227 | #mocha-stats canvas { 228 | width: 40px; 229 | height: 40px; 230 | } 231 | 232 | code .comment { color: #ddd } 233 | code .init { color: #2F6FAD } 234 | code .string { color: #5890AD } 235 | code .keyword { color: #8A6343 } 236 | code .number { color: #2F6FAD } 237 | 238 | @media screen and (max-device-width: 480px) { 239 | body { 240 | padding: 60px 0px; 241 | } 242 | 243 | #stats { 244 | position: absolute; 245 | } 246 | } -------------------------------------------------------------------------------- /dist/lightrouter.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Gary Green. 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 | 16 | (function(factory) { 17 | 18 | if (typeof exports !== 'undefined') 19 | { 20 | module.exports = factory(); 21 | } 22 | else 23 | { 24 | window.LightRouter = factory(window); 25 | } 26 | 27 | }(function(window) { 28 | 29 | function LightRouter(options) 30 | { 31 | /** 32 | * Path root (will be stripped out when testing path-based routes) 33 | * @type string 34 | */ 35 | this.pathRoot = ''; 36 | 37 | /** 38 | * Routes 39 | * @type array 40 | */ 41 | this.routes = []; 42 | 43 | /** 44 | * Default routing type [hash or path] 45 | * @type string 46 | */ 47 | this.type = 'path'; 48 | 49 | /** 50 | * Custom path (mainly used for testing) 51 | * @type string 52 | */ 53 | this.path = null; 54 | 55 | /** 56 | * Custom hash (mainly used for testing) 57 | * @type string 58 | */ 59 | this.hash = null; 60 | 61 | /** 62 | * Context to call matched routes under 63 | * @type {mixed} 64 | */ 65 | this.context = this; 66 | 67 | /** 68 | * Handler for string based callbacks 69 | * @type {object|function} 70 | */ 71 | this.handler = window; 72 | 73 | /** 74 | * Named param replace and matching regex 75 | * @type {Object} 76 | */ 77 | var namedParam = '([\\w-]+)'; 78 | this.namedParam = { 79 | match: new RegExp('{(' + namedParam + ')}', 'g'), 80 | replace: namedParam 81 | }; 82 | 83 | options = options || {}; 84 | 85 | if (options.type) this.setType(options.type); 86 | if (options.path) this.setPath(options.path); 87 | if (options.pathRoot) this.setPathRoot(options.pathRoot); 88 | if (options.hash) this.setHash(options.hash); 89 | if (options.context) this.setContext(options.context); 90 | if (options.handler) this.setHandler(options.handler); 91 | 92 | if (options.routes) 93 | { 94 | var route; 95 | for (route in options.routes) 96 | { 97 | this.add(route, options.routes[route]); 98 | } 99 | } 100 | } 101 | 102 | LightRouter.prototype = { 103 | 104 | /** 105 | * Route constructor 106 | * @type {Route} 107 | */ 108 | Route: Route, 109 | 110 | /** 111 | * Add a route 112 | * @param string|RegExp route 113 | * @param string|function callback 114 | * @return self 115 | */ 116 | add: function(route, callback) { 117 | this.routes.push(new this.Route(route, callback, this)); 118 | return this; 119 | }, 120 | 121 | 122 | /** 123 | * Empty/clear all the routes 124 | * @return self 125 | */ 126 | empty: function() { 127 | this.routes = []; 128 | return this; 129 | }, 130 | 131 | /** 132 | * Set's the routing type 133 | * @param self 134 | */ 135 | setType: function(type) { 136 | this.type = type; 137 | return this; 138 | }, 139 | 140 | /** 141 | * Set the path root url 142 | * @param string url 143 | * @return self 144 | */ 145 | setPathRoot: function(url) { 146 | this.pathRoot = url; 147 | return this; 148 | }, 149 | 150 | /** 151 | * Sets the custom path to test routes against 152 | * @param string path 153 | * @return self 154 | */ 155 | setPath: function(path) { 156 | this.path = path; 157 | return this; 158 | }, 159 | 160 | /** 161 | * Sets the custom hash to test routes against 162 | * @param string hash 163 | * @return self 164 | */ 165 | setHash: function(hash) { 166 | this.hash = hash; 167 | return this; 168 | }, 169 | 170 | /** 171 | * Sets context to call matched routes under 172 | * @param mixed context 173 | * @return self 174 | */ 175 | setContext: function(context) { 176 | this.context = context; 177 | return this; 178 | }, 179 | 180 | /** 181 | * Set handler 182 | * @param mixed context 183 | * @return self 184 | */ 185 | setHandler: function(handler) { 186 | this.handler = handler; 187 | return this; 188 | }, 189 | 190 | /** 191 | * Gets the url to test the routes against 192 | * @return self 193 | */ 194 | getUrl: function(routeType) { 195 | 196 | var url; 197 | routeType = routeType || this.type; 198 | 199 | if (routeType == 'path') 200 | { 201 | var rootRegex = new RegExp('^' + this.pathRoot + '/?'); 202 | url = this.path || window.location.pathname.substring(1); 203 | url = url.replace(rootRegex, ''); 204 | } 205 | else if (routeType == 'hash') 206 | { 207 | url = this.hash || window.location.hash.substring(1); 208 | } 209 | 210 | return decodeURI(url); 211 | }, 212 | 213 | /** 214 | * Attempt to match a one-time route and callback 215 | * 216 | * @param {string} path 217 | * @param {closure|string} callback 218 | * @return {mixed} 219 | */ 220 | match: function(path, callback) { 221 | var route = new this.Route(path, callback, this); 222 | if (route.test(this.getUrl())) 223 | { 224 | return route.run(); 225 | } 226 | }, 227 | 228 | /** 229 | * Run the router 230 | * @return Route|undefined 231 | */ 232 | run: function() { 233 | var url = this.getUrl(), route; 234 | 235 | for (var i in this.routes) 236 | { 237 | // Get the route 238 | route = this.routes[i]; 239 | 240 | // Test and run the route if it matches 241 | if (route.test(url)) 242 | { 243 | route.run(); 244 | return route; 245 | } 246 | } 247 | } 248 | }; 249 | 250 | 251 | /** 252 | * Route object 253 | * @param {string} path 254 | * @param {string} closure 255 | * @param {LightRouter} router Instance of the light router the route belongs to. 256 | */ 257 | function Route(path, callback, router) 258 | { 259 | this.path = path; 260 | this.callback = callback; 261 | this.router = router; 262 | this.values = []; 263 | } 264 | 265 | Route.prototype = { 266 | 267 | /** 268 | * Converts route to a regex (if required) so that it's suitable for matching against. 269 | * @param string route 270 | * @return RegExp 271 | */ 272 | regex: function() { 273 | 274 | var path = this.path; 275 | 276 | if (typeof path === 'string') 277 | { 278 | return new RegExp('^' + path.replace(/\//g, '\\/').replace(this.router.namedParam.match, this.router.namedParam.replace) + '$'); 279 | } 280 | return path; 281 | }, 282 | 283 | /** 284 | * Get the matching param keys 285 | * @return object Object keyed with param name (or index) with the value. 286 | */ 287 | params: function() { 288 | 289 | var obj = {}, name, values = this.values, params = values, i, t = 0, path = this.path; 290 | 291 | if (typeof path === 'string') 292 | { 293 | t = 1; 294 | params = path.match(this.router.namedParam.match); 295 | } 296 | 297 | for (i in params) 298 | { 299 | name = t ? params[i].replace(this.router.namedParam.match, '$1') : i; 300 | obj[name] = values[i]; 301 | } 302 | 303 | return obj; 304 | }, 305 | 306 | /** 307 | * Test the route to see if it matches 308 | * @param {string} url Url to match against 309 | * @return {boolean} 310 | */ 311 | test: function(url) { 312 | var matches; 313 | if (matches = url.match(this.regex())) 314 | { 315 | this.values = matches.slice(1); 316 | return true; 317 | } 318 | return false; 319 | }, 320 | 321 | /** 322 | * Run the route callback with the matched params 323 | * @return {mixed} 324 | */ 325 | run: function() { 326 | if (typeof this.callback === 'string') 327 | { 328 | return this.router.handler[this.callback](this.params()); 329 | } 330 | return this.callback.apply(this.router.context, [this.params()]); 331 | } 332 | }; 333 | 334 | return LightRouter; 335 | 336 | })); -------------------------------------------------------------------------------- /src/lightrouter.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Gary Green. 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 | 16 | (function(window, factory) { 17 | 18 | if (typeof exports === 'object') 19 | { 20 | module.exports = factory(window); 21 | } 22 | else 23 | { 24 | window.LightRouter = factory(window); 25 | } 26 | 27 | }(typeof window === 'undefined' ? undefined : window, function(window) { 28 | 29 | function LightRouter(options) 30 | { 31 | /** 32 | * Path root (will be stripped out when testing path-based routes) 33 | * @type string 34 | */ 35 | this.pathRoot = ''; 36 | 37 | /** 38 | * Routes 39 | * @type array 40 | */ 41 | this.routes = []; 42 | 43 | /** 44 | * Default routing type [hash or path] 45 | * @type string 46 | */ 47 | this.type = 'path'; 48 | 49 | /** 50 | * Custom path (mainly used for testing) 51 | * @type string 52 | */ 53 | this.path = null; 54 | 55 | /** 56 | * Custom hash (mainly used for testing) 57 | * @type string 58 | */ 59 | this.hash = null; 60 | 61 | /** 62 | * Context to call matched routes under 63 | * @type {mixed} 64 | */ 65 | this.context = this; 66 | 67 | /** 68 | * Handler for string based callbacks 69 | * @type {object|function} 70 | */ 71 | this.handler = window; 72 | 73 | /** 74 | * Named param replace and matching regex 75 | * @type {Object} 76 | */ 77 | var namedParam = '([\\w-]+)'; 78 | this.namedParam = { 79 | match: new RegExp('{(' + namedParam + ')}', 'g'), 80 | replace: namedParam 81 | }; 82 | 83 | options = options || {}; 84 | 85 | if (options.type) this.setType(options.type); 86 | if (options.path) this.setPath(options.path); 87 | if (options.pathRoot) this.setPathRoot(options.pathRoot); 88 | if (options.hash) this.setHash(options.hash); 89 | if (options.context) this.setContext(options.context); 90 | if (options.handler) this.setHandler(options.handler); 91 | 92 | if (options.routes) 93 | { 94 | var route; 95 | for (route in options.routes) 96 | { 97 | this.add(route, options.routes[route]); 98 | } 99 | } 100 | } 101 | 102 | LightRouter.prototype = { 103 | 104 | /** 105 | * Route constructor 106 | * @type {Route} 107 | */ 108 | Route: Route, 109 | 110 | /** 111 | * Add a route 112 | * @param string|RegExp route 113 | * @param string|function callback 114 | * @return self 115 | */ 116 | add: function(route, callback) { 117 | this.routes.push(new this.Route(route, callback, this)); 118 | return this; 119 | }, 120 | 121 | 122 | /** 123 | * Empty/clear all the routes 124 | * @return self 125 | */ 126 | empty: function() { 127 | this.routes = []; 128 | return this; 129 | }, 130 | 131 | /** 132 | * Set's the routing type 133 | * @param self 134 | */ 135 | setType: function(type) { 136 | this.type = type; 137 | return this; 138 | }, 139 | 140 | /** 141 | * Set the path root url 142 | * @param string url 143 | * @return self 144 | */ 145 | setPathRoot: function(url) { 146 | this.pathRoot = url; 147 | return this; 148 | }, 149 | 150 | /** 151 | * Sets the custom path to test routes against 152 | * @param string path 153 | * @return self 154 | */ 155 | setPath: function(path) { 156 | this.path = path; 157 | return this; 158 | }, 159 | 160 | /** 161 | * Sets the custom hash to test routes against 162 | * @param string hash 163 | * @return self 164 | */ 165 | setHash: function(hash) { 166 | this.hash = hash; 167 | return this; 168 | }, 169 | 170 | /** 171 | * Sets context to call matched routes under 172 | * @param mixed context 173 | * @return self 174 | */ 175 | setContext: function(context) { 176 | this.context = context; 177 | return this; 178 | }, 179 | 180 | /** 181 | * Set handler 182 | * @param mixed context 183 | * @return self 184 | */ 185 | setHandler: function(handler) { 186 | this.handler = handler; 187 | return this; 188 | }, 189 | 190 | /** 191 | * Gets the url to test the routes against 192 | * @return self 193 | */ 194 | getUrl: function(routeType) { 195 | 196 | var url; 197 | routeType = routeType || this.type; 198 | 199 | if (routeType == 'path') 200 | { 201 | var rootRegex = new RegExp('^' + this.pathRoot + '/?'); 202 | url = this.path || window.location.pathname.substring(1); 203 | url = url.replace(rootRegex, ''); 204 | } 205 | else if (routeType == 'hash') 206 | { 207 | url = this.hash || window.location.hash.substring(1); 208 | } 209 | 210 | return decodeURI(url); 211 | }, 212 | 213 | /** 214 | * Attempt to match a one-time route and callback 215 | * 216 | * @param {string} path 217 | * @param {closure|string} callback 218 | * @return {mixed} 219 | */ 220 | match: function(path, callback) { 221 | var route = new this.Route(path, callback, this); 222 | if (route.test(this.getUrl())) 223 | { 224 | return route.run(); 225 | } 226 | }, 227 | 228 | /** 229 | * Run the router 230 | * @return Route|undefined 231 | */ 232 | run: function() { 233 | var url = this.getUrl(), route; 234 | 235 | for (var i in this.routes) 236 | { 237 | // Get the route 238 | route = this.routes[i]; 239 | 240 | // Test and run the route if it matches 241 | if (route.test(url)) 242 | { 243 | route.run(); 244 | return route; 245 | } 246 | } 247 | } 248 | }; 249 | 250 | 251 | /** 252 | * Route object 253 | * @param {string} path 254 | * @param {string} closure 255 | * @param {LightRouter} router Instance of the light router the route belongs to. 256 | */ 257 | function Route(path, callback, router) 258 | { 259 | this.path = path; 260 | this.callback = callback; 261 | this.router = router; 262 | this.values = []; 263 | } 264 | 265 | Route.prototype = { 266 | 267 | /** 268 | * Converts route to a regex (if required) so that it's suitable for matching against. 269 | * @param string route 270 | * @return RegExp 271 | */ 272 | regex: function() { 273 | 274 | var path = this.path; 275 | 276 | if (typeof path === 'string') 277 | { 278 | return new RegExp('^' + path.replace(/\//g, '\\/').replace(this.router.namedParam.match, this.router.namedParam.replace) + '$'); 279 | } 280 | return path; 281 | }, 282 | 283 | /** 284 | * Get the matching param keys 285 | * @return object Object keyed with param name (or index) with the value. 286 | */ 287 | params: function() { 288 | 289 | var obj = {}, name, values = this.values, params = values, i, t = 0, path = this.path; 290 | 291 | if (typeof path === 'string') 292 | { 293 | t = 1; 294 | params = path.match(this.router.namedParam.match); 295 | } 296 | 297 | for (i in params) 298 | { 299 | name = t ? params[i].replace(this.router.namedParam.match, '$1') : i; 300 | obj[name] = values[i]; 301 | } 302 | 303 | return obj; 304 | }, 305 | 306 | /** 307 | * Test the route to see if it matches 308 | * @param {string} url Url to match against 309 | * @return {boolean} 310 | */ 311 | test: function(url) { 312 | var matches; 313 | if (matches = url.match(this.regex())) 314 | { 315 | this.values = matches.slice(1); 316 | return true; 317 | } 318 | return false; 319 | }, 320 | 321 | /** 322 | * Run the route callback with the matched params 323 | * @return {mixed} 324 | */ 325 | run: function() { 326 | if (typeof this.callback === 'string') 327 | { 328 | return this.router.handler[this.callback](this.params()); 329 | } 330 | return this.callback.apply(this.router.context, [this.params()]); 331 | } 332 | }; 333 | 334 | return LightRouter; 335 | 336 | })); -------------------------------------------------------------------------------- /tests/main.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | var assert, LightRouter; 4 | 5 | if (typeof exports === 'object') 6 | { 7 | assert = require('chai').assert, 8 | LightRouter = require('../src/lightrouter.js'); 9 | } 10 | else 11 | { 12 | // Browser testing support 13 | assert = window.chai.assert; 14 | LightRouter = window.LightRouter; 15 | } 16 | 17 | 18 | describe('initialisation', function() { 19 | 20 | it('should be able to initialise the router', function() { 21 | 22 | var router = new LightRouter(); 23 | 24 | }); 25 | 26 | 27 | it('should be able to initialise routes and root url', function() { 28 | 29 | var router = new LightRouter({ 30 | routes: { 31 | 'route1/test': function() { }, 32 | 'route2/test2': function() { } 33 | }, 34 | pathRoot: 'path/to/app' 35 | }); 36 | 37 | assert.lengthOf(router.routes, 2); 38 | assert.equal(router.pathRoot, 'path/to/app'); 39 | 40 | }); 41 | 42 | 43 | it('should default to path based routing', function() { 44 | 45 | var router = new LightRouter(); 46 | assert(router.type, 'path'); 47 | 48 | }); 49 | 50 | }); 51 | 52 | 53 | describe('function tests', function() { 54 | 55 | it('can getUrl() and remove the root', function() { 56 | 57 | var router = new LightRouter({ 58 | path: 'my/app/path/test/123', 59 | pathRoot: 'my/app/path' 60 | }); 61 | 62 | assert.equal(router.getUrl(), 'test/123'); 63 | 64 | }); 65 | 66 | 67 | it('can getUrl() a specific routing type', function() { 68 | 69 | var router = new LightRouter({ 70 | path: 'app/path/test-path', 71 | hash: 'test-hash', 72 | pathRoot: 'app/path' 73 | }); 74 | 75 | assert.equal(router.getUrl('path'), 'test-path'); 76 | assert.equal(router.getUrl('hash'), 'test-hash'); 77 | }); 78 | 79 | 80 | it('getUrl() should decode the url', function() { 81 | 82 | var router = new LightRouter({ 83 | pathRoot: 'my/app/path%20test', 84 | path: 'my/app/path%20test/articles/some%20category%20name/email@address.com' 85 | }); 86 | 87 | assert(router.getUrl(), 'my/app/path test/articles/some category name/email@address.com'); 88 | 89 | }); 90 | 91 | 92 | it('add manual routes', function(done) { 93 | 94 | var router = new LightRouter({ path: 'articles/123' }); 95 | 96 | var unmatchCallback = function() { 97 | throw('should not have called this.'); 98 | }; 99 | 100 | var matchCallback = function(params) { 101 | assert.equal(params.id, '123'); 102 | done(); 103 | }; 104 | 105 | router.add('blogs/{id}', unmatchCallback); 106 | router.add('articles/{id}', matchCallback); 107 | router.run(); 108 | 109 | }); 110 | 111 | 112 | it('can set a manual path', function() { 113 | var router = new LightRouter(); 114 | assert.equal(router.setPath('test/blah'), router); 115 | assert.equal(router.path, 'test/blah'); 116 | }); 117 | 118 | 119 | it('can set a root url', function() { 120 | var router = new LightRouter(), 121 | pathRoot = 'my/app/path'; 122 | assert.equal(router.setPathRoot(pathRoot), router); 123 | assert.equal(router.pathRoot, pathRoot); 124 | }); 125 | 126 | 127 | it('can set a hash routing type', function() { 128 | 129 | var router = new LightRouter({ 130 | hash: 'articles/456' 131 | }); 132 | 133 | assert.equal(router.setType('hash'), router); 134 | assert.equal(router.type, 'hash'); 135 | assert.equal(router.getUrl(), 'articles/456'); 136 | 137 | }); 138 | 139 | 140 | it('can set a location routing type', function() { 141 | 142 | var router = new LightRouter({ 143 | path: 'articles/456' 144 | }); 145 | 146 | router.setType('path'); 147 | assert.equal(router.type, 'path'); 148 | assert.equal(router.getUrl(), 'articles/456'); 149 | 150 | }); 151 | 152 | 153 | it('can empty all the routes', function() { 154 | 155 | var router = new LightRouter({ 156 | routes: { 157 | 'test1': function() { }, 158 | 'test2': function() { } 159 | } 160 | }); 161 | 162 | assert.lengthOf(router.routes, 2); 163 | assert(router.empty(), router); 164 | assert.lengthOf(router.routes, 0); 165 | 166 | }); 167 | 168 | 169 | }); 170 | 171 | 172 | describe('context on route', function() { 173 | 174 | it('defaults to LightRouter as context', function() { 175 | 176 | var router = new LightRouter({ 177 | path: 'test', 178 | routes: { 179 | 'test': function() { assert.instanceOf(this, LightRouter); } 180 | } 181 | }).run(); 182 | 183 | }); 184 | 185 | 186 | it('can supply a context when running route', function(done) { 187 | 188 | var router = new LightRouter({ 189 | context: { success: done }, 190 | path: 'test', 191 | routes: { 192 | 'test': function() { this.success(); } 193 | } 194 | }).run(); 195 | 196 | }); 197 | 198 | }); 199 | 200 | 201 | describe('route handler', function() { 202 | 203 | it('supports string based handler with objects', function() { 204 | 205 | var handler = { 206 | editUser: function(params) { assert.equal(params.id, 5) } 207 | }; 208 | 209 | var router1 = new LightRouter({ 210 | path: 'users/5/edit', 211 | handler: handler, 212 | routes: { 213 | 'users/{id}/edit': 'editUser' 214 | } 215 | }).run(); 216 | 217 | }); 218 | 219 | 220 | it('can supply a context when running route', function(done) { 221 | 222 | var router = new LightRouter({ 223 | context: { success: done }, 224 | path: 'test', 225 | routes: { 226 | 'test': function() { this.success(); } 227 | } 228 | }).run(); 229 | 230 | }); 231 | 232 | }); 233 | 234 | 235 | describe('general route testing', function() { 236 | 237 | 238 | it('should match index', function(done) { 239 | 240 | var router = new LightRouter({ 241 | path: 'my/app/path', 242 | pathRoot: 'my/app/path', 243 | routes: { 244 | '': function() { done(); } 245 | } 246 | }).run(); 247 | 248 | }); 249 | 250 | 251 | it('should perform exact matching of route', function(done) { 252 | 253 | var router = new LightRouter({ 254 | path: 'my/app/path/test-123/blah', 255 | pathRoot: 'my/app/path', 256 | routes: { 257 | 'test-123/bla': function() { 258 | throw('should not have called this'); 259 | }, 260 | 'est-123/blah': function() { 261 | throw('should not have called this'); 262 | }, 263 | 'test-123/blah': function() { done(); } 264 | } 265 | }).run(); 266 | 267 | }); 268 | 269 | 270 | it('should be case sensitive by default', function(done) { 271 | 272 | var router = new LightRouter({ 273 | path: 'my/app/path/testCAsE/3', 274 | pathRoot: 'my/app/path', 275 | routes: { 276 | 'testcase': function() { 277 | throw('should not have called this'); 278 | }, 279 | 'testCAsE/(\\d+)$': function() { done(); } 280 | } 281 | }).run(); 282 | 283 | }); 284 | 285 | 286 | it('should allow adding of manual route regex with case insensitivity', function(done) { 287 | 288 | var router = new LightRouter({ 289 | path: 'my/app/path/testCAsE/3', 290 | pathRoot: 'my/app/path' 291 | }); 292 | 293 | router 294 | .add(/^testcase/i, function() { done(); }) 295 | .add(/testcasE/, function() { 296 | throw('should not have called this'); 297 | }) 298 | .run(); 299 | }); 300 | 301 | 302 | it('should allow manually matching a route', function() { 303 | 304 | var router = new LightRouter({ 305 | path: 'admin/users/12' 306 | }); 307 | 308 | var matched = 0; 309 | 310 | router.match('admin/users/{id}', function(params) { 311 | assert.equal(params.id, 12); 312 | matched++; 313 | }); 314 | 315 | router.match('admin/.*', function() { matched++; }); 316 | 317 | router.match('test', function() { 318 | matched++; 319 | }); 320 | 321 | assert.equal(matched, 2); 322 | }); 323 | 324 | }); 325 | 326 | 327 | describe('route parameters', function() { 328 | 329 | it('should match alpha, digit, underscore or dash for named parameters by default', function(done) { 330 | 331 | var router = new LightRouter({ 332 | path: 'articles/some-Named-slug-23_2011!apple', 333 | routes: { 334 | 'articles/{slug}!{fruit}': function(params) { 335 | assert.equal(params.slug, 'some-Named-slug-23_2011'); 336 | assert.equal(params.fruit, 'apple'); 337 | done(); 338 | } 339 | } 340 | }).run(); 341 | 342 | }); 343 | 344 | 345 | it('should match path seperated route parameters', function(done) { 346 | 347 | var router = new LightRouter({ 348 | pathRoot: 'my/app/path-test', 349 | path: 'my/app/path-test/articles/some-category_NAME/4463', 350 | routes: { 351 | 'articles/{category}/{id}': function(params) { 352 | assert.equal(params.category, 'some-category_NAME'); 353 | assert.equal(params.id, 4463); 354 | done(); 355 | } 356 | } 357 | }).run(); 358 | 359 | }); 360 | 361 | 362 | it('should match in brackets', function(done) { 363 | 364 | var router = new LightRouter({ 365 | pathRoot: 'my/app/path-test', 366 | path: 'my/app/path-test/articles/56/edit', 367 | routes: { 368 | 'articles/(?:create|{id}/edit)': function(params) { 369 | assert.equal(params.id, 56); 370 | done(); 371 | } 372 | } 373 | }).run(); 374 | 375 | }); 376 | 377 | it('should match route added with no params with empty object', function(done) { 378 | 379 | var router = new LightRouter({ 380 | pathRoot: 'my/app/path%20test', 381 | path: 'my/app/path%20test/articles/create', 382 | routes: { 383 | 'articles\/(?:create|edit\/(?:\d+))': function(params) { 384 | assert.isObject(params); 385 | assert.equal(Object.keys(params), 0); 386 | done(); 387 | } 388 | } 389 | }).run(); 390 | }); 391 | 392 | 393 | it('should match route manually added with no params with empty object', function(done) { 394 | 395 | var router = new LightRouter({ 396 | pathRoot: 'my/app/path%20test', 397 | path: 'my/app/path%20test/articles/create' 398 | }); 399 | router.add(/articles\/(?:create|edit\/(?:\d+))/, function(params) { 400 | assert.isObject(params); 401 | assert.equal(Object.keys(params), 0); 402 | done(); 403 | }) 404 | .run(); 405 | 406 | }); 407 | 408 | 409 | it('should match route manually added regex with params', function(done) { 410 | 411 | var router = new LightRouter({ 412 | pathRoot: 'my/app/path%20test', 413 | path: 'my/app/path%20test/articles/edit/789/900' 414 | }) 415 | 416 | router.add(/articles\/(?:create|edit\/(\d+))\/(\d+)/, function(params) { 417 | assert.isObject(params); 418 | assert.equal(params[0], 789); 419 | assert.equal(params[1], 900); 420 | done(); 421 | }) 422 | .run(); 423 | }); 424 | 425 | 426 | it('stops when it matches first route', function() { 427 | 428 | var matched = 0; 429 | var router = new LightRouter({ 430 | path: 'ab', 431 | routes: { 432 | 'a.': function() { matched++; }, 433 | 'ab': function() { matched++; } 434 | } 435 | }).run(); 436 | 437 | assert.equal(matched, 1); 438 | }); 439 | 440 | 441 | it('passes matched route on run', function() { 442 | 443 | var router = new LightRouter({ 444 | path: 'test1', 445 | routes: { 446 | 'test0': function() { }, 447 | 'test1': function() { } 448 | } 449 | }); 450 | 451 | var matchedRoute = router.run(); 452 | assert.instanceOf(matchedRoute, router.Route); 453 | }); 454 | 455 | }); 456 | 457 | describe('hash route testing specifics', function() { 458 | 459 | it('should not attempt to replace path root url when hash routing', function() { 460 | 461 | var router = new LightRouter({ 462 | hash: 'articles/123', 463 | type: 'hash', 464 | pathRoot: 'articles' 465 | }); 466 | 467 | assert.equal(router.getUrl(), 'articles/123'); 468 | 469 | }); 470 | 471 | }); 472 | 473 | })(); -------------------------------------------------------------------------------- /tests/browser/mocha.js: -------------------------------------------------------------------------------- 1 | ;(function(){ 2 | 3 | // CommonJS require() 4 | 5 | function require(p){ 6 | var path = require.resolve(p) 7 | , mod = require.modules[path]; 8 | if (!mod) throw new Error('failed to require "' + p + '"'); 9 | if (!mod.exports) { 10 | mod.exports = {}; 11 | mod.call(mod.exports, mod, mod.exports, require.relative(path)); 12 | } 13 | return mod.exports; 14 | } 15 | 16 | require.modules = {}; 17 | 18 | require.resolve = function (path){ 19 | var orig = path 20 | , reg = path + '.js' 21 | , index = path + '/index.js'; 22 | return require.modules[reg] && reg 23 | || require.modules[index] && index 24 | || orig; 25 | }; 26 | 27 | require.register = function (path, fn){ 28 | require.modules[path] = fn; 29 | }; 30 | 31 | require.relative = function (parent) { 32 | return function(p){ 33 | if ('.' != p.charAt(0)) return require(p); 34 | 35 | var path = parent.split('/') 36 | , segs = p.split('/'); 37 | path.pop(); 38 | 39 | for (var i = 0; i < segs.length; i++) { 40 | var seg = segs[i]; 41 | if ('..' == seg) path.pop(); 42 | else if ('.' != seg) path.push(seg); 43 | } 44 | 45 | return require(path.join('/')); 46 | }; 47 | }; 48 | 49 | 50 | require.register("browser/debug.js", function(module, exports, require){ 51 | 52 | module.exports = function(type){ 53 | return function(){ 54 | } 55 | }; 56 | 57 | }); // module: browser/debug.js 58 | 59 | require.register("browser/diff.js", function(module, exports, require){ 60 | /* See license.txt for terms of usage */ 61 | 62 | /* 63 | * Text diff implementation. 64 | * 65 | * This library supports the following APIS: 66 | * JsDiff.diffChars: Character by character diff 67 | * JsDiff.diffWords: Word (as defined by \b regex) diff which ignores whitespace 68 | * JsDiff.diffLines: Line based diff 69 | * 70 | * JsDiff.diffCss: Diff targeted at CSS content 71 | * 72 | * These methods are based on the implementation proposed in 73 | * "An O(ND) Difference Algorithm and its Variations" (Myers, 1986). 74 | * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927 75 | */ 76 | var JsDiff = (function() { 77 | function clonePath(path) { 78 | return { newPos: path.newPos, components: path.components.slice(0) }; 79 | } 80 | function removeEmpty(array) { 81 | var ret = []; 82 | for (var i = 0; i < array.length; i++) { 83 | if (array[i]) { 84 | ret.push(array[i]); 85 | } 86 | } 87 | return ret; 88 | } 89 | function escapeHTML(s) { 90 | var n = s; 91 | n = n.replace(/&/g, "&"); 92 | n = n.replace(//g, ">"); 94 | n = n.replace(/"/g, """); 95 | 96 | return n; 97 | } 98 | 99 | 100 | var fbDiff = function(ignoreWhitespace) { 101 | this.ignoreWhitespace = ignoreWhitespace; 102 | }; 103 | fbDiff.prototype = { 104 | diff: function(oldString, newString) { 105 | // Handle the identity case (this is due to unrolling editLength == 0 106 | if (newString == oldString) { 107 | return [{ value: newString }]; 108 | } 109 | if (!newString) { 110 | return [{ value: oldString, removed: true }]; 111 | } 112 | if (!oldString) { 113 | return [{ value: newString, added: true }]; 114 | } 115 | 116 | newString = this.tokenize(newString); 117 | oldString = this.tokenize(oldString); 118 | 119 | var newLen = newString.length, oldLen = oldString.length; 120 | var maxEditLength = newLen + oldLen; 121 | var bestPath = [{ newPos: -1, components: [] }]; 122 | 123 | // Seed editLength = 0 124 | var oldPos = this.extractCommon(bestPath[0], newString, oldString, 0); 125 | if (bestPath[0].newPos+1 >= newLen && oldPos+1 >= oldLen) { 126 | return bestPath[0].components; 127 | } 128 | 129 | for (var editLength = 1; editLength <= maxEditLength; editLength++) { 130 | for (var diagonalPath = -1*editLength; diagonalPath <= editLength; diagonalPath+=2) { 131 | var basePath; 132 | var addPath = bestPath[diagonalPath-1], 133 | removePath = bestPath[diagonalPath+1]; 134 | oldPos = (removePath ? removePath.newPos : 0) - diagonalPath; 135 | if (addPath) { 136 | // No one else is going to attempt to use this value, clear it 137 | bestPath[diagonalPath-1] = undefined; 138 | } 139 | 140 | var canAdd = addPath && addPath.newPos+1 < newLen; 141 | var canRemove = removePath && 0 <= oldPos && oldPos < oldLen; 142 | if (!canAdd && !canRemove) { 143 | bestPath[diagonalPath] = undefined; 144 | continue; 145 | } 146 | 147 | // Select the diagonal that we want to branch from. We select the prior 148 | // path whose position in the new string is the farthest from the origin 149 | // and does not pass the bounds of the diff graph 150 | if (!canAdd || (canRemove && addPath.newPos < removePath.newPos)) { 151 | basePath = clonePath(removePath); 152 | this.pushComponent(basePath.components, oldString[oldPos], undefined, true); 153 | } else { 154 | basePath = clonePath(addPath); 155 | basePath.newPos++; 156 | this.pushComponent(basePath.components, newString[basePath.newPos], true, undefined); 157 | } 158 | 159 | var oldPos = this.extractCommon(basePath, newString, oldString, diagonalPath); 160 | 161 | if (basePath.newPos+1 >= newLen && oldPos+1 >= oldLen) { 162 | return basePath.components; 163 | } else { 164 | bestPath[diagonalPath] = basePath; 165 | } 166 | } 167 | } 168 | }, 169 | 170 | pushComponent: function(components, value, added, removed) { 171 | var last = components[components.length-1]; 172 | if (last && last.added === added && last.removed === removed) { 173 | // We need to clone here as the component clone operation is just 174 | // as shallow array clone 175 | components[components.length-1] = 176 | {value: this.join(last.value, value), added: added, removed: removed }; 177 | } else { 178 | components.push({value: value, added: added, removed: removed }); 179 | } 180 | }, 181 | extractCommon: function(basePath, newString, oldString, diagonalPath) { 182 | var newLen = newString.length, 183 | oldLen = oldString.length, 184 | newPos = basePath.newPos, 185 | oldPos = newPos - diagonalPath; 186 | while (newPos+1 < newLen && oldPos+1 < oldLen && this.equals(newString[newPos+1], oldString[oldPos+1])) { 187 | newPos++; 188 | oldPos++; 189 | 190 | this.pushComponent(basePath.components, newString[newPos], undefined, undefined); 191 | } 192 | basePath.newPos = newPos; 193 | return oldPos; 194 | }, 195 | 196 | equals: function(left, right) { 197 | var reWhitespace = /\S/; 198 | if (this.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right)) { 199 | return true; 200 | } else { 201 | return left == right; 202 | } 203 | }, 204 | join: function(left, right) { 205 | return left + right; 206 | }, 207 | tokenize: function(value) { 208 | return value; 209 | } 210 | }; 211 | 212 | var CharDiff = new fbDiff(); 213 | 214 | var WordDiff = new fbDiff(true); 215 | WordDiff.tokenize = function(value) { 216 | return removeEmpty(value.split(/(\s+|\b)/)); 217 | }; 218 | 219 | var CssDiff = new fbDiff(true); 220 | CssDiff.tokenize = function(value) { 221 | return removeEmpty(value.split(/([{}:;,]|\s+)/)); 222 | }; 223 | 224 | var LineDiff = new fbDiff(); 225 | LineDiff.tokenize = function(value) { 226 | return value.split(/^/m); 227 | }; 228 | 229 | return { 230 | diffChars: function(oldStr, newStr) { return CharDiff.diff(oldStr, newStr); }, 231 | diffWords: function(oldStr, newStr) { return WordDiff.diff(oldStr, newStr); }, 232 | diffLines: function(oldStr, newStr) { return LineDiff.diff(oldStr, newStr); }, 233 | 234 | diffCss: function(oldStr, newStr) { return CssDiff.diff(oldStr, newStr); }, 235 | 236 | createPatch: function(fileName, oldStr, newStr, oldHeader, newHeader) { 237 | var ret = []; 238 | 239 | ret.push("Index: " + fileName); 240 | ret.push("==================================================================="); 241 | ret.push("--- " + fileName + (typeof oldHeader === "undefined" ? "" : "\t" + oldHeader)); 242 | ret.push("+++ " + fileName + (typeof newHeader === "undefined" ? "" : "\t" + newHeader)); 243 | 244 | var diff = LineDiff.diff(oldStr, newStr); 245 | if (!diff[diff.length-1].value) { 246 | diff.pop(); // Remove trailing newline add 247 | } 248 | diff.push({value: "", lines: []}); // Append an empty value to make cleanup easier 249 | 250 | function contextLines(lines) { 251 | return lines.map(function(entry) { return ' ' + entry; }); 252 | } 253 | function eofNL(curRange, i, current) { 254 | var last = diff[diff.length-2], 255 | isLast = i === diff.length-2, 256 | isLastOfType = i === diff.length-3 && (current.added === !last.added || current.removed === !last.removed); 257 | 258 | // Figure out if this is the last line for the given file and missing NL 259 | if (!/\n$/.test(current.value) && (isLast || isLastOfType)) { 260 | curRange.push('\\ No newline at end of file'); 261 | } 262 | } 263 | 264 | var oldRangeStart = 0, newRangeStart = 0, curRange = [], 265 | oldLine = 1, newLine = 1; 266 | for (var i = 0; i < diff.length; i++) { 267 | var current = diff[i], 268 | lines = current.lines || current.value.replace(/\n$/, "").split("\n"); 269 | current.lines = lines; 270 | 271 | if (current.added || current.removed) { 272 | if (!oldRangeStart) { 273 | var prev = diff[i-1]; 274 | oldRangeStart = oldLine; 275 | newRangeStart = newLine; 276 | 277 | if (prev) { 278 | curRange = contextLines(prev.lines.slice(-4)); 279 | oldRangeStart -= curRange.length; 280 | newRangeStart -= curRange.length; 281 | } 282 | } 283 | curRange.push.apply(curRange, lines.map(function(entry) { return (current.added?"+":"-") + entry; })); 284 | eofNL(curRange, i, current); 285 | 286 | if (current.added) { 287 | newLine += lines.length; 288 | } else { 289 | oldLine += lines.length; 290 | } 291 | } else { 292 | if (oldRangeStart) { 293 | // Close out any changes that have been output (or join overlapping) 294 | if (lines.length <= 8 && i < diff.length-2) { 295 | // Overlapping 296 | curRange.push.apply(curRange, contextLines(lines)); 297 | } else { 298 | // end the range and output 299 | var contextSize = Math.min(lines.length, 4); 300 | ret.push( 301 | "@@ -" + oldRangeStart + "," + (oldLine-oldRangeStart+contextSize) 302 | + " +" + newRangeStart + "," + (newLine-newRangeStart+contextSize) 303 | + " @@"); 304 | ret.push.apply(ret, curRange); 305 | ret.push.apply(ret, contextLines(lines.slice(0, contextSize))); 306 | if (lines.length <= 4) { 307 | eofNL(ret, i, current); 308 | } 309 | 310 | oldRangeStart = 0; newRangeStart = 0; curRange = []; 311 | } 312 | } 313 | oldLine += lines.length; 314 | newLine += lines.length; 315 | } 316 | } 317 | 318 | return ret.join('\n') + '\n'; 319 | }, 320 | 321 | convertChangesToXML: function(changes){ 322 | var ret = []; 323 | for ( var i = 0; i < changes.length; i++) { 324 | var change = changes[i]; 325 | if (change.added) { 326 | ret.push(""); 327 | } else if (change.removed) { 328 | ret.push(""); 329 | } 330 | 331 | ret.push(escapeHTML(change.value)); 332 | 333 | if (change.added) { 334 | ret.push(""); 335 | } else if (change.removed) { 336 | ret.push(""); 337 | } 338 | } 339 | return ret.join(""); 340 | } 341 | }; 342 | })(); 343 | 344 | if (typeof module !== "undefined") { 345 | module.exports = JsDiff; 346 | } 347 | 348 | }); // module: browser/diff.js 349 | 350 | require.register("browser/events.js", function(module, exports, require){ 351 | 352 | /** 353 | * Module exports. 354 | */ 355 | 356 | exports.EventEmitter = EventEmitter; 357 | 358 | /** 359 | * Check if `obj` is an array. 360 | */ 361 | 362 | function isArray(obj) { 363 | return '[object Array]' == {}.toString.call(obj); 364 | } 365 | 366 | /** 367 | * Event emitter constructor. 368 | * 369 | * @api public 370 | */ 371 | 372 | function EventEmitter(){}; 373 | 374 | /** 375 | * Adds a listener. 376 | * 377 | * @api public 378 | */ 379 | 380 | EventEmitter.prototype.on = function (name, fn) { 381 | if (!this.$events) { 382 | this.$events = {}; 383 | } 384 | 385 | if (!this.$events[name]) { 386 | this.$events[name] = fn; 387 | } else if (isArray(this.$events[name])) { 388 | this.$events[name].push(fn); 389 | } else { 390 | this.$events[name] = [this.$events[name], fn]; 391 | } 392 | 393 | return this; 394 | }; 395 | 396 | EventEmitter.prototype.addListener = EventEmitter.prototype.on; 397 | 398 | /** 399 | * Adds a volatile listener. 400 | * 401 | * @api public 402 | */ 403 | 404 | EventEmitter.prototype.once = function (name, fn) { 405 | var self = this; 406 | 407 | function on () { 408 | self.removeListener(name, on); 409 | fn.apply(this, arguments); 410 | }; 411 | 412 | on.listener = fn; 413 | this.on(name, on); 414 | 415 | return this; 416 | }; 417 | 418 | /** 419 | * Removes a listener. 420 | * 421 | * @api public 422 | */ 423 | 424 | EventEmitter.prototype.removeListener = function (name, fn) { 425 | if (this.$events && this.$events[name]) { 426 | var list = this.$events[name]; 427 | 428 | if (isArray(list)) { 429 | var pos = -1; 430 | 431 | for (var i = 0, l = list.length; i < l; i++) { 432 | if (list[i] === fn || (list[i].listener && list[i].listener === fn)) { 433 | pos = i; 434 | break; 435 | } 436 | } 437 | 438 | if (pos < 0) { 439 | return this; 440 | } 441 | 442 | list.splice(pos, 1); 443 | 444 | if (!list.length) { 445 | delete this.$events[name]; 446 | } 447 | } else if (list === fn || (list.listener && list.listener === fn)) { 448 | delete this.$events[name]; 449 | } 450 | } 451 | 452 | return this; 453 | }; 454 | 455 | /** 456 | * Removes all listeners for an event. 457 | * 458 | * @api public 459 | */ 460 | 461 | EventEmitter.prototype.removeAllListeners = function (name) { 462 | if (name === undefined) { 463 | this.$events = {}; 464 | return this; 465 | } 466 | 467 | if (this.$events && this.$events[name]) { 468 | this.$events[name] = null; 469 | } 470 | 471 | return this; 472 | }; 473 | 474 | /** 475 | * Gets all listeners for a certain event. 476 | * 477 | * @api public 478 | */ 479 | 480 | EventEmitter.prototype.listeners = function (name) { 481 | if (!this.$events) { 482 | this.$events = {}; 483 | } 484 | 485 | if (!this.$events[name]) { 486 | this.$events[name] = []; 487 | } 488 | 489 | if (!isArray(this.$events[name])) { 490 | this.$events[name] = [this.$events[name]]; 491 | } 492 | 493 | return this.$events[name]; 494 | }; 495 | 496 | /** 497 | * Emits an event. 498 | * 499 | * @api public 500 | */ 501 | 502 | EventEmitter.prototype.emit = function (name) { 503 | if (!this.$events) { 504 | return false; 505 | } 506 | 507 | var handler = this.$events[name]; 508 | 509 | if (!handler) { 510 | return false; 511 | } 512 | 513 | var args = [].slice.call(arguments, 1); 514 | 515 | if ('function' == typeof handler) { 516 | handler.apply(this, args); 517 | } else if (isArray(handler)) { 518 | var listeners = handler.slice(); 519 | 520 | for (var i = 0, l = listeners.length; i < l; i++) { 521 | listeners[i].apply(this, args); 522 | } 523 | } else { 524 | return false; 525 | } 526 | 527 | return true; 528 | }; 529 | }); // module: browser/events.js 530 | 531 | require.register("browser/fs.js", function(module, exports, require){ 532 | 533 | }); // module: browser/fs.js 534 | 535 | require.register("browser/path.js", function(module, exports, require){ 536 | 537 | }); // module: browser/path.js 538 | 539 | require.register("browser/progress.js", function(module, exports, require){ 540 | 541 | /** 542 | * Expose `Progress`. 543 | */ 544 | 545 | module.exports = Progress; 546 | 547 | /** 548 | * Initialize a new `Progress` indicator. 549 | */ 550 | 551 | function Progress() { 552 | this.percent = 0; 553 | this.size(0); 554 | this.fontSize(11); 555 | this.font('helvetica, arial, sans-serif'); 556 | } 557 | 558 | /** 559 | * Set progress size to `n`. 560 | * 561 | * @param {Number} n 562 | * @return {Progress} for chaining 563 | * @api public 564 | */ 565 | 566 | Progress.prototype.size = function(n){ 567 | this._size = n; 568 | return this; 569 | }; 570 | 571 | /** 572 | * Set text to `str`. 573 | * 574 | * @param {String} str 575 | * @return {Progress} for chaining 576 | * @api public 577 | */ 578 | 579 | Progress.prototype.text = function(str){ 580 | this._text = str; 581 | return this; 582 | }; 583 | 584 | /** 585 | * Set font size to `n`. 586 | * 587 | * @param {Number} n 588 | * @return {Progress} for chaining 589 | * @api public 590 | */ 591 | 592 | Progress.prototype.fontSize = function(n){ 593 | this._fontSize = n; 594 | return this; 595 | }; 596 | 597 | /** 598 | * Set font `family`. 599 | * 600 | * @param {String} family 601 | * @return {Progress} for chaining 602 | */ 603 | 604 | Progress.prototype.font = function(family){ 605 | this._font = family; 606 | return this; 607 | }; 608 | 609 | /** 610 | * Update percentage to `n`. 611 | * 612 | * @param {Number} n 613 | * @return {Progress} for chaining 614 | */ 615 | 616 | Progress.prototype.update = function(n){ 617 | this.percent = n; 618 | return this; 619 | }; 620 | 621 | /** 622 | * Draw on `ctx`. 623 | * 624 | * @param {CanvasRenderingContext2d} ctx 625 | * @return {Progress} for chaining 626 | */ 627 | 628 | Progress.prototype.draw = function(ctx){ 629 | var percent = Math.min(this.percent, 100) 630 | , size = this._size 631 | , half = size / 2 632 | , x = half 633 | , y = half 634 | , rad = half - 1 635 | , fontSize = this._fontSize; 636 | 637 | ctx.font = fontSize + 'px ' + this._font; 638 | 639 | var angle = Math.PI * 2 * (percent / 100); 640 | ctx.clearRect(0, 0, size, size); 641 | 642 | // outer circle 643 | ctx.strokeStyle = '#9f9f9f'; 644 | ctx.beginPath(); 645 | ctx.arc(x, y, rad, 0, angle, false); 646 | ctx.stroke(); 647 | 648 | // inner circle 649 | ctx.strokeStyle = '#eee'; 650 | ctx.beginPath(); 651 | ctx.arc(x, y, rad - 1, 0, angle, true); 652 | ctx.stroke(); 653 | 654 | // text 655 | var text = this._text || (percent | 0) + '%' 656 | , w = ctx.measureText(text).width; 657 | 658 | ctx.fillText( 659 | text 660 | , x - w / 2 + 1 661 | , y + fontSize / 2 - 1); 662 | 663 | return this; 664 | }; 665 | 666 | }); // module: browser/progress.js 667 | 668 | require.register("browser/tty.js", function(module, exports, require){ 669 | 670 | exports.isatty = function(){ 671 | return true; 672 | }; 673 | 674 | exports.getWindowSize = function(){ 675 | return [window.innerHeight, window.innerWidth]; 676 | }; 677 | }); // module: browser/tty.js 678 | 679 | require.register("context.js", function(module, exports, require){ 680 | 681 | /** 682 | * Expose `Context`. 683 | */ 684 | 685 | module.exports = Context; 686 | 687 | /** 688 | * Initialize a new `Context`. 689 | * 690 | * @api private 691 | */ 692 | 693 | function Context(){} 694 | 695 | /** 696 | * Set or get the context `Runnable` to `runnable`. 697 | * 698 | * @param {Runnable} runnable 699 | * @return {Context} 700 | * @api private 701 | */ 702 | 703 | Context.prototype.runnable = function(runnable){ 704 | if (0 == arguments.length) return this._runnable; 705 | this.test = this._runnable = runnable; 706 | return this; 707 | }; 708 | 709 | /** 710 | * Set test timeout `ms`. 711 | * 712 | * @param {Number} ms 713 | * @return {Context} self 714 | * @api private 715 | */ 716 | 717 | Context.prototype.timeout = function(ms){ 718 | this.runnable().timeout(ms); 719 | return this; 720 | }; 721 | 722 | /** 723 | * Set test slowness threshold `ms`. 724 | * 725 | * @param {Number} ms 726 | * @return {Context} self 727 | * @api private 728 | */ 729 | 730 | Context.prototype.slow = function(ms){ 731 | this.runnable().slow(ms); 732 | return this; 733 | }; 734 | 735 | /** 736 | * Inspect the context void of `._runnable`. 737 | * 738 | * @return {String} 739 | * @api private 740 | */ 741 | 742 | Context.prototype.inspect = function(){ 743 | return JSON.stringify(this, function(key, val){ 744 | if ('_runnable' == key) return; 745 | if ('test' == key) return; 746 | return val; 747 | }, 2); 748 | }; 749 | 750 | }); // module: context.js 751 | 752 | require.register("hook.js", function(module, exports, require){ 753 | 754 | /** 755 | * Module dependencies. 756 | */ 757 | 758 | var Runnable = require('./runnable'); 759 | 760 | /** 761 | * Expose `Hook`. 762 | */ 763 | 764 | module.exports = Hook; 765 | 766 | /** 767 | * Initialize a new `Hook` with the given `title` and callback `fn`. 768 | * 769 | * @param {String} title 770 | * @param {Function} fn 771 | * @api private 772 | */ 773 | 774 | function Hook(title, fn) { 775 | Runnable.call(this, title, fn); 776 | this.type = 'hook'; 777 | } 778 | 779 | /** 780 | * Inherit from `Runnable.prototype`. 781 | */ 782 | 783 | function F(){}; 784 | F.prototype = Runnable.prototype; 785 | Hook.prototype = new F; 786 | Hook.prototype.constructor = Hook; 787 | 788 | 789 | /** 790 | * Get or set the test `err`. 791 | * 792 | * @param {Error} err 793 | * @return {Error} 794 | * @api public 795 | */ 796 | 797 | Hook.prototype.error = function(err){ 798 | if (0 == arguments.length) { 799 | var err = this._error; 800 | this._error = null; 801 | return err; 802 | } 803 | 804 | this._error = err; 805 | }; 806 | 807 | }); // module: hook.js 808 | 809 | require.register("interfaces/bdd.js", function(module, exports, require){ 810 | 811 | /** 812 | * Module dependencies. 813 | */ 814 | 815 | var Suite = require('../suite') 816 | , Test = require('../test'); 817 | 818 | /** 819 | * BDD-style interface: 820 | * 821 | * describe('Array', function(){ 822 | * describe('#indexOf()', function(){ 823 | * it('should return -1 when not present', function(){ 824 | * 825 | * }); 826 | * 827 | * it('should return the index when present', function(){ 828 | * 829 | * }); 830 | * }); 831 | * }); 832 | * 833 | */ 834 | 835 | module.exports = function(suite){ 836 | var suites = [suite]; 837 | 838 | suite.on('pre-require', function(context, file, mocha){ 839 | 840 | /** 841 | * Execute before running tests. 842 | */ 843 | 844 | context.before = function(fn){ 845 | suites[0].beforeAll(fn); 846 | }; 847 | 848 | /** 849 | * Execute after running tests. 850 | */ 851 | 852 | context.after = function(fn){ 853 | suites[0].afterAll(fn); 854 | }; 855 | 856 | /** 857 | * Execute before each test case. 858 | */ 859 | 860 | context.beforeEach = function(fn){ 861 | suites[0].beforeEach(fn); 862 | }; 863 | 864 | /** 865 | * Execute after each test case. 866 | */ 867 | 868 | context.afterEach = function(fn){ 869 | suites[0].afterEach(fn); 870 | }; 871 | 872 | /** 873 | * Describe a "suite" with the given `title` 874 | * and callback `fn` containing nested suites 875 | * and/or tests. 876 | */ 877 | 878 | context.describe = context.context = function(title, fn){ 879 | var suite = Suite.create(suites[0], title); 880 | suites.unshift(suite); 881 | fn.call(suite); 882 | suites.shift(); 883 | return suite; 884 | }; 885 | 886 | /** 887 | * Pending describe. 888 | */ 889 | 890 | context.xdescribe = 891 | context.xcontext = 892 | context.describe.skip = function(title, fn){ 893 | var suite = Suite.create(suites[0], title); 894 | suite.pending = true; 895 | suites.unshift(suite); 896 | fn.call(suite); 897 | suites.shift(); 898 | }; 899 | 900 | /** 901 | * Exclusive suite. 902 | */ 903 | 904 | context.describe.only = function(title, fn){ 905 | var suite = context.describe(title, fn); 906 | mocha.grep(suite.fullTitle()); 907 | }; 908 | 909 | /** 910 | * Describe a specification or test-case 911 | * with the given `title` and callback `fn` 912 | * acting as a thunk. 913 | */ 914 | 915 | context.it = context.specify = function(title, fn){ 916 | var suite = suites[0]; 917 | if (suite.pending) var fn = null; 918 | var test = new Test(title, fn); 919 | suite.addTest(test); 920 | return test; 921 | }; 922 | 923 | /** 924 | * Exclusive test-case. 925 | */ 926 | 927 | context.it.only = function(title, fn){ 928 | var test = context.it(title, fn); 929 | mocha.grep(test.fullTitle()); 930 | }; 931 | 932 | /** 933 | * Pending test case. 934 | */ 935 | 936 | context.xit = 937 | context.xspecify = 938 | context.it.skip = function(title){ 939 | context.it(title); 940 | }; 941 | }); 942 | }; 943 | 944 | }); // module: interfaces/bdd.js 945 | 946 | require.register("interfaces/exports.js", function(module, exports, require){ 947 | 948 | /** 949 | * Module dependencies. 950 | */ 951 | 952 | var Suite = require('../suite') 953 | , Test = require('../test'); 954 | 955 | /** 956 | * TDD-style interface: 957 | * 958 | * exports.Array = { 959 | * '#indexOf()': { 960 | * 'should return -1 when the value is not present': function(){ 961 | * 962 | * }, 963 | * 964 | * 'should return the correct index when the value is present': function(){ 965 | * 966 | * } 967 | * } 968 | * }; 969 | * 970 | */ 971 | 972 | module.exports = function(suite){ 973 | var suites = [suite]; 974 | 975 | suite.on('require', visit); 976 | 977 | function visit(obj) { 978 | var suite; 979 | for (var key in obj) { 980 | if ('function' == typeof obj[key]) { 981 | var fn = obj[key]; 982 | switch (key) { 983 | case 'before': 984 | suites[0].beforeAll(fn); 985 | break; 986 | case 'after': 987 | suites[0].afterAll(fn); 988 | break; 989 | case 'beforeEach': 990 | suites[0].beforeEach(fn); 991 | break; 992 | case 'afterEach': 993 | suites[0].afterEach(fn); 994 | break; 995 | default: 996 | suites[0].addTest(new Test(key, fn)); 997 | } 998 | } else { 999 | var suite = Suite.create(suites[0], key); 1000 | suites.unshift(suite); 1001 | visit(obj[key]); 1002 | suites.shift(); 1003 | } 1004 | } 1005 | } 1006 | }; 1007 | 1008 | }); // module: interfaces/exports.js 1009 | 1010 | require.register("interfaces/index.js", function(module, exports, require){ 1011 | 1012 | exports.bdd = require('./bdd'); 1013 | exports.tdd = require('./tdd'); 1014 | exports.qunit = require('./qunit'); 1015 | exports.exports = require('./exports'); 1016 | 1017 | }); // module: interfaces/index.js 1018 | 1019 | require.register("interfaces/qunit.js", function(module, exports, require){ 1020 | 1021 | /** 1022 | * Module dependencies. 1023 | */ 1024 | 1025 | var Suite = require('../suite') 1026 | , Test = require('../test'); 1027 | 1028 | /** 1029 | * QUnit-style interface: 1030 | * 1031 | * suite('Array'); 1032 | * 1033 | * test('#length', function(){ 1034 | * var arr = [1,2,3]; 1035 | * ok(arr.length == 3); 1036 | * }); 1037 | * 1038 | * test('#indexOf()', function(){ 1039 | * var arr = [1,2,3]; 1040 | * ok(arr.indexOf(1) == 0); 1041 | * ok(arr.indexOf(2) == 1); 1042 | * ok(arr.indexOf(3) == 2); 1043 | * }); 1044 | * 1045 | * suite('String'); 1046 | * 1047 | * test('#length', function(){ 1048 | * ok('foo'.length == 3); 1049 | * }); 1050 | * 1051 | */ 1052 | 1053 | module.exports = function(suite){ 1054 | var suites = [suite]; 1055 | 1056 | suite.on('pre-require', function(context){ 1057 | 1058 | /** 1059 | * Execute before running tests. 1060 | */ 1061 | 1062 | context.before = function(fn){ 1063 | suites[0].beforeAll(fn); 1064 | }; 1065 | 1066 | /** 1067 | * Execute after running tests. 1068 | */ 1069 | 1070 | context.after = function(fn){ 1071 | suites[0].afterAll(fn); 1072 | }; 1073 | 1074 | /** 1075 | * Execute before each test case. 1076 | */ 1077 | 1078 | context.beforeEach = function(fn){ 1079 | suites[0].beforeEach(fn); 1080 | }; 1081 | 1082 | /** 1083 | * Execute after each test case. 1084 | */ 1085 | 1086 | context.afterEach = function(fn){ 1087 | suites[0].afterEach(fn); 1088 | }; 1089 | 1090 | /** 1091 | * Describe a "suite" with the given `title`. 1092 | */ 1093 | 1094 | context.suite = function(title){ 1095 | if (suites.length > 1) suites.shift(); 1096 | var suite = Suite.create(suites[0], title); 1097 | suites.unshift(suite); 1098 | }; 1099 | 1100 | /** 1101 | * Describe a specification or test-case 1102 | * with the given `title` and callback `fn` 1103 | * acting as a thunk. 1104 | */ 1105 | 1106 | context.test = function(title, fn){ 1107 | suites[0].addTest(new Test(title, fn)); 1108 | }; 1109 | }); 1110 | }; 1111 | 1112 | }); // module: interfaces/qunit.js 1113 | 1114 | require.register("interfaces/tdd.js", function(module, exports, require){ 1115 | 1116 | /** 1117 | * Module dependencies. 1118 | */ 1119 | 1120 | var Suite = require('../suite') 1121 | , Test = require('../test'); 1122 | 1123 | /** 1124 | * TDD-style interface: 1125 | * 1126 | * suite('Array', function(){ 1127 | * suite('#indexOf()', function(){ 1128 | * suiteSetup(function(){ 1129 | * 1130 | * }); 1131 | * 1132 | * test('should return -1 when not present', function(){ 1133 | * 1134 | * }); 1135 | * 1136 | * test('should return the index when present', function(){ 1137 | * 1138 | * }); 1139 | * 1140 | * suiteTeardown(function(){ 1141 | * 1142 | * }); 1143 | * }); 1144 | * }); 1145 | * 1146 | */ 1147 | 1148 | module.exports = function(suite){ 1149 | var suites = [suite]; 1150 | 1151 | suite.on('pre-require', function(context, file, mocha){ 1152 | 1153 | /** 1154 | * Execute before each test case. 1155 | */ 1156 | 1157 | context.setup = function(fn){ 1158 | suites[0].beforeEach(fn); 1159 | }; 1160 | 1161 | /** 1162 | * Execute after each test case. 1163 | */ 1164 | 1165 | context.teardown = function(fn){ 1166 | suites[0].afterEach(fn); 1167 | }; 1168 | 1169 | /** 1170 | * Execute before the suite. 1171 | */ 1172 | 1173 | context.suiteSetup = function(fn){ 1174 | suites[0].beforeAll(fn); 1175 | }; 1176 | 1177 | /** 1178 | * Execute after the suite. 1179 | */ 1180 | 1181 | context.suiteTeardown = function(fn){ 1182 | suites[0].afterAll(fn); 1183 | }; 1184 | 1185 | /** 1186 | * Describe a "suite" with the given `title` 1187 | * and callback `fn` containing nested suites 1188 | * and/or tests. 1189 | */ 1190 | 1191 | context.suite = function(title, fn){ 1192 | var suite = Suite.create(suites[0], title); 1193 | suites.unshift(suite); 1194 | fn.call(suite); 1195 | suites.shift(); 1196 | return suite; 1197 | }; 1198 | 1199 | /** 1200 | * Exclusive test-case. 1201 | */ 1202 | 1203 | context.suite.only = function(title, fn){ 1204 | var suite = context.suite(title, fn); 1205 | mocha.grep(suite.fullTitle()); 1206 | }; 1207 | 1208 | /** 1209 | * Describe a specification or test-case 1210 | * with the given `title` and callback `fn` 1211 | * acting as a thunk. 1212 | */ 1213 | 1214 | context.test = function(title, fn){ 1215 | var test = new Test(title, fn); 1216 | suites[0].addTest(test); 1217 | return test; 1218 | }; 1219 | 1220 | /** 1221 | * Exclusive test-case. 1222 | */ 1223 | 1224 | context.test.only = function(title, fn){ 1225 | var test = context.test(title, fn); 1226 | mocha.grep(test.fullTitle()); 1227 | }; 1228 | 1229 | /** 1230 | * Pending test case. 1231 | */ 1232 | 1233 | context.test.skip = function(title){ 1234 | context.test(title); 1235 | }; 1236 | }); 1237 | }; 1238 | 1239 | }); // module: interfaces/tdd.js 1240 | 1241 | require.register("mocha.js", function(module, exports, require){ 1242 | /*! 1243 | * mocha 1244 | * Copyright(c) 2011 TJ Holowaychuk 1245 | * MIT Licensed 1246 | */ 1247 | 1248 | /** 1249 | * Module dependencies. 1250 | */ 1251 | 1252 | var path = require('browser/path') 1253 | , utils = require('./utils'); 1254 | 1255 | /** 1256 | * Expose `Mocha`. 1257 | */ 1258 | 1259 | exports = module.exports = Mocha; 1260 | 1261 | /** 1262 | * Expose internals. 1263 | */ 1264 | 1265 | exports.utils = utils; 1266 | exports.interfaces = require('./interfaces'); 1267 | exports.reporters = require('./reporters'); 1268 | exports.Runnable = require('./runnable'); 1269 | exports.Context = require('./context'); 1270 | exports.Runner = require('./runner'); 1271 | exports.Suite = require('./suite'); 1272 | exports.Hook = require('./hook'); 1273 | exports.Test = require('./test'); 1274 | 1275 | /** 1276 | * Return image `name` path. 1277 | * 1278 | * @param {String} name 1279 | * @return {String} 1280 | * @api private 1281 | */ 1282 | 1283 | function image(name) { 1284 | return __dirname + '/../images/' + name + '.png'; 1285 | } 1286 | 1287 | /** 1288 | * Setup mocha with `options`. 1289 | * 1290 | * Options: 1291 | * 1292 | * - `ui` name "bdd", "tdd", "exports" etc 1293 | * - `reporter` reporter instance, defaults to `mocha.reporters.Dot` 1294 | * - `globals` array of accepted globals 1295 | * - `timeout` timeout in milliseconds 1296 | * - `bail` bail on the first test failure 1297 | * - `slow` milliseconds to wait before considering a test slow 1298 | * - `ignoreLeaks` ignore global leaks 1299 | * - `grep` string or regexp to filter tests with 1300 | * 1301 | * @param {Object} options 1302 | * @api public 1303 | */ 1304 | 1305 | function Mocha(options) { 1306 | options = options || {}; 1307 | this.files = []; 1308 | this.options = options; 1309 | this.grep(options.grep); 1310 | this.suite = new exports.Suite('', new exports.Context); 1311 | this.ui(options.ui); 1312 | this.bail(options.bail); 1313 | this.reporter(options.reporter); 1314 | if (options.timeout) this.timeout(options.timeout); 1315 | if (options.slow) this.slow(options.slow); 1316 | } 1317 | 1318 | /** 1319 | * Enable or disable bailing on the first failure. 1320 | * 1321 | * @param {Boolean} [bail] 1322 | * @api public 1323 | */ 1324 | 1325 | Mocha.prototype.bail = function(bail){ 1326 | if (0 == arguments.length) bail = true; 1327 | this.suite.bail(bail); 1328 | return this; 1329 | }; 1330 | 1331 | /** 1332 | * Add test `file`. 1333 | * 1334 | * @param {String} file 1335 | * @api public 1336 | */ 1337 | 1338 | Mocha.prototype.addFile = function(file){ 1339 | this.files.push(file); 1340 | return this; 1341 | }; 1342 | 1343 | /** 1344 | * Set reporter to `reporter`, defaults to "dot". 1345 | * 1346 | * @param {String|Function} reporter name or constructor 1347 | * @api public 1348 | */ 1349 | 1350 | Mocha.prototype.reporter = function(reporter){ 1351 | if ('function' == typeof reporter) { 1352 | this._reporter = reporter; 1353 | } else { 1354 | reporter = reporter || 'dot'; 1355 | try { 1356 | this._reporter = require('./reporters/' + reporter); 1357 | } catch (err) { 1358 | this._reporter = require(reporter); 1359 | } 1360 | if (!this._reporter) throw new Error('invalid reporter "' + reporter + '"'); 1361 | } 1362 | return this; 1363 | }; 1364 | 1365 | /** 1366 | * Set test UI `name`, defaults to "bdd". 1367 | * 1368 | * @param {String} bdd 1369 | * @api public 1370 | */ 1371 | 1372 | Mocha.prototype.ui = function(name){ 1373 | name = name || 'bdd'; 1374 | this._ui = exports.interfaces[name]; 1375 | if (!this._ui) throw new Error('invalid interface "' + name + '"'); 1376 | this._ui = this._ui(this.suite); 1377 | return this; 1378 | }; 1379 | 1380 | /** 1381 | * Load registered files. 1382 | * 1383 | * @api private 1384 | */ 1385 | 1386 | Mocha.prototype.loadFiles = function(fn){ 1387 | var self = this; 1388 | var suite = this.suite; 1389 | var pending = this.files.length; 1390 | this.files.forEach(function(file){ 1391 | file = path.resolve(file); 1392 | suite.emit('pre-require', global, file, self); 1393 | suite.emit('require', require(file), file, self); 1394 | suite.emit('post-require', global, file, self); 1395 | --pending || (fn && fn()); 1396 | }); 1397 | }; 1398 | 1399 | /** 1400 | * Enable growl support. 1401 | * 1402 | * @api private 1403 | */ 1404 | 1405 | Mocha.prototype._growl = function(runner, reporter) { 1406 | var notify = require('growl'); 1407 | 1408 | runner.on('end', function(){ 1409 | var stats = reporter.stats; 1410 | if (stats.failures) { 1411 | var msg = stats.failures + ' of ' + runner.total + ' tests failed'; 1412 | notify(msg, { name: 'mocha', title: 'Failed', image: image('error') }); 1413 | } else { 1414 | notify(stats.passes + ' tests passed in ' + stats.duration + 'ms', { 1415 | name: 'mocha' 1416 | , title: 'Passed' 1417 | , image: image('ok') 1418 | }); 1419 | } 1420 | }); 1421 | }; 1422 | 1423 | /** 1424 | * Add regexp to grep, if `re` is a string it is escaped. 1425 | * 1426 | * @param {RegExp|String} re 1427 | * @return {Mocha} 1428 | * @api public 1429 | */ 1430 | 1431 | Mocha.prototype.grep = function(re){ 1432 | this.options.grep = 'string' == typeof re 1433 | ? new RegExp(utils.escapeRegexp(re)) 1434 | : re; 1435 | return this; 1436 | }; 1437 | 1438 | /** 1439 | * Invert `.grep()` matches. 1440 | * 1441 | * @return {Mocha} 1442 | * @api public 1443 | */ 1444 | 1445 | Mocha.prototype.invert = function(){ 1446 | this.options.invert = true; 1447 | return this; 1448 | }; 1449 | 1450 | /** 1451 | * Ignore global leaks. 1452 | * 1453 | * @return {Mocha} 1454 | * @api public 1455 | */ 1456 | 1457 | Mocha.prototype.ignoreLeaks = function(){ 1458 | this.options.ignoreLeaks = true; 1459 | return this; 1460 | }; 1461 | 1462 | /** 1463 | * Enable global leak checking. 1464 | * 1465 | * @return {Mocha} 1466 | * @api public 1467 | */ 1468 | 1469 | Mocha.prototype.checkLeaks = function(){ 1470 | this.options.ignoreLeaks = false; 1471 | return this; 1472 | }; 1473 | 1474 | /** 1475 | * Enable growl support. 1476 | * 1477 | * @return {Mocha} 1478 | * @api public 1479 | */ 1480 | 1481 | Mocha.prototype.growl = function(){ 1482 | this.options.growl = true; 1483 | return this; 1484 | }; 1485 | 1486 | /** 1487 | * Ignore `globals` array or string. 1488 | * 1489 | * @param {Array|String} globals 1490 | * @return {Mocha} 1491 | * @api public 1492 | */ 1493 | 1494 | Mocha.prototype.globals = function(globals){ 1495 | this.options.globals = (this.options.globals || []).concat(globals); 1496 | return this; 1497 | }; 1498 | 1499 | /** 1500 | * Set the timeout in milliseconds. 1501 | * 1502 | * @param {Number} timeout 1503 | * @return {Mocha} 1504 | * @api public 1505 | */ 1506 | 1507 | Mocha.prototype.timeout = function(timeout){ 1508 | this.suite.timeout(timeout); 1509 | return this; 1510 | }; 1511 | 1512 | /** 1513 | * Set slowness threshold in milliseconds. 1514 | * 1515 | * @param {Number} slow 1516 | * @return {Mocha} 1517 | * @api public 1518 | */ 1519 | 1520 | Mocha.prototype.slow = function(slow){ 1521 | this.suite.slow(slow); 1522 | return this; 1523 | }; 1524 | 1525 | /** 1526 | * Makes all tests async (accepting a callback) 1527 | * 1528 | * @return {Mocha} 1529 | * @api public 1530 | */ 1531 | 1532 | Mocha.prototype.asyncOnly = function(){ 1533 | this.options.asyncOnly = true; 1534 | return this; 1535 | }; 1536 | 1537 | /** 1538 | * Run tests and invoke `fn()` when complete. 1539 | * 1540 | * @param {Function} fn 1541 | * @return {Runner} 1542 | * @api public 1543 | */ 1544 | 1545 | Mocha.prototype.run = function(fn){ 1546 | if (this.files.length) this.loadFiles(); 1547 | var suite = this.suite; 1548 | var options = this.options; 1549 | var runner = new exports.Runner(suite); 1550 | var reporter = new this._reporter(runner); 1551 | runner.ignoreLeaks = false !== options.ignoreLeaks; 1552 | runner.asyncOnly = options.asyncOnly; 1553 | if (options.grep) runner.grep(options.grep, options.invert); 1554 | if (options.globals) runner.globals(options.globals); 1555 | if (options.growl) this._growl(runner, reporter); 1556 | return runner.run(fn); 1557 | }; 1558 | 1559 | }); // module: mocha.js 1560 | 1561 | require.register("ms.js", function(module, exports, require){ 1562 | 1563 | /** 1564 | * Helpers. 1565 | */ 1566 | 1567 | var s = 1000; 1568 | var m = s * 60; 1569 | var h = m * 60; 1570 | var d = h * 24; 1571 | 1572 | /** 1573 | * Parse or format the given `val`. 1574 | * 1575 | * @param {String|Number} val 1576 | * @return {String|Number} 1577 | * @api public 1578 | */ 1579 | 1580 | module.exports = function(val){ 1581 | if ('string' == typeof val) return parse(val); 1582 | return format(val); 1583 | } 1584 | 1585 | /** 1586 | * Parse the given `str` and return milliseconds. 1587 | * 1588 | * @param {String} str 1589 | * @return {Number} 1590 | * @api private 1591 | */ 1592 | 1593 | function parse(str) { 1594 | var m = /^((?:\d+)?\.?\d+) *(ms|seconds?|s|minutes?|m|hours?|h|days?|d|years?|y)?$/i.exec(str); 1595 | if (!m) return; 1596 | var n = parseFloat(m[1]); 1597 | var type = (m[2] || 'ms').toLowerCase(); 1598 | switch (type) { 1599 | case 'years': 1600 | case 'year': 1601 | case 'y': 1602 | return n * 31557600000; 1603 | case 'days': 1604 | case 'day': 1605 | case 'd': 1606 | return n * 86400000; 1607 | case 'hours': 1608 | case 'hour': 1609 | case 'h': 1610 | return n * 3600000; 1611 | case 'minutes': 1612 | case 'minute': 1613 | case 'm': 1614 | return n * 60000; 1615 | case 'seconds': 1616 | case 'second': 1617 | case 's': 1618 | return n * 1000; 1619 | case 'ms': 1620 | return n; 1621 | } 1622 | } 1623 | 1624 | /** 1625 | * Format the given `ms`. 1626 | * 1627 | * @param {Number} ms 1628 | * @return {String} 1629 | * @api public 1630 | */ 1631 | 1632 | function format(ms) { 1633 | if (ms == d) return Math.round(ms / d) + ' day'; 1634 | if (ms > d) return Math.round(ms / d) + ' days'; 1635 | if (ms == h) return Math.round(ms / h) + ' hour'; 1636 | if (ms > h) return Math.round(ms / h) + ' hours'; 1637 | if (ms == m) return Math.round(ms / m) + ' minute'; 1638 | if (ms > m) return Math.round(ms / m) + ' minutes'; 1639 | if (ms == s) return Math.round(ms / s) + ' second'; 1640 | if (ms > s) return Math.round(ms / s) + ' seconds'; 1641 | return ms + ' ms'; 1642 | } 1643 | }); // module: ms.js 1644 | 1645 | require.register("reporters/base.js", function(module, exports, require){ 1646 | 1647 | /** 1648 | * Module dependencies. 1649 | */ 1650 | 1651 | var tty = require('browser/tty') 1652 | , diff = require('browser/diff') 1653 | , ms = require('../ms'); 1654 | 1655 | /** 1656 | * Save timer references to avoid Sinon interfering (see GH-237). 1657 | */ 1658 | 1659 | var Date = global.Date 1660 | , setTimeout = global.setTimeout 1661 | , setInterval = global.setInterval 1662 | , clearTimeout = global.clearTimeout 1663 | , clearInterval = global.clearInterval; 1664 | 1665 | /** 1666 | * Check if both stdio streams are associated with a tty. 1667 | */ 1668 | 1669 | var isatty = tty.isatty(1) && tty.isatty(2); 1670 | 1671 | /** 1672 | * Expose `Base`. 1673 | */ 1674 | 1675 | exports = module.exports = Base; 1676 | 1677 | /** 1678 | * Enable coloring by default. 1679 | */ 1680 | 1681 | exports.useColors = isatty; 1682 | 1683 | /** 1684 | * Default color map. 1685 | */ 1686 | 1687 | exports.colors = { 1688 | 'pass': 90 1689 | , 'fail': 31 1690 | , 'bright pass': 92 1691 | , 'bright fail': 91 1692 | , 'bright yellow': 93 1693 | , 'pending': 36 1694 | , 'suite': 0 1695 | , 'error title': 0 1696 | , 'error message': 31 1697 | , 'error stack': 90 1698 | , 'checkmark': 32 1699 | , 'fast': 90 1700 | , 'medium': 33 1701 | , 'slow': 31 1702 | , 'green': 32 1703 | , 'light': 90 1704 | , 'diff gutter': 90 1705 | , 'diff added': 42 1706 | , 'diff removed': 41 1707 | }; 1708 | 1709 | /** 1710 | * Default symbol map. 1711 | */ 1712 | 1713 | exports.symbols = { 1714 | ok: '✓', 1715 | err: '✖', 1716 | dot: '․' 1717 | }; 1718 | 1719 | // With node.js on Windows: use symbols available in terminal default fonts 1720 | if ('win32' == process.platform) { 1721 | exports.symbols.ok = '\u221A'; 1722 | exports.symbols.err = '\u00D7'; 1723 | exports.symbols.dot = '.'; 1724 | } 1725 | 1726 | /** 1727 | * Color `str` with the given `type`, 1728 | * allowing colors to be disabled, 1729 | * as well as user-defined color 1730 | * schemes. 1731 | * 1732 | * @param {String} type 1733 | * @param {String} str 1734 | * @return {String} 1735 | * @api private 1736 | */ 1737 | 1738 | var color = exports.color = function(type, str) { 1739 | if (!exports.useColors) return str; 1740 | return '\u001b[' + exports.colors[type] + 'm' + str + '\u001b[0m'; 1741 | }; 1742 | 1743 | /** 1744 | * Expose term window size, with some 1745 | * defaults for when stderr is not a tty. 1746 | */ 1747 | 1748 | exports.window = { 1749 | width: isatty 1750 | ? process.stdout.getWindowSize 1751 | ? process.stdout.getWindowSize(1)[0] 1752 | : tty.getWindowSize()[1] 1753 | : 75 1754 | }; 1755 | 1756 | /** 1757 | * Expose some basic cursor interactions 1758 | * that are common among reporters. 1759 | */ 1760 | 1761 | exports.cursor = { 1762 | hide: function(){ 1763 | process.stdout.write('\u001b[?25l'); 1764 | }, 1765 | 1766 | show: function(){ 1767 | process.stdout.write('\u001b[?25h'); 1768 | }, 1769 | 1770 | deleteLine: function(){ 1771 | process.stdout.write('\u001b[2K'); 1772 | }, 1773 | 1774 | beginningOfLine: function(){ 1775 | process.stdout.write('\u001b[0G'); 1776 | }, 1777 | 1778 | CR: function(){ 1779 | exports.cursor.deleteLine(); 1780 | exports.cursor.beginningOfLine(); 1781 | } 1782 | }; 1783 | 1784 | /** 1785 | * Outut the given `failures` as a list. 1786 | * 1787 | * @param {Array} failures 1788 | * @api public 1789 | */ 1790 | 1791 | exports.list = function(failures){ 1792 | console.error(); 1793 | failures.forEach(function(test, i){ 1794 | // format 1795 | var fmt = color('error title', ' %s) %s:\n') 1796 | + color('error message', ' %s') 1797 | + color('error stack', '\n%s\n'); 1798 | 1799 | // msg 1800 | var err = test.err 1801 | , message = err.message || '' 1802 | , stack = err.stack || message 1803 | , index = stack.indexOf(message) + message.length 1804 | , msg = stack.slice(0, index) 1805 | , actual = err.actual 1806 | , expected = err.expected 1807 | , escape = true; 1808 | 1809 | // explicitly show diff 1810 | if (err.showDiff) { 1811 | escape = false; 1812 | err.actual = actual = JSON.stringify(actual, null, 2); 1813 | err.expected = expected = JSON.stringify(expected, null, 2); 1814 | } 1815 | 1816 | // actual / expected diff 1817 | if ('string' == typeof actual && 'string' == typeof expected) { 1818 | var len = Math.max(actual.length, expected.length); 1819 | 1820 | if (len < 20) msg = errorDiff(err, 'Chars', escape); 1821 | else msg = errorDiff(err, 'Words', escape); 1822 | 1823 | // linenos 1824 | var lines = msg.split('\n'); 1825 | if (lines.length > 4) { 1826 | var width = String(lines.length).length; 1827 | msg = lines.map(function(str, i){ 1828 | return pad(++i, width) + ' |' + ' ' + str; 1829 | }).join('\n'); 1830 | } 1831 | 1832 | // legend 1833 | msg = '\n' 1834 | + color('diff removed', 'actual') 1835 | + ' ' 1836 | + color('diff added', 'expected') 1837 | + '\n\n' 1838 | + msg 1839 | + '\n'; 1840 | 1841 | // indent 1842 | msg = msg.replace(/^/gm, ' '); 1843 | 1844 | fmt = color('error title', ' %s) %s:\n%s') 1845 | + color('error stack', '\n%s\n'); 1846 | } 1847 | 1848 | // indent stack trace without msg 1849 | stack = stack.slice(index ? index + 1 : index) 1850 | .replace(/^/gm, ' '); 1851 | 1852 | console.error(fmt, (i + 1), test.fullTitle(), msg, stack); 1853 | }); 1854 | }; 1855 | 1856 | /** 1857 | * Initialize a new `Base` reporter. 1858 | * 1859 | * All other reporters generally 1860 | * inherit from this reporter, providing 1861 | * stats such as test duration, number 1862 | * of tests passed / failed etc. 1863 | * 1864 | * @param {Runner} runner 1865 | * @api public 1866 | */ 1867 | 1868 | function Base(runner) { 1869 | var self = this 1870 | , stats = this.stats = { suites: 0, tests: 0, passes: 0, pending: 0, failures: 0 } 1871 | , failures = this.failures = []; 1872 | 1873 | if (!runner) return; 1874 | this.runner = runner; 1875 | 1876 | runner.stats = stats; 1877 | 1878 | runner.on('start', function(){ 1879 | stats.start = new Date; 1880 | }); 1881 | 1882 | runner.on('suite', function(suite){ 1883 | stats.suites = stats.suites || 0; 1884 | suite.root || stats.suites++; 1885 | }); 1886 | 1887 | runner.on('test end', function(test){ 1888 | stats.tests = stats.tests || 0; 1889 | stats.tests++; 1890 | }); 1891 | 1892 | runner.on('pass', function(test){ 1893 | stats.passes = stats.passes || 0; 1894 | 1895 | var medium = test.slow() / 2; 1896 | test.speed = test.duration > test.slow() 1897 | ? 'slow' 1898 | : test.duration > medium 1899 | ? 'medium' 1900 | : 'fast'; 1901 | 1902 | stats.passes++; 1903 | }); 1904 | 1905 | runner.on('fail', function(test, err){ 1906 | stats.failures = stats.failures || 0; 1907 | stats.failures++; 1908 | test.err = err; 1909 | failures.push(test); 1910 | }); 1911 | 1912 | runner.on('end', function(){ 1913 | stats.end = new Date; 1914 | stats.duration = new Date - stats.start; 1915 | }); 1916 | 1917 | runner.on('pending', function(){ 1918 | stats.pending++; 1919 | }); 1920 | } 1921 | 1922 | /** 1923 | * Output common epilogue used by many of 1924 | * the bundled reporters. 1925 | * 1926 | * @api public 1927 | */ 1928 | 1929 | Base.prototype.epilogue = function(){ 1930 | var stats = this.stats 1931 | , fmt 1932 | , tests; 1933 | 1934 | console.log(); 1935 | 1936 | function pluralize(n) { 1937 | return 1 == n ? 'test' : 'tests'; 1938 | } 1939 | 1940 | // failure 1941 | if (stats.failures) { 1942 | fmt = color('bright fail', ' ' + exports.symbols.err) 1943 | + color('fail', ' %d of %d %s failed') 1944 | + color('light', ':') 1945 | 1946 | console.error(fmt, 1947 | stats.failures, 1948 | this.runner.total, 1949 | pluralize(this.runner.total)); 1950 | 1951 | Base.list(this.failures); 1952 | console.error(); 1953 | return; 1954 | } 1955 | 1956 | // pass 1957 | fmt = color('bright pass', ' ') 1958 | + color('green', ' %d %s complete') 1959 | + color('light', ' (%s)'); 1960 | 1961 | console.log(fmt, 1962 | stats.tests || 0, 1963 | pluralize(stats.tests), 1964 | ms(stats.duration)); 1965 | 1966 | // pending 1967 | if (stats.pending) { 1968 | fmt = color('pending', ' ') 1969 | + color('pending', ' %d %s pending'); 1970 | 1971 | console.log(fmt, stats.pending, pluralize(stats.pending)); 1972 | } 1973 | 1974 | console.log(); 1975 | }; 1976 | 1977 | /** 1978 | * Pad the given `str` to `len`. 1979 | * 1980 | * @param {String} str 1981 | * @param {String} len 1982 | * @return {String} 1983 | * @api private 1984 | */ 1985 | 1986 | function pad(str, len) { 1987 | str = String(str); 1988 | return Array(len - str.length + 1).join(' ') + str; 1989 | } 1990 | 1991 | /** 1992 | * Return a character diff for `err`. 1993 | * 1994 | * @param {Error} err 1995 | * @return {String} 1996 | * @api private 1997 | */ 1998 | 1999 | function errorDiff(err, type, escape) { 2000 | return diff['diff' + type](err.actual, err.expected).map(function(str){ 2001 | if (escape) { 2002 | str.value = str.value 2003 | .replace(/\t/g, '') 2004 | .replace(/\r/g, '') 2005 | .replace(/\n/g, '\n'); 2006 | } 2007 | if (str.added) return colorLines('diff added', str.value); 2008 | if (str.removed) return colorLines('diff removed', str.value); 2009 | return str.value; 2010 | }).join(''); 2011 | } 2012 | 2013 | /** 2014 | * Color lines for `str`, using the color `name`. 2015 | * 2016 | * @param {String} name 2017 | * @param {String} str 2018 | * @return {String} 2019 | * @api private 2020 | */ 2021 | 2022 | function colorLines(name, str) { 2023 | return str.split('\n').map(function(str){ 2024 | return color(name, str); 2025 | }).join('\n'); 2026 | } 2027 | 2028 | }); // module: reporters/base.js 2029 | 2030 | require.register("reporters/doc.js", function(module, exports, require){ 2031 | 2032 | /** 2033 | * Module dependencies. 2034 | */ 2035 | 2036 | var Base = require('./base') 2037 | , utils = require('../utils'); 2038 | 2039 | /** 2040 | * Expose `Doc`. 2041 | */ 2042 | 2043 | exports = module.exports = Doc; 2044 | 2045 | /** 2046 | * Initialize a new `Doc` reporter. 2047 | * 2048 | * @param {Runner} runner 2049 | * @api public 2050 | */ 2051 | 2052 | function Doc(runner) { 2053 | Base.call(this, runner); 2054 | 2055 | var self = this 2056 | , stats = this.stats 2057 | , total = runner.total 2058 | , indents = 2; 2059 | 2060 | function indent() { 2061 | return Array(indents).join(' '); 2062 | } 2063 | 2064 | runner.on('suite', function(suite){ 2065 | if (suite.root) return; 2066 | ++indents; 2067 | console.log('%s
', indent()); 2068 | ++indents; 2069 | console.log('%s

%s

', indent(), utils.escape(suite.title)); 2070 | console.log('%s
', indent()); 2071 | }); 2072 | 2073 | runner.on('suite end', function(suite){ 2074 | if (suite.root) return; 2075 | console.log('%s
', indent()); 2076 | --indents; 2077 | console.log('%s
', indent()); 2078 | --indents; 2079 | }); 2080 | 2081 | runner.on('pass', function(test){ 2082 | console.log('%s
%s
', indent(), utils.escape(test.title)); 2083 | var code = utils.escape(utils.clean(test.fn.toString())); 2084 | console.log('%s
%s
', indent(), code); 2085 | }); 2086 | } 2087 | 2088 | }); // module: reporters/doc.js 2089 | 2090 | require.register("reporters/dot.js", function(module, exports, require){ 2091 | 2092 | /** 2093 | * Module dependencies. 2094 | */ 2095 | 2096 | var Base = require('./base') 2097 | , color = Base.color; 2098 | 2099 | /** 2100 | * Expose `Dot`. 2101 | */ 2102 | 2103 | exports = module.exports = Dot; 2104 | 2105 | /** 2106 | * Initialize a new `Dot` matrix test reporter. 2107 | * 2108 | * @param {Runner} runner 2109 | * @api public 2110 | */ 2111 | 2112 | function Dot(runner) { 2113 | Base.call(this, runner); 2114 | 2115 | var self = this 2116 | , stats = this.stats 2117 | , width = Base.window.width * .75 | 0 2118 | , n = 0; 2119 | 2120 | runner.on('start', function(){ 2121 | process.stdout.write('\n '); 2122 | }); 2123 | 2124 | runner.on('pending', function(test){ 2125 | process.stdout.write(color('pending', Base.symbols.dot)); 2126 | }); 2127 | 2128 | runner.on('pass', function(test){ 2129 | if (++n % width == 0) process.stdout.write('\n '); 2130 | if ('slow' == test.speed) { 2131 | process.stdout.write(color('bright yellow', Base.symbols.dot)); 2132 | } else { 2133 | process.stdout.write(color(test.speed, Base.symbols.dot)); 2134 | } 2135 | }); 2136 | 2137 | runner.on('fail', function(test, err){ 2138 | if (++n % width == 0) process.stdout.write('\n '); 2139 | process.stdout.write(color('fail', Base.symbols.dot)); 2140 | }); 2141 | 2142 | runner.on('end', function(){ 2143 | console.log(); 2144 | self.epilogue(); 2145 | }); 2146 | } 2147 | 2148 | /** 2149 | * Inherit from `Base.prototype`. 2150 | */ 2151 | 2152 | function F(){}; 2153 | F.prototype = Base.prototype; 2154 | Dot.prototype = new F; 2155 | Dot.prototype.constructor = Dot; 2156 | 2157 | }); // module: reporters/dot.js 2158 | 2159 | require.register("reporters/html-cov.js", function(module, exports, require){ 2160 | 2161 | /** 2162 | * Module dependencies. 2163 | */ 2164 | 2165 | var JSONCov = require('./json-cov') 2166 | , fs = require('browser/fs'); 2167 | 2168 | /** 2169 | * Expose `HTMLCov`. 2170 | */ 2171 | 2172 | exports = module.exports = HTMLCov; 2173 | 2174 | /** 2175 | * Initialize a new `JsCoverage` reporter. 2176 | * 2177 | * @param {Runner} runner 2178 | * @api public 2179 | */ 2180 | 2181 | function HTMLCov(runner) { 2182 | var jade = require('jade') 2183 | , file = __dirname + '/templates/coverage.jade' 2184 | , str = fs.readFileSync(file, 'utf8') 2185 | , fn = jade.compile(str, { filename: file }) 2186 | , self = this; 2187 | 2188 | JSONCov.call(this, runner, false); 2189 | 2190 | runner.on('end', function(){ 2191 | process.stdout.write(fn({ 2192 | cov: self.cov 2193 | , coverageClass: coverageClass 2194 | })); 2195 | }); 2196 | } 2197 | 2198 | /** 2199 | * Return coverage class for `n`. 2200 | * 2201 | * @return {String} 2202 | * @api private 2203 | */ 2204 | 2205 | function coverageClass(n) { 2206 | if (n >= 75) return 'high'; 2207 | if (n >= 50) return 'medium'; 2208 | if (n >= 25) return 'low'; 2209 | return 'terrible'; 2210 | } 2211 | }); // module: reporters/html-cov.js 2212 | 2213 | require.register("reporters/html.js", function(module, exports, require){ 2214 | 2215 | /** 2216 | * Module dependencies. 2217 | */ 2218 | 2219 | var Base = require('./base') 2220 | , utils = require('../utils') 2221 | , Progress = require('../browser/progress') 2222 | , escape = utils.escape; 2223 | 2224 | /** 2225 | * Save timer references to avoid Sinon interfering (see GH-237). 2226 | */ 2227 | 2228 | var Date = global.Date 2229 | , setTimeout = global.setTimeout 2230 | , setInterval = global.setInterval 2231 | , clearTimeout = global.clearTimeout 2232 | , clearInterval = global.clearInterval; 2233 | 2234 | /** 2235 | * Expose `Doc`. 2236 | */ 2237 | 2238 | exports = module.exports = HTML; 2239 | 2240 | /** 2241 | * Stats template. 2242 | */ 2243 | 2244 | var statsTemplate = '
    ' 2245 | + '
  • ' 2246 | + '
  • passes: 0
  • ' 2247 | + '
  • failures: 0
  • ' 2248 | + '
  • duration: 0s
  • ' 2249 | + '
'; 2250 | 2251 | /** 2252 | * Initialize a new `Doc` reporter. 2253 | * 2254 | * @param {Runner} runner 2255 | * @api public 2256 | */ 2257 | 2258 | function HTML(runner, root) { 2259 | Base.call(this, runner); 2260 | 2261 | var self = this 2262 | , stats = this.stats 2263 | , total = runner.total 2264 | , stat = fragment(statsTemplate) 2265 | , items = stat.getElementsByTagName('li') 2266 | , passes = items[1].getElementsByTagName('em')[0] 2267 | , passesLink = items[1].getElementsByTagName('a')[0] 2268 | , failures = items[2].getElementsByTagName('em')[0] 2269 | , failuresLink = items[2].getElementsByTagName('a')[0] 2270 | , duration = items[3].getElementsByTagName('em')[0] 2271 | , canvas = stat.getElementsByTagName('canvas')[0] 2272 | , report = fragment('
    ') 2273 | , stack = [report] 2274 | , progress 2275 | , ctx 2276 | 2277 | root = root || document.getElementById('mocha'); 2278 | 2279 | if (canvas.getContext) { 2280 | var ratio = window.devicePixelRatio || 1; 2281 | canvas.style.width = canvas.width; 2282 | canvas.style.height = canvas.height; 2283 | canvas.width *= ratio; 2284 | canvas.height *= ratio; 2285 | ctx = canvas.getContext('2d'); 2286 | ctx.scale(ratio, ratio); 2287 | progress = new Progress; 2288 | } 2289 | 2290 | if (!root) return error('#mocha div missing, add it to your document'); 2291 | 2292 | // pass toggle 2293 | on(passesLink, 'click', function(){ 2294 | unhide(); 2295 | var name = /pass/.test(report.className) ? '' : ' pass'; 2296 | report.className = report.className.replace(/fail|pass/g, '') + name; 2297 | if (report.className.trim()) hideSuitesWithout('test pass'); 2298 | }); 2299 | 2300 | // failure toggle 2301 | on(failuresLink, 'click', function(){ 2302 | unhide(); 2303 | var name = /fail/.test(report.className) ? '' : ' fail'; 2304 | report.className = report.className.replace(/fail|pass/g, '') + name; 2305 | if (report.className.trim()) hideSuitesWithout('test fail'); 2306 | }); 2307 | 2308 | root.appendChild(stat); 2309 | root.appendChild(report); 2310 | 2311 | if (progress) progress.size(40); 2312 | 2313 | runner.on('suite', function(suite){ 2314 | if (suite.root) return; 2315 | 2316 | // suite 2317 | var url = '?grep=' + encodeURIComponent(suite.fullTitle()); 2318 | var el = fragment('
  • %s

  • ', url, escape(suite.title)); 2319 | 2320 | // container 2321 | stack[0].appendChild(el); 2322 | stack.unshift(document.createElement('ul')); 2323 | el.appendChild(stack[0]); 2324 | }); 2325 | 2326 | runner.on('suite end', function(suite){ 2327 | if (suite.root) return; 2328 | stack.shift(); 2329 | }); 2330 | 2331 | runner.on('fail', function(test, err){ 2332 | if ('hook' == test.type) runner.emit('test end', test); 2333 | }); 2334 | 2335 | runner.on('test end', function(test){ 2336 | // TODO: add to stats 2337 | var percent = stats.tests / this.total * 100 | 0; 2338 | if (progress) progress.update(percent).draw(ctx); 2339 | 2340 | // update stats 2341 | var ms = new Date - stats.start; 2342 | text(passes, stats.passes); 2343 | text(failures, stats.failures); 2344 | text(duration, (ms / 1000).toFixed(2)); 2345 | 2346 | // test 2347 | if ('passed' == test.state) { 2348 | var el = fragment('
  • %e%ems

  • ', test.speed, test.title, test.duration, encodeURIComponent(test.fullTitle())); 2349 | } else if (test.pending) { 2350 | var el = fragment('
  • %e

  • ', test.title); 2351 | } else { 2352 | var el = fragment('
  • %e

  • ', test.title, encodeURIComponent(test.fullTitle())); 2353 | var str = test.err.stack || test.err.toString(); 2354 | 2355 | // FF / Opera do not add the message 2356 | if (!~str.indexOf(test.err.message)) { 2357 | str = test.err.message + '\n' + str; 2358 | } 2359 | 2360 | // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we 2361 | // check for the result of the stringifying. 2362 | if ('[object Error]' == str) str = test.err.message; 2363 | 2364 | // Safari doesn't give you a stack. Let's at least provide a source line. 2365 | if (!test.err.stack && test.err.sourceURL && test.err.line !== undefined) { 2366 | str += "\n(" + test.err.sourceURL + ":" + test.err.line + ")"; 2367 | } 2368 | 2369 | el.appendChild(fragment('
    %e
    ', str)); 2370 | } 2371 | 2372 | // toggle code 2373 | // TODO: defer 2374 | if (!test.pending) { 2375 | var h2 = el.getElementsByTagName('h2')[0]; 2376 | 2377 | on(h2, 'click', function(){ 2378 | pre.style.display = 'none' == pre.style.display 2379 | ? 'block' 2380 | : 'none'; 2381 | }); 2382 | 2383 | var pre = fragment('
    %e
    ', utils.clean(test.fn.toString())); 2384 | el.appendChild(pre); 2385 | pre.style.display = 'none'; 2386 | } 2387 | 2388 | // Don't call .appendChild if #mocha-report was already .shift()'ed off the stack. 2389 | if (stack[0]) stack[0].appendChild(el); 2390 | }); 2391 | } 2392 | 2393 | /** 2394 | * Display error `msg`. 2395 | */ 2396 | 2397 | function error(msg) { 2398 | document.body.appendChild(fragment('
    %s
    ', msg)); 2399 | } 2400 | 2401 | /** 2402 | * Return a DOM fragment from `html`. 2403 | */ 2404 | 2405 | function fragment(html) { 2406 | var args = arguments 2407 | , div = document.createElement('div') 2408 | , i = 1; 2409 | 2410 | div.innerHTML = html.replace(/%([se])/g, function(_, type){ 2411 | switch (type) { 2412 | case 's': return String(args[i++]); 2413 | case 'e': return escape(args[i++]); 2414 | } 2415 | }); 2416 | 2417 | return div.firstChild; 2418 | } 2419 | 2420 | /** 2421 | * Check for suites that do not have elements 2422 | * with `classname`, and hide them. 2423 | */ 2424 | 2425 | function hideSuitesWithout(classname) { 2426 | var suites = document.getElementsByClassName('suite'); 2427 | for (var i = 0; i < suites.length; i++) { 2428 | var els = suites[i].getElementsByClassName(classname); 2429 | if (0 == els.length) suites[i].className += ' hidden'; 2430 | } 2431 | } 2432 | 2433 | /** 2434 | * Unhide .hidden suites. 2435 | */ 2436 | 2437 | function unhide() { 2438 | var els = document.getElementsByClassName('suite hidden'); 2439 | for (var i = 0; i < els.length; ++i) { 2440 | els[i].className = els[i].className.replace('suite hidden', 'suite'); 2441 | } 2442 | } 2443 | 2444 | /** 2445 | * Set `el` text to `str`. 2446 | */ 2447 | 2448 | function text(el, str) { 2449 | if (el.textContent) { 2450 | el.textContent = str; 2451 | } else { 2452 | el.innerText = str; 2453 | } 2454 | } 2455 | 2456 | /** 2457 | * Listen on `event` with callback `fn`. 2458 | */ 2459 | 2460 | function on(el, event, fn) { 2461 | if (el.addEventListener) { 2462 | el.addEventListener(event, fn, false); 2463 | } else { 2464 | el.attachEvent('on' + event, fn); 2465 | } 2466 | } 2467 | 2468 | }); // module: reporters/html.js 2469 | 2470 | require.register("reporters/index.js", function(module, exports, require){ 2471 | 2472 | exports.Base = require('./base'); 2473 | exports.Dot = require('./dot'); 2474 | exports.Doc = require('./doc'); 2475 | exports.TAP = require('./tap'); 2476 | exports.JSON = require('./json'); 2477 | exports.HTML = require('./html'); 2478 | exports.List = require('./list'); 2479 | exports.Min = require('./min'); 2480 | exports.Spec = require('./spec'); 2481 | exports.Nyan = require('./nyan'); 2482 | exports.XUnit = require('./xunit'); 2483 | exports.Markdown = require('./markdown'); 2484 | exports.Progress = require('./progress'); 2485 | exports.Landing = require('./landing'); 2486 | exports.JSONCov = require('./json-cov'); 2487 | exports.HTMLCov = require('./html-cov'); 2488 | exports.JSONStream = require('./json-stream'); 2489 | exports.Teamcity = require('./teamcity'); 2490 | 2491 | }); // module: reporters/index.js 2492 | 2493 | require.register("reporters/json-cov.js", function(module, exports, require){ 2494 | 2495 | /** 2496 | * Module dependencies. 2497 | */ 2498 | 2499 | var Base = require('./base'); 2500 | 2501 | /** 2502 | * Expose `JSONCov`. 2503 | */ 2504 | 2505 | exports = module.exports = JSONCov; 2506 | 2507 | /** 2508 | * Initialize a new `JsCoverage` reporter. 2509 | * 2510 | * @param {Runner} runner 2511 | * @param {Boolean} output 2512 | * @api public 2513 | */ 2514 | 2515 | function JSONCov(runner, output) { 2516 | var self = this 2517 | , output = 1 == arguments.length ? true : output; 2518 | 2519 | Base.call(this, runner); 2520 | 2521 | var tests = [] 2522 | , failures = [] 2523 | , passes = []; 2524 | 2525 | runner.on('test end', function(test){ 2526 | tests.push(test); 2527 | }); 2528 | 2529 | runner.on('pass', function(test){ 2530 | passes.push(test); 2531 | }); 2532 | 2533 | runner.on('fail', function(test){ 2534 | failures.push(test); 2535 | }); 2536 | 2537 | runner.on('end', function(){ 2538 | var cov = global._$jscoverage || {}; 2539 | var result = self.cov = map(cov); 2540 | result.stats = self.stats; 2541 | result.tests = tests.map(clean); 2542 | result.failures = failures.map(clean); 2543 | result.passes = passes.map(clean); 2544 | if (!output) return; 2545 | process.stdout.write(JSON.stringify(result, null, 2 )); 2546 | }); 2547 | } 2548 | 2549 | /** 2550 | * Map jscoverage data to a JSON structure 2551 | * suitable for reporting. 2552 | * 2553 | * @param {Object} cov 2554 | * @return {Object} 2555 | * @api private 2556 | */ 2557 | 2558 | function map(cov) { 2559 | var ret = { 2560 | instrumentation: 'node-jscoverage' 2561 | , sloc: 0 2562 | , hits: 0 2563 | , misses: 0 2564 | , coverage: 0 2565 | , files: [] 2566 | }; 2567 | 2568 | for (var filename in cov) { 2569 | var data = coverage(filename, cov[filename]); 2570 | ret.files.push(data); 2571 | ret.hits += data.hits; 2572 | ret.misses += data.misses; 2573 | ret.sloc += data.sloc; 2574 | } 2575 | 2576 | ret.files.sort(function(a, b) { 2577 | return a.filename.localeCompare(b.filename); 2578 | }); 2579 | 2580 | if (ret.sloc > 0) { 2581 | ret.coverage = (ret.hits / ret.sloc) * 100; 2582 | } 2583 | 2584 | return ret; 2585 | }; 2586 | 2587 | /** 2588 | * Map jscoverage data for a single source file 2589 | * to a JSON structure suitable for reporting. 2590 | * 2591 | * @param {String} filename name of the source file 2592 | * @param {Object} data jscoverage coverage data 2593 | * @return {Object} 2594 | * @api private 2595 | */ 2596 | 2597 | function coverage(filename, data) { 2598 | var ret = { 2599 | filename: filename, 2600 | coverage: 0, 2601 | hits: 0, 2602 | misses: 0, 2603 | sloc: 0, 2604 | source: {} 2605 | }; 2606 | 2607 | data.source.forEach(function(line, num){ 2608 | num++; 2609 | 2610 | if (data[num] === 0) { 2611 | ret.misses++; 2612 | ret.sloc++; 2613 | } else if (data[num] !== undefined) { 2614 | ret.hits++; 2615 | ret.sloc++; 2616 | } 2617 | 2618 | ret.source[num] = { 2619 | source: line 2620 | , coverage: data[num] === undefined 2621 | ? '' 2622 | : data[num] 2623 | }; 2624 | }); 2625 | 2626 | ret.coverage = ret.hits / ret.sloc * 100; 2627 | 2628 | return ret; 2629 | } 2630 | 2631 | /** 2632 | * Return a plain-object representation of `test` 2633 | * free of cyclic properties etc. 2634 | * 2635 | * @param {Object} test 2636 | * @return {Object} 2637 | * @api private 2638 | */ 2639 | 2640 | function clean(test) { 2641 | return { 2642 | title: test.title 2643 | , fullTitle: test.fullTitle() 2644 | , duration: test.duration 2645 | } 2646 | } 2647 | 2648 | }); // module: reporters/json-cov.js 2649 | 2650 | require.register("reporters/json-stream.js", function(module, exports, require){ 2651 | 2652 | /** 2653 | * Module dependencies. 2654 | */ 2655 | 2656 | var Base = require('./base') 2657 | , color = Base.color; 2658 | 2659 | /** 2660 | * Expose `List`. 2661 | */ 2662 | 2663 | exports = module.exports = List; 2664 | 2665 | /** 2666 | * Initialize a new `List` test reporter. 2667 | * 2668 | * @param {Runner} runner 2669 | * @api public 2670 | */ 2671 | 2672 | function List(runner) { 2673 | Base.call(this, runner); 2674 | 2675 | var self = this 2676 | , stats = this.stats 2677 | , total = runner.total; 2678 | 2679 | runner.on('start', function(){ 2680 | console.log(JSON.stringify(['start', { total: total }])); 2681 | }); 2682 | 2683 | runner.on('pass', function(test){ 2684 | console.log(JSON.stringify(['pass', clean(test)])); 2685 | }); 2686 | 2687 | runner.on('fail', function(test, err){ 2688 | console.log(JSON.stringify(['fail', clean(test)])); 2689 | }); 2690 | 2691 | runner.on('end', function(){ 2692 | process.stdout.write(JSON.stringify(['end', self.stats])); 2693 | }); 2694 | } 2695 | 2696 | /** 2697 | * Return a plain-object representation of `test` 2698 | * free of cyclic properties etc. 2699 | * 2700 | * @param {Object} test 2701 | * @return {Object} 2702 | * @api private 2703 | */ 2704 | 2705 | function clean(test) { 2706 | return { 2707 | title: test.title 2708 | , fullTitle: test.fullTitle() 2709 | , duration: test.duration 2710 | } 2711 | } 2712 | }); // module: reporters/json-stream.js 2713 | 2714 | require.register("reporters/json.js", function(module, exports, require){ 2715 | 2716 | /** 2717 | * Module dependencies. 2718 | */ 2719 | 2720 | var Base = require('./base') 2721 | , cursor = Base.cursor 2722 | , color = Base.color; 2723 | 2724 | /** 2725 | * Expose `JSON`. 2726 | */ 2727 | 2728 | exports = module.exports = JSONReporter; 2729 | 2730 | /** 2731 | * Initialize a new `JSON` reporter. 2732 | * 2733 | * @param {Runner} runner 2734 | * @api public 2735 | */ 2736 | 2737 | function JSONReporter(runner) { 2738 | var self = this; 2739 | Base.call(this, runner); 2740 | 2741 | var tests = [] 2742 | , failures = [] 2743 | , passes = []; 2744 | 2745 | runner.on('test end', function(test){ 2746 | tests.push(test); 2747 | }); 2748 | 2749 | runner.on('pass', function(test){ 2750 | passes.push(test); 2751 | }); 2752 | 2753 | runner.on('fail', function(test){ 2754 | failures.push(test); 2755 | }); 2756 | 2757 | runner.on('end', function(){ 2758 | var obj = { 2759 | stats: self.stats 2760 | , tests: tests.map(clean) 2761 | , failures: failures.map(clean) 2762 | , passes: passes.map(clean) 2763 | }; 2764 | 2765 | process.stdout.write(JSON.stringify(obj, null, 2)); 2766 | }); 2767 | } 2768 | 2769 | /** 2770 | * Return a plain-object representation of `test` 2771 | * free of cyclic properties etc. 2772 | * 2773 | * @param {Object} test 2774 | * @return {Object} 2775 | * @api private 2776 | */ 2777 | 2778 | function clean(test) { 2779 | return { 2780 | title: test.title 2781 | , fullTitle: test.fullTitle() 2782 | , duration: test.duration 2783 | } 2784 | } 2785 | }); // module: reporters/json.js 2786 | 2787 | require.register("reporters/landing.js", function(module, exports, require){ 2788 | 2789 | /** 2790 | * Module dependencies. 2791 | */ 2792 | 2793 | var Base = require('./base') 2794 | , cursor = Base.cursor 2795 | , color = Base.color; 2796 | 2797 | /** 2798 | * Expose `Landing`. 2799 | */ 2800 | 2801 | exports = module.exports = Landing; 2802 | 2803 | /** 2804 | * Airplane color. 2805 | */ 2806 | 2807 | Base.colors.plane = 0; 2808 | 2809 | /** 2810 | * Airplane crash color. 2811 | */ 2812 | 2813 | Base.colors['plane crash'] = 31; 2814 | 2815 | /** 2816 | * Runway color. 2817 | */ 2818 | 2819 | Base.colors.runway = 90; 2820 | 2821 | /** 2822 | * Initialize a new `Landing` reporter. 2823 | * 2824 | * @param {Runner} runner 2825 | * @api public 2826 | */ 2827 | 2828 | function Landing(runner) { 2829 | Base.call(this, runner); 2830 | 2831 | var self = this 2832 | , stats = this.stats 2833 | , width = Base.window.width * .75 | 0 2834 | , total = runner.total 2835 | , stream = process.stdout 2836 | , plane = color('plane', '✈') 2837 | , crashed = -1 2838 | , n = 0; 2839 | 2840 | function runway() { 2841 | var buf = Array(width).join('-'); 2842 | return ' ' + color('runway', buf); 2843 | } 2844 | 2845 | runner.on('start', function(){ 2846 | stream.write('\n '); 2847 | cursor.hide(); 2848 | }); 2849 | 2850 | runner.on('test end', function(test){ 2851 | // check if the plane crashed 2852 | var col = -1 == crashed 2853 | ? width * ++n / total | 0 2854 | : crashed; 2855 | 2856 | // show the crash 2857 | if ('failed' == test.state) { 2858 | plane = color('plane crash', '✈'); 2859 | crashed = col; 2860 | } 2861 | 2862 | // render landing strip 2863 | stream.write('\u001b[4F\n\n'); 2864 | stream.write(runway()); 2865 | stream.write('\n '); 2866 | stream.write(color('runway', Array(col).join('⋅'))); 2867 | stream.write(plane) 2868 | stream.write(color('runway', Array(width - col).join('⋅') + '\n')); 2869 | stream.write(runway()); 2870 | stream.write('\u001b[0m'); 2871 | }); 2872 | 2873 | runner.on('end', function(){ 2874 | cursor.show(); 2875 | console.log(); 2876 | self.epilogue(); 2877 | }); 2878 | } 2879 | 2880 | /** 2881 | * Inherit from `Base.prototype`. 2882 | */ 2883 | 2884 | function F(){}; 2885 | F.prototype = Base.prototype; 2886 | Landing.prototype = new F; 2887 | Landing.prototype.constructor = Landing; 2888 | 2889 | }); // module: reporters/landing.js 2890 | 2891 | require.register("reporters/list.js", function(module, exports, require){ 2892 | 2893 | /** 2894 | * Module dependencies. 2895 | */ 2896 | 2897 | var Base = require('./base') 2898 | , cursor = Base.cursor 2899 | , color = Base.color; 2900 | 2901 | /** 2902 | * Expose `List`. 2903 | */ 2904 | 2905 | exports = module.exports = List; 2906 | 2907 | /** 2908 | * Initialize a new `List` test reporter. 2909 | * 2910 | * @param {Runner} runner 2911 | * @api public 2912 | */ 2913 | 2914 | function List(runner) { 2915 | Base.call(this, runner); 2916 | 2917 | var self = this 2918 | , stats = this.stats 2919 | , n = 0; 2920 | 2921 | runner.on('start', function(){ 2922 | console.log(); 2923 | }); 2924 | 2925 | runner.on('test', function(test){ 2926 | process.stdout.write(color('pass', ' ' + test.fullTitle() + ': ')); 2927 | }); 2928 | 2929 | runner.on('pending', function(test){ 2930 | var fmt = color('checkmark', ' -') 2931 | + color('pending', ' %s'); 2932 | console.log(fmt, test.fullTitle()); 2933 | }); 2934 | 2935 | runner.on('pass', function(test){ 2936 | var fmt = color('checkmark', ' '+Base.symbols.dot) 2937 | + color('pass', ' %s: ') 2938 | + color(test.speed, '%dms'); 2939 | cursor.CR(); 2940 | console.log(fmt, test.fullTitle(), test.duration); 2941 | }); 2942 | 2943 | runner.on('fail', function(test, err){ 2944 | cursor.CR(); 2945 | console.log(color('fail', ' %d) %s'), ++n, test.fullTitle()); 2946 | }); 2947 | 2948 | runner.on('end', self.epilogue.bind(self)); 2949 | } 2950 | 2951 | /** 2952 | * Inherit from `Base.prototype`. 2953 | */ 2954 | 2955 | function F(){}; 2956 | F.prototype = Base.prototype; 2957 | List.prototype = new F; 2958 | List.prototype.constructor = List; 2959 | 2960 | 2961 | }); // module: reporters/list.js 2962 | 2963 | require.register("reporters/markdown.js", function(module, exports, require){ 2964 | /** 2965 | * Module dependencies. 2966 | */ 2967 | 2968 | var Base = require('./base') 2969 | , utils = require('../utils'); 2970 | 2971 | /** 2972 | * Expose `Markdown`. 2973 | */ 2974 | 2975 | exports = module.exports = Markdown; 2976 | 2977 | /** 2978 | * Initialize a new `Markdown` reporter. 2979 | * 2980 | * @param {Runner} runner 2981 | * @api public 2982 | */ 2983 | 2984 | function Markdown(runner) { 2985 | Base.call(this, runner); 2986 | 2987 | var self = this 2988 | , stats = this.stats 2989 | , level = 0 2990 | , buf = ''; 2991 | 2992 | function title(str) { 2993 | return Array(level).join('#') + ' ' + str; 2994 | } 2995 | 2996 | function indent() { 2997 | return Array(level).join(' '); 2998 | } 2999 | 3000 | function mapTOC(suite, obj) { 3001 | var ret = obj; 3002 | obj = obj[suite.title] = obj[suite.title] || { suite: suite }; 3003 | suite.suites.forEach(function(suite){ 3004 | mapTOC(suite, obj); 3005 | }); 3006 | return ret; 3007 | } 3008 | 3009 | function stringifyTOC(obj, level) { 3010 | ++level; 3011 | var buf = ''; 3012 | var link; 3013 | for (var key in obj) { 3014 | if ('suite' == key) continue; 3015 | if (key) link = ' - [' + key + '](#' + utils.slug(obj[key].suite.fullTitle()) + ')\n'; 3016 | if (key) buf += Array(level).join(' ') + link; 3017 | buf += stringifyTOC(obj[key], level); 3018 | } 3019 | --level; 3020 | return buf; 3021 | } 3022 | 3023 | function generateTOC(suite) { 3024 | var obj = mapTOC(suite, {}); 3025 | return stringifyTOC(obj, 0); 3026 | } 3027 | 3028 | generateTOC(runner.suite); 3029 | 3030 | runner.on('suite', function(suite){ 3031 | ++level; 3032 | var slug = utils.slug(suite.fullTitle()); 3033 | buf += '' + '\n'; 3034 | buf += title(suite.title) + '\n'; 3035 | }); 3036 | 3037 | runner.on('suite end', function(suite){ 3038 | --level; 3039 | }); 3040 | 3041 | runner.on('pass', function(test){ 3042 | var code = utils.clean(test.fn.toString()); 3043 | buf += test.title + '.\n'; 3044 | buf += '\n```js\n'; 3045 | buf += code + '\n'; 3046 | buf += '```\n\n'; 3047 | }); 3048 | 3049 | runner.on('end', function(){ 3050 | process.stdout.write('# TOC\n'); 3051 | process.stdout.write(generateTOC(runner.suite)); 3052 | process.stdout.write(buf); 3053 | }); 3054 | } 3055 | }); // module: reporters/markdown.js 3056 | 3057 | require.register("reporters/min.js", function(module, exports, require){ 3058 | 3059 | /** 3060 | * Module dependencies. 3061 | */ 3062 | 3063 | var Base = require('./base'); 3064 | 3065 | /** 3066 | * Expose `Min`. 3067 | */ 3068 | 3069 | exports = module.exports = Min; 3070 | 3071 | /** 3072 | * Initialize a new `Min` minimal test reporter (best used with --watch). 3073 | * 3074 | * @param {Runner} runner 3075 | * @api public 3076 | */ 3077 | 3078 | function Min(runner) { 3079 | Base.call(this, runner); 3080 | 3081 | runner.on('start', function(){ 3082 | // clear screen 3083 | process.stdout.write('\u001b[2J'); 3084 | // set cursor position 3085 | process.stdout.write('\u001b[1;3H'); 3086 | }); 3087 | 3088 | runner.on('end', this.epilogue.bind(this)); 3089 | } 3090 | 3091 | /** 3092 | * Inherit from `Base.prototype`. 3093 | */ 3094 | 3095 | function F(){}; 3096 | F.prototype = Base.prototype; 3097 | Min.prototype = new F; 3098 | Min.prototype.constructor = Min; 3099 | 3100 | 3101 | }); // module: reporters/min.js 3102 | 3103 | require.register("reporters/nyan.js", function(module, exports, require){ 3104 | /** 3105 | * Module dependencies. 3106 | */ 3107 | 3108 | var Base = require('./base') 3109 | , color = Base.color; 3110 | 3111 | /** 3112 | * Expose `Dot`. 3113 | */ 3114 | 3115 | exports = module.exports = NyanCat; 3116 | 3117 | /** 3118 | * Initialize a new `Dot` matrix test reporter. 3119 | * 3120 | * @param {Runner} runner 3121 | * @api public 3122 | */ 3123 | 3124 | function NyanCat(runner) { 3125 | Base.call(this, runner); 3126 | 3127 | var self = this 3128 | , stats = this.stats 3129 | , width = Base.window.width * .75 | 0 3130 | , rainbowColors = this.rainbowColors = self.generateColors() 3131 | , colorIndex = this.colorIndex = 0 3132 | , numerOfLines = this.numberOfLines = 4 3133 | , trajectories = this.trajectories = [[], [], [], []] 3134 | , nyanCatWidth = this.nyanCatWidth = 11 3135 | , trajectoryWidthMax = this.trajectoryWidthMax = (width - nyanCatWidth) 3136 | , scoreboardWidth = this.scoreboardWidth = 5 3137 | , tick = this.tick = 0 3138 | , n = 0; 3139 | 3140 | runner.on('start', function(){ 3141 | Base.cursor.hide(); 3142 | self.draw('start'); 3143 | }); 3144 | 3145 | runner.on('pending', function(test){ 3146 | self.draw('pending'); 3147 | }); 3148 | 3149 | runner.on('pass', function(test){ 3150 | self.draw('pass'); 3151 | }); 3152 | 3153 | runner.on('fail', function(test, err){ 3154 | self.draw('fail'); 3155 | }); 3156 | 3157 | runner.on('end', function(){ 3158 | Base.cursor.show(); 3159 | for (var i = 0; i < self.numberOfLines; i++) write('\n'); 3160 | self.epilogue(); 3161 | }); 3162 | } 3163 | 3164 | /** 3165 | * Draw the nyan cat with runner `status`. 3166 | * 3167 | * @param {String} status 3168 | * @api private 3169 | */ 3170 | 3171 | NyanCat.prototype.draw = function(status){ 3172 | this.appendRainbow(); 3173 | this.drawScoreboard(); 3174 | this.drawRainbow(); 3175 | this.drawNyanCat(status); 3176 | this.tick = !this.tick; 3177 | }; 3178 | 3179 | /** 3180 | * Draw the "scoreboard" showing the number 3181 | * of passes, failures and pending tests. 3182 | * 3183 | * @api private 3184 | */ 3185 | 3186 | NyanCat.prototype.drawScoreboard = function(){ 3187 | var stats = this.stats; 3188 | var colors = Base.colors; 3189 | 3190 | function draw(color, n) { 3191 | write(' '); 3192 | write('\u001b[' + color + 'm' + n + '\u001b[0m'); 3193 | write('\n'); 3194 | } 3195 | 3196 | draw(colors.green, stats.passes); 3197 | draw(colors.fail, stats.failures); 3198 | draw(colors.pending, stats.pending); 3199 | write('\n'); 3200 | 3201 | this.cursorUp(this.numberOfLines); 3202 | }; 3203 | 3204 | /** 3205 | * Append the rainbow. 3206 | * 3207 | * @api private 3208 | */ 3209 | 3210 | NyanCat.prototype.appendRainbow = function(){ 3211 | var segment = this.tick ? '_' : '-'; 3212 | var rainbowified = this.rainbowify(segment); 3213 | 3214 | for (var index = 0; index < this.numberOfLines; index++) { 3215 | var trajectory = this.trajectories[index]; 3216 | if (trajectory.length >= this.trajectoryWidthMax) trajectory.shift(); 3217 | trajectory.push(rainbowified); 3218 | } 3219 | }; 3220 | 3221 | /** 3222 | * Draw the rainbow. 3223 | * 3224 | * @api private 3225 | */ 3226 | 3227 | NyanCat.prototype.drawRainbow = function(){ 3228 | var self = this; 3229 | 3230 | this.trajectories.forEach(function(line, index) { 3231 | write('\u001b[' + self.scoreboardWidth + 'C'); 3232 | write(line.join('')); 3233 | write('\n'); 3234 | }); 3235 | 3236 | this.cursorUp(this.numberOfLines); 3237 | }; 3238 | 3239 | /** 3240 | * Draw the nyan cat with `status`. 3241 | * 3242 | * @param {String} status 3243 | * @api private 3244 | */ 3245 | 3246 | NyanCat.prototype.drawNyanCat = function(status) { 3247 | var self = this; 3248 | var startWidth = this.scoreboardWidth + this.trajectories[0].length; 3249 | var color = '\u001b[' + startWidth + 'C'; 3250 | var padding = ''; 3251 | 3252 | write(color); 3253 | write('_,------,'); 3254 | write('\n'); 3255 | 3256 | write(color); 3257 | padding = self.tick ? ' ' : ' '; 3258 | write('_|' + padding + '/\\_/\\ '); 3259 | write('\n'); 3260 | 3261 | write(color); 3262 | padding = self.tick ? '_' : '__'; 3263 | var tail = self.tick ? '~' : '^'; 3264 | var face; 3265 | switch (status) { 3266 | case 'pass': 3267 | face = '( ^ .^)'; 3268 | break; 3269 | case 'fail': 3270 | face = '( o .o)'; 3271 | break; 3272 | default: 3273 | face = '( - .-)'; 3274 | } 3275 | write(tail + '|' + padding + face + ' '); 3276 | write('\n'); 3277 | 3278 | write(color); 3279 | padding = self.tick ? ' ' : ' '; 3280 | write(padding + '"" "" '); 3281 | write('\n'); 3282 | 3283 | this.cursorUp(this.numberOfLines); 3284 | }; 3285 | 3286 | /** 3287 | * Move cursor up `n`. 3288 | * 3289 | * @param {Number} n 3290 | * @api private 3291 | */ 3292 | 3293 | NyanCat.prototype.cursorUp = function(n) { 3294 | write('\u001b[' + n + 'A'); 3295 | }; 3296 | 3297 | /** 3298 | * Move cursor down `n`. 3299 | * 3300 | * @param {Number} n 3301 | * @api private 3302 | */ 3303 | 3304 | NyanCat.prototype.cursorDown = function(n) { 3305 | write('\u001b[' + n + 'B'); 3306 | }; 3307 | 3308 | /** 3309 | * Generate rainbow colors. 3310 | * 3311 | * @return {Array} 3312 | * @api private 3313 | */ 3314 | 3315 | NyanCat.prototype.generateColors = function(){ 3316 | var colors = []; 3317 | 3318 | for (var i = 0; i < (6 * 7); i++) { 3319 | var pi3 = Math.floor(Math.PI / 3); 3320 | var n = (i * (1.0 / 6)); 3321 | var r = Math.floor(3 * Math.sin(n) + 3); 3322 | var g = Math.floor(3 * Math.sin(n + 2 * pi3) + 3); 3323 | var b = Math.floor(3 * Math.sin(n + 4 * pi3) + 3); 3324 | colors.push(36 * r + 6 * g + b + 16); 3325 | } 3326 | 3327 | return colors; 3328 | }; 3329 | 3330 | /** 3331 | * Apply rainbow to the given `str`. 3332 | * 3333 | * @param {String} str 3334 | * @return {String} 3335 | * @api private 3336 | */ 3337 | 3338 | NyanCat.prototype.rainbowify = function(str){ 3339 | var color = this.rainbowColors[this.colorIndex % this.rainbowColors.length]; 3340 | this.colorIndex += 1; 3341 | return '\u001b[38;5;' + color + 'm' + str + '\u001b[0m'; 3342 | }; 3343 | 3344 | /** 3345 | * Stdout helper. 3346 | */ 3347 | 3348 | function write(string) { 3349 | process.stdout.write(string); 3350 | } 3351 | 3352 | /** 3353 | * Inherit from `Base.prototype`. 3354 | */ 3355 | 3356 | function F(){}; 3357 | F.prototype = Base.prototype; 3358 | NyanCat.prototype = new F; 3359 | NyanCat.prototype.constructor = NyanCat; 3360 | 3361 | 3362 | }); // module: reporters/nyan.js 3363 | 3364 | require.register("reporters/progress.js", function(module, exports, require){ 3365 | 3366 | /** 3367 | * Module dependencies. 3368 | */ 3369 | 3370 | var Base = require('./base') 3371 | , cursor = Base.cursor 3372 | , color = Base.color; 3373 | 3374 | /** 3375 | * Expose `Progress`. 3376 | */ 3377 | 3378 | exports = module.exports = Progress; 3379 | 3380 | /** 3381 | * General progress bar color. 3382 | */ 3383 | 3384 | Base.colors.progress = 90; 3385 | 3386 | /** 3387 | * Initialize a new `Progress` bar test reporter. 3388 | * 3389 | * @param {Runner} runner 3390 | * @param {Object} options 3391 | * @api public 3392 | */ 3393 | 3394 | function Progress(runner, options) { 3395 | Base.call(this, runner); 3396 | 3397 | var self = this 3398 | , options = options || {} 3399 | , stats = this.stats 3400 | , width = Base.window.width * .50 | 0 3401 | , total = runner.total 3402 | , complete = 0 3403 | , max = Math.max; 3404 | 3405 | // default chars 3406 | options.open = options.open || '['; 3407 | options.complete = options.complete || '▬'; 3408 | options.incomplete = options.incomplete || Base.symbols.dot; 3409 | options.close = options.close || ']'; 3410 | options.verbose = false; 3411 | 3412 | // tests started 3413 | runner.on('start', function(){ 3414 | console.log(); 3415 | cursor.hide(); 3416 | }); 3417 | 3418 | // tests complete 3419 | runner.on('test end', function(){ 3420 | complete++; 3421 | var incomplete = total - complete 3422 | , percent = complete / total 3423 | , n = width * percent | 0 3424 | , i = width - n; 3425 | 3426 | cursor.CR(); 3427 | process.stdout.write('\u001b[J'); 3428 | process.stdout.write(color('progress', ' ' + options.open)); 3429 | process.stdout.write(Array(n).join(options.complete)); 3430 | process.stdout.write(Array(i).join(options.incomplete)); 3431 | process.stdout.write(color('progress', options.close)); 3432 | if (options.verbose) { 3433 | process.stdout.write(color('progress', ' ' + complete + ' of ' + total)); 3434 | } 3435 | }); 3436 | 3437 | // tests are complete, output some stats 3438 | // and the failures if any 3439 | runner.on('end', function(){ 3440 | cursor.show(); 3441 | console.log(); 3442 | self.epilogue(); 3443 | }); 3444 | } 3445 | 3446 | /** 3447 | * Inherit from `Base.prototype`. 3448 | */ 3449 | 3450 | function F(){}; 3451 | F.prototype = Base.prototype; 3452 | Progress.prototype = new F; 3453 | Progress.prototype.constructor = Progress; 3454 | 3455 | 3456 | }); // module: reporters/progress.js 3457 | 3458 | require.register("reporters/spec.js", function(module, exports, require){ 3459 | 3460 | /** 3461 | * Module dependencies. 3462 | */ 3463 | 3464 | var Base = require('./base') 3465 | , cursor = Base.cursor 3466 | , color = Base.color; 3467 | 3468 | /** 3469 | * Expose `Spec`. 3470 | */ 3471 | 3472 | exports = module.exports = Spec; 3473 | 3474 | /** 3475 | * Initialize a new `Spec` test reporter. 3476 | * 3477 | * @param {Runner} runner 3478 | * @api public 3479 | */ 3480 | 3481 | function Spec(runner) { 3482 | Base.call(this, runner); 3483 | 3484 | var self = this 3485 | , stats = this.stats 3486 | , indents = 0 3487 | , n = 0; 3488 | 3489 | function indent() { 3490 | return Array(indents).join(' ') 3491 | } 3492 | 3493 | runner.on('start', function(){ 3494 | console.log(); 3495 | }); 3496 | 3497 | runner.on('suite', function(suite){ 3498 | ++indents; 3499 | console.log(color('suite', '%s%s'), indent(), suite.title); 3500 | }); 3501 | 3502 | runner.on('suite end', function(suite){ 3503 | --indents; 3504 | if (1 == indents) console.log(); 3505 | }); 3506 | 3507 | runner.on('test', function(test){ 3508 | process.stdout.write(indent() + color('pass', ' ◦ ' + test.title + ': ')); 3509 | }); 3510 | 3511 | runner.on('pending', function(test){ 3512 | var fmt = indent() + color('pending', ' - %s'); 3513 | console.log(fmt, test.title); 3514 | }); 3515 | 3516 | runner.on('pass', function(test){ 3517 | if ('fast' == test.speed) { 3518 | var fmt = indent() 3519 | + color('checkmark', ' ' + Base.symbols.ok) 3520 | + color('pass', ' %s '); 3521 | cursor.CR(); 3522 | console.log(fmt, test.title); 3523 | } else { 3524 | var fmt = indent() 3525 | + color('checkmark', ' ' + Base.symbols.ok) 3526 | + color('pass', ' %s ') 3527 | + color(test.speed, '(%dms)'); 3528 | cursor.CR(); 3529 | console.log(fmt, test.title, test.duration); 3530 | } 3531 | }); 3532 | 3533 | runner.on('fail', function(test, err){ 3534 | cursor.CR(); 3535 | console.log(indent() + color('fail', ' %d) %s'), ++n, test.title); 3536 | }); 3537 | 3538 | runner.on('end', self.epilogue.bind(self)); 3539 | } 3540 | 3541 | /** 3542 | * Inherit from `Base.prototype`. 3543 | */ 3544 | 3545 | function F(){}; 3546 | F.prototype = Base.prototype; 3547 | Spec.prototype = new F; 3548 | Spec.prototype.constructor = Spec; 3549 | 3550 | 3551 | }); // module: reporters/spec.js 3552 | 3553 | require.register("reporters/tap.js", function(module, exports, require){ 3554 | 3555 | /** 3556 | * Module dependencies. 3557 | */ 3558 | 3559 | var Base = require('./base') 3560 | , cursor = Base.cursor 3561 | , color = Base.color; 3562 | 3563 | /** 3564 | * Expose `TAP`. 3565 | */ 3566 | 3567 | exports = module.exports = TAP; 3568 | 3569 | /** 3570 | * Initialize a new `TAP` reporter. 3571 | * 3572 | * @param {Runner} runner 3573 | * @api public 3574 | */ 3575 | 3576 | function TAP(runner) { 3577 | Base.call(this, runner); 3578 | 3579 | var self = this 3580 | , stats = this.stats 3581 | , n = 1 3582 | , passes = 0 3583 | , failures = 0; 3584 | 3585 | runner.on('start', function(){ 3586 | var total = runner.grepTotal(runner.suite); 3587 | console.log('%d..%d', 1, total); 3588 | }); 3589 | 3590 | runner.on('test end', function(){ 3591 | ++n; 3592 | }); 3593 | 3594 | runner.on('pending', function(test){ 3595 | console.log('ok %d %s # SKIP -', n, title(test)); 3596 | }); 3597 | 3598 | runner.on('pass', function(test){ 3599 | passes++; 3600 | console.log('ok %d %s', n, title(test)); 3601 | }); 3602 | 3603 | runner.on('fail', function(test, err){ 3604 | failures++; 3605 | console.log('not ok %d %s', n, title(test)); 3606 | if (err.stack) console.log(err.stack.replace(/^/gm, ' ')); 3607 | }); 3608 | 3609 | runner.on('end', function(){ 3610 | console.log('# tests ' + (passes + failures)); 3611 | console.log('# pass ' + passes); 3612 | console.log('# fail ' + failures); 3613 | }); 3614 | } 3615 | 3616 | /** 3617 | * Return a TAP-safe title of `test` 3618 | * 3619 | * @param {Object} test 3620 | * @return {String} 3621 | * @api private 3622 | */ 3623 | 3624 | function title(test) { 3625 | return test.fullTitle().replace(/#/g, ''); 3626 | } 3627 | 3628 | }); // module: reporters/tap.js 3629 | 3630 | require.register("reporters/teamcity.js", function(module, exports, require){ 3631 | 3632 | /** 3633 | * Module dependencies. 3634 | */ 3635 | 3636 | var Base = require('./base'); 3637 | 3638 | /** 3639 | * Expose `Teamcity`. 3640 | */ 3641 | 3642 | exports = module.exports = Teamcity; 3643 | 3644 | /** 3645 | * Initialize a new `Teamcity` reporter. 3646 | * 3647 | * @param {Runner} runner 3648 | * @api public 3649 | */ 3650 | 3651 | function Teamcity(runner) { 3652 | Base.call(this, runner); 3653 | var stats = this.stats; 3654 | 3655 | runner.on('start', function() { 3656 | console.log("##teamcity[testSuiteStarted name='mocha.suite']"); 3657 | }); 3658 | 3659 | runner.on('test', function(test) { 3660 | console.log("##teamcity[testStarted name='" + escape(test.fullTitle()) + "']"); 3661 | }); 3662 | 3663 | runner.on('fail', function(test, err) { 3664 | console.log("##teamcity[testFailed name='" + escape(test.fullTitle()) + "' message='" + escape(err.message) + "']"); 3665 | }); 3666 | 3667 | runner.on('pending', function(test) { 3668 | console.log("##teamcity[testIgnored name='" + escape(test.fullTitle()) + "' message='pending']"); 3669 | }); 3670 | 3671 | runner.on('test end', function(test) { 3672 | console.log("##teamcity[testFinished name='" + escape(test.fullTitle()) + "' duration='" + test.duration + "']"); 3673 | }); 3674 | 3675 | runner.on('end', function() { 3676 | console.log("##teamcity[testSuiteFinished name='mocha.suite' duration='" + stats.duration + "']"); 3677 | }); 3678 | } 3679 | 3680 | /** 3681 | * Escape the given `str`. 3682 | */ 3683 | 3684 | function escape(str) { 3685 | return str 3686 | .replace(/\|/g, "||") 3687 | .replace(/\n/g, "|n") 3688 | .replace(/\r/g, "|r") 3689 | .replace(/\[/g, "|[") 3690 | .replace(/\]/g, "|]") 3691 | .replace(/\u0085/g, "|x") 3692 | .replace(/\u2028/g, "|l") 3693 | .replace(/\u2029/g, "|p") 3694 | .replace(/'/g, "|'"); 3695 | } 3696 | 3697 | }); // module: reporters/teamcity.js 3698 | 3699 | require.register("reporters/xunit.js", function(module, exports, require){ 3700 | 3701 | /** 3702 | * Module dependencies. 3703 | */ 3704 | 3705 | var Base = require('./base') 3706 | , utils = require('../utils') 3707 | , escape = utils.escape; 3708 | 3709 | /** 3710 | * Save timer references to avoid Sinon interfering (see GH-237). 3711 | */ 3712 | 3713 | var Date = global.Date 3714 | , setTimeout = global.setTimeout 3715 | , setInterval = global.setInterval 3716 | , clearTimeout = global.clearTimeout 3717 | , clearInterval = global.clearInterval; 3718 | 3719 | /** 3720 | * Expose `XUnit`. 3721 | */ 3722 | 3723 | exports = module.exports = XUnit; 3724 | 3725 | /** 3726 | * Initialize a new `XUnit` reporter. 3727 | * 3728 | * @param {Runner} runner 3729 | * @api public 3730 | */ 3731 | 3732 | function XUnit(runner) { 3733 | Base.call(this, runner); 3734 | var stats = this.stats 3735 | , tests = [] 3736 | , self = this; 3737 | 3738 | runner.on('pass', function(test){ 3739 | tests.push(test); 3740 | }); 3741 | 3742 | runner.on('fail', function(test){ 3743 | tests.push(test); 3744 | }); 3745 | 3746 | runner.on('end', function(){ 3747 | console.log(tag('testsuite', { 3748 | name: 'Mocha Tests' 3749 | , tests: stats.tests 3750 | , failures: stats.failures 3751 | , errors: stats.failures 3752 | , skip: stats.tests - stats.failures - stats.passes 3753 | , timestamp: (new Date).toUTCString() 3754 | , time: stats.duration / 1000 3755 | }, false)); 3756 | 3757 | tests.forEach(test); 3758 | console.log(''); 3759 | }); 3760 | } 3761 | 3762 | /** 3763 | * Inherit from `Base.prototype`. 3764 | */ 3765 | 3766 | function F(){}; 3767 | F.prototype = Base.prototype; 3768 | XUnit.prototype = new F; 3769 | XUnit.prototype.constructor = XUnit; 3770 | 3771 | 3772 | /** 3773 | * Output tag for the given `test.` 3774 | */ 3775 | 3776 | function test(test) { 3777 | var attrs = { 3778 | classname: test.parent.fullTitle() 3779 | , name: test.title 3780 | , time: test.duration / 1000 3781 | }; 3782 | 3783 | if ('failed' == test.state) { 3784 | var err = test.err; 3785 | attrs.message = escape(err.message); 3786 | console.log(tag('testcase', attrs, false, tag('failure', attrs, false, cdata(err.stack)))); 3787 | } else if (test.pending) { 3788 | console.log(tag('testcase', attrs, false, tag('skipped', {}, true))); 3789 | } else { 3790 | console.log(tag('testcase', attrs, true) ); 3791 | } 3792 | } 3793 | 3794 | /** 3795 | * HTML tag helper. 3796 | */ 3797 | 3798 | function tag(name, attrs, close, content) { 3799 | var end = close ? '/>' : '>' 3800 | , pairs = [] 3801 | , tag; 3802 | 3803 | for (var key in attrs) { 3804 | pairs.push(key + '="' + escape(attrs[key]) + '"'); 3805 | } 3806 | 3807 | tag = '<' + name + (pairs.length ? ' ' + pairs.join(' ') : '') + end; 3808 | if (content) tag += content + ''; 3818 | } 3819 | 3820 | }); // module: reporters/xunit.js 3821 | 3822 | require.register("runnable.js", function(module, exports, require){ 3823 | 3824 | /** 3825 | * Module dependencies. 3826 | */ 3827 | 3828 | var EventEmitter = require('browser/events').EventEmitter 3829 | , debug = require('browser/debug')('mocha:runnable') 3830 | , milliseconds = require('./ms'); 3831 | 3832 | /** 3833 | * Save timer references to avoid Sinon interfering (see GH-237). 3834 | */ 3835 | 3836 | var Date = global.Date 3837 | , setTimeout = global.setTimeout 3838 | , setInterval = global.setInterval 3839 | , clearTimeout = global.clearTimeout 3840 | , clearInterval = global.clearInterval; 3841 | 3842 | /** 3843 | * Object#toString(). 3844 | */ 3845 | 3846 | var toString = Object.prototype.toString; 3847 | 3848 | /** 3849 | * Expose `Runnable`. 3850 | */ 3851 | 3852 | module.exports = Runnable; 3853 | 3854 | /** 3855 | * Initialize a new `Runnable` with the given `title` and callback `fn`. 3856 | * 3857 | * @param {String} title 3858 | * @param {Function} fn 3859 | * @api private 3860 | */ 3861 | 3862 | function Runnable(title, fn) { 3863 | this.title = title; 3864 | this.fn = fn; 3865 | this.async = fn && fn.length; 3866 | this.sync = ! this.async; 3867 | this._timeout = 2000; 3868 | this._slow = 75; 3869 | this.timedOut = false; 3870 | } 3871 | 3872 | /** 3873 | * Inherit from `EventEmitter.prototype`. 3874 | */ 3875 | 3876 | function F(){}; 3877 | F.prototype = EventEmitter.prototype; 3878 | Runnable.prototype = new F; 3879 | Runnable.prototype.constructor = Runnable; 3880 | 3881 | 3882 | /** 3883 | * Set & get timeout `ms`. 3884 | * 3885 | * @param {Number|String} ms 3886 | * @return {Runnable|Number} ms or self 3887 | * @api private 3888 | */ 3889 | 3890 | Runnable.prototype.timeout = function(ms){ 3891 | if (0 == arguments.length) return this._timeout; 3892 | if ('string' == typeof ms) ms = milliseconds(ms); 3893 | debug('timeout %d', ms); 3894 | this._timeout = ms; 3895 | if (this.timer) this.resetTimeout(); 3896 | return this; 3897 | }; 3898 | 3899 | /** 3900 | * Set & get slow `ms`. 3901 | * 3902 | * @param {Number|String} ms 3903 | * @return {Runnable|Number} ms or self 3904 | * @api private 3905 | */ 3906 | 3907 | Runnable.prototype.slow = function(ms){ 3908 | if (0 === arguments.length) return this._slow; 3909 | if ('string' == typeof ms) ms = milliseconds(ms); 3910 | debug('timeout %d', ms); 3911 | this._slow = ms; 3912 | return this; 3913 | }; 3914 | 3915 | /** 3916 | * Return the full title generated by recursively 3917 | * concatenating the parent's full title. 3918 | * 3919 | * @return {String} 3920 | * @api public 3921 | */ 3922 | 3923 | Runnable.prototype.fullTitle = function(){ 3924 | return this.parent.fullTitle() + ' ' + this.title; 3925 | }; 3926 | 3927 | /** 3928 | * Clear the timeout. 3929 | * 3930 | * @api private 3931 | */ 3932 | 3933 | Runnable.prototype.clearTimeout = function(){ 3934 | clearTimeout(this.timer); 3935 | }; 3936 | 3937 | /** 3938 | * Inspect the runnable void of private properties. 3939 | * 3940 | * @return {String} 3941 | * @api private 3942 | */ 3943 | 3944 | Runnable.prototype.inspect = function(){ 3945 | return JSON.stringify(this, function(key, val){ 3946 | if ('_' == key[0]) return; 3947 | if ('parent' == key) return '#'; 3948 | if ('ctx' == key) return '#'; 3949 | return val; 3950 | }, 2); 3951 | }; 3952 | 3953 | /** 3954 | * Reset the timeout. 3955 | * 3956 | * @api private 3957 | */ 3958 | 3959 | Runnable.prototype.resetTimeout = function(){ 3960 | var self = this 3961 | , ms = this.timeout(); 3962 | 3963 | this.clearTimeout(); 3964 | if (ms) { 3965 | this.timer = setTimeout(function(){ 3966 | self.callback(new Error('timeout of ' + ms + 'ms exceeded')); 3967 | self.timedOut = true; 3968 | }, ms); 3969 | } 3970 | }; 3971 | 3972 | /** 3973 | * Run the test and invoke `fn(err)`. 3974 | * 3975 | * @param {Function} fn 3976 | * @api private 3977 | */ 3978 | 3979 | Runnable.prototype.run = function(fn){ 3980 | var self = this 3981 | , ms = this.timeout() 3982 | , start = new Date 3983 | , ctx = this.ctx 3984 | , finished 3985 | , emitted; 3986 | 3987 | if (ctx) ctx.runnable(this); 3988 | 3989 | // timeout 3990 | if (this.async) { 3991 | if (ms) { 3992 | this.timer = setTimeout(function(){ 3993 | done(new Error('timeout of ' + ms + 'ms exceeded')); 3994 | self.timedOut = true; 3995 | }, ms); 3996 | } 3997 | } 3998 | 3999 | // called multiple times 4000 | function multiple(err) { 4001 | if (emitted) return; 4002 | emitted = true; 4003 | self.emit('error', err || new Error('done() called multiple times')); 4004 | } 4005 | 4006 | // finished 4007 | function done(err) { 4008 | if (self.timedOut) return; 4009 | if (finished) return multiple(err); 4010 | self.clearTimeout(); 4011 | self.duration = new Date - start; 4012 | finished = true; 4013 | fn(err); 4014 | } 4015 | 4016 | // for .resetTimeout() 4017 | this.callback = done; 4018 | 4019 | // async 4020 | if (this.async) { 4021 | try { 4022 | this.fn.call(ctx, function(err){ 4023 | if (err instanceof Error || toString.call(err) === "[object Error]") return done(err); 4024 | if (null != err) return done(new Error('done() invoked with non-Error: ' + err)); 4025 | done(); 4026 | }); 4027 | } catch (err) { 4028 | done(err); 4029 | } 4030 | return; 4031 | } 4032 | 4033 | if (this.asyncOnly) { 4034 | return done(new Error('--async-only option in use without declaring `done()`')); 4035 | } 4036 | 4037 | // sync 4038 | try { 4039 | if (!this.pending) this.fn.call(ctx); 4040 | this.duration = new Date - start; 4041 | fn(); 4042 | } catch (err) { 4043 | fn(err); 4044 | } 4045 | }; 4046 | 4047 | }); // module: runnable.js 4048 | 4049 | require.register("runner.js", function(module, exports, require){ 4050 | 4051 | /** 4052 | * Module dependencies. 4053 | */ 4054 | 4055 | var EventEmitter = require('browser/events').EventEmitter 4056 | , debug = require('browser/debug')('mocha:runner') 4057 | , Test = require('./test') 4058 | , utils = require('./utils') 4059 | , filter = utils.filter 4060 | , keys = utils.keys; 4061 | 4062 | /** 4063 | * Non-enumerable globals. 4064 | */ 4065 | 4066 | var globals = [ 4067 | 'setTimeout', 4068 | 'clearTimeout', 4069 | 'setInterval', 4070 | 'clearInterval', 4071 | 'XMLHttpRequest', 4072 | 'Date' 4073 | ]; 4074 | 4075 | /** 4076 | * Expose `Runner`. 4077 | */ 4078 | 4079 | module.exports = Runner; 4080 | 4081 | /** 4082 | * Initialize a `Runner` for the given `suite`. 4083 | * 4084 | * Events: 4085 | * 4086 | * - `start` execution started 4087 | * - `end` execution complete 4088 | * - `suite` (suite) test suite execution started 4089 | * - `suite end` (suite) all tests (and sub-suites) have finished 4090 | * - `test` (test) test execution started 4091 | * - `test end` (test) test completed 4092 | * - `hook` (hook) hook execution started 4093 | * - `hook end` (hook) hook complete 4094 | * - `pass` (test) test passed 4095 | * - `fail` (test, err) test failed 4096 | * 4097 | * @api public 4098 | */ 4099 | 4100 | function Runner(suite) { 4101 | var self = this; 4102 | this._globals = []; 4103 | this.suite = suite; 4104 | this.total = suite.total(); 4105 | this.failures = 0; 4106 | this.on('test end', function(test){ self.checkGlobals(test); }); 4107 | this.on('hook end', function(hook){ self.checkGlobals(hook); }); 4108 | this.grep(/.*/); 4109 | this.globals(this.globalProps().concat(['errno'])); 4110 | } 4111 | 4112 | /** 4113 | * Wrapper for setImmediate, process.nextTick, or browser polyfill. 4114 | * 4115 | * @param {Function} fn 4116 | * @api private 4117 | */ 4118 | 4119 | Runner.immediately = global.setImmediate || process.nextTick; 4120 | 4121 | /** 4122 | * Inherit from `EventEmitter.prototype`. 4123 | */ 4124 | 4125 | function F(){}; 4126 | F.prototype = EventEmitter.prototype; 4127 | Runner.prototype = new F; 4128 | Runner.prototype.constructor = Runner; 4129 | 4130 | 4131 | /** 4132 | * Run tests with full titles matching `re`. Updates runner.total 4133 | * with number of tests matched. 4134 | * 4135 | * @param {RegExp} re 4136 | * @param {Boolean} invert 4137 | * @return {Runner} for chaining 4138 | * @api public 4139 | */ 4140 | 4141 | Runner.prototype.grep = function(re, invert){ 4142 | debug('grep %s', re); 4143 | this._grep = re; 4144 | this._invert = invert; 4145 | this.total = this.grepTotal(this.suite); 4146 | return this; 4147 | }; 4148 | 4149 | /** 4150 | * Returns the number of tests matching the grep search for the 4151 | * given suite. 4152 | * 4153 | * @param {Suite} suite 4154 | * @return {Number} 4155 | * @api public 4156 | */ 4157 | 4158 | Runner.prototype.grepTotal = function(suite) { 4159 | var self = this; 4160 | var total = 0; 4161 | 4162 | suite.eachTest(function(test){ 4163 | var match = self._grep.test(test.fullTitle()); 4164 | if (self._invert) match = !match; 4165 | if (match) total++; 4166 | }); 4167 | 4168 | return total; 4169 | }; 4170 | 4171 | /** 4172 | * Return a list of global properties. 4173 | * 4174 | * @return {Array} 4175 | * @api private 4176 | */ 4177 | 4178 | Runner.prototype.globalProps = function() { 4179 | var props = utils.keys(global); 4180 | 4181 | // non-enumerables 4182 | for (var i = 0; i < globals.length; ++i) { 4183 | if (~utils.indexOf(props, globals[i])) continue; 4184 | props.push(globals[i]); 4185 | } 4186 | 4187 | return props; 4188 | }; 4189 | 4190 | /** 4191 | * Allow the given `arr` of globals. 4192 | * 4193 | * @param {Array} arr 4194 | * @return {Runner} for chaining 4195 | * @api public 4196 | */ 4197 | 4198 | Runner.prototype.globals = function(arr){ 4199 | if (0 == arguments.length) return this._globals; 4200 | debug('globals %j', arr); 4201 | utils.forEach(arr, function(arr){ 4202 | this._globals.push(arr); 4203 | }, this); 4204 | return this; 4205 | }; 4206 | 4207 | /** 4208 | * Check for global variable leaks. 4209 | * 4210 | * @api private 4211 | */ 4212 | 4213 | Runner.prototype.checkGlobals = function(test){ 4214 | if (this.ignoreLeaks) return; 4215 | var ok = this._globals; 4216 | var globals = this.globalProps(); 4217 | var isNode = process.kill; 4218 | var leaks; 4219 | 4220 | // check length - 2 ('errno' and 'location' globals) 4221 | if (isNode && 1 == ok.length - globals.length) return 4222 | else if (2 == ok.length - globals.length) return; 4223 | 4224 | leaks = filterLeaks(ok, globals); 4225 | this._globals = this._globals.concat(leaks); 4226 | 4227 | if (leaks.length > 1) { 4228 | this.fail(test, new Error('global leaks detected: ' + leaks.join(', ') + '')); 4229 | } else if (leaks.length) { 4230 | this.fail(test, new Error('global leak detected: ' + leaks[0])); 4231 | } 4232 | }; 4233 | 4234 | /** 4235 | * Fail the given `test`. 4236 | * 4237 | * @param {Test} test 4238 | * @param {Error} err 4239 | * @api private 4240 | */ 4241 | 4242 | Runner.prototype.fail = function(test, err){ 4243 | ++this.failures; 4244 | test.state = 'failed'; 4245 | 4246 | if ('string' == typeof err) { 4247 | err = new Error('the string "' + err + '" was thrown, throw an Error :)'); 4248 | } 4249 | 4250 | this.emit('fail', test, err); 4251 | }; 4252 | 4253 | /** 4254 | * Fail the given `hook` with `err`. 4255 | * 4256 | * Hook failures (currently) hard-end due 4257 | * to that fact that a failing hook will 4258 | * surely cause subsequent tests to fail, 4259 | * causing jumbled reporting. 4260 | * 4261 | * @param {Hook} hook 4262 | * @param {Error} err 4263 | * @api private 4264 | */ 4265 | 4266 | Runner.prototype.failHook = function(hook, err){ 4267 | this.fail(hook, err); 4268 | this.emit('end'); 4269 | }; 4270 | 4271 | /** 4272 | * Run hook `name` callbacks and then invoke `fn()`. 4273 | * 4274 | * @param {String} name 4275 | * @param {Function} function 4276 | * @api private 4277 | */ 4278 | 4279 | Runner.prototype.hook = function(name, fn){ 4280 | var suite = this.suite 4281 | , hooks = suite['_' + name] 4282 | , self = this 4283 | , timer; 4284 | 4285 | function next(i) { 4286 | var hook = hooks[i]; 4287 | if (!hook) return fn(); 4288 | self.currentRunnable = hook; 4289 | 4290 | self.emit('hook', hook); 4291 | 4292 | hook.on('error', function(err){ 4293 | self.failHook(hook, err); 4294 | }); 4295 | 4296 | hook.run(function(err){ 4297 | hook.removeAllListeners('error'); 4298 | var testError = hook.error(); 4299 | if (testError) self.fail(self.test, testError); 4300 | if (err) return self.failHook(hook, err); 4301 | self.emit('hook end', hook); 4302 | next(++i); 4303 | }); 4304 | } 4305 | 4306 | Runner.immediately(function(){ 4307 | next(0); 4308 | }); 4309 | }; 4310 | 4311 | /** 4312 | * Run hook `name` for the given array of `suites` 4313 | * in order, and callback `fn(err)`. 4314 | * 4315 | * @param {String} name 4316 | * @param {Array} suites 4317 | * @param {Function} fn 4318 | * @api private 4319 | */ 4320 | 4321 | Runner.prototype.hooks = function(name, suites, fn){ 4322 | var self = this 4323 | , orig = this.suite; 4324 | 4325 | function next(suite) { 4326 | self.suite = suite; 4327 | 4328 | if (!suite) { 4329 | self.suite = orig; 4330 | return fn(); 4331 | } 4332 | 4333 | self.hook(name, function(err){ 4334 | if (err) { 4335 | self.suite = orig; 4336 | return fn(err); 4337 | } 4338 | 4339 | next(suites.pop()); 4340 | }); 4341 | } 4342 | 4343 | next(suites.pop()); 4344 | }; 4345 | 4346 | /** 4347 | * Run hooks from the top level down. 4348 | * 4349 | * @param {String} name 4350 | * @param {Function} fn 4351 | * @api private 4352 | */ 4353 | 4354 | Runner.prototype.hookUp = function(name, fn){ 4355 | var suites = [this.suite].concat(this.parents()).reverse(); 4356 | this.hooks(name, suites, fn); 4357 | }; 4358 | 4359 | /** 4360 | * Run hooks from the bottom up. 4361 | * 4362 | * @param {String} name 4363 | * @param {Function} fn 4364 | * @api private 4365 | */ 4366 | 4367 | Runner.prototype.hookDown = function(name, fn){ 4368 | var suites = [this.suite].concat(this.parents()); 4369 | this.hooks(name, suites, fn); 4370 | }; 4371 | 4372 | /** 4373 | * Return an array of parent Suites from 4374 | * closest to furthest. 4375 | * 4376 | * @return {Array} 4377 | * @api private 4378 | */ 4379 | 4380 | Runner.prototype.parents = function(){ 4381 | var suite = this.suite 4382 | , suites = []; 4383 | while (suite = suite.parent) suites.push(suite); 4384 | return suites; 4385 | }; 4386 | 4387 | /** 4388 | * Run the current test and callback `fn(err)`. 4389 | * 4390 | * @param {Function} fn 4391 | * @api private 4392 | */ 4393 | 4394 | Runner.prototype.runTest = function(fn){ 4395 | var test = this.test 4396 | , self = this; 4397 | 4398 | if (this.asyncOnly) test.asyncOnly = true; 4399 | 4400 | try { 4401 | test.on('error', function(err){ 4402 | self.fail(test, err); 4403 | }); 4404 | test.run(fn); 4405 | } catch (err) { 4406 | fn(err); 4407 | } 4408 | }; 4409 | 4410 | /** 4411 | * Run tests in the given `suite` and invoke 4412 | * the callback `fn()` when complete. 4413 | * 4414 | * @param {Suite} suite 4415 | * @param {Function} fn 4416 | * @api private 4417 | */ 4418 | 4419 | Runner.prototype.runTests = function(suite, fn){ 4420 | var self = this 4421 | , tests = suite.tests.slice() 4422 | , test; 4423 | 4424 | function next(err) { 4425 | // if we bail after first err 4426 | if (self.failures && suite._bail) return fn(); 4427 | 4428 | // next test 4429 | test = tests.shift(); 4430 | 4431 | // all done 4432 | if (!test) return fn(); 4433 | 4434 | // grep 4435 | var match = self._grep.test(test.fullTitle()); 4436 | if (self._invert) match = !match; 4437 | if (!match) return next(); 4438 | 4439 | // pending 4440 | if (test.pending) { 4441 | self.emit('pending', test); 4442 | self.emit('test end', test); 4443 | return next(); 4444 | } 4445 | 4446 | // execute test and hook(s) 4447 | self.emit('test', self.test = test); 4448 | self.hookDown('beforeEach', function(){ 4449 | self.currentRunnable = self.test; 4450 | self.runTest(function(err){ 4451 | test = self.test; 4452 | 4453 | if (err) { 4454 | self.fail(test, err); 4455 | self.emit('test end', test); 4456 | return self.hookUp('afterEach', next); 4457 | } 4458 | 4459 | test.state = 'passed'; 4460 | self.emit('pass', test); 4461 | self.emit('test end', test); 4462 | self.hookUp('afterEach', next); 4463 | }); 4464 | }); 4465 | } 4466 | 4467 | this.next = next; 4468 | next(); 4469 | }; 4470 | 4471 | /** 4472 | * Run the given `suite` and invoke the 4473 | * callback `fn()` when complete. 4474 | * 4475 | * @param {Suite} suite 4476 | * @param {Function} fn 4477 | * @api private 4478 | */ 4479 | 4480 | Runner.prototype.runSuite = function(suite, fn){ 4481 | var total = this.grepTotal(suite) 4482 | , self = this 4483 | , i = 0; 4484 | 4485 | debug('run suite %s', suite.fullTitle()); 4486 | 4487 | if (!total) return fn(); 4488 | 4489 | this.emit('suite', this.suite = suite); 4490 | 4491 | function next() { 4492 | var curr = suite.suites[i++]; 4493 | if (!curr) return done(); 4494 | self.runSuite(curr, next); 4495 | } 4496 | 4497 | function done() { 4498 | self.suite = suite; 4499 | self.hook('afterAll', function(){ 4500 | self.emit('suite end', suite); 4501 | fn(); 4502 | }); 4503 | } 4504 | 4505 | this.hook('beforeAll', function(){ 4506 | self.runTests(suite, next); 4507 | }); 4508 | }; 4509 | 4510 | /** 4511 | * Handle uncaught exceptions. 4512 | * 4513 | * @param {Error} err 4514 | * @api private 4515 | */ 4516 | 4517 | Runner.prototype.uncaught = function(err){ 4518 | debug('uncaught exception %s', err.message); 4519 | var runnable = this.currentRunnable; 4520 | if (!runnable || 'failed' == runnable.state) return; 4521 | runnable.clearTimeout(); 4522 | err.uncaught = true; 4523 | this.fail(runnable, err); 4524 | 4525 | // recover from test 4526 | if ('test' == runnable.type) { 4527 | this.emit('test end', runnable); 4528 | this.hookUp('afterEach', this.next); 4529 | return; 4530 | } 4531 | 4532 | // bail on hooks 4533 | this.emit('end'); 4534 | }; 4535 | 4536 | /** 4537 | * Run the root suite and invoke `fn(failures)` 4538 | * on completion. 4539 | * 4540 | * @param {Function} fn 4541 | * @return {Runner} for chaining 4542 | * @api public 4543 | */ 4544 | 4545 | Runner.prototype.run = function(fn){ 4546 | var self = this 4547 | , fn = fn || function(){}; 4548 | 4549 | function uncaught(err){ 4550 | self.uncaught(err); 4551 | } 4552 | 4553 | debug('start'); 4554 | 4555 | // callback 4556 | this.on('end', function(){ 4557 | debug('end'); 4558 | process.removeListener('uncaughtException', uncaught); 4559 | fn(self.failures); 4560 | }); 4561 | 4562 | // run suites 4563 | this.emit('start'); 4564 | this.runSuite(this.suite, function(){ 4565 | debug('finished running'); 4566 | self.emit('end'); 4567 | }); 4568 | 4569 | // uncaught exception 4570 | process.on('uncaughtException', uncaught); 4571 | 4572 | return this; 4573 | }; 4574 | 4575 | /** 4576 | * Filter leaks with the given globals flagged as `ok`. 4577 | * 4578 | * @param {Array} ok 4579 | * @param {Array} globals 4580 | * @return {Array} 4581 | * @api private 4582 | */ 4583 | 4584 | function filterLeaks(ok, globals) { 4585 | return filter(globals, function(key){ 4586 | var matched = filter(ok, function(ok){ 4587 | if (~ok.indexOf('*')) return 0 == key.indexOf(ok.split('*')[0]); 4588 | // Opera and IE expose global variables for HTML element IDs (issue #243) 4589 | if (/^mocha-/.test(key)) return true; 4590 | return key == ok; 4591 | }); 4592 | return matched.length == 0 && (!global.navigator || 'onerror' !== key); 4593 | }); 4594 | } 4595 | 4596 | }); // module: runner.js 4597 | 4598 | require.register("suite.js", function(module, exports, require){ 4599 | 4600 | /** 4601 | * Module dependencies. 4602 | */ 4603 | 4604 | var EventEmitter = require('browser/events').EventEmitter 4605 | , debug = require('browser/debug')('mocha:suite') 4606 | , milliseconds = require('./ms') 4607 | , utils = require('./utils') 4608 | , Hook = require('./hook'); 4609 | 4610 | /** 4611 | * Expose `Suite`. 4612 | */ 4613 | 4614 | exports = module.exports = Suite; 4615 | 4616 | /** 4617 | * Create a new `Suite` with the given `title` 4618 | * and parent `Suite`. When a suite with the 4619 | * same title is already present, that suite 4620 | * is returned to provide nicer reporter 4621 | * and more flexible meta-testing. 4622 | * 4623 | * @param {Suite} parent 4624 | * @param {String} title 4625 | * @return {Suite} 4626 | * @api public 4627 | */ 4628 | 4629 | exports.create = function(parent, title){ 4630 | var suite = new Suite(title, parent.ctx); 4631 | suite.parent = parent; 4632 | if (parent.pending) suite.pending = true; 4633 | title = suite.fullTitle(); 4634 | parent.addSuite(suite); 4635 | return suite; 4636 | }; 4637 | 4638 | /** 4639 | * Initialize a new `Suite` with the given 4640 | * `title` and `ctx`. 4641 | * 4642 | * @param {String} title 4643 | * @param {Context} ctx 4644 | * @api private 4645 | */ 4646 | 4647 | function Suite(title, ctx) { 4648 | this.title = title; 4649 | this.ctx = ctx; 4650 | this.suites = []; 4651 | this.tests = []; 4652 | this.pending = false; 4653 | this._beforeEach = []; 4654 | this._beforeAll = []; 4655 | this._afterEach = []; 4656 | this._afterAll = []; 4657 | this.root = !title; 4658 | this._timeout = 2000; 4659 | this._slow = 75; 4660 | this._bail = false; 4661 | } 4662 | 4663 | /** 4664 | * Inherit from `EventEmitter.prototype`. 4665 | */ 4666 | 4667 | function F(){}; 4668 | F.prototype = EventEmitter.prototype; 4669 | Suite.prototype = new F; 4670 | Suite.prototype.constructor = Suite; 4671 | 4672 | 4673 | /** 4674 | * Return a clone of this `Suite`. 4675 | * 4676 | * @return {Suite} 4677 | * @api private 4678 | */ 4679 | 4680 | Suite.prototype.clone = function(){ 4681 | var suite = new Suite(this.title); 4682 | debug('clone'); 4683 | suite.ctx = this.ctx; 4684 | suite.timeout(this.timeout()); 4685 | suite.slow(this.slow()); 4686 | suite.bail(this.bail()); 4687 | return suite; 4688 | }; 4689 | 4690 | /** 4691 | * Set timeout `ms` or short-hand such as "2s". 4692 | * 4693 | * @param {Number|String} ms 4694 | * @return {Suite|Number} for chaining 4695 | * @api private 4696 | */ 4697 | 4698 | Suite.prototype.timeout = function(ms){ 4699 | if (0 == arguments.length) return this._timeout; 4700 | if ('string' == typeof ms) ms = milliseconds(ms); 4701 | debug('timeout %d', ms); 4702 | this._timeout = parseInt(ms, 10); 4703 | return this; 4704 | }; 4705 | 4706 | /** 4707 | * Set slow `ms` or short-hand such as "2s". 4708 | * 4709 | * @param {Number|String} ms 4710 | * @return {Suite|Number} for chaining 4711 | * @api private 4712 | */ 4713 | 4714 | Suite.prototype.slow = function(ms){ 4715 | if (0 === arguments.length) return this._slow; 4716 | if ('string' == typeof ms) ms = milliseconds(ms); 4717 | debug('slow %d', ms); 4718 | this._slow = ms; 4719 | return this; 4720 | }; 4721 | 4722 | /** 4723 | * Sets whether to bail after first error. 4724 | * 4725 | * @parma {Boolean} bail 4726 | * @return {Suite|Number} for chaining 4727 | * @api private 4728 | */ 4729 | 4730 | Suite.prototype.bail = function(bail){ 4731 | if (0 == arguments.length) return this._bail; 4732 | debug('bail %s', bail); 4733 | this._bail = bail; 4734 | return this; 4735 | }; 4736 | 4737 | /** 4738 | * Run `fn(test[, done])` before running tests. 4739 | * 4740 | * @param {Function} fn 4741 | * @return {Suite} for chaining 4742 | * @api private 4743 | */ 4744 | 4745 | Suite.prototype.beforeAll = function(fn){ 4746 | if (this.pending) return this; 4747 | var hook = new Hook('"before all" hook', fn); 4748 | hook.parent = this; 4749 | hook.timeout(this.timeout()); 4750 | hook.slow(this.slow()); 4751 | hook.ctx = this.ctx; 4752 | this._beforeAll.push(hook); 4753 | this.emit('beforeAll', hook); 4754 | return this; 4755 | }; 4756 | 4757 | /** 4758 | * Run `fn(test[, done])` after running tests. 4759 | * 4760 | * @param {Function} fn 4761 | * @return {Suite} for chaining 4762 | * @api private 4763 | */ 4764 | 4765 | Suite.prototype.afterAll = function(fn){ 4766 | if (this.pending) return this; 4767 | var hook = new Hook('"after all" hook', fn); 4768 | hook.parent = this; 4769 | hook.timeout(this.timeout()); 4770 | hook.slow(this.slow()); 4771 | hook.ctx = this.ctx; 4772 | this._afterAll.push(hook); 4773 | this.emit('afterAll', hook); 4774 | return this; 4775 | }; 4776 | 4777 | /** 4778 | * Run `fn(test[, done])` before each test case. 4779 | * 4780 | * @param {Function} fn 4781 | * @return {Suite} for chaining 4782 | * @api private 4783 | */ 4784 | 4785 | Suite.prototype.beforeEach = function(fn){ 4786 | if (this.pending) return this; 4787 | var hook = new Hook('"before each" hook', fn); 4788 | hook.parent = this; 4789 | hook.timeout(this.timeout()); 4790 | hook.slow(this.slow()); 4791 | hook.ctx = this.ctx; 4792 | this._beforeEach.push(hook); 4793 | this.emit('beforeEach', hook); 4794 | return this; 4795 | }; 4796 | 4797 | /** 4798 | * Run `fn(test[, done])` after each test case. 4799 | * 4800 | * @param {Function} fn 4801 | * @return {Suite} for chaining 4802 | * @api private 4803 | */ 4804 | 4805 | Suite.prototype.afterEach = function(fn){ 4806 | if (this.pending) return this; 4807 | var hook = new Hook('"after each" hook', fn); 4808 | hook.parent = this; 4809 | hook.timeout(this.timeout()); 4810 | hook.slow(this.slow()); 4811 | hook.ctx = this.ctx; 4812 | this._afterEach.push(hook); 4813 | this.emit('afterEach', hook); 4814 | return this; 4815 | }; 4816 | 4817 | /** 4818 | * Add a test `suite`. 4819 | * 4820 | * @param {Suite} suite 4821 | * @return {Suite} for chaining 4822 | * @api private 4823 | */ 4824 | 4825 | Suite.prototype.addSuite = function(suite){ 4826 | suite.parent = this; 4827 | suite.timeout(this.timeout()); 4828 | suite.slow(this.slow()); 4829 | suite.bail(this.bail()); 4830 | this.suites.push(suite); 4831 | this.emit('suite', suite); 4832 | return this; 4833 | }; 4834 | 4835 | /** 4836 | * Add a `test` to this suite. 4837 | * 4838 | * @param {Test} test 4839 | * @return {Suite} for chaining 4840 | * @api private 4841 | */ 4842 | 4843 | Suite.prototype.addTest = function(test){ 4844 | test.parent = this; 4845 | test.timeout(this.timeout()); 4846 | test.slow(this.slow()); 4847 | test.ctx = this.ctx; 4848 | this.tests.push(test); 4849 | this.emit('test', test); 4850 | return this; 4851 | }; 4852 | 4853 | /** 4854 | * Return the full title generated by recursively 4855 | * concatenating the parent's full title. 4856 | * 4857 | * @return {String} 4858 | * @api public 4859 | */ 4860 | 4861 | Suite.prototype.fullTitle = function(){ 4862 | if (this.parent) { 4863 | var full = this.parent.fullTitle(); 4864 | if (full) return full + ' ' + this.title; 4865 | } 4866 | return this.title; 4867 | }; 4868 | 4869 | /** 4870 | * Return the total number of tests. 4871 | * 4872 | * @return {Number} 4873 | * @api public 4874 | */ 4875 | 4876 | Suite.prototype.total = function(){ 4877 | return utils.reduce(this.suites, function(sum, suite){ 4878 | return sum + suite.total(); 4879 | }, 0) + this.tests.length; 4880 | }; 4881 | 4882 | /** 4883 | * Iterates through each suite recursively to find 4884 | * all tests. Applies a function in the format 4885 | * `fn(test)`. 4886 | * 4887 | * @param {Function} fn 4888 | * @return {Suite} 4889 | * @api private 4890 | */ 4891 | 4892 | Suite.prototype.eachTest = function(fn){ 4893 | utils.forEach(this.tests, fn); 4894 | utils.forEach(this.suites, function(suite){ 4895 | suite.eachTest(fn); 4896 | }); 4897 | return this; 4898 | }; 4899 | 4900 | }); // module: suite.js 4901 | 4902 | require.register("test.js", function(module, exports, require){ 4903 | 4904 | /** 4905 | * Module dependencies. 4906 | */ 4907 | 4908 | var Runnable = require('./runnable'); 4909 | 4910 | /** 4911 | * Expose `Test`. 4912 | */ 4913 | 4914 | module.exports = Test; 4915 | 4916 | /** 4917 | * Initialize a new `Test` with the given `title` and callback `fn`. 4918 | * 4919 | * @param {String} title 4920 | * @param {Function} fn 4921 | * @api private 4922 | */ 4923 | 4924 | function Test(title, fn) { 4925 | Runnable.call(this, title, fn); 4926 | this.pending = !fn; 4927 | this.type = 'test'; 4928 | } 4929 | 4930 | /** 4931 | * Inherit from `Runnable.prototype`. 4932 | */ 4933 | 4934 | function F(){}; 4935 | F.prototype = Runnable.prototype; 4936 | Test.prototype = new F; 4937 | Test.prototype.constructor = Test; 4938 | 4939 | 4940 | }); // module: test.js 4941 | 4942 | require.register("utils.js", function(module, exports, require){ 4943 | 4944 | /** 4945 | * Module dependencies. 4946 | */ 4947 | 4948 | var fs = require('browser/fs') 4949 | , path = require('browser/path') 4950 | , join = path.join 4951 | , debug = require('browser/debug')('mocha:watch'); 4952 | 4953 | /** 4954 | * Ignored directories. 4955 | */ 4956 | 4957 | var ignore = ['node_modules', '.git']; 4958 | 4959 | /** 4960 | * Escape special characters in the given string of html. 4961 | * 4962 | * @param {String} html 4963 | * @return {String} 4964 | * @api private 4965 | */ 4966 | 4967 | exports.escape = function(html){ 4968 | return String(html) 4969 | .replace(/&/g, '&') 4970 | .replace(/"/g, '"') 4971 | .replace(//g, '>'); 4973 | }; 4974 | 4975 | /** 4976 | * Array#forEach (<=IE8) 4977 | * 4978 | * @param {Array} array 4979 | * @param {Function} fn 4980 | * @param {Object} scope 4981 | * @api private 4982 | */ 4983 | 4984 | exports.forEach = function(arr, fn, scope){ 4985 | for (var i = 0, l = arr.length; i < l; i++) 4986 | fn.call(scope, arr[i], i); 4987 | }; 4988 | 4989 | /** 4990 | * Array#indexOf (<=IE8) 4991 | * 4992 | * @parma {Array} arr 4993 | * @param {Object} obj to find index of 4994 | * @param {Number} start 4995 | * @api private 4996 | */ 4997 | 4998 | exports.indexOf = function(arr, obj, start){ 4999 | for (var i = start || 0, l = arr.length; i < l; i++) { 5000 | if (arr[i] === obj) 5001 | return i; 5002 | } 5003 | return -1; 5004 | }; 5005 | 5006 | /** 5007 | * Array#reduce (<=IE8) 5008 | * 5009 | * @param {Array} array 5010 | * @param {Function} fn 5011 | * @param {Object} initial value 5012 | * @api private 5013 | */ 5014 | 5015 | exports.reduce = function(arr, fn, val){ 5016 | var rval = val; 5017 | 5018 | for (var i = 0, l = arr.length; i < l; i++) { 5019 | rval = fn(rval, arr[i], i, arr); 5020 | } 5021 | 5022 | return rval; 5023 | }; 5024 | 5025 | /** 5026 | * Array#filter (<=IE8) 5027 | * 5028 | * @param {Array} array 5029 | * @param {Function} fn 5030 | * @api private 5031 | */ 5032 | 5033 | exports.filter = function(arr, fn){ 5034 | var ret = []; 5035 | 5036 | for (var i = 0, l = arr.length; i < l; i++) { 5037 | var val = arr[i]; 5038 | if (fn(val, i, arr)) ret.push(val); 5039 | } 5040 | 5041 | return ret; 5042 | }; 5043 | 5044 | /** 5045 | * Object.keys (<=IE8) 5046 | * 5047 | * @param {Object} obj 5048 | * @return {Array} keys 5049 | * @api private 5050 | */ 5051 | 5052 | exports.keys = Object.keys || function(obj) { 5053 | var keys = [] 5054 | , has = Object.prototype.hasOwnProperty // for `window` on <=IE8 5055 | 5056 | for (var key in obj) { 5057 | if (has.call(obj, key)) { 5058 | keys.push(key); 5059 | } 5060 | } 5061 | 5062 | return keys; 5063 | }; 5064 | 5065 | /** 5066 | * Watch the given `files` for changes 5067 | * and invoke `fn(file)` on modification. 5068 | * 5069 | * @param {Array} files 5070 | * @param {Function} fn 5071 | * @api private 5072 | */ 5073 | 5074 | exports.watch = function(files, fn){ 5075 | var options = { interval: 100 }; 5076 | files.forEach(function(file){ 5077 | debug('file %s', file); 5078 | fs.watchFile(file, options, function(curr, prev){ 5079 | if (prev.mtime < curr.mtime) fn(file); 5080 | }); 5081 | }); 5082 | }; 5083 | 5084 | /** 5085 | * Ignored files. 5086 | */ 5087 | 5088 | function ignored(path){ 5089 | return !~ignore.indexOf(path); 5090 | } 5091 | 5092 | /** 5093 | * Lookup files in the given `dir`. 5094 | * 5095 | * @return {Array} 5096 | * @api private 5097 | */ 5098 | 5099 | exports.files = function(dir, ret){ 5100 | ret = ret || []; 5101 | 5102 | fs.readdirSync(dir) 5103 | .filter(ignored) 5104 | .forEach(function(path){ 5105 | path = join(dir, path); 5106 | if (fs.statSync(path).isDirectory()) { 5107 | exports.files(path, ret); 5108 | } else if (path.match(/\.(js|coffee)$/)) { 5109 | ret.push(path); 5110 | } 5111 | }); 5112 | 5113 | return ret; 5114 | }; 5115 | 5116 | /** 5117 | * Compute a slug from the given `str`. 5118 | * 5119 | * @param {String} str 5120 | * @return {String} 5121 | * @api private 5122 | */ 5123 | 5124 | exports.slug = function(str){ 5125 | return str 5126 | .toLowerCase() 5127 | .replace(/ +/g, '-') 5128 | .replace(/[^-\w]/g, ''); 5129 | }; 5130 | 5131 | /** 5132 | * Strip the function definition from `str`, 5133 | * and re-indent for pre whitespace. 5134 | */ 5135 | 5136 | exports.clean = function(str) { 5137 | str = str 5138 | .replace(/^function *\(.*\) *{/, '') 5139 | .replace(/\s+\}$/, ''); 5140 | 5141 | var spaces = str.match(/^\n?( *)/)[1].length 5142 | , re = new RegExp('^ {' + spaces + '}', 'gm'); 5143 | 5144 | str = str.replace(re, ''); 5145 | 5146 | return exports.trim(str); 5147 | }; 5148 | 5149 | /** 5150 | * Escape regular expression characters in `str`. 5151 | * 5152 | * @param {String} str 5153 | * @return {String} 5154 | * @api private 5155 | */ 5156 | 5157 | exports.escapeRegexp = function(str){ 5158 | return str.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&"); 5159 | }; 5160 | 5161 | /** 5162 | * Trim the given `str`. 5163 | * 5164 | * @param {String} str 5165 | * @return {String} 5166 | * @api private 5167 | */ 5168 | 5169 | exports.trim = function(str){ 5170 | return str.replace(/^\s+|\s+$/g, ''); 5171 | }; 5172 | 5173 | /** 5174 | * Parse the given `qs`. 5175 | * 5176 | * @param {String} qs 5177 | * @return {Object} 5178 | * @api private 5179 | */ 5180 | 5181 | exports.parseQuery = function(qs){ 5182 | return exports.reduce(qs.replace('?', '').split('&'), function(obj, pair){ 5183 | var i = pair.indexOf('=') 5184 | , key = pair.slice(0, i) 5185 | , val = pair.slice(++i); 5186 | 5187 | obj[key] = decodeURIComponent(val); 5188 | return obj; 5189 | }, {}); 5190 | }; 5191 | 5192 | /** 5193 | * Highlight the given string of `js`. 5194 | * 5195 | * @param {String} js 5196 | * @return {String} 5197 | * @api private 5198 | */ 5199 | 5200 | function highlight(js) { 5201 | return js 5202 | .replace(//g, '>') 5204 | .replace(/\/\/(.*)/gm, '//$1') 5205 | .replace(/('.*?')/gm, '$1') 5206 | .replace(/(\d+\.\d+)/gm, '$1') 5207 | .replace(/(\d+)/gm, '$1') 5208 | .replace(/\bnew *(\w+)/gm, 'new $1') 5209 | .replace(/\b(function|new|throw|return|var|if|else)\b/gm, '$1') 5210 | } 5211 | 5212 | /** 5213 | * Highlight the contents of tag `name`. 5214 | * 5215 | * @param {String} name 5216 | * @api private 5217 | */ 5218 | 5219 | exports.highlightTags = function(name) { 5220 | var code = document.getElementsByTagName(name); 5221 | for (var i = 0, len = code.length; i < len; ++i) { 5222 | code[i].innerHTML = highlight(code[i].innerHTML); 5223 | } 5224 | }; 5225 | 5226 | }); // module: utils.js 5227 | /** 5228 | * Node shims. 5229 | * 5230 | * These are meant only to allow 5231 | * mocha.js to run untouched, not 5232 | * to allow running node code in 5233 | * the browser. 5234 | */ 5235 | 5236 | var process = {}; 5237 | process.exit = function(status){}; 5238 | process.stdout = {}; 5239 | global = window; 5240 | 5241 | /** 5242 | * Remove uncaughtException listener. 5243 | */ 5244 | 5245 | process.removeListener = function(e){ 5246 | if ('uncaughtException' == e) { 5247 | window.onerror = null; 5248 | } 5249 | }; 5250 | 5251 | /** 5252 | * Implements uncaughtException listener. 5253 | */ 5254 | 5255 | process.on = function(e, fn){ 5256 | if ('uncaughtException' == e) { 5257 | window.onerror = function(err, url, line){ 5258 | fn(new Error(err + ' (' + url + ':' + line + ')')); 5259 | }; 5260 | } 5261 | }; 5262 | 5263 | // boot 5264 | ;(function(){ 5265 | 5266 | /** 5267 | * Expose mocha. 5268 | */ 5269 | 5270 | var Mocha = window.Mocha = require('mocha'), 5271 | mocha = window.mocha = new Mocha({ reporter: 'html' }); 5272 | 5273 | var immediateQueue = [] 5274 | , immediateTimeout; 5275 | 5276 | function timeslice() { 5277 | var immediateStart = new Date().getTime(); 5278 | while (immediateQueue.length && (new Date().getTime() - immediateStart) < 100) { 5279 | immediateQueue.shift()(); 5280 | } 5281 | if (immediateQueue.length) { 5282 | immediateTimeout = setTimeout(timeslice, 0); 5283 | } else { 5284 | immediateTimeout = null; 5285 | } 5286 | } 5287 | 5288 | /** 5289 | * High-performance override of Runner.immediately. 5290 | */ 5291 | 5292 | Mocha.Runner.immediately = function(callback) { 5293 | immediateQueue.push(callback); 5294 | if (!immediateTimeout) { 5295 | immediateTimeout = setTimeout(timeslice, 0); 5296 | } 5297 | }; 5298 | 5299 | /** 5300 | * Override ui to ensure that the ui functions are initialized. 5301 | * Normally this would happen in Mocha.prototype.loadFiles. 5302 | */ 5303 | 5304 | mocha.ui = function(ui){ 5305 | Mocha.prototype.ui.call(this, ui); 5306 | this.suite.emit('pre-require', window, null, this); 5307 | return this; 5308 | }; 5309 | 5310 | /** 5311 | * Setup mocha with the given setting options. 5312 | */ 5313 | 5314 | mocha.setup = function(opts){ 5315 | if ('string' == typeof opts) opts = { ui: opts }; 5316 | for (var opt in opts) this[opt](opts[opt]); 5317 | return this; 5318 | }; 5319 | 5320 | /** 5321 | * Run mocha, returning the Runner. 5322 | */ 5323 | 5324 | mocha.run = function(fn){ 5325 | var options = mocha.options; 5326 | mocha.globals('location'); 5327 | 5328 | var query = Mocha.utils.parseQuery(window.location.search || ''); 5329 | if (query.grep) mocha.grep(query.grep); 5330 | if (query.invert) mocha.invert(); 5331 | 5332 | return Mocha.prototype.run.call(mocha, function(){ 5333 | Mocha.utils.highlightTags('code'); 5334 | if (fn) fn(); 5335 | }); 5336 | }; 5337 | })(); 5338 | })(); --------------------------------------------------------------------------------